diff --git a/.gitattributes b/.gitattributes index e69de29bb..705308cd0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -0,0 +1,9 @@ +# Android engine and test binaries are required build inputs, but must never +# become ordinary Git blobs. Keep the repository history source-only while +# allowing a full checkout to materialize the existing test environment. +platforms/android/**/*.a filter=lfs diff=lfs merge=lfs -text +platforms/android/**/*.aar filter=lfs diff=lfs merge=lfs -text +platforms/android/**/*.exe filter=lfs diff=lfs merge=lfs -text +platforms/android/**/*.jar filter=lfs diff=lfs merge=lfs -text +platforms/android/**/*.so filter=lfs diff=lfs merge=lfs -text +platforms/android/tools/astyle filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 627aeb287..027f8871c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: - name: Check FFI backend boundaries run: npm run check:ffi-boundaries + - name: Test React Native package + run: npm run test:react-native + - name: Download V8 run: ./scripts/download_v8.sh @@ -33,7 +36,7 @@ jobs: run: npm run build-libffi - name: Build metadata generator - run: npm run build-metagen + run: METADATA_GENERATOR_ARCHS="$(uname -m)" npm run build-metagen - name: Generate macOS metadata run: npm run metagen macos @@ -52,6 +55,7 @@ jobs: IOS_BUILD_TIMEOUT_MS: "600000" IOS_TEST_TIMEOUT_MS: "600000" IOS_TEST_INACTIVITY_TIMEOUT_MS: "180000" + IOS_LOG_JUNIT: "1" IOS_TEST_VERBOSE_SPECS: "1" IOS_SIMCTL_QUERY_TIMEOUT_MS: "10000" run: npm run test:ios diff --git a/.gitignore b/.gitignore index d8af2d700..60c6630c7 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,8 @@ package-lock.json v8_build .npmrc /Frameworks/ +/.kiro/ +/opencode.json /llvm/ @@ -52,10 +54,10 @@ v8_build .cipd/ # project template -/templates/ios/.build_env_vars.sh -/templates/ios/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/ -/templates/visionos/.build_env_vars.sh -/templates/visionos/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +/platforms/apple/templates/ios/.build_env_vars.sh +/platforms/apple/templates/ios/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/ +/platforms/apple/templates/visionos/.build_env_vars.sh +/platforms/apple/templates/visionos/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist .cache/ @@ -66,11 +68,19 @@ packages/*/metadata-json SwiftBindgen # Generated Objective-C/C dispatch wrappers -NativeScript/ffi/napi/GeneratedSignatureDispatch.inc -NativeScript/ffi/napi/GeneratedSignatureDispatch.inc.stamp +NativeScript/ffi/**/GeneratedSignatureDispatch.inc +NativeScript/ffi/**/GeneratedSignatureDispatch.inc.stamp +NativeScript/ffi/**/GeneratedGsdSignatureDispatch.inc +NativeScript/ffi/**/GeneratedGsdSignatureDispatch.inc.stamp + +# Packaged native framework artifacts +packages/*/NativeScript.xcframework/ # React Native TurboModule package staging packages/react-native/dist/ packages/react-native/ios/vendor/ packages/react-native/metadata/ -packages/react-native/native-api-jsi/ +packages/react-native/native-api/ + +# Rust metadata bundle analyzer build output +metadata-generator/symbol-analyzer/target/ diff --git a/NativeScript/CMakeLists.txt b/NativeScript/CMakeLists.txt index b9386f462..7515ffe29 100644 --- a/NativeScript/CMakeLists.txt +++ b/NativeScript/CMakeLists.txt @@ -20,12 +20,12 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_FLAGS}") # Arguments set(TARGET_PLATFORM "macos" CACHE STRING "Target platform for the Objective-C bridge") set(TARGET_ENGINE "v8" CACHE STRING "Target JS engine for the NativeScript runtime") -set(NS_FFI_BACKEND "auto" CACHE STRING "FFI backend: auto, napi, or direct") +set(NS_FFI_BACKEND "auto" CACHE STRING "FFI backend: auto, napi, v8, jsc, quickjs, or hermes") set(NS_GSD_BACKEND "auto" CACHE STRING "Generated signature dispatch backend: auto, v8, jsc, quickjs, hermes, napi, or none") set(METADATA_SIZE 0 CACHE STRING "Size of embedded metadata in bytes") set(BUILD_CLI_BINARY OFF CACHE BOOL "Build the NativeScript CLI binary") set(BUILD_MACOS_NODE_API OFF CACHE BOOL "Build the NativeScript macOS Node API dylib") -set_property(CACHE NS_FFI_BACKEND PROPERTY STRINGS auto napi direct) +set_property(CACHE NS_FFI_BACKEND PROPERTY STRINGS auto napi v8 jsc quickjs hermes) set_property(CACHE NS_GSD_BACKEND PROPERTY STRINGS auto v8 jsc quickjs hermes napi none) if (BUILD_MACOS_NODE_API) @@ -139,36 +139,46 @@ message(STATUS "GENERIC_NAPI = ${GENERIC_NAPI}") if(NS_FFI_BACKEND STREQUAL "auto") if(GENERIC_NAPI OR TARGET_ENGINE_NONE) set(NS_EFFECTIVE_FFI_BACKEND "napi") - elseif(TARGET_ENGINE_HERMES OR TARGET_ENGINE_V8 OR TARGET_ENGINE_JSC OR TARGET_ENGINE_QUICKJS) - set(NS_EFFECTIVE_FFI_BACKEND "direct") + elseif(TARGET_ENGINE_HERMES) + set(NS_EFFECTIVE_FFI_BACKEND "hermes") + elseif(TARGET_ENGINE_V8) + set(NS_EFFECTIVE_FFI_BACKEND "v8") + elseif(TARGET_ENGINE_JSC) + set(NS_EFFECTIVE_FFI_BACKEND "jsc") + elseif(TARGET_ENGINE_QUICKJS) + set(NS_EFFECTIVE_FFI_BACKEND "quickjs") else() set(NS_EFFECTIVE_FFI_BACKEND "napi") endif() -elseif(NS_FFI_BACKEND STREQUAL "napi" OR NS_FFI_BACKEND STREQUAL "direct") +elseif(NS_FFI_BACKEND STREQUAL "napi" OR + NS_FFI_BACKEND STREQUAL "v8" OR + NS_FFI_BACKEND STREQUAL "jsc" OR + NS_FFI_BACKEND STREQUAL "quickjs" OR + NS_FFI_BACKEND STREQUAL "hermes") set(NS_EFFECTIVE_FFI_BACKEND "${NS_FFI_BACKEND}") else() message(FATAL_ERROR "Unknown NS_FFI_BACKEND: ${NS_FFI_BACKEND}") endif() -if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct" AND +if(NOT NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi" AND (GENERIC_NAPI OR TARGET_ENGINE_NONE OR BUILD_MACOS_NODE_API)) - message(FATAL_ERROR "NS_FFI_BACKEND=direct requires an embedded JS runtime build") + message(FATAL_ERROR + "NS_FFI_BACKEND=${NS_EFFECTIVE_FFI_BACKEND} requires an embedded JS runtime build") endif() -message(STATUS "NS_FFI_BACKEND = ${NS_FFI_BACKEND} (${NS_EFFECTIVE_FFI_BACKEND})") - -if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct" AND - NOT (NS_GSD_BACKEND STREQUAL "auto" OR NS_GSD_BACKEND STREQUAL "none")) +if(NOT NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi" AND + NOT NS_EFFECTIVE_FFI_BACKEND STREQUAL "${TARGET_ENGINE}") message(FATAL_ERROR - "NS_GSD_BACKEND is only used by the Node-API FFI backend. " - "Use NS_GSD_BACKEND=auto or none with NS_FFI_BACKEND=direct.") + "NS_FFI_BACKEND=${NS_EFFECTIVE_FFI_BACKEND} requires TARGET_ENGINE=${NS_EFFECTIVE_FFI_BACKEND}") endif() +message(STATUS "NS_FFI_BACKEND = ${NS_FFI_BACKEND} (${NS_EFFECTIVE_FFI_BACKEND})") + if(NS_GSD_BACKEND STREQUAL "auto") - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") - set(NS_EFFECTIVE_GSD_BACKEND "none") - else() + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi") set(NS_EFFECTIVE_GSD_BACKEND "napi") + else() + set(NS_EFFECTIVE_GSD_BACKEND "${NS_EFFECTIVE_FFI_BACKEND}") endif() elseif(NS_GSD_BACKEND STREQUAL "v8" OR NS_GSD_BACKEND STREQUAL "jsc" OR @@ -200,81 +210,86 @@ if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi" AND "NS_FFI_BACKEND=napi is the pure Node-API FFI backend and only supports " "NS_GSD_BACKEND=napi or none.") endif() +if(NOT NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi" AND + NS_EFFECTIVE_GSD_BACKEND STREQUAL "napi") + message(FATAL_ERROR + "NS_FFI_BACKEND=${NS_EFFECTIVE_FFI_BACKEND} cannot use NS_GSD_BACKEND=napi. " + "Use the matching engine backend or none.") +endif() message(STATUS "NS_GSD_BACKEND = ${NS_GSD_BACKEND} (${NS_EFFECTIVE_GSD_BACKEND})") # Set up sources include_directories( ./ - ffi/shared ../metadata-generator/include napi/common libffi/${LIBFFI_BUILD}/include ) set(FFI_SHARED_SOURCE_FILES - ffi/shared/Tasks.cpp + ffi/objc/shared/Tasks.cpp ) set(FFI_NAPI_SOURCE_FILES - ffi/napi/AutoreleasePool.mm - ffi/napi/Protocol.mm - ffi/napi/ObjCBridge.mm - ffi/napi/Block.mm - ffi/napi/Class.mm - ffi/napi/Closure.mm - ffi/napi/ClassMember.mm - ffi/napi/Cif.mm - ffi/napi/TypeConv.mm - ffi/napi/Util.mm - ffi/napi/Struct.mm - ffi/napi/ObjectRef.mm - ffi/napi/JSObject.mm - ffi/napi/Enum.mm - ffi/napi/Variable.mm - ffi/napi/Object.mm - ffi/napi/CFunction.mm - ffi/napi/Interop.mm - ffi/napi/InlineFunctions.mm - ffi/napi/ClassBuilder.mm + ffi/objc/napi/AutoreleasePool.mm + ffi/objc/napi/Protocol.mm + ffi/objc/napi/ObjCBridge.mm + ffi/objc/napi/Block.mm + ffi/objc/napi/Class.mm + ffi/objc/napi/Closure.mm + ffi/objc/napi/ClassMember.mm + ffi/objc/napi/Cif.mm + ffi/objc/napi/TypeConv.mm + ffi/objc/napi/Util.mm + ffi/objc/napi/Struct.mm + ffi/objc/napi/ObjectRef.mm + ffi/objc/napi/JSObject.mm + ffi/objc/napi/Enum.mm + ffi/objc/napi/Variable.mm + ffi/objc/napi/Object.mm + ffi/objc/napi/CFunction.mm + ffi/objc/napi/Interop.mm + ffi/objc/napi/InlineFunctions.mm + ffi/objc/napi/ClassBuilder.mm ) -set(FFI_DIRECT_SHARED_SOURCE_FILES - ffi/shared/direct/EmbeddedMetadata.mm +set(FFI_ENGINE_SHARED_SOURCE_FILES + ffi/objc/shared/MetadataState.mm ) -set(FFI_HERMES_DIRECT_SOURCE_FILES - ${FFI_DIRECT_SHARED_SOURCE_FILES} - ffi/hermes/jsi/NativeApiJsi.mm +set(FFI_HERMES_ENGINE_SOURCE_FILES + ${FFI_ENGINE_SHARED_SOURCE_FILES} + ffi/objc/hermes/NativeApiJsi.mm ) -set(FFI_V8_DIRECT_SOURCE_FILES - ${FFI_DIRECT_SHARED_SOURCE_FILES} - ffi/v8/NativeApiV8.mm - ffi/v8/NativeApiV8HostObjects.mm - ffi/v8/NativeApiV8Runtime.mm - ffi/v8/NativeApiV8Value.mm +set(FFI_V8_ENGINE_SOURCE_FILES + ${FFI_ENGINE_SHARED_SOURCE_FILES} + ffi/objc/v8/NativeApiV8.mm + ffi/objc/v8/NativeApiV8HostObjects.mm + ffi/objc/v8/NativeApiV8Runtime.mm + ffi/objc/v8/NativeApiV8Value.mm ) -set(FFI_JSC_DIRECT_SOURCE_FILES - ${FFI_DIRECT_SHARED_SOURCE_FILES} - ffi/jsc/NativeApiJSC.mm - ffi/jsc/NativeApiJSCHostObjects.mm - ffi/jsc/NativeApiJSCRuntime.mm - ffi/jsc/NativeApiJSCValue.mm +set(FFI_JSC_ENGINE_SOURCE_FILES + ${FFI_ENGINE_SHARED_SOURCE_FILES} + ffi/objc/jsc/NativeApiJSC.mm + ffi/objc/jsc/NativeApiJSCHostObjects.mm + ffi/objc/jsc/NativeApiJSCRuntime.mm + ffi/objc/jsc/NativeApiJSCValue.mm ) -set(FFI_QUICKJS_DIRECT_SOURCE_FILES - ${FFI_DIRECT_SHARED_SOURCE_FILES} - ffi/quickjs/NativeApiQuickJSHostObjects.mm - ffi/quickjs/NativeApiQuickJS.mm - ffi/quickjs/NativeApiQuickJSRuntime.mm - ffi/quickjs/NativeApiQuickJSValue.mm +set(FFI_QUICKJS_ENGINE_SOURCE_FILES + ${FFI_ENGINE_SHARED_SOURCE_FILES} + ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm + ffi/objc/quickjs/NativeApiQuickJS.mm + ffi/objc/quickjs/NativeApiQuickJSRuntime.mm + ffi/objc/quickjs/NativeApiQuickJSValue.mm ) set(SOURCE_FILES ${FFI_SHARED_SOURCE_FILES} - runtime/NativeScriptException.mm + runtime/apple/NativeScriptException.mm ) if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi") @@ -287,28 +302,34 @@ endif() if(ENABLE_JS_RUNTIME) set(SOURCE_FILES ${SOURCE_FILES} - runtime/modules/console/Console.cpp - runtime/Runtime.cpp - runtime/modules/worker/Worker.mm - runtime/modules/worker/MessageJSON.cpp - runtime/modules/worker/MessageV8.cpp - runtime/modules/worker/ConcurrentQueue.cpp - runtime/modules/worker/WorkerImpl.mm - runtime/modules/worker/WorkerImpl.mm - runtime/modules/module/ModuleInternal.cpp - runtime/modules/node/Node.cpp - runtime/modules/node/FS.cpp - runtime/modules/node/Path.cpp - runtime/modules/node/Process.cpp - runtime/modules/node/VM.cpp - runtime/modules/performance/Performance.cpp - runtime/ThreadSafeFunction.mm - runtime/Bundle.mm - runtime/modules/timers/Timers.mm - runtime/modules/app/App.mm - runtime/modules/web/Web.mm - runtime/NativeScript.mm - runtime/RuntimeConfig.cpp + runtime/apple/modules/console/Console.cpp + runtime/apple/Runtime.cpp + runtime/apple/modules/worker/Worker.mm + runtime/apple/modules/worker/MessageJSON.cpp + runtime/apple/modules/worker/MessageV8.cpp + runtime/apple/modules/worker/ConcurrentQueue.cpp + runtime/apple/modules/worker/WorkerImpl.mm + runtime/apple/modules/module/ModuleInternal.cpp + runtime/apple/modules/node/Node.cpp + runtime/apple/modules/node/FS.cpp + runtime/apple/modules/node/Path.cpp + runtime/apple/modules/node/Process.cpp + runtime/apple/modules/node/VM.cpp + runtime/apple/modules/performance/Performance.cpp + runtime/apple/ThreadSafeFunction.mm + runtime/apple/Bundle.mm + runtime/apple/modules/timers/Timers.mm + runtime/apple/modules/app/App.mm + runtime/apple/modules/web/Web.mm + runtime/apple/NativeScript.mm + runtime/apple/RuntimeConfig.cpp + # resolveMainPath() (cli/BundleLoader.h) is called directly from + # NativeScript.mm's runMainApplication, so it must be compiled into every + # framework/app target that includes NativeScript.mm -- not only the + # BUILD_CLI_BINARY executable. cli/main.cpp and cli/segappend.cpp stay + # CLI-binary-only below (main.cpp defines main(), which cannot also be + # linked into the NativeScript shared library). + cli/BundleLoader.mm runtime/modules/url/ada/ada.cpp runtime/modules/url/URL.cpp runtime/modules/url/URLSearchParams.cpp @@ -328,10 +349,10 @@ if(ENABLE_JS_RUNTIME) napi/v8/SimpleAllocator.cpp ) - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "v8") set(SOURCE_FILES ${SOURCE_FILES} - ${FFI_V8_DIRECT_SOURCE_FILES} + ${FFI_V8_ENGINE_SOURCE_FILES} ) endif() @@ -359,10 +380,10 @@ if(ENABLE_JS_RUNTIME) napi/hermes/jsr.cpp ) - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "hermes") set(SOURCE_FILES ${SOURCE_FILES} - ${FFI_HERMES_DIRECT_SOURCE_FILES} + ${FFI_HERMES_ENGINE_SOURCE_FILES} ) endif() @@ -398,10 +419,10 @@ if(ENABLE_JS_RUNTIME) napi/quickjs/jsr.cpp ) - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "quickjs") set(SOURCE_FILES ${SOURCE_FILES} - ${FFI_QUICKJS_DIRECT_SOURCE_FILES} + ${FFI_QUICKJS_ENGINE_SOURCE_FILES} ) endif() @@ -417,10 +438,10 @@ if(ENABLE_JS_RUNTIME) napi/jsc/jsr.cpp ) - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "jsc") set(SOURCE_FILES ${SOURCE_FILES} - ${FFI_JSC_DIRECT_SOURCE_FILES} + ${FFI_JSC_ENGINE_SOURCE_FILES} ) endif() @@ -435,7 +456,6 @@ if(BUILD_CLI_BINARY) set(SOURCE_FILES ${SOURCE_FILES} cli/main.cpp cli/segappend.cpp - cli/BundleLoader.mm ) endif() @@ -511,40 +531,48 @@ elseif(TARGET_ENGINE_JSC) target_compile_definitions(${NAME} PRIVATE TARGET_ENGINE_JSC) endif() -set(NS_GSD_BACKEND_V8_VALUE 0) -set(NS_GSD_BACKEND_JSC_VALUE 0) -set(NS_GSD_BACKEND_QUICKJS_VALUE 0) set(NS_GSD_BACKEND_HERMES_VALUE 0) set(NS_GSD_BACKEND_NAPI_VALUE 0) -set(NS_FFI_BACKEND_DIRECT_VALUE 0) +set(NS_GSD_BACKEND_PREPARED_VALUE 0) set(NS_FFI_BACKEND_NAPI_VALUE 0) +set(NS_FFI_BACKEND_V8_VALUE 0) +set(NS_FFI_BACKEND_JSC_VALUE 0) +set(NS_FFI_BACKEND_QUICKJS_VALUE 0) +set(NS_FFI_BACKEND_HERMES_VALUE 0) if(NS_EFFECTIVE_GSD_BACKEND STREQUAL "v8") - set(NS_GSD_BACKEND_V8_VALUE 1) + set(NS_GSD_BACKEND_PREPARED_VALUE 1) elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "jsc") - set(NS_GSD_BACKEND_JSC_VALUE 1) + set(NS_GSD_BACKEND_PREPARED_VALUE 1) elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "quickjs") - set(NS_GSD_BACKEND_QUICKJS_VALUE 1) + set(NS_GSD_BACKEND_PREPARED_VALUE 1) elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "hermes") set(NS_GSD_BACKEND_HERMES_VALUE 1) elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "napi") set(NS_GSD_BACKEND_NAPI_VALUE 1) endif() -if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") - set(NS_FFI_BACKEND_DIRECT_VALUE 1) -elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi") +if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi") set(NS_FFI_BACKEND_NAPI_VALUE 1) +elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "v8") + set(NS_FFI_BACKEND_V8_VALUE 1) +elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "jsc") + set(NS_FFI_BACKEND_JSC_VALUE 1) +elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "quickjs") + set(NS_FFI_BACKEND_QUICKJS_VALUE 1) +elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "hermes") + set(NS_FFI_BACKEND_HERMES_VALUE 1) endif() target_compile_definitions(${NAME} PRIVATE - NS_GSD_BACKEND_V8=${NS_GSD_BACKEND_V8_VALUE} - NS_GSD_BACKEND_JSC=${NS_GSD_BACKEND_JSC_VALUE} - NS_GSD_BACKEND_QUICKJS=${NS_GSD_BACKEND_QUICKJS_VALUE} NS_GSD_BACKEND_HERMES=${NS_GSD_BACKEND_HERMES_VALUE} NS_GSD_BACKEND_NAPI=${NS_GSD_BACKEND_NAPI_VALUE} - NS_FFI_BACKEND_DIRECT=${NS_FFI_BACKEND_DIRECT_VALUE} + NS_GSD_BACKEND_PREPARED=${NS_GSD_BACKEND_PREPARED_VALUE} NS_FFI_BACKEND_NAPI=${NS_FFI_BACKEND_NAPI_VALUE} + NS_FFI_BACKEND_V8=${NS_FFI_BACKEND_V8_VALUE} + NS_FFI_BACKEND_JSC=${NS_FFI_BACKEND_JSC_VALUE} + NS_FFI_BACKEND_QUICKJS=${NS_FFI_BACKEND_QUICKJS_VALUE} + NS_FFI_BACKEND_HERMES=${NS_FFI_BACKEND_HERMES_VALUE} ) set(FRAMEWORK_VERSION_VALUE "${VERSION}") @@ -657,13 +685,13 @@ if(TARGET_ENGINE_V8) # Prefer universal sim slice if present set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64-simulator/libv8_monolith.framework") if(NOT EXISTS "${V8_SLICE_DIR}") - set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64-simulator/libv8_monolith.framework") # fallback + set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64-simulator/libv8_monolith.framework") endif() elseif(TARGET_PLATFORM STREQUAL "ios") # Prefer universal sim slice if present set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64/libv8_monolith.framework") if(NOT EXISTS "${V8_SLICE_DIR}") - set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64/libv8_monolith.framework") # fallback + set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64/libv8_monolith.framework") endif() elseif(TARGET_PLATFORM STREQUAL "visionos-sim") set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/xrsimulator-arm64/libv8_monolith.framework") diff --git a/NativeScript/cli/BundleLoader.mm b/NativeScript/cli/BundleLoader.mm index 6fc249579..14be4d875 100644 --- a/NativeScript/cli/BundleLoader.mm +++ b/NativeScript/cli/BundleLoader.mm @@ -1,50 +1,148 @@ #include "BundleLoader.h" #include +#include +#include // Check if Resources/app/ exists, then load package.json["main"] || app/index.js full file path -std::string resolveMainPath() { +static NSString* resourcesPathForExecutable(NSString* executablePath) { + if (executablePath == nil || [executablePath length] == 0) { + return nil; + } + + NSString* standardizedPath = [executablePath stringByStandardizingPath]; + NSString* macOSPath = [standardizedPath stringByDeletingLastPathComponent]; + NSString* contentsPath = [macOSPath stringByDeletingLastPathComponent]; + if ([[macOSPath lastPathComponent] isEqualToString:@"MacOS"] && + [[contentsPath lastPathComponent] isEqualToString:@"Contents"]) { + return [contentsPath stringByAppendingPathComponent:@"Resources"]; + } + + return nil; +} + +static bool shouldLogBundleResolution() { + return getenv("NS_BUNDLE_LOADER_DEBUG") != nullptr; +} + +static void addCandidatePath(NSMutableArray* candidates, NSString* path) { + if (path == nil || [path length] == 0) { + return; + } + + NSString* standardizedPath = [path stringByStandardizingPath]; + if (![candidates containsObject:standardizedPath]) { + [candidates addObject:standardizedPath]; + } +} + +static std::string resolveMainPathInResources(NSString* resourcesPath) { NSFileManager* fileManager = [NSFileManager defaultManager]; - NSString* resourcesPath = [[NSBundle mainBundle] resourcePath]; NSString* appPath = [resourcesPath stringByAppendingPathComponent:@"app"]; BOOL isDir; if ([fileManager fileExistsAtPath:appPath isDirectory:&isDir] && isDir) { + if (shouldLogBundleResolution()) { + NSLog(@"NativeScript BundleLoader checking app path: %@", appPath); + } NSString* packageJsonPath = [appPath stringByAppendingPathComponent:@"package.json"]; if ([fileManager fileExistsAtPath:packageJsonPath]) { NSData* jsonData = [NSData dataWithContentsOfFile:packageJsonPath]; - NSError* error; + NSError* error = nil; NSDictionary* packageDict = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; if (error == nil) { NSString* mainEntry = packageDict[@"main"]; + if (shouldLogBundleResolution()) { + NSLog(@"NativeScript BundleLoader package main: %@ from %@", mainEntry, packageJsonPath); + } if (mainEntry != nil) { NSString* mainPath = [appPath stringByAppendingPathComponent:mainEntry]; if ([fileManager fileExistsAtPath:mainPath]) { + if (shouldLogBundleResolution()) { + NSLog(@"NativeScript BundleLoader resolved main: %@", mainPath); + } return std::string([mainPath UTF8String]); } if ([[mainEntry pathExtension] length] == 0) { NSString* mainPathMjs = [mainPath stringByAppendingPathExtension:@"mjs"]; if ([fileManager fileExistsAtPath:mainPathMjs]) { + if (shouldLogBundleResolution()) { + NSLog(@"NativeScript BundleLoader resolved main: %@", mainPathMjs); + } return std::string([mainPathMjs UTF8String]); } NSString* mainPathJs = [mainPath stringByAppendingPathExtension:@"js"]; if ([fileManager fileExistsAtPath:mainPathJs]) { + if (shouldLogBundleResolution()) { + NSLog(@"NativeScript BundleLoader resolved main: %@", mainPathJs); + } return std::string([mainPathJs UTF8String]); } } } + } else if (shouldLogBundleResolution()) { + NSLog(@"NativeScript BundleLoader failed to parse %@: %@", packageJsonPath, error); } } // Fallback to app/index.js NSString* indexPath = [appPath stringByAppendingPathComponent:@"index.js"]; if ([fileManager fileExistsAtPath:indexPath]) { + if (shouldLogBundleResolution()) { + NSLog(@"NativeScript BundleLoader resolved fallback main: %@", indexPath); + } return std::string([indexPath UTF8String]); } + } else if (shouldLogBundleResolution()) { + NSLog(@"NativeScript BundleLoader skipped resources path: %@ appPath=%@ exists=%d isDir=%d", + resourcesPath, + appPath, + [fileManager fileExistsAtPath:appPath], + isDir); + } + + return ""; +} + +std::string resolveMainPath() { + NSMutableArray* candidates = [NSMutableArray array]; + addCandidatePath(candidates, [[NSBundle mainBundle] resourcePath]); + addCandidatePath(candidates, resourcesPathForExecutable([[NSBundle mainBundle] executablePath])); + + NSArray* arguments = [[NSProcessInfo processInfo] arguments]; + if ([arguments count] > 0) { + addCandidatePath(candidates, resourcesPathForExecutable([arguments objectAtIndex:0])); + } + + uint32_t executablePathLength = 0; + _NSGetExecutablePath(nullptr, &executablePathLength); + if (executablePathLength > 0) { + char* executablePathBuffer = static_cast(malloc(executablePathLength)); + if (executablePathBuffer != nullptr) { + if (_NSGetExecutablePath(executablePathBuffer, &executablePathLength) == 0) { + addCandidatePath(candidates, resourcesPathForExecutable([NSString stringWithUTF8String:executablePathBuffer])); + } + free(executablePathBuffer); + } + } + + NSString* currentDirectory = [[NSFileManager defaultManager] currentDirectoryPath]; + addCandidatePath(candidates, currentDirectory); + addCandidatePath(candidates, [currentDirectory stringByAppendingPathComponent:@"Resources"]); + addCandidatePath(candidates, [[currentDirectory stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Resources"]); + + for (NSString* resourcesPath in candidates) { + if (shouldLogBundleResolution()) { + NSLog(@"NativeScript BundleLoader candidate resources: %@", resourcesPath); + } + std::string mainPath = resolveMainPathInResources(resourcesPath); + if (!mainPath.empty()) { + return mainPath; + } } return ""; diff --git a/NativeScript/cli/main.cpp b/NativeScript/cli/main.cpp index f9ea139c0..f72be8600 100644 --- a/NativeScript/cli/main.cpp +++ b/NativeScript/cli/main.cpp @@ -5,12 +5,12 @@ #include #include -#include "runtime/NativeScriptException.h" -#include "runtime/Bundle.h" -#include "runtime/Runtime.h" -#include "runtime/RuntimeConfig.h" +#include "runtime/apple/NativeScriptException.h" +#include "runtime/apple/Bundle.h" +#include "runtime/apple/Runtime.h" +#include "runtime/apple/RuntimeConfig.h" #include "segappend.h" -#include "ffi/shared/Tasks.h" +#include "ffi/objc/shared/Tasks.h" #include "BundleLoader.h" using namespace nativescript; diff --git a/NativeScript/ffi/hermes/jsi/NativeApiJsi.h b/NativeScript/ffi/hermes/jsi/NativeApiJsi.h deleted file mode 100644 index 82df76143..000000000 --- a/NativeScript/ffi/hermes/jsi/NativeApiJsi.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef NATIVE_API_JSI_H -#define NATIVE_API_JSI_H - -#include -#include -#include - -#include - -namespace nativescript { - -class NativeApiJsiScheduler { - public: - virtual ~NativeApiJsiScheduler() = default; - virtual void invokeOnJS(std::function task) = 0; - virtual void invokeOnUI(std::function task) = 0; -}; - -struct NativeApiJsiConfig { - const char* metadataPath = nullptr; - const void* metadataPtr = nullptr; - const char* globalName = "__nativeScriptNativeApi"; - std::shared_ptr scheduler = nullptr; - std::function)> nativeInvocationInvoker = nullptr; - std::function)> nativeCallbackInvoker = nullptr; - std::function)> jsThreadCallbackInvoker = nullptr; - bool invokeCallbacksOnNativeCallerThread = false; - bool installGlobalSymbols = false; -}; - -facebook::jsi::Object CreateNativeApiJSI( - facebook::jsi::Runtime& runtime, - const NativeApiJsiConfig& config = NativeApiJsiConfig{}); - -void InstallNativeApiJSI( - facebook::jsi::Runtime& runtime, - const NativeApiJsiConfig& config = NativeApiJsiConfig{}); - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiJSI( - facebook::jsi::Runtime* runtime, const char* metadataPath); - -#endif // NATIVE_API_JSI_H diff --git a/NativeScript/ffi/hermes/jsi/NativeApiJsi.mm b/NativeScript/ffi/hermes/jsi/NativeApiJsi.mm deleted file mode 100644 index 70a80bbfe..000000000 --- a/NativeScript/ffi/hermes/jsi/NativeApiJsi.mm +++ /dev/null @@ -1,89 +0,0 @@ -#include "NativeApiJsi.h" - -#ifdef TARGET_ENGINE_HERMES - -#import -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Metadata.h" -#include "MetadataReader.h" -#include "ffi.h" - -@protocol NativeApiJsiClassBuilderProtocol -@end - -#ifdef EMBED_METADATA_SIZE -extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; -#endif - -namespace nativescript { -namespace { - -using facebook::jsi::Array; -using facebook::jsi::ArrayBuffer; -using facebook::jsi::BigInt; -using facebook::jsi::Function; -using facebook::jsi::HostObject; -using facebook::jsi::MutableBuffer; -using facebook::jsi::Object; -using facebook::jsi::PropNameID; -using facebook::jsi::Runtime; -using facebook::jsi::String; -using facebook::jsi::StringBuffer; -using facebook::jsi::Value; -using metagen::MDMemberFlag; -using metagen::MDMetadataReader; -using metagen::MDSectionOffset; -using metagen::MDTypeKind; - -// clang-format off -#include "jsi/NativeApiJsiBridge.h" -#include "jsi/NativeApiJsiHostObjects.h" -#include "jsi/NativeApiJsiCallbacks.h" -#include "jsi/NativeApiJsiConversion.h" -#include "jsi/NativeApiJsiInvocation.h" -#include "jsi/NativeApiJsiClassBuilder.h" -#include "jsi/NativeApiJsiHostObject.h" -// clang-format on - -} // namespace - -#include "jsi/NativeApiJsiInstall.h" - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiJSI(facebook::jsi::Runtime* runtime, - const char* metadataPath) { - if (runtime == nullptr) { - return; - } - nativescript::NativeApiJsiConfig config; - config.metadataPath = metadataPath; - nativescript::InstallNativeApiJSI(*runtime, config); -} - -#endif // TARGET_ENGINE_HERMES diff --git a/NativeScript/ffi/hermes/jsi/README.md b/NativeScript/ffi/hermes/jsi/README.md deleted file mode 100644 index ca07222b5..000000000 --- a/NativeScript/ffi/hermes/jsi/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Native API JSI bridge - -This directory contains the Hermes-first JSI entrypoint for NativeScript Native -API access. - -The backend is split by FFI responsibility: - -- `../../shared/jsi/NativeApiJsiBridge.h` owns metadata indexing, symbol lookup, scheduler - state, and bridge lifetime caches. -- `../../shared/jsi/NativeApiJsiHostObjects.h` owns class, object, protocol, pointer, - reference, struct, and union host objects. -- `../../shared/jsi/NativeApiJsiCallbacks.h` owns signatures, libffi callback trampolines, - JS blocks, and native function pointer callback lifetime. -- `../../shared/jsi/NativeApiJsiConversion.h` owns JSI/native type conversion and the - `interop` helper surface. -- `../../shared/jsi/NativeApiJsiInvocation.h` owns constants, enums, C function calls, - function pointer calls, and Objective-C selector dispatch. -- `../../shared/jsi/NativeApiJsiHostObject.h` owns the public API host object exposed to JS. -- `../../shared/jsi/NativeApiJsiInstall.h` owns runtime/global installation. - -The core installer is engine-host agnostic: - -```cpp -nativescript::NativeApiJsiConfig config; -config.metadataPath = metadataPath; -config.metadataPtr = metadataPtr; -nativescript::InstallNativeApiJSI(runtime, config); -``` - -NativeScript's Hermes runtime installs this automatically as -`globalThis.__nativeScriptNativeApi`. - -React Native integrations should include `NativeApiJsiReactNative.h` from a -TurboModule implementation and pass the module's JS/UI `CallInvoker`s: - -```cpp -nativescript::InstallReactNativeNativeApiJSI( - runtime, jsInvoker, uiInvoker, metadataPath, metadataPtr); -``` - -The React Native adapter is intentionally only a scheduler/config shim. The -native API host object, metadata loading, primitive C function dispatch, -Objective-C class/object handles, and selector invocation live in the shared -JSI implementation so they can be used by both NativeScript Hermes and a React -Native TurboModule without going through Node-API. - -The direct JSI backend is still moving toward full NativeScript bridge parity. -It covers the metadata-backed Objective-C class/function/constant/enum paths -needed by the React Native TurboModule, plus metadata-backed structs/unions, -primitive array/vector value marshalling, JS blocks, C function pointer -callbacks, protocol wrappers, pointer/reference helpers, and the core `interop` -helpers (`Pointer`, `Reference`, `sizeof`, `alloc`, `free`, `adopt`, -`handleof`, `stringFromCString`, `bufferFromData`, and `addProtocol`). Struct -and union constructors, plus protocol symbols, are installed on `globalThis` -along with `interop` so common NativeScript-style calls such as -`CGRect({ origin, size })`, `interop.sizeof(CGRect)`, and -`interop.handleof(value)` work through JSI. - -The remaining RN FFI-suite skip is the explicit `interop.addMethod` decorator -hook. JavaScript-defined Objective-C subclasses created through `.extend(...)` -use the JSI class-builder path and are covered by the React Native compatibility -suite. diff --git a/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp new file mode 100644 index 000000000..8d1ed0bbc --- /dev/null +++ b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp @@ -0,0 +1,1926 @@ +// +// Created by Ammar Ahmed on 20/09/2024. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "JEnv.h" +#include "CallbackHandlers.h" +#include "Util.h" +#include "JniLocalRef.h" +#include "MetadataNode.h" +#include "MethodCache.h" +#include "ArgConverter.h" +#include "JsArgConverter.h" +#include "GlobalHelpers.h" +#include + +#ifdef USE_MIMALLOC + +#include "mimalloc.h" + +#endif + +using namespace std; +using namespace tns; + +void CallbackHandlers::Init(napi_env env) { + JEnv jEnv; + + JAVA_LANG_STRING = jEnv.FindClass("java/lang/String"); + assert(JAVA_LANG_STRING != nullptr); + + RUNTIME_CLASS = jEnv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + RESOLVE_CLASS_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "resolveClass", + "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Z)Ljava/lang/Class;"); + assert(RESOLVE_CLASS_METHOD_ID != nullptr); + + CURRENT_OBJECTID_FIELD_ID = jEnv.GetFieldID(RUNTIME_CLASS, "currentObjectId", "I"); + assert(CURRENT_OBJECTID_FIELD_ID != nullptr); + + MAKE_INSTANCE_STRONG_ID = jEnv.GetMethodID(RUNTIME_CLASS, "makeInstanceStrong", + "(Ljava/lang/Object;I)V"); + assert(MAKE_INSTANCE_STRONG_ID != nullptr); + + GET_TYPE_METADATA = jEnv.GetStaticMethodID(RUNTIME_CLASS, "getTypeMetadata", + "(Ljava/lang/String;I)[Ljava/lang/String;"); + assert(GET_TYPE_METADATA != nullptr); + + ENABLE_VERBOSE_LOGGING_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "enableVerboseLogging", + "()V"); + assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr); + + DISABLE_VERBOSE_LOGGING_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "disableVerboseLogging", + "()V"); + assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr); + + INIT_WORKER_METHOD_ID = jEnv.GetStaticMethodID(RUNTIME_CLASS, "initWorker", + "(Ljava/lang/String;Ljava/lang/String;I)V"); + + assert(INIT_WORKER_METHOD_ID != nullptr); + + SEND_MESSAGE_TO_WORKER_METHOD_ID = jEnv.GetStaticMethodID(RUNTIME_CLASS, + "sendMessageFromMainToWorker", + "(ILjava/lang/String;)V"); + assert(SEND_MESSAGE_TO_WORKER_METHOD_ID != nullptr); + + SEND_MESSAGE_TO_MAIN_METHOD_ID = jEnv.GetStaticMethodID(RUNTIME_CLASS, + "sendMessageFromWorkerToMain", + "(Ljava/lang/String;)V"); + assert(SEND_MESSAGE_TO_MAIN_METHOD_ID != nullptr); + + TERMINATE_WORKER_METHOD_ID = jEnv.GetStaticMethodID(RUNTIME_CLASS, "workerObjectTerminate", + "(I)V"); + assert(TERMINATE_WORKER_METHOD_ID != nullptr); + + WORKER_SCOPE_CLOSE_METHOD_ID = jEnv.GetStaticMethodID(RUNTIME_CLASS, "workerScopeClose", "()V"); + assert(WORKER_SCOPE_CLOSE_METHOD_ID != nullptr); + + MetadataNode::Init(env); + + MethodCache::Init(); +} + +napi_value CallbackHandlers::CallJavaMethod(napi_env env, napi_value caller, const string &className, + const string &methodName, MetadataEntry *entry, + bool isFromInterface, bool isStatic, napi_callback_info info, size_t argc, napi_value* argv) { + + JEnv jEnv; + jclass clazz; + jmethodID mid; + string *sig = nullptr; + string *returnType = nullptr; + auto retType = MethodReturnType::Unknown; + MethodCache::CacheMethodInfo mi; + bool isSuper = false; + + if ((entry != nullptr) && entry->getIsResolved()) { + auto &entrySignature = entry->getSig(); + isStatic = entry->isStatic; + + if (entry->memberId == nullptr) { + clazz = jEnv.FindClass(className); + + if (clazz == nullptr) { + MetadataNode *callerNode = MetadataNode::GetNodeFromHandle(env, caller); + const string callerClassName = callerNode->GetName(); + + DEBUG_WRITE("Cannot resolve class: %s while calling method: %s callerClassName: %s", + className.c_str(), methodName.c_str(), callerClassName.c_str()); + clazz = jEnv.FindClass(callerClassName); + if (clazz == nullptr) { + //todo: plamen5kov: throw exception here + DEBUG_WRITE("Cannot resolve caller's class name: %s", callerClassName.c_str()); + return nullptr; + } + + if (isStatic) { + if (isFromInterface) { + auto methodAndClassPair = jEnv.GetInterfaceStaticMethodIDAndJClass( + className, + methodName, + entrySignature); + entry->memberId = methodAndClassPair.first; + clazz = methodAndClassPair.second; + } else { + entry->memberId = jEnv.GetStaticMethodID(clazz, methodName, entrySignature); + } + } else { + entry->memberId = jEnv.GetMethodID(clazz, methodName, entrySignature); + } + + if (entry->memberId == nullptr) { + //todo: plamen5kov: throw exception here + DEBUG_WRITE("Cannot resolve a method %s on caller class: %s", + methodName.c_str(), callerClassName.c_str()); + return nullptr; + } + } else { + if (isStatic) { + if (isFromInterface) { + auto methodAndClassPair = jEnv.GetInterfaceStaticMethodIDAndJClass( + className, + methodName, entrySignature); + entry->memberId = methodAndClassPair.first; + clazz = methodAndClassPair.second; + } else { + entry->memberId = jEnv.GetStaticMethodID(clazz, methodName, entrySignature); + } + } else { + entry->memberId = jEnv.GetMethodID(clazz, methodName, entrySignature); + } + + if (entry->memberId == nullptr) { + //todo: plamen5kov: throw exception here + DEBUG_WRITE("Cannot resolve a method %s on class: %s", methodName.c_str(), + className.c_str()); + return nullptr; + } + } + entry->clazz = clazz; + } + + mid = reinterpret_cast(entry->memberId); + clazz = entry->clazz; + sig = &entry->getSig(); + returnType = &entry->getReturnType(); + retType = entry->getRetType(); + } else { + DEBUG_WRITE("Resolving method: %s on className %s", methodName.c_str(), className.c_str()); + + clazz = jEnv.FindClass(className); + if (clazz != nullptr) { + mi = MethodCache::ResolveMethodSignature(env, className, methodName, argc, argv, isStatic); + if (mi.mid == nullptr) { + DEBUG_WRITE("Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d", + className.c_str(), methodName.c_str(), isStatic, isSuper); + return nullptr; + } + } else { + MetadataNode *callerNode = MetadataNode::GetNodeFromHandle(env, caller); + const string callerClassName = callerNode->GetName(); + DEBUG_WRITE("Resolving method on caller class: %s.%s on className %s", + callerClassName.c_str(), methodName.c_str(), className.c_str()); + mi = MethodCache::ResolveMethodSignature(env, callerClassName, methodName, argc, argv, + isStatic); + if (mi.mid == nullptr) { + DEBUG_WRITE( + "Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d, callerClass=%s", + className.c_str(), methodName.c_str(), isStatic, isSuper, + callerClassName.c_str()); + return nullptr; + } + } + + clazz = mi.clazz; + mid = mi.mid; + sig = &mi.signature; + returnType = &mi.returnType; + retType = mi.retType; + } + + if (!isStatic) { + DEBUG_WRITE("CallJavaMethod on instance %s", methodName.c_str()); + } else { + DEBUG_WRITE("CallJavaMethod on class %s", methodName.c_str()); + } + + JsArgConverter argConverter = (entry != nullptr && entry->isExtensionFunction) + ? JsArgConverter(env, caller, argv, argc, *sig, entry) + : JsArgConverter(env, argv, argc, false, *sig, entry); + + + if (!argConverter.IsValid()) { + JsArgConverter::Error err = argConverter.GetError(); + throw NativeScriptException(err.msg); + } + + JniLocalRef callerJavaObject; + + jvalue *javaArgs = argConverter.ToArgs(); + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + if (!isStatic) { + int objectId = -1; + + callerJavaObject = objectManager->GetJavaObjectByJsObject(caller, &objectId); + isSuper = objectManager->GetIsSuper(objectId, caller); + + if (callerJavaObject.IsNull()) { + stringstream ss; + + napi_value new_target; + napi_get_new_target(env, info, &new_target); + if (!napi_util::is_null_or_undefined(env, new_target)) { + ss << "No java object found on which to call \"" << methodName + << "\" method. It is possible your Javascript object is not linked with the corresponding Java class. Try passing context(this) to the constructor function."; + } else { + ss << "Failed calling " << methodName << " on a " << className + << " instance. The JavaScript instance no longer has available Java instance counterpart."; + } + throw NativeScriptException(ss.str()); + } + } + + napi_value returnValue; + + switch (retType) { + case MethodReturnType::Void: { + if (isStatic) { + jEnv.CallStaticVoidMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + jEnv.CallNonvirtualVoidMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + jEnv.CallVoidMethodA(callerJavaObject, mid, javaArgs); + } + returnValue = nullptr; + break; + } + case MethodReturnType::Boolean: { + jboolean result; + if (isStatic) { + result = jEnv.CallStaticBooleanMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualBooleanMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallBooleanMethodA(callerJavaObject, mid, javaArgs); + } + + napi_get_boolean(env, result != 0, &returnValue); + break; + } + case MethodReturnType::Byte: { + jbyte result; + if (isStatic) { + result = jEnv.CallStaticByteMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualByteMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallByteMethodA(callerJavaObject, mid, javaArgs); + } + + napi_create_int32(env, result, &returnValue); + break; + } + case MethodReturnType::Char: { + jchar result; + if (isStatic) { + result = jEnv.CallStaticCharMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualCharMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallCharMethodA(callerJavaObject, mid, javaArgs); + } + + JniLocalRef str(jEnv.NewString(&result, 1)); + jboolean bol = true; + const char *resP = jEnv.GetStringUTFChars(str, &bol); + returnValue = ArgConverter::convertToJsString(env, resP, 1); + jEnv.ReleaseStringUTFChars(str, resP); + break; + } + case MethodReturnType::Short: { + jshort result; + if (isStatic) { + result = jEnv.CallStaticShortMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualShortMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallShortMethodA(callerJavaObject, mid, javaArgs); + } + + napi_create_int32(env, result, &returnValue); + + break; + } + case MethodReturnType::Int: { + jint result; + if (isStatic) { + result = jEnv.CallStaticIntMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualIntMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallIntMethodA(callerJavaObject, mid, javaArgs); + } + napi_create_int32(env, result, &returnValue); + break; + + } + case MethodReturnType::Long: { + jlong result; + if (isStatic) { + result = jEnv.CallStaticLongMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualLongMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallLongMethodA(callerJavaObject, mid, javaArgs); + } + returnValue = ArgConverter::ConvertFromJavaLong(env, result); + break; + } + case MethodReturnType::Float: { + jfloat result; + if (isStatic) { + result = jEnv.CallStaticFloatMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualFloatMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallFloatMethodA(callerJavaObject, mid, javaArgs); + } + napi_create_double(env, (double) result, &returnValue); + break; + } + case MethodReturnType::Double: { + jdouble result; + if (isStatic) { + result = jEnv.CallStaticDoubleMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualDoubleMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallDoubleMethodA(callerJavaObject, mid, javaArgs); + } + napi_create_double(env, (double) result, &returnValue); + break; + } + case MethodReturnType::String: { + jobject result = nullptr; + bool exceptionOccurred; + + if (isStatic) { + result = jEnv.CallStaticObjectMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallObjectMethodA(callerJavaObject, mid, javaArgs); + } + + if (result != nullptr) { + returnValue = ArgConverter::jstringToJsString(env, static_cast(result)); + jEnv.DeleteLocalRef(result); + } else { + napi_get_null(env, &returnValue); + } + + break; + } + case MethodReturnType::Object: { + jobject result = nullptr; + bool exceptionOccurred; + + if (isStatic) { + result = jEnv.CallStaticObjectMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallObjectMethodA(callerJavaObject, mid, javaArgs); + } + + if (result != nullptr) { + auto isString = jEnv.IsInstanceOf(result, JAVA_LANG_STRING); + + if (isString) { + returnValue = ArgConverter::jstringToJsString(env, (jstring) result); + } else { + jint javaObjectID = objectManager->GetOrCreateObjectId(result); + returnValue = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, returnValue)) { + returnValue = objectManager->CreateJSWrapper(javaObjectID, *returnType, + result); + } + } + + jEnv.DeleteLocalRef(result); + } else { + napi_get_null(env, &returnValue); + } + + break; + } + default: { + returnValue = napi_util::undefined(env); + assert(false); + break; + } + } + + + return returnValue; +} + + +bool CallbackHandlers::RegisterInstance(napi_env env, napi_value jsObject, + const std::string &fullClassName, + const ArgsWrapper &argWrapper, + napi_value implementationObject, + bool isInterface, + napi_value *jsThisProxy, + const std::string &baseClassName) { + bool success; + + DEBUG_WRITE("RegisterInstance called for '%s'", fullClassName.c_str()); + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + JEnv jEnv; + + jclass generatedJavaClass = ResolveClass(env, baseClassName, fullClassName, + implementationObject, + isInterface); + + int javaObjectID = objectManager->GenerateNewObjectID(); + + objectManager->Link(jsObject, javaObjectID); + + // resolve constructor + auto mi = MethodCache::ResolveConstructorSignature(env, argWrapper, fullClassName, + generatedJavaClass, isInterface); + + // while the "instance" is being created, if an exception is thrown during the construction + // this scope will guarantee the "javaObjectID" will be set to -1 and won't have an invalid value + jobject instance; + { + JavaObjectIdScope objIdScope(jEnv, CURRENT_OBJECTID_FIELD_ID, runtime->GetJavaRuntime(), + javaObjectID); + + if (argWrapper.type == ArgType::Interface) { + instance = jEnv.NewObject(generatedJavaClass, mi.mid); + } else { + // resolve arguments before passing them on to the constructor + // JSToJavaConverter argConverter(isolate, argWrapper.args, mi.signature); + + + + JsArgConverter argConverter(env, argWrapper.argv, argWrapper.argc, mi.signature); + auto ctorArgs = argConverter.ToArgs(); + + instance = jEnv.NewObjectA(generatedJavaClass, mi.mid, ctorArgs); + } + } + + // Set runtimeId field on interface and extended classes + if (runtime->GetId() != 0 && (isInterface || implementationObject != nullptr)) { + jfieldID runtimeIdField; + auto itFound = jclass_to_runtimeId_cache.find(generatedJavaClass); + if (itFound != jclass_to_runtimeId_cache.end()) { + runtimeIdField = itFound->second; + } else { + runtimeIdField = jEnv.GetFieldID(generatedJavaClass, "runtimeId", "I"); + jclass_to_runtimeId_cache.emplace(generatedJavaClass, runtimeIdField); + } + if (runtimeIdField != nullptr) { + jint runtimeId = runtime->GetId(); // Assuming GetId() returns the current runtime's id + DEBUG_WRITE("Setting runtimeId %d on instance of %s", runtimeId, fullClassName.c_str()); + jEnv.SetIntField(instance, runtimeIdField, runtimeId); + } + } + + jEnv.CallVoidMethod(runtime->GetJavaRuntime(), MAKE_INSTANCE_STRONG_ID, instance, javaObjectID); + + AdjustAmountOfExternalAllocatedMemory(env); + + JniLocalRef localInstance(instance); + success = !localInstance.IsNull(); + + if (success) { + *jsThisProxy = objectManager->GetOrCreateProxy(javaObjectID, jsObject); + } else { + DEBUG_WRITE_FORCE("RegisterInstance failed with null new instance class: %s", + fullClassName.c_str()); + } + + return success; +} + +jclass CallbackHandlers::ResolveClass(napi_env env, const string &baseClassName, + const string &fullClassName, + napi_value implementationObject, bool isInterface) { + JEnv jEnv; + jclass globalRefToGeneratedClass = jEnv.CheckForClassInCache(fullClassName); + + if (globalRefToGeneratedClass == nullptr) { + + // get needed arguments in order to load binding + JniLocalRef javaBaseClassName(jEnv.NewStringUTF(baseClassName.c_str())); + JniLocalRef javaFullClassName(jEnv.NewStringUTF(fullClassName.c_str())); + + jobjectArray methodOverrides = GetMethodOverrides(env, jEnv, implementationObject); + + jobjectArray implementedInterfaces = GetImplementedInterfaces(env, jEnv, + implementationObject); + + auto runtime = Runtime::GetRuntime(env); + + // create or load generated binding (java class) + jclass generatedClass = (jclass) jEnv.CallObjectMethod(runtime->GetJavaRuntime(), + RESOLVE_CLASS_METHOD_ID, + (jstring) javaBaseClassName, + (jstring) javaFullClassName, + methodOverrides, + implementedInterfaces, + isInterface); + + globalRefToGeneratedClass = jEnv.InsertClassIntoCache(fullClassName, generatedClass); + + jEnv.DeleteGlobalRef(methodOverrides); + jEnv.DeleteGlobalRef(implementedInterfaces); + } + + return globalRefToGeneratedClass; +} + +// Called by ExtendMethodCallback when extending a class +string CallbackHandlers::ResolveClassName(napi_env env, jclass &clazz) { + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + auto className = objectManager->GetClassName(clazz); + return className; +} + +napi_value CallbackHandlers::GetArrayElement(napi_env env, napi_value array, + uint32_t index, const string &arraySignature) { + return arrayElementAccessor.GetArrayElement(env, array, index, arraySignature); +} + +void CallbackHandlers::SetArrayElement(napi_env env, napi_value array, + uint32_t index, + const string &arraySignature, napi_value value) { + + arrayElementAccessor.SetArrayElement(env, array, index, arraySignature, value); +} + +napi_value CallbackHandlers::GetJavaField(napi_env env, napi_value caller, + FieldCallbackData *fieldData) { + return fieldAccessor.GetJavaField(env, caller, fieldData); +} + +void CallbackHandlers::SetJavaField(napi_env env, napi_value target, + napi_value value, FieldCallbackData *fieldData) { + fieldAccessor.SetJavaField(env, target, value, fieldData); +} + +void CallbackHandlers::AdjustAmountOfExternalAllocatedMemory(napi_env env) { + auto runtime = Runtime::GetRuntime(env); + runtime->AdjustAmountOfExternalAllocatedMemory(); + runtime->TryCallGC(); +} + +napi_value CallbackHandlers::CreateJSWrapper(napi_env env, jint javaObjectID, + const string &typeName) { + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + return objectManager->CreateJSWrapper(javaObjectID, typeName); +} + +jobjectArray +CallbackHandlers::GetImplementedInterfaces(napi_env env, JEnv &jEnv, + napi_value implementationObject) { + if (implementationObject == nullptr || napi_util::is_undefined(env, implementationObject)) { + return CallbackHandlers::GetJavaStringArray(jEnv, 0); + } + + vector interfacesToImplement; + + napi_value prop; + napi_get_named_property(env, implementationObject, "interfaces", &prop); + bool isArray; + napi_is_array(env, prop, &isArray); + + if (isArray) { + uint32_t length; + napi_get_array_length(env, prop, &length); + + for (int j = 0; j < length; j++) { + napi_value element; + napi_get_element(env, prop, j, &element); + + if (napi_util::is_object(env, element)) { + auto node = MetadataNode::GetTypeMetadataName(env, element); + + node = Util::ReplaceAll(node, std::string("/"), std::string(".")); + + jstring value = jEnv.NewStringUTF(node.c_str()); + interfacesToImplement.push_back(value); + } + } + } + + int interfacesCount = interfacesToImplement.size(); + + jobjectArray implementedInterfaces = CallbackHandlers::GetJavaStringArray(jEnv, + interfacesCount); + for (int i = 0; i < interfacesCount; i++) { + jEnv.SetObjectArrayElement(implementedInterfaces, i, interfacesToImplement[i]); + } + + for (int i = 0; i < interfacesCount; i++) { + jEnv.DeleteLocalRef(interfacesToImplement[i]); + } + + return implementedInterfaces; +} + +jobjectArray +CallbackHandlers::GetMethodOverrides(napi_env env, JEnv &jEnv, napi_value implementationObject) { + if (implementationObject == nullptr || napi_util::is_undefined(env, implementationObject)) { + return CallbackHandlers::GetJavaStringArray(jEnv, 0); + } + + vector methodNames; + + napi_value propNames; + + napi_get_all_property_names(env, implementationObject, napi_key_own_only, + napi_key_all_properties, napi_key_numbers_to_strings, &propNames); + + uint32_t length; + napi_get_array_length(env, propNames, &length); + + for (int i = 0; i < length; i++) { + napi_value element; + napi_get_element(env, propNames, i, &element); + auto name = ArgConverter::ConvertToString(env, element); + + if (name == "super") { + continue; + } + + napi_value method; + + napi_get_property(env, implementationObject, element, &method); + + bool methodFound = napi_util::is_of_type(env, method, napi_function); + + if (methodFound) { + jstring value = jEnv.NewStringUTF(name.c_str()); + methodNames.push_back(value); + } + } + + int methodCount = methodNames.size(); + + jobjectArray methodOverrides = CallbackHandlers::GetJavaStringArray(jEnv, methodCount); + for (int i = 0; i < methodCount; i++) { + jEnv.SetObjectArrayElement(methodOverrides, i, methodNames[i]); + } + + for (int i = 0; i < methodCount; i++) { + jEnv.DeleteLocalRef(methodNames[i]); + } + + return methodOverrides; +} + +napi_value CallbackHandlers::RunOnMainThreadCallback(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + assert(argc == 1); + assert(napi_util::is_of_type(env, args[0], napi_function)); + + uint64_t key = ++count_; + bool inserted; + + std::tie(std::ignore, inserted) = cache_.try_emplace(key, env, args[0]); + assert(inserted && "Main thread callback ID should not be duplicated"); + + auto value = Callback(key); + auto size = sizeof(Callback); + auto wrote = write(Runtime::GetWriter(), &value, size); + + + + return nullptr; +} + +int CallbackHandlers::RunOnMainThreadFdCallback(int fd, int events, void *data) { + struct Callback value; + auto size = sizeof(Callback); + ssize_t nr = read(fd, &value, sizeof(value)); + + auto key = value.id_; + + auto it = cache_.find(key); + if (it == cache_.end()) { + return 1; + } + + napi_env env = it->second.env_; + napi_ref callback_ref = it->second.callback_; + + NapiScope scope(env); + + napi_value cb = napi_util::get_ref_value(env, callback_ref); + + napi_value global; + napi_get_global(env, &global); + + cache_.erase(it); + + napi_value result; + napi_status status = napi_call_function(env, global, cb, 0, nullptr, &result); + + if (status != napi_ok) { + napi_throw_error(env, nullptr, "Error calling JavaScript callback"); + } + + + return 1; +} + +napi_value CallbackHandlers::LogMethodCallback(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + try { + if (argc > 0) { + napi_valuetype valuetype; + napi_typeof(env, args[0], &valuetype); + if (valuetype == napi_string) { + size_t str_size; + napi_get_value_string_utf8(env, args[0], nullptr, 0, &str_size); + std::string message(str_size + 1, '\0'); + napi_get_value_string_utf8(env, args[0], &message[0], str_size + 1, &str_size); + DEBUG_WRITE("%s", message.c_str()); + } + } + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value CallbackHandlers::DrainMicrotaskCallback(napi_env env, napi_callback_info info) { + js_execute_pending_jobs(env); + return nullptr; +} + +napi_value CallbackHandlers::TimeCallback(napi_env env, napi_callback_info info) { + auto nano = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + double duration = nano.time_since_epoch().count(); + napi_value result; + napi_create_double(env, duration, &result); + return result; +} + +napi_value +CallbackHandlers::ReleaseNativeCounterpartCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS(); + + if (argc != 1) { + napi_throw_error(env, "0", "Unexpected arguments count!"); + return napi_util::undefined(env); + } + + if (!napi_util::is_of_type(env, argv[0], napi_object)) { + napi_throw_error(env, "0", "Argument is not an object!"); + return napi_util::undefined(env); + } + + + Runtime::GetRuntime(env)->GetObjectManager()->ReleaseNativeObject(env, argv[0]); + return napi_util::undefined(env); +} + +void CallbackHandlers::validateProvidedArgumentsLength(napi_env env, napi_callback_info info, + int expectedSize) { + size_t argc = 0; + napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr); + if ((int) argc != expectedSize) { + throw NativeScriptException("Unexpected arguments count!"); + } +} + +napi_value +CallbackHandlers::DumpReferenceTablesMethodCallback(napi_env env, napi_callback_info info) { + DumpReferenceTablesMethod(); + return nullptr; +} + +void CallbackHandlers::DumpReferenceTablesMethod() { + try { + JEnv jEnv; + jclass vmDbgClass = jEnv.FindClass("dalvik/system/VMDebug"); + if (vmDbgClass != nullptr) { + jmethodID mid = jEnv.GetStaticMethodID(vmDbgClass, "dumpReferenceTables", "()V"); + if (mid != 0) { + jEnv.CallStaticVoidMethod(vmDbgClass, mid); + } + } + } + catch (NativeScriptException &e) { + // e.ReThrowToNapi(env); + } + catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + // nsEx.ReThrowToV8(); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + // nsEx.ReThrowToV8(); + } +} + +napi_value +CallbackHandlers::EnableVerboseLoggingMethodCallback(napi_env env, napi_callback_info info) { + try { + tns::LogEnabled = true; + JEnv jEnv; + jEnv.CallVoidMethod(Runtime::GetRuntime(env)->GetJavaRuntime(), + ENABLE_VERBOSE_LOGGING_METHOD_ID); + } + catch (NativeScriptException &e) { + // e.ReThrowToNapi(env); + } + catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + // nsEx.ReThrowToV8(); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + // nsEx.ReThrowToV8(); + } + return nullptr; +} + +napi_value +CallbackHandlers::DisableVerboseLoggingMethodCallback(napi_env env, napi_callback_info info) { + try { + tns::LogEnabled = false; + JEnv jEnv; + jEnv.CallVoidMethod(Runtime::GetRuntime(env)->GetJavaRuntime(), + DISABLE_VERBOSE_LOGGING_METHOD_ID); + } + catch (NativeScriptException &e) { + // e.ReThrowToNapi(env); + } + catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + // nsEx.ReThrowToV8(); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + // nsEx.ReThrowToV8(); + } + return nullptr; +} + +napi_value CallbackHandlers::ExitMethodCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + auto msg = ArgConverter::ConvertToString(env, argv[0]); + DEBUG_WRITE_FATAL("FORCE EXIT: %s", msg.c_str()); + exit(-1); + return nullptr; +} + +void CallbackHandlers::CreateGlobalCastFunctions(napi_env env) { + napi_value global; + napi_get_global(env, &global); + castFunctions.CreateGlobalCastFunctions(env, global); +} + +vector CallbackHandlers::GetTypeMetadata(const string &name, int index) { + JEnv env; + + string canonicalName = Util::ConvertFromJniToCanonicalName(name); + + JniLocalRef className(env.NewStringUTF(canonicalName.c_str())); + jint idx = index; + + JniLocalRef pubApi( + env.CallStaticObjectMethod(RUNTIME_CLASS, GET_TYPE_METADATA, (jstring) className, idx)); + + jsize length = env.GetArrayLength(pubApi); + + assert(length > 0); + + vector result; + + for (jsize i = 0; i < length; i++) { + JniLocalRef s(env.GetObjectArrayElement(pubApi, i)); + const char *pc = env.GetStringUTFChars(s, nullptr); + result.push_back(string(pc)); + env.ReleaseStringUTFChars(s, pc); + } + + return result; +} + +napi_value CallbackHandlers::CallJSMethod(napi_env env, JNIEnv *_jEnv, + napi_value jsObject, jclass claz,const string &methodName,int javaObjectId, + jobjectArray args) { + JEnv jEnv(_jEnv); + napi_value result; + napi_value method; + +#ifndef __HERMES__ + auto runtime = Runtime::GetRuntime(env); + method = runtime->js_method_cache->getCachedMethod(javaObjectId, methodName); + if (!method) { +#endif + napi_get_named_property(env, jsObject, methodName.c_str(), &method); +#ifndef __HERMES__ + if (napi_util::is_of_type(env, method, napi_function)) { + runtime->js_method_cache->cacheMethod(javaObjectId, methodName, method); + } + } +#endif + + if (method == nullptr || !napi_util::is_of_type(env, method, napi_function)) { + stringstream ss; + ss << "Cannot find method '" << methodName << "' implementation"; + throw NativeScriptException(ss.str()); + } else { + DEBUG_WRITE("Calling JS Method %s", methodName.c_str()); + + bool exceptionPending; + napi_is_exception_pending(env, &exceptionPending); + + int argc = jEnv.GetArrayLength(args) / 3; + if (argc > 0) { + napi_value* jsArgs = nullptr; + napi_value stack_args[8]; + if (argc <= 8) { + jsArgs = stack_args; + } else { +#ifdef USE_MIMALLOC + jsArgs = (napi_value *) mi_malloc(sizeof(napi_value) * argc); +#else + jsArgs = (napi_value *) malloc(sizeof(napi_value) * argc); +#endif + } + ArgConverter::ConvertJavaArgsToJsArgs(env, args, argc, jsArgs); + napi_call_function(env, jsObject, method, argc, jsArgs, &result); + + if (argc > 8) { +#ifdef USE_MIMALLOC + mi_free(jsArgs); +#else + free(jsArgs); +#endif + } + } else { + napi_call_function(env, jsObject, method, 0, nullptr, &result); + } + + if (!exceptionPending) { + napi_is_exception_pending(env, &exceptionPending); + if (exceptionPending) { + napi_value error; + napi_get_and_clear_last_exception(env, &error); + throw NativeScriptException(env, error, "Error calling js method: " + methodName); + } + } + } + + + return result; +} + +napi_value CallbackHandlers::FindClass(napi_env env, const char *name) { + napi_value clazz = nullptr; + JEnv jEnv; + jclass javaClass = jEnv.FindClass(name); + if (jEnv.ExceptionCheck() == JNI_FALSE) { + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + jint javaObjectID = objectManager->GetOrCreateObjectId(javaClass); + clazz = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (clazz == nullptr) { + clazz = objectManager->CreateJSWrapper(javaObjectID, "Ljava/lang/Class;", javaClass); + } + } + return clazz; +} + +int CallbackHandlers::GetArrayLength(napi_env env, napi_value arr) { + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + JEnv jEnv; + + auto javaArr = objectManager->GetJavaObjectByJsObjectFast(arr); + + auto length = jEnv.GetArrayLength(javaArr); + + return length; +} + +jobjectArray CallbackHandlers::GetJavaStringArray(JEnv &jEnv, int length) { + if (length > CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH) { + stringstream ss; + ss << "You are trying to override more methods than the limit of " + << CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH; + throw NativeScriptException(ss.str()); + } + + JniLocalRef tmpArr(jEnv.NewObjectArray(length, JAVA_LANG_STRING, nullptr)); + return (jobjectArray) jEnv.NewGlobalRef(tmpArr); +} + +CallbackHandlers::func_AChoreographer_getInstance AChoreographer_getInstance_; + +CallbackHandlers::func_AChoreographer_postFrameCallback AChoreographer_postFrameCallback_; +CallbackHandlers::func_AChoreographer_postFrameCallbackDelayed AChoreographer_postFrameCallbackDelayed_; + +CallbackHandlers::func_AChoreographer_postFrameCallback64 AChoreographer_postFrameCallback64_; +CallbackHandlers::func_AChoreographer_postFrameCallbackDelayed64 AChoreographer_postFrameCallbackDelayed64_; + +void CallbackHandlers::PostCallback(napi_env env, napi_callback_info info, + CallbackHandlers::FrameCallbackCacheEntry *entry) { + size_t argc = 2; + napi_value args[2]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + ALooper_prepare(0); + auto instance = AChoreographer_getInstance_(); + napi_value delay = args[1]; + napi_valuetype delayType; + napi_typeof(env, delay, &delayType); + + if (android_get_device_api_level() >= 29) { + if (delayType == napi_number) { + uint32_t delayValue; + napi_get_value_uint32(env, delay, &delayValue); + AChoreographer_postFrameCallbackDelayed64_(instance, entry->frameCallback64_, entry, + delayValue); + } else { + AChoreographer_postFrameCallback64_(instance, entry->frameCallback64_, entry); + } + } else { + if (delayType == napi_number) { + int64_t delayValue; + napi_get_value_int64(env, delay, &delayValue); + AChoreographer_postFrameCallbackDelayed_(instance, entry->frameCallback_, entry, + static_cast(delayValue)); + } else { + AChoreographer_postFrameCallback_(instance, entry->frameCallback_, entry); + } + } +} + +napi_value CallbackHandlers::PostFrameCallback(napi_env env, napi_callback_info info) { + if (android_get_device_api_level() >= 24) { + InitChoreographer(); + + size_t argc = 2; + napi_value args[2]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + if (argc < 1) { + napi_throw_type_error(env, nullptr, "Frame callback argument is not a function"); + return nullptr; + } + + napi_valuetype argType; + napi_typeof(env, args[0], &argType); + if (argType != napi_function) { + napi_throw_type_error(env, nullptr, "Frame callback argument is not a function"); + return nullptr; + } + + napi_value func = args[0]; + + napi_value idKey; + napi_create_string_utf8(env, "_postFrameCallbackId", NAPI_AUTO_LENGTH, &idKey); + + napi_value pId; + napi_get_property(env, func, idKey, &pId); + + napi_valuetype pIdType; + napi_typeof(env, pId, &pIdType); + if (pIdType == napi_number) { + int32_t id; + napi_get_value_int32(env, pId, &id); + auto cb = frameCallbackCache_.find(id); + if (cb != frameCallbackCache_.end()) { + bool shouldReschedule = !cb->second.isScheduled(); + cb->second.markScheduled(); + if (shouldReschedule) { + PostCallback(env, info, &cb->second); + } + return nullptr; + } + } + + uint64_t key = ++frameCallbackCount_; + + napi_value keyValue; + napi_create_int64(env, key, &keyValue); + napi_set_property(env, func, idKey, keyValue); + + auto [val, inserted] = frameCallbackCache_.try_emplace(key, env, func, key); + assert(inserted && "Frame callback ID should not be duplicated"); + + val->second.markScheduled(); + PostCallback(env, info, &val->second); + + } + return nullptr; +} + +napi_value CallbackHandlers::RemoveFrameCallback(napi_env env, napi_callback_info info) { + if (android_get_device_api_level() >= 24) { + InitChoreographer(); + + size_t argc = 1; + napi_value args[1]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + if (argc < 1) { + napi_throw_type_error(env, nullptr, "Frame callback argument is not a function"); + return nullptr; + } + + napi_valuetype argType; + napi_typeof(env, args[0], &argType); + if (argType != napi_function) { + napi_throw_type_error(env, nullptr, "Frame callback argument is not a function"); + return nullptr; + } + + napi_value func = args[0]; + + napi_value idKey; + napi_create_string_utf8(env, "_postFrameCallbackId", NAPI_AUTO_LENGTH, &idKey); + + napi_value pId; + napi_get_property(env, func, idKey, &pId); + + if (pId != nullptr && napi_util::is_of_type(env, pId, napi_number)) { + int32_t id; + napi_get_value_int32(env, pId, &id); + auto cb = frameCallbackCache_.find(id); + if (cb != frameCallbackCache_.end()) { + cb->second.markRemoved(); + } + } + } + return nullptr; +} + +void CallbackHandlers::InitChoreographer() { + if (AChoreographer_getInstance_ == nullptr) { + void *lib = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL); + if (lib != nullptr) { + AChoreographer_getInstance_ = reinterpret_cast( + dlsym(lib, "AChoreographer_getInstance")); + AChoreographer_postFrameCallback_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallback")); + AChoreographer_postFrameCallbackDelayed_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallbackDelayed")); + + assert(AChoreographer_getInstance_); + assert(AChoreographer_postFrameCallback_); + assert(AChoreographer_postFrameCallbackDelayed_); + + if (android_get_device_api_level() >= 29) { + AChoreographer_postFrameCallback64_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallback64")); + AChoreographer_postFrameCallbackDelayed64_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallbackDelayed64")); + + assert(AChoreographer_postFrameCallback64_); + assert(AChoreographer_postFrameCallbackDelayed64_); + } + } + } +} + +void CallbackHandlers::RemoveEnvEntries(napi_env env) { + for (auto it = cache_.begin(); it != cache_.end();) { + if (it->second.env_ == env) { + it = cache_.erase(it); + } else { + ++it; + } + } + + for (auto it = frameCallbackCache_.begin(); it != frameCallbackCache_.end();) { + if (it->second.env == env) { + it = frameCallbackCache_.erase(it); + } else { + ++it; + } + } +} + +// Worker + +napi_value CallbackHandlers::NewThreadCallback(napi_env env, napi_callback_info info) { + try { + NAPI_CALLBACK_BEGIN_VARGS() + + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + if (napi_util::is_null_or_undefined(env, newTarget)) { + throw NativeScriptException("Worker should be called as a constructor!"); + } + + if (argc != 1) { + throw NativeScriptException( + "Worker should be called with one parameter (name of file to run) or a URL/URL OBJECT to the file"); + } + + napi_valuetype value_type; + napi_typeof(env, argv[0], &value_type); + + if (value_type != napi_string && value_type != napi_object) { + throw NativeScriptException( + "Worker should be called with one parameter (name of file to run) or a URL/URL OBJECT to the file"); + } + + napi_value workerFilePath; + std::string baseurl_str; + if (value_type == napi_object) { + napi_get_named_property(env, argv[0], "href", &workerFilePath); + if (napi_util::is_null_or_undefined(env, workerFilePath)) { + throw NativeScriptException( + "Worker should be called with one parameter (name of file to run) or a URL to the file"); + } + } else { + workerFilePath = argv[0]; + } + + + + napi_value global; + napi_get_global(env, &global); + + auto frames = tns::BuildStacktraceFrames(env, nullptr, 1); + string currentExecutingScriptNameStr = + frames.size() < 3 ? frames[0].filename : frames[2].filename; + + auto lastForwardSlash = currentExecutingScriptNameStr.find_last_of("/"); + auto currentDir = currentExecutingScriptNameStr.substr(0, lastForwardSlash + 1); + std::string fileSchema("file://"); + if (currentDir.compare(0, fileSchema.length(), fileSchema) == 0) { + currentDir = currentDir.substr(fileSchema.length()); + } + + std::string workerPath = ArgConverter::ConvertToString(env, workerFilePath); + + if (workerPath.compare(0, fileSchema.length(), fileSchema) == 0) { + workerPath = workerPath.substr(fileSchema.length()); + auto workerPathPrefix = workerPath.substr(0, 1) == "/" ? "~" : "~/"; + workerPath = workerPathPrefix + workerPath; + } + + DEBUG_WRITE("Worker Path: %s, Current Dir: %s", workerPath.c_str(), currentDir.c_str()); + + + + // Will throw if path is invalid or doesn't exist + ModuleInternal::CheckFileExists(env, workerPath, currentDir); + + auto workerId = nextWorkerId++; + napi_value workerIdValue; + napi_create_int32(env, workerId, &workerIdValue); + napi_set_named_property(env, jsThis, "workerId", workerIdValue); + + id2WorkerMap.emplace(workerId, napi_util::make_ref(env, jsThis)); + + DEBUG_WRITE("Called Worker constructor id=%d", workerId); + + JEnv jEnv; + JniLocalRef filePath(jEnv.NewStringUTF(workerPath.c_str())); + JniLocalRef dirPath(jEnv.NewStringUTF(currentDir.c_str())); + jEnv.CallStaticVoidMethod(RUNTIME_CLASS, INIT_WORKER_METHOD_ID, (jstring) filePath, + (jstring) dirPath, workerId); + + napi_value stack; + napi_value error; + napi_value empty; + napi_create_string_utf8(env, "",0, &empty); + napi_create_error(env, empty, empty, &error); + napi_get_named_property(env, error, "stack", &stack); + napi_set_named_property(env, jsThis, "__stack__", stack); + + return jsThis; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value +CallbackHandlers::WorkerObjectPostMessageCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS(); + + try { + if (argc != 1) { + NativeScriptException exception( + "Failed to execute 'postMessage' on 'Worker': 1 argument required."); + throw exception; + } + + napi_value isTerminated; + napi_get_named_property(env, jsThis, "isTerminated", &isTerminated); + if (!napi_util::is_null_or_undefined(env, isTerminated)) { + bool terminated; + napi_get_value_bool(env, isTerminated, &terminated); + if (terminated) { + return nullptr; + } + } + + std::string msg = tns::JsonStringifyObject(env, argv[0], false); + + // get worker's ID that is associated on the other side - in Java + napi_value jsId; + napi_get_named_property(env, jsThis, "workerId", &jsId); + auto id = napi_util::get_int32(env, jsId); + + JEnv jEnv; + + jstring jmsg = jEnv.NewStringUTF(msg.c_str()); + JniLocalRef jmsgRef(jmsg); + + jEnv.CallStaticVoidMethod(RUNTIME_CLASS, SEND_MESSAGE_TO_WORKER_METHOD_ID, id, + (jstring) jmsgRef); + + DEBUG_WRITE( + "MAIN: WorkerObjectPostMessageCallback called postMessage on Worker object(id=%d)", + id); + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +void CallbackHandlers::WorkerGlobalOnMessageCallback(napi_env env, jstring message) { + NapiScope scope(env); + try { + napi_value globalObject; + napi_get_global(env, &globalObject); + + napi_value callback; + napi_get_named_property(env, globalObject, "onmessage", &callback); + + if (napi_util::is_of_type(env, callback, napi_function)) { + std::string msgString = ArgConverter::jstringToString(message); + napi_value dataObject = tns::JsonParseString(env, msgString.c_str()); + + napi_value obj; + napi_create_object(env, &obj); + if (napi_util::is_of_type(env, dataObject, napi_object)) { + napi_set_named_property(env, obj, "data", dataObject); + } + + napi_value args[1] = { + obj + }; + + napi_value result; + napi_status status = napi_call_function(env, globalObject, callback, 1, args, &result); + if (status == napi_pending_exception) { + napi_value error; + napi_get_and_clear_last_exception(env, &error); + CallWorkerScopeOnErrorHandle(env, error); + } + } else { + DEBUG_WRITE( + "WORKER: WorkerGlobalOnMessageCallback couldn't fire a worker's `onmessage` callback because it isn't implemented!"); + } + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } +} + + +napi_value +CallbackHandlers::WorkerGlobalPostMessageCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + try { + if (argc != 1) { + napi_throw_error(env, nullptr, + "Failed to execute 'postMessage' on WorkerGlobalScope: 1 argument required."); + return nullptr; + } + + bool pendingException; + napi_is_exception_pending(env, &pendingException); + if (pendingException) { + napi_value err; + napi_get_and_clear_last_exception(env, &err); + CallWorkerScopeOnErrorHandle(env, err); + } + + napi_value objToStringify = argv[0]; + std::string msg = tns::JsonStringifyObject(env, objToStringify, false); + + JEnv jenv; + auto jmsg = jenv.NewStringUTF(msg.c_str()); + JniLocalRef jmsgRef(jmsg); + + jenv.CallStaticVoidMethod(RUNTIME_CLASS, SEND_MESSAGE_TO_MAIN_METHOD_ID, (jstring) jmsgRef); + + DEBUG_WRITE("WORKER: WorkerGlobalPostMessageCallback called."); + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +void CallbackHandlers::WorkerObjectOnMessageCallback(napi_env env, jint workerId, jstring message) { + NapiScope scope(env); + try { + + auto workerFound = CallbackHandlers::id2WorkerMap.find(workerId); + + if (workerFound == CallbackHandlers::id2WorkerMap.end()) { + DEBUG_WRITE( + "MAIN: WorkerObjectOnMessageCallback no worker instance was found with workerId=%d.", + workerId); + return; + } + + napi_ref workerPersistent = workerFound->second; + + napi_value worker; + napi_get_reference_value(env, workerPersistent, &worker); + + napi_value global; + napi_get_global(env, &global); + + napi_value callback; + napi_get_named_property(env, worker, "onmessage", &callback); + + if (napi_util::is_of_type(env, callback, napi_function)) { + std::string msgString = ArgConverter::jstringToString(message); + + napi_value dataObject = tns::JsonParseString(env, msgString.c_str()); + + napi_value obj; + napi_create_object(env, &obj); + napi_set_named_property(env, obj, "data", dataObject); + + napi_value args[1] = {obj}; + + napi_value result; + napi_status status = napi_call_function(env, worker, callback, 1, args, &result); + if (status != napi_ok) { + throw NativeScriptException("Error calling onmessage callback"); + } + } else { + DEBUG_WRITE( + "MAIN: WorkerObjectOnMessageCallback couldn't fire a worker(id=%d) object's `onmessage` callback because it isn't implemented.", + workerId); + } + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } +} + +napi_value CallbackHandlers::WorkerObjectTerminateCallback(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value thiz; + napi_get_cb_info(env, info, &argc, nullptr, &thiz, nullptr); + + DEBUG_WRITE("WORKER: WorkerObjectTerminateCallback called."); + + try { + napi_value global; + napi_get_global(env, &global); + + napi_value jsId; + napi_get_named_property(env, thiz, "workerId", &jsId); + + int32_t id; + napi_get_value_int32(env, jsId, &id); + + napi_value isTerminated; + napi_get_named_property(env, thiz, "isTerminated", &isTerminated); + if (!napi_util::is_null_or_undefined(env, isTerminated)) { + bool terminated; + napi_get_value_bool(env, isTerminated, &terminated); + if (terminated) { + return nullptr; + } + } + + napi_value trueValue; + napi_get_boolean(env, true, &trueValue); + napi_set_named_property(env, thiz, "isTerminated", trueValue); + + JEnv jenv; + jenv.CallStaticVoidMethod(RUNTIME_CLASS, TERMINATE_WORKER_METHOD_ID, id); + + CallbackHandlers::ClearWorkerPersistent(env, id); + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value CallbackHandlers::WorkerGlobalCloseCallback(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value thiz; + napi_get_cb_info(env, info, &argc, nullptr, &thiz, nullptr); + + DEBUG_WRITE("WORKER: WorkerThreadCloseCallback called."); + + try { + napi_value global; + napi_get_global(env, &global); + + napi_value isTerminated; + napi_get_named_property(env, global, "isTerminating", &isTerminated); + if (!napi_util::is_null_or_undefined(env, isTerminated)) { + bool terminated; + napi_get_value_bool(env, isTerminated, &terminated); + if (terminated) { + return nullptr; + } + } + + napi_value trueValue; + napi_get_boolean(env, true, &trueValue); + napi_set_named_property(env, global, "isTerminating", trueValue); + + napi_value callback; + napi_get_named_property(env, global, "onclose", &callback); + if (napi_util::is_of_type(env, callback, napi_function)) { + napi_value result; + napi_call_function(env, global, callback, 0, nullptr, &result); + } + + bool pendingException; + napi_is_exception_pending(env, &pendingException); + if (pendingException) { + napi_value err; + napi_get_and_clear_last_exception(env, &err); + CallWorkerScopeOnErrorHandle(env, err); + } + + JEnv jenv; + jenv.CallStaticVoidMethod(RUNTIME_CLASS, WORKER_SCOPE_CLOSE_METHOD_ID); + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return napi_util::undefined(env); +} + +void CallbackHandlers::CallWorkerScopeOnErrorHandle(napi_env env, napi_value error) { + try { + napi_value global; + napi_get_global(env, &global); + + napi_value callback; + napi_get_named_property(env, global, "onerror", &callback); + + napi_value message = nullptr; + napi_value stack = nullptr; + std::vector frames; + if (napi_util::is_of_type(env, error, napi_object)) { + frames = tns::BuildStacktraceFrames(env, error, 1); + napi_get_named_property(env, error, "message", &message); + napi_get_named_property(env, error, "stack", &stack); + } else { + napi_coerce_to_string(env, error, &message); + napi_create_string_utf8(env, "", 0, &stack); + } + + if (napi_util::is_of_type(env, callback, napi_function)) { + napi_value args[1] = {error}; + napi_value result; + napi_call_function(env, global, callback, 1, args, &result); + + bool pendingException; + napi_is_exception_pending(env, &pendingException); + if (pendingException) { + napi_value perror = nullptr; + napi_value pmessage = nullptr; + napi_value pstack = nullptr; + napi_get_and_clear_last_exception(env, &perror); + + std::vector pframes; + if (napi_util::is_of_type(env, perror, napi_object)) { + pframes = tns::BuildStacktraceFrames(env, perror, 1); + napi_get_named_property(env, perror, "message", &pmessage); + napi_get_named_property(env, perror, "stack", &pstack); + } else { + napi_coerce_to_string(env, perror, &pmessage); + napi_create_string_utf8(env, "", 0, &pstack); + } + + auto line = 0; + std::string filename; + if (!pframes.empty()) { + line = pframes[0].line; + filename = pframes[0].filename; + } + Runtime::GetRuntime(env)->PassUncaughtExceptionFromWorkerToMainHandler( + pmessage, + pstack, + ArgConverter::convertToJsString(env, filename), + line + ); + } else if (!napi_util::is_null_or_undefined(env, result)) { + bool handled; + napi_get_value_bool(env, result, &handled); + if (handled) { + return; + } + } + } + + auto line = 0; + std::string filename; + if (!frames.empty()) { + line = frames[0].line; + filename = frames[0].filename; + } + Runtime::GetRuntime(env)->PassUncaughtExceptionFromWorkerToMainHandler( + message, + stack, + ArgConverter::convertToJsString(env, filename), + line + ); + + + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } +} + +void CallbackHandlers::CallWorkerObjectOnErrorHandle(napi_env env, jint workerId, jstring message, + jstring stackTrace, jstring filename, + jint lineno, jstring threadName) { + NapiScope scope(env); + try { + auto workerFound = CallbackHandlers::id2WorkerMap.find(workerId); + + if (workerFound == CallbackHandlers::id2WorkerMap.end()) { + DEBUG_WRITE( + "MAIN: CallWorkerObjectOnErrorHandle no worker instance was found with workerId=%d.", + workerId); + return; + } + + napi_ref workerPersistent = workerFound->second; + + napi_value worker; + napi_get_reference_value(env, workerPersistent, &worker); + + napi_value callback; + napi_get_named_property(env, worker, "onerror", &callback); + + if (napi_util::is_of_type(env, callback, napi_function)) { + napi_value errEvent; + napi_value msgValue = ArgConverter::jstringToJsString(env, message); + napi_value codeValue; + napi_create_string_utf8(env, "",0, &codeValue); + napi_create_error(env,codeValue, msgValue, &errEvent); + napi_value stack_main_thread; + napi_get_named_property(env, worker, "__stack__", &stack_main_thread); + std::string main_stack = napi_util::get_string_value(env, stack_main_thread); + + std::string curr_stack = ArgConverter::jstringToString(stackTrace); + + std::string full_stack = curr_stack + "\n" + main_stack.substr(main_stack.find_first_of("\n") + 1) ; + + napi_value full_stack_value; + napi_create_string_utf8(env, full_stack.c_str(), full_stack.size(), &full_stack_value); + + napi_set_named_property(env, errEvent, "stack", full_stack_value); + + napi_value args[1] = {errEvent}; + + napi_value result; + napi_status status = napi_call_function(env, worker, callback, 1, args, &result); + if (status != napi_ok) { + napi_value exception; + napi_get_and_clear_last_exception(env, &exception); + if (!napi_util::is_null_or_undefined(env, exception)) { + throw NativeScriptException(env, exception, + "Error calling onerror on Worker Object"); + } else { + throw NativeScriptException("Error calling onerror on Worker Object"); + } + } + + bool handled; + napi_get_value_bool(env, result, &handled); + + if (handled) { + return; + } + } + + // Exception wasn't handled, or is critical -> Throw exception + std::string strMessage = ArgConverter::jstringToString(message); + std::string strFilename = ArgConverter::jstringToString(filename); + std::string strThreadname = ArgConverter::jstringToString(threadName); + std::string strStackTrace = ArgConverter::jstringToString(stackTrace); + + DEBUG_WRITE( + "Unhandled exception in '%s' thread. file: %s, line %d, message: %s\nStackTrace: %s", + strThreadname.c_str(), strFilename.c_str(), lineno, strMessage.c_str(), + strStackTrace.c_str()); + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } +} + +void CallbackHandlers::ClearWorkerPersistent(napi_env env, int workerId) { + NapiScope scope(env); + DEBUG_WRITE("ClearWorkerPersistent called for workerId=%d", workerId); + + auto workerFound = CallbackHandlers::id2WorkerMap.find(workerId); + + if (workerFound == CallbackHandlers::id2WorkerMap.end()) { + DEBUG_WRITE( + "MAIN | WORKER: ClearWorkerPersistent no worker instance was found with workerId=%d ! The worker may already be terminated.", + workerId); + return; + } + + napi_ref workerPersistent = workerFound->second; + napi_delete_reference(env, workerPersistent); + + id2WorkerMap.erase(workerId); +} + +void CallbackHandlers::TerminateWorkerThread(napi_env env) { + JSEnterScope + try { + Runtime::GetRuntime(env)->DestroyRuntime(); + } catch (NativeScriptException &e) { + e.ReThrowToJava(nullptr); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJava(nullptr); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(nullptr); + } + +} + +robin_hood::unordered_map CallbackHandlers::cache_; +robin_hood::unordered_map CallbackHandlers::jclass_to_runtimeId_cache; + +robin_hood::unordered_map CallbackHandlers::frameCallbackCache_; + +std::atomic_int64_t CallbackHandlers::count_ = {0}; +std::atomic_uint64_t CallbackHandlers::frameCallbackCount_ = {0}; + +int CallbackHandlers::nextWorkerId = 0; +int CallbackHandlers::lastCallId = -1; +napi_value CallbackHandlers::lastCallValue = nullptr; +robin_hood::unordered_map CallbackHandlers::id2WorkerMap; + +short CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH = 100; +jclass CallbackHandlers::RUNTIME_CLASS = nullptr; +jclass CallbackHandlers::JAVA_LANG_STRING = nullptr; +jfieldID CallbackHandlers::CURRENT_OBJECTID_FIELD_ID = nullptr; +jmethodID CallbackHandlers::RESOLVE_CLASS_METHOD_ID = nullptr; +jmethodID CallbackHandlers::MAKE_INSTANCE_STRONG_ID = nullptr; +jmethodID CallbackHandlers::GET_TYPE_METADATA = nullptr; +jmethodID CallbackHandlers::ENABLE_VERBOSE_LOGGING_METHOD_ID = nullptr; +jmethodID CallbackHandlers::DISABLE_VERBOSE_LOGGING_METHOD_ID = nullptr; +jmethodID CallbackHandlers::INIT_WORKER_METHOD_ID = nullptr; +jmethodID CallbackHandlers::SEND_MESSAGE_TO_MAIN_METHOD_ID = nullptr; +jmethodID CallbackHandlers::SEND_MESSAGE_TO_WORKER_METHOD_ID = nullptr; +jmethodID CallbackHandlers::TERMINATE_WORKER_METHOD_ID = nullptr; +jmethodID CallbackHandlers::WORKER_SCOPE_CLOSE_METHOD_ID = nullptr; + +NumericCasts CallbackHandlers::castFunctions; + +ArrayElementAccessor CallbackHandlers::arrayElementAccessor; +FieldAccessor CallbackHandlers::fieldAccessor; diff --git a/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h new file mode 100644 index 000000000..4d7e4d1d5 --- /dev/null +++ b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h @@ -0,0 +1,418 @@ +#ifndef CALLBACKHANDLERS_H_ +#define CALLBACKHANDLERS_H_ + +#include +#include +#include +#include "JEnv.h" +#include "ArgsWrapper.h" +#include "MetadataEntry.h" +#include "FieldCallbackData.h" +#include "MetadataTreeNode.h" +#include "NumericCasts.h" +#include "FieldAccessor.h" +#include "ArrayElementAccessor.h" +#include "ObjectManager.h" +#include "robin_hood.h" +#include +#include "NativeScriptAssert.h" +#include "NativeScriptException.h" +#include "Runtime.h" + +namespace tns { + class CallbackHandlers { + public: + /* + * Stores persistent handles of all 'Worker' objects initialized on the main thread + * Note: No isolates different than that of the main thread should access this map + */ + static robin_hood::unordered_map id2WorkerMap; + + static int nextWorkerId; + + static void Init(napi_env env); + + static napi_value + CreateJSWrapper(napi_env env, jint javaObjectID, const std::string &typeName); + + static bool RegisterInstance(napi_env env, napi_value jsObject, + const std::string &fullClassName, + const ArgsWrapper &argWrapper, + napi_value implementationObject, + bool isInterface, + napi_value *jsThisProxy, + const std::string &baseClassName = std::string()); + + static jclass ResolveClass(napi_env env, const std::string &baseClassName, + const std::string &fullClassName, + napi_value implementationObject, + bool isInterface); + + static std::string ResolveClassName(napi_env env, jclass &clazz); + + static napi_value + GetArrayElement(napi_env env, napi_value array, uint32_t index, + const std::string &arraySignature); + + static void + SetArrayElement(napi_env env, napi_value array, uint32_t index, + const std::string &arraySignature, napi_value value); + + static int GetArrayLength(napi_env env, napi_value arr); + + static napi_value + CallJavaMethod(napi_env env, napi_value caller, const std::string &className, + const std::string &methodName, MetadataEntry *entry, bool isFromInterface, + bool isStatic, napi_callback_info info, size_t argc, napi_value* argv); + + static napi_value + CallJSMethod(napi_env env, JNIEnv *jEnv, napi_value jsObject,jclass claz, + const std::string &methodName,int javaObjectId, jobjectArray args); + static napi_value + GetJavaField(napi_env env, napi_value caller, + FieldCallbackData *fieldData); + + static void SetJavaField(napi_env env, napi_value target, + napi_value value, FieldCallbackData *fieldData); + + static napi_value RunOnMainThreadCallback(napi_env env, napi_callback_info info); + + static int RunOnMainThreadFdCallback(int fd, int events, void *data); + + static napi_value LogMethodCallback(napi_env env, napi_callback_info info); + + static napi_value TimeCallback(napi_env env, napi_callback_info info); + + static napi_value + DumpReferenceTablesMethodCallback(napi_env env, napi_callback_info info); + + static napi_value DrainMicrotaskCallback(napi_env env, napi_callback_info info); + + static void DumpReferenceTablesMethod(); + + static napi_value ExitMethodCallback(napi_env env, napi_callback_info info); + + static void CreateGlobalCastFunctions(napi_env env); + + static std::vector GetTypeMetadata(const std::string &name, int index); + + /* + * Gets all methods in the implementation object, and packs them in a jobjectArray + * to pass them to Java Land, so that their corresponding Java callbacks are written when + * the dexFactory generates the class + */ + static jobjectArray + GetMethodOverrides(napi_env env, JEnv &jEnv, napi_value implementationObject); + + /* + * Gets all interfaces declared in the 'interfaces' array inside the implementation object, + * and packs them in a jobjectArray to pass them to Java Land, so that they may be + * implemented when the dexFactory generates the corresponding class + */ + static jobjectArray + GetImplementedInterfaces(napi_env env, JEnv &jEnv, napi_value implementationObject); + + static napi_value + EnableVerboseLoggingMethodCallback(napi_env env, napi_callback_info info); + + static napi_value + DisableVerboseLoggingMethodCallback(napi_env env, napi_callback_info info); + + static napi_value ReleaseNativeCounterpartCallback(napi_env env, napi_callback_info info); + + static napi_value FindClass(napi_env env, const char *name); + + static napi_value NewThreadCallback(napi_env env, napi_callback_info info); + + /* + * main -> worker messaging + * Fired when a Worker instance's postMessage is called + */ + static napi_value WorkerObjectPostMessageCallback(napi_env env, napi_callback_info info); + + /* + * main -> worker messaging + * Fired when worker object has "postMessage" and the worker has implemented "onMessage" handler + * In case "onMessage" handler isn't implemented no exception is thrown + */ + static void WorkerGlobalOnMessageCallback(napi_env env, jstring message); + + /* + * worker -> main thread messaging + * Fired when a Worker script's "postMessage" is called + */ + static napi_value WorkerGlobalPostMessageCallback(napi_env env, napi_callback_info info); + + /* + * worker -> main messaging + * Fired when worker has sent a message to main and the worker object has implemented "onMessage" handler + * In case "onMessage" handler isn't implemented no exception is thrown + */ + static void WorkerObjectOnMessageCallback(napi_env env, jint workerId, jstring message); + + /* + * Fired when a Worker instance's terminate is called (immediately stops execution of the thread) + */ + static napi_value WorkerObjectTerminateCallback(napi_env env, napi_callback_info info); + + /* + * Fired when a Worker script's close is called + */ + static napi_value WorkerGlobalCloseCallback(napi_env env, napi_callback_info info); + + /* + * Clears the persistent Worker object handle associated with a workerId + * Occurs when calling a worker object's `terminate` or a worker thread's global scope `close` + */ + static void ClearWorkerPersistent(napi_env env, int workerId); + + /* + * Terminates the currently executing Isolate. No scripts can be executed after this call + */ + static void TerminateWorkerThread(napi_env env); + + /* + * Is called when an unhandled exception is thrown inside the worker + * Will execute 'onerror' if one is provided inside the Worker Scope + * Will make the exception "bubble up" through to main, to be handled by the Worker Object + * if 'onerror' isn't implemented or returns false + */ + static void CallWorkerScopeOnErrorHandle(napi_env env, napi_value tc); + + /* + * Is called when an unhandled exception bubbles up from the worker scope to the main thread Worker Object + * Will execute `onerror` if one is implemented for the Worker Object instance + * Will throw a NativeScript Exception if 'onerror' isn't implemented or returns false + */ + static void CallWorkerObjectOnErrorHandle(napi_env env, jint workerId, jstring message, + jstring stackTrace, jstring filename, jint lineno, + jstring threadName); + + static napi_value PostFrameCallback(napi_env env, napi_callback_info info); + + static napi_value RemoveFrameCallback(napi_env env, napi_callback_info info); + + static void RemoveEnvEntries(napi_env env); + + struct AChoreographer; + + typedef void (*AChoreographer_frameCallback)(long frameTimeNanos, void *data); + + typedef void (*AChoreographer_frameCallback64)(int64_t frameTimeNanos, void *data); + + typedef AChoreographer *(*func_AChoreographer_getInstance)(); + + typedef void (*func_AChoreographer_postFrameCallback)( + AChoreographer *choreographer, AChoreographer_frameCallback callback, + void *data); + + typedef void (*func_AChoreographer_postFrameCallback64)( + AChoreographer *choreographer, AChoreographer_frameCallback64 callback, + void *data); + + typedef void (*func_AChoreographer_postFrameCallbackDelayed)( + AChoreographer *choreographer, AChoreographer_frameCallback callback, + void *data, long delayMillis); + + typedef void (*func_AChoreographer_postFrameCallbackDelayed64)( + AChoreographer *choreographer, AChoreographer_frameCallback64 callback, + void *data, uint32_t delayMillis); + + + static jint lastCallId; + static napi_value lastCallValue; + + private: + CallbackHandlers() { + } + + static void AdjustAmountOfExternalAllocatedMemory(napi_env napiEnv); + + /* + * Helper method that creates a java string array for sending strings over JNI + */ + static jobjectArray GetJavaStringArray(JEnv &jEnv, int length); + + static void + validateProvidedArgumentsLength(napi_env env, napi_callback_info info, int expectedSize); + + static short MAX_JAVA_STRING_ARRAY_LENGTH; + + static jclass RUNTIME_CLASS; + + static jclass JAVA_LANG_STRING; + + static jmethodID RESOLVE_CLASS_METHOD_ID; + + static jfieldID CURRENT_OBJECTID_FIELD_ID; + + static jmethodID MAKE_INSTANCE_STRONG_ID; + + static jmethodID GET_TYPE_METADATA; + + static jmethodID ENABLE_VERBOSE_LOGGING_METHOD_ID; + + static jmethodID DISABLE_VERBOSE_LOGGING_METHOD_ID; + + static jmethodID INIT_WORKER_METHOD_ID; + + static jmethodID SEND_MESSAGE_TO_WORKER_METHOD_ID; + static jmethodID SEND_MESSAGE_TO_MAIN_METHOD_ID; + static jmethodID TERMINATE_WORKER_METHOD_ID; + static jmethodID WORKER_SCOPE_CLOSE_METHOD_ID; + + static NumericCasts castFunctions; + + static ArrayElementAccessor arrayElementAccessor; + + static FieldAccessor fieldAccessor; + + struct JavaObjectIdScope { + JavaObjectIdScope(JEnv &_jEnv, jfieldID fieldId, jobject runtime, int javaObjectId) + : jEnv(_jEnv), _fieldID(fieldId), _runtime(runtime) { + jEnv.SetIntField(_runtime, _fieldID, javaObjectId); + } + + ~JavaObjectIdScope() { + jEnv.SetIntField(_runtime, _fieldID, -1); + } + + private: + JEnv jEnv; + jfieldID _fieldID; + jobject _runtime; + }; + + static std::atomic_int64_t count_; + + struct Callback { + Callback() {} + + Callback(uint64_t id) + : id_(id) { + } + + uint64_t id_; + }; + + struct CacheEntry { + CacheEntry(napi_env env, napi_value callback) + : env_(env) { + napi_create_reference(env, callback, 1, &callback_); + } + + ~CacheEntry() { + napi_delete_reference(env_, callback_); + } + + napi_env env_; + napi_ref callback_; + }; + + static robin_hood::unordered_map cache_; + + static robin_hood::unordered_map jclass_to_runtimeId_cache; + + static std::atomic_uint64_t frameCallbackCount_; + + struct FrameCallbackCacheEntry { + FrameCallbackCacheEntry(napi_env _env, napi_value callback_, uint64_t aId) + : env(_env), + id(aId) { + napi_create_reference(env, callback_, 1, &callback); + } + + ~FrameCallbackCacheEntry() { + napi_delete_reference(env, callback); + } + + napi_env env; + napi_ref callback; + uint64_t id; + + bool isScheduled() { + return scheduled; + } + + void markScheduled() { + scheduled = true; + removed = false; + } + + void markRemoved() { + // we can never unschedule a callback, so we just mark it as removed + removed = true; + uint32_t result; + } + + AChoreographer_frameCallback frameCallback_ = [](long ts, void *data) { + execute((double) ts, data); + }; + + AChoreographer_frameCallback64 frameCallback64_ = [](int64_t ts, void *data) { + execute((double) ts, data); + }; + + static void execute(double ts, void *data) { + if (data != nullptr) { + + auto entry = static_cast(data); + if (entry->shouldRemoveBeforeCall()) { + frameCallbackCache_.erase(entry->id); // invalidates *entry + return; + } + napi_env env = entry->env; + NapiScope scope(env); + napi_value cb = napi_util::get_ref_value(env, entry->callback); + + napi_value global; + napi_get_global(env, &global); + + entry->markUnscheduled(); + + napi_value args[1]; + napi_create_double(env, ts, &args[0]); + + napi_valuetype type; + napi_typeof(env, cb, &type); + + napi_value result; + napi_call_function(env, global, cb, 1, args, &result); + + + // check if we should remove it (it should be both unscheduled and removed) + if (entry->shouldRemoveAfterCall()) { + frameCallbackCache_.erase(entry->id); // invalidates *entry + } + } + } + + private: + bool removed = false; + bool scheduled = false; + + void markUnscheduled() { + scheduled = false; + removed = true; + } + + bool shouldRemoveBeforeCall() { + return removed; + } + + bool shouldRemoveAfterCall() { + return !scheduled && removed; + } + }; + + static robin_hood::unordered_map frameCallbackCache_; + + static void InitChoreographer(); + + static void PostCallback(napi_env env, napi_callback_info info, + FrameCallbackCacheEntry *entry); + + }; +} + +#endif /* CALLBACKHANDLERS_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/constants/Constants.cpp b/NativeScript/ffi/jni/napi/constants/Constants.cpp new file mode 100644 index 000000000..b957a7eea --- /dev/null +++ b/NativeScript/ffi/jni/napi/constants/Constants.cpp @@ -0,0 +1,12 @@ +/* + * Constants.cpp + * + * Created on: Nov 6, 2015 + * Author: gatanasov + */ + +#include "Constants.h" + +std::string Constants::APP_ROOT_FOLDER_PATH = ""; +bool Constants::CACHE_COMPILED_CODE = false; + diff --git a/NativeScript/ffi/jni/napi/constants/Constants.h b/NativeScript/ffi/jni/napi/constants/Constants.h new file mode 100644 index 000000000..7f41e29c5 --- /dev/null +++ b/NativeScript/ffi/jni/napi/constants/Constants.h @@ -0,0 +1,33 @@ +#ifndef CONSTANTS_H_ +#define CONSTANTS_H_ + +#include + +#define PROP_KEY_EXTEND "extend" +#define PROP_KEY_NULLOBJECT "null" +#define PROP_KEY_NULL_NODE_NAME "nullNode" +#define PROP_KEY_VALUEOF "valueOf" +#define PROP_KEY_CLASS "class" +#define PRIVATE_TYPE_NAME "#typename" +#define CLASS_IMPLEMENTATION_OBJECT "t::ClassImplementationObject" +#define PROP_KEY_SUPER "super" +#define PROP_KEY_SUPERVALUE "supervalue" +#define PRIVATE_JSINFO "#js_info" +#define PRIVATE_CALLSUPER "#supercall" +#define PRIVATE_IS_NAPI "#is_napi" +#define PROP_KEY_TOSTRING "toString" +#define PROP_KEY_IS_PROTOTYPE_IMPLEMENTATION_OBJECT "__isPrototypeImplementationObject" + +class Constants { + public: + const static char CLASS_NAME_LOCATION_SEPARATOR = '_'; + + static std::string APP_ROOT_FOLDER_PATH; + static bool CACHE_COMPILED_CODE; + + private: + Constants() { + } +}; + +#endif /* CONSTANTS_H_ */ diff --git a/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp b/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp new file mode 100644 index 000000000..f919a24a7 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp @@ -0,0 +1,293 @@ +#include "ArgConverter.h" +#include "ObjectManager.h" +#include "Util.h" +#include "NativeScriptException.h" +#include "NumericCasts.h" +#include "NativeScriptAssert.h" +#include +#ifdef USE_MIMALLOC +#include "mimalloc.h" +#endif + + +using namespace std; +using namespace tns; + +namespace { +napi_value EnsurePlainConstructorThis(napi_env env, napi_value jsThis, napi_value prototype) { + if (!napi_util::is_null_or_undefined(env, jsThis)) { + return jsThis; + } + + napi_value receiver = nullptr; + if (napi_create_object(env, &receiver) != napi_ok || receiver == nullptr) { + return nullptr; + } + + if (!napi_util::is_null_or_undefined(env, prototype)) { + napi_util::setPrototypeOf(env, receiver, prototype); + } + + return receiver; +} +} + +void ArgConverter::Init(napi_env env) { + auto cache = GetTypeLongCache(env); + + napi_value longNumberCtorFunc; + napi_value valueOfFunc; + napi_value toStringFunc; + + napi_define_class(env, "NativeScriptLongNumber", NAPI_AUTO_LENGTH, ArgConverter::NativeScriptLongFunctionCallback,nullptr, 0, nullptr, &longNumberCtorFunc); + + napi_value longNumberPrototype = napi_util::get_prototype(env, longNumberCtorFunc); + + napi_create_function(env, "valueOf", strlen("valueOf"), + ArgConverter::NativeScriptLongValueOfFunctionCallback, nullptr, + &valueOfFunc); + + napi_create_function(env, "toString", strlen("toString"), + ArgConverter::NativeScriptLongToStringFunctionCallback, nullptr, + &toStringFunc); + + + napi_set_named_property(env, longNumberPrototype, "valueOf", valueOfFunc); + napi_set_named_property(env, longNumberPrototype, "toString", toStringFunc); + + cache->LongNumberCtorFunc = napi_util::make_ref(env, longNumberCtorFunc, 1); + napi_value nanValue; + napi_create_double(env, numeric_limits::quiet_NaN(), &nanValue); + + + napi_value global; + napi_get_global(env, &global); + + napi_value numCtor; + napi_get_named_property(env, global, "Number", &numCtor); + + napi_value nanObject; + napi_new_instance(env, numCtor, 1, &nanValue, &nanObject); + + cache->NanNumberObject = napi_util::make_ref(env, nanObject, 1); +} + +napi_value ArgConverter::NativeScriptLongValueOfFunctionCallback(napi_env env, napi_callback_info info) { + try { + napi_value result; + napi_create_double(env, numeric_limits::quiet_NaN(), &result); + return result; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value ArgConverter::NativeScriptLongToStringFunctionCallback(napi_env env, napi_callback_info info) { + try { + napi_value thisArg; + napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr); + + napi_value value; + napi_get_named_property(env, thisArg, "value", &value); + + return value; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value ArgConverter::NativeScriptLongFunctionCallback(napi_env env, napi_callback_info info) { + try { + NAPI_CALLBACK_BEGIN(1); + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget) + ? napi_util::get_prototype(env, newTarget) + : nullptr; + napi_value receiver = EnsurePlainConstructorThis(env, jsThis, receiverPrototype); + if (receiver == nullptr) { + return nullptr; + } + auto cache = GetTypeLongCache(env); + napi_value javaLong; + napi_get_boolean(env, true, &javaLong); + napi_set_named_property(env, receiver, "javaLong", javaLong); + + NumericCasts::MarkAsLong(env, receiver, argv[0]); + + napi_set_named_property(env, receiver, "prototype", napi_util::get_ref_value(env, cache->NanNumberObject)); + return receiver; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +void ArgConverter::ConvertJavaArgsToJsArgs(napi_env env, jobjectArray args, size_t argc, napi_value* arr) { + JEnv jenv; + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + int jArrayIndex = 0; + for (int i = 0; i < argc; i++) { + JniLocalRef argTypeIDObj(jenv.GetObjectArrayElement(args, jArrayIndex++)); + JniLocalRef arg(jenv.GetObjectArrayElement(args, jArrayIndex++)); + JniLocalRef argJavaClassPath(jenv.GetObjectArrayElement(args, jArrayIndex++)); + + Type argTypeID = (Type) JType::IntValue(jenv, argTypeIDObj); + + napi_value jsArg; + switch (argTypeID) { + case Type::Boolean: + napi_get_boolean(env, JType::BooleanValue(jenv, arg), &jsArg); + break; + case Type::Char: + jsArg = jcharToJsString(env, JType::CharValue(jenv, arg)); + break; + case Type::Byte: + napi_create_int32(env, JType::ByteValue(jenv, arg), &jsArg); + break; + case Type::Short: + napi_create_int32(env, JType::ShortValue(jenv, arg), &jsArg); + break; + case Type::Int: + napi_create_int32(env, JType::IntValue(jenv, arg), &jsArg); + break; + case Type::Long: + napi_create_int64(env, JType::LongValue(jenv, arg), &jsArg); + break; + case Type::Float: + napi_create_double(env, JType::FloatValue(jenv, arg), &jsArg); + break; + case Type::Double: + napi_create_double(env, JType::DoubleValue(jenv, arg), &jsArg); + break; + case Type::String: + jsArg = jstringToJsString(env, (jstring) arg); + break; + case Type::JsObject: { + jint javaObjectID = JType::IntValue(jenv, arg); + jsArg = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, jsArg)) { + string argClassName = jstringToString(ObjectToString(argJavaClassPath)); + argClassName = Util::ConvertFromCanonicalToJniName(argClassName); + jsArg = objectManager->CreateJSWrapper(javaObjectID, argClassName); + } + break; + } + case Type::Null: + napi_get_null(env, &jsArg); + break; + } + + arr[i] = jsArg; + } + +} + +napi_value ArgConverter::ConvertFromJavaLong(napi_env env, jlong value) { + napi_value convertedValue; + long long longValue = value; + + if ((-JS_LONG_LIMIT < longValue) && (longValue < JS_LONG_LIMIT)) { + napi_create_double(env, longValue, &convertedValue); + } else { + auto cache = GetTypeLongCache(env); + char strNumber[24]; + sprintf(strNumber, "%lld", longValue); + napi_value strValue; + napi_create_string_utf8(env, strNumber, NAPI_AUTO_LENGTH, &strValue); + napi_value args[1] = {strValue}; + + napi_new_instance(env, napi_util::get_ref_value(env, cache->LongNumberCtorFunc), 1, args, + &convertedValue); + } + + return convertedValue; +} + +int64_t ArgConverter::ConvertToJavaLong(napi_env env, napi_value value) { + napi_value valueProp; + napi_get_named_property(env, value, "value", &valueProp); + + size_t str_len; + napi_get_value_string_utf8(env, valueProp, nullptr, 0, &str_len); + string num(str_len, '\0'); + napi_get_value_string_utf8(env, valueProp, &num[0], str_len + 1, &str_len); + + int64_t longValue = atoll(num.c_str()); + + return longValue; +} + +ArgConverter::TypeLongOperationsCache *ArgConverter::GetTypeLongCache(napi_env env) { + TypeLongOperationsCache *cache; + auto itFound = s_type_long_operations_cache.find(env); + if (itFound == s_type_long_operations_cache.end()) { + cache = new TypeLongOperationsCache; + s_type_long_operations_cache.emplace(env, cache); + } else { + cache = itFound->second; + } + + return cache; +} + +u16string ArgConverter::ConvertToUtf16String(napi_env env, napi_value s) { + if (s == nullptr) { + return {}; + } else { + size_t str_len; + napi_get_value_string_utf8(env, s, nullptr, 0, &str_len); + string str(str_len, '\0'); + napi_get_value_string_utf8(env, s, &str[0], str_len + 1, &str_len); + auto utf16str = Util::ConvertFromUtf8ToUtf16(str); + + return utf16str; + } +} + +void ArgConverter::onDisposeEnv(napi_env env) { + auto itFound = s_type_long_operations_cache.find(env); + if (itFound != s_type_long_operations_cache.end()) { + if (itFound->second->LongNumberCtorFunc) { + napi_delete_reference(env, itFound->second->LongNumberCtorFunc); + } + if (itFound->second->NanNumberObject) { + napi_delete_reference(env, itFound->second->NanNumberObject); + } + delete itFound->second; + s_type_long_operations_cache.erase(itFound); + } +} + +robin_hood::unordered_map ArgConverter::s_type_long_operations_cache; diff --git a/NativeScript/ffi/jni/napi/conversion/ArgConverter.h b/NativeScript/ffi/jni/napi/conversion/ArgConverter.h new file mode 100644 index 000000000..a42a14cf2 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArgConverter.h @@ -0,0 +1,137 @@ +/* + * ArgConverter.h + * + * Created on: Jan 29, 2014 + * Author: slavchev + */ + +#ifndef ARGCONVERTER_H_ +#define ARGCONVERTER_H_ + +#include "Runtime.h" +#include "NativeScriptAssert.h" +#include "JEnv.h" +#include +#include + +namespace tns { + + class ArgConverter { + public: + static void Init(napi_env env); + + static void ConvertJavaArgsToJsArgs(napi_env env, jobjectArray args, size_t length, napi_value* arr); + + static napi_value ConvertFromJavaLong(napi_env env, jlong value); + + static int64_t ConvertToJavaLong(napi_env env, napi_value value); + + static napi_value jstringToJsString(napi_env env, jstring value) { + if (value == nullptr) return napi_util::null(env); + + JEnv jenv; + auto chars = jenv.GetStringUTFChars(value,JNI_FALSE); + auto length = jenv.GetStringUTFLength(value); + auto jsString = convertToJsString(env, chars, length); + jenv.ReleaseStringUTFChars(value, chars); + + return jsString; + } + + static std::string jstringToString(jstring value) { + if (value == nullptr) { + return {}; + } + + JEnv jenv; + + jboolean f = JNI_FALSE; + auto chars = jenv.GetStringUTFChars(value, &f); + std::string s(chars); + jenv.ReleaseStringUTFChars(value, chars); + + return s; + } + + inline static std::string ConvertToString(napi_env env, napi_value s) { + if (s == nullptr) { + return {}; + } else { + return napi_util::get_string_value(env, s); + } + } + + static std::u16string ConvertToUtf16String(napi_env env, napi_value s); + + inline static jstring ConvertToJavaString(napi_env env, napi_value jsValue) { + JEnv jenv; + return jenv.NewStringUTF(napi_util::get_string_value(env, jsValue, 0)); + } + + inline static napi_value convertToJsString(napi_env env, const jchar *data, int length) { + napi_value result; + napi_create_string_utf16(env, reinterpret_cast(data), length, + &result); + return result; + } + + inline static napi_value convertToJsString(napi_env env, const std::string &s) { + napi_value result; + napi_create_string_utf8(env, s.c_str(), s.length(), &result); + return result; + } + + inline static napi_value convertToJsString(napi_env env, const char *data, int length) { + napi_value result; + napi_create_string_utf8(env, data, length, &result); + return result; + } + + inline static napi_value + ConvertToJsUTF16String(napi_env env, const std::u16string &utf16string) { + napi_value result; + napi_create_string_utf16(env, reinterpret_cast(utf16string.data()), + utf16string.length(), &result); + return result; + } + + static void onDisposeEnv(napi_env env); + + private: + + // TODO: plamen5kov: rewrite logic for java long number operations in javascript (java long -> javascript number operations check) + static const long long JS_LONG_LIMIT = ((long long) 1) << 53; + + struct TypeLongOperationsCache { + napi_ref LongNumberCtorFunc; + napi_ref NanNumberObject; + }; + + static TypeLongOperationsCache *GetTypeLongCache(napi_env env); + + inline static jstring ObjectToString(jobject object) { + return (jstring) object; + } + + inline static napi_value jcharToJsString(napi_env env, jchar value) { + auto v8String = convertToJsString(env, &value, 1); + return v8String; + } + + static napi_value NativeScriptLongFunctionCallback(napi_env env, napi_callback_info info); + + static napi_value + NativeScriptLongValueOfFunctionCallback(napi_env env, napi_callback_info info); + + static napi_value + NativeScriptLongToStringFunctionCallback(napi_env env, napi_callback_info info); + + /* + * "s_type_long_operations_cache" used to keep function + * dealing with operations concerning java long -> javascript number. + */ + static robin_hood::unordered_map s_type_long_operations_cache; + }; +} + +#endif /* ARGCONVERTER_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArgsWrapper.h b/NativeScript/ffi/jni/napi/conversion/ArgsWrapper.h new file mode 100644 index 000000000..70b96d8c8 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArgsWrapper.h @@ -0,0 +1,30 @@ +/* + * ArgsWrapper.h + * + * Created on: Dec 20, 2013 + * Author: slavchev + */ + +#ifndef ARGSWRAPPER_H_ +#define ARGSWRAPPER_H_ +#include "js_native_api.h" + +namespace tns { +enum class ArgType { + Class, + Interface +}; + +struct ArgsWrapper { + public: + ArgsWrapper(napi_value* argv_, size_t argc_, ArgType t) + : + argv(argv_), argc(argc_), type(t) { + } + napi_value* argv; + size_t argc; + ArgType type; +}; +} + +#endif /* ARGSWRAPPER_H_ */ diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.cpp b/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.cpp new file mode 100644 index 000000000..3d544248c --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.cpp @@ -0,0 +1,127 @@ +#include "ArrayBufferHelper.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include + +using namespace tns; + +ArrayBufferHelper::ArrayBufferHelper() + : m_objectManager(nullptr), m_ByteBufferClass(nullptr), m_isDirectMethodID(nullptr), + m_remainingMethodID(nullptr), m_getMethodID(nullptr) { +} + +void ArrayBufferHelper::CreateConvertFunctions(napi_env env, napi_value global, ObjectManager* objectManager) { + m_objectManager = objectManager; + napi_value fromFunc; + napi_create_function(env, "from", NAPI_AUTO_LENGTH, CreateFromCallbackStatic, this, &fromFunc); + + napi_value arrBufferCtorFunc; + napi_get_named_property(env, global, "ArrayBuffer", &arrBufferCtorFunc); + napi_set_named_property(env, arrBufferCtorFunc, "from", fromFunc); +} + +napi_value ArrayBufferHelper::CreateFromCallbackStatic(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + try { + auto thiz = reinterpret_cast(data); + return thiz->CreateFromCallbackImpl(env, argc, argv); + } catch (NativeScriptException& e) { + e.ReThrowToNapi(env); + } catch (std::exception& e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value ArrayBufferHelper::CreateFromCallbackImpl(napi_env env, size_t argc, napi_value* args) { + if (argc != 1) { + throw NativeScriptException("Wrong number of arguments (1 expected)"); + } + + napi_value arg = args[0]; + + bool isObject = napi_util::is_object(env, arg); + + if (!isObject) { + throw NativeScriptException("Wrong type of argument (object expected)"); + } + + auto argObj = arg; + + auto obj = m_objectManager->GetJavaObjectByJsObject(argObj); + + if (obj.IsNull()) { + throw NativeScriptException("Wrong type of argument (object expected)"); + } + + JEnv jEnv; + + if (m_ByteBufferClass == nullptr) { + m_ByteBufferClass = jEnv.FindClass("java/nio/ByteBuffer"); + assert(m_ByteBufferClass != nullptr); + } + + auto isByteBuffer = jEnv.IsInstanceOf(obj, m_ByteBufferClass); + + if (!isByteBuffer) { + throw NativeScriptException("Wrong type of argument (ByteBuffer expected)"); + } + + if (m_isDirectMethodID == nullptr) { + m_isDirectMethodID = jEnv.GetMethodID(m_ByteBufferClass, "isDirect", "()Z"); + assert(m_isDirectMethodID != nullptr); + } + + auto ret = jEnv.CallBooleanMethod(obj, m_isDirectMethodID); + + auto isDirectBuffer = ret == JNI_TRUE; + + napi_value arrayBuffer; + + if (isDirectBuffer) { + auto data = jEnv.GetDirectBufferAddress(obj); + auto size = jEnv.GetDirectBufferCapacity(obj); + + void* externalData = data; + napi_create_external_arraybuffer(env, externalData, size, nullptr, nullptr, &arrayBuffer); + } else { + if (m_remainingMethodID == nullptr) { + m_remainingMethodID = jEnv.GetMethodID(m_ByteBufferClass, "remaining", "()I"); + assert(m_remainingMethodID != nullptr); + } + + int bufferRemainingSize = jEnv.CallIntMethod(obj, m_remainingMethodID); + + if (m_getMethodID == nullptr) { + m_getMethodID = jEnv.GetMethodID(m_ByteBufferClass, "get", "([BII)Ljava/nio/ByteBuffer;"); + assert(m_getMethodID != nullptr); + } + + jbyteArray byteArray = jEnv.NewByteArray(bufferRemainingSize); + jEnv.CallObjectMethod(obj, m_getMethodID, byteArray, 0, bufferRemainingSize); + + auto byteArrayElements = jEnv.GetByteArrayElements(byteArray, 0); + + jbyte* data = new jbyte[bufferRemainingSize]; + memcpy(data, byteArrayElements, bufferRemainingSize); + + napi_create_external_arraybuffer(env, data, bufferRemainingSize, [](napi_env env, void* finalize_data, void* finalize_hint) { + delete[] static_cast(finalize_data); + }, nullptr, &arrayBuffer); + + jEnv.ReleaseByteArrayElements(byteArray, byteArrayElements, 0); + } + + napi_value nativeObjectKey; + napi_create_string_utf8(env, "nativeObject", NAPI_AUTO_LENGTH, &nativeObjectKey); + napi_set_property(env, arrayBuffer, nativeObjectKey, argObj); + + return arrayBuffer; +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.h b/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.h new file mode 100644 index 000000000..cccd172cd --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.h @@ -0,0 +1,29 @@ +#ifndef ARRAYBUFFERHELPER_H_ +#define ARRAYBUFFERHELPER_H_ + +#include "ObjectManager.h" + +namespace tns { + class ArrayBufferHelper { + public: + ArrayBufferHelper(); + + void CreateConvertFunctions(napi_env env, napi_value global, ObjectManager* objectManager); + + private: + + static napi_value CreateFromCallbackStatic(napi_env env, napi_callback_info info); + + napi_value CreateFromCallbackImpl(napi_env env, size_t argc, napi_value* args); + + ObjectManager* m_objectManager; + + jclass m_ByteBufferClass; + jmethodID m_isDirectMethodID; + jmethodID m_remainingMethodID; + jmethodID m_getMethodID; + }; +} + + +#endif /* ARRAYBUFFERHELPER_H_ */ diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.cpp b/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.cpp new file mode 100644 index 000000000..f7cafee2e --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.cpp @@ -0,0 +1,222 @@ +#include "ArrayElementAccessor.h" +#include "JsArgToArrayConverter.h" +#include "ArgConverter.h" +#include "Util.h" +#include "NativeScriptException.h" +#include "Runtime.h" + +using namespace std; +using namespace tns; + +napi_value ArrayElementAccessor::GetArrayElement(napi_env env, napi_value array, uint32_t index, const string& arraySignature) { + JEnv jenv; + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + auto arr = objectManager->GetJavaObjectByJsObject(array); + + assertNonNullNativeArray(arr); + + napi_value value; + jsize startIndex = index; + const jsize length = 1; + + const string elementSignature = arraySignature.substr(1); + jboolean isCopy = false; + + if (elementSignature == "Z") { + jbooleanArray boolArr = static_cast(arr); + jboolean boolArrValue; + jenv.GetBooleanArrayRegion(boolArr, startIndex, length, &boolArrValue); + value = ConvertToJsValue(env, objectManager, jenv, elementSignature, &boolArrValue); + } else if (elementSignature == "B") { + jbyteArray byteArr = static_cast(arr); + jbyte byteArrValue; + jenv.GetByteArrayRegion(byteArr, startIndex, length, &byteArrValue); + value = ConvertToJsValue(env, objectManager, jenv, elementSignature, &byteArrValue); + } else if (elementSignature == "C") { + jcharArray charArr = static_cast(arr); + jchar charArrValue; + jenv.GetCharArrayRegion(charArr, startIndex, length, &charArrValue); + JniLocalRef s(jenv.NewString(&charArrValue, 1)); + const char* singleChar = jenv.GetStringUTFChars(s, &isCopy); + value = ConvertToJsValue(env, objectManager, jenv, elementSignature, singleChar); + jenv.ReleaseStringUTFChars(s, singleChar); + } else if (elementSignature == "S") { + jshortArray shortArr = static_cast(arr); + jshort shortArrValue; + jenv.GetShortArrayRegion(shortArr, startIndex, length, &shortArrValue); + value = ConvertToJsValue(env, objectManager, jenv, elementSignature, &shortArrValue); + } else if (elementSignature == "I") { + jintArray intArr = static_cast(arr); + jint intArrValue; + jenv.GetIntArrayRegion(intArr, startIndex, length, &intArrValue); + value = ConvertToJsValue(env, objectManager, jenv, elementSignature, &intArrValue); + } else if (elementSignature == "J") { + jlongArray longArr = static_cast(arr); + jlong longArrValue; + jenv.GetLongArrayRegion(longArr, startIndex, length, &longArrValue); + value = ConvertToJsValue(env, objectManager, jenv, elementSignature, &longArrValue); + } else if (elementSignature == "F") { + jfloatArray floatArr = static_cast(arr); + jfloat floatArrValue; + jenv.GetFloatArrayRegion(floatArr, startIndex, length, &floatArrValue); + value = ConvertToJsValue(env, objectManager, jenv, elementSignature, &floatArrValue); + } else if (elementSignature == "D") { + jdoubleArray doubleArr = static_cast(arr); + jdouble doubleArrValue; + jenv.GetDoubleArrayRegion(doubleArr, startIndex, length, &doubleArrValue); + value = ConvertToJsValue(env, objectManager, jenv, elementSignature, &doubleArrValue); + } else { + jobject result = jenv.GetObjectArrayElement(static_cast(arr), index); + value = ConvertToJsValue(env, objectManager, jenv, elementSignature, &result); + jenv.DeleteLocalRef(result); + } + + return value; +} + +void ArrayElementAccessor::SetArrayElement(napi_env env, napi_value array, uint32_t index, const string& arraySignature, napi_value value) { + JEnv jenv; + + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + tns::JniLocalRef arr = objectManager->GetJavaObjectByJsObject(array); + + assertNonNullNativeArray(arr); + + const string elementSignature = arraySignature.substr(1); + jboolean isCopy = false; + + if (elementSignature == "Z") { //bool + bool boolElementValue; + napi_get_value_bool(env, value, &boolElementValue); + jboolean jboolElementValue = static_cast(boolElementValue); + jbooleanArray boolArr = static_cast(arr); + jenv.SetBooleanArrayRegion(boolArr, index, 1, &jboolElementValue); + } else if (elementSignature == "B") { //byte + int32_t byteElementValue; + napi_get_value_int32(env, value, &byteElementValue); + jbyte jbyteElementValue = static_cast(byteElementValue); + jbyteArray byteArr = static_cast(arr); + jenv.SetByteArrayRegion(byteArr, index, 1, &jbyteElementValue); + } else if (elementSignature == "C") { //char + size_t str_len; + napi_get_value_string_utf8(env, value, nullptr, 0, &str_len); + string str(str_len, '\0'); + napi_get_value_string_utf8(env, value, &str[0], str_len + 1, &str_len); + JniLocalRef s(jenv.NewString(reinterpret_cast(str.c_str()), 1)); + const char* singleChar = jenv.GetStringUTFChars(s, &isCopy); + jchar charElementValue = *singleChar; + jenv.ReleaseStringUTFChars(s, singleChar); + jcharArray charArr = static_cast(arr); + jenv.SetCharArrayRegion(charArr, index, 1, &charElementValue); + } else if (elementSignature == "S") { //short + int32_t shortElementValue; + napi_get_value_int32(env, value, &shortElementValue); + jshort jshortElementValue = static_cast(shortElementValue); + jshortArray shortArr = static_cast(arr); + jenv.SetShortArrayRegion(shortArr, index, 1, &jshortElementValue); + } else if (elementSignature == "I") { //int + int32_t intElementValue; + napi_get_value_int32(env, value, &intElementValue); + jint jintElementValue = static_cast(intElementValue); + jintArray intArr = static_cast(arr); + jenv.SetIntArrayRegion(intArr, index, 1, &jintElementValue); + } else if (elementSignature == "J") { //long + int64_t longElementValue; + napi_get_value_int64(env, value, &longElementValue); + jlong jlongElementValue = static_cast(longElementValue); + jlongArray longArr = static_cast(arr); + jenv.SetLongArrayRegion(longArr, index, 1, &jlongElementValue); + } else if (elementSignature == "F") { //float + double floatElementValue; + napi_get_value_double(env, value, &floatElementValue); + jfloat jfloatElementValue = static_cast(floatElementValue); + jfloatArray floatArr = static_cast(arr); + jenv.SetFloatArrayRegion(floatArr, index, 1, &jfloatElementValue); + } else if (elementSignature == "D") { //double + double doubleElementValue; + napi_get_value_double(env, value, &doubleElementValue); + jdouble jdoubleElementValue = static_cast(doubleElementValue); + jdoubleArray doubleArr = static_cast(arr); + jenv.SetDoubleArrayRegion(doubleArr, index, 1, &jdoubleElementValue); + } else { //string or object + napi_valuetype ref_type; + + napi_typeof(env, value, &ref_type); + + if (ref_type == napi_object || ref_type == napi_function || ref_type == napi_string) { + JsArgToArrayConverter argConverter(env, value, false, (int) Type::Null); + if (argConverter.IsValid()) { + jobjectArray objArr = static_cast(arr); + jobject objectElementValue = argConverter.GetConvertedArg(); + jenv.SetObjectArrayElement(objArr, index, objectElementValue); + } else { + JsArgToArrayConverter::Error err = argConverter.GetError(); + throw NativeScriptException(string(err.msg)); + } + } else { + throw NativeScriptException(string("Cannot assign primitive value to array of objects.")); + } + } + +} + +napi_value ArrayElementAccessor::ConvertToJsValue(napi_env env, ObjectManager* objectManager, JEnv& jenv, const string& elementSignature, const void* value) { + napi_value jsValue; + + jint val = *(jint*) value; + + if (elementSignature == "Z") { + napi_get_boolean(env, *(jboolean*) value, &jsValue); + } else if (elementSignature == "B") { + napi_create_int32(env, *(jbyte*) value, &jsValue); + } else if (elementSignature == "C") { + napi_create_string_utf8(env, (const char*) value, 1, &jsValue); + } else if (elementSignature == "S") { + napi_create_int32(env, *(jshort*) value, &jsValue); + } else if (elementSignature == "I") { + napi_create_int32(env, *(jint*) value, &jsValue); + } else if (elementSignature == "J") { + napi_create_int64(env, *(jlong*) value, &jsValue); + } else if (elementSignature == "F") { + napi_create_double(env, *(jfloat*) value, &jsValue); + } else if (elementSignature == "D") { + napi_create_double(env, *(jdouble*) value, &jsValue); + } else { + if (nullptr != (*(jobject*) value)) { + bool isString = elementSignature == "Ljava/lang/String;"; + + if (isString) { + jsValue = ArgConverter::jstringToJsString(env, *(jstring *) value); + } else { + jint javaObjectID = objectManager->GetOrCreateObjectId(*(jobject*) value); + jsValue = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, jsValue)) { + string className; + if (elementSignature[0] == '[') { + className = Util::JniClassPathToCanonicalName(elementSignature); + } else { + className = objectManager->GetClassName(*(jobject*) value); + } + + jsValue = objectManager->CreateJSWrapper(javaObjectID, className); + } + } + } else { + napi_get_null(env, &jsValue); + } + } + + return jsValue; +} + +void ArrayElementAccessor::assertNonNullNativeArray(tns::JniLocalRef& arrayReference) { + if(arrayReference.IsNull()){ + throw NativeScriptException("Failed calling indexer operator on native array. The JavaScript instance no longer has available Java instance counterpart."); + } +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.h b/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.h new file mode 100644 index 000000000..a17a687f6 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.h @@ -0,0 +1,24 @@ +#ifndef ARRAYELEMENTACCESSOR_H_ +#define ARRAYELEMENTACCESSOR_H_ + +#include "JEnv.h" +#include "JniLocalRef.h" +#include "js_native_api.h" +#include +#include "ObjectManager.h" + + +namespace tns { + class ArrayElementAccessor { + public: + napi_value GetArrayElement(napi_env env, napi_value array, uint32_t index, const std::string& arraySignature); + + void SetArrayElement(napi_env env, napi_value array, uint32_t index, const std::string& arraySignature, napi_value value); + + private: + napi_value ConvertToJsValue(napi_env env, ObjectManager* objectManager, JEnv& jEnv, const std::string& elementSignature, const void* value); + void assertNonNullNativeArray(tns::JniLocalRef& arrayReference); + }; +} + +#endif /* ARRAYELEMENTACCESSOR_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayHelper.cpp b/NativeScript/ffi/jni/napi/conversion/ArrayHelper.cpp new file mode 100644 index 000000000..415774843 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayHelper.cpp @@ -0,0 +1,177 @@ +#include "ArrayHelper.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +ArrayHelper::ArrayHelper() { +} + +void ArrayHelper::Init(napi_env env) { + JEnv jenv; + + RUNTIME_CLASS = jenv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + CREATE_ARRAY_HELPER = jenv.GetStaticMethodID(RUNTIME_CLASS, "createArrayHelper", "(Ljava/lang/String;I)Ljava/lang/Object;"); + assert(CREATE_ARRAY_HELPER != nullptr); + + napi_value global; + napi_get_global(env, &global); + + napi_value arrayConstructor; + napi_get_named_property(env, global, "Array", &arrayConstructor); + + napi_util::napi_set_function(env, arrayConstructor, "create", CreateJavaArrayCallback, nullptr); + +} + +napi_value ArrayHelper::CreateJavaArrayCallback(napi_env env, napi_callback_info info) { + try { + napi_value array = CreateJavaArray(env, info); + return array; + } catch (NativeScriptException& e) { + e.ReThrowToNapi(env); + } catch (std::exception& e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value ArrayHelper::CreateJavaArray(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS() + + if (argc != 2) { + Throw(env, "Expect two parameters."); + return nullptr; + } + + napi_value type = argv[0]; + napi_value length = argv[1]; + + JniLocalRef array; + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + napi_valuetype typeType; + napi_typeof(env, type, &typeType); + + napi_valuetype lengthType; + napi_typeof(env, length, &lengthType); + + if (typeType == napi_string) { + if (lengthType != napi_number) { + Throw(env, "Expect integer value as a second argument."); + return nullptr; + } + + bool isFloat = napi_util::is_float(env, length); + + if (isFloat) { + Throw(env, "Expect integer value as a second argument. It is a float"); + return nullptr; + } + + int32_t len; + napi_get_value_int32(env, length, &len); + if (len < 0) { + Throw(env, "Expect non-negative integer value as a second argument."); + return nullptr; + } + + string typeName = ArgConverter::ConvertToString(env, type); + array = JniLocalRef(CreateArrayByClassName(typeName, len)); + } else if (typeType == napi_function || typeType == napi_object) { + if (lengthType != napi_number) { + Throw(env, "Expect integer value as a second argument."); + return nullptr; + } + + bool isFloat = napi_util::is_float(env, length); + + if (isFloat) { + Throw(env, "Expect integer value as a second argument."); + return nullptr; + } + + int32_t len; + napi_get_value_int32(env, length, &len); + if (len < 0) { + Throw(env, "Expect non-negative integer value as a second argument."); + return nullptr; + } + + napi_value classVal; + napi_get_named_property(env, type, "class", &classVal); + + napi_valuetype classValType; + napi_typeof(env, classVal, &classValType); + + if (classValType == napi_undefined) { + Throw(env, "Expect known class as a second argument."); + return nullptr; + } + + auto c = objectManager->GetJavaObjectByJsObject(classVal); + + JEnv jenv; + array = jenv.NewObjectArray(len, static_cast(c), nullptr); + } else { + Throw(env, "Expect primitive type name or class function as a first argument"); + return nullptr; + } + + jint javaObjectID = objectManager->GetOrCreateObjectId(array); + return objectManager->CreateJSWrapper(javaObjectID, "" /* ignored */, array); +} + +void ArrayHelper::Throw(napi_env env, const std::string& errorMessage) { + napi_value errMsg; + napi_create_string_utf8(env, errorMessage.c_str(), NAPI_AUTO_LENGTH, &errMsg); + + napi_value err; + napi_create_error(env, nullptr, errMsg, &err); + + napi_throw(env, err); +} + +jobject ArrayHelper::CreateArrayByClassName(const string& typeName, int length) { + JEnv jEnv; + jobject array; + + if (typeName == "char") { + array = jEnv.NewCharArray(length); + } else if (typeName == "boolean") { + array = jEnv.NewBooleanArray(length); + } else if (typeName == "byte") { + array = jEnv.NewByteArray(length); + } else if (typeName == "short") { + array = jEnv.NewShortArray(length); + } else if (typeName == "int") { + array = jEnv.NewIntArray(length); + } else if (typeName == "long") { + array = jEnv.NewLongArray(length); + } else if (typeName == "float") { + array = jEnv.NewFloatArray(length); + } else if (typeName == "double") { + array = jEnv.NewDoubleArray(length); + } else { + JniLocalRef s(jEnv.NewStringUTF(typeName.c_str())); + array = jEnv.CallStaticObjectMethod(RUNTIME_CLASS, CREATE_ARRAY_HELPER, (jstring)s, length); + } + + return array; +} + +jclass ArrayHelper::RUNTIME_CLASS = nullptr; +jmethodID ArrayHelper::CREATE_ARRAY_HELPER = nullptr; \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayHelper.h b/NativeScript/ffi/jni/napi/conversion/ArrayHelper.h new file mode 100644 index 000000000..bb234df26 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayHelper.h @@ -0,0 +1,30 @@ +#ifndef ARRAYHELPER_H_ +#define ARRAYHELPER_H_ + +#include "js_native_api.h" +#include "ObjectManager.h" +#include + +namespace tns { +class ArrayHelper { + public: + static void Init(napi_env env); + + private: + ArrayHelper(); + + static napi_value CreateJavaArrayCallback(napi_env env, napi_callback_info info); + + static napi_value CreateJavaArray(napi_env env, napi_callback_info info); + + static void Throw(napi_env env, const std::string& errorMessage); + + static jobject CreateArrayByClassName(const std::string& typeName, int length); + + static jclass RUNTIME_CLASS; + + static jmethodID CREATE_ARRAY_HELPER; +}; +} + +#endif /* ARRAYHELPER_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/JsArgConverter.cpp b/NativeScript/ffi/jni/napi/conversion/JsArgConverter.cpp new file mode 100644 index 000000000..bc078c47f --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/JsArgConverter.cpp @@ -0,0 +1,818 @@ +#include "JsArgConverter.h" +#include "ObjectManager.h" +#include "JniSignatureParser.h" +#include "JsArgToArrayConverter.h" +#include "ArgConverter.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include + +using namespace std; +using namespace tns; + +JsArgConverter::JsArgConverter(napi_env env, napi_value caller, napi_value *args, size_t argc, + const std::string &methodSignature, MetadataEntry *entry) + : m_env(env), m_isValid(true), m_methodSignature(methodSignature), m_error(Error()) { + int napiProvidedArgumentsLength = argc; + m_argsLen = 1 + napiProvidedArgumentsLength; + + if (m_argsLen > 0) { + if ((entry != nullptr) && (entry->getIsResolved())) { + if (entry->parsedSig.empty()) { + JniSignatureParser parser(m_methodSignature); + entry->parsedSig = parser.Parse(); + } + m_tokens = entry->parsedSig; + } else { + JniSignatureParser parser(m_methodSignature); + m_tokens = parser.Parse(); + } + + m_isValid = ConvertArg(env, caller, 0); + + if (!m_isValid) { + throw NativeScriptException("Error while converting argument!"); + } + + for (size_t i = 0; i < napiProvidedArgumentsLength; i++) { + m_isValid = ConvertArg(env, args[i], i + 1); + + if (!m_isValid) { + break; + } + } + } +} + +JsArgConverter::JsArgConverter(napi_env env, napi_value *args, size_t argc, + bool hasImplementationObject, const std::string &methodSignature, + MetadataEntry *entry) + : m_env(env), m_isValid(true), m_methodSignature(methodSignature), m_error(Error()) { + m_argsLen = !hasImplementationObject ? argc : argc - 1; + + if (m_argsLen > 0) { + if ((entry != nullptr) && (entry->getIsResolved())) { + if (entry->parsedSig.empty()) { + JniSignatureParser parser(m_methodSignature); + entry->parsedSig = parser.Parse(); + } + m_tokens = entry->parsedSig; + } else { + JniSignatureParser parser(m_methodSignature); + m_tokens = parser.Parse(); + } + + for (size_t i = 0; i < m_argsLen; i++) { + m_isValid = ConvertArg(env, args[i], i); + + if (!m_isValid) { + break; + } + } + } +} + +JsArgConverter::JsArgConverter(napi_env env, napi_value *args, size_t argc, + const std::string &methodSignature) + : m_env(env), m_isValid(true), m_methodSignature(methodSignature), m_error(Error()) { + m_argsLen = argc; + + JniSignatureParser parser(m_methodSignature); + m_tokens = parser.Parse(); + + for (size_t i = 0; i < m_argsLen; i++) { + m_isValid = ConvertArg(env, args[i], i); + + if (!m_isValid) { + break; + } + } +} + +tns::BufferCastType JsArgConverter::GetCastType(napi_typedarray_type type) { + switch (type) { + case napi_uint16_array: + case napi_int16_array: + return tns::BufferCastType::Short; + case napi_uint32_array: + case napi_int32_array: + return tns::BufferCastType::Int; + case napi_float32_array: + return tns::BufferCastType::Float; + case napi_float64_array: + return tns::BufferCastType::Double; + case napi_bigint64_array: + case napi_biguint64_array: + return tns::BufferCastType::Long; + default: + return tns::BufferCastType::Byte; + } +} + +bool JsArgConverter::ConvertArg(napi_env env, napi_value arg, int index) { + bool success = false; + + char buff[1024]; + + const auto &typeSignature = m_tokens.at(index); + + if (arg == nullptr) { + SetConvertedObject(index, nullptr); + success = false; + } else { + napi_valuetype argType; + napi_typeof(m_env, arg, &argType); + + if (argType == napi_object || argType == napi_function) { + bool isArray; + napi_is_array(m_env, arg, &isArray); + + if (isArray) { + success = typeSignature[0] == '['; + + if (success) { + success = ConvertJavaScriptArray(env, arg, index); + } + + if (!success) { + sprintf(buff, "Cannot convert array to %s at index %d", typeSignature.c_str(), + index); + } + } else { + + CastType castType = CastType::None; + +#ifdef USE_HOST_OBJECT + void *data; + napi_get_host_object_data(env, arg, &data); + if (data) { + castType = CastType::None; + } else { + castType = NumericCasts::GetCastType(env, arg); + } +#else + castType = NumericCasts::GetCastType(m_env, arg); +#endif + + napi_value castValue; + napi_valuetype valueType = napi_undefined; + if (castType != CastType::None) { + castValue = NumericCasts::GetCastValue(m_env, arg); + if (castValue != nullptr) { + napi_typeof(env, castValue, &valueType); + } + + } + + JniLocalRef obj; + + auto runtime = Runtime::GetRuntime(m_env); + auto objectManager = runtime->GetObjectManager(); + + JEnv jEnv; + + switch (castType) { + case CastType::Char: + if (valueType == napi_string) { + string value = ArgConverter::ConvertToString(m_env, castValue); + m_args[index].c = (jchar) value[0]; + success = true; + } + break; + + case CastType::Byte: + if (valueType == napi_string) { + string strValue = ArgConverter::ConvertToString(m_env, castValue); + int byteArg = atoi(strValue.c_str()); + jbyte value = (jbyte) byteArg; + success = ConvertFromCastFunctionObject(value, index); + } else if (valueType == napi_number) { + int byteArg = napi_util::get_int32(env, castValue); + jbyte value = (jbyte) byteArg; + success = ConvertFromCastFunctionObject(value, index); + } + + break; + + case CastType::Short: + if (valueType == napi_string) { + string strValue = ArgConverter::ConvertToString(m_env, castValue); + int shortArg = atoi(strValue.c_str()); + jshort value = (jshort) shortArg; + success = ConvertFromCastFunctionObject(value, index); + } else if (valueType == napi_number) { + int shortArg; + napi_get_value_int32(m_env, castValue, &shortArg); + jshort value = (jshort) shortArg; + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::Long: + if (valueType == napi_string) { + string strValue = ArgConverter::ConvertToString(m_env, castValue); + int64_t longArg = atoll(strValue.c_str()); + jlong value = (jlong) longArg; + success = ConvertFromCastFunctionObject(value, index); + } else if (valueType == napi_number) { + int64_t longArg; + napi_get_value_int64(m_env, castValue, &longArg); + jlong value = (jlong) longArg; + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::Float: + if (valueType == napi_number) { + double floatArg; + napi_get_value_double(m_env, castValue, &floatArg); + jfloat value = (jfloat) floatArg; + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::Double: + if (valueType == napi_number) { + double doubleArg; + napi_get_value_double(m_env, castValue, &doubleArg); + jdouble value = (jdouble) doubleArg; + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::None: + obj = objectManager->GetJavaObjectByJsObject(arg); + + if (obj.IsNull()) { + bool isArrayBuffer = false; + bool isDataView = false; + bool isTypedArray = false; + + napi_is_arraybuffer(env, arg, &isArrayBuffer); + if (!isArrayBuffer) { + napi_is_typedarray(env, arg, &isTypedArray); + if (!isTypedArray) { + napi_is_dataview(env, arg, &isDataView); + } + } + + if (isArrayBuffer || isDataView || isTypedArray) { + obj = JsArgConverter::GetByteBuffer(env, arg, isArrayBuffer, + isTypedArray, isDataView); + } + } + +#ifdef USE_HOST_OBJECT + if (!data) { +#endif + napi_value nullNode; + napi_get_named_property(env, arg, PROP_KEY_NULL_NODE_NAME, &nullNode); + if (!napi_util::is_null_or_undefined(env, nullNode)) { + SetConvertedObject(index, nullptr); + success = true; + break; + } +#ifdef USE_HOST_OBJECT + } +#endif + + success = !obj.IsNull(); + + if (success) { + SetConvertedObject(index, obj.Move(), false); + } else { + if (napi_util::is_number_object(env, arg)) { + success = ConvertJavaScriptNumber(env, arg, index, true); + break; + } else if (napi_util::is_string_object(env, arg)) { + napi_value stringValue = napi_util::valueOf(env, arg); + success = ConvertJavaScriptString(env, stringValue, index); + break; + } else if (napi_util::is_boolean_object(env, arg)) { + napi_value boolValue = napi_util::valueOf(env, arg); + success = ConvertJavaScriptBoolean(env, boolValue, index); + break; + } + + if (!success) { + sprintf(buff, "Cannot convert object to %s at index %d", + typeSignature.c_str(), index); + } + } + break; + + default: + throw NativeScriptException("Unsupported cast type"); + } + } + } else if (argType == napi_number) { + success = ConvertJavaScriptNumber(env, arg, index, false); + + if (!success) { + sprintf(buff, "Cannot convert number to %s at index %d", typeSignature.c_str(), + index); + } + } else if (argType == napi_boolean) { + success = ConvertJavaScriptBoolean(env, arg, index); + + if (!success) { + sprintf(buff, "Cannot convert boolean to %s at index %d", typeSignature.c_str(), + index); + } + } else if (argType == napi_string) { + success = ConvertJavaScriptString(env, arg, index); + + if (!success) { + sprintf(buff, "Cannot convert string to %s at index %d", typeSignature.c_str(), + index); + } + } else if (argType == napi_undefined || argType == napi_null) { + SetConvertedObject(index, nullptr); + success = true; + } else { + SetConvertedObject(index, nullptr); + success = false; + } + } + + if (!success) { + m_error.index = index; + m_error.msg = string(buff); + } + + return success; +} + + +void JsArgConverter::SetConvertedObject(int index, jobject obj, bool isGlobal) { + m_args[index].l = obj; + if ((obj != nullptr) && !isGlobal) { + m_args_refs[m_args_refs_size++] = index; + } +} + +bool JsArgConverter::ConvertJavaScriptNumber(napi_env env, napi_value jsValue, int index, + bool isNumberObject = false) { + bool success = true; + + jvalue value = {0}; + + const auto &typeSignature = m_tokens.at(index); + + const char typePrefix = typeSignature[0]; + + switch (typePrefix) { + case 'B': { // byte + int32_t intValue; + if (isNumberObject) { + napi_get_value_int32(env, napi_util::valueOf(env, jsValue), &intValue); + } else { + napi_get_value_int32(env, jsValue, &intValue); + } + value.b = (jbyte) intValue; + break; + } + case 'S': { // short + int intValue; + if (isNumberObject) { + napi_get_value_int32(env, napi_util::valueOf(env, jsValue), &intValue); + } else { + napi_get_value_int32(env, jsValue, &intValue); + } + value.s = (jshort) intValue; + break; + } + case 'I': { // int + int intValue; + if (isNumberObject) { + napi_get_value_int32(env, napi_util::valueOf(env, jsValue), &intValue); + } else { + napi_get_value_int32(env, jsValue, &intValue); + } + value.i = (jint) intValue; + break; + } + case 'J': { // long + int64_t intValue; + if (isNumberObject) { + napi_get_value_int64(env, napi_util::valueOf(env, jsValue), &intValue); + } else { + napi_get_value_int64(env, jsValue, &intValue); + } + value.j = (jlong) intValue; + break; + } + case 'F': { // float + double doubleValue; + if (isNumberObject) { + napi_get_value_double(env, napi_util::valueOf(env, jsValue), &doubleValue); + } else { + napi_get_value_double(env, jsValue, &doubleValue); + } + value.f = (jfloat) doubleValue; + break; + } + case 'D': { // double + double doubleValue; + if (isNumberObject) { + napi_get_value_double(env, napi_util::valueOf(env, jsValue), &doubleValue); + } else { + napi_get_value_double(env, jsValue, &doubleValue); + } + value.d = (jdouble) doubleValue; + break; + } + default: + success = false; + break; + } + + if (success) { + m_args[index] = value; + } + + return success; +} + +bool JsArgConverter::ConvertJavaScriptBoolean(napi_env env, napi_value jsValue, int index) { + bool success; + + const auto &typeSignature = m_tokens.at(index); + + if (typeSignature == "Z") { + bool argValue; + napi_get_value_bool(env, jsValue, &argValue); + + jboolean value = argValue ? JNI_TRUE : JNI_FALSE; + m_args[index].z = value; + success = true; + } else { + success = false; + } + + return success; +} + +bool JsArgConverter::ConvertJavaScriptString(napi_env env, napi_value jsValue, int index) { + jstring stringObject = ArgConverter::ConvertToJavaString(env, jsValue); + SetConvertedObject(index, stringObject); + return true; +} + +bool JsArgConverter::ConvertJavaScriptArray(napi_env env, napi_value jsArr, int index) { + bool success = true; + + jarray arr = nullptr; + + uint32_t jsLen; + napi_get_array_length(env, jsArr, &jsLen); + + const jsize arrLength = jsLen; + + const auto &arraySignature = m_tokens.at(index); + + std::string elementType = arraySignature.substr(1); + + const char elementTypePrefix = elementType[0]; + + jclass elementClass; + std::string strippedClassName; + + JEnv jenv; + switch (elementTypePrefix) { + case 'Z': { + arr = jenv.NewBooleanArray(arrLength); + std::vector bools(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + napi_get_element(env, jsArr, i, &element); + + bool boolValue; + napi_get_value_bool(env, element, &boolValue); + bools[i] = (jboolean) boolValue; + } + jenv.SetBooleanArrayRegion((jbooleanArray) arr, 0, arrLength, bools.data()); + break; + } + case 'B': { + arr = jenv.NewByteArray(arrLength); + std::vector bytes(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + napi_get_element(env, jsArr, i, &element); + int32_t intValue; + napi_get_value_int32(env, element, &intValue); + bytes[i] = (jbyte) intValue; + } + jenv.SetByteArrayRegion((jbyteArray) arr, 0, arrLength, bytes.data()); + break; + } + case 'C': { + arr = jenv.NewCharArray(arrLength); + std::vector chars(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + napi_get_element(env, jsArr, i, &element); + size_t str_len; + napi_get_value_string_utf8(env, element, nullptr, 0, &str_len); + std::string str(str_len, '\0'); + napi_get_value_string_utf8(env, element, &str[0], str_len + 1, &str_len); + chars[i] = (jchar) str[0]; + } + jenv.SetCharArrayRegion((jcharArray) arr, 0, arrLength, chars.data()); + break; + } + case 'S': { + arr = jenv.NewShortArray(arrLength); + std::vector shorts(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + napi_get_element(env, jsArr, i, &element); + int32_t intValue; + napi_get_value_int32(env, element, &intValue); + shorts[i] = (jshort) intValue; + } + jenv.SetShortArrayRegion((jshortArray) arr, 0, arrLength, shorts.data()); + break; + } + case 'I': { + arr = jenv.NewIntArray(arrLength); + std::vector ints(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + napi_get_element(env, jsArr, i, &element); + int32_t intValue; + napi_get_value_int32(env, element, &intValue); + ints[i] = (jint) intValue; + } + jenv.SetIntArrayRegion((jintArray) arr, 0, arrLength, ints.data()); + break; + } + case 'J': { + arr = jenv.NewLongArray(arrLength); + std::vector longs(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + napi_get_element(env, jsArr, i, &element); + int64_t intValue; + napi_get_value_int64(env, element, &intValue); + longs[i] = (jlong) intValue; + } + jenv.SetLongArrayRegion((jlongArray) arr, 0, arrLength, longs.data()); + break; + } + case 'F': { + arr = jenv.NewFloatArray(arrLength); + std::vector floats(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + napi_get_element(env, jsArr, i, &element); + double doubleValue; + napi_get_value_double(env, element, &doubleValue); + floats[i] = (jfloat) doubleValue; + } + jenv.SetFloatArrayRegion((jfloatArray) arr, 0, arrLength, floats.data()); + break; + } + case 'D': { + arr = jenv.NewDoubleArray(arrLength); + std::vector doubles(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + napi_get_element(env, jsArr, i, &element); + double doubleValue; + napi_get_value_double(env, element, &doubleValue); + doubles[i] = (jdouble) doubleValue; + } + jenv.SetDoubleArrayRegion((jdoubleArray) arr, 0, arrLength, doubles.data()); + break; + } + case 'L': + strippedClassName = elementType.substr(1, elementType.length() - 2); + elementClass = jenv.FindClass(strippedClassName); + arr = jenv.NewObjectArray(arrLength, elementClass, nullptr); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + napi_get_element(env, jsArr, i, &element); + JsArgToArrayConverter c(env, element, false, (int) Type::Null); + jobject o = c.GetConvertedArg(); + jenv.SetObjectArrayElement((jobjectArray) arr, (int) i, o); + } + break; + default: + success = false; + break; + } + + if (success) { + SetConvertedObject(index, arr); + } + + return success; +} + + +template +bool JsArgConverter::ConvertFromCastFunctionObject(T value, int index) { + bool success = false; + + const auto &typeSignature = m_tokens.at(index); + + const char typeSignaturePrefix = typeSignature[0]; + + switch (typeSignaturePrefix) { + case 'B': + m_args[index].b = (jbyte) value; + success = true; + break; + + case 'S': + m_args[index].s = (jshort) value; + success = true; + break; + + case 'I': + m_args[index].i = (jint) value; + success = true; + break; + + case 'J': + m_args[index].j = (jlong) value; + success = true; + break; + + case 'F': + m_args[index].f = (jfloat) value; + success = true; + break; + + case 'D': + m_args[index].d = (jdouble) value; + success = true; + break; + + default: + success = false; + break; + } + + return success; +} + +int JsArgConverter::Length() const { + return m_argsLen; +} + +bool JsArgConverter::IsValid() const { + return m_isValid; +} + +jvalue *JsArgConverter::ToArgs() { + return m_args; +} + +JsArgConverter::Error JsArgConverter::GetError() const { + return m_error; +} + +JsArgConverter::~JsArgConverter() { + if (m_argsLen > 0) { + JEnv env; + for (int i = 0; i < m_args_refs_size; i++) { + int index = m_args_refs[i]; + if (index != -1) { + env.DeleteLocalRef(m_args[index].l); + } + } + } +} + +JniLocalRef JsArgConverter::GetByteBuffer(napi_env env, napi_value object, bool isArrayBuffer, + bool isTypedArray, bool isDataView) { + JEnv jEnv; + + BufferCastType bufferCastType = tns::BufferCastType::Byte; + size_t offset = 0; + size_t length = 0; + void *data = nullptr; + + if (isTypedArray) { + napi_typedarray_type type; + napi_value arrayBuffer; + size_t byteOffset; + napi_get_typedarray_info(env, object, &type, nullptr, &data, + &arrayBuffer, &byteOffset); + + + + + napi_get_arraybuffer_info(env, arrayBuffer, nullptr, &length); + + offset = byteOffset; + bufferCastType = JsArgConverter::GetCastType(type); + } else if (isArrayBuffer) { + napi_get_arraybuffer_info(env, object, &data, &length); + } else if (isDataView) { + napi_get_dataview_info(env, object, &length, &data, nullptr, + &offset); + } + + JniLocalRef directBuffer(jEnv.NewDirectByteBuffer( + static_cast(data) + (isDataView || isTypedArray ? offset : 0), length)); + JniLocalRef directBufferClazz(jEnv.GetObjectClass(directBuffer)); + + auto byteOrderId = BYTE_ORDER_METHOD_ID; + + if (!BYTE_ORDER_METHOD_ID) { + byteOrderId = jEnv.GetMethodID(directBufferClazz, "order", + "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); + BYTE_ORDER_METHOD_ID = byteOrderId; + } + + JniLocalRef byteOrderClazz(jEnv.FindClass("java/nio/ByteOrder")); + + auto byteOrderEnumId = BYTE_ORDER_ENUM_ID; + + if (!byteOrderEnumId) { + byteOrderEnumId = jEnv.GetStaticMethodID(byteOrderClazz, + "nativeOrder", + "()Ljava/nio/ByteOrder;"); + BYTE_ORDER_ENUM_ID = byteOrderEnumId; + } + + JniLocalRef nativeByteOrder(jEnv.CallStaticObjectMethodA(byteOrderClazz, + byteOrderEnumId, + nullptr)); + directBuffer = JniLocalRef(jEnv.CallObjectMethod(directBuffer, byteOrderId, + static_cast(nativeByteOrder))); + + JniLocalRef buffer; + + if (bufferCastType == BufferCastType::Short) { + auto id = AS_SHORT_BUFFER; + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asShortBuffer", + "()Ljava/nio/ShortBuffer;"); + AS_SHORT_BUFFER = id; + } + + buffer = JniLocalRef(jEnv.CallObjectMethodA(directBuffer, id, nullptr)); + } else if (bufferCastType == BufferCastType::Int) { + auto id = AS_INT_BUFFER; + + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asIntBuffer", + "()Ljava/nio/IntBuffer;"); + AS_INT_BUFFER = id; + } + buffer = JniLocalRef(jEnv.CallObjectMethodA(directBuffer, id, nullptr)); + } else if (bufferCastType == BufferCastType::Long) { + auto id = AS_LONG_BUFFER; + + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asLongBuffer", + "()Ljava/nio/LongBuffer;"); + AS_LONG_BUFFER = id; + } + + buffer = JniLocalRef(jEnv.CallObjectMethodA(directBuffer, id, nullptr)); + } else if (bufferCastType == BufferCastType::Float) { + + auto id = AS_FLOAT_BUFFER; + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asFloatBuffer", + "()Ljava/nio/FloatBuffer;"); + AS_FLOAT_BUFFER = id; + } + + buffer = JniLocalRef(jEnv.CallObjectMethodA(directBuffer, id, nullptr)); + } else if (bufferCastType == BufferCastType::Double) { + + auto id = AS_DOUBLE_BUFFER; + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asDoubleBuffer", + "()Ljava/nio/DoubleBuffer;"); + AS_DOUBLE_BUFFER = id; + } + buffer = JniLocalRef(jEnv.CallObjectMethodA(directBuffer, id, nullptr)); + } else { + buffer = std::move(directBuffer); + } + + ObjectManager *objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + + int id = objectManager->GetOrCreateObjectId(buffer); + ObjectManager::MarkObject(env, object); + + objectManager->Link(object, id); + + return objectManager->GetJavaObjectByJsObject(object); +} + +jmethodID JsArgConverter::BYTE_ORDER_METHOD_ID = nullptr; +jmethodID JsArgConverter::BYTE_ORDER_ENUM_ID = nullptr; +jmethodID JsArgConverter::AS_SHORT_BUFFER = nullptr; +jmethodID JsArgConverter::AS_LONG_BUFFER = nullptr; +jmethodID JsArgConverter::AS_FLOAT_BUFFER = nullptr; +jmethodID JsArgConverter::AS_INT_BUFFER = nullptr; +jmethodID JsArgConverter::AS_DOUBLE_BUFFER = nullptr; diff --git a/NativeScript/ffi/jni/napi/conversion/JsArgConverter.h b/NativeScript/ffi/jni/napi/conversion/JsArgConverter.h new file mode 100644 index 000000000..33ce2049d --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/JsArgConverter.h @@ -0,0 +1,100 @@ +#ifndef JSARGCONVERTER_H_ +#define JSARGCONVERTER_H_ + +#include +#include +#include "JEnv.h" +#include "Runtime.h" +#include "MetadataEntry.h" + +namespace tns { + + enum class BufferCastType { + Byte, + Short, + Int, + Long, + Float, + Double + }; + + class JsArgConverter { + public: + + JsArgConverter(napi_env env, napi_value caller, napi_value* args, size_t argc, const std::string& methodSignature, MetadataEntry* entry); + + JsArgConverter(napi_env env, napi_value* args, size_t argc, bool hasImplementationObject, const std::string& methodSignature, MetadataEntry* entry); + + JsArgConverter(napi_env env, napi_value* args, size_t argc, const std::string& methodSignature); + + ~JsArgConverter(); + + jvalue* ToArgs(); + + int Length() const; + + bool IsValid() const; + + struct Error; + + Error GetError() const; + + struct Error { + Error() : + index(-1), msg(std::string()) { + } + + int index; + std::string msg; + }; + + static BufferCastType GetCastType(napi_typedarray_type type); + + static JniLocalRef GetByteBuffer(napi_env env, napi_value object, bool isArrayBuffer, bool isTypedArray, bool isDataView); + + + + static jmethodID BYTE_ORDER_METHOD_ID; + static jmethodID BYTE_ORDER_ENUM_ID; + static jmethodID AS_SHORT_BUFFER; + static jmethodID AS_INT_BUFFER; + static jmethodID AS_LONG_BUFFER; + static jmethodID AS_FLOAT_BUFFER; + static jmethodID AS_DOUBLE_BUFFER; + private: + + bool ConvertArg(napi_env env, napi_value arg, int index); + + bool ConvertJavaScriptArray(napi_env env, napi_value jsArr, int index); + + bool ConvertJavaScriptNumber(napi_env env, napi_value jsValue, int index, bool isNumberObject); + + bool ConvertJavaScriptBoolean(napi_env env, napi_value jsValue, int index); + + bool ConvertJavaScriptString(napi_env env, napi_value jsValue, int index); + + void SetConvertedObject(int index, jobject obj, bool isGlobal = false); + + + template + bool ConvertFromCastFunctionObject(T value, int index); + + napi_env m_env; + + int m_argsLen; + + bool m_isValid; + + jvalue m_args[255]; + int m_args_refs[255]; + int m_args_refs_size = 0; + + std::string m_methodSignature; + + std::vector m_tokens; + + Error m_error; + }; +} + +#endif /* JSARGCONVERTER_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.cpp b/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.cpp new file mode 100644 index 000000000..2548c1339 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.cpp @@ -0,0 +1,429 @@ +#include "JsArgToArrayConverter.h" +#include +#include "ObjectManager.h" +#include "ArgConverter.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include "MetadataNode.h" +#include "JsArgConverter.h" + +using namespace std; +using namespace tns; + +JsArgToArrayConverter::JsArgToArrayConverter(napi_env env, napi_value arg, + bool isImplementationObject, int classReturnType) + : m_arr(nullptr), m_argsAsObject(nullptr), m_argsLen(0), m_isValid(false), m_error(Error()), + m_return_type(classReturnType) { + if (!isImplementationObject) { + m_argsLen = 1; + m_argsAsObject = new jobject[m_argsLen]; + memset(m_argsAsObject, 0, m_argsLen * sizeof(jobject)); + + m_isValid = ConvertArg(env, arg, 0); + } +} + +JsArgToArrayConverter::JsArgToArrayConverter(napi_env env, size_t argc, napi_value *argv, + bool hasImplementationObject) + : m_arr(nullptr), m_argsAsObject(nullptr), m_argsLen(0), m_isValid(false), m_error(Error()), + m_return_type(static_cast(Type::Null)) { + m_argsLen = !hasImplementationObject ? argc : argc - 2; + + bool success = true; + + if (m_argsLen > 0) { + m_argsAsObject = new jobject[m_argsLen]; + memset(m_argsAsObject, 0, m_argsLen * sizeof(jobject)); + + for (int i = 0; i < m_argsLen; i++) { + success = ConvertArg(env, argv[i], i); + + if (!success) { + break; + } + } + } + + m_isValid = success; +} + +bool JsArgToArrayConverter::ConvertArg(napi_env env, napi_value arg, int index) { + bool success = false; + stringstream s; + + JEnv jEnv; + + Type returnType = JType::getClassType(m_return_type); + + napi_valuetype argType; + napi_typeof(env, arg, &argType); + + if (argType == napi_undefined || argType == napi_null) { + SetConvertedObject(jEnv, index, nullptr); + success = true; + } else if (argType == napi_number) { + double d; + napi_get_value_double(env, arg, &d); + int64_t i = (int64_t) d; + + bool isWholeNumber = d == i; + + if (isWholeNumber) { + jobject obj; + + if ((INT_MIN <= i) && (i <= INT_MAX) && + (returnType == Type::Int || returnType == Type::Null)) { + obj = JType::NewInt(jEnv, (jint) i); + } else { + obj = JType::NewLong(jEnv, (jlong) d); + } + + SetConvertedObject(jEnv, index, obj); + success = true; + } else { + jobject obj; + + if ((FLT_MIN <= d) && (d <= FLT_MAX) && + (returnType == Type::Float || returnType == Type::Null)) { + obj = JType::NewFloat(jEnv, (jfloat) d); + } else { + obj = JType::NewDouble(jEnv, (jdouble) d); + } + + SetConvertedObject(jEnv, index, obj); + success = true; + } + } else if (argType == napi_boolean) { + bool value; + napi_get_value_bool(env, arg, &value); + auto javaObject = JType::NewBoolean(jEnv, value); + SetConvertedObject(jEnv, index, javaObject); + success = true; + } else if (argType == napi_string) { + auto stringObject = ArgConverter::ConvertToJavaString(env, arg); + SetConvertedObject(jEnv, index, stringObject); + success = true; + } else if (argType == napi_object || argType == napi_function) { + napi_value jsObj = arg; + + + CastType castType = CastType::None; +#ifdef USE_HOST_OBJECT + void *data; + napi_get_host_object_data(env, jsObj, &data); + if (data) { + castType = CastType::None; + } else { + castType = NumericCasts::GetCastType(env, jsObj); + } +#else + castType = NumericCasts::GetCastType(env, jsObj); +#endif + + napi_value castValue; + jchar charValue; + jbyte byteValue; + jshort shortValue; + jlong longValue; + jfloat floatValue; + jdouble doubleValue; + jobject javaObject; + JniLocalRef obj; + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + switch (castType) { + case CastType::Char: + castValue = NumericCasts::GetCastValue(env, jsObj); + charValue = '\0'; + if (castValue != nullptr) { + string str = ArgConverter::ConvertToString(env, castValue); + charValue = (jchar) str[0]; + } + javaObject = JType::NewChar(jEnv, charValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Byte: + castValue = NumericCasts::GetCastValue(env, jsObj); + byteValue = 0; + + if (castValue != nullptr) { + if (napi_util::is_of_type(env, castValue, napi_string)) { + string value = ArgConverter::ConvertToString(env, castValue); + int byteArg = atoi(value.c_str()); + byteValue = (jbyte) byteArg; + } else { + int byteArg = napi_util::get_int32(env, castValue); + byteValue = (jbyte) byteArg; + } + } + + javaObject = JType::NewByte(jEnv, byteValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Short: + castValue = NumericCasts::GetCastValue(env, jsObj); + shortValue = 0; + if (castValue != nullptr) { + if (napi_util::is_of_type(env, castValue, napi_string)) { + string value = ArgConverter::ConvertToString(env, castValue); + int shortArg = atoi(value.c_str()); + shortValue = (jshort) shortArg; + } else { + int shortArg = napi_util::get_int32(env, castValue); + shortValue = (jshort) shortArg; + } + } + + javaObject = JType::NewShort(jEnv, shortValue); + + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Long: + castValue = NumericCasts::GetCastValue(env, jsObj); + longValue = 0; + if (castValue != nullptr) { + if (napi_util::is_of_type(env, castValue, napi_string)) { + auto strValue = ArgConverter::ConvertToString(env, castValue); + longValue = atoll(strValue.c_str()); + } else { + int64_t longArg; + napi_get_value_int64(env, castValue, &longArg); + longValue = (jlong) longArg; + } + } + javaObject = JType::NewLong(jEnv, longValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Float: + castValue = NumericCasts::GetCastValue(env, jsObj); + floatValue = 0; + if (castValue != nullptr) { + double floatArg; + napi_get_value_double(env, castValue, &floatArg); + floatValue = (jfloat) floatArg; + } + javaObject = JType::NewFloat(jEnv, floatValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Double: + castValue = NumericCasts::GetCastValue(env, jsObj); + doubleValue = 0; + if (castValue != nullptr) { + double doubleArg; + napi_get_value_double(env, castValue, &doubleArg); + doubleValue = (jdouble) doubleArg; + } + javaObject = JType::NewDouble(jEnv, doubleValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::None: + + obj = objectManager->GetJavaObjectByJsObject(jsObj); + + if (obj.IsNull()) { + bool isArrayBuffer = false; + bool isDataView = false; + bool isTypedArray = false; + + napi_is_arraybuffer(env, jsObj, &isArrayBuffer); + if (!isArrayBuffer) { + napi_is_typedarray(env, jsObj, &isTypedArray); + if (!isTypedArray) { + napi_is_dataview(env, jsObj, &isDataView); + } + } + + if (isArrayBuffer || isDataView || isTypedArray) { + obj = JsArgConverter::GetByteBuffer(env, jsObj, isArrayBuffer, isTypedArray, + isDataView); + } + } + + +#ifdef USE_HOST_OBJECT + if (!data) { +#endif + napi_value privateValue; + napi_get_named_property(env, jsObj, PROP_KEY_NULL_NODE_NAME, &privateValue); + if (!napi_util::is_null_or_undefined(env, privateValue)) { + void *data; + napi_get_value_external(env, privateValue, &data); + auto node = reinterpret_cast(data); + if (node == nullptr) { + s << "Cannot get type of the null argument at index " << index; + success = false; + break; + } + + auto type = node->GetName(); + auto nullObjName = "com/tns/NullObject"; + auto nullObjCtorSig = "(Ljava/lang/Class;)V"; + + jclass nullClazz = jEnv.FindClass(nullObjName); + jmethodID ctor = jEnv.GetMethodID(nullClazz, "", nullObjCtorSig); + jclass clazzToNull = jEnv.FindClass(type); + jobject nullObjType = jEnv.NewObject(nullClazz, ctor, clazzToNull); + + if (nullObjType != nullptr) { + SetConvertedObject(jEnv, index, nullObjType, false); + } else { + SetConvertedObject(jEnv, index, nullptr); + } + + success = true; + return success; + } + +#ifdef USE_HOST_OBJECT + } +#endif + + + success = !obj.IsNull(); + if (success) { + SetConvertedObject(jEnv, index, obj.Move(), false); + } else { + if (napi_util::is_number_object(env, arg)) { + napi_value numValue = napi_util::valueOf(env, arg); + bool isFloat = napi_util::is_float(env, numValue); + if (isFloat) { + double floatArg; + napi_get_value_double(env, numValue, &floatArg); + jfloat value = (jfloat) floatArg; + javaObject = JType::NewFloat(jEnv, value); + SetConvertedObject(jEnv, index, javaObject); + success = true; + } else { + int intArg; + napi_get_value_int32(env, numValue, &intArg); + jint value = (jint) intArg; + javaObject = JType::NewInt(jEnv, value); + SetConvertedObject(jEnv, index, javaObject); + success = true; + } + break; + } else if (napi_util::is_string_object(env, arg)) { + napi_value stringValue = napi_util::valueOf(env, arg); + javaObject = ArgConverter::ConvertToJavaString(env, stringValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + } else if (napi_util::is_boolean_object(env, arg)) { + napi_value boolValue = napi_util::valueOf(env, arg); + bool value = napi_util::get_bool(env, boolValue); + javaObject = JType::NewBoolean(jEnv, value); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + } + + if (!success) { + napi_value objStr; + napi_coerce_to_string(env, jsObj, &objStr); + const char *objStrValue = napi_util::get_string_value(env, objStr); + s << "Cannot marshal JavaScript argument " << objStrValue << " at index " + << index + << " to Java type."; + } + } + break; + + default: + throw NativeScriptException("Unsupported cast type"); + } + } else { + s << "Cannot marshal JavaScript argument at index " << index << " to Java type."; + success = false; + } + + if (!success) { + m_error.index = index; + m_error.msg = s.str(); + } + + return success; +} + +jobject JsArgToArrayConverter::GetConvertedArg() { + return (m_argsLen > 0) ? m_argsAsObject[0] : nullptr; +} + +void JsArgToArrayConverter::SetConvertedObject(JEnv &env, int index, jobject obj, bool isGlobal) { + m_argsAsObject[index] = obj; + if ((obj != nullptr) && !isGlobal) { + m_storedIndexes.push_back(index); + } +} + +int JsArgToArrayConverter::Length() const { + return m_argsLen; +} + +bool JsArgToArrayConverter::IsValid() const { + return m_isValid; +} + +JsArgToArrayConverter::Error JsArgToArrayConverter::GetError() const { + return m_error; +} + +jobjectArray JsArgToArrayConverter::ToJavaArray() { + if ((m_arr == nullptr) && (m_argsLen > 0)) { + if (m_argsLen >= JsArgToArrayConverter::MAX_JAVA_PARAMS_COUNT) { + stringstream ss; + ss << "You are trying to override more than the MAX_JAVA_PARAMS_COUNT: " + << JsArgToArrayConverter::MAX_JAVA_PARAMS_COUNT; + throw NativeScriptException(ss.str()); + } + + JEnv jEnv; + + if (JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS == nullptr) { + JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS = jEnv.FindClass("java/lang/Object"); + } + + JniLocalRef tmpArr( + jEnv.NewObjectArray(m_argsLen, JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS, + nullptr)); + m_arr = (jobjectArray) jEnv.NewGlobalRef(tmpArr); + + for (int i = 0; i < m_argsLen; i++) { + jEnv.SetObjectArrayElement(m_arr, i, m_argsAsObject[i]); + } + } + + return m_arr; +} + +JsArgToArrayConverter::~JsArgToArrayConverter() { + if (m_argsLen > 0) { + JEnv env; + + env.DeleteGlobalRef(m_arr); + + int length = m_storedIndexes.size(); + for (int i = 0; i < length; i++) { + int index = m_storedIndexes[i]; + env.DeleteLocalRef(m_argsAsObject[index]); + } + + delete[] m_argsAsObject; + } +} + +jclass JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS = nullptr; diff --git a/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.h b/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.h new file mode 100644 index 000000000..1d0c37590 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.h @@ -0,0 +1,65 @@ +#ifndef JSARGTOARRAYCONVERTER_H_ +#define JSARGTOARRAYCONVERTER_H_ + +#include "JEnv.h" +#include "JniLocalRef.h" +#include "js_native_api.h" +#include +#include + +namespace tns { +class JsArgToArrayConverter { + public: + JsArgToArrayConverter(napi_env env, size_t argc, napi_value* argv, bool hasImplementationObject); + + JsArgToArrayConverter(napi_env env, napi_value arg, bool isImplementationObject, int classReturnType); + + ~JsArgToArrayConverter(); + + jobjectArray ToJavaArray(); + + jobject GetConvertedArg(); + + int Length() const; + + bool IsValid() const; + + struct Error; + + Error GetError() const; + + struct Error { + Error() : + index(-1), msg(std::string()) { + } + + int index; + std::string msg; + }; + + private: + bool ConvertArg(napi_env env, napi_value arg, int index); + + void SetConvertedObject(JEnv& env, int index, jobject obj, bool isGlobal = false); + + int m_argsLen; + + int m_return_type; + + bool m_isValid; + + Error m_error; + + std::vector m_storedIndexes; + + jobject* m_argsAsObject; + + jobjectArray m_arr; + + short MAX_JAVA_PARAMS_COUNT = 256; + + static jclass JAVA_LANG_OBJECT_CLASS; +}; +} + +#endif /* JSARGTOARRAYCONVERTER_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/NumericCasts.cpp b/NativeScript/ffi/jni/napi/conversion/NumericCasts.cpp new file mode 100644 index 000000000..148020676 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/NumericCasts.cpp @@ -0,0 +1,218 @@ +#include "NumericCasts.h" +#include "NativeScriptAssert.h" +#include "Util.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include + +using namespace std; +using namespace tns; + +void NumericCasts::CreateGlobalCastFunctions(napi_env env, napi_value globalObject) { + + napi_value longFunc, byteFunc, shortFunc, doubleFunc, floatFunc, charFunc; + + napi_create_function(env, "long", NAPI_AUTO_LENGTH, NumericCasts::MarkAsLongCallback, nullptr, + &longFunc); + napi_create_function(env, "byte", NAPI_AUTO_LENGTH, NumericCasts::MarkAsByteCallback, nullptr, + &byteFunc); + napi_create_function(env, "short", NAPI_AUTO_LENGTH, NumericCasts::MarkAsShortCallback, nullptr, + &shortFunc); + napi_create_function(env, "double", NAPI_AUTO_LENGTH, NumericCasts::MarkAsDoubleCallback, + nullptr, + &doubleFunc); + napi_create_function(env, "float", NAPI_AUTO_LENGTH, NumericCasts::MarkAsFloatCallback, nullptr, + &floatFunc); + napi_create_function(env, "char", NAPI_AUTO_LENGTH, NumericCasts::MarkAsCharCallback, nullptr, + &charFunc); + + napi_set_named_property(env, globalObject, "long", longFunc); + napi_set_named_property(env, globalObject, "byte", byteFunc); + napi_set_named_property(env, globalObject, "short", shortFunc); + napi_set_named_property(env, globalObject, "double", doubleFunc); + napi_set_named_property(env, globalObject, "float", floatFunc); + napi_set_named_property(env, globalObject, "char", charFunc); +} + +void NumericCasts::MarkAsLong(napi_env env, napi_value object, napi_value value) { + MarkJsObject(env, object, CastType::Long, value); +} + + +napi_value NumericCasts::MarkAsLongCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + if (argc != 1) { + napi_throw_error(env, nullptr, "long(x) should be called with single parameter"); + return nullptr; + } + + napi_valuetype type; + napi_typeof(env, argv[0], &type); + + if (type != napi_string && type != napi_number) { + napi_throw_error(env, nullptr, + "long(x) should be called with single parameter containing a long number representation"); + return nullptr; + } + + napi_value value = argv[0]; + + napi_value cast; + napi_create_object(env, &cast); + MarkJsObject(env, cast, CastType::Long, value); + return cast; +} + +napi_value NumericCasts::MarkAsByteCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + if (argc != 1) { + napi_throw_error(env, nullptr, "byte(x) should be called with single parameter"); + return nullptr; + } + + napi_valuetype type; + napi_typeof(env, argv[0], &type); + + if (type != napi_string && type != napi_number && !napi_util::is_number_object(env, argv[0]) && !napi_util::is_string_object(env, argv[0])) { + napi_throw_error(env, nullptr, + "byte(x) should be called with single parameter containing a byte number representation"); + return nullptr; + } + napi_value value; + if (type == napi_number) { + value = argv[0]; + } else { + napi_coerce_to_string(env, argv[0], &value); + } + + napi_value cast; + napi_create_object(env, &cast); + MarkJsObject(env, cast, CastType::Byte, value); + return cast; +} + +napi_value NumericCasts::MarkAsShortCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + if (argc != 1) { + napi_throw_error(env, nullptr, "short(x) should be called with single parameter"); + return nullptr; + } + + napi_valuetype type; + napi_typeof(env, argv[0], &type); + + if (type != napi_string && type != napi_number && !napi_util::is_number_object(env, argv[0]) && !napi_util::is_string_object(env, argv[0])) { + napi_throw_error(env, nullptr, + "short(x) should be called with single parameter containing a byte number representation"); + return nullptr; + } + napi_value value; + if (type == napi_number) { + value = argv[0]; + } else { + napi_coerce_to_string(env, argv[0], &value); + } + + napi_value cast; + napi_create_object(env, &cast); + MarkJsObject(env, cast, CastType::Short, value); + return cast; +} + +napi_value NumericCasts::MarkAsCharCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + if (argc != 1) { + napi_throw_error(env, nullptr, "char(x) should be called with single parameter"); + return nullptr; + } + + napi_valuetype type; + napi_typeof(env, argv[0], &type); + + if (type != napi_string) { + napi_throw_error(env, nullptr, + "char(x) should be called with single parameter containing a char representation"); + return nullptr; + } + + size_t str_len; + napi_get_value_string_utf8(env, argv[0], nullptr, 0, &str_len); + if (str_len != 1) { + napi_throw_error(env, nullptr, + "char(x) should be called with single parameter containing a single char"); + return nullptr; + } + + + napi_value cast; + napi_create_object(env, &cast); + MarkJsObject(env, cast, CastType::Char, argv[0]); + return cast; +} + +napi_value NumericCasts::MarkAsFloatCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + if (argc != 1) { + napi_throw_error(env, nullptr, "float(x) should be called with single parameter"); + return nullptr; + } + + napi_valuetype type; + napi_typeof(env, argv[0], &type); + + if (type != napi_number) { + napi_throw_error(env, nullptr, + "float(x) should be called with single parameter containing a float number representation"); + return nullptr; + } + + napi_value value = argv[0]; + + napi_value cast; + napi_create_object(env, &cast); + MarkJsObject(env, cast, CastType::Float, value); + return cast; +} + +napi_value NumericCasts::MarkAsDoubleCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + if (argc != 1) { + napi_throw_error(env, nullptr, "double(x) should be called with single parameter"); + return nullptr; + } + + napi_valuetype type; + napi_typeof(env, argv[0], &type); + + if (type != napi_number) { + napi_throw_error(env, nullptr, + "double(x) should be called with single parameter containing a double number representation"); + return nullptr; + } + + napi_value value = argv[0]; + + napi_value cast; + napi_create_object(env, &cast); + MarkJsObject(env, cast, CastType::Double, value); + return cast; +} + +void +NumericCasts::MarkJsObject(napi_env env, napi_value object, CastType castType, napi_value value) { + napi_value type; + napi_create_int32(env, static_cast(castType), &type); + + napi_set_named_property(env, object, s_castMarker, type); + napi_set_named_property(env, object, "value", value); + +// DEBUG_WRITE("MarkJsObject: Marking js object with cast type: %d", castType); +} + +const char *NumericCasts::s_castMarker = "t::cast"; \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/NumericCasts.h b/NativeScript/ffi/jni/napi/conversion/NumericCasts.h new file mode 100644 index 000000000..27041e161 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/NumericCasts.h @@ -0,0 +1,75 @@ +#ifndef NUMERICCASTS_H_ +#define NUMERICCASTS_H_ + +#include "js_native_api.h" +#include "Runtime.h" +#include + +namespace tns { + enum class CastType { + None, + Char, + Byte, + Short, + Long, + Float, + Double + }; + + class NumericCasts { + public: + void CreateGlobalCastFunctions(napi_env env, napi_value globalObject); + + inline static CastType GetCastType(napi_env env, napi_value object) { + CastType ret = CastType::None; + +#ifdef USE_HOST_OBJECT + void *data; + napi_get_host_object_data(env, object, &data); + if (data != nullptr) return ret; +#endif + + napi_value hidden; + + bool hasProperty; + napi_has_named_property(env, object, s_castMarker, &hasProperty); + + if (hasProperty) { + napi_get_named_property(env, object, s_castMarker, &hidden); + int32_t castType; + napi_get_value_int32(env, hidden, &castType); + ret = static_cast(castType); + } + + return ret; + } + + inline static napi_value GetCastValue(napi_env env, napi_value object) { + napi_value value; + napi_get_named_property(env, object, "value", &value); + return value; + } + + static void MarkAsLong(napi_env env, napi_value object, napi_value value); + + private: + static napi_value MarkAsLongCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsByteCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsShortCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsCharCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsFloatCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsDoubleCallback(napi_env env, napi_callback_info info); + + static void + MarkJsObject(napi_env env, napi_value object, CastType castType, napi_value value); + + static const char *s_castMarker; + }; +} + +#endif /* NUMERICCASTS_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/exceptions/NativeScriptAssert.h b/NativeScript/ffi/jni/napi/exceptions/NativeScriptAssert.h new file mode 100644 index 000000000..0fe1ce5a8 --- /dev/null +++ b/NativeScript/ffi/jni/napi/exceptions/NativeScriptAssert.h @@ -0,0 +1,22 @@ +/* + * nativescriptassert.h + * + * Created on: 12.11.2013 + * Author: blagoev + */ + +#ifndef NATIVESCRIPTASSERT_H_ +#define NATIVESCRIPTASSERT_H_ + +#include + +namespace tns { +extern bool LogEnabled; + +#define DEBUG_WRITE(fmt, args...) if (tns::LogEnabled) __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native", fmt, ##args) +//#define DEBUG_WRITE(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native", fmt, ##args) +#define DEBUG_WRITE_FORCE(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native", fmt, ##args) +#define DEBUG_WRITE_FATAL(fmt, args...) __android_log_print(ANDROID_LOG_FATAL, "TNS.Native", fmt, ##args) +} + +#endif /* NATIVESCRIPTASSERT_H_ */ diff --git a/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.cpp b/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.cpp new file mode 100644 index 000000000..966164c4c --- /dev/null +++ b/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.cpp @@ -0,0 +1,405 @@ +#include "Util.h" +#include "NativeScriptException.h" +#include "ArgConverter.h" +#include "NativeScriptAssert.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +NativeScriptException::NativeScriptException(JEnv& env) + : m_javascriptException(nullptr), m_napiEnv(nullptr) { + jthrowable thrw = env.ExceptionOccurred(); + m_javaException = JniLocalRef(thrw); + env.ExceptionClear(); + DEBUG_WRITE("%s, %s", GetExceptionMessage(env, m_javaException).c_str(), GetExceptionStackTrace(env, m_javaException).c_str()); +} + +NativeScriptException::NativeScriptException(const string& message) + : m_javascriptException(nullptr), m_napiEnv(nullptr), m_javaException(JniLocalRef()), m_message(message) { + + DEBUG_WRITE("%s", m_message.c_str()); +} + +NativeScriptException::NativeScriptException(const string& message, const string& stackTrace) + : m_javascriptException(nullptr), m_napiEnv(nullptr), m_javaException(JniLocalRef()), m_message(message), m_stackTrace(stackTrace) { + + DEBUG_WRITE("%s, %s ", m_message.c_str(), m_stackTrace.c_str()); +} + +NativeScriptException::NativeScriptException(napi_env env, napi_value error, const string& message) + : m_javascriptException(nullptr), m_napiEnv(env), m_javaException(JniLocalRef()) { + napi_create_reference(env, error, 1, &m_javascriptException); + m_message = GetErrorMessage(env, error, message); + m_stackTrace = GetErrorStackTrace(env, error); + m_fullMessage = GetFullMessage(env, error, m_message); +} + +NativeScriptException::NativeScriptException(NativeScriptException&& other) noexcept + : m_javascriptException(other.m_javascriptException), + m_napiEnv(other.m_napiEnv), + m_javaException(std::move(other.m_javaException)), + m_message(std::move(other.m_message)), + m_stackTrace(std::move(other.m_stackTrace)), + m_fullMessage(std::move(other.m_fullMessage)) { + other.m_javascriptException = nullptr; + other.m_napiEnv = nullptr; +} + +NativeScriptException::~NativeScriptException() { + if (m_javascriptException != nullptr && m_napiEnv != nullptr) { + napi_delete_reference(m_napiEnv, m_javascriptException); + } +} + +void NativeScriptException::ReThrowToNapi(napi_env env) { + napi_value errObj; + + if (m_javascriptException != nullptr) { + napi_get_reference_value(env, m_javascriptException, &errObj); + if (napi_util::is_of_type(env, errObj, napi_object)) { + if (!m_fullMessage.empty()) { + napi_set_named_property(env, errObj, "fullMessage", ArgConverter::convertToJsString(env, m_fullMessage)); + } else if (!m_message.empty()) { + napi_set_named_property(env, errObj, "fullMessage", ArgConverter::convertToJsString(env, m_message)); + } + } + } else if (!m_fullMessage.empty()) { + napi_create_error(env, nullptr, ArgConverter::convertToJsString(env, m_fullMessage), &errObj); + } else if (!m_message.empty()) { + napi_create_error(env, nullptr, ArgConverter::convertToJsString(env, m_message), &errObj); + } else if (!m_javaException.IsNull()) { + errObj = WrapJavaToJsException(env); + } else { + napi_create_error(env, nullptr, ArgConverter::convertToJsString(env, "No javascript exception or message provided."), &errObj); + } + + napi_throw(env, errObj); + + if (m_javascriptException != nullptr) { + napi_delete_reference(env, m_javascriptException); + m_javascriptException = nullptr; + m_napiEnv = nullptr; + } + +// JSLeave +} + +void NativeScriptException::ReThrowToJava(napi_env env) { + if (env) { + NapiScope scope(env); + } + jthrowable ex = nullptr; + bool transferredJavascriptException = false; + JEnv jEnv; + + if (!m_javaException.IsNull()) { + std::string excClassName; + if (env) { + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + excClassName = objectManager->GetClassName((jobject)m_javaException); + } + + if (excClassName == "com/tns/NativeScriptException") { + ex = m_javaException; + } else { + JniLocalRef msg(jEnv.NewStringUTF("Java Error!")); + JniLocalRef stack(jEnv.NewStringUTF("")); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring)msg, (jstring)stack, (jobject)m_javaException)); + } + } else if (m_javascriptException != nullptr && env != nullptr) { + napi_value errObj; + napi_get_reference_value(env, m_javascriptException, &errObj); + if (napi_util::is_of_type(env, errObj, napi_object)) { + auto exObj = TryGetJavaThrowableObject(jEnv, env, errObj); + ex = (jthrowable)exObj.Move(); + } + + JniLocalRef msg(jEnv.NewStringUTF(m_message.c_str())); + JniLocalRef stackTrace(jEnv.NewStringUTF(m_stackTrace.c_str())); + + if (ex == nullptr) { + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring)msg, (jstring)stackTrace, reinterpret_cast(m_javascriptException))); + transferredJavascriptException = true; + } else { + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + auto excClassName = objectManager->GetClassName(ex); + if (excClassName != "com/tns/NativeScriptException") { + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring)msg, (jstring)stackTrace, ex)); + } + } + } else if (!m_message.empty()) { + JniLocalRef msg(jEnv.NewStringUTF(m_message.c_str())); + JniLocalRef stackTrace(jEnv.NewStringUTF(m_stackTrace.c_str())); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring)msg, (jstring)stackTrace, (jlong)0)); + } else { + JniLocalRef msg(jEnv.NewStringUTF("No java exception or message provided.")); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring)msg, (jstring)nullptr, (jlong)0)); + } + jEnv.Throw(ex); + + if (m_javascriptException != nullptr) { + if (!transferredJavascriptException && m_napiEnv != nullptr) { + napi_delete_reference(m_napiEnv, m_javascriptException); + } + m_javascriptException = nullptr; + m_napiEnv = nullptr; + } +} + +void NativeScriptException::Init() { + JEnv jenv; + + RUNTIME_CLASS = jenv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + THROWABLE_CLASS = jenv.FindClass("java/lang/Throwable"); + assert(THROWABLE_CLASS != nullptr); + + NATIVESCRIPTEXCEPTION_CLASS = jenv.FindClass("com/tns/NativeScriptException"); + assert(NATIVESCRIPTEXCEPTION_CLASS != nullptr); + + NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = jenv.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "", "(Ljava/lang/String;Ljava/lang/String;J)V"); + assert(NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID != nullptr); + + NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = jenv.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V"); + assert(NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID != nullptr); + + NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = jenv.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getStackTraceAsString", "(Ljava/lang/Throwable;)Ljava/lang/String;"); + assert(NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID != nullptr); + + NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = jenv.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getMessage", "(Ljava/lang/Throwable;)Ljava/lang/String;"); + assert(NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID != nullptr); +} + +// ON N-API UNCAUGHT EXCEPTION +void NativeScriptException::OnUncaughtError(napi_env env, napi_value error) { + string errorMessage = GetErrorMessage(env, error); + string stackTrace = GetErrorStackTrace(env, error); + + NativeScriptException e(errorMessage, stackTrace); + e.ReThrowToJava(env); +} + +void NativeScriptException::CallJsFuncWithErr(napi_env env, napi_value errObj, bool isDiscarded) { + napi_value global; + napi_get_global(env, &global); + + napi_value handler = nullptr; + if (isDiscarded) { + napi_get_named_property(env, global, "__onDiscardedError", &handler); + } else { + napi_get_named_property(env, global, "__onUncaughtError", &handler); + } + + if (napi_util::is_of_type(env, handler, napi_function)) { + napi_value result; + napi_call_function(env, global, handler, 1, &errObj, &result); + } +} + +napi_value NativeScriptException::WrapJavaToJsException(napi_env env) { + napi_value errObj; + + JEnv jenv; + + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + + string excClassName = objectManager->GetClassName((jobject)m_javaException); + if (excClassName == "com/tns/NativeScriptException") { + jfieldID fieldID = jenv.GetFieldID(jenv.GetObjectClass(m_javaException), "jsValueAddress", "J"); + jlong addr = jenv.GetLongField(m_javaException, fieldID); + + if (addr != 0) { + auto pv = reinterpret_cast(addr); + napi_get_reference_value(env, pv, &errObj); + napi_delete_reference(env, pv); + jenv.SetLongField(m_javaException, fieldID, 0); + } else { + errObj = GetJavaExceptionFromEnv(env, m_javaException, jenv); + } + } else { + errObj = GetJavaExceptionFromEnv(env, m_javaException, jenv); + } + + return errObj; +} + +napi_value NativeScriptException::GetJavaExceptionFromEnv(napi_env env, const JniLocalRef& exc, JEnv& jenv) { + auto errMsg = GetExceptionMessage(jenv, exc); + auto stackTrace = GetExceptionStackTrace(jenv, exc); + DEBUG_WRITE("Error during java interop errorMessage: %s\n stackTrace:\n %s", errMsg.c_str(), stackTrace.c_str()); + + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + + napi_value msg = ArgConverter::convertToJsString(env, errMsg); + napi_value errObj; + napi_value code = ArgConverter::convertToJsString(env, "0", 1); + napi_create_error(env, code, msg, &errObj); + + jint javaObjectID = objectManager->GetOrCreateObjectId((jobject)exc); + auto nativeExceptionObject = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, nativeExceptionObject)) { + string className = objectManager->GetClassName((jobject)exc); + nativeExceptionObject = objectManager->CreateJSWrapper(javaObjectID, className); + } + + napi_set_named_property(env, errObj, "nativeException", nativeExceptionObject); + + string jsStackTraceMessage = GetErrorStackTrace(env, errObj); + napi_set_named_property(env, errObj, "stack", ArgConverter::convertToJsString(env, jsStackTraceMessage)); + napi_set_named_property(env, errObj, "stackTrace", ArgConverter::convertToJsString(env, jsStackTraceMessage + stackTrace) ); + + return errObj; +} + +string NativeScriptException::GetFullMessage(napi_env env, napi_value error, const string& jsExceptionMessage) { + bool isError; + napi_is_error(env, error, &isError); + if (!isError) { + return jsExceptionMessage; + } + + stringstream ss; + ss << jsExceptionMessage; + + string stackTraceMessage = GetErrorStackTrace(env, error); + + ss << endl << "StackTrace: " << endl << stackTraceMessage << endl; + + string loggedMessage = ss.str(); + + PrintErrorMessage(loggedMessage); + + return loggedMessage; +} + +JniLocalRef NativeScriptException::TryGetJavaThrowableObject(JEnv& env, napi_env napiEnv, napi_value jsObj) { + JniLocalRef javaThrowableObject; + + auto objectManager = Runtime::GetRuntime(napiEnv)->GetObjectManager(); + + auto javaObj = objectManager->GetJavaObjectByJsObject(jsObj); + JniLocalRef objClass; + + if (!javaObj.IsNull()) { + objClass = JniLocalRef(env.GetObjectClass(javaObj)); + } else { + napi_value nativeEx; + napi_get_named_property(napiEnv, jsObj, "nativeException", &nativeEx); + if (napi_util::is_object(napiEnv, nativeEx)) { + javaObj = objectManager->GetJavaObjectByJsObject(nativeEx); + objClass = JniLocalRef(env.GetObjectClass(javaObj)); + } + } + + auto isThrowable = !objClass.IsNull() ? env.IsAssignableFrom(objClass, THROWABLE_CLASS) : JNI_FALSE; + + if (isThrowable == JNI_TRUE) { + javaThrowableObject = JniLocalRef(env.NewLocalRef(javaObj)); + } + + return javaThrowableObject; +} + +void NativeScriptException::PrintErrorMessage(const string& errorMessage) { + stringstream ss(errorMessage); + string line; + while (getline(ss, line, '\n')) { + DEBUG_WRITE("%s", line.c_str()); + } +} + +string NativeScriptException::GetErrorMessage(napi_env env, napi_value error, const string& prependMessage) { + bool isError; + napi_is_error(env, error, &isError); + + if (!isError) { + napi_value err; + napi_coerce_to_string(env, error, &err); + return napi_util::get_string_value(env, err); + } + + napi_value message; + napi_get_named_property(env, error, "message", &message); + + string mes = ArgConverter::ConvertToString(env, message); + + stringstream ss; + + if (!prependMessage.empty()) { + ss << prependMessage << endl; + } + + string errMessage; + bool hasFullErrorMessage = false; + napi_value fullMessage; + napi_get_named_property(env, error, "fullMessage", &fullMessage); + if (napi_util::is_of_type(env, fullMessage, napi_string)) { + hasFullErrorMessage = true; + errMessage = ArgConverter::ConvertToString(env, fullMessage); + ss << errMessage; + } + + if (!mes.empty()) { + if (hasFullErrorMessage) { + ss << endl; + } + ss << mes; + } + + return ss.str(); +} + +string NativeScriptException::GetErrorStackTrace(napi_env env, napi_value error) { + stringstream ss; + + bool isError; + napi_is_error(env, error, &isError); + if (!isError) return ""; + + napi_value stack; + napi_get_named_property(env, error, "stack", &stack); + + + string stackStr = ArgConverter::ConvertToString(env, stack); + ss << stackStr; + + return ss.str(); +} + +string NativeScriptException::GetExceptionMessage(JEnv& env, jthrowable exception) { + string errMsg; + JniLocalRef msg(env.CallStaticObjectMethod(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID, exception)); + + const char* msgStr = env.GetStringUTFChars(msg, nullptr); + + errMsg.append(msgStr); + + env.ReleaseStringUTFChars(msg, msgStr); + + return errMsg; +} + +string NativeScriptException::GetExceptionStackTrace(JEnv& env, jthrowable exception) { + string errStackTrace; + JniLocalRef msg(env.CallStaticObjectMethod(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID, exception)); + + const char* msgStr = env.GetStringUTFChars(msg, nullptr); + + errStackTrace.append(msgStr); + + env.ReleaseStringUTFChars(msg, msgStr); + + return errStackTrace; +} + +jclass NativeScriptException::RUNTIME_CLASS = nullptr; +jclass NativeScriptException::THROWABLE_CLASS = nullptr; +jclass NativeScriptException::NATIVESCRIPTEXCEPTION_CLASS = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = nullptr; diff --git a/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.h b/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.h new file mode 100644 index 000000000..3a93db615 --- /dev/null +++ b/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.h @@ -0,0 +1,112 @@ +#ifndef NATIVESCRIPTEXCEPTION_H_ +#define NATIVESCRIPTEXCEPTION_H_ + +#include "js_native_api.h" +#include "JEnv.h" +#include "JniLocalRef.h" +#include "ObjectManager.h" + +namespace tns { +class NativeScriptException { + public: + /* + * Generates a NativeScriptException with java error from environment + */ + NativeScriptException(JEnv& env); + + /* + * Generates a NativeScriptException with given message + */ + NativeScriptException(const std::string& message); + + /* + * Generates a NativeScriptException with given message and stackTrace + */ + NativeScriptException(const std::string& message, const std::string& stackTrace); + + /* + * Generates a NativeScriptException with javascript error from napi_env and a prepend message if any + */ + NativeScriptException(napi_env env, napi_value error, const std::string& message = ""); + + NativeScriptException(const NativeScriptException&) = delete; + NativeScriptException& operator=(const NativeScriptException&) = delete; + NativeScriptException(NativeScriptException&& other) noexcept; + ~NativeScriptException(); + + void ReThrowToNapi(napi_env env); + void ReThrowToJava(napi_env env); + + static void Init(); + + /* + * This handler is attached to Node-API to handle uncaught javascript exceptions. + */ + static void OnUncaughtError(napi_env env, napi_value error); + + /* + * Calls the global "__onUncaughtError" or "__onDiscardedError" if such is provided + */ + static void CallJsFuncWithErr(napi_env env, napi_value errObj, bool isDiscarded); + + private: + /* + * Try to get native exception or NativeScriptException from js object + */ + JniLocalRef TryGetJavaThrowableObject(JEnv& env, napi_env napiEnv, napi_value jsObj); + + /* + * Gets java exception message from jthrowable + */ + std::string GetExceptionMessage(JEnv& env, jthrowable exception); + + /* + * Gets java exception stack trace from jthrowable + */ + std::string GetExceptionStackTrace(JEnv& env, jthrowable exception); + + /* + * Gets the member m_javaException, wraps it and creates a javascript error object from it + */ + napi_value WrapJavaToJsException(napi_env env); + + /* + * Gets all the information from a java exception and puts it in a javascript error object + */ + napi_value GetJavaExceptionFromEnv(napi_env env, const JniLocalRef& exc, JEnv& jenv); + + /* + * Gets all the information from a js message and an js error object and puts it in a string + */ + static std::string GetErrorMessage(napi_env env, napi_value error, const std::string& prependMessage = ""); + + /* + * Generates string stack trace from js StackTrace + */ + static std::string GetErrorStackTrace(napi_env env, napi_value stackTrace); + + /* + * Adds a prepend message to the normal message process + */ + std::string GetFullMessage(napi_env env, napi_value error, const std::string& jsExceptionMessage); + + napi_ref m_javascriptException; + napi_env m_napiEnv; + JniLocalRef m_javaException; + std::string m_message; + std::string m_stackTrace; + std::string m_fullMessage; + + static jclass RUNTIME_CLASS; + static jclass THROWABLE_CLASS; + static jclass NATIVESCRIPTEXCEPTION_CLASS; + static jmethodID NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID; + static jmethodID NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID; + static jmethodID NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID; + static jmethodID NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID; + + static void PrintErrorMessage(const std::string& errorMessage); +}; +} + +#endif /* NATIVESCRIPTEXCEPTION_H_ */ diff --git a/NativeScript/ffi/jni/napi/global/GlobalHelpers.cpp b/NativeScript/ffi/jni/napi/global/GlobalHelpers.cpp new file mode 100644 index 000000000..c81812709 --- /dev/null +++ b/NativeScript/ffi/jni/napi/global/GlobalHelpers.cpp @@ -0,0 +1,195 @@ +#include "GlobalHelpers.h" +#include "ArgConverter.h" +#include "CallbackHandlers.h" +#include "JEnv.h" +#include "NativeScriptException.h" +#include +#include "robin_hood.h" +#include "Util.h" +#include + +using namespace std; + +static robin_hood::unordered_map envToPersistentSmartJSONStringify = robin_hood::unordered_map(); + +napi_value GetSmartJSONStringifyFunction(napi_env env) { + auto it = envToPersistentSmartJSONStringify.find(env); + if (it != envToPersistentSmartJSONStringify.end()) { + napi_value smartStringifyFunction; + napi_get_reference_value(env, it->second, &smartStringifyFunction); + return smartStringifyFunction; + } + + const char * smartStringifyFunctionScript = R"( + (function () { + function smartStringify(object, handleCirculars) { + if (!handleCirculars) { + return JSON.stringify(object, null, 2); + } + + const seen = []; + var replacer = function (key, value) { + if (value != null && typeof value == "object") { + if (seen.indexOf(value) >= 0) { + if (key) { + return "[Circular]"; + } + return; + } + seen.push(value); + } + return value; + }; + return JSON.stringify(object, replacer, 2); + } + return smartStringify; +})(); +)"; + + + napi_value source; + napi_create_string_utf8(env, smartStringifyFunctionScript, strlen(smartStringifyFunctionScript), &source); + + napi_value global; + napi_get_global(env, &global); + + napi_value result; + napi_status status = js_execute_script(env, source, "", &result); + if (status != napi_ok) { + return nullptr; + } + + if (!napi_util::is_of_type(env, result, napi_function)) { + return nullptr; + } + + napi_ref smartStringifyPersistentFunction; + napi_create_reference(env, result, 1, &smartStringifyPersistentFunction); + + envToPersistentSmartJSONStringify.emplace(env, smartStringifyPersistentFunction); + + return result; +} + + + +std::string tns::JsonStringifyObject(napi_env env, napi_value value, bool handleCircularReferences) { + if (value == nullptr) { + return ""; + } + + napi_value smartJSONStringifyFunction = GetSmartJSONStringifyFunction(env); + std::string result; + if (smartJSONStringifyFunction != nullptr) { + napi_value resultValue; + napi_value args[2]; + args[0] = value; + args[1] = handleCircularReferences ? napi_util::get_true(env) : napi_util::get_false(env); + napi_status status = napi_call_function(env, napi_util::global(env), smartJSONStringifyFunction, 2, args, &resultValue); + if (status != napi_ok) { + napi_value exception; + napi_get_and_clear_last_exception(env, &exception); + if (!napi_util::is_null_or_undefined(env, exception)) { + throw NativeScriptException(env, exception, "Error converting object to json"); + } else { + throw NativeScriptException("Error converting object to json"); + } + } + result = ArgConverter::ConvertToString(env, resultValue); + } + + return result; +} + +napi_value tns::JsonParseString(napi_env env, const std::string& value) { + napi_value global; + napi_value json; + napi_value parse; + + napi_get_global(env, &global); + napi_get_named_property(env, global, "JSON", &json); + napi_get_named_property(env, json, "parse", &parse); + + napi_value args[1]; + args[0] = ArgConverter::convertToJsString(env, value); + napi_value result; + napi_status status = napi_call_function(env, json, parse, 1, args, &result); + if (status != napi_ok) { + napi_value exception; + napi_get_and_clear_last_exception(env, &exception); + if (!napi_util::is_null_or_undefined(env, exception)) { + throw NativeScriptException(env, exception, "Error converting json string to object"); + } else { + throw NativeScriptException("Error converting json string to object"); + } + } + return result; +} + +std::vector tns::BuildStacktraceFrames(napi_env env, napi_value error, int size) { + std::vector frames; + napi_value stack; + if (error != nullptr) { + napi_get_named_property(env, error, "stack", &stack); + } else { +#ifndef __HERMES__ + napi_value err; + napi_value msg; + napi_create_string_utf8(env, "Error", strlen("Error"), &msg); + #ifdef __PRIMJS__ + napi_value error_ctor; + napi_get_named_property(env, napi_util::global(env), "Error", &error_ctor); + + napi_new_instance(env, error_ctor, 1, &msg, &err); + #else + napi_create_error(env, msg, msg, &err); + #endif + napi_get_named_property(env, err, "stack", &stack); +#else + napi_value global; + napi_get_global(env, &global); + napi_value getErrorStack; + napi_get_named_property(env, global, "getErrorStack", &getErrorStack); + napi_call_function(env, global, getErrorStack, 0, nullptr, &stack); +#endif + } + + if (napi_util::is_null_or_undefined(env, stack)) return frames; + + string stackTrace = napi_util::get_string_value(env, stack); + vector stackLines; + Util::SplitString(stackTrace, "\n", stackLines); + + int current = 0; + int count = 0; + for (auto &frame : stackLines) { + count++; +#ifdef __HERMES__ + if (error == nullptr && count < 3) continue; +#endif + +//#ifdef __JSC__ + regex frameRegex(R"((file:.*):(\d+):(\d+))"); +//#else +// regex frameRegex(R"(\((.*):(\d+):(\d+)\))"); +//#endif + smatch match; + if (regex_search(frame, match, frameRegex)) { + current++; + frames.emplace_back(stoi(match[2].str()), + stoi(match[3].str()), + match[1].str(), + frame); + if (current == size) break; + } + } + return frames; +} + +void tns::GlobalHelpers::onDisposeEnv(napi_env env) { + auto found = envToPersistentSmartJSONStringify.find(env); + if (found != envToPersistentSmartJSONStringify.end()) { + napi_delete_reference(env, found->second); + } + envToPersistentSmartJSONStringify.erase(env); +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/global/GlobalHelpers.h b/NativeScript/ffi/jni/napi/global/GlobalHelpers.h new file mode 100644 index 000000000..f2d9ce809 --- /dev/null +++ b/NativeScript/ffi/jni/napi/global/GlobalHelpers.h @@ -0,0 +1,34 @@ +#ifndef NAPI_GLOBALHELPERS_H_ +#define NAPI_GLOBALHELPERS_H_ + +#include "jni.h" +#include "js_native_api.h" +#include +#include +#include + +namespace tns { +std::string JsonStringifyObject(napi_env env, napi_value value, bool handleCircularReferences = true); + +napi_value JsonParseString(napi_env env, const std::string& value); + +struct JsStacktraceFrame { + JsStacktraceFrame(): line(0), col(0) {} + JsStacktraceFrame( + int _line, int _col, std::string _filename, std::string _text + ): line(_line), col(_col), filename(std::move(_filename)), text(std::move(_text)) {} + + int line; + int col; + std::string filename; + std::string text; +}; + +std::vector BuildStacktraceFrames(napi_env env, napi_value error, int size); + +namespace GlobalHelpers { + void onDisposeEnv(napi_env env); +} +} + +#endif /* NAPI_GLOBALHELPERS_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.cpp b/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.cpp new file mode 100644 index 000000000..fdbe50d60 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.cpp @@ -0,0 +1,11 @@ +#include "DesugaredInterfaceCompanionClassNameResolver.h" + +std::string DesugaredInterfaceCompanionClassNameResolver::resolveD8InterfaceCompanionClassName( + const std::string& interfaceName) { + return interfaceName + D8_COMPANION_CLASS_SUFFIX; +} + +std::string DesugaredInterfaceCompanionClassNameResolver::resolveBazelInterfaceCompanionClassName( + const std::string& interfaceName) { + return interfaceName + BAZEL_COMPANION_CLASS_SUFFIX; +} diff --git a/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.h b/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.h new file mode 100644 index 000000000..052dc8952 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.h @@ -0,0 +1,22 @@ +#ifndef TEST_APP_DESUGAREDINTERFACECOMPANIONCLASSNAMERESOLVER_H +#define TEST_APP_DESUGAREDINTERFACECOMPANIONCLASSNAMERESOLVER_H + + +#include + +class DesugaredInterfaceCompanionClassNameResolver { + +public: + std::string resolveD8InterfaceCompanionClassName(const std::string& interfaceName); + + std::string resolveBazelInterfaceCompanionClassName(const std::string& interfaceName); + +private: + const std::string BAZEL_COMPANION_CLASS_SUFFIX = "$$CC"; + const std::string D8_COMPANION_CLASS_SUFFIX = "$-CC"; + + +}; + + +#endif //TEST_APP_DESUGAREDINTERFACECOMPANIONCLASSNAMERESOLVER_H diff --git a/NativeScript/ffi/jni/napi/jni/DirectBuffer.cpp b/NativeScript/ffi/jni/napi/jni/DirectBuffer.cpp new file mode 100644 index 000000000..1b3e6f8a3 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/DirectBuffer.cpp @@ -0,0 +1,56 @@ +#include "DirectBuffer.h" +#include "JniLocalRef.h" + +using namespace tns; + +DirectBuffer::DirectBuffer(uint32_t length) { + m_length = length; + + m_data = new int[m_length]; + + m_end = m_data + m_length; + + Reset(); + + int capacity = m_length * sizeof(int); + + JEnv env; + JniLocalRef buff(env.NewDirectByteBuffer(m_data, capacity)); + + m_buff = env.NewGlobalRef(buff); +} + +DirectBuffer::operator jobject() const { + return m_buff; +} + +int* DirectBuffer::GetData() const { + return m_data; +} + +int DirectBuffer::Length() const { + return m_length; +} + +int DirectBuffer::Size() const { + return m_pos - m_data; +} + +void DirectBuffer::Reset() { + m_pos = m_data; +} + +bool DirectBuffer::Write(int value) { + bool canWrite = m_pos < m_end; + if (canWrite) { + int bigEndianInt = __builtin_bswap32(value); + *(m_pos++) = bigEndianInt; + } + return canWrite; +} + +DirectBuffer::~DirectBuffer() { + JEnv env; + env.DeleteGlobalRef(m_buff); + delete[] m_data; +} diff --git a/NativeScript/ffi/jni/napi/jni/DirectBuffer.h b/NativeScript/ffi/jni/napi/jni/DirectBuffer.h new file mode 100644 index 000000000..4de8a8a63 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/DirectBuffer.h @@ -0,0 +1,30 @@ +#ifndef DIRECTBUFFER_H_ +#define DIRECTBUFFER_H_ + +#include "JEnv.h" + +namespace tns { +class DirectBuffer { + public: + DirectBuffer(uint32_t capacity = 65536); + ~DirectBuffer(); + + operator jobject() const; + + int* GetData() const; + int Length() const; + int Size() const; + + void Reset(); + bool Write(int value); + + private: + jobject m_buff; + int* m_data; + jlong m_length; + int* m_pos; + int* m_end; +}; +} + +#endif /* DIRECTBUFFER_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/File.cpp b/NativeScript/ffi/jni/napi/jni/File.cpp new file mode 100644 index 000000000..0dad918da --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/File.cpp @@ -0,0 +1,108 @@ +/* + * File.cpp + * + * Created on: Jun 24, 2015 + * Author: gatanasov + */ + +#include "File.h" +#include +#include +#include +#include + +using namespace std; + +namespace tns { + +string File::ReadText(const string& filePath) { + int len; + bool isNew; + const char* content = ReadText(filePath, len, isNew); + + string s(content, len); + + if (isNew) { + delete[] content; + } + + return s; +} + +void* File::ReadBinary(const string& filePath, int& length) { + length = 0; + + auto file = fopen(filePath.c_str(), READ_BINARY); + if (!file) { + return nullptr; + } + + fseek(file, 0, SEEK_END); + length = ftell(file); + rewind(file); + + uint8_t* data = new uint8_t[length]; + fread(data, sizeof(uint8_t), length, file); + fclose(file); + + return data; +} + +bool File::WriteBinary(const string& filePath, const void* data, int length) { + auto file = fopen(filePath.c_str(), WRITE_BINARY); + if (!file) { + return false; + } + + auto writtenBytes = fwrite(data, sizeof(uint8_t), length, file); + fclose(file); + + return writtenBytes == length; +} + +const char* File::ReadText(const string& filePath, int& charLength, bool& isNew) { + FILE* file = fopen(filePath.c_str(), "rb"); + fseek(file, 0, SEEK_END); + + charLength = ftell(file); + isNew = charLength > BUFFER_SIZE; + + rewind(file); + + if (isNew) { + char* newBuffer = new char[charLength]; + fread(newBuffer, 1, charLength, file); + fclose(file); + + return newBuffer; + } + + fread(Buffer, 1, charLength, file); + fclose(file); + + return Buffer; +} + +std::unique_ptr File::ReadFile(const std::string &filePath, int &length, int extraBuffer) { + FILE *file = fopen(filePath.c_str(), "rb"); + if (!file) { + std::stringstream ss; + ss << "metadata file (" << filePath << ") couldn't be opened! (Error: " << errno << ") "; +// throw NativeScriptException(ss.str()); + } + + fseek(file, 0, SEEK_END); + length = ftell(file); + std::unique_ptr buffer(new char[length + extraBuffer]); + rewind(file); + fread(buffer.get(), 1, length, file); + fclose(file); + + return buffer; + } + +char* File::Buffer = new char[BUFFER_SIZE]; + +const char* File::WRITE_BINARY = "wb"; +const char* File::READ_BINARY = "rb"; +} diff --git a/NativeScript/ffi/jni/napi/jni/File.h b/NativeScript/ffi/jni/napi/jni/File.h new file mode 100644 index 000000000..de9509282 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/File.h @@ -0,0 +1,29 @@ +/* + * File.h + * + * Created on: Jun 24, 2015 + * Author: gatanasov + */ + +#ifndef JNI_FILE_H_ +#define JNI_FILE_H_ + +#include + +namespace tns { +class File { + public: + static const char* ReadText(const std::string& filePath, int& length, bool& isNew); + static std::string ReadText(const std::string& filePath); + static bool WriteBinary(const std::string& filePath, const void* inData, int length); + static void* ReadBinary(const std::string& filePath, int& length); + static std::unique_ptr ReadFile(const std::string &filePath, int &length, int extraBuffer = 0); +private: + static const int BUFFER_SIZE = 1024 * 1024; + static char* Buffer; + static const char* WRITE_BINARY; + static const char* READ_BINARY; +}; +} + +#endif /* JNI_FILE_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/JEnv.cpp b/NativeScript/ffi/jni/napi/jni/JEnv.cpp new file mode 100644 index 000000000..190b2817a --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JEnv.cpp @@ -0,0 +1,901 @@ +#include "JEnv.h" +#include +#include "Util.h" +#include "DesugaredInterfaceCompanionClassNameResolver.h" +#include "NativeScriptException.h" + +using namespace tns; +using namespace std; + +JEnv::JEnv() + : m_env(nullptr) { + JNIEnv *env = nullptr; + jint ret = s_jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + + if ((ret != JNI_OK) || (env == nullptr)) { + ret = s_jvm->AttachCurrentThread(&env, nullptr); + assert(ret == JNI_OK); + assert(env != nullptr); + } + + m_env = env; +} + +JEnv::JEnv(JNIEnv *jniEnv) { + jint ret = s_jvm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6); + + if ((ret != JNI_OK) || (jniEnv == nullptr)) { + ret = s_jvm->AttachCurrentThread(&jniEnv, nullptr); + assert(ret == JNI_OK); + assert(jniEnv != nullptr); + } + + m_env = jniEnv; +} + +JEnv::~JEnv() { +} + +JEnv::operator JNIEnv* () const { + return m_env; +} + +jmethodID JEnv::GetMethodID(jclass clazz, const string &name, const string &sig) { + jmethodID mid = m_env->GetMethodID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return mid; +} + +jmethodID JEnv::GetStaticMethodID(jclass clazz, const string &name, const string &sig) { + jmethodID mid = m_env->GetStaticMethodID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return mid; +} + +jfieldID JEnv::GetFieldID(jclass clazz, const string &name, const string &sig) { + jfieldID fid = m_env->GetFieldID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return fid; +} + +jfieldID JEnv::GetStaticFieldID(jclass clazz, const string &name, const string &sig) { + jfieldID fid = m_env->GetStaticFieldID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return fid; +} + +void JEnv::CallStaticVoidMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + m_env->CallStaticVoidMethodA(clazz, methodID, args); + CheckForJavaException(); +} + +void JEnv::CallNonvirtualVoidMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + m_env->CallNonvirtualVoidMethodA(obj, clazz, methodID, args); + CheckForJavaException(); +} + +void JEnv::CallVoidMethodA(jobject obj, jmethodID methodID, jvalue *args) { + m_env->CallVoidMethodA(obj, methodID, args); + CheckForJavaException(); +} + +jboolean JEnv::CallStaticBooleanMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jboolean jbl = m_env->CallStaticBooleanMethodA(clazz, methodID, args); + CheckForJavaException(); + return jbl; +} + +jboolean +JEnv::CallNonvirtualBooleanMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jboolean jbl = m_env->CallNonvirtualBooleanMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jbl; +} + +jboolean JEnv::CallBooleanMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jboolean jbl = m_env->CallBooleanMethodA(obj, methodID, args); + CheckForJavaException(); + return jbl; +} + +jbyte JEnv::CallStaticByteMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jbyte jbt = m_env->CallStaticByteMethodA(clazz, methodID, args); + CheckForJavaException(); + return jbt; +} + +jbyte JEnv::CallNonvirtualByteMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jbyte jbt = m_env->CallNonvirtualByteMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jbt; +} + +jbyte JEnv::CallByteMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jbyte jbt = m_env->CallByteMethodA(obj, methodID, args); + CheckForJavaException(); + return jbt; +} + +jchar JEnv::CallStaticCharMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jchar jch = m_env->CallStaticCharMethodA(clazz, methodID, args); + CheckForJavaException(); + return jch; +} + +jchar JEnv::CallNonvirtualCharMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jchar jch = m_env->CallNonvirtualCharMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jch; +} + +jchar JEnv::CallCharMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jchar jch = m_env->CallCharMethodA(obj, methodID, args); + CheckForJavaException(); + return jch; +} + +jshort JEnv::CallStaticShortMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jshort jsh = m_env->CallStaticShortMethodA(clazz, methodID, args); + CheckForJavaException(); + return jsh; + +} + +jshort +JEnv::CallNonvirtualShortMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jshort jsh = m_env->CallNonvirtualShortMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jsh; +} + +jshort JEnv::CallShortMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jshort jsh = m_env->CallShortMethodA(obj, methodID, args); + CheckForJavaException(); + return jsh; +} + +jint JEnv::CallStaticIntMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jint ji = m_env->CallStaticIntMethodA(clazz, methodID, args); + CheckForJavaException(); + return ji; + +} + +jint JEnv::CallNonvirtualIntMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jint ji = m_env->CallNonvirtualIntMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return ji; +} + +jint JEnv::CallIntMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jint ji = m_env->CallIntMethodA(obj, methodID, args); + CheckForJavaException(); + return ji; +} + +jlong JEnv::CallStaticLongMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jlong jl = m_env->CallStaticLongMethodA(clazz, methodID, args); + CheckForJavaException(); + return jl; +} + +jlong JEnv::CallNonvirtualLongMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jlong jl = m_env->CallNonvirtualLongMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jl; +} + +jlong JEnv::CallLongMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jlong jl = m_env->CallLongMethodA(obj, methodID, args); + CheckForJavaException(); + return jl; +} + +jfloat JEnv::CallStaticFloatMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jfloat jfl = m_env->CallStaticFloatMethodA(clazz, methodID, args); + CheckForJavaException(); + return jfl; +} + +jfloat +JEnv::CallNonvirtualFloatMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jfloat jfl = m_env->CallNonvirtualFloatMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jfl; +} + +jfloat JEnv::CallFloatMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jfloat jfl = m_env->CallFloatMethodA(obj, methodID, args); + CheckForJavaException(); + return jfl; +} + +jdouble JEnv::CallStaticDoubleMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jdouble jdb = m_env->CallStaticDoubleMethodA(clazz, methodID, args); + CheckForJavaException(); + return jdb; +} + +jdouble +JEnv::CallNonvirtualDoubleMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jdouble jdb = m_env->CallNonvirtualDoubleMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jdb; +} + +jdouble JEnv::CallDoubleMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jdouble jdb = m_env->CallDoubleMethodA(obj, methodID, args); + CheckForJavaException(); + return jdb; +} + +jobject JEnv::CallStaticObjectMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jobject jo = m_env->CallStaticObjectMethodA(clazz, methodID, args); + CheckForJavaException(); + return jo; +} + +jobject +JEnv::CallNonvirtualObjectMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jobject jo = m_env->CallNonvirtualObjectMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jo; +} + +jobject JEnv::CallObjectMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jobject jo = m_env->CallObjectMethodA(obj, methodID, args); + CheckForJavaException(); + return jo; +} + +jobject JEnv::GetStaticObjectField(jclass clazz, jfieldID fieldID) { + jobject jo = m_env->GetStaticObjectField(clazz, fieldID); + CheckForJavaException(); + return jo; +} + +jboolean JEnv::GetStaticBooleanField(jclass clazz, jfieldID fieldID) { + jboolean jbl = m_env->GetStaticBooleanField(clazz, fieldID); + CheckForJavaException(); + return jbl; +} + +jbyte JEnv::GetStaticByteField(jclass clazz, jfieldID fieldID) { + jbyte jbt = m_env->GetStaticByteField(clazz, fieldID); + CheckForJavaException(); + return jbt; +} + +jchar JEnv::GetStaticCharField(jclass clazz, jfieldID fieldID) { + jchar jch = m_env->GetStaticCharField(clazz, fieldID); + CheckForJavaException(); + return jch; +} + +jshort JEnv::GetStaticShortField(jclass clazz, jfieldID fieldID) { + jshort jsh = m_env->GetStaticShortField(clazz, fieldID); + CheckForJavaException(); + return jsh; +} + +jint JEnv::GetStaticIntField(jclass clazz, jfieldID fieldID) { + jint ji = m_env->GetStaticIntField(clazz, fieldID); + CheckForJavaException(); + return ji; +} + +jlong JEnv::GetStaticLongField(jclass clazz, jfieldID fieldID) { + jlong jl = m_env->GetStaticLongField(clazz, fieldID); + CheckForJavaException(); + return jl; +} + +jfloat JEnv::GetStaticFloatField(jclass clazz, jfieldID fieldID) { + jfloat jfl = m_env->GetStaticFloatField(clazz, fieldID); + CheckForJavaException(); + return jfl; +} + +jdouble JEnv::GetStaticDoubleField(jclass clazz, jfieldID fieldID) { + jdouble jd = m_env->GetStaticDoubleField(clazz, fieldID); + CheckForJavaException(); + return jd; +} + +void JEnv::SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value) { + m_env->SetStaticObjectField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value) { + m_env->SetStaticBooleanField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value) { + m_env->SetStaticByteField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value) { + m_env->SetStaticCharField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value) { + m_env->SetStaticShortField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticIntField(jclass clazz, jfieldID fieldID, jint value) { + m_env->SetStaticIntField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value) { + m_env->SetStaticLongField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value) { + m_env->SetStaticFloatField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value) { + m_env->SetStaticDoubleField(clazz, fieldID, value); + CheckForJavaException(); +} + +jobject JEnv::GetObjectField(jobject obj, jfieldID fieldID) { + jobject jo = m_env->GetObjectField(obj, fieldID); + CheckForJavaException(); + return jo; +} + +jboolean JEnv::GetBooleanField(jobject obj, jfieldID fieldID) { + jboolean jbl = m_env->GetBooleanField(obj, fieldID); + CheckForJavaException(); + return jbl; +} + +jbyte JEnv::GetByteField(jobject obj, jfieldID fieldID) { + jbyte jbt = m_env->GetByteField(obj, fieldID); + CheckForJavaException(); + return jbt; +} + +jchar JEnv::GetCharField(jobject obj, jfieldID fieldID) { + jchar jch = m_env->GetCharField(obj, fieldID); + CheckForJavaException(); + return jch; +} + +jshort JEnv::GetShortField(jobject obj, jfieldID fieldID) { + jshort jsh = m_env->GetShortField(obj, fieldID); + CheckForJavaException(); + return jsh; +} + +jint JEnv::GetIntField(jobject obj, jfieldID fieldID) { + jint ji = m_env->GetIntField(obj, fieldID); + CheckForJavaException(); + return ji; +} + +jlong JEnv::GetLongField(jobject obj, jfieldID fieldID) { + jlong jl = m_env->GetLongField(obj, fieldID); + CheckForJavaException(); + return jl; +} + +jfloat JEnv::GetFloatField(jobject obj, jfieldID fieldID) { + jfloat jfl = m_env->GetFloatField(obj, fieldID); + CheckForJavaException(); + return jfl; +} + +jdouble JEnv::GetDoubleField(jobject obj, jfieldID fieldID) { + jdouble jd = m_env->GetDoubleField(obj, fieldID); + CheckForJavaException(); + return jd; +} + +void JEnv::SetObjectField(jobject obj, jfieldID fieldID, jobject value) { + m_env->SetObjectField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetBooleanField(jobject obj, jfieldID fieldID, jboolean value) { + m_env->SetBooleanField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetByteField(jobject obj, jfieldID fieldID, jbyte value) { + m_env->SetByteField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetCharField(jobject obj, jfieldID fieldID, jchar value) { + m_env->SetCharField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetShortField(jobject obj, jfieldID fieldID, jshort value) { + m_env->SetShortField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetIntField(jobject obj, jfieldID fieldID, jint value) { + m_env->SetIntField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetLongField(jobject obj, jfieldID fieldID, jlong value) { + m_env->SetLongField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetFloatField(jobject obj, jfieldID fieldID, jfloat value) { + m_env->SetFloatField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetDoubleField(jobject obj, jfieldID fieldID, jdouble value) { + m_env->SetDoubleField(obj, fieldID, value); + CheckForJavaException(); +} + +jstring JEnv::NewString(const jchar *unicodeChars, jsize len) { + jstring jst = m_env->NewString(unicodeChars, len); + CheckForJavaException(); + return jst; +} + +jstring JEnv::NewStringUTF(const char *bytes) { + jstring jst = m_env->NewStringUTF(bytes); + CheckForJavaException(); + return jst; +} + +jobjectArray JEnv::NewObjectArray(jsize length, jclass elementClass, jobject initialElement) { + jobjectArray joa = m_env->NewObjectArray(length, elementClass, initialElement); + CheckForJavaException(); + return joa; +} + +jobject JEnv::GetObjectArrayElement(jobjectArray array, jsize index) { + jobject jo = m_env->GetObjectArrayElement(array, index); + CheckForJavaException(); + return jo; +} + +void JEnv::SetObjectArrayElement(jobjectArray array, jsize index, jobject value) { + m_env->SetObjectArrayElement(array, index, value); + CheckForJavaException(); +} + +const char *JEnv::GetStringUTFChars(jstring str, jboolean *isCopy) { + const char *cc = m_env->GetStringUTFChars(str, isCopy); + CheckForJavaException(); + return cc; +} + +void JEnv::ReleaseStringUTFChars(jstring str, const char *utf) { + m_env->ReleaseStringUTFChars(str, utf); + CheckForJavaException(); +} + +const jchar *JEnv::GetStringChars(jstring str, jboolean *isCopy) { + const jchar *cjc = m_env->GetStringChars(str, isCopy); + CheckForJavaException(); + return cjc; +} + +void JEnv::ReleaseStringChars(jstring str, const jchar *chars) { + m_env->ReleaseStringChars(str, chars); + CheckForJavaException(); +} + +const int JEnv::GetStringLength(jstring str) { + const int ci = m_env->GetStringLength(str); + CheckForJavaException(); + return ci; +} + +const int JEnv::GetStringUTFLength(jstring str) { + const int ci = m_env->GetStringUTFLength(str); + CheckForJavaException(); + return ci; +} + +void JEnv::GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) { + m_env->GetStringUTFRegion(str, start, len, buf); + CheckForJavaException(); +} + +jint JEnv::Throw(jthrowable obj) { + return m_env->Throw(obj); +} + +jint JEnv::ThrowNew(jclass clazz, const string &message) { + return m_env->ThrowNew(clazz, message.c_str()); +} + +jthrowable JEnv::ExceptionOccurred() { + jthrowable jt = m_env->ExceptionOccurred(); + return jt; +} + +void JEnv::ExceptionDescribe() { + m_env->ExceptionDescribe(); + CheckForJavaException(); +} + +void JEnv::ExceptionClear() { + m_env->ExceptionClear(); +} + +jboolean JEnv::IsInstanceOf(jobject obj, jclass clazz) { + jboolean jbl = m_env->IsInstanceOf(obj, clazz); + CheckForJavaException(); + return jbl; +} + +jobjectRefType JEnv::GetObjectRefType(jobject obj) { + jobjectRefType ort = m_env->GetObjectRefType(obj); + CheckForJavaException(); + return ort; +} + +jobject JEnv::NewGlobalRef(jobject obj) { + jobject jo = m_env->NewGlobalRef(obj); +// CheckForJavaException(); + return jo; +} + +jweak JEnv::NewWeakGlobalRef(jobject obj) { + jweak jw = m_env->NewWeakGlobalRef(obj); + CheckForJavaException(); + return jw; +} + +void JEnv::DeleteGlobalRef(jobject globalRef) { + m_env->DeleteGlobalRef(globalRef); + CheckForJavaException(); +} + +void JEnv::DeleteWeakGlobalRef(jweak obj) { + m_env->DeleteWeakGlobalRef(obj); + CheckForJavaException(); +} + +jobject JEnv::NewLocalRef(jobject ref) { + jobject jo = m_env->NewLocalRef(ref); + CheckForJavaException(); + return jo; +} + +void JEnv::DeleteLocalRef(jobject localRef) { + m_env->DeleteLocalRef(localRef); +} + +jbyteArray JEnv::NewByteArray(jsize length) { + jbyteArray jba = m_env->NewByteArray(length); + CheckForJavaException(); + return jba; +} + +jbooleanArray JEnv::NewBooleanArray(jsize length) { + jbooleanArray jba = m_env->NewBooleanArray(length); + CheckForJavaException(); + return jba; +} + +jcharArray JEnv::NewCharArray(jsize length) { + jcharArray jca = m_env->NewCharArray(length); + CheckForJavaException(); + return jca; +} + +jshortArray JEnv::NewShortArray(jsize length) { + jshortArray jsa = m_env->NewShortArray(length); + CheckForJavaException(); + return jsa; +} + +jintArray JEnv::NewIntArray(jsize length) { + jintArray jia = m_env->NewIntArray(length); + CheckForJavaException(); + return jia; +} + +jlongArray JEnv::NewLongArray(jsize length) { + jlongArray jla = m_env->NewLongArray(length); + CheckForJavaException(); + return jla; +} + +jfloatArray JEnv::NewFloatArray(jsize length) { + jfloatArray jfa = m_env->NewFloatArray(length); + CheckForJavaException(); + return jfa; +} + +jdoubleArray JEnv::NewDoubleArray(jsize length) { + jdoubleArray jda = m_env->NewDoubleArray(length); + CheckForJavaException(); + return jda; +} + +jbyte *JEnv::GetByteArrayElements(jbyteArray array, jboolean *isCopy) { + jbyte *jbt = m_env->GetByteArrayElements(array, isCopy); + CheckForJavaException(); + return jbt; +} + +void JEnv::ReleaseByteArrayElements(jbyteArray array, jbyte *elems, jint mode) { + m_env->ReleaseByteArrayElements(array, elems, mode); + CheckForJavaException(); +} + +void JEnv::GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean *buf) { + m_env->GetBooleanArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte *buf) { + m_env->GetByteArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar *buf) { + m_env->GetCharArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort *buf) { + m_env->GetShortArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetIntArrayRegion(jintArray array, jsize start, jsize len, jint *buf) { + m_env->GetIntArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +jint *JEnv::GetIntArrayElements(jintArray array, jboolean *isCopy) { + jint *jin = m_env->GetIntArrayElements(array, isCopy); + CheckForJavaException(); + return jin; +} + +void JEnv::GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong *buf) { + m_env->GetLongArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat *buf) { + m_env->GetFloatArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble *buf) { + m_env->GetDoubleArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte *buf) { + m_env->SetByteArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean *buf) { + m_env->SetBooleanArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar *buf) { + m_env->SetCharArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort *buf) { + m_env->SetShortArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint *buf) { + m_env->SetIntArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong *buf) { + m_env->SetLongArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat *buf) { + m_env->SetFloatArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble *buf) { + m_env->SetDoubleArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +jclass JEnv::FindClass(const string &className) { + jclass global_class = CheckForClassInCache(className); + + if (global_class == nullptr) { + auto classIsMissing = CheckForClassMissingCache(className); + // class is missing. Set the same JNI error we had when we tried to find it the first time + if (classIsMissing != nullptr) { + m_env->Throw(classIsMissing); + return nullptr; + } + jclass tmp = m_env->FindClass(className.c_str()); + + if (m_env->ExceptionCheck() == JNI_TRUE) { + m_env->ExceptionClear(); + string cannonicalClassName = Util::ConvertFromJniToCanonicalName(className); + jstring s = m_env->NewStringUTF(cannonicalClassName.c_str()); + tmp = static_cast(m_env->CallStaticObjectMethod(RUNTIME_CLASS, + GET_CACHED_CLASS_METHOD_ID, s)); + + m_env->DeleteLocalRef(s); + // we failed our static class check + // if we continue, we will crash (C++ level) + // so just return null and let the runtime deal with the NativeScriptException + if (m_env->ExceptionCheck() == JNI_TRUE) { + auto tmpException = m_env->ExceptionOccurred(); + m_env->ExceptionClear(); + m_env->Throw(InsertClassIntoMissingCache(className, tmpException)); + return nullptr; + } + } + + global_class = InsertClassIntoCache(className, tmp); + } + + return global_class; +} + +jclass JEnv::CheckForClassInCache(const string &className) { + jclass global_class = nullptr; + auto itFound = s_classCache.find(className); + + if (itFound != s_classCache.end()) { + global_class = itFound->second; + } + + return global_class; +} + +jclass JEnv::InsertClassIntoCache(const string &className, jclass &tmp) { + auto global_class = reinterpret_cast(m_env->NewGlobalRef(tmp)); + s_classCache.emplace(className, global_class); + m_env->DeleteLocalRef(tmp); + + return global_class; +} + +jthrowable JEnv::CheckForClassMissingCache(const string &className) { + jthrowable throwable = nullptr; + auto itFound = s_missingClasses.find(className); + + if (itFound != s_missingClasses.end()) { + throwable = itFound->second; + } + + return throwable; +} + +jthrowable JEnv::InsertClassIntoMissingCache(const string &className,const jthrowable &tmp) { + auto throwable = reinterpret_cast(m_env->NewGlobalRef(tmp)); + s_missingClasses.emplace(className, throwable); + m_env->DeleteLocalRef(tmp); + + return throwable; +} + +jobject JEnv::NewDirectByteBuffer(void *address, jlong capacity) { + jobject jo = m_env->NewDirectByteBuffer(address, capacity); + CheckForJavaException(); + return jo; +} + +void *JEnv::GetDirectBufferAddress(jobject buf) { + void *v = m_env->GetDirectBufferAddress(buf); + CheckForJavaException(); + return v; +} + +jlong JEnv::GetDirectBufferCapacity(jobject buf) { + jlong jl = m_env->GetDirectBufferCapacity(buf); + CheckForJavaException(); + return jl; +} + +jboolean JEnv::IsAssignableFrom(jclass clazz1, jclass clazz2) { + jboolean jbl = m_env->IsAssignableFrom(clazz1, clazz2); + CheckForJavaException(); + return jbl; +} + +void JEnv::Init(JavaVM *jvm) { + assert(jvm != nullptr); + s_jvm = jvm; + + JEnv env; + RUNTIME_CLASS = env.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + GET_CACHED_CLASS_METHOD_ID = env.GetStaticMethodID(RUNTIME_CLASS, "getCachedClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + assert(GET_CACHED_CLASS_METHOD_ID != nullptr); +} + +jclass JEnv::GetObjectClass(jobject obj) { + jclass jcl = m_env->GetObjectClass(obj); + CheckForJavaException(); + return jcl; +} + +jsize JEnv::GetArrayLength(jarray array) { + jsize jsz = m_env->GetArrayLength(array); + CheckForJavaException(); + return jsz; +} + +//recursion if we put: CheckForJavaException(); +//in this method +jboolean JEnv::ExceptionCheck() { + return m_env->ExceptionCheck(); +} + +void JEnv::CheckForJavaException() { + if (ExceptionCheck() == JNI_TRUE) { + throw NativeScriptException(*this); + } +} + +JavaVM *JEnv::s_jvm = nullptr; +robin_hood::unordered_map JEnv::s_classCache; +robin_hood::unordered_map JEnv::s_missingClasses; +jclass JEnv::RUNTIME_CLASS = nullptr; +jmethodID JEnv::GET_CACHED_CLASS_METHOD_ID = nullptr; + +std::pair +JEnv::GetInterfaceStaticMethodIDAndJClass(const std::string &interfaceName, + const std::string &methodName, + const std::string &sig) { + + DesugaredInterfaceCompanionClassNameResolver companionClassNameResolver; + std::string possibleCalleeNames[] = {interfaceName, + companionClassNameResolver.resolveBazelInterfaceCompanionClassName( + interfaceName), + companionClassNameResolver.resolveD8InterfaceCompanionClassName( + interfaceName)}; + + for (const std::string& calleeName: possibleCalleeNames) { + jclass clazz = this->FindClass(calleeName); + + if (clazz != NULL) { + jmethodID methodId = m_env->GetStaticMethodID(clazz, methodName.c_str(), sig.c_str()); + + if (ExceptionCheck() == JNI_FALSE) { + return std::make_pair(methodId, clazz); + } + + ExceptionClear(); + } + } + + throw NativeScriptException( + "Could not call static interface method with name: " + methodName + " and signature: " + + sig + " for interface: " + interfaceName); + +} + + diff --git a/NativeScript/ffi/jni/napi/jni/JEnv.h b/NativeScript/ffi/jni/napi/jni/JEnv.h new file mode 100644 index 000000000..d1de99d57 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JEnv.h @@ -0,0 +1,456 @@ +#ifndef JENV_H_ +#define JENV_H_ + +#include "jni.h" +#include "robin_hood.h" +#include + +namespace tns { + class JEnv { + public: + JEnv(); + + JEnv(JNIEnv *jniEnv); + + ~JEnv(); + + operator JNIEnv *() const; + + jclass GetObjectClass(jobject obj); + + jsize GetArrayLength(jarray array); + + jmethodID GetMethodID(jclass clazz, const std::string &name, const std::string &sig); + + jmethodID GetStaticMethodID(jclass clazz, const std::string &name, const std::string &sig); + + std::pair GetInterfaceStaticMethodIDAndJClass( + const std::string &interfaceName, const std::string &methodName, + const std::string &sig); + + jfieldID GetFieldID(jclass clazz, const std::string &name, const std::string &sig); + + jfieldID GetStaticFieldID(jclass clazz, const std::string &name, const std::string &sig); + + void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + void CallVoidMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jboolean CallStaticBooleanMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jboolean + CallNonvirtualBooleanMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jboolean CallBooleanMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jbyte CallStaticByteMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jbyte + CallNonvirtualByteMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jbyte CallByteMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jchar CallStaticCharMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jchar + CallNonvirtualCharMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jchar CallCharMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jshort CallStaticShortMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jshort + CallNonvirtualShortMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jshort CallShortMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jint CallStaticIntMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jint CallNonvirtualIntMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jint CallIntMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jlong CallStaticLongMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jlong + CallNonvirtualLongMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jlong CallLongMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jfloat CallStaticFloatMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jfloat + CallNonvirtualFloatMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jfloat CallFloatMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jdouble CallStaticDoubleMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jdouble + CallNonvirtualDoubleMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jdouble CallDoubleMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jobject CallStaticObjectMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jobject + CallNonvirtualObjectMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jobject CallObjectMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jobject GetStaticObjectField(jclass clazz, jfieldID fieldID); + + jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID); + + jbyte GetStaticByteField(jclass clazz, jfieldID fieldID); + + jchar GetStaticCharField(jclass clazz, jfieldID fieldID); + + jshort GetStaticShortField(jclass clazz, jfieldID fieldID); + + jint GetStaticIntField(jclass clazz, jfieldID fieldID); + + jlong GetStaticLongField(jclass clazz, jfieldID fieldID); + + jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID); + + jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID); + + void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value); + + void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value); + + void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value); + + void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value); + + void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value); + + void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value); + + void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value); + + void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value); + + void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value); + + jobject GetObjectField(jobject obj, jfieldID fieldID); + + jboolean GetBooleanField(jobject obj, jfieldID fieldID); + + jbyte GetByteField(jobject obj, jfieldID fieldID); + + jchar GetCharField(jobject obj, jfieldID fieldID); + + jshort GetShortField(jobject obj, jfieldID fieldID); + + jint GetIntField(jobject obj, jfieldID fieldID); + + jlong GetLongField(jobject obj, jfieldID fieldID); + + jfloat GetFloatField(jobject obj, jfieldID fieldID); + + jdouble GetDoubleField(jobject obj, jfieldID fieldID); + + void SetObjectField(jobject obj, jfieldID fieldID, jobject value); + + void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value); + + void SetByteField(jobject obj, jfieldID fieldID, jbyte value); + + void SetCharField(jobject obj, jfieldID fieldID, jchar value); + + void SetShortField(jobject obj, jfieldID fieldID, jshort value); + + void SetIntField(jobject obj, jfieldID fieldID, jint value); + + void SetLongField(jobject obj, jfieldID fieldID, jlong value); + + void SetFloatField(jobject obj, jfieldID fieldID, jfloat value); + + void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value); + + jstring NewString(const jchar *unicodeChars, jsize len); + + jstring NewStringUTF(const char *bytes); + + jobjectArray NewObjectArray(jsize length, jclass elementClass, jobject initialElement); + + jobject GetObjectArrayElement(jobjectArray array, jsize index); + + void SetObjectArrayElement(jobjectArray array, jsize index, jobject value); + + const char *GetStringUTFChars(jstring str, jboolean *isCopy); + + void ReleaseStringUTFChars(jstring str, const char *utf); + + const jchar *GetStringChars(jstring str, jboolean *isCopy); + + void ReleaseStringChars(jstring str, const jchar *chars); + + const int GetStringLength(jstring str); + + const int GetStringUTFLength(jstring str); + + void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf); + + jint Throw(jthrowable obj); + + jint ThrowNew(jclass clazz, const std::string &message); + + jboolean ExceptionCheck(); + + jthrowable ExceptionOccurred(); + + void ExceptionDescribe(); + + void ExceptionClear(); + + jboolean IsInstanceOf(jobject obj, jclass clazz); + + jobjectRefType GetObjectRefType(jobject obj); + + jobject NewGlobalRef(jobject obj); + + jweak NewWeakGlobalRef(jobject obj); + + void DeleteGlobalRef(jobject globalRef); + + void DeleteWeakGlobalRef(jweak obj); + + jobject NewLocalRef(jobject ref); + + void DeleteLocalRef(jobject localRef); + + jbyteArray NewByteArray(jsize length); + + jbooleanArray NewBooleanArray(jsize length); + + jcharArray NewCharArray(jsize length); + + jshortArray NewShortArray(jsize length); + + jintArray NewIntArray(jsize length); + + jlongArray NewLongArray(jsize length); + + jfloatArray NewFloatArray(jsize length); + + jdoubleArray NewDoubleArray(jsize length); + + jbyte *GetByteArrayElements(jbyteArray array, jboolean *isCopy); + + + void ReleaseByteArrayElements(jbyteArray array, jbyte *elems, jint mode); + + void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean *buf); + + void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte *buf); + + void GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar *buf); + + void GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort *buf); + + void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint *buf); + + jint *GetIntArrayElements(jintArray array, jboolean *isCopy); + + void GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong *buf); + + void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat *buf); + + void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble *buf); + + void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte *buf); + + void + SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean *buf); + + void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar *buf); + + void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort *buf); + + void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint *buf); + + void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong *buf); + + void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat *buf); + + void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble *buf); + + jclass FindClass(const std::string &className); + + /* + * The "CheckForClassInCache" will check if a class is loaded into the cache + * if it is: it returns a global reference of it + * if it is not: it will return "nullptr". + */ + jclass CheckForClassInCache(const std::string &className); + + /* + * "InsertClassIntoCache" will take care of deleting the LocalReference of passed "jclass& tmp". + * A new GlobalReference object will be created from "tmp". The function returns the global object. + */ + jclass InsertClassIntoCache(const std::string &className, jclass &tmp); + + + /* + * The "CheckForClassMissing" will check if a class has been checked and it was missing, if it is, it will return the original throwable + * this is useful for rethrowing exceptions if they were caught in the previous attempt of loading it. + * if it is not: it will return "nullptr". + */ + jthrowable CheckForClassMissingCache(const std::string &className); + + jthrowable InsertClassIntoMissingCache(const std::string &className, const jthrowable &tmp); + + jobject NewDirectByteBuffer(void *address, jlong capacity); + + void *GetDirectBufferAddress(jobject buf); + + jlong GetDirectBufferCapacity(jobject buf); + + jboolean IsAssignableFrom(jclass clazz1, jclass clazz2); + + template + void CallVoidMethod(jobject obj, jmethodID methodID, Args ... args) { + m_env->CallVoidMethod(obj, methodID, args...); + CheckForJavaException(); + } + + template + void CallStaticVoidMethod(jclass clazz, jmethodID methodID, Args ... args) { + m_env->CallStaticVoidMethod(clazz, methodID, args...); + CheckForJavaException(); + } + + template + void CallAppFail(jclass clazz, jmethodID methodID, Args ... args) { + m_env->CallStaticVoidMethod(clazz, methodID, args...); + } + + template + jint CallStaticIntMethod(jclass clazz, jmethodID methodID, Args ... args) { + jint ji = m_env->CallStaticIntMethod(clazz, methodID, args...); + CheckForJavaException(); + return ji; + } + + template + jlong CallStaticLongMethod(jclass clazz, jmethodID methodID, Args ... args) { + jlong jd = m_env->CallStaticLongMethod(clazz, methodID, args...); + CheckForJavaException(); + return jd; + } + + template + jobject CallStaticObjectMethod(jclass clazz, jmethodID methodID, Args ... args) { + jobject jo = m_env->CallStaticObjectMethod(clazz, methodID, args...); + CheckForJavaException(); + return jo; + } + + template + jboolean CallStaticBooleanMethod(jclass clazz, jmethodID methodID, Args ... args) { + jboolean jbl = m_env->CallStaticBooleanMethod(clazz, methodID, args...); + CheckForJavaException(); + return jbl; + } + + template + jobject CallObjectMethod(jobject obj, jmethodID methodID, Args ... args) { + jobject jo = m_env->CallObjectMethod(obj, methodID, args...); + CheckForJavaException(); + return jo; + } + + template + jboolean CallBooleanMethod(jobject obj, jmethodID methodID, Args ... args) { + jboolean jbl = m_env->CallBooleanMethod(obj, methodID, args...); + CheckForJavaException(); + return jbl; + } + + template + jchar CallCharMethod(jobject obj, jmethodID methodID, Args ... args) { + jchar jc = m_env->CallCharMethod(obj, methodID, args...); + CheckForJavaException(); + return jc; + } + + template + jbyte CallByteMethod(jobject obj, jmethodID methodID, Args ... args) { + jbyte jbt = m_env->CallByteMethod(obj, methodID, args...); + CheckForJavaException(); + return jbt; + } + + template + jshort CallShortMethod(jobject obj, jmethodID methodID, Args ... args) { + jshort jsh = m_env->CallShortMethod(obj, methodID, args...); + CheckForJavaException(); + return jsh; + } + + template + jint CallIntMethod(jobject obj, jmethodID methodID, Args ... args) { + jint ji = m_env->CallIntMethod(obj, methodID, args...); + CheckForJavaException(); + return ji; + } + + template + jlong CallLongMethod(jobject obj, jmethodID methodID, Args ... args) { + jlong jl = m_env->CallLongMethod(obj, methodID, args...); + CheckForJavaException(); + return jl; + } + + template + jfloat CallFloatMethod(jobject obj, jmethodID methodID, Args ... args) { + jfloat jf = m_env->CallFloatMethod(obj, methodID, args...); + CheckForJavaException(); + return jf; + } + + template + jdouble CallDoubleMethod(jobject obj, jmethodID methodID, Args ... args) { + jdouble jd = m_env->CallDoubleMethod(obj, methodID, args...); + CheckForJavaException(); + return jd; + } + + template + jobject NewObject(jclass clazz, jmethodID methodID, Args ... args) { + jobject jo = m_env->NewObject(clazz, methodID, args...); + CheckForJavaException(); + return jo; + + } + + jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue *args) { + jobject jo = m_env->NewObjectA(clazz, methodID, args); + CheckForJavaException(); + return jo; + } + + static void Init(JavaVM *jvm); + + private: + void CheckForJavaException(); + + JNIEnv *m_env; + + static JavaVM *s_jvm; + + static jclass RUNTIME_CLASS; + + static jmethodID GET_CACHED_CLASS_METHOD_ID; + + static robin_hood::unordered_map s_classCache; + static robin_hood::unordered_map s_missingClasses; + }; +} + +#endif /* JENV_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/JType.cpp b/NativeScript/ffi/jni/napi/jni/JType.cpp new file mode 100644 index 000000000..479bfb0e3 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JType.cpp @@ -0,0 +1,155 @@ +#include "JType.h" +#include "NativeScriptAssert.h" + +namespace tns { +Type JType::getClassType(int retType) { + Type classReturnType = static_cast(retType); + return classReturnType; +} + +jobject JType::NewByte(JEnv env, jbyte value) { + EnsureInstance(env, &Byte, Type::Byte); + return env.NewObject(Byte->clazz, Byte->ctor, value); +} + +jobject JType::NewChar(JEnv env, jchar value) { + EnsureInstance(env, &Char, Type::Char); + return env.NewObject(Char->clazz, Char->ctor, value); +} + +jobject JType::NewBoolean(JEnv env, jboolean value) { + EnsureInstance(env, &Boolean, Type::Boolean); + return env.NewObject(Boolean->clazz, Boolean->ctor, value); +} + +jobject JType::NewShort(JEnv env, jshort value) { + EnsureInstance(env, &Short, Type::Short); + return env.NewObject(Short->clazz, Short->ctor, value); +} + +jobject JType::NewInt(JEnv env, jint value) { + EnsureInstance(env, &Int, Type::Int); + return env.NewObject(Int->clazz, Int->ctor, value); +} + +jobject JType::NewLong(JEnv env, jlong value) { + EnsureInstance(env, &Long, Type::Long); + return env.NewObject(Long->clazz, Long->ctor, value); +} + +jobject JType::NewFloat(JEnv env, jfloat value) { + EnsureInstance(env, &Float, Type::Float); + return env.NewObject(Float->clazz, Float->ctor, value); +} + +jobject JType::NewDouble(JEnv env, jdouble value) { + EnsureInstance(env, &Double, Type::Double); + return env.NewObject(Double->clazz, Double->ctor, value); +} + +jbyte JType::ByteValue(JEnv env, jobject value) { + EnsureInstance(env, &Byte, Type::Byte); + return env.CallByteMethod(value, Byte->valueMethodId); +} + +jchar JType::CharValue(JEnv env, jobject value) { + EnsureInstance(env, &Char, Type::Char); + return env.CallCharMethod(value, Char->valueMethodId); +} + +jboolean JType::BooleanValue(JEnv env, jobject value) { + EnsureInstance(env, &Boolean, Type::Boolean); + return env.CallBooleanMethod(value, Boolean->valueMethodId); +} + +jshort JType::ShortValue(JEnv env, jobject value) { + EnsureInstance(env, &Short, Type::Short); + return env.CallShortMethod(value, Short->valueMethodId); +} + +jint JType::IntValue(JEnv env, jobject value) { + EnsureInstance(env, &Int, Type::Int); + return env.CallIntMethod(value, Int->valueMethodId); +} + +jlong JType::LongValue(JEnv env, jobject value) { + EnsureInstance(env, &Long, Type::Long); + return env.CallLongMethod(value, Long->valueMethodId); +} + +jfloat JType::FloatValue(JEnv env, jobject value) { + EnsureInstance(env, &Float, Type::Float); + return env.CallFloatMethod(value, Float->valueMethodId); +} + +jdouble JType::DoubleValue(JEnv env, jobject value) { + EnsureInstance(env, &Double, Type::Double); + return env.CallDoubleMethod(value, Double->valueMethodId); +} + +void JType::EnsureInstance(JEnv env, JType** instance, Type type) { + if ((*instance) != nullptr) { + return; + } + + *instance = new JType(); + + (*instance)->Init(env, type); +} + +void JType::Init(JEnv env, Type type) { + switch (type) { + case Type::Byte: + this->clazz = env.FindClass("java/lang/Byte"); + this->ctor = env.GetMethodID(this->clazz, "", "(B)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "byteValue", "()B"); + break; + case Type::Char: + this->clazz = env.FindClass("java/lang/Character"); + this->ctor = env.GetMethodID(this->clazz, "", "(C)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "charValue", "()C"); + break; + case Type::Boolean: + this->clazz = env.FindClass("java/lang/Boolean"); + this->ctor = env.GetMethodID(this->clazz, "", "(Z)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "booleanValue", "()Z"); + break; + case Type::Short: + this->clazz = env.FindClass("java/lang/Short"); + this->ctor = env.GetMethodID(this->clazz, "", "(S)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "shortValue", "()S"); + break; + case Type::Int: + this->clazz = env.FindClass("java/lang/Integer"); + this->ctor = env.GetMethodID(this->clazz, "", "(I)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "intValue", "()I"); + break; + case Type::Long: + this->clazz = env.FindClass("java/lang/Long"); + this->ctor = env.GetMethodID(this->clazz, "", "(J)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "longValue", "()J"); + break; + case Type::Float: + this->clazz = env.FindClass("java/lang/Float"); + this->ctor = env.GetMethodID(this->clazz, "", "(F)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "floatValue", "()F"); + break; + case Type::Double: + this->clazz = env.FindClass("java/lang/Double"); + this->ctor = env.GetMethodID(this->clazz, "", "(D)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "doubleValue", "()D"); + break; + default: + break; + } +} + +JType* JType::Byte; +JType* JType::Char; +JType* JType::Boolean; +JType* JType::Short; +JType* JType::Int; +JType* JType::Long; +JType* JType::Float; +JType* JType::Double; +} diff --git a/NativeScript/ffi/jni/napi/jni/JType.h b/NativeScript/ffi/jni/napi/jni/JType.h new file mode 100644 index 000000000..d3bfcf2c9 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JType.h @@ -0,0 +1,66 @@ +#ifndef JNIPRIMITIVETYPE_H_ +#define JNIPRIMITIVETYPE_H_ + +#include "JEnv.h" + +namespace tns { +enum class Type + : int { + Boolean, + Char, + Byte, + Short, + Int, + Long, + Float, + Double, + String, + JsObject, + Null +}; + +class JType { + public: + static jobject NewByte(JEnv env, jbyte value); + static jobject NewChar(JEnv env, jchar value); + static jobject NewBoolean(JEnv env, jboolean value); + static jobject NewShort(JEnv env, jshort value); + static jobject NewInt(JEnv env, jint value); + static jobject NewLong(JEnv env, jlong value); + static jobject NewFloat(JEnv env, jfloat value); + static jobject NewDouble(JEnv env, jdouble value); + + static jbyte ByteValue(JEnv env, jobject value); + static jchar CharValue(JEnv env, jobject value); + static jboolean BooleanValue(JEnv env, jobject value); + static jshort ShortValue(JEnv env, jobject value); + static jint IntValue(JEnv env, jobject value); + static jlong LongValue(JEnv env, jobject value); + static jfloat FloatValue(JEnv env, jobject value); + static jdouble DoubleValue(JEnv env, jobject value); + + static Type getClassType(int retType); + + private: + JType() { + } + + void Init(JEnv env, Type type); + static void EnsureInstance(JEnv env, JType** instance, Type type); + + jclass clazz; + jmethodID ctor; + jmethodID valueMethodId; + + static JType* Byte; + static JType* Char; + static JType* Boolean; + static JType* Short; + static JType* Int; + static JType* Long; + static JType* Float; + static JType* Double; +}; +} + +#endif /* JNIPRIMITIVETYPE_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/JniLocalRef.h b/NativeScript/ffi/jni/napi/jni/JniLocalRef.h new file mode 100644 index 000000000..069864339 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JniLocalRef.h @@ -0,0 +1,123 @@ +#ifndef JNILOCALREF_H_ +#define JNILOCALREF_H_ + +#include "JEnv.h" +#include "JType.h" + +namespace tns { +class JniLocalRef { + public: + JniLocalRef() + : m_obj(nullptr) { + } + + JniLocalRef(jobject obj) + : m_obj(obj) { + } + + JniLocalRef(jclass obj) + : m_obj(obj) { + } + + JniLocalRef(JniLocalRef&& rhs) + : m_obj(rhs.m_obj) { + rhs.m_obj = nullptr; + } + + bool IsNull() const { + return m_obj == nullptr; + } + + jobject Move() { + auto value = m_obj; + m_obj = nullptr; + return value; + } + + JniLocalRef& operator=(JniLocalRef&& rhs) { + if (this == &rhs) { + return *this; + } + if (m_obj != nullptr) { + JEnv env; + env.DeleteLocalRef(m_obj); + } + m_obj = rhs.m_obj; + rhs.m_obj = nullptr; + return *this; + } + + operator jobject() const { + return m_obj; + } + + operator jstring() const { + return reinterpret_cast(m_obj); + } + + operator jclass() const { + return reinterpret_cast(m_obj); + } + + operator jboolean() const { + JEnv env; + return JType::BooleanValue(env, m_obj); + } + + operator jthrowable() const { + return reinterpret_cast(m_obj); + } + + operator jarray()const { + return reinterpret_cast(m_obj); + } + + operator jbyteArray() const { + return reinterpret_cast(m_obj); + } + + operator jshortArray() const { + return reinterpret_cast(m_obj); + } + + operator jintArray() const { + return reinterpret_cast(m_obj); + } + + operator jlongArray() const { + return reinterpret_cast(m_obj); + } + + operator jfloatArray() const { + return reinterpret_cast(m_obj); + } + + operator jdoubleArray() const { + return reinterpret_cast(m_obj); + } + + operator jbooleanArray() const { + return reinterpret_cast(m_obj); + } + + operator jcharArray() const { + return reinterpret_cast(m_obj); + } + + operator jobjectArray() const { + return reinterpret_cast(m_obj); + } + + ~JniLocalRef() { + if (m_obj != nullptr) { + JEnv env; + env.DeleteLocalRef(m_obj); + } + } + + private: + jobject m_obj; +}; +} + +#endif /* JNILOCALREF_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/JniSignatureParser.cpp b/NativeScript/ffi/jni/napi/jni/JniSignatureParser.cpp new file mode 100644 index 000000000..3d6df3559 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JniSignatureParser.cpp @@ -0,0 +1,102 @@ +#include "JniSignatureParser.h" + +#include + +using namespace std; +using namespace tns; + +JniSignatureParser::JniSignatureParser(const string& signature) + : m_signature(signature) { +} + +vector JniSignatureParser::Parse() { + size_t startIdx = m_signature.find_first_of('('); + + assert(startIdx != string::npos); + + size_t endIdx = m_signature.find_first_of(')'); + + assert(endIdx != string::npos); + + vector tokens = ParseParams(startIdx + 1, endIdx); + + return tokens; +} + +vector JniSignatureParser::ParseParams(int stardIdx, int endIdx) { + vector tokens; + + m_pos = stardIdx; + + while (m_pos < endIdx) { + string token = ReadNextToken(endIdx); + tokens.push_back(token); + } + + return tokens; +} + +string JniSignatureParser::ReadNextToken(int endIdx) { + string token; + + char currChar = m_signature[m_pos]; + + int idx; + bool endFound; + bool testNextChar = true; + + switch (currChar) { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + ++m_pos; + token.push_back(currChar); + break; + + case 'L': + idx = m_signature.find(';', m_pos); + assert(idx != string::npos); + token = m_signature.substr(m_pos, idx - m_pos + 1); + m_pos = idx + 1; + break; + + case '[': + idx = m_pos; + endFound = false; + while (!endFound && (idx < endIdx)) { + currChar = m_signature[idx++]; + if (testNextChar) { + switch (currChar) { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + endFound = true; + break; + } + testNextChar = currChar == '['; + } else { + endFound = currChar == ';'; + } + } + assert(endFound); + token = m_signature.substr(m_pos, idx - m_pos); + m_pos = idx; + break; + + default: + assert(false); + break; + } + + return token; +} diff --git a/NativeScript/ffi/jni/napi/jni/JniSignatureParser.h b/NativeScript/ffi/jni/napi/jni/JniSignatureParser.h new file mode 100644 index 000000000..9904884ca --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JniSignatureParser.h @@ -0,0 +1,26 @@ +#ifndef JNISIGNATUREPARSER_H_ +#define JNISIGNATUREPARSER_H_ + +#include +#include + +namespace tns { +class JniSignatureParser { + public: + JniSignatureParser(const std::string& signature); + + std::vector Parse(); + + private: + + std::vector ParseParams(int stardIdx, int endIdx); + + std::string ReadNextToken(int endIdx); + + int m_pos; + + std::string m_signature; +}; +} + +#endif /* JNISIGNATUREPARSER_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/LRUCache.h b/NativeScript/ffi/jni/napi/jni/LRUCache.h new file mode 100644 index 000000000..c3318ff6d --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/LRUCache.h @@ -0,0 +1,158 @@ +#ifndef LRUCACHE_H_ +#define LRUCACHE_H_ + +/* + Copyright (c) 2010-2011, Tim Day + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +namespace tns { +// Class providing fixed-size (by number of records) +// LRU-replacement cache of a function with signature +// V f(K). +// MAP should be one of std::map or std::unordered_map. +// Variadic template args used to deal with the +// different type argument signatures of those +// containers; the default comparator/hash/allocator +// will be used. +template +class LRUCache { + public: + + typedef K key_type; + typedef V value_type; + + // Key access history, most recent at back + typedef std::list key_tracker_type; + + // Key to value and key history iterator + typedef std::unordered_map< key_type, std::pair > key_to_value_type; + + // Constuctor specifies the cached function and + // the maximum number of records to be stored + LRUCache(value_type (*loadCallback)(const key_type&, void*), void (*evictCallback)(const value_type&, void*), size_t capacity, void* state) + : m_loadCallback(loadCallback), m_capacity(capacity), m_evictCallback(evictCallback), m_state(state) { + assert(m_loadCallback != nullptr); + assert((0 < m_capacity) && (m_capacity < 10000)); + } + + // Obtain value of the cached function for k + value_type operator()(const key_type& k) { + + // Attempt to find existing record + auto it = m_key_to_value.find(k); + + if (it == m_key_to_value.end()) { + + // We don't have it: + + // Evaluate function and create new record + const value_type v = m_loadCallback(k, m_state); + insert(k,v); + + // Return the freshly computed value + return v; + + } else { + // We do have it: + + // Update access record by moving + // accessed key to back of list + m_key_tracker.splice(m_key_tracker.end(), m_key_tracker, (*it).second.second); + + // Return the retrieved value + return (*it).second.first; + } + } + + // Obtain the cached keys, most recently used element + // at head, least recently used at tail. + // This method is provided purely to support testing. + template void get_keys(IT dst) const { + auto src = m_key_tracker.rbegin(); + + while (src != m_key_tracker.rend()) { + *dst++ = *src++; + } + } + + void update(const key_type& key, const value_type& value) { + jweak ref = m_loadCallback(key, m_state); + insert(key, ref); + } + + private: + + // Record a fresh key-value pair in the cache + void insert(const key_type& k, const value_type& v) { + // Method is only called on cache misses + assert(m_key_to_value.find(k) == m_key_to_value.end()); + + // Make space if necessary + if (m_key_to_value.size() == m_capacity) { + evict(); + } + + // Record k as most-recently-used key + auto it = m_key_tracker.insert(m_key_tracker.end(), k); + + // Create the key-value entry, + // linked to the usage record. + m_key_to_value.insert(std::make_pair(k, std::make_pair(v, it))); + // No need to check return, + // given previous assert. + } + + // Purge the least-recently-used element in the cache + void evict() { + // Assert method is never called when cache is empty + assert(!m_key_tracker.empty()); + + // Identify least recently used key + auto it = m_key_to_value.find(m_key_tracker.front()); + assert(it != m_key_to_value.end()); + + if (m_evictCallback != nullptr) { + m_evictCallback((*it).second.first, m_state); + } + + // Erase both elements to completely purge record + m_key_to_value.erase(it); + m_key_tracker.pop_front(); + } + + // The function to be cached + value_type (*m_loadCallback)(const key_type&, void*); + + void (*m_evictCallback)(const value_type&, void*); + + // Maximum number of key-value pairs to be retained + const size_t m_capacity; + + // Key access history + key_tracker_type m_key_tracker; + + // user-defined state to pass to callback + void* m_state; + + // Key-to-value lookup + key_to_value_type m_key_to_value; +}; +} + +#endif /* LRUCACHE_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/Logger.cpp b/NativeScript/ffi/jni/napi/jni/Logger.cpp new file mode 100644 index 000000000..4e35d4cf9 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/Logger.cpp @@ -0,0 +1,9 @@ +#include "Logger.h" + +using namespace tns; + +Logger::Logger() { +} + +void Logger::Write() { +} diff --git a/NativeScript/ffi/jni/napi/jni/Logger.h b/NativeScript/ffi/jni/napi/jni/Logger.h new file mode 100644 index 000000000..6b77c4f24 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/Logger.h @@ -0,0 +1,14 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +namespace tns { +class Logger { + public: + Logger(); + + void Write(); + private: +}; +} + +#endif /* LOGGER_H_ */ diff --git a/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.cpp b/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.cpp new file mode 100644 index 000000000..4b2f356d6 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.cpp @@ -0,0 +1,69 @@ +#include "NativeScriptException.h" +#include "JSONObjectHelper.h" +#include "ArgConverter.h" +#include +#include +#include + +using namespace tns; + +void JSONObjectHelper::RegisterFromFunction(napi_env env, napi_value value) { + napi_valuetype type; + napi_typeof(env, value, &type); + if (type != napi_function && type != napi_object) { + return; + } + + bool hasProperty; + napi_has_named_property(env, value, "from", &hasProperty); + if (hasProperty) { + return; + } + + napi_value from = CreateFromFunction(env); + napi_set_named_property(env, value, "from", from); +} + + +napi_value JSONObjectHelper::CreateFromFunction(napi_env env) { + static const char* source = R"((() => function from(data) { + if (!data) throw new Error("Expected one parameter"); + let store; + switch (typeof data) { + case "string": + case "boolean": + case "number": { + return data; + } + case "object": { + if (!data) { + return null; + } + + if (data instanceof Date) { + return data.toJSON(); + } + + if (Array.isArray(data)) { + store = new org.json.JSONArray(); + data.forEach((item) => store.put(from(item))); + return store; + } + + store = new org.json.JSONObject(); + Object.keys(data).forEach((key) => store.put(key, from(data[key]))); + return store; + } + default: + return null; + } + })();)"; + + napi_value script; + napi_create_string_utf8(env, source, NAPI_AUTO_LENGTH, &script); + + napi_value result; + js_execute_script(env, script, "", &result); + + return result; +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.h b/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.h new file mode 100644 index 000000000..cbe730398 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.h @@ -0,0 +1,17 @@ +#ifndef JSONOBJECTHELPER_H_ +#define JSONOBJECTHELPER_H_ + +#include "js_native_api.h" + +namespace tns { + + class JSONObjectHelper { + public: + static void RegisterFromFunction(napi_env env, napi_value value); + private: + static napi_value CreateFromFunction(napi_env env); + }; + +} + +#endif //JSONOBJECTHELPER_H_ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/metadata/FieldAccessor.cpp b/NativeScript/ffi/jni/napi/metadata/FieldAccessor.cpp new file mode 100644 index 000000000..7572a931b --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/FieldAccessor.cpp @@ -0,0 +1,366 @@ +#include "FieldAccessor.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +napi_value +FieldAccessor::GetJavaField(napi_env env, napi_value target, FieldCallbackData *fieldData) { + JEnv jEnv; + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + napi_value fieldResult; + + JniLocalRef targetJavaObject; + + auto &fieldMetadata = fieldData->metadata; + + const auto &fieldTypeName = fieldMetadata.getSig(); + auto isStatic = fieldMetadata.isStatic; + + auto isPrimitiveType = fieldTypeName.size() == 1; + if (fieldData->fid == nullptr) { + auto isFieldArray = fieldTypeName[0] == '['; + auto fieldJniSig = isPrimitiveType + ? fieldTypeName + : (isFieldArray + ? fieldTypeName + : ("L" + fieldTypeName + ";")); + + if (isStatic) { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + fieldData->fid = jEnv.GetStaticFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + } else { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + fieldData->fid = jEnv.GetFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + } + } + + if (!isStatic) { + // Using fast, target is always the original *this* + targetJavaObject = objectManager->GetJavaObjectByJsObjectFast(target); + + if (targetJavaObject.IsNull()) { + stringstream ss; + ss << "Cannot access property '" << fieldMetadata.getName().c_str() + << "' because there is no corresponding Java object"; + throw NativeScriptException(ss.str()); + } + + } + + + auto fieldId = fieldData->fid; + auto clazz = fieldData->clazz; + + if (isPrimitiveType) { + switch (fieldTypeName[0]) { + case 'Z': { // bool + jboolean result; + if (isStatic) { + result = jEnv.GetStaticBooleanField(clazz, fieldId); + } else { + result = jEnv.GetBooleanField(targetJavaObject, fieldId); + } + fieldResult = + result == JNI_TRUE ? napi_util::get_true(env) : napi_util::get_false(env); + break; + } + case 'B': { // byte + jbyte result; + if (isStatic) { + result = jEnv.GetStaticByteField(clazz, fieldId); + } else { + result = jEnv.GetByteField(targetJavaObject, fieldId); + } + napi_create_int32(env, result, &fieldResult); + break; + } + case 'C': { // char + jchar result; + if (isStatic) { + result = jEnv.GetStaticCharField(clazz, fieldId); + } else { + result = jEnv.GetCharField(targetJavaObject, fieldId); + } + + JniLocalRef str(jEnv.NewString(&result, 1)); + jboolean bol = true; + const char *resP = jEnv.GetStringUTFChars(str, &bol); + fieldResult = ArgConverter::convertToJsString(env, resP, 1); + jEnv.ReleaseStringUTFChars(str, resP); + break; + } + case 'S': { // short + jshort result; + if (isStatic) { + result = jEnv.GetStaticShortField(clazz, fieldId); + } else { + result = jEnv.GetShortField(targetJavaObject, fieldId); + } + napi_create_int32(env, result, &fieldResult); + break; + } + case 'I': { // int + jint result; + if (isStatic) { + result = jEnv.GetStaticIntField(clazz, fieldId); + } else { + result = jEnv.GetIntField(targetJavaObject, fieldId); + } + + napi_create_int32(env, result, &fieldResult); + break; + } + case 'J': { // long + jlong result; + if (isStatic) { + result = jEnv.GetStaticLongField(clazz, fieldId); + } else { + result = jEnv.GetLongField(targetJavaObject, fieldId); + } + + fieldResult = ArgConverter::ConvertFromJavaLong(env, result); + break; + } + case 'F': { // float + jfloat result; + if (isStatic) { + result = jEnv.GetStaticFloatField(clazz, fieldId); + } else { + result = jEnv.GetFloatField(targetJavaObject, fieldId); + } + napi_create_double(env, (double) result, &fieldResult); + break; + } + case 'D': { // double + jdouble result; + if (isStatic) { + result = jEnv.GetStaticDoubleField(clazz, fieldId); + } else { + result = jEnv.GetDoubleField(targetJavaObject, fieldId); + } + napi_create_double(env, (double) result, &fieldResult); + break; + } + default: { + stringstream ss; + ss << "(InternalError): in FieldAccessor::GetJavaField: Unknown field type: '" + << fieldTypeName[0] << "'"; + throw NativeScriptException(ss.str()); + } + } + } else { + jobject result; + + if (isStatic) { + result = jEnv.GetStaticObjectField(clazz, fieldId); + } else { + result = jEnv.GetObjectField(targetJavaObject, fieldId); + } + + if (result != nullptr) { + + bool isString = fieldTypeName == "java/lang/String"; + if (isString) { + fieldResult = ArgConverter::jstringToJsString(env, (jstring) result); + } else { + int javaObjectID = objectManager->GetOrCreateObjectId(result); + auto objectResult = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, objectResult)) { + objectResult = objectManager->CreateJSWrapper(javaObjectID, fieldTypeName, + result); + } + + fieldResult = objectResult; + } + jEnv.DeleteLocalRef(result); + } else { + napi_get_null(env, &fieldResult); + } + } + return fieldResult; +} + +void FieldAccessor::SetJavaField(napi_env env, napi_value target, napi_value value, + FieldCallbackData *fieldData) { + JEnv jEnv; + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + JniLocalRef targetJavaObject; + + auto &fieldMetadata = fieldData->metadata; + + const auto &fieldTypeName = fieldMetadata.getSig(); + auto isStatic = fieldMetadata.isStatic; + + auto isPrimitiveType = fieldTypeName.size() == 1; + auto isFieldArray = fieldTypeName[0] == '['; + + if (fieldData->fid == nullptr) { + auto fieldJniSig = isPrimitiveType + ? fieldTypeName + : (isFieldArray + ? fieldTypeName + : ("L" + fieldTypeName + ";")); + + if (isStatic) { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + assert(fieldData->clazz != nullptr); + fieldData->fid = jEnv.GetStaticFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + assert(fieldData->fid != nullptr); + } else { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + assert(fieldData->clazz != nullptr); + fieldData->fid = jEnv.GetFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + assert(fieldData->fid != nullptr); + } + } + + if (!isStatic) { + // Using fast, target is always the original *this* + targetJavaObject = objectManager->GetJavaObjectByJsObjectFast(target); + + if (targetJavaObject.IsNull()) { + stringstream ss; + ss << "Cannot access property '" << fieldMetadata.getName().c_str() + << "' because there is no corresponding Java object"; + throw NativeScriptException(ss.str()); + } + } + + auto fieldId = fieldData->fid; + auto clazz = fieldData->clazz; + + if (isPrimitiveType) { + switch (fieldTypeName[0]) { + case 'Z': { // bool + // TODO: validate value is a boolean before calling + bool boolValue = napi_util::is_of_type(env, value, napi_boolean) + ? napi_util::get_bool(env, value) : false; + if (isStatic) { + jEnv.SetStaticBooleanField(clazz, fieldId, boolValue); + } else { + jEnv.SetBooleanField(targetJavaObject, fieldId, + boolValue); + } + break; + } + case 'B': { // byte + // TODO: validate value is a byte before calling + jbyte intValue = !napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_int32(env, value) : 0; + if (isStatic) { + jEnv.SetStaticByteField(clazz, fieldId, intValue); + } else { + jEnv.SetByteField(targetJavaObject, fieldId, intValue); + } + break; + } + case 'C': { // char + const char *stringValue = napi_util::get_string_value(env, value, 1); + JniLocalRef strValue(jEnv.NewStringUTF(stringValue)); + const char *chars = jEnv.GetStringUTFChars(strValue, 0); + + if (isStatic) { + jEnv.SetStaticCharField(clazz, fieldId, chars[0]); + } else { + jEnv.SetCharField(targetJavaObject, fieldId, chars[0]); + } + jEnv.ReleaseStringUTFChars(strValue, chars); + break; + } + case 'S': { // short + // TODO: validate value is a short before calling + short shortValue = !napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_int32(env, value) : 0; + if (isStatic) { + jEnv.SetStaticShortField(clazz, fieldId, shortValue); + } else { + jEnv.SetShortField(targetJavaObject, fieldId, shortValue); + } + break; + } + case 'I': { // int + // TODO: validate value is a int before calling + int intValue = napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_int32(env, value) : 0; + if (isStatic) { + jEnv.SetStaticIntField(clazz, fieldId, intValue); + } else { + jEnv.SetIntField(targetJavaObject, fieldId, intValue); + } + break; + } + case 'J': { // long + jlong longValue = static_cast(ArgConverter::ConvertToJavaLong(env, value)); + if (isStatic) { + jEnv.SetStaticLongField(clazz, fieldId, longValue); + } else { + jEnv.SetLongField(targetJavaObject, fieldId, longValue); + } + break; + } + case 'F': { // float + float floatValue = napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_number(env, + value) : 0.0; + if (isStatic) { + jEnv.SetStaticFloatField(clazz, fieldId, + static_cast(floatValue)); + } else { + jEnv.SetFloatField(targetJavaObject, fieldId, + static_cast(floatValue)); + } + break; + } + case 'D': { // double + double doubleValue = napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_number(env, + value) : 0.0; + if (isStatic) { + jEnv.SetStaticDoubleField(clazz, fieldId, doubleValue); + } else { + jEnv.SetDoubleField(targetJavaObject, fieldId, doubleValue); + } + break; + } + default: { + stringstream ss; + ss << "(InternalError): in FieldAccessor::SetJavaField: Unknown field type: '" + << fieldTypeName[0] << "'"; + throw NativeScriptException(ss.str()); + } + } + } else { + bool isString = fieldTypeName == "java/lang/String"; + JniLocalRef result; + + if (!napi_util::is_null(env, value) && !napi_util::is_undefined(env, value)) { + if (isString) { + // TODO: validate valie is a string; + result = ArgConverter::ConvertToJavaString(env, value); + } else { + result = objectManager->GetJavaObjectByJsObject(value); + } + } + + if (isStatic) { + jEnv.SetStaticObjectField(clazz, fieldId, result); + } else { + jEnv.SetObjectField(targetJavaObject, fieldId, result); + } + } +} diff --git a/NativeScript/ffi/jni/napi/metadata/FieldAccessor.h b/NativeScript/ffi/jni/napi/metadata/FieldAccessor.h new file mode 100644 index 000000000..cdc98675c --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/FieldAccessor.h @@ -0,0 +1,18 @@ +#ifndef FIELDACCESSOR_H_ +#define FIELDACCESSOR_H_ + +#include "JEnv.h" +#include +#include "ObjectManager.h" +#include "FieldCallbackData.h" + +namespace tns { +class FieldAccessor { + public: + napi_value GetJavaField(napi_env env, napi_value target, FieldCallbackData* fieldData); + + void SetJavaField(napi_env env, napi_value target, napi_value value, FieldCallbackData* fieldData); +}; +} + +#endif /* FIELDACCESSOR_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/FieldCallbackData.h b/NativeScript/ffi/jni/napi/metadata/FieldCallbackData.h new file mode 100644 index 000000000..95476c78e --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/FieldCallbackData.h @@ -0,0 +1,22 @@ +#ifndef FIELDCALLBACKDATA_H_ +#define FIELDCALLBACKDATA_H_ + +#include "jni.h" +#include "MetadataEntry.h" + +namespace tns { + struct FieldCallbackData { + FieldCallbackData(MetadataEntry metadata) + : + metadata(metadata), fid(nullptr), clazz(nullptr) { + + } + + MetadataEntry metadata; + jfieldID fid; + jclass clazz; + }; + +} + +#endif /* FIELDCALLBACKDATA_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.cpp new file mode 100644 index 000000000..a2fee850d --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.cpp @@ -0,0 +1,136 @@ +// +// Created by Ammar Ahmed on 28/09/2024. +// + +#include "MetadataBuilder.h" +#include +#include +#include +#include +#include +#include +#include +#include "NativeScriptException.h" +#include "NativeScriptAssert.h" +#include "File.h" +#include "CallbackHandlers.h" + + +using namespace tns; + +MetadataReader MetadataBuilder::BuildMetadata(const std::string &filesPath) { + timeval time1; + gettimeofday(&time1, nullptr); + + string baseDir = filesPath; + baseDir.append("/metadata"); + + DIR* dir = opendir(baseDir.c_str()); + + if(dir == nullptr){ + stringstream ss; + ss << "metadata folder couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + + // TODO: Is there a way to detect if the screen is locked as verification + // We assume based on the error that this is the only way to get this specific error here at this point + if (errno == ENOENT || errno == EACCES) { + // Log the error with error code + __android_log_print(ANDROID_LOG_ERROR, "TNS.error", "%s", ss.str().c_str()); + + // While the screen is locked after boot; we cannot access our own apps directory on Android 9+ + // So the only thing to do at this point is just exit normally w/o crashing! + + // The only reason we should be in this specific path; is if: + // 1) android:directBootAware="true" flag is set on receiver + // 2) android.intent.action.LOCKED_BOOT_COMPLETED intent is set in manifest on above receiver + // See: https://developer.android.com/guide/topics/manifest/receiver-element + // and: https://developer.android.com/training/articles/direct-boot + // This specific path occurs if you using the NativeScript-Local-Notification plugin, the + // receiver code runs fine, but the app actually doesn't need to startup. The Native code tries to + // startup because the receiver is triggered. So even though we are exiting, the receiver will have + // done its job + + exit(0); + } + else { + throw NativeScriptException(ss.str()); + } + } + + string nodesFile = baseDir + "/treeNodeStream.dat"; + string namesFile = baseDir + "/treeStringsStream.dat"; + string valuesFile = baseDir + "/treeValueStream.dat"; + + FILE* f = fopen(nodesFile.c_str(), "rb"); + if (f == nullptr) { + stringstream ss; + ss << "metadata file (treeNodeStream.dat) couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + + throw NativeScriptException(ss.str()); + } + fseek(f, 0, SEEK_END); + int lenNodes = ftell(f); + assert((lenNodes % sizeof(MetadataTreeNodeRawData)) == 0); + char* nodes = new char[lenNodes]; + rewind(f); + fread(nodes, 1, lenNodes, f); + fclose(f); + + const int _512KB = 524288; + + f = fopen(namesFile.c_str(), "rb"); + if (f == nullptr) { + stringstream ss; + ss << "metadata file (treeStringsStream.dat) couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + throw NativeScriptException(ss.str()); + } + fseek(f, 0, SEEK_END); + int lenNames = ftell(f); + char* names = new char[lenNames + _512KB]; + rewind(f); + fread(names, 1, lenNames, f); + fclose(f); + + f = fopen(valuesFile.c_str(), "rb"); + if (f == nullptr) { + stringstream ss; + ss << "metadata file (treeValueStream.dat) couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + throw NativeScriptException(ss.str()); + } + fseek(f, 0, SEEK_END); + int lenValues = ftell(f); + char* values = new char[lenValues + _512KB]; + rewind(f); + fread(values, 1, lenValues, f); + fclose(f); + + timeval time2; + gettimeofday(&time2, nullptr); + + DEBUG_WRITE("lenNodes=%d, lenNames=%d, lenValues=%d", lenNodes, lenNames, lenValues); + + long millis1 = (time1.tv_sec * 1000) + (time1.tv_usec / 1000); + long millis2 = (time2.tv_sec * 1000) + (time2.tv_usec / 1000); + + DEBUG_WRITE("time=%ld", (millis2 - millis1)); + + auto reader = BuildMetadata(lenNodes, reinterpret_cast(nodes), lenNames, reinterpret_cast(names), lenValues, reinterpret_cast(values)); + delete[] nodes; + return reader; +} + +MetadataReader MetadataBuilder::BuildMetadata(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData) { + return MetadataReader(nodesLength, nodeData, nameLength, nameData, valueLength, + valueData, CallbackHandlers::GetTypeMetadata); + + +} diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.h b/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.h new file mode 100644 index 000000000..d6e85baa0 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.h @@ -0,0 +1,25 @@ +// +// Created by Ammar Ahmed on 28/09/2024. +// + +#ifndef TESTAPPNAPI_METADATABUILDER_H +#define TESTAPPNAPI_METADATABUILDER_H + +#include +#include "MetadataReader.h" + +namespace tns { + + class MetadataBuilder { + public: + static MetadataReader BuildMetadata(const std::string &filesPath); + + private: + static MetadataReader + BuildMetadata(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData); + }; + +} // tns + +#endif //TESTAPPNAPI_METADATABUILDER_H diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp new file mode 100644 index 000000000..7836dfbfd --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp @@ -0,0 +1,135 @@ +#include "MetadataNode.h" +#include "MetadataEntry.h" +#include "MetadataMethodInfo.h" +#include "MetadataReader.h" + +using namespace tns; + +MetadataEntry::MetadataEntry(MetadataTreeNode *m_treeNode, NodeType nodeType) : + treeNode(m_treeNode), type(nodeType), isExtensionFunction(false), isStatic(false), + isTypeMember(false), memberId(nullptr), clazz(nullptr), mi(nullptr),fi(nullptr), sfi(nullptr), + retType(MethodReturnType::Unknown), + paramCount(-1), isFinal(false), isResolved(false), retTypeParsed(false), + isFinalSet(false), isResolvedSet(false) {} + +std::string &MetadataEntry::getName() { + if (!name.empty()) return name; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Field) { + name = reader->ReadName(fi->nameOffset); + } else if (type == NodeType::StaticField) { + name = reader->ReadName(sfi->nameOffset); + } else if (type == NodeType::Method) { + name = mi.GetName(); + } + + return name; +} + +std::string &MetadataEntry::getSig() { + if (!sig.empty()) return sig; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Field) { + sig = reader->ReadTypeName(fi->nodeId); + } else if (type == NodeType::StaticField) { + sig = reader->ReadTypeName(sfi->nodeId); + } else if (type == NodeType::Method) { + uint8_t sigLength = mi.GetSignatureLength(); + if (sigLength > 0) + sig = mi.GetSignature(); + + } + + return sig; +} + +std::string &MetadataEntry::getReturnType() { + if (!returnType.empty()) return returnType; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Method) { + if (mi.GetSignatureLength() > 0) { + returnType = MetadataReader::ParseReturnType(this->getSig()); + } + } else { + return returnType; + } + + return returnType; +} + +MethodReturnType MetadataEntry::getRetType() { + if (retTypeParsed) return retType; + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Method && !this->getReturnType().empty()) { + retType = MetadataReader::GetReturnType(this->returnType); + } + + retTypeParsed = true; + + return retType; +} + +std::string &MetadataEntry::getDeclaringType() { + if (!declaringType.empty()) return declaringType; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::StaticField) { + declaringType = reader->ReadTypeName(sfi->declaringType); + } else if (type == NodeType::Method && isStatic) { + declaringType = mi.GetDeclaringType(); + } + + return declaringType; +} + +int MetadataEntry::getParamCount() { + if (paramCount != -1) return paramCount; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Method) { + auto sigLength = mi.GetSignatureLength(); + if (sigLength > 0) { + paramCount = sigLength - 1; + } else { + paramCount = 0; + } + } + + return paramCount; +} + +bool MetadataEntry::getIsFinal() { + if (isFinalSet) return isFinal; + + if (type == NodeType::Field) { + isFinal = fi->finalModifier == MetadataTreeNode::FINAL; + } else if (type == NodeType::StaticField) { + isFinal = sfi->finalModifier == MetadataTreeNode::FINAL; + } + + isFinalSet = true; + + return isFinal; +} + +bool MetadataEntry::getIsResolved() { + if (isResolvedSet) return isResolved; + + auto reader = MetadataNode::getMetadataReader(); + if (type == NodeType::Method) { + isResolved = mi.CheckIsResolved() == 1; + } + + isResolvedSet = true; + + return isResolved; +} diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h new file mode 100644 index 000000000..6e708526c --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h @@ -0,0 +1,116 @@ +#ifndef METADATAENTRY_H_ +#define METADATAENTRY_H_ + +#include +#include "jni.h" +#include "MetadataTreeNode.h" +#include "MetadataMethodInfo.h" +#include "MetadataFieldInfo.h" + +namespace tns { + enum class NodeType { + Package, + Class, + Interface, + Method, + Field, + StaticField + }; + + enum class MethodReturnType { + Unknown, + Void, + Byte, + Short, + Int, + Long, + Float, + Double, + Char, + Boolean, + String, + Object + }; + + class MetadataEntry { + public: + + MetadataEntry(MetadataTreeNode *m_treeNode, NodeType nodeType); + + MetadataEntry(const MetadataEntry &other) = default; + + MetadataEntry &operator=(const MetadataEntry &other) { + if (this != &other) { + treeNode = other.treeNode; + type = other.type; + isExtensionFunction = other.isExtensionFunction; + isStatic = other.isStatic; + isTypeMember = other.isTypeMember; + memberId = other.memberId; + clazz = other.clazz; + parsedSig = other.parsedSig; + mi = other.mi; + fi = other.fi; + sfi = other.sfi; + name = other.name; + sig = other.sig; + returnType = other.returnType; + retType = other.retType; + declaringType = other.declaringType; + paramCount = other.paramCount; + isFinal = other.isFinal; + isResolved = other.isResolved; + isResolvedSet = other.isResolvedSet; + isFinalSet = other.isFinalSet; + } + return *this; + } + + std::string &getName(); + + std::string &getSig(); + + std::string &getReturnType(); + + MethodReturnType getRetType(); + + std::string &getDeclaringType(); + + int getParamCount(); + + bool getIsFinal(); + + bool getIsResolved(); + + MetadataTreeNode *treeNode; + NodeType type; + bool isExtensionFunction; + bool isStatic; + bool isTypeMember; + void *memberId; + jclass clazz; + std::vector parsedSig; + + MethodInfo mi; + FieldInfo *fi; + StaticFieldInfo *sfi; + + std::string name; + std::string sig; + std::string returnType; + MethodReturnType retType; + std::string declaringType; + int paramCount; + bool isFinal; + bool isResolved; + + private: + + bool retTypeParsed; + bool isFinalSet; + bool isResolvedSet; + + }; +} + +#endif /* METADATAENTRY_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataFieldInfo.h b/NativeScript/ffi/jni/napi/metadata/MetadataFieldInfo.h new file mode 100644 index 000000000..6c428960d --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataFieldInfo.h @@ -0,0 +1,28 @@ +#ifndef METADATAFIELDINFO_H_ +#define METADATAFIELDINFO_H_ + +#include + +namespace tns { +struct __attribute__ ((__packed__)) FieldInfo { + FieldInfo() + : +nameOffset(0), nodeId(0), finalModifier(0) { +} + +uint32_t nameOffset; +uint16_t nodeId; +uint8_t finalModifier; +}; + +struct __attribute__ ((__packed__)) StaticFieldInfo: FieldInfo { + StaticFieldInfo() + : +FieldInfo(), declaringType(0) { +} + +uint16_t declaringType; +}; +} + +#endif /* METADATAFIELDINFO_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.cpp new file mode 100644 index 000000000..42289ab89 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.cpp @@ -0,0 +1,99 @@ +#include "MetadataMethodInfo.h" +#include "MetadataNode.h" + + +using namespace tns; + +std::string MethodInfo::GetName() { + string methodName = MetadataNode::getMetadataReader()->ReadName(nameOffset); + return methodName; +} + +uint8_t MethodInfo::CheckIsResolved() { + return resolvedData; +} + +uint16_t MethodInfo::GetSignatureLength() { + return m_signatureLength; +} + +std::string MethodInfo::GetSignature() { //use nodeId's to read the whole signature + auto m_reader = MetadataNode::getMetadataReader(); + string signature = "("; + string ret; + for (int i = 0; i < m_signatureLength; i++) { + uint16_t nodeId = nodeIds[i]; + string curArgTypeName = m_reader->ReadTypeName(nodeId); + MetadataTreeNode* node = m_reader->GetNodeById(nodeId); + + uint8_t nodeType = m_reader->GetNodeType(node); + bool isRefType = m_reader->IsNodeTypeClass(nodeType) || m_reader->IsNodeTypeInterface(nodeType); + if (i == 0) { + if ((curArgTypeName[0] != '[') && isRefType) { + ret.append("L"); + } + ret.append(curArgTypeName); + if ((curArgTypeName[0] != '[') && isRefType) { + ret.append(";"); + } + } else { + if ((curArgTypeName[0] != '[') && isRefType) { + signature.append("L"); + } + signature.append(curArgTypeName); + if ((curArgTypeName[0] != '[') && isRefType) { + signature.append(";"); + } + } + } + if (ret.empty()) { + ret = "V"; + } + signature += ")" + ret; + + return signature; +} + +std::string MethodInfo::GetDeclaringType() { + auto m_reader = MetadataNode::getMetadataReader(); + + return m_reader->ReadTypeName(declaringNodeId); +} + +int MethodInfo::GetSizeOfReadMethodInfo() { + + if (!sizeMeasured) { + sizeMeasured = true; + // name + nameOffset = *reinterpret_cast(m_pData); + m_pData += sizeof(uint32_t); + // resolved data + resolvedData = *reinterpret_cast(m_pData); + m_pData += sizeof(uint8_t); + // sig length + m_signatureLength = *reinterpret_cast(m_pData); + m_pData += sizeof(uint16_t); + + // signature + if (m_signatureLength > 0) { + uint16_t* nodeIdPtr = reinterpret_cast(m_pData); + nodeIds.resize(m_signatureLength); + for (int i = 0; i < m_signatureLength; i++) { + nodeIds[i] = *nodeIdPtr++; + } + m_pData += m_signatureLength * sizeof(uint16_t); + } + + // declaring type + if (isStatic) { + auto declaringTypePtr = reinterpret_cast(m_pData); + declaringNodeId = *declaringTypePtr; + m_pData += sizeof(uint16_t); + } + + + + } + + return m_pData - m_pStartData; +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.h b/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.h new file mode 100644 index 000000000..aadda536e --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.h @@ -0,0 +1,66 @@ +#ifndef METHODINFOSMARTPOINTER_H_ +#define METHODINFOSMARTPOINTER_H_ + +#include +#include +#include + +using namespace std; + +namespace tns { + class MethodInfo { + public: + + MethodInfo(uint8_t *pValue) + : isStatic(false), m_pData(pValue), m_pStartData(pValue), m_signatureLength(0), + sizeMeasured(false), nameOffset(0), resolvedData(0), + declaringNodeId(0){ + } + + MethodInfo(const MethodInfo& other) = default; + + MethodInfo& operator=(const MethodInfo& other) { + if (this != &other) { + isStatic = other.isStatic; + m_pData = other.m_pData; + m_pStartData = other.m_pStartData; + m_signatureLength = other.m_signatureLength; + sizeMeasured = other.sizeMeasured; + nameOffset = other.nameOffset; + resolvedData = other.resolvedData; + declaringNodeId = other.declaringNodeId; + nodeIds = other.nodeIds; + } + return *this; + } + + std::string GetName(); + + uint8_t CheckIsResolved(); + + uint16_t GetSignatureLength(); + + std::string GetSignature(); + + std::string GetDeclaringType(); //used only for static methods + + int GetSizeOfReadMethodInfo(); + + bool isStatic; + + private: + uint8_t *m_pData; //where we currently read + uint8_t *m_pStartData; // pointer to the beginning + uint16_t m_signatureLength; + bool sizeMeasured; + + uint32_t nameOffset; + uint8_t resolvedData; + uint16_t declaringNodeId; + std::vector nodeIds; + + + }; +} + +#endif /* METHODINFOSMARTPOINTER_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp new file mode 100644 index 000000000..bfe667475 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp @@ -0,0 +1,2225 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "NativeScriptException.h" +#include "MetadataNode.h" +#include "CallbackHandlers.h" +#include "NativeScriptAssert.h" +#include "File.h" +#include "Runtime.h" +#include "ArgConverter.h" +#include "FieldCallbackData.h" +#include "MetadataBuilder.h" +#include "ArgsWrapper.h" +#include "Util.h" +#include "GlobalHelpers.h" +#include "JSONObjectHelper.h" + +using namespace std; + +namespace { +napi_value EnsureConstructorThis(napi_env env, napi_value jsThis, napi_value prototype) { + if (!napi_util::is_null_or_undefined(env, jsThis)) { + return jsThis; + } + + auto runtime = Runtime::GetRuntime(env); + auto receiver = runtime->GetObjectManager()->GetEmptyObject(); + if (!napi_util::is_null_or_undefined(env, receiver) && + !napi_util::is_null_or_undefined(env, prototype)) { + napi_util::setPrototypeOf(env, receiver, prototype); + } + + return receiver; +} +} + +void MetadataNode::Init(napi_env env) { + auto cache = GetMetadataNodeCache(env); +} + +napi_value MetadataNode::CreateArrayObjectConstructor(napi_env env) { + auto it = s_arrayObjects.find(env); + if (it != s_arrayObjects.end()) { + auto value = napi_util::get_ref_value(env, it->second); + if (!napi_util::is_null_or_undefined(env, value)) return value; + } + + auto node = GetOrCreate("java/lang/Object"); + auto objectConstructor = node->GetConstructorFunction(env); + + napi_value arrayConstructor; + const char *name = "ArrayObjectWrapper"; + napi_define_class(env, name, strlen(name), + [](napi_env env, napi_callback_info info) -> napi_value { + NAPI_CALLBACK_BEGIN(0) + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget) + ? napi_util::get_prototype(env, newTarget) + : nullptr; + return EnsureConstructorThis(env, jsThis, receiverPrototype); + }, nullptr, 0, nullptr, &arrayConstructor); + napi_value proto = napi_util::get_prototype(env, arrayConstructor); + ObjectManager::MarkObject(env, proto); + + napi_util::napi_set_function(env, proto, "setValueAtIndex", ArraySetterCallback, nullptr); + napi_util::napi_set_function(env, proto, "getValueAtIndex", ArrayGetterCallback, nullptr); + napi_util::napi_set_function(env, proto, "getAllValues", ArrayGetAllValuesCallback, nullptr); + napi_util::define_property(env, proto, "length", nullptr, ArrayLengthCallback); + + napi_util::napi_inherits(env, arrayConstructor, objectConstructor); + + s_arrayObjects.emplace(env, napi_util::make_ref(env, arrayConstructor)); + + return arrayConstructor; +} + +napi_value MetadataNode::CreateExtendedJSWrapper(napi_env env, ObjectManager *objectManager, + const std::string &proxyClassName, + int javaObjectID) { + napi_value extInstance = nullptr; + + auto cacheData = GetCachedExtendedClassData(env, proxyClassName); + + if (cacheData.node != nullptr) { + extInstance = objectManager->GetEmptyObject(); + if (napi_util::is_null_or_undefined(env, extInstance)) { + return nullptr; + } + ObjectManager::MarkSuperCall(env, extInstance); + napi_value extendedCtorFunc = napi_util::get_ref_value(env, + cacheData.extendedCtorFunction); + napi_value extendedPrototype = napi_util::get_prototype(env, extendedCtorFunc); + napi_util::setPrototypeOf(env, extInstance, extendedPrototype); + + napi_set_named_property(env, extInstance, CONSTRUCTOR, extendedCtorFunc); + + SetInstanceMetadata(env, extInstance, cacheData.node); + } + + return extInstance; +} + +string MetadataNode::GetTypeMetadataName(napi_env env, napi_value value) { + napi_value typeMetadataName; + napi_get_named_property(env, value, PRIVATE_TYPE_NAME, &typeMetadataName); + + return napi_util::get_string_value(env, typeMetadataName); +} + + +bool MetadataNode::isArray() { + return m_isArray; +} + +napi_value MetadataNode::CreateJSWrapper(napi_env env, ObjectManager *objectManager) { + napi_value obj; + + if (m_isArray) { + obj = CreateArrayWrapper(env); + } else { + obj = objectManager->GetEmptyObject(); + napi_value ctorFunc = GetConstructorFunction(env); + napi_set_named_property(env, obj, CONSTRUCTOR, ctorFunc); + napi_util::setPrototypeOf(env, obj, napi_util::get_prototype(env, ctorFunc)); + SetInstanceMetadata(env, obj, this); + } + + return obj; +} + +napi_value MetadataNode::ArrayGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + try { + + napi_value index = argv[0]; + int32_t indexValue; + napi_get_value_int32(env, index, &indexValue); + auto node = GetInstanceMetadata(env, jsThis); + + return CallbackHandlers::GetArrayElement(env, jsThis, indexValue, node->m_name); + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ArrayGetAllValuesCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0); + try { + auto node = GetInstanceMetadata(env, jsThis); + auto length = CallbackHandlers::GetArrayLength(env, jsThis); + napi_value arr; + napi_create_array(env, &arr); + + for (int i = 0; i < length; i++) { + napi_value element = CallbackHandlers::GetArrayElement(env, jsThis, i, node->m_name); + napi_set_element(env, arr, i, element); + } + + return arr; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ArraySetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(2); + + try { + + napi_value index = argv[0]; + napi_value value = argv[1]; + + int32_t indexValue; + napi_get_value_int32(env, index, &indexValue); + auto node = GetInstanceMetadata(env, jsThis); + + CallbackHandlers::SetArrayElement(env, jsThis, indexValue, node->m_name, value); + return value; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ArrayLengthCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + + try { + int length = CallbackHandlers::GetArrayLength(env, jsThis); + + napi_value len; + napi_create_int32(env, length, &len); + return len; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::CreateArrayWrapper(napi_env env) { + napi_value constructor = CreateArrayObjectConstructor(env); + napi_value instance; + napi_new_instance(env, constructor, 0, nullptr, &instance); + SetInstanceMetadata(env, instance, this); + return instance; +} + +napi_value MetadataNode::GetImplementationObject(napi_env env, napi_value object) { + auto target = object; + napi_value currentPrototype = target; + + napi_value implementationObject; + + napi_get_named_property(env, currentPrototype, CLASS_IMPLEMENTATION_OBJECT, + &implementationObject); + + if (implementationObject != nullptr && !napi_util::is_undefined(env, implementationObject)) { + return implementationObject; + } + + bool hasProperty; + + napi_value prototypeImplObjectKey; + napi_create_string_utf8(env, PROP_KEY_IS_PROTOTYPE_IMPLEMENTATION_OBJECT, NAPI_AUTO_LENGTH, + &prototypeImplObjectKey); + napi_has_own_property(env, object, prototypeImplObjectKey, &hasProperty); + + if (hasProperty) { + bool maybeHasOwnProperty; + napi_value prototypeKey; + napi_create_string_utf8(env, PROTOTYPE, NAPI_AUTO_LENGTH, &prototypeKey); + napi_has_own_property(env, object, prototypeKey, &maybeHasOwnProperty); + + if (!maybeHasOwnProperty) { + return nullptr; + } + + return napi_util::get_prototype(env, object); + } + + napi_value activityImplementationObject; + napi_get_named_property(env, object, "t::ActivityImplementationObject", + &activityImplementationObject); + + if (activityImplementationObject != nullptr && + !napi_util::is_undefined(env, activityImplementationObject)) { + return activityImplementationObject; + } + + napi_value lastPrototype; + + bool prototypeCycleDetected = false; + + bool foundImplementationObject = false; + + while (!foundImplementationObject) { + currentPrototype = napi_util::get_prototype(env, currentPrototype); + + if (napi_util::is_null(env, currentPrototype)) { + break; + } + + if (lastPrototype == currentPrototype) { + auto abovePrototype = napi_util::get_prototype(env, currentPrototype); + prototypeCycleDetected = abovePrototype == currentPrototype; + break; + } + + if (currentPrototype == nullptr || napi_util::is_null(env, currentPrototype) || + prototypeCycleDetected) { + return nullptr; + } else { + napi_value implObject; + napi_get_named_property(env, currentPrototype, CLASS_IMPLEMENTATION_OBJECT, + &implObject); + + if (implObject != nullptr && !napi_util::is_undefined(env, implObject)) { + foundImplementationObject = true; + return currentPrototype; + } + } + lastPrototype = currentPrototype; + } + + return implementationObject; +} + +void MetadataNode::SetInstanceMetadata(napi_env env, napi_value object, MetadataNode *node) { + auto cache = GetMetadataNodeCache(env); + napi_value external; + napi_create_external(env, node, [](napi_env env, void *d1, void *d2) {}, node, &external); + napi_set_named_property(env, object, "#instance_metadata", external); +// napi_wrap(env, object, node, nullptr, nullptr, nullptr); +} + + +napi_value MetadataNode::ExtendedClassConstructorCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS() + + try { + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + if (napi_util::is_null_or_undefined(env, newTarget)) return nullptr; + napi_value receiver = EnsureConstructorThis(env, jsThis, napi_util::get_prototype(env, newTarget)); + if (napi_util::is_null_or_undefined(env, receiver)) return nullptr; + + auto extData = reinterpret_cast(data); + SetInstanceMetadata(env, receiver, extData->node); + + napi_value implementationObject = napi_util::get_ref_value(env, + extData->implementationObject); + ObjectManager::MarkSuperCall(env, receiver); + + string fullClassName = extData->fullClassName; + + ArgsWrapper argWrapper(argv.data(), argc, ArgType::Class); + napi_value jsThisProxy; + bool success = CallbackHandlers::RegisterInstance(env, receiver, fullClassName, argWrapper, + implementationObject, false, + &jsThisProxy, extData->node->m_name); + + return jsThisProxy; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::InterfaceConstructorCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS() + + try { + + napi_valuetype arg1Type; + napi_valuetype arg2Type; + + napi_typeof(env, argv[0], &arg1Type); + + if (argc == 2) { + napi_typeof(env, argv[1], &arg2Type); + } + + napi_value implementationObject; + napi_value interfaceName; + + if (argc == 1) { + if (arg1Type != napi_object) { + throw NativeScriptException( + string("Invalid arguments provided, first argument must be an object if only one argument is provided")); + return nullptr; + } + implementationObject = argv[0]; + } else if (argc == 2) { + if (arg1Type != napi_string) { + throw NativeScriptException( + string("Invalid arguments provided, first argument must be a string if only two argument is provided")); + return nullptr; + } + + if (arg2Type != napi_object) { + throw NativeScriptException( + string("Invalid arguments provided, second argument must be an object if only one argument is provided")); + return nullptr; + } + + interfaceName = argv[0]; + implementationObject = argv[1]; + } else { + throw NativeScriptException( + string("Invalid arguments provided, first argument must be a string and second argument must be an object")); + } + + auto node = reinterpret_cast(data); + + auto className = node->m_implType; + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget) + ? napi_util::get_prototype(env, newTarget) + : nullptr; + napi_value receiver = EnsureConstructorThis(env, jsThis, receiverPrototype); + if (napi_util::is_null_or_undefined(env, receiver)) return nullptr; + + SetInstanceMetadata(env, receiver, node); + + ObjectManager::MarkSuperCall(env, receiver); + + + napi_util::setPrototypeOf(env, implementationObject, + napi_util::getPrototypeOf(env, receiver)); + + napi_util::setPrototypeOf(env, receiver, implementationObject); + + napi_set_named_property(env, receiver, CLASS_IMPLEMENTATION_OBJECT, implementationObject); + + ArgsWrapper argsWrapper(argv.data(), argc, ArgType::Interface); + + napi_value jsThisProxy; + auto success = CallbackHandlers::RegisterInstance(env, receiver, className, argsWrapper, + implementationObject, true, &jsThisProxy); + return jsThisProxy; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ClassConstructorCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS() + + try { + + auto node = reinterpret_cast(data); + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget) + ? napi_util::get_prototype(env, newTarget) + : nullptr; + napi_value receiver = EnsureConstructorThis(env, jsThis, receiverPrototype); + if (napi_util::is_null_or_undefined(env, receiver)) return nullptr; + + SetInstanceMetadata(env, receiver, node); + + string extendName; + auto className = node->m_name; + + string fullClassName = CreateFullClassName(className, extendName); + + ArgsWrapper argsWrapper(argv.data(), argc, ArgType::Class); + napi_value jsThisProxy; + bool success = CallbackHandlers::RegisterInstance(env, receiver, fullClassName, argsWrapper, + nullptr, false, &jsThisProxy, className); + + return jsThisProxy; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +string MetadataNode::CreateFullClassName(const std::string &className, + const std::string &extendNameAndLocation = "") { + string fullClassName = className; + + // create a class name consisting only of the base class name + last file name part + line + column + variable identifier + if (!extendNameAndLocation.empty()) { + string tempClassName = className; + fullClassName = Util::ReplaceAll(tempClassName, "$", "_"); + fullClassName += "_" + extendNameAndLocation; + } + + return fullClassName; +} + +bool MetadataNode::IsValidExtendName(napi_env env, napi_value name) { + string extendName = ArgConverter::ConvertToString(env, name); + + for (char currentSymbol: extendName) { + bool isValidExtendNameSymbol = isalpha(currentSymbol) || + isdigit(currentSymbol) || + currentSymbol == '_'; + if (!isValidExtendNameSymbol) { + return false; + } + } + + return true; +} + + +bool +MetadataNode::GetExtendLocation(napi_env env, string &extendLocation, bool isTypeScriptExtend) { + stringstream extendLocationStream; + + auto frames = tns::BuildStacktraceFrames(env, nullptr, 4); + tns::JsStacktraceFrame *frame; + if (isTypeScriptExtend) { + if (Util::Contains(frames[2].text, "call_super")) { + frame = &frames[3]; + } else { + frame = &frames[2]; // the _super.apply call to ts_helpers will always be the third call frame + } + } else { + frame = &frames[0]; + } + + if (frame == NULL) { + DEBUG_WRITE("%s", "FRAME IS NULL!"); + return true; + } + + string srcFileName = Util::ReplaceAll(frame->filename, "file://", ""); + + string fullPathToFile; + if (srcFileName == "" || srcFileName == "" || srcFileName == "JavaScript") { + fullPathToFile = "script"; + } else { + string hardcodedPathToSkip = Constants::APP_ROOT_FOLDER_PATH; + int startIndex = hardcodedPathToSkip.length(); + int strToTakeLen = srcFileName.length() - startIndex - 3; + fullPathToFile = srcFileName.substr(startIndex, strToTakeLen); + fullPathToFile = srcFileName; + replace(fullPathToFile.begin(), fullPathToFile.end(), '/', '_'); + replace(fullPathToFile.begin(), fullPathToFile.end(), '.', '_'); + replace(fullPathToFile.begin(), fullPathToFile.end(), '-', '_'); + replace(fullPathToFile.begin(), fullPathToFile.end(), ' ', '_'); + + vector pathParts; + Util::SplitString(fullPathToFile, "_", pathParts); + fullPathToFile = + pathParts.back() == "js" ? pathParts[pathParts.size() - 2] : pathParts.back(); + } + + if (frame->line < 0) { + extendLocationStream << fullPathToFile << " unknown line number"; + extendLocation = extendLocationStream.str(); + return false; + } + + if (frame->col < 0) { + extendLocationStream << fullPathToFile << " line:" << frame->line + << " unknown column number"; + extendLocation = extendLocationStream.str(); + return false; + } + int column = frame->col; + if (frame->line == 1) { + column -= ModuleInternal::MODULE_PROLOGUE_LENGTH; + } + +#ifdef __HERMES__ + column = column - 6; +#endif + + extendLocationStream << fullPathToFile << "_" << frame->line << "_" << column << "_"; + extendLocation = extendLocationStream.str(); + return true; +} + + +bool MetadataNode::ValidateExtendArguments(napi_env env, size_t argc, napi_value *argv, + bool extendLocationFound, string &extendLocation, + napi_value *extendName, napi_value *implementationObject, + bool isTypeScriptExtend) { + + if (argc == 1) { + if (!extendLocationFound) { + stringstream ss; + ss << "Invalid extend() call. No name specified for extend at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + if (!napi_util::is_object(env, argv[0])) { + stringstream ss; + ss << "Invalid extend() call. No implementation object specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + *implementationObject = argv[0]; + } else if (argc == 2 || isTypeScriptExtend) { + if (!napi_util::is_of_type(env, argv[0], napi_string)) { + stringstream ss; + ss << "Invalid extend() call. No name for extend specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + if (!napi_util::is_object(env, argv[1])) { + stringstream ss; + ss + << "Invalid extend() call. Named extend should be called with second object parameter containing overridden methods at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + DEBUG_WRITE("ExtendsCallMethodHandler: getting extend name"); + + *extendName = argv[0]; + bool isValidExtendName = IsValidExtendName(env, *extendName); + if (!isValidExtendName) { + stringstream ss; + ss << "The extend name \"" << ArgConverter::ConvertToString(env, *extendName) + << "\" you provided contains invalid symbols. Try using the symbols [a-z, A-Z, 0-9, _]." + << endl; + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + *implementationObject = argv[1]; + } else { + stringstream ss; + ss << "Invalid extend() call at location: " << extendLocation.c_str(); + string exceptionMessage = ss.str(); + throw NativeScriptException(exceptionMessage); + } + + return true; +} + +MetadataNode::ExtendedClassCacheData +MetadataNode::GetCachedExtendedClassData(napi_env env, const string &proxyClassName) { + auto cache = GetMetadataNodeCache(env); + ExtendedClassCacheData cacheData; + auto itFound = cache->ExtendedCtorFuncCache.find(proxyClassName); + if (itFound != cache->ExtendedCtorFuncCache.end()) { + cacheData = itFound->second; + } + + return cacheData; +} + +MetadataNode::MetadataNodeCache *MetadataNode::GetMetadataNodeCache(napi_env env) { + auto cache = s_metadata_node_cache.Get(env); + if (cache) return cache; + cache = new MetadataNodeCache; + s_metadata_node_cache.Insert(env, cache); + return cache; +} + +MetadataNode::MetadataNode(MetadataTreeNode *treeNode) : m_treeNode(treeNode) { + uint8_t nodeType = s_metadataReader.GetNodeType(treeNode); + + m_name = s_metadataReader.ReadTypeName(m_treeNode); + + uint8_t parentNodeType = s_metadataReader.GetNodeType(treeNode->parent); + + m_isArray = s_metadataReader.IsNodeTypeArray(parentNodeType); + + bool isInterface = s_metadataReader.IsNodeTypeInterface(nodeType); + + if (!m_isArray && isInterface) { + bool isPrefix; + auto impTypeName = s_metadataReader.ReadInterfaceImplementationTypeName(m_treeNode, + isPrefix); + m_implType = isPrefix + ? (impTypeName + m_name) + : impTypeName; + } +} + +void MetadataNode::CreateTopLevelNamespaces(napi_env env) { + napi_value global; + + napi_get_global(env, &global); + + auto root = s_metadataReader.GetRoot(); + + const auto &children = *root->children; + + for (auto treeNode: children) { + uint8_t nodeType = s_metadataReader.GetNodeType(treeNode); + + if (nodeType == MetadataTreeNode::PACKAGE) { + auto node = GetOrCreateInternal(treeNode); + + napi_value packageObj = node->CreateWrapper(env); + + string nameSpace = node->m_treeNode->name; + // if the namespaces matches a javascript keyword, prefix it with $ to avoid TypeScript and JavaScript errors + if (IsJavascriptKeyword(nameSpace)) { + nameSpace = "$" + nameSpace; + } + napi_set_named_property(env, global, nameSpace.c_str(), packageObj); + } + } +} + +MetadataTreeNode *MetadataNode::GetOrCreateTreeNodeByName(const string &className) { + MetadataTreeNode *result = nullptr; + + auto itFound = s_name2TreeNodeCache.find(className); + + if (itFound != s_name2TreeNodeCache.end()) { + result = itFound->second; + } else { + result = s_metadataReader.GetOrCreateTreeNodeByName(className); + + s_name2TreeNodeCache.emplace(className, result); + } + + return result; +} + +string MetadataNode::GetName() { + return m_name; +} + +MetadataNode *MetadataNode::GetOrCreate(const string &className) { + MetadataNode *node = nullptr; + + auto it = s_name2NodeCache.find(className); + + if (it == s_name2NodeCache.end()) { + MetadataTreeNode *treeNode = GetOrCreateTreeNodeByName(className); + + node = GetOrCreateInternal(treeNode); + + s_name2NodeCache.emplace(className, node); + } else { + node = it->second; + } + + return node; +} + +MetadataNode *MetadataNode::GetOrCreateInternal(MetadataTreeNode *treeNode) { + MetadataNode *result = nullptr; + + auto it = s_treeNode2NodeCache.find(treeNode); + + if (it != s_treeNode2NodeCache.end()) { + result = it->second; + } else { + auto name = GetJniClassName(treeNode); + if (!name.empty()) { + auto it2 = s_name2NodeCache.find(name); + if ( it2 != s_name2NodeCache.end()) { + result = it2->second; + } + } + + if (!result) { + result = new MetadataNode(treeNode); + s_treeNode2NodeCache.emplace(treeNode, result); + if (!result->m_name.empty()) { + s_name2NodeCache.emplace(result->m_name, result); + } + } + } + + auto found = s_treeNode2NodeCache.find(treeNode); + if (found == s_treeNode2NodeCache.end()) { + s_treeNode2NodeCache.emplace(treeNode, result); + } + + return result; +} + +MetadataEntry MetadataNode::GetChildMetadataForPackage(MetadataNode *node, const char *propName) { + assert(node->m_treeNode->children != nullptr); + + MetadataEntry child(nullptr, NodeType::Class); + + const auto &children = *node->m_treeNode->children; + + for (auto treeNodeChild: children) { + if (strcmp(treeNodeChild->name.c_str(), propName) == 0) { + child.name = propName; + child.treeNode = treeNodeChild; + child.type = static_cast(s_metadataReader.GetNodeType(treeNodeChild)); + + if (s_metadataReader.IsNodeTypeInterface((uint8_t) child.type)) { + bool isPrefix; + string declaringType = s_metadataReader.ReadInterfaceImplementationTypeName( + treeNodeChild, isPrefix); + child.declaringType = isPrefix + ? (declaringType + + s_metadataReader.ReadTypeName(child.treeNode)) + : declaringType; + } + } + } + + return child; +} + +bool MetadataNode::IsJavascriptKeyword(const std::string &word) { + static set keywords; + + if (keywords.empty()) { + string kw[]{"abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", + "class", "const", "continue", "debugger", "default", "delete", "do", + "double", "else", "enum", "eval", "export", "extends", "false", "final", + "finally", "float", "for", "function", "goto", "if", "implements", + "import", "in", "instanceof", "int", "interface", "let", "long", "native", + "new", "null", "package", "private", "protected", "public", "return", + "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", + "transient", "true", "try", "typeof", "var", "void", "volatile", "while", + "with", "yield"}; + + keywords = set(kw, kw + sizeof(kw) / sizeof(kw[0])); + } + + return keywords.find(word) != keywords.end(); +} + +napi_value MetadataNode::CreateWrapper(napi_env env) { + napi_value result; + uint8_t nodeType = s_metadataReader.GetNodeType(m_treeNode); + bool isClass = s_metadataReader.IsNodeTypeClass(nodeType), + isInterface = s_metadataReader.IsNodeTypeInterface(nodeType); + napi_status status; + + if (isClass || isInterface) { + result = GetConstructorFunction(env); + } else if (s_metadataReader.IsNodeTypePackage(nodeType)) { + result = CreatePackageObject(env); + } else { + std::stringstream ss; + ss << "(InternalError): Can't create proxy for this type=" << static_cast(nodeType); + throw NativeScriptException(ss.str()); + } + + return result; +} + +napi_value MetadataNode::PackageGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + try { + auto childTreeNode = static_cast(data); + auto node = GetOrCreateInternal(childTreeNode->parent); + DEBUG_WRITE("Get package item: %s", childTreeNode->name.c_str()); + auto hiddenPropName = "__" + childTreeNode->name; + napi_value hiddenValue; + napi_get_named_property(env, jsThis, hiddenPropName.c_str(), &hiddenValue); + + if (!napi_util::is_null_or_undefined(env, hiddenValue)) { + DEBUG_WRITE("Get cached package item: %s", hiddenPropName.c_str()); + return hiddenValue; + } + + auto childNode = MetadataNode::GetOrCreateInternal(childTreeNode); + hiddenValue = childNode->CreateWrapper(env); + + uint8_t childNodeType = s_metadataReader.GetNodeType(childTreeNode); + bool isInterface = s_metadataReader.IsNodeTypeInterface(childNodeType); + if (isInterface) { + // For all java interfaces we register the special Symbol.hasInstance property + // which is invoked by the instanceof operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance). + // For example: + // + // Object.defineProperty(android.view.animation.Interpolator, Symbol.hasInstance, { + // value: function(obj) { + // return true; + // } + // }); + RegisterSymbolHasInstanceCallback(env, childTreeNode, hiddenValue); + } + auto parentNode = GetOrCreateInternal(childTreeNode->parent); + if (parentNode->m_name == "org/json" && childTreeNode->name == "JSONObject") { + JSONObjectHelper::RegisterFromFunction(env, hiddenValue); + } + + napi_set_named_property(env, jsThis, hiddenPropName.c_str(), hiddenValue); + + return hiddenValue; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +void MetadataNode::RegisterSymbolHasInstanceCallback(napi_env env, const MetadataTreeNode *treeNode, + napi_value interface) { + if (napi_util::is_undefined(env, interface) || napi_util::is_null(env, interface)) { + return; + } + + JEnv jEnv; + + auto className = GetJniClassName(treeNode); + auto clazz = jEnv.FindClass(className); + if (clazz == nullptr) { + return; + } + + napi_value hasInstance; + napi_value symbol; + napi_value global; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Symbol", &symbol); + napi_get_named_property(env, symbol, "hasInstance", &hasInstance); + napi_value method; + napi_create_function(env, "hasInstance", NAPI_AUTO_LENGTH, SymbolHasInstanceCallback, clazz, + &method); + + napi_property_descriptor desc = { + nullptr, // utf8name + hasInstance, // name + nullptr, // method + nullptr, // getter + nullptr, // setter + method, // value + napi_default, // attributes + nullptr // data + }; + napi_define_properties(env, interface, 1, &desc); +} + +napi_value MetadataNode::SymbolHasInstanceCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS(); + if (argc != 1) { + throw NativeScriptException(string("Symbol.hasInstance must take exactly 1 argument")); + return nullptr; + } + + napi_value object = argv[0]; + + if (!napi_util::is_object(env, object)) { + return napi_util::get_false(env); + } + + auto clazz = reinterpret_cast(data); + auto runtime = Runtime::GetRuntime(env); + + auto objectManager = runtime->GetObjectManager(); + auto obj = objectManager->GetJavaObjectByJsObject(object); + + if (obj.IsNull()) { + // Couldn't find a corresponding java instance counterpart. This could happen + // if the "instanceof" operator is invoked on a pure javascript instance + return napi_util::get_false(env); + } + + JEnv jEnv; + auto isInstanceOf = jEnv.IsInstanceOf(obj, clazz); + + napi_value result; + napi_get_boolean(env, isInstanceOf, &result); + + return result; + +} + + +std::string MetadataNode::GetJniClassName(const MetadataTreeNode *node) { + std::stack s; + + while (node != nullptr && !node->name.empty()) { + s.push(node->name); + node = node->parent; + } + + string fullClassName; + while (!s.empty()) { + auto top = s.top(); + fullClassName = (fullClassName.empty()) ? top : fullClassName + "/" + top; + s.pop(); + } + + return fullClassName; +} + +napi_value MetadataNode::CreatePackageObject(napi_env env) { + napi_value packageObj; + napi_create_object(env, &packageObj); + + auto ptrChildren = this->m_treeNode->children; + + if (ptrChildren != nullptr) { + const auto &children = *ptrChildren; + auto lastChildName = ""; + for (auto childNode: children) { + if (strcmp(childNode->name.c_str(), lastChildName) == 0) { + continue; + } + lastChildName = childNode->name.c_str(); + napi_property_descriptor descriptor{ + childNode->name.c_str(), + nullptr, + nullptr, + PackageGetterCallback, + nullptr, + nullptr, + napi_default_jsproperty, + childNode}; + napi_define_properties(env, packageObj, 1, &descriptor); + } + } + + return packageObj; +} + +std::vector MetadataNode::SetClassMembers( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode) { + + if (treeNode->metadata != nullptr) { + return SetInstanceMembersFromRuntimeMetadata( + env, constructor, instanceMethodsCallbackData, + baseInstanceMethodsCallbackData, treeNode); + } + + return SetClassMembersFromStaticMetadata( + env, constructor, instanceMethodsCallbackData, + baseInstanceMethodsCallbackData, treeNode); +} + +std::vector MetadataNode::SetClassMembersFromStaticMetadata( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode) { + + std::vector instanceMethodData; + + uint8_t *curPtr = s_metadataReader.GetValueData() + treeNode->offsetValue + 1; + + auto nodeType = s_metadataReader.GetNodeType(treeNode); + auto curType = s_metadataReader.ReadTypeName(treeNode); + curPtr += sizeof(uint16_t /* baseClassId */); + + if (s_metadataReader.IsNodeTypeInterface(nodeType)) { + curPtr += sizeof(uint8_t) + sizeof(uint32_t); + } + + std::string lastMethodName; + MethodCallbackData *callbackData = nullptr; + + robin_hood::unordered_map collectedExtensionMethods; + + napi_value prototype = napi_util::get_prototype(env, constructor); + + auto extensionFunctionsCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + collectedExtensionMethods.reserve(extensionFunctionsCount); + + for (auto i = 0; i < extensionFunctionsCount; i++) { + auto entry = MetadataReader::ReadExtensionFunctionEntry(&curPtr); + + auto &methodName = entry.getName(); + if (methodName != lastMethodName) { + callbackData = tryGetExtensionMethodCallbackData(collectedExtensionMethods, + methodName); + + if (callbackData == nullptr) { + callbackData = new MethodCallbackData(this); + MetadataNode::GetMetadataNodeCache(env)->methodCallbackData.push_back(callbackData); + + napi_value method; + napi_create_function(env, methodName.c_str(), methodName.size(), MethodCallback, + callbackData, &method); + + napi_util::define_property_value(env, prototype, methodName.c_str(), method, napi_default_method); + lastMethodName = methodName; + collectedExtensionMethods.emplace(methodName, callbackData); + + } + } + + callbackData->candidates.push_back(std::move(entry)); + } + + auto instanceMethodCount = *reinterpret_cast(curPtr); + collectedExtensionMethods.reserve(instanceMethodCount); + curPtr += sizeof(uint16_t); + + for (auto i = 0; i < instanceMethodCount; i++) { + auto entry = MetadataReader::ReadInstanceMethodEntry(&curPtr); + auto &methodName = entry.getName(); + if (methodName != lastMethodName) { + callbackData = tryGetExtensionMethodCallbackData(collectedExtensionMethods, + methodName); + + + if (callbackData == nullptr) { + callbackData = new MethodCallbackData(this); + MetadataNode::GetMetadataNodeCache(env)->methodCallbackData.push_back(callbackData); + napi_value method; + napi_create_function(env, methodName.c_str(), methodName.size(), MethodCallback, + callbackData, &method); + napi_util::define_property_value(env, prototype, methodName.c_str(), method, napi_default_method); + collectedExtensionMethods.emplace(methodName, callbackData); + } + + instanceMethodData.push_back(callbackData); + instanceMethodsCallbackData.push_back(callbackData); + + auto itFound = std::find_if(baseInstanceMethodsCallbackData.begin(), + baseInstanceMethodsCallbackData.end(), + [&methodName](MethodCallbackData *x) { + return x->candidates.front().name == methodName; + }); + if (itFound != baseInstanceMethodsCallbackData.end()) { + callbackData->parent = *itFound; + } + + lastMethodName = methodName; + } + + callbackData->candidates.push_back(std::move(entry)); + } + + auto instanceFieldCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (auto i = 0; i < instanceFieldCount; i++) { + auto entry = MetadataReader::ReadInstanceFieldEntry(&curPtr); + auto &fieldName = entry.getName(); + auto fieldInfo = new FieldCallbackData(entry); + fieldInfo->metadata.declaringType = curType; + napi_util::define_property(env, prototype, fieldName.c_str(), nullptr, + FieldAccessorGetterCallback, FieldAccessorSetterCallback, + fieldInfo); + + MetadataNode::GetMetadataNodeCache(env)->fieldCallbackData.push_back(fieldInfo); + + } + + auto kotlinPropertiesCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (int i = 0; i < kotlinPropertiesCount; ++i) { + uint32_t nameOffset = *reinterpret_cast(curPtr); + auto propertyName = s_metadataReader.ReadName(nameOffset); + curPtr += sizeof(uint32_t); + + auto hasGetter = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + + std::string getterMethodName; + if (hasGetter >= 1) { + auto entry = MetadataReader::ReadInstanceMethodEntry(&curPtr); + getterMethodName = entry.getName(); + } + + auto hasSetter = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + + std::string setterMethodName; + if (hasSetter >= 1) { + auto entry = MetadataReader::ReadInstanceMethodEntry(&curPtr); + setterMethodName = entry.getName(); + } + + auto propertyInfo = new PropertyCallbackData(propertyName, getterMethodName, + setterMethodName); + MetadataNode::GetMetadataNodeCache(env)->propertyCallbackData.push_back(propertyInfo); + napi_util::define_property(env, prototype, propertyName.c_str(), nullptr, + PropertyAccessorGetterCallback, PropertyAccessorSetterCallback, + propertyInfo); + } + + // Set static class members on constructor + lastMethodName.clear(); + callbackData = nullptr; + + auto origin = Constants::APP_ROOT_FOLDER_PATH + this->m_name; + + // get candidates from static methods metadata + auto staticMethodCout = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (auto i = 0; i < staticMethodCout; i++) { + auto entry = MetadataReader::ReadStaticMethodEntry(&curPtr); + // In java there can be multiple methods of same name with different parameters. + auto &methodName = entry.getName(); + if (methodName != lastMethodName) { + callbackData = new MethodCallbackData(this); + MetadataNode::GetMetadataNodeCache(env)->methodCallbackData.push_back(callbackData); + napi_value method; + napi_create_function(env, methodName.c_str(), methodName.size(), MethodCallback, + callbackData, &method); + + napi_util::define_property_value(env, constructor, methodName.c_str(), method, napi_default_method); + lastMethodName = methodName; + } + callbackData->candidates.push_back(std::move(entry)); + } + + napi_value extendMethod; + napi_create_function(env, PROP_KEY_EXTEND, sizeof(PROP_KEY_EXTEND), ExtendMethodCallback, this, + &extendMethod); + napi_set_named_property(env, constructor, PROP_KEY_EXTEND, extendMethod); + + // get candidates from static fields metadata + auto staticFieldCout = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (auto i = 0; i < staticFieldCout; i++) { + auto entry = MetadataReader::ReadStaticFieldEntry(&curPtr); + auto &fieldName = entry.getName(); + auto fieldInfo = new FieldCallbackData(entry); + napi_value method; + napi_util::define_property(env, constructor, fieldName.c_str(), nullptr, + FieldAccessorGetterCallback, FieldAccessorSetterCallback, + fieldInfo); + MetadataNode::GetMetadataNodeCache(env)->fieldCallbackData.push_back(fieldInfo); + } + + + napi_util::define_property(env, constructor, PROP_KEY_NULLOBJECT, nullptr, + NullObjectAccessorGetterCallback, nullptr, this); + + + std::string tname = s_metadataReader.ReadTypeName(treeNode); + napi_set_named_property(env, constructor, PRIVATE_TYPE_NAME, + ArgConverter::convertToJsString(env, tname)); + + SetClassAccessor(env, constructor); + + return instanceMethodData; +} + +MetadataNode::MethodCallbackData *MetadataNode::tryGetExtensionMethodCallbackData( + const robin_hood::unordered_map &collectedMethodCallbackData, + const std::string &lookupName) { + + if (collectedMethodCallbackData.empty()) { + return nullptr; + } + + auto itFound = collectedMethodCallbackData.find(lookupName); + if (itFound != collectedMethodCallbackData.end()) { + return itFound->second; + } + + return nullptr; +} + +bool MetadataNode::IsNodeTypeInterface() { + uint8_t nodeType = s_metadataReader.GetNodeType(m_treeNode); + return s_metadataReader.IsNodeTypeInterface(nodeType); +} + +std::vector MetadataNode::SetInstanceMembersFromRuntimeMetadata( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode) { + assert(treeNode->metadata != nullptr); + + std::vector instanceMethodData; + + std::string line; + const std::string &metadata = *treeNode->metadata; + std::stringstream s(metadata); + + std::string kind; + std::string name; + std::string signature; + int paramCount; + + std::getline(s, line); // type line + std::getline(s, line); // base class line + + std::string lastMethodName; + MethodCallbackData *callbackData = nullptr; + + napi_value proto = napi_util::get_prototype(env, constructor); + while (std::getline(s, line)) { + std::stringstream tmp(line); + tmp >> kind >> name >> signature >> paramCount; + + char chKind = kind[0]; + + assert((chKind == 'M') || (chKind == 'F')); + + MetadataEntry entry(nullptr, NodeType::Field); + + entry.name = name; + entry.sig = signature; + entry.paramCount = paramCount; + entry.isStatic = false; + if (chKind == 'M') { + if (entry.name != lastMethodName) { + entry.type = NodeType::Method; + callbackData = new MethodCallbackData(this); + MetadataNode::GetMetadataNodeCache(env)->methodCallbackData.push_back(callbackData); + instanceMethodData.push_back(callbackData); + instanceMethodsCallbackData.push_back(callbackData); + + auto itFound = std::find_if(baseInstanceMethodsCallbackData.begin(), + baseInstanceMethodsCallbackData.end(), + [&entry](MethodCallbackData *x) { + return x->candidates.front().name == entry.name; + }); + if (itFound != baseInstanceMethodsCallbackData.end()) { + callbackData->parent = *itFound; + } + + napi_value method; + napi_create_function(env, entry.name.c_str(), NAPI_AUTO_LENGTH, MethodCallback, + callbackData, &method); + napi_set_named_property(env, proto, entry.name.c_str(), method); + + lastMethodName = entry.name; + } + callbackData->candidates.push_back(std::move(entry)); + } else if (chKind == 'F') { + entry.type = NodeType::Field; + auto *fieldInfo = new FieldCallbackData(entry); + napi_util::define_property(env, proto, entry.name.c_str(), nullptr, + FieldAccessorGetterCallback, FieldAccessorSetterCallback, + fieldInfo); + + MetadataNode::GetMetadataNodeCache(env)->fieldCallbackData.push_back(fieldInfo); + } + } + + return instanceMethodData; +} + +void MetadataNode::SetClassAccessor(napi_env env, napi_value constructor) { + napi_util::define_property(env, constructor, PROP_KEY_CLASS, nullptr, + ClassAccessorGetterCallback, nullptr, nullptr); +} + +napi_value MetadataNode::ClassAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0); + try { + napi_value name; + napi_get_named_property(env, jsThis, PRIVATE_TYPE_NAME, &name); + const char *nameValue = napi_util::get_string_value(env, name); + return CallbackHandlers::FindClass(env, nameValue); + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::GetConstructorFunction(napi_env env) { + std::vector instanceMethodsCallbackData; + return GetConstructorFunctionInternal(env, m_treeNode, instanceMethodsCallbackData); +} + +napi_value MetadataNode::GetConstructorFunctionInternal(napi_env env, MetadataTreeNode *treeNode, + std::vector instanceMethodsCallbackData) { + + auto cache = GetMetadataNodeCache(env); + auto itFound = cache->CtorFuncCache.find(treeNode); + if (itFound != cache->CtorFuncCache.end()) { + if (itFound->second.constructorFunction != nullptr) { + auto value = napi_util::get_ref_value(env, itFound->second.constructorFunction); + if (!napi_util::is_null_or_undefined(env, value)) { + instanceMethodsCallbackData = itFound->second.instanceMethodCallbacks; + return value; + } + } + } + + if (itFound != cache->CtorFuncCache.end()) { + itFound->second.instanceMethodCallbacks.clear(); + if (itFound->second.constructorFunction != nullptr) { + napi_delete_reference(env, itFound->second.constructorFunction); + } + cache->CtorFuncCache.erase(itFound); + } + + auto node = GetOrCreateInternal(treeNode); + + JEnv jEnv; + // if we already have an exception (which will be rethrown later) + // then we don't want to ignore the next exception + bool ignoreFindClassException = jEnv.ExceptionCheck() == JNI_FALSE; + auto currentClass = jEnv.FindClass(node->m_name); + if (ignoreFindClassException && jEnv.ExceptionCheck()) { + jEnv.ExceptionClear(); + // JNI found an exception looking up this class + // but we don't care, because this means this class doesn't exist + // like when you try to get a class that only exists in a higher API level + CtorCacheData ctorCacheItem(nullptr, instanceMethodsCallbackData); + cache->CtorFuncCache.emplace(treeNode, ctorCacheItem); + return nullptr; + }; + + auto currentNode = treeNode; + std::string finalName(currentNode->name); + while (currentNode->parent) { + if (!currentNode->parent->name.empty()) { + finalName = currentNode->parent->name + "." + finalName; + } + currentNode = currentNode->parent; + } + + // 1. Create the class and get the constructor + + napi_value constructor; + auto isInterface = s_metadataReader.IsNodeTypeInterface(treeNode->type); + napi_define_class(env, finalName.c_str(), NAPI_AUTO_LENGTH, + isInterface ? InterfaceConstructorCallback : ClassConstructorCallback, + node, 0, nullptr, &constructor); + + // Mark this constructor's prototype as a runtime object. + ObjectManager::MarkObject(env, napi_util::get_prototype(env, constructor)); + + // 2. Create the base constructor if it doesn't exist and inherit from it. + napi_value baseConstructor; + std::vector baseInstanceMethodsCallbackData; + auto tmpTreeNode = treeNode; + std::vector skippedBaseTypes; + + while (true) { + auto baseTreeNode = s_metadataReader.GetBaseClassNode(tmpTreeNode); + if (CheckClassHierarchy(jEnv, currentClass, treeNode, baseTreeNode, skippedBaseTypes)) { + tmpTreeNode = baseTreeNode; + continue; + } + + if ((baseTreeNode != treeNode) && (baseTreeNode != nullptr) && + (baseTreeNode->offsetValue > 0)) { + baseConstructor = GetConstructorFunctionInternal(env, baseTreeNode, + baseInstanceMethodsCallbackData); + + + if (baseConstructor != nullptr) { + napi_util::napi_inherits(env, constructor, baseConstructor); + } + } else { + baseConstructor = nullptr; + } + break; + } + + // 3. Define the class members now. + auto instanceMethodData = node->SetClassMembers(env, constructor, + instanceMethodsCallbackData, + baseInstanceMethodsCallbackData, treeNode); + + if (!skippedBaseTypes.empty()) { + // If there is a mismatch between base type of this class in metadata compared to the class + // at runtime, we will add methods of base class to this class's prototype. + node->SetMissingBaseMethods(env, skippedBaseTypes, instanceMethodData, constructor); + } + + + SetInnerTypes(env, constructor, treeNode); + + napi_ref constructorRef = napi_util::make_ref(env, constructor); + + if (baseConstructor != nullptr && !napi_util::is_undefined(env, baseConstructor)) { + napi_util::setPrototypeOf(env, constructor, baseConstructor); + } + + CtorCacheData ctorCacheItem(constructorRef, instanceMethodsCallbackData); + cache->CtorFuncCache.emplace(treeNode, ctorCacheItem); + + return constructor; +} + +void MetadataNode::SetInnerTypes(napi_env env, napi_value constructor, MetadataTreeNode *treeNode) { + if (treeNode->children != nullptr) { + const auto &children = *treeNode->children; + std::vector childNames(children.size()); + + for (auto curChild: children) { + bool hasOwnProperty = false; + napi_value childName; + napi_create_string_utf8(env, curChild->name.c_str(), curChild->name.size(), &childName); + napi_has_own_property(env, constructor, childName, &hasOwnProperty); + if (!hasOwnProperty) { + napi_util::define_property(env, constructor, curChild->name.c_str(), nullptr, + InnerTypeGetterCallback, nullptr, curChild); + } + } + } +} + +napi_value MetadataNode::InnerTypeGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + try { + auto curChild = reinterpret_cast(data); + auto childNode = GetOrCreateInternal(curChild); + auto cache = GetMetadataNodeCache(env); + auto itFound = cache->CtorFuncCache.find(childNode->m_treeNode); + if (itFound != cache->CtorFuncCache.end()) { + auto value = napi_util::get_ref_value(env, itFound->second.constructorFunction); + if (!napi_util::is_null_or_undefined(env, value)) return value; + } + napi_value constructor = childNode->GetConstructorFunction(env); + return constructor; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +MetadataReader *MetadataNode::getMetadataReader() { + return &MetadataNode::s_metadataReader; +} + +napi_value MetadataNode::NullObjectAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + try { + + bool value; + napi_value nullNodeKey; + napi_create_string_utf8(env, PROP_KEY_NULL_NODE_NAME, NAPI_AUTO_LENGTH, &nullNodeKey); + napi_has_own_property(env, jsThis, nullNodeKey, &value); + + if (!value) { + auto node = reinterpret_cast(data); + napi_value external; + napi_create_external(env, node, [](napi_env env, void *d1, void *d2) {}, node, + &external); + napi_set_named_property(env, jsThis, PROP_KEY_NULL_NODE_NAME, external); + + napi_util::napi_set_function(env, + jsThis, + "valueOf", MetadataNode::NullValueOfCallback); + } + + return jsThis; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::NullValueOfCallback(napi_env env, napi_callback_info info) { + napi_value nullValue; + napi_get_null(env, &nullValue); + return nullValue; +} + +napi_value MetadataNode::FieldAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0); + try { + auto fieldData = reinterpret_cast(data); + auto &fieldMetadata = fieldData->metadata; + + if (fieldMetadata.getDeclaringType().empty()) { + return UNDEFINED; + } + + if (!fieldMetadata.isStatic) { + bool is__this__ = false; + napi_has_named_property(env, jsThis, "__napi::this", &is__this__); + if (!is__this__) { + napi_value constructor; + napi_value prototype; + napi_get_named_property(env, jsThis, "constructor", &constructor); + if (!napi_util::is_null_or_undefined(env, constructor)) { + napi_get_named_property(env, constructor, "prototype", &prototype); + bool isHolder; + napi_strict_equals(env, prototype, jsThis, &isHolder); + if (isHolder) { + return UNDEFINED; + } else { + napi_set_named_property(env, jsThis, "__napi::this", + napi_util::get_true(env)); + } + } + } + } + + return CallbackHandlers::GetJavaField(env, jsThis, fieldData); + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return UNDEFINED; +} +napi_ref propRef = nullptr; +napi_value MetadataNode::FieldAccessorSetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + try { + auto fieldData = reinterpret_cast(data); + auto &fieldMetadata = fieldData->metadata; + + if (!fieldMetadata.isStatic) { + bool is__this__ = false; + napi_has_named_property(env, jsThis, "__napi::this", &is__this__); + if (!is__this__) { + napi_value constructor; + napi_value prototype; + napi_get_named_property(env, jsThis, "constructor", &constructor); + if (!napi_util::is_null_or_undefined(env, constructor)) { + napi_get_named_property(env, constructor, "prototype", &prototype); + bool isHolder; + napi_strict_equals(env, prototype, jsThis, &isHolder); + if (isHolder) { + return UNDEFINED; + } else { + napi_set_named_property(env, jsThis, "__napi::this", + napi_util::get_true(env)); + } + } + } + } + + if (fieldMetadata.getIsFinal()) { + stringstream ss; + ss << "You are trying to set \"" << fieldMetadata.getName() + << "\" which is a final field! Final fields can only be read."; + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } else { + CallbackHandlers::SetJavaField(env, jsThis, argv[0], fieldData); + return argv[0]; + } + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return UNDEFINED; +} + +napi_value MetadataNode::PropertyAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + + try { + auto propertyCallbackData = reinterpret_cast(data); + + if (propertyCallbackData->getterMethodName.empty()) { + return nullptr; + } + + napi_value getter; + napi_get_named_property(env, jsThis, propertyCallbackData->getterMethodName.c_str(), + &getter); + + napi_value result; + napi_call_function(env, jsThis, getter, 0, nullptr, &result); + return result; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::PropertyAccessorSetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + try { + auto propertyCallbackData = reinterpret_cast(data); + + if (propertyCallbackData->setterMethodName.empty()) { + return nullptr; + } + + napi_value setter; + napi_get_named_property(env, jsThis, propertyCallbackData->setterMethodName.c_str(), + &setter); + + napi_value result; + napi_call_function(env, jsThis, setter, 1, &argv[0], &result); + + return result; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ExtendMethodCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS() + + try { + napi_value extendName; + napi_value implementationObject; + string extendLocation; + + auto hasDot = false; + auto isTypeScriptExtend = false; + + if (argc == 2) { + if (!napi_util::is_of_type(env, argv[0], napi_string)) { + stringstream ss; + ss << "Invalid extend() call. No name for extend specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + if (!napi_util::is_of_type(env, argv[1], napi_object)) { + stringstream ss; + ss << "Invalid extend() call. No implementation object specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + string strName = napi_util::get_string_value(env, argv[0]); + hasDot = strName.find('.') != string::npos; + } else if (argc == 3) { + if (napi_util::is_of_type(env, argv[2], napi_boolean)) { + napi_get_value_bool(env, argv[2], &isTypeScriptExtend); + }; + } + + auto node = reinterpret_cast(data); + + if (hasDot) { + extendName = argv[0]; + implementationObject = argv[1]; + } else { + bool validExtend = GetExtendLocation(env, extendLocation, isTypeScriptExtend); + napi_create_string_utf8(env, "", 0, &extendName); + auto validArgs = ValidateExtendArguments(env, argc, argv.data(), validExtend, + extendLocation, + &extendName, &implementationObject, + isTypeScriptExtend); + if (!validArgs) { + return nullptr; + } + } + + + string extendNameAndLocation = + extendLocation + ArgConverter::ConvertToString(env, extendName); + string fullClassName; + string baseClassName = node->m_name; + if (!hasDot) { + fullClassName = TNS_PREFIX + CreateFullClassName(baseClassName, extendNameAndLocation); + } else { + fullClassName = ArgConverter::ConvertToString(env, argv[0]); + } + + uint8_t nodeType = s_metadataReader.GetNodeType(node->m_treeNode); + bool isInterface = s_metadataReader.IsNodeTypeInterface(nodeType); + auto clazz = CallbackHandlers::ResolveClass(env, baseClassName, fullClassName, + implementationObject, isInterface); + auto fullExtendedName = CallbackHandlers::ResolveClassName(env, clazz); + + auto cachedData = GetCachedExtendedClassData(env, fullExtendedName); + if (cachedData.extendedCtorFunction != nullptr) { + auto value = napi_util::get_ref_value(env, cachedData.extendedCtorFunction); + if (!napi_util::is_null_or_undefined(env, value)) return value; + } + + napi_value implementationObjectName; + napi_get_named_property(env, implementationObject, CLASS_IMPLEMENTATION_OBJECT, + &implementationObjectName); + + if (napi_util::is_null_or_undefined(env, implementationObjectName)) { + napi_set_named_property(env, implementationObject, CLASS_IMPLEMENTATION_OBJECT, + ArgConverter::convertToJsString(env, fullExtendedName)); + } else { + string usedClassName = ArgConverter::ConvertToString(env, implementationObjectName); + stringstream s; + s << "This object is used to extend another class '" << usedClassName << "'"; + throw NativeScriptException(s.str()); + } + + auto baseClassCtorFunction = node->GetConstructorFunction(env); + + auto extendedClassCallbackData = new ExtendedClassCallbackData( + node, extendNameAndLocation, napi_util::make_ref(env, implementationObject), + fullClassName); + GetMetadataNodeCache(env)->extendedClassCallbackData.push_back(extendedClassCallbackData); + + napi_value extendFuncCtor; + napi_define_class(env, fullExtendedName.c_str(), NAPI_AUTO_LENGTH, + MetadataNode::ExtendedClassConstructorCallback, + extendedClassCallbackData, 0, nullptr, + &extendFuncCtor); + napi_value extendFuncPrototype = napi_util::get_prototype(env, extendFuncCtor); + ObjectManager::MarkObject(env, extendFuncPrototype); + + napi_util::setPrototypeOf(env, implementationObject, + napi_util::get_prototype(env, baseClassCtorFunction)); + + napi_util::define_property( + env, implementationObject, PROP_KEY_SUPER, nullptr, SuperAccessorGetterCallback, + nullptr, nullptr); + + napi_util::setPrototypeOf(env, extendFuncPrototype, implementationObject); + + napi_util::setPrototypeOf(env, extendFuncCtor, baseClassCtorFunction); + + SetClassAccessor(env, extendFuncCtor); + + napi_set_named_property(env, extendFuncCtor, PRIVATE_TYPE_NAME, + ArgConverter::convertToJsString(env, fullExtendedName)); + + s_name2NodeCache.emplace(fullExtendedName, node); + + ExtendedClassCacheData cacheData(napi_util::make_ref(env, extendFuncCtor), fullExtendedName, + node); + auto cache = GetMetadataNodeCache(env); + cache->ExtendedCtorFuncCache.emplace(fullExtendedName, cacheData); + + return extendFuncCtor; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + + +napi_value MetadataNode::SuperAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + + try { + + napi_value superValue; + napi_get_named_property(env, jsThis, PROP_KEY_SUPERVALUE, &superValue); + + if (napi_util::is_null_or_undefined(env, superValue)) { + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + superValue = objectManager->GetEmptyObject(); + + napi_delete_property(env, superValue, + ArgConverter::convertToJsString(env, PROP_KEY_TOSTRING), nullptr); + napi_delete_property(env, superValue, + ArgConverter::convertToJsString(env, PROP_KEY_VALUEOF), nullptr); + ObjectManager::MarkSuperCall(env, superValue); + + napi_value superProto = napi_util::getPrototypeOf(env, napi_util::getPrototypeOf(env, + napi_util::getPrototypeOf( + env, + jsThis))); + + napi_util::setPrototypeOf(env, superValue, superProto); + objectManager->CloneLink(jsThis, superValue); + auto node = GetInstanceMetadata(env, jsThis); + SetInstanceMetadata(env, superValue, node); + + int javaObjectID = -1; + objectManager->GetJavaObjectByJsObject(jsThis, &javaObjectID); + if (javaObjectID != -1) { + superValue = objectManager->GetOrCreateProxyWeak(javaObjectID, superValue); + } + napi_set_named_property(env, jsThis, PROP_KEY_SUPERVALUE, superValue); + } + + return superValue; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::MethodCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS() + + try { + MetadataEntry *entry = nullptr; + + auto callbackData = reinterpret_cast(data); + auto initialCallbackData = reinterpret_cast(data); + + string *className; + auto &first = callbackData->candidates.front(); + auto &methodName = first.getName(); + + while ((callbackData != nullptr) && (entry == nullptr)) { + auto &candidates = callbackData->candidates; + + className = &callbackData->node->m_name; + + // Iterates through all methods and finds the best match based on the number of arguments + auto found = false; + for (auto &c: candidates) { + found = (!c.isExtensionFunction && c.getParamCount() == argc) || + (c.isExtensionFunction && c.getParamCount() == argc + 1); + if (found) { + if (c.isExtensionFunction) { + className = &c.getDeclaringType(); + } + entry = &c; + DEBUG_WRITE("MetaDataEntry Method %s's signature is: %s", + entry->getName().c_str(), + entry->getSig().c_str()); + break; + } + } + + // Iterates through the parent class's methods to find a good match + if (!found) { + callbackData = callbackData->parent; + } + } + + + if (argc == 0 && methodName == PROP_KEY_VALUEOF) { + return jsThis; + } else { +// Runtime::GetRuntime(env)->clearPendingError(); + bool isFromInterface = initialCallbackData->node->IsNodeTypeInterface(); + napi_value result = CallbackHandlers::CallJavaMethod(env, jsThis, *className, methodName, entry, + isFromInterface, first.isStatic, info, + argc, argv.data()); +// napi_value error; +// error = Runtime::GetRuntime(env)->getPendingError(); +// if (error) { +// throw NativeScriptException(env, error); +// } + return result; + } + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +/** + * Compare class hierarchy in metadata with that at runtime. If a base class is missing + * at runtime, we must add all it's methods to the current class. + */ +bool +MetadataNode::CheckClassHierarchy(JEnv &env, jclass currentClass, MetadataTreeNode *currentTreeNode, + MetadataTreeNode *baseTreeNode, + std::vector &skippedBaseTypes) { + auto shouldSkipBaseClass = false; + if ((currentClass != nullptr) && (baseTreeNode != currentTreeNode) && + (baseTreeNode != nullptr) && + (baseTreeNode->offsetValue > 0)) { + auto baseNode = GetOrCreateInternal(baseTreeNode); + auto baseClass = env.FindClass(baseNode->m_name); + if (baseClass != nullptr) { + auto isBaseClass = env.IsAssignableFrom(currentClass, baseClass) == JNI_TRUE; + if (!isBaseClass) { + skippedBaseTypes.push_back(baseTreeNode); + shouldSkipBaseClass = true; + } + } + } + return shouldSkipBaseClass; +} + +void MetadataNode::SetMissingBaseMethods( + napi_env env, const std::vector &skippedBaseTypes, + const std::vector &instanceMethodData, + napi_value constructor) { + for (auto treeNode: skippedBaseTypes) { + uint8_t *curPtr = s_metadataReader.GetValueData() + treeNode->offsetValue + 1; + + auto nodeType = s_metadataReader.GetNodeType(treeNode); + auto curType = s_metadataReader.ReadTypeName(treeNode); + curPtr += sizeof(uint16_t /* baseClassId */); + + if (s_metadataReader.IsNodeTypeInterface(nodeType)) { + curPtr += sizeof(uint8_t) + sizeof(uint32_t); + } + + // Get candidates from instance methods metadata + auto instanceMethodCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + MethodCallbackData *callbackData = nullptr; + + for (auto i = 0; i < instanceMethodCount; i++) { + auto entry = MetadataReader::ReadInstanceMethodEntry(&curPtr); + auto &methodName = entry.getName(); + auto isConstructor = methodName == ""; + if (isConstructor) { + continue; + } + + callbackData = nullptr; + for (auto data: instanceMethodData) { + if (data->candidates.front().name == methodName) { + callbackData = data; + break; + } + } + + if (callbackData == nullptr) { + callbackData = new MethodCallbackData(this); + MetadataNode::GetMetadataNodeCache(env)->methodCallbackData.push_back(callbackData); + napi_value proto = napi_util::get_prototype(env, constructor); + napi_value method; + napi_create_function(env, methodName.c_str(), NAPI_AUTO_LENGTH, MethodCallback, + callbackData, &method); + napi_set_named_property(env, proto, methodName.c_str(), method); + } + + bool foundSameSig = false; + for (auto &m: callbackData->candidates) { + foundSameSig = m.getSig() == entry.getSig(); + if (foundSameSig) { + break; + } + } + + if (!foundSameSig) { + callbackData->candidates.push_back(std::move(entry)); + } + } + } +} + +void MetadataNode::BuildMetadata(const std::string &filesPath) { + s_metadataReader = MetadataBuilder::BuildMetadata(filesPath); +} + +void MetadataNode::onDisposeEnv(napi_env env) { + { + auto it = s_metadata_node_cache.Get(env); + if (it != nullptr) { + for (const auto &entry: it->CtorFuncCache) { + if (entry.second.constructorFunction != nullptr) { + napi_delete_reference(env, entry.second.constructorFunction); + } + } + it->CtorFuncCache.clear(); + + for (const auto &entry: it->ExtendedCtorFuncCache) { + if (entry.second.extendedCtorFunction != nullptr) { + napi_delete_reference(env, entry.second.extendedCtorFunction); + } + } + it->ExtendedCtorFuncCache.clear(); + + for (const auto data: it->methodCallbackData) { + delete data; + } + it->methodCallbackData.clear(); + + for (const auto &entry: it->fieldCallbackData) { + delete entry; + } + it->fieldCallbackData.clear(); + + for (const auto data: it->propertyCallbackData) { + delete data; + } + it->propertyCallbackData.clear(); + + for (const auto data: it->extendedClassCallbackData) { + if (data->implementationObject != nullptr) { + napi_delete_reference(env, data->implementationObject); + } + delete data; + } + it->extendedClassCallbackData.clear(); + } + s_metadata_node_cache.Remove(env); + delete it; + } + { + auto it = s_arrayObjects.find(env); + if (it != s_arrayObjects.end()) { + if (it->second != nullptr) { + napi_delete_reference(env, it->second); + } + s_arrayObjects.erase(it); + } + } +} + + +string MetadataNode::TNS_PREFIX = "com/tns/gen/"; +MetadataReader MetadataNode::s_metadataReader; +robin_hood::unordered_map MetadataNode::s_name2NodeCache; +robin_hood::unordered_map MetadataNode::s_name2TreeNodeCache; +robin_hood::unordered_map MetadataNode::s_treeNode2NodeCache; +tns::ConcurrentMap MetadataNode::s_metadata_node_cache; +robin_hood::unordered_map MetadataNode::s_arrayObjects; + +bool MetadataNode::s_profilerEnabled = false; diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataNode.h b/NativeScript/ffi/jni/napi/metadata/MetadataNode.h new file mode 100644 index 000000000..2813c2ee1 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataNode.h @@ -0,0 +1,307 @@ +#ifndef METADATA_NODE_H +#define METADATA_NODE_H + +#include +#include "MetadataTreeNode.h" +#include "MetadataEntry.h" +#include "robin_hood.h" +#include "MetadataReader.h" +#include "Runtime.h" + +#include "FieldCallbackData.h" +using namespace tns; + +class MetadataNode { +public: + static void Init(napi_env env); + + static void BuildMetadata(const std::string &filesPath); + + static void CreateTopLevelNamespaces(napi_env env); + + napi_value CreateWrapper(napi_env env); + + napi_value CreateJSWrapper(napi_env env, tns::ObjectManager *objectManager); + + napi_value CreateArrayWrapper(napi_env env); + + static MetadataNode *GetOrCreate(const std::string &className); + + static MetadataReader *getMetadataReader(); + + static napi_value GetImplementationObject(napi_env env, napi_value object); + + inline static MetadataNode* GetInstanceMetadata(napi_env env, napi_value object) { + void *node; + napi_value external; + napi_get_named_property(env, object, "#instance_metadata", &external); + + if (napi_util::is_null_or_undefined(env, external)) return nullptr; + + napi_get_value_external(env, external, &node); + if (node == nullptr) + return nullptr; + return reinterpret_cast(node); + } + + inline static MetadataNode* GetNodeFromHandle(napi_env env, napi_value value) { + auto node = GetInstanceMetadata(env, value); + return node; + } + + static string GetTypeMetadataName(napi_env env, napi_value value); + + static napi_value CreateExtendedJSWrapper(napi_env env, ObjectManager *objectManager, + const std::string &proxyClassName, int javaObjectID); + + std::string GetName(); + + static void onDisposeEnv(napi_env env); + + bool isArray(); + +private: + struct CtorCacheData; + struct PackageGetterMethodData; + struct MethodCallbackData; + struct ExtendedClassCallbackData; + struct ExtendedClassCacheData; + struct MetadataNodeCache; + + static string CreateFullClassName(const std::string& className, const std::string& extendNameAndLocation); + + static napi_value CreateArrayObjectConstructor(napi_env env); + + static void SetInstanceMetadata(napi_env env, napi_value object, MetadataNode* node); + + + static bool + CheckClassHierarchy(JEnv &env, jclass currentClass, MetadataTreeNode *currentTreeNode, + MetadataTreeNode *baseTreeNode, + std::vector &skippedBaseTypes); + + static MetadataNode *GetOrCreateInternal(MetadataTreeNode *treeNode); + + static MetadataNodeCache *GetMetadataNodeCache(napi_env env); + + explicit MetadataNode(MetadataTreeNode *treeNode); + + void SetMissingBaseMethods( + napi_env env, const std::vector &skippedBaseTypes, + const std::vector &instanceMethodData, + napi_value ctor); + + + + + napi_value GetConstructorFunction(napi_env env); + + napi_value GetConstructorFunctionInternal(napi_env env, MetadataTreeNode *treeNode, + std::vector instanceMethodsCallbackData); + + napi_value CreatePackageObject(napi_env env); + + + static bool IsValidExtendName(napi_env env, napi_value name); + static bool GetExtendLocation(napi_env env, std::string& extendLocation, bool isTypeScriptExtend); + static ExtendedClassCacheData GetCachedExtendedClassData(napi_env env, const std::string& proxyClassName); + static std::string GetJniClassName(const MetadataTreeNode* node); + + + static void SetClassAccessor(napi_env env, napi_value constructor); + + static MetadataEntry GetChildMetadataForPackage(MetadataNode *node, const char *propName); + + static MetadataTreeNode *GetOrCreateTreeNodeByName(const std::string &className); + + bool IsNodeTypeInterface(); + + std::vector SetClassMembersFromStaticMetadata( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode); + + std::vector SetInstanceMembersFromRuntimeMetadata( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode); + + + inline static MethodCallbackData *tryGetExtensionMethodCallbackData( + const robin_hood::unordered_map &collectedMethodCallbackData, + const std::string &lookupName); + + std::vector SetClassMembers( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode); + + + static napi_value NullObjectAccessorGetterCallback(napi_env env, napi_callback_info info); + + static napi_value FieldAccessorGetterCallback(napi_env env, napi_callback_info info); + + static napi_value FieldAccessorSetterCallback(napi_env env, napi_callback_info info); + + static napi_value ArraySetterCallback(napi_env env, napi_callback_info info); + + static napi_value ArrayGetterCallback(napi_env env, napi_callback_info info); + + static napi_value ArrayGetAllValuesCallback(napi_env env, napi_callback_info info); + + static napi_value ArrayLengthCallback(napi_env env, napi_callback_info info); + + static napi_value PropertyAccessorGetterCallback(napi_env env, napi_callback_info info); + + static napi_value PropertyAccessorSetterCallback(napi_env env, napi_callback_info info); + + static napi_value ExtendMethodCallback(napi_env env, napi_callback_info info); + + static napi_value MethodCallback(napi_env env, napi_callback_info info); + + static napi_value ClassAccessorGetterCallback(napi_env env, napi_callback_info info); + + static napi_value PackageGetterCallback(napi_env env, napi_callback_info info); + + static napi_value ExtendedClassConstructorCallback(napi_env env, napi_callback_info info); + + static napi_value InterfaceConstructorCallback(napi_env env, napi_callback_info info); + + static napi_value ClassConstructorCallback(napi_env env, napi_callback_info info); + + static void SetInnerTypes(napi_env env, napi_value constructor, MetadataTreeNode *treeNode); + + static napi_value InnerTypeGetterCallback(napi_env env, napi_callback_info info); + + static napi_value NullValueOfCallback(napi_env env, napi_callback_info info); + + + static void RegisterSymbolHasInstanceCallback(napi_env env, const MetadataTreeNode *treeNode, napi_value interface); + + static napi_value SymbolHasInstanceCallback(napi_env env, napi_callback_info info); + + static napi_value SuperAccessorGetterCallback(napi_env env, napi_callback_info info); + + static bool ValidateExtendArguments(napi_env env, size_t argc, napi_value * argv, bool extendLocationFound, string &extendLocation, napi_value* extendName, napi_value* implementationObject, bool isTypeScriptExtend); + + MetadataTreeNode *m_treeNode; + + std::string m_name; + std::string m_implType; + bool m_isArray; + + static bool IsJavascriptKeyword(const std::string &word); + + static std::string TNS_PREFIX; + static MetadataReader s_metadataReader; + + static robin_hood::unordered_map s_name2NodeCache; + static robin_hood::unordered_map s_name2TreeNodeCache; + static robin_hood::unordered_map s_treeNode2NodeCache; + static tns::ConcurrentMap s_metadata_node_cache; + static robin_hood::unordered_map s_arrayObjects; + + struct CtorCacheData { + CtorCacheData(napi_ref _constructorFunction, + std::vector _instanceMethodCallbacks) + : + constructorFunction(_constructorFunction), + instanceMethodCallbacks(_instanceMethodCallbacks) { + } + + napi_ref constructorFunction; + std::vector instanceMethodCallbacks; + }; + + struct MethodCallbackData { + MethodCallbackData() + : + node(nullptr), parent(nullptr), isSuper(false) { + } + + MethodCallbackData(MetadataNode *_node) + : + node(_node), parent(nullptr), isSuper(false) { + } + + std::vector candidates; + MetadataNode *node; + MethodCallbackData *parent; + bool isSuper; + }; + + struct PackageGetterMethodData { + PackageGetterMethodData() : utf8name(nullptr), node(nullptr), value(nullptr) {} + + PackageGetterMethodData(const char *_utf8name, MetadataNode *_node, napi_ref _value) + : utf8name(_utf8name), node(_node), value(_value) {} + + const char *utf8name; + MetadataNode *node; + napi_ref value; + }; + + struct ExtendedClassCacheData { + ExtendedClassCacheData() + : + extendedCtorFunction(nullptr), node(nullptr) { + } + + ExtendedClassCacheData(napi_ref extCtorFunc, const std::string &_extendedName, + MetadataNode *_node) + : + extendedName(_extendedName), node(_node) { + extendedCtorFunction = extCtorFunc; + } + + napi_ref extendedCtorFunction; + std::string extendedName; + MetadataNode *node; + }; + + struct PropertyCallbackData { + PropertyCallbackData(std::string _propertyName, std::string _getterMethodName, + std::string _setterMethodName) + : + propertyName(_propertyName), getterMethodName(_getterMethodName), + setterMethodName(_setterMethodName) { + + } + + std::string propertyName; + std::string getterMethodName; + std::string setterMethodName; + }; + + struct ExtendedClassCallbackData { + ExtendedClassCallbackData(MetadataNode *_node, const std::string &_extendedName, + napi_ref _implementationObject, std::string _fullClassName) + : + node(_node), extendedName(_extendedName), fullClassName(_fullClassName) { + implementationObject = _implementationObject; + } + + MetadataNode *node; + std::string extendedName; + napi_ref implementationObject; + + std::string fullClassName; + }; + + struct MetadataNodeCache { + robin_hood::unordered_map CtorFuncCache; + robin_hood::unordered_map ExtendedCtorFuncCache; + std::vector methodCallbackData; + std::vector fieldCallbackData; + std::vector propertyCallbackData; + std::vector extendedClassCallbackData; + }; + + static bool s_profilerEnabled; + +}; + +#endif //METADATA_NODE_H diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp new file mode 100644 index 000000000..af2020493 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp @@ -0,0 +1,364 @@ +#include "MetadataReader.h" +#include "MetadataMethodInfo.h" +#include +#include "Util.h" +#include + +using namespace std; +using namespace tns; + +MetadataReader::MetadataReader() : m_root(nullptr), m_nodesLength(0), m_nameLength(0), + m_valueLength(0), + m_nodeData(nullptr), m_nameData(nullptr), m_valueData(nullptr), + m_getTypeMetadataCallback(nullptr) {} + +MetadataReader::MetadataReader(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData, + GetTypeMetadataCallback getTypeMetadataCallback) + : + m_nodesLength(nodesLength), m_nameLength(nameLength), + m_valueLength(valueLength), m_nodeData(nodeData), m_nameData(nameData), + m_valueData(valueData), + m_getTypeMetadataCallback(getTypeMetadataCallback) { + m_root = BuildTree(); +} + + + +// helper debug function when need to convert a metadata node to its full name +//std::string toFullName(MetadataTreeNode* p) { +// std::string final = p->name; +// while((p = p->parent) && !p->name.empty()) { +// final.insert(0,p->name + "."); +// }; +// return final; +//} + +MetadataTreeNode *MetadataReader::BuildTree() { + MetadataTreeNodeRawData *rootNodeData = reinterpret_cast(m_nodeData); + + MetadataTreeNodeRawData *curNodeData = rootNodeData; + + int len = m_nodesLength / sizeof(MetadataTreeNodeRawData); + + m_v.resize(len + 1000); + MetadataTreeNode *emptyNode = nullptr; + fill(m_v.begin(), m_v.end(), emptyNode); + + for (int i = 0; i < len; i++) { + MetadataTreeNode *node = GetNodeById(i); + if (nullptr == node) { + node = new MetadataTreeNode; + node->name = ReadName(curNodeData->offsetName); + node->offsetValue = curNodeData->offsetValue; + m_v[i] = node; + } + + uint16_t curNodeDataId = curNodeData - rootNodeData; + + if (curNodeDataId != curNodeData->firstChildId) { + node->children = new vector; + MetadataTreeNodeRawData *childNodeData = rootNodeData + curNodeData->firstChildId; + while (true) { + + uint16_t childNodeDataId = childNodeData - rootNodeData; + + MetadataTreeNode *childNode; + // node (and its next siblings) already visited, so we don't need to visit it again + if (m_v[childNodeDataId] != emptyNode) { + childNode = m_v[childNodeDataId]; + __android_log_print(ANDROID_LOG_ERROR, "TNS.error", + "Consistency error in metadata. A child should never have been visited before its parent. Parent: %s Child: %s. Child metadata id: %u", + node->name.c_str(), childNode->name.c_str(), + childNodeDataId); + break; + } else { + childNode = new MetadataTreeNode; + childNode->name = ReadName(childNodeData->offsetName); + childNode->offsetValue = childNodeData->offsetValue; + } + childNode->parent = node; + + node->children->push_back(childNode); + + m_v[childNodeDataId] = childNode; + + if (childNodeDataId == childNodeData->nextSiblingId) { + break; + } + + childNodeData = rootNodeData + childNodeData->nextSiblingId; + } + } + + curNodeData++; + } + + return GetNodeById(0); +} + +MetadataTreeNode *MetadataReader::GetNodeById(uint16_t nodeId) { + return m_v[nodeId]; +} + + +string MetadataReader::ReadTypeName(MetadataTreeNode *treeNode) { + string name; + + auto itFound = m_typeNameCache.find(treeNode); + + if (itFound != m_typeNameCache.end()) { + name = itFound->second; + } else { + name = ReadTypeNameInternal(treeNode); + + m_typeNameCache.emplace(treeNode, name); + } + + return name; +} + +string MetadataReader::ReadTypeNameInternal(MetadataTreeNode *treeNode) { + string name; + + uint8_t prevNodeType; + + while (treeNode->parent != nullptr) { + int curNodeType = GetNodeType(treeNode); + + bool isArrayElement = treeNode->offsetValue > ARRAY_OFFSET; + + if (isArrayElement) { + uint16_t forwardNodeId = treeNode->offsetValue - ARRAY_OFFSET; + MetadataTreeNode *forwardNode = GetNodeById(forwardNodeId); + name = ReadTypeName(forwardNode); + uint8_t forwardNodeType = GetNodeType(forwardNode); + if (IsNodeTypeInterface(forwardNodeType) || IsNodeTypeClass(forwardNodeType)) { + name = "L" + name + ";"; + } + } else { + if (!name.empty()) { + if (!IsNodeTypeArray(curNodeType)) { + if ((IsNodeTypeClass(prevNodeType) || IsNodeTypeInterface(prevNodeType)) + && (IsNodeTypeClass(curNodeType) || IsNodeTypeInterface(curNodeType))) { + name = "$" + name; + } else { + name = "/" + name; + } + } + } + + name = treeNode->name + name; + + prevNodeType = curNodeType; + } + + treeNode = treeNode->parent; + } + + return name; +} + +uint8_t *MetadataReader::GetValueData() const { + return m_valueData; +} + +uint16_t MetadataReader::GetNodeId(MetadataTreeNode *treeNode) { + auto itFound = find(m_v.begin(), m_v.end(), treeNode); + assert(itFound != m_v.end()); + uint16_t nodeId = itFound - m_v.begin(); + + return nodeId; +} + +MetadataTreeNode *MetadataReader::GetRoot() const { + return m_root; +} + +uint8_t MetadataReader::GetNodeType(MetadataTreeNode *treeNode) { + if (treeNode->type == MetadataTreeNode::INVALID_TYPE) { + uint8_t nodeType; + + uint32_t offsetValue = treeNode->offsetValue; + + if (offsetValue == 0) { + nodeType = MetadataTreeNode::PACKAGE; + } else if ((0 < offsetValue) && (offsetValue < ARRAY_OFFSET)) { + nodeType = *(m_valueData + offsetValue); + } else if (offsetValue == ARRAY_OFFSET) { + nodeType = MetadataTreeNode::ARRAY; + } else { + uint16_t nodeId = offsetValue - ARRAY_OFFSET; + MetadataTreeNode *arrElemNode = GetNodeById(nodeId); + nodeType = *(m_valueData + arrElemNode->offsetValue); + } + + treeNode->type = nodeType; + } + + return treeNode->type; +} + +MetadataTreeNode *MetadataReader::GetOrCreateTreeNodeByName(const string &className) { + MetadataTreeNode *treeNode = GetRoot(); + + int arrayIdx = -1; + string arrayName = "["; + + while (className[++arrayIdx] == '[') { + MetadataTreeNode *child = treeNode->GetChild(arrayName); + + if (child == nullptr) { + vector *children = treeNode->children; + if (children == nullptr) { + children = treeNode->children = new vector; + } + + child = new MetadataTreeNode; + child->name = "["; + child->parent = treeNode; + child->offsetValue = ARRAY_OFFSET; + + children->push_back(child); + m_v.push_back(child); + } + + treeNode = child; + } + + string cn = className.substr(arrayIdx); + + if (arrayIdx > 0) { + char last = *cn.rbegin(); + if (last == ';') { + cn = cn.substr(1, cn.length() - 2); + } + } + + vector names; + Util::SplitString(cn, "/$", names); + + if (arrayIdx > 0) { + bool found = false; + MetadataTreeNode *forwardedNode = GetOrCreateTreeNodeByName(cn); + + uint16_t forwardedNodeId = GetNodeId(forwardedNode); + if (treeNode->children == nullptr) { + treeNode->children = new vector(); + } + vector &children = *treeNode->children; + for (auto childNode: children) { + uint32_t childNodeId = (childNode->offsetValue >= ARRAY_OFFSET) + ? (childNode->offsetValue - ARRAY_OFFSET) + : + GetNodeId(childNode); + + if (childNodeId == forwardedNodeId) { + treeNode = childNode; + found = true; + break; + } + } + + if (!found) { + MetadataTreeNode *forwardNode = new MetadataTreeNode; + forwardNode->offsetValue = forwardedNodeId + ARRAY_OFFSET; + forwardNode->parent = treeNode; + + m_v.push_back(forwardNode); + children.push_back(forwardNode); + + treeNode = forwardNode; + } + + return treeNode; + } + + int curIdx = 0; + for (auto it = names.begin(); it != names.end(); ++it) { + MetadataTreeNode *child = treeNode->GetChild(*it); + + if (child == nullptr) { + vector api = m_getTypeMetadataCallback(cn, curIdx); + + for (const auto &part: api) { + vector *children = treeNode->children; + if (children == nullptr) { + children = treeNode->children = new vector; + } + + child = new MetadataTreeNode; + child->name = *it++; + child->parent = treeNode; + + string line; + string kind; + string name; + stringstream s(part); + + getline(s, line); + stringstream typeLine(line); + typeLine >> kind >> name; + auto cKind = kind[0]; + + // package, class, interface + assert((cKind == 'P') || (cKind == 'C') || (cKind == 'I')); + + if ((cKind == 'C') || (cKind == 'I')) { + child->metadata = new string(part); + child->type = (cKind == 'C') ? MetadataTreeNode::CLASS + : MetadataTreeNode::INTERFACE; + if (name == "S") { + child->type |= MetadataTreeNode::STATIC; + } + + getline(s, line); + stringstream baseClassLine(line); + baseClassLine >> kind >> name; + cKind = kind[0]; + + assert(cKind == 'B'); + auto baseClassTreeNode = GetOrCreateTreeNodeByName(name); + auto baseClassNodeId = GetNodeId(baseClassTreeNode); + + child->offsetValue = m_valueLength; + m_valueData[m_valueLength++] = child->type; + *reinterpret_cast(m_valueData + m_valueLength) = baseClassNodeId; + m_valueLength += sizeof(uint16_t); + } else { + child->type = MetadataTreeNode::PACKAGE; + } + + m_v.push_back(child); + children->push_back(child); + + treeNode = child; + } + + return treeNode; + } else { + treeNode = child; + } + ++curIdx; + } + + return treeNode; +} + +MetadataTreeNode *MetadataReader::GetBaseClassNode(MetadataTreeNode *treeNode) { + MetadataTreeNode *baseClassNode = nullptr; + + if (treeNode != nullptr) { + uint16_t baseClassNodeId = *reinterpret_cast(m_valueData + + treeNode->offsetValue + 1); + + size_t nodeCount = m_v.size(); + + assert(baseClassNodeId < nodeCount); + + baseClassNode = GetNodeById(baseClassNodeId); + } + + return baseClassNode; +} + diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataReader.h b/NativeScript/ffi/jni/napi/metadata/MetadataReader.h new file mode 100644 index 000000000..8d19683d7 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataReader.h @@ -0,0 +1,238 @@ +#ifndef METADATAREADER_H_ +#define METADATAREADER_H_ + +#include "MetadataEntry.h" +#include "MetadataFieldInfo.h" +#include +#include +#include +#include "robin_hood.h" + +namespace tns { + typedef std::vector (*GetTypeMetadataCallback)(const std::string &classname, + int index); + + class MetadataReader { + public: + MetadataReader(); + + MetadataReader(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData, + GetTypeMetadataCallback getTypeMetadataCallack); + + inline static MetadataEntry ReadInstanceFieldEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Field); + entry.fi = *reinterpret_cast(data); + entry.isStatic = false; + entry.isTypeMember = false; + + *data += sizeof(FieldInfo); + + return entry; + } + + inline static MetadataEntry ReadStaticFieldEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::StaticField); + entry.sfi = *reinterpret_cast(data); + entry.isStatic = true; + entry.isTypeMember = false; + + *data += sizeof(StaticFieldInfo); + + return entry; + } + + inline static MetadataEntry ReadInstanceMethodEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Method); + entry.isTypeMember = true; + + entry.mi = MethodInfo(*data); // Assign MethodInfo object directly + *data += entry.mi.GetSizeOfReadMethodInfo(); + + return entry; + } + + inline static MetadataEntry ReadStaticMethodEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Method); + entry.isTypeMember = true; + + entry.mi = MethodInfo(*data); // Assign MethodInfo object directly + entry.mi.isStatic = true; + entry.isStatic = true; + + *data += entry.mi.GetSizeOfReadMethodInfo(); + + return entry; + } + + inline static MetadataEntry ReadExtensionFunctionEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Method); + + entry.mi = MethodInfo(*data); // Assign MethodInfo object directly + entry.mi.isStatic = true; + entry.isExtensionFunction = true; + entry.isStatic = true; + + *data += entry.mi.GetSizeOfReadMethodInfo(); + + return entry; + } + + inline std::string ReadTypeName(uint16_t nodeId) { + MetadataTreeNode *treeNode = GetNodeById(nodeId); + + return ReadTypeName(treeNode); + } + + std::string ReadTypeName(MetadataTreeNode *treeNode); + + inline std::string ReadName(uint32_t offset) { + uint16_t length = *reinterpret_cast(m_nameData + offset); + + std::string name(reinterpret_cast(m_nameData + offset + sizeof(uint16_t)), + length); + + return name; + } + + inline std::string + ReadInterfaceImplementationTypeName(MetadataTreeNode *treeNode, bool &isPrefix) { + uint8_t *data = + m_valueData + treeNode->offsetValue + sizeof(uint8_t) + sizeof(uint16_t); + + isPrefix = *data == 1; + + uint32_t pos = *reinterpret_cast(data + sizeof(uint8_t)); + + uint16_t len = *reinterpret_cast(m_nameData + pos); + + char *ptr = reinterpret_cast(m_nameData + pos + sizeof(uint16_t)); + + std::string name(ptr, len); + + assert(name.length() == len); + + return name; + } + + uint8_t *GetValueData() const; + + uint8_t GetNodeType(MetadataTreeNode *treeNode); + + uint16_t GetNodeId(MetadataTreeNode *treeNode); + + MetadataTreeNode *GetRoot() const; + + MetadataTreeNode *GetOrCreateTreeNodeByName(const std::string &className); + + MetadataTreeNode *GetBaseClassNode(MetadataTreeNode *treeNode); + + MetadataTreeNode *GetNodeById(uint16_t nodeId); + + inline bool IsNodeTypeArray(uint8_t type) { + bool isArray = (((type & MetadataTreeNode::PRIMITIVE) == 0) && + ((type & MetadataTreeNode::ARRAY) == MetadataTreeNode::ARRAY)); + + return isArray; + } + + inline bool IsNodeTypeStatic(uint8_t type) { + bool isStatic = (type & MetadataTreeNode::STATIC) == MetadataTreeNode::STATIC; + + return isStatic; + } + + inline bool IsNodeTypeClass(uint8_t type) { + bool isClass = (((type & MetadataTreeNode::PRIMITIVE) == 0) && + ((type & MetadataTreeNode::CLASS) == MetadataTreeNode::CLASS)); + + return isClass; + } + + inline bool IsNodeTypeInterface(uint8_t type) { + bool isInterface = (((type & MetadataTreeNode::PRIMITIVE) == 0) && + ((type & MetadataTreeNode::INTERFACE) == + MetadataTreeNode::INTERFACE)); + + return isInterface; + } + + inline bool IsNodeTypePackage(uint8_t type) { + bool isPackage = type == MetadataTreeNode::PACKAGE; + + return isPackage; + } + + inline static std::string ParseReturnType(const std::string &signature) { + int idx = signature.find(')'); + auto returnType = signature.substr(idx + 1); + return returnType; + } + + inline static MethodReturnType GetReturnType(const std::string &returnType) { + MethodReturnType retType; + char retTypePrefix = returnType[0]; + switch (retTypePrefix) { + case 'V': + retType = MethodReturnType::Void; + break; + case 'B': + retType = MethodReturnType::Byte; + break; + case 'S': + retType = MethodReturnType::Short; + break; + case 'I': + retType = MethodReturnType::Int; + break; + case 'J': + retType = MethodReturnType::Long; + break; + case 'F': + retType = MethodReturnType::Float; + break; + case 'D': + retType = MethodReturnType::Double; + break; + case 'C': + retType = MethodReturnType::Char; + break; + case 'Z': + retType = MethodReturnType::Boolean; + break; + case '[': + case 'L': + retType = (returnType == "Ljava/lang/String;") + ? MethodReturnType::String + : MethodReturnType::Object; + break; + default: + assert(false); + break; + } + return retType; + } + + private: +// static const uint32_t ARRAY_OFFSET = 1000000000; + static const uint32_t ARRAY_OFFSET = INT32_MAX; // 2147483647 + + MetadataTreeNode *BuildTree(); + + std::string ReadTypeNameInternal(MetadataTreeNode *treeNode); + + MetadataTreeNode *m_root; + uint32_t m_nodesLength; + uint32_t m_nameLength; + uint32_t m_valueLength; + uint8_t *m_nodeData; + uint8_t *m_nameData; + uint8_t *m_valueData; + std::vector m_v; + GetTypeMetadataCallback m_getTypeMetadataCallback; + + robin_hood::unordered_map m_typeNameCache; + }; +} + +#endif /* METADATAREADER_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.cpp new file mode 100644 index 000000000..d8f909084 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.cpp @@ -0,0 +1,26 @@ +#include "MetadataTreeNode.h" + +using namespace std; +using namespace tns; + +MetadataTreeNode::MetadataTreeNode() + : + children(nullptr), parent(nullptr), metadata(nullptr), offsetValue(0), type(INVALID_TYPE) { +} + +MetadataTreeNode* MetadataTreeNode::GetChild(const string& childName) { + MetadataTreeNode* child = nullptr; + + if (children != nullptr) { + auto itEnd = children->end(); + auto itFound = find_if(children->begin(), itEnd, [&childName] (MetadataTreeNode *x) { + return x->name == childName; + }); + if (itFound != itEnd) { + child = *itFound; + } + } + + return child; +} + diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h new file mode 100644 index 000000000..e30cfb1ea --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h @@ -0,0 +1,49 @@ +#ifndef TREENODE_H_ +#define TREENODE_H_ + +#include +#include + +namespace tns { +struct MetadataTreeNode { + MetadataTreeNode(); + + MetadataTreeNode* GetChild(const std::string& name); + + std::string name; + MetadataTreeNode* parent; + uint32_t offsetValue; + std::vector* children; + // + std::string* metadata; + uint8_t type; + + static const uint8_t PACKAGE = 0; + static const uint8_t CLASS = 1 << 0; + static const uint8_t INTERFACE = 1 << 1; + static const uint8_t STATIC = 1 << 2; + static const uint8_t ARRAY = 1 << 3; + static const uint8_t PRIMITIVE = 1 << 4; + + static const uint8_t FINAL = 1; + + static const uint8_t PRIMITIVE_BYTE = 1 + PRIMITIVE; + static const uint8_t PRIMITIVE_SHORT = 2 + PRIMITIVE; + static const uint8_t PRIMITIVE_INT = 3 + PRIMITIVE; + static const uint8_t PRIMITIVE_LONG = 4 + PRIMITIVE; + static const uint8_t PRIMITIVE_FLOAT = 5 + PRIMITIVE; + static const uint8_t PRIMITIVE_DOUBLE = 6 + PRIMITIVE; + static const uint8_t PRIMITIVE_BOOL = 7 + PRIMITIVE; + static const uint8_t PRIMITIVE_CHAR = 8 + PRIMITIVE; + static const uint8_t INVALID_TYPE = 0xFF; +}; + +struct MetadataTreeNodeRawData { + uint16_t firstChildId; + uint16_t nextSiblingId; + uint32_t offsetName; + uint32_t offsetValue; +}; +} + +#endif /* TREENODE_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MethodCache.cpp b/NativeScript/ffi/jni/napi/metadata/MethodCache.cpp new file mode 100644 index 000000000..24a1f0f4b --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MethodCache.cpp @@ -0,0 +1,34 @@ +#include "MethodCache.h" +#include "JniLocalRef.h" +#include "JsArgToArrayConverter.h" +#include "MetadataNode.h" +#include "NativeScriptAssert.h" +#include "Util.h" +#include "ArgConverter.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +void MethodCache::Init() +{ + JEnv jEnv; + + RUNTIME_CLASS = jEnv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + RESOLVE_METHOD_OVERLOAD_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "resolveMethodOverload", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;"); + assert(RESOLVE_METHOD_OVERLOAD_METHOD_ID != nullptr); + + RESOLVE_CONSTRUCTOR_SIGNATURE_ID = jEnv.GetMethodID(RUNTIME_CLASS, "resolveConstructorSignature", "(Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/String;"); + assert(RESOLVE_CONSTRUCTOR_SIGNATURE_ID != nullptr); +} + + +robin_hood::unordered_map MethodCache::s_method_ctor_signature_cache; +jclass MethodCache::RUNTIME_CLASS = nullptr; +jmethodID MethodCache::RESOLVE_METHOD_OVERLOAD_METHOD_ID = nullptr; +jmethodID MethodCache::RESOLVE_CONSTRUCTOR_SIGNATURE_ID = nullptr; diff --git a/NativeScript/ffi/jni/napi/metadata/MethodCache.h b/NativeScript/ffi/jni/napi/metadata/MethodCache.h new file mode 100644 index 000000000..bcf8e17d8 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MethodCache.h @@ -0,0 +1,369 @@ +#ifndef METHODCACHE_H_ +#define METHODCACHE_H_ + +#include +#include +#include "JEnv.h" +#include "MetadataEntry.h" +#include "ArgsWrapper.h" +#include "NativeScriptAssert.h" +#include "MetadataReader.h" +#include "Runtime.h" +#include "MetadataNode.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include "JsArgToArrayConverter.h" +#include "Util.h" + +namespace tns { +/* + * MethodCache: class dealing with method/constructor resolution. + */ +class MethodCache { + public: + /* + * CacheMethodInfo: struct holding resolved methods/constructor resolution + */ + struct CacheMethodInfo { + CacheMethodInfo() + : + retType(MethodReturnType::Unknown), mid(nullptr), clazz(nullptr), isStatic(false) { + } + std::string signature; + std::string returnType; + MethodReturnType retType; + jmethodID mid; + jclass clazz; + bool isStatic; + }; + + static void Init(); + + inline static MethodCache::CacheMethodInfo ResolveMethodSignature(napi_env env, const string &className, const string &methodName, size_t argc, napi_value* argv, bool isStatic) + { + CacheMethodInfo method_info; + + auto encoded_method_signature = EncodeSignature(env, className, methodName,argc, argv, isStatic); + auto it = s_method_ctor_signature_cache.find(encoded_method_signature); + + if (it == s_method_ctor_signature_cache.end()) + { + auto signature = ResolveJavaMethod(env, argc, argv, className, methodName); + + DEBUG_WRITE("ResolveMethodSignature %s='%s'", encoded_method_signature.c_str(), signature.c_str()); + + if (!signature.empty()) + { + JEnv jEnv; + auto clazz = jEnv.FindClass(className); + assert(clazz != nullptr); + method_info.clazz = clazz; + method_info.signature = signature; + method_info.returnType = MetadataReader::ParseReturnType(method_info.signature); + method_info.retType = MetadataReader::GetReturnType(method_info.returnType); + method_info.isStatic = isStatic; + method_info.mid = isStatic + ? jEnv.GetStaticMethodID(clazz, methodName, signature) + : jEnv.GetMethodID(clazz, methodName, signature); + + s_method_ctor_signature_cache.emplace(encoded_method_signature, method_info); + } + } + else + { + method_info = (*it).second; + } + + return method_info; + } + + inline static MethodCache::CacheMethodInfo ResolveConstructorSignature(napi_env env, const ArgsWrapper &argWrapper, const string &fullClassName, jclass javaClass, bool isInterface) + { + CacheMethodInfo constructor_info; + + auto encoded_ctor_signature = EncodeSignature(env, fullClassName, "", argWrapper.argc, argWrapper.argv, false); + auto it = s_method_ctor_signature_cache.find(encoded_ctor_signature); + + if (it == s_method_ctor_signature_cache.end()) + { + auto signature = ResolveConstructor(env, argWrapper.argc, argWrapper.argv, javaClass, isInterface); + + DEBUG_WRITE("ResolveConstructorSignature %s='%s'", encoded_ctor_signature.c_str(), signature.c_str()); + + if (!signature.empty()) + { + JEnv jEnv; + constructor_info.clazz = javaClass; + constructor_info.signature = signature; + constructor_info.mid = jEnv.GetMethodID(javaClass, "", signature); + + s_method_ctor_signature_cache.emplace(encoded_ctor_signature, constructor_info); + } + } + else + { + constructor_info = (*it).second; + } + + return constructor_info; + } + +private: + MethodCache() { + } + + // Encoded signature .S/I....<...> + inline static string EncodeSignature(napi_env env, const string &className, const string &methodName, size_t argc, napi_value* argv, bool isStatic) + { + string sig(className); + sig.append("."); + if (isStatic) + { + sig.append("S."); + } + else + { + sig.append("I."); + } + sig.append(methodName); + sig.append("."); + + stringstream s; + s << argc; + sig.append(s.str()); + + for (int i = 0; i < argc; i++) + { + sig.append("."); + sig.append(GetType(env, argv[i])); + } + + return sig; + } + + inline static string GetType(napi_env env, napi_value value) + { + napi_valuetype valueType; + napi_typeof(env, value, &valueType); + string type = ""; + + if (valueType == napi_object || valueType == napi_function) + { + + napi_value nullNode; + napi_get_named_property(env, value, PROP_KEY_NULL_NODE_NAME, &nullNode); + + if (!napi_util::is_null_or_undefined(env, nullNode)) + { + void *data; + napi_get_value_external(env, nullNode, &data); + auto treeNode = reinterpret_cast(data); + + type = (treeNode != nullptr) ? treeNode->GetName() : ""; + + DEBUG_WRITE("Parameter of type %s with NULL value is passed to the method.", type.c_str()); + return type; + } + } + + + if (valueType == napi_string) { + type = "string"; + } else if (valueType == napi_null) { + type = "null"; + } else if (valueType == napi_undefined) { + type = "undefined"; + } else if (valueType == napi_number) { + type = "number"; + } else if (valueType == napi_object) { + type = "object"; + } else if (napi_util::is_array(env, value)) { + type = "array"; + } else if (valueType == napi_function) { + type = "function"; + } else if (napi_util::is_typedarray(env, value)) { + type = "typedarray"; + } else if (valueType == napi_boolean) { + type = "bool"; + } else if (napi_util::is_dataview(env, value)) { + type = "view"; + } else if (napi_util::is_date(env, value)) { + type = "date"; + } + + // Handle special cases for typed arrays + if (type == "typedarray") + { + napi_typedarray_type arrayType; + napi_get_typedarray_info(env, value, &arrayType, nullptr, nullptr, nullptr, nullptr); + switch (arrayType) + { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + type = "bytebuffer"; + break; + case napi_int16_array: + case napi_uint16_array: + type = "shortbuffer"; + break; + case napi_int32_array: + case napi_uint32_array: + type = "intbuffer"; + break; + case napi_bigint64_array: + case napi_biguint64_array: + type = "longbuffer"; + break; + case napi_float32_array: + type = "floatbuffer"; + break; + case napi_float64_array: + type = "doublebuffer"; + break; + default: + type = ""; + } + } + + // Handle special cases for numbers + if (type == "number") + { + double d; + napi_get_value_double(env, value, &d); + int64_t i = (int64_t)d; + bool isInteger = d == i; + type = isInteger ? "intnumber" : "doublenumber"; + } + + // Handle special cases for objects + if (type == "object" || type == "function") + { + auto castType = NumericCasts::GetCastType(env, value); + MetadataNode *node; + + switch (castType) + { + case CastType::Char: + type = "char"; + break; + case CastType::Byte: + type = "byte"; + break; + case CastType::Short: + type = "short"; + break; + case CastType::Long: + type = "long"; + break; + case CastType::Float: + type = "float"; + break; + case CastType::Double: + type = "double"; + break; + case CastType::None: + node = MetadataNode::GetNodeFromHandle(env, value); + type = (node != nullptr) ? node->GetName() : ""; + + if (type == "") { + if (napi_util::is_number_object(env, value)) { + napi_value numValue = napi_util::valueOf(env, value); + bool isFloat = napi_util::is_float(env, numValue); + if (isFloat) { + type = "float"; + } else { + type = "int"; + } + } else if (napi_util::is_string_object(env, value)) { + type = "string"; + } else if (napi_util::is_number_object(env, value)) { + type = "bool"; + } + } + + break; + default: + throw NativeScriptException("Unsupported cast type"); + } + } + + if (type == "undefined") { + type = "null"; + } + + return type; + } + + inline static string ResolveJavaMethod(napi_env env , size_t argc, napi_value* argv, const string &className, const string &methodName) + { + JEnv jEnv; + + JsArgToArrayConverter argConverter(env, argc, argv, false); + + auto canonicalClassName = Util::ConvertFromJniToCanonicalName(className); + JniLocalRef jsClassName(jEnv.NewStringUTF(canonicalClassName.c_str())); + JniLocalRef jsMethodName(jEnv.NewStringUTF(methodName.c_str())); + + jobjectArray arrArgs = argConverter.ToJavaArray(); + + auto runtime = Runtime::GetRuntime(env); + + jstring signature = (jstring)jEnv.CallObjectMethod(runtime->GetJavaRuntime(), RESOLVE_METHOD_OVERLOAD_METHOD_ID, (jstring)jsClassName, (jstring)jsMethodName, arrArgs); + + string resolvedSignature; + + const char *str = jEnv.GetStringUTFChars(signature, nullptr); + resolvedSignature = string(str); + jEnv.ReleaseStringUTFChars(signature, str); + + jEnv.DeleteLocalRef(signature); + + return resolvedSignature; + } + + inline static string ResolveConstructor(napi_env env, size_t argc, napi_value* argv, jclass javaClass, bool isInterface) + { + JEnv jEnv; + string resolvedSignature; + + JsArgToArrayConverter argConverter(env, argc, argv, isInterface); + if (argConverter.IsValid()) + { + jobjectArray javaArgs = argConverter.ToJavaArray(); + + auto runtime = Runtime::GetRuntime(env); + + jstring signature = (jstring)jEnv.CallObjectMethod(runtime->GetJavaRuntime(), RESOLVE_CONSTRUCTOR_SIGNATURE_ID, javaClass, javaArgs); + + const char *str = jEnv.GetStringUTFChars(signature, nullptr); + resolvedSignature = string(str); + jEnv.ReleaseStringUTFChars(signature, str); + jEnv.DeleteLocalRef(signature); + } + else + { + JsArgToArrayConverter::Error err = argConverter.GetError(); + throw NativeScriptException(err.msg); + } + + return resolvedSignature; + } + + static jclass RUNTIME_CLASS; + + static jmethodID RESOLVE_METHOD_OVERLOAD_METHOD_ID; + + static jmethodID RESOLVE_CONSTRUCTOR_SIGNATURE_ID; + + /* + * "s_method_ctor_signature_cache" holding all resolved CacheMethodInfo against an encoded_signature string. + * Used for caching the resolved constructor or method signature. + * The encoded signature has template: .S/I....<...> + */ + static robin_hood::unordered_map s_method_ctor_signature_cache; +}; +} + +#endif /* METHODCACHE_H_ */ + diff --git a/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp new file mode 100644 index 000000000..7917bfd9d --- /dev/null +++ b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp @@ -0,0 +1,621 @@ +#include "ObjectManager.h" +#include "NativeScriptAssert.h" +#include "MetadataNode.h" +#include "ArgConverter.h" +#include "Util.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include +#include + +using namespace std; +using namespace tns; + +ObjectManager::ObjectManager(jobject javaRuntimeObject) : + m_javaRuntimeObject(javaRuntimeObject), + m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this), + m_currentObjectId(0), + m_jsObjectProxyCreator(nullptr), + m_jsObjectCtor(nullptr), + m_env(nullptr) { + + JEnv env; + auto runtimeClass = env.FindClass("com/tns/Runtime"); + assert(runtimeClass != nullptr); + + GET_JAVAOBJECT_BY_ID_METHOD_ID = env.GetMethodID(runtimeClass, "getJavaObjectByID", + "(I)Ljava/lang/Object;"); + assert(GET_JAVAOBJECT_BY_ID_METHOD_ID != nullptr); + + GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = env.GetMethodID(runtimeClass, + "getOrCreateJavaObjectID", + "(Ljava/lang/Object;)I"); + assert(GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID != nullptr); + + MAKE_INSTANCE_WEAK_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceWeak", + "(I)V"); + assert(MAKE_INSTANCE_WEAK_METHOD_ID != nullptr); + + MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceWeak", + "(Ljava/nio/ByteBuffer;IZ)V"); + assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr); + + MAKE_INSTANCE_STRONG_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceStrong", + "(I)V"); + assert(MAKE_INSTANCE_STRONG_METHOD_ID != nullptr); + + JAVA_LANG_CLASS = env.FindClass("java/lang/Class"); + assert(JAVA_LANG_CLASS != nullptr); + + GET_NAME_METHOD_ID = env.GetMethodID(JAVA_LANG_CLASS, "getName", "()Ljava/lang/String;"); + assert(GET_NAME_METHOD_ID != nullptr); +} + + +void ObjectManager::Init(napi_env env) { + m_env = env; + napi_value jsObjectCtor; + napi_define_class(env, "JSObject", NAPI_AUTO_LENGTH, JSObjectConstructorCallback, nullptr, + 0, + nullptr, &jsObjectCtor); + + napi_set_named_property(env, napi_util::get_prototype(env, jsObjectCtor), PRIVATE_IS_NAPI, + napi_util::get_true(env)); + m_jsObjectCtor = napi_util::make_ref(env, jsObjectCtor, 1); +} + + +void ObjectManager::OnDisposeEnv() { + JEnv jEnv; + if (this->m_jsObjectCtor) napi_delete_reference(m_env, this->m_jsObjectCtor); + if (this->m_jsObjectProxyCreator) napi_delete_reference(m_env, this->m_jsObjectProxyCreator); + + for (auto &entry: m_idToProxy) { + if (!entry.second) continue; + napi_delete_reference(m_env, entry.second); + } + m_idToProxy.clear(); + + for (auto &entry: m_idToObject) { + if (!entry.second) continue; + napi_delete_reference(m_env, entry.second); + } + m_idToObject.clear(); +} + +napi_value ObjectManager::GetOrCreateProxyWeak(jint javaObjectID, napi_value instance) { + napi_value proxy = nullptr; +#ifdef USE_HOST_OBJECT + bool is_array = false; + napi_value getter = nullptr; + napi_value setter = nullptr; + + napi_has_named_property(m_env, instance, "__is__javaArray", &is_array); + void* data; + napi_unwrap(m_env, instance, &data); + + if (is_array) { + napi_value global; + napi_get_global(m_env, &global); + napi_get_named_property(m_env, global, "getNativeArrayProp", &getter); + napi_get_named_property(m_env, global, "setNativeArrayProp", &setter); + } + + napi_create_host_object(m_env, instance, nullptr, data, is_array, getter, setter, &proxy); +#else + napi_value argv[2]; + argv[0] = instance; + napi_create_int32(m_env, javaObjectID, &argv[1]); + + if (!this->m_jsObjectProxyCreator) { + napi_value jsObjectProxyCreator; + napi_get_named_property(m_env, napi_util::global(m_env), "__createNativeProxy", + &jsObjectProxyCreator); + this->m_jsObjectProxyCreator = napi_util::make_ref(m_env, jsObjectProxyCreator); + } + + napi_call_function(m_env, napi_util::global(m_env), + napi_util::get_ref_value(m_env, this->m_jsObjectProxyCreator), + 2, argv, &proxy); + +#endif + return proxy; +} + +napi_value ObjectManager::GetOrCreateProxy(jint javaObjectID, napi_value instance) { + napi_value proxy = nullptr; + auto it = m_idToProxy.find(javaObjectID); + if (it != m_idToProxy.end() && it->second != nullptr) { + proxy = napi_util::get_ref_value(m_env, it->second); + if (!napi_util::is_null_or_undefined(m_env, proxy)) { + return proxy; + } else { + napi_delete_reference(m_env, it->second); + m_idToProxy.erase(javaObjectID); + } + } + + DEBUG_WRITE("%s %d", "Creating a new proxy for java object with id:", javaObjectID); + +#ifdef USE_HOST_OBJECT + bool is_array = false; + napi_value getter = nullptr; + napi_value setter = nullptr; + + napi_has_named_property(m_env, instance, "__is__javaArray", &is_array); + + auto data = new JSInstanceInfo(javaObjectID); + + if (is_array) { + napi_value global; + napi_get_global(m_env, &global); + napi_get_named_property(m_env, global, "getNativeArrayProp", &getter); + napi_get_named_property(m_env, global, "setNativeArrayProp", &setter); + } + + napi_create_host_object(m_env, instance, JSObjectProxyFinalizerCallback, data, is_array, getter, setter, &proxy); + +#else + napi_value argv[2]; + argv[0] = instance; + napi_create_int32(m_env, javaObjectID, &argv[1]); + + if (!this->m_jsObjectProxyCreator) { + napi_value jsObjectProxyCreator; + napi_get_named_property(m_env, napi_util::global(m_env), "__createNativeProxy", + &jsObjectProxyCreator); + this->m_jsObjectProxyCreator = napi_util::make_ref(m_env, jsObjectProxyCreator); + } + + napi_call_function(m_env, napi_util::global(m_env), + napi_util::get_ref_value(m_env, this->m_jsObjectProxyCreator), + 2, argv, &proxy); + + if (!proxy) { + DEBUG_WRITE("Failed to create proxy for javaObjectId %d", javaObjectID); + return nullptr; + } + + + auto data = new JSInstanceInfo(javaObjectID); + + napi_value external; + napi_create_external(m_env, data, JSObjectProxyFinalizerCallback, data, &external); + napi_set_named_property(m_env, proxy, "[[external]]", external); + + +#endif + + auto javaObjectIdFound = m_weakObjectIds.find(javaObjectID); + if (javaObjectIdFound != m_weakObjectIds.end()) { + m_weakObjectIds.erase(javaObjectID); + JEnv jenv; + jenv.CallVoidMethod(m_javaRuntimeObject, + MAKE_INSTANCE_STRONG_METHOD_ID, + javaObjectID); + DEBUG_WRITE("Making instance strong: %d", javaObjectID); + } + + m_idToProxy.emplace(javaObjectID, napi_util::make_ref(m_env, proxy, 0)); + + return proxy; +} + +JniLocalRef ObjectManager::GetJavaObjectByJsObject(napi_value object, int *objectId) { + int32_t javaObjectId = -1; + if (objectId) { + javaObjectId = *objectId; + } + +#ifdef USE_HOST_OBJECT + void* data = nullptr; + napi_get_host_object_data(m_env, object, &data); + if (data) { + auto info = (JSInstanceInfo *) data; + javaObjectId = info->JavaObjectID; + } else { + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); + if (jsInstanceInfo != nullptr) javaObjectId = jsInstanceInfo->JavaObjectID; + } +#else + if (javaObjectId == -1) { + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); + if (jsInstanceInfo != nullptr) javaObjectId = jsInstanceInfo->JavaObjectID; + } +#endif + + if (objectId) { + *objectId = javaObjectId; + } + + if (javaObjectId != -1) return GetJavaObjectByID(javaObjectId); + + return {}; +} + +JniLocalRef ObjectManager::GetJavaObjectByJsObjectFast(napi_value object) { + void *data = nullptr; + +#ifdef USE_HOST_OBJECT + napi_get_host_object_data(m_env, object, &data); +#endif + + if (!data) napi_unwrap(m_env, object, &data); + + if (data) { + auto info = reinterpret_cast(data); + return GetJavaObjectByID(info->JavaObjectID); + } + + return GetJavaObjectByJsObject(object); +} + +ObjectManager::JSInstanceInfo *ObjectManager::GetJSInstanceInfo(napi_value object) { + if (!IsRuntimeJsObject(object)) return nullptr; + + return GetJSInstanceInfoFromRuntimeObject(object); +} + +bool ObjectManager::IsHostObject(napi_value object) { +#ifdef USE_HOST_OBJECT + bool isHostObject; + napi_is_host_object(m_env, object, &isHostObject); + return isHostObject; +#endif + return false; +} + +ObjectManager::JSInstanceInfo * +ObjectManager::GetJSInstanceInfoFromRuntimeObject(napi_value object) { + napi_value jsInfo; + napi_get_named_property(m_env, object, PRIVATE_JSINFO, &jsInfo); + + if (napi_util::is_null_or_undefined(m_env, jsInfo)) { + napi_value proto = napi_util::get__proto__(m_env, object); + //Typescript object layout has an object instance as child of the actual registered instance. checking for that + if (!napi_util::is_null_or_undefined(m_env, proto)) { + if (IsRuntimeJsObject(proto)) { + napi_get_named_property(m_env, proto, PRIVATE_JSINFO, &jsInfo); + } + } + } + + if (!napi_util::is_null_or_undefined(m_env, jsInfo)) { + void *data; + napi_get_value_external(m_env, jsInfo, &data); + auto info = reinterpret_cast(data); + return info; + } + return nullptr; +} + +bool ObjectManager::IsRuntimeJsObject(napi_value object) { + if (object == nullptr) return false; + + bool result = false; + if (napi_has_named_property(m_env, object, PRIVATE_IS_NAPI, &result) != napi_ok) { + return false; + } + return result; +} + +JniLocalRef ObjectManager::GetJavaObjectByID(uint32_t javaObjectID) { + JEnv env; + return JniLocalRef(env.NewLocalRef(m_cache(javaObjectID))); +} + +jobject ObjectManager::GetJavaObjectByIDImpl(uint32_t javaObjectID) { + JEnv env; + jobject object = env.CallObjectMethod(m_javaRuntimeObject, GET_JAVAOBJECT_BY_ID_METHOD_ID, + javaObjectID); + return object; +} + +void ObjectManager::UpdateCache(int objectID, jobject obj) { + m_cache.update(objectID, obj); +} + +int ObjectManager::GetOrCreateObjectId(jobject object) { + JEnv env; + jint javaObjectID = env.CallIntMethod(m_javaRuntimeObject, + GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID, object); + return javaObjectID; +} + +napi_value ObjectManager::GetJsObjectByJavaObject(int javaObjectID) { + auto it = m_idToObject.find(javaObjectID); + if (it == m_idToObject.end()) { + return nullptr; + } + + napi_value instance = napi_util::get_ref_value(m_env, it->second); + if (napi_util::is_null_or_undefined(m_env, instance)) return nullptr; + return GetOrCreateProxy(javaObjectID, instance); +} + + +napi_value +ObjectManager::CreateJSWrapper(jint javaObjectID, const std::string &typeName) { + return CreateJSWrapperHelper(javaObjectID, typeName, nullptr); +} + +napi_value +ObjectManager::CreateJSWrapper(jint javaObjectID, const std::string &typeName, jobject instance) { + JEnv jenv; + JniLocalRef clazz(jenv.GetObjectClass(instance)); + + return CreateJSWrapperHelper(javaObjectID, typeName, clazz); +} + +napi_value +ObjectManager::CreateJSWrapperHelper(jint javaObjectID, const std::string &typeName, jclass clazz) { + auto className = (clazz != nullptr) ? GetClassName(clazz) : typeName; + + auto node = MetadataNode::GetOrCreate(className); + napi_value proxy = nullptr; + napi_value jsWrapper = node->CreateJSWrapper(m_env, this); + if (jsWrapper != nullptr) { + Link(jsWrapper, javaObjectID); + if (node->isArray()) { + napi_set_named_property(m_env, jsWrapper, "__is__javaArray", + napi_util::get_true(m_env)); + } + proxy = GetOrCreateProxy(javaObjectID, jsWrapper); + } + + return proxy; +} + +void ObjectManager::Link(napi_value object, uint32_t javaObjectID) { + if (!IsRuntimeJsObject(object)) { + std::string errMsg("Trying to link invalid 'this' to a Java object"); + throw NativeScriptException(errMsg); + } + + DEBUG_WRITE("Linking js object and java instance id: %d", javaObjectID); + + auto jsInstanceInfo = new JSInstanceInfo(javaObjectID); + + napi_ref objectHandle = napi_util::make_ref(m_env, object, 1); + + napi_value jsInfo; + napi_create_external(m_env, jsInstanceInfo, JSObjectFinalizerCallback, jsInstanceInfo, &jsInfo); + napi_set_named_property(m_env, object, PRIVATE_JSINFO, jsInfo); + + // Wrapped but does not handle data lifecycle. only used for fast access. + napi_wrap(m_env, object, jsInstanceInfo, [](napi_env env, void *data, void *hint) {}, jsInstanceInfo, + nullptr); + + m_idToObject.emplace(javaObjectID, objectHandle); +} + +bool ObjectManager::CloneLink(napi_value src, napi_value dest) { + auto jsInfo = GetJSInstanceInfo(src); + + auto success = jsInfo != nullptr; + + if (success) { + napi_value external; + napi_create_external(m_env, jsInfo, [](napi_env env, void* d1, void*d2) {}, jsInfo, &external); + napi_set_named_property(m_env, dest, PRIVATE_JSINFO, external); + napi_wrap(m_env, dest, jsInfo, [](napi_env env, void *data, void *hint) {}, jsInfo, + nullptr); + } + + return success; +} + +string ObjectManager::GetClassName(jobject javaObject) { + JEnv env; + JniLocalRef objectClass(env.GetObjectClass(javaObject)); + + return GetClassName((jclass) objectClass); +} + +bool ObjectManager::GetIsSuper(int objectId, napi_value value) { + auto it = m_idToSuper.find(objectId); + if (it != m_idToSuper.end()) return it->second; + napi_value superValue; + napi_get_named_property(m_env, value, PRIVATE_CALLSUPER, &superValue); + bool isSuper = napi_util::get_bool(m_env, superValue); + m_idToSuper.emplace(objectId, isSuper); + return isSuper; +} + +string ObjectManager::GetClassName(jclass clazz) { + JEnv env; + JniLocalRef javaCanonicalName(env.CallObjectMethod(clazz, GET_NAME_METHOD_ID)); + + string className = ArgConverter::jstringToString(javaCanonicalName); + + std::replace(className.begin(), className.end(), '.', '/'); + + return className; +} + +void +ObjectManager::JSObjectFinalizerCallback(napi_env env, void *finalizeData, void *finalizeHint) { + #ifdef __HERMES__ + if (finalizeHint == nullptr) return; + auto data = reinterpret_cast(finalizeHint); + #else + if (finalizeData == nullptr) return; + auto data = reinterpret_cast(finalizeData); + #endif + + DEBUG_WRITE("JS Object finalizer called for object id: %d", data->JavaObjectID); + delete data; +} + +void ObjectManager::JSObjectProxyFinalizerCallback(napi_env env, void *finalizeData, + void *finalizeHint) { + +#ifdef __HERMES__ + if (finalizeHint == nullptr) return; + auto state = reinterpret_cast(finalizeHint); +#else + if (finalizeData == nullptr) return; + auto state = reinterpret_cast(finalizeData); +#endif + + auto rt = Runtime::GetRuntimeUnchecked(env); + if (rt && !rt->is_destroying) { + + auto objManager = rt->GetObjectManager(); + auto itFound = objManager->m_weakObjectIds.find(state->JavaObjectID); + + DEBUG_WRITE("JS Proxy finalizer called for object id: %d", state->JavaObjectID); + if (itFound == objManager->m_weakObjectIds.end()) { + objManager->m_weakObjectIds.emplace(state->JavaObjectID); + JEnv jEnv; + jEnv.CallVoidMethod(objManager->m_javaRuntimeObject, + objManager->MAKE_INSTANCE_WEAK_METHOD_ID, + state->JavaObjectID); + + } + } + delete state; +} + +int ObjectManager::GenerateNewObjectID() { + const int one = 1; + int oldValue = __sync_fetch_and_add(&m_currentObjectId, one); + return oldValue; +} + +jweak ObjectManager::NewWeakGlobalRefCallback(const int &javaObjectID, void *state) { + auto objManager = reinterpret_cast(state); + JniLocalRef obj(objManager->GetJavaObjectByIDImpl(javaObjectID)); + JEnv jEnv; + jweak weakRef = jEnv.NewWeakGlobalRef(obj); + + return weakRef; +} + +void ObjectManager::DeleteWeakGlobalRefCallback(const jweak &object, void *state) { + JEnv jEnv; + jEnv.DeleteWeakGlobalRef(object); +} + +napi_value ObjectManager::GetEmptyObject() { + napi_value emptyObjCtorFunc = napi_util::get_ref_value(m_env, m_jsObjectCtor); + + napi_value ex; + napi_get_and_clear_last_exception(m_env, &ex); + + napi_value jsWrapper = nullptr; + auto status = napi_new_instance(m_env, emptyObjCtorFunc, 0, nullptr, &jsWrapper); + if (status == napi_ok && !napi_util::is_null_or_undefined(m_env, jsWrapper)) { + return jsWrapper; + } + + napi_get_and_clear_last_exception(m_env, &ex); + + status = napi_create_object(m_env, &jsWrapper); + if (status != napi_ok || jsWrapper == nullptr) return nullptr; + + MarkObject(m_env, jsWrapper); + auto prototype = napi_util::get_prototype(m_env, emptyObjCtorFunc); + if (!napi_util::is_null_or_undefined(m_env, prototype)) { + napi_util::setPrototypeOf(m_env, jsWrapper, prototype); + } + + if (napi_util::is_null_or_undefined(m_env, jsWrapper)) { + return nullptr; + } + + return jsWrapper; +} + +napi_value ObjectManager::JSObjectConstructorCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0); + return jsThis; +} + +void ObjectManager::ReleaseObjectNow(napi_env env, int javaObjectId) { + auto rt = Runtime::GetRuntimeUnchecked(env); + if (!rt || rt->is_destroying) return; + ObjectManager *objMgr = rt->GetObjectManager(); + + auto itFound = objMgr->m_weakObjectIds.find(javaObjectId); + if (itFound == objMgr->m_weakObjectIds.end()) { + JEnv jEnv; + jEnv.CallVoidMethod(objMgr->m_javaRuntimeObject, objMgr->MAKE_INSTANCE_WEAK_METHOD_ID, + javaObjectId); + objMgr->m_weakObjectIds.emplace(javaObjectId); + } + + auto found = objMgr->m_idToProxy.find(javaObjectId); + if (found != objMgr->m_idToProxy.end()) { + napi_delete_reference(env, found->second); + objMgr->m_idToProxy.erase(javaObjectId); + } + + found = objMgr->m_idToObject.find(javaObjectId); + if (found != objMgr->m_idToObject.end()) { + napi_delete_reference(env, found->second); + objMgr->m_idToObject.erase(javaObjectId); + } + + Runtime::GetRuntime(env)->js_method_cache->cleanupObject(javaObjectId); +} + +void ObjectManager::ReleaseNativeObject(napi_env env, napi_value object) { + int32_t javaObjectId = -1; + JSInstanceInfo *jsInstanceInfo; + +#ifdef USE_HOST_OBJECT + void* data; + napi_get_host_object_data(env, object, &data); + if (data) { + jsInstanceInfo = reinterpret_cast(data); + } else { +#endif + jsInstanceInfo = GetJSInstanceInfo(object); +#ifdef USE_HOST_OBJECT + } +#endif + + if (jsInstanceInfo) { + javaObjectId = jsInstanceInfo->JavaObjectID; + } + + if (javaObjectId == -1) { + napi_throw_error(env, "0", "Trying to release a non native object!"); + return; + } + + ReleaseObjectNow(env, javaObjectId); +} + +void ObjectManager::OnGarbageCollected(JNIEnv *jEnv, jintArray object_ids) { + JEnv jenv(jEnv); + auto rt = Runtime::GetRuntimeUnchecked(m_env); + if (!rt || rt->is_destroying) return; + + jsize length = jenv.GetArrayLength(object_ids); + jint *cppArray = jenv.GetIntArrayElements(object_ids, nullptr); + if (cppArray == nullptr) return; + for (jsize i = 0; i < length; i++) { + if (rt->is_destroying) break; + int javaObjectId = cppArray[i]; + auto itFound = this->m_idToObject.find(javaObjectId); + if (itFound != this->m_idToObject.end()) { + napi_delete_reference(m_env, itFound->second); + this->m_idToObject.erase(javaObjectId); + + if (rt && !rt->is_destroying) { + rt->js_method_cache->cleanupObject(javaObjectId); + } + + DEBUG_WRITE("JS Object released for object id: %d", javaObjectId); + // auto found = this->m_idToProxy.find(javaObjectId); + // if (found != this->m_idToProxy.end()) { + // napi_delete_reference(m_env, found->second); + // this->m_idToProxy.erase(javaObjectId); + // } + } + + } + jEnv->ReleaseIntArrayElements(object_ids, cppArray, JNI_ABORT); +} diff --git a/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.h b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.h new file mode 100644 index 000000000..52ca46a94 --- /dev/null +++ b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.h @@ -0,0 +1,154 @@ +#ifndef OBJECTMANAGER_H_ +#define OBJECTMANAGER_H_ + +#include "js_native_api.h" +#include "JEnv.h" +#include "JniLocalRef.h" +#include "DirectBuffer.h" +#include "LRUCache.h" +#include +#include +#include +#include +#include +#include "Constants.h" + + +namespace tns { + class ObjectManager { + public: + ObjectManager(jobject javaRuntimeObject); + + void OnDisposeEnv(); + + void Init(napi_env env); + + JniLocalRef GetJavaObjectByJsObject(napi_value object, int *objectId = nullptr); + + + JniLocalRef GetJavaObjectByJsObjectFast(napi_value object); + + void UpdateCache(int objectID, jobject obj); + + int GetOrCreateObjectId(jobject object); + + napi_value GetJsObjectByJavaObject(int javaObjectID); + + napi_value + CreateJSWrapper(jint javaObjectID, const std::string &typeName); + + napi_value + CreateJSWrapper(jint javaObjectID, const std::string &typeName, jobject instance); + + napi_value GetOrCreateProxy(jint javaObjectID, napi_value instance); + + napi_value GetOrCreateProxyWeak(jint javaObjectID, napi_value instance); + + void Link(napi_value object, uint32_t javaObjectID); + + bool CloneLink(napi_value src, napi_value dest); + + bool IsRuntimeJsObject(napi_value object); + + std::string GetClassName(jobject javaObject); + + std::string GetClassName(jclass clazz); + + int GenerateNewObjectID(); + + napi_value GetEmptyObject(); + + inline static void MarkObject(napi_env env, napi_value object) { + napi_value marker; + napi_get_boolean(env, true, &marker); + napi_set_named_property(env, object, PRIVATE_IS_NAPI, marker); + } + + inline static void MarkSuperCall(napi_env env, napi_value object) { + napi_value marker; + napi_get_boolean(env, true, &marker); + napi_set_named_property(env, object, PRIVATE_CALLSUPER, marker); + } + + void OnGarbageCollected(JNIEnv *jEnv, jintArray object_ids); + + void ReleaseNativeObject(napi_env env, napi_value object); + + inline static void ReleaseObjectNow(napi_env env, int javaObjectId); + + bool GetIsSuper(int objectId, napi_value value); + + bool IsHostObject(napi_value object); + + private: + static napi_value JSObjectConstructorCallback(napi_env env, napi_callback_info info); + + struct JSInstanceInfo { + public: + explicit JSInstanceInfo(uint32_t javaObjectID) + : JavaObjectID(javaObjectID) { + } + + uint32_t JavaObjectID; + }; + + + JSInstanceInfo *GetJSInstanceInfo(napi_value object); + + JSInstanceInfo *GetJSInstanceInfoFromRuntimeObject(napi_value object); + + napi_value + CreateJSWrapperHelper(jint javaObjectID, const std::string &typeName, jclass clazz); + + static void JSObjectFinalizerCallback(napi_env env, void *finalizeData, void *finalizeHint); + + static void + JSObjectProxyFinalizerCallback(napi_env env, void *finalizeData, void *finalizeHint); + + JniLocalRef GetJavaObjectByID(uint32_t javaObjectID); + + jobject GetJavaObjectByIDImpl(uint32_t javaObjectID); + + static jweak NewWeakGlobalRefCallback(const int &javaObjectID, void *state); + + static void DeleteWeakGlobalRefCallback(const jweak &object, void *state); + + jobject m_javaRuntimeObject; + + napi_env m_env; + + robin_hood::unordered_map m_idToProxy; + robin_hood::unordered_map m_idToObject; + robin_hood::unordered_map m_idToSuper; + robin_hood::unordered_set m_weakObjectIds; + robin_hood::unordered_set m_markedAsWeakIds; + + LRUCache m_cache; + + volatile int m_currentObjectId; + + DirectBuffer m_buff; + + DirectBuffer m_outBuff; + + jclass JAVA_LANG_CLASS; + + jmethodID GET_NAME_METHOD_ID; + + jmethodID GET_JAVAOBJECT_BY_ID_METHOD_ID; + + jmethodID GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID; + + jmethodID MAKE_INSTANCE_WEAK_BATCH_METHOD_ID; + + jmethodID MAKE_INSTANCE_WEAK_METHOD_ID; + + jmethodID MAKE_INSTANCE_STRONG_METHOD_ID; + + napi_ref m_jsObjectCtor; + + napi_ref m_jsObjectProxyCreator; + }; +} + +#endif /* OBJECTMANAGER_H_ */ diff --git a/NativeScript/ffi/jni/napi/weakref/WeakRef.cpp b/NativeScript/ffi/jni/napi/weakref/WeakRef.cpp new file mode 100644 index 000000000..3d15512e1 --- /dev/null +++ b/NativeScript/ffi/jni/napi/weakref/WeakRef.cpp @@ -0,0 +1,79 @@ +// +// Created by Ammar Ahmed on 03/12/2024. +// + +#include "WeakRef.h" +#include "native_api_util.h" + +using namespace tns; + +WeakRef::WeakRef(napi_env env, napi_value value) : env_(env), ref_(nullptr) { + napi_create_reference(env, value, 1, &ref_); +} + +WeakRef::~WeakRef() { + if (ref_ != nullptr) { + napi_delete_reference(env_, ref_); + } +} + +void WeakRef::Init(napi_env env) { + napi_value global; + napi_get_global(env, &global); + napi_property_descriptor properties[] = { + { "get", 0, Deref, 0, 0, 0, napi_default, 0 }, + { "deref", 0, Deref, 0, 0, 0, napi_default, 0 } + }; + + napi_value wr; + napi_get_named_property(env, global, "WeakRef", &wr); + if (napi_util::is_null_or_undefined(env, wr)) { + napi_value cons; + napi_define_class(env, "WeakRef", NAPI_AUTO_LENGTH, New, nullptr, 2, properties, &cons); + napi_set_named_property(env, global, "WeakRef", cons); + } +} + +napi_value WeakRef::New(napi_env env, napi_callback_info info) { + napi_value target; + napi_get_new_target(env, info, &target); + bool is_constructor = target != nullptr; + + if (!is_constructor) { + napi_throw_error(env, nullptr, "WeakRef must be called as a constructor"); + return nullptr; + } + + size_t arg_len; + napi_get_cb_info(env, info, &arg_len, nullptr, nullptr, nullptr); + + if (arg_len != 1) { + napi_throw_error(env, nullptr, "WeakRef constructor must be called with one argument"); + return nullptr; + } + + size_t argc = 1; + napi_value args[1]; + napi_value jsThis; + napi_get_cb_info(env, info, &argc, args, &jsThis, nullptr); + + auto obj = new WeakRef(env, args[0]); + napi_wrap(env, jsThis, reinterpret_cast(obj), [](napi_env env, void* data, void* hint) { + delete reinterpret_cast(data); + }, nullptr, nullptr); + + return jsThis; +} + +napi_value WeakRef::Deref(napi_env env, napi_callback_info info) { + napi_value jsThis; + napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); + + WeakRef* obj; + napi_unwrap(env, jsThis, reinterpret_cast(&obj)); + + napi_value result; + napi_get_reference_value(env, obj->ref_, &result); + + return result; +} diff --git a/NativeScript/ffi/jni/napi/weakref/WeakRef.h b/NativeScript/ffi/jni/napi/weakref/WeakRef.h new file mode 100644 index 000000000..08579a40c --- /dev/null +++ b/NativeScript/ffi/jni/napi/weakref/WeakRef.h @@ -0,0 +1,28 @@ +// +// Created by Ammar Ahmed on 03/12/2024. +// + +#ifndef TEST_APP_WEAKREF_H +#define TEST_APP_WEAKREF_H + +#include "js_native_api.h" + +namespace tns { + class WeakRef { + public: + static void Init(napi_env env); + static napi_value New(napi_env env, napi_callback_info info); + + private: + explicit WeakRef(napi_env env, napi_value value); + ~WeakRef(); + + napi_env env_; + napi_ref ref_; + + static napi_value Get(napi_env env, napi_callback_info info); + static napi_value Deref(napi_env env, napi_callback_info info); + }; + +} +#endif //TEST_APP_WEAKREF_H diff --git a/NativeScript/ffi/jsc/NativeApiJSC.h b/NativeScript/ffi/jsc/NativeApiJSC.h deleted file mode 100644 index 0bf60c969..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSC.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H -#define NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H - -#include "ffi/shared/direct/NativeApiDirect.h" -#include - -namespace nativescript { - -using NativeApiJSCConfig = NativeApiDirectConfig; - -void InstallNativeApiJSC(JSGlobalContextRef context, - const NativeApiJSCConfig& config = NativeApiJSCConfig{}); - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiJSC(JSGlobalContextRef context, - const char* metadataPath); - -#endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H diff --git a/NativeScript/ffi/jsc/NativeApiJSC.mm b/NativeScript/ffi/jsc/NativeApiJSC.mm deleted file mode 100644 index b1a6fa398..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSC.mm +++ /dev/null @@ -1,70 +0,0 @@ -#include "NativeApiJSC.h" - -#ifdef TARGET_ENGINE_JSC - -#include "NativeApiJSCRuntime.h" - -namespace nativescript { - -using NativeApiJsiConfig = NativeApiDirectConfig; -using NativeApiJsiScheduler = NativeApiDirectScheduler; - -namespace { - -using facebook::jsi::Array; -using facebook::jsi::ArrayBuffer; -using facebook::jsi::BigInt; -using facebook::jsi::Function; -using facebook::jsi::HostObject; -using facebook::jsi::MutableBuffer; -using facebook::jsi::Object; -using facebook::jsi::PropNameID; -using facebook::jsi::Runtime; -using facebook::jsi::String; -using facebook::jsi::StringBuffer; -using facebook::jsi::Value; -using metagen::MDMemberFlag; -using metagen::MDMetadataReader; -using metagen::MDSectionOffset; -using metagen::MDTypeKind; - -// clang-format off -#include "jsi/NativeApiJsiBridge.h" -#include "jsi/NativeApiJsiHostObjects.h" -// clang-format on -#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 - -std::shared_ptr retainNativeApiJsiRuntime(Runtime& runtime) { - return std::make_shared(runtime.state()); -} - -// clang-format off -#include "jsi/NativeApiJsiCallbacks.h" -#include "jsi/NativeApiJsiConversion.h" -#include "jsi/NativeApiJsiInvocation.h" -#include "jsi/NativeApiJsiClassBuilder.h" -#include "jsi/NativeApiJsiHostObject.h" -// clang-format on - -} // namespace - -#include "jsi/NativeApiJsiInstall.h" - -void InstallNativeApiJSC(JSGlobalContextRef context, const NativeApiJSCConfig& config) { - if (context == nullptr) { - return; - } - Runtime runtime(context); - InstallNativeApiJSI(runtime, config); -} - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiJSC(JSGlobalContextRef context, - const char* metadataPath) { - nativescript::NativeApiJSCConfig config; - config.metadataPath = metadataPath; - nativescript::InstallNativeApiJSC(context, config); -} - -#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/jsc/NativeApiJSCHostObjects.mm b/NativeScript/ffi/jsc/NativeApiJSCHostObjects.mm deleted file mode 100644 index ab2da20a8..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSCHostObjects.mm +++ /dev/null @@ -1,182 +0,0 @@ -#include "NativeApiJSCRuntime.h" - -#ifdef TARGET_ENGINE_JSC - -namespace facebook { -namespace jsi { - -namespace jscdirect { - -JSClassRef hostClass(Runtime& runtime); -JSClassRef functionClass(Runtime& runtime); - -JSValueRef hostGetProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, - JSValueRef* exception) { - auto* holder = static_cast(JSObjectGetPrivate(object)); - if (holder == nullptr || holder->hostObject == nullptr) { - return nullptr; - } - Runtime runtime(holder->state); - try { - Value result = holder->hostObject->get(runtime, PropNameID(stringToUtf8(propertyName))); - return result.isUndefined() ? nullptr : result.local(runtime); - } catch (const std::exception& error) { - setException(context, exception, error); - return JSValueMakeUndefined(context); - } -} - -bool hostSetProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, - JSValueRef value, JSValueRef* exception) { - auto* holder = static_cast(JSObjectGetPrivate(object)); - if (holder == nullptr || holder->hostObject == nullptr) { - return false; - } - Runtime runtime(holder->state); - try { - holder->hostObject->set(runtime, PropNameID(stringToUtf8(propertyName)), Value(runtime, value)); - return true; - } catch (const std::exception& error) { - setException(context, exception, error); - return true; - } -} - -void hostGetPropertyNames(JSContextRef, JSObjectRef object, - JSPropertyNameAccumulatorRef propertyNames) { - auto* holder = static_cast(JSObjectGetPrivate(object)); - if (holder == nullptr || holder->hostObject == nullptr) { - return; - } - Runtime runtime(holder->state); - try { - for (const auto& property : holder->hostObject->getPropertyNames(runtime)) { - JSStringRef name = makeJSString(property.utf8(runtime)); - JSPropertyNameAccumulatorAddName(propertyNames, name); - JSStringRelease(name); - } - } catch (const std::exception&) { - } -} - -void hostFinalize(JSObjectRef object) { - delete static_cast(JSObjectGetPrivate(object)); -} - -JSValueRef functionCall(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, - size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { - auto* holder = static_cast(JSObjectGetPrivate(function)); - if (holder == nullptr || !holder->callback) { - return JSValueMakeUndefined(context); - } - Runtime runtime(holder->state); - std::vector args; - args.reserve(argumentCount); - for (size_t i = 0; i < argumentCount; i++) { - args.emplace_back(runtime, arguments[i]); - } - try { - Value thisValue(runtime, thisObject); - Value result = - holder->callback(runtime, thisValue, args.empty() ? nullptr : args.data(), args.size()); - return result.local(runtime); - } catch (const std::exception& error) { - setException(context, exception, error); - return JSValueMakeUndefined(context); - } -} - -void functionFinalize(JSObjectRef object) { - delete static_cast(JSObjectGetPrivate(object)); -} - -JSClassRef hostClass(Runtime& runtime) { - auto state = runtime.state(); - if (state->hostClass == nullptr) { - JSClassDefinition definition = kJSClassDefinitionEmpty; - definition.className = "NativeScriptDirectHostObject"; - definition.getProperty = hostGetProperty; - definition.setProperty = hostSetProperty; - definition.getPropertyNames = hostGetPropertyNames; - definition.finalize = hostFinalize; - state->hostClass = JSClassCreate(&definition); - } - return state->hostClass; -} - -JSClassRef functionClass(Runtime& runtime) { - auto state = runtime.state(); - if (state->functionClass == nullptr) { - JSClassDefinition definition = kJSClassDefinitionEmpty; - definition.className = "NativeScriptDirectFunction"; - definition.callAsFunction = functionCall; - definition.finalize = functionFinalize; - state->functionClass = JSClassCreate(&definition); - } - return state->functionClass; -} - -void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function) { - if (context == nullptr || function == nullptr) { - return; - } - - JSValueRef exception = nullptr; - JSStringRef functionName = makeJSString("Function"); - JSValueRef functionValue = - JSObjectGetProperty(context, JSContextGetGlobalObject(context), functionName, &exception); - JSStringRelease(functionName); - if (exception != nullptr || functionValue == nullptr || - !JSValueIsObject(context, functionValue)) { - return; - } - - exception = nullptr; - JSObjectRef functionConstructor = JSValueToObject(context, functionValue, &exception); - if (exception != nullptr || functionConstructor == nullptr) { - return; - } - - JSStringRef prototypeName = makeJSString("prototype"); - JSValueRef prototypeValue = - JSObjectGetProperty(context, functionConstructor, prototypeName, &exception); - JSStringRelease(prototypeName); - if (exception != nullptr || prototypeValue == nullptr || - !JSValueIsObject(context, prototypeValue)) { - return; - } - - JSObjectSetPrototype(context, function, prototypeValue); -} - -} // namespace jscdirect - -Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken) { - auto* holder = new jscdirect::HostObjectHolder(runtime.state(), std::move(host), typeToken); - JSObjectRef object = JSObjectMake(runtime.context(), jscdirect::hostClass(runtime), holder); - return Object::fromValueStorage(Value(runtime, object).storage_); -} - -Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback) { - auto* holder = new jscdirect::FunctionHolder(runtime.state(), std::move(callback)); - JSObjectRef function = JSObjectMake(runtime.context(), jscdirect::functionClass(runtime), holder); - jscdirect::setFunctionPrototype(runtime.context(), function); - std::string functionName = name.utf8(runtime); - if (!functionName.empty()) { - JSStringRef property = jscdirect::makeJSString("name"); - JSStringRef valueString = jscdirect::makeJSString(functionName); - JSValueRef value = JSValueMakeString(runtime.context(), valueString); - JSObjectSetProperty(runtime.context(), function, property, value, kJSPropertyAttributeReadOnly, - nullptr); - JSStringRelease(valueString); - JSStringRelease(property); - } - return Function(Object::fromValueStorage(Value(runtime, function).storage_)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/jsc/NativeApiJSCRuntime.h b/NativeScript/ffi/jsc/NativeApiJSCRuntime.h deleted file mode 100644 index 305399f41..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSCRuntime.h +++ /dev/null @@ -1,839 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H -#define NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H - -#ifdef TARGET_ENGINE_JSC - -#import -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Metadata.h" -#include "MetadataReader.h" -#include "ffi.h" - -@protocol NativeApiJsiClassBuilderProtocol -@end - -#ifdef EMBED_METADATA_SIZE -extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; -#endif - -namespace facebook { -namespace jsi { - -class Runtime; -class Value; -class Object; -class Function; -class Array; -class String; -class BigInt; -class ArrayBuffer; - -class JSError : public std::runtime_error { - public: - JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} - explicit JSError(const std::string& message) : std::runtime_error(message) {} -}; - -class StringBuffer { - public: - explicit StringBuffer(std::string value) : value_(std::move(value)) {} - const char* data() const { return value_.data(); } - size_t size() const { return value_.size(); } - - private: - std::string value_; -}; - -class MutableBuffer { - public: - virtual ~MutableBuffer() = default; - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -class PropNameID { - public: - PropNameID() = default; - explicit PropNameID(std::string value) : value_(std::move(value)) {} - - static PropNameID forAscii(Runtime&, const char* value) { - return PropNameID(value != nullptr ? value : ""); - } - - static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } - - std::string utf8(Runtime&) const { return value_; } - - private: - std::string value_; -}; - -class HostObject { - public: - virtual ~HostObject() = default; - virtual Value get(Runtime& runtime, const PropNameID& name); - virtual void set(Runtime& runtime, const PropNameID& name, const Value& value); - virtual std::vector getPropertyNames(Runtime& runtime); -}; - -using HostFunctionType = std::function; - -namespace jscdirect { - -inline std::string stringToUtf8(JSStringRef string) { - if (string == nullptr) { - return {}; - } - size_t capacity = JSStringGetMaximumUTF8CStringSize(string); - std::string result(capacity, '\0'); - size_t written = JSStringGetUTF8CString(string, result.data(), capacity); - if (written == 0) { - return {}; - } - result.resize(written - 1); - return result; -} - -inline JSStringRef makeJSString(const std::string& value) { - NSString* string = [[NSString alloc] initWithBytes:value.data() - length:value.size() - encoding:NSUTF8StringEncoding]; - if (string == nil) { - return JSStringCreateWithUTF8CString(value.c_str()); - } - - NSUInteger length = [string length]; - std::vector characters(length); - if (length > 0) { - [string getCharacters:characters.data() range:NSMakeRange(0, length)]; - } - [string release]; - return JSStringCreateWithCharacters(characters.data(), length); -} - -inline JSStringRef makeJSString(const char* value) { - return JSStringCreateWithUTF8CString(value != nullptr ? value : ""); -} - -inline std::string valueToUtf8(JSContextRef context, JSValueRef value) { - if (value == nullptr) { - return {}; - } - JSValueRef exception = nullptr; - JSStringRef string = JSValueToStringCopy(context, value, &exception); - if (string == nullptr || exception != nullptr) { - if (string != nullptr) { - JSStringRelease(string); - } - return {}; - } - std::string result = stringToUtf8(string); - JSStringRelease(string); - return result; -} - -inline JSValueRef makeError(JSContextRef context, const std::string& message) { - JSStringRef string = makeJSString(message); - JSValueRef argument = JSValueMakeString(context, string); - JSStringRelease(string); - JSValueRef exception = nullptr; - JSObjectRef error = JSObjectMakeError(context, 1, &argument, &exception); - if (error != nullptr && exception == nullptr) { - return error; - } - return argument; -} - -inline void setException(JSContextRef context, JSValueRef* exception, const std::exception& error) { - if (exception != nullptr) { - *exception = makeError(context, error.what()); - } -} - -struct RuntimeState { - explicit RuntimeState(JSGlobalContextRef context) : context(context) {} - - ~RuntimeState() { - if (hostClass != nullptr) { - JSClassRelease(hostClass); - } - if (functionClass != nullptr) { - JSClassRelease(functionClass); - } - } - - JSGlobalContextRef context = nullptr; - JSClassRef hostClass = nullptr; - JSClassRef functionClass = nullptr; -}; - -struct ValueStorage { - enum class Kind { - Undefined, - Null, - Bool, - Number, - JSC, - }; - - explicit ValueStorage(Kind kind) : kind(kind) {} - - ~ValueStorage() { - if (context != nullptr && value != nullptr) { - JSValueUnprotect(context, value); - } - } - - Kind kind = Kind::Undefined; - bool boolValue = false; - double numberValue = 0; - JSGlobalContextRef context = nullptr; - JSValueRef value = nullptr; -}; - -template -const void* hostObjectTypeToken() { - static int token = 0; - return &token; -} - -struct HostObjectHolder { - HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, - const void* typeToken) - : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} - - std::shared_ptr state; - std::shared_ptr hostObject; - const void* typeToken = nullptr; -}; - -struct FunctionHolder { - FunctionHolder(std::shared_ptr state, HostFunctionType callback) - : state(std::move(state)), callback(std::move(callback)) {} - - std::shared_ptr state; - HostFunctionType callback; -}; - -struct ArrayBufferHolder { - explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} - - std::shared_ptr buffer; -}; - -} // namespace jscdirect - -class Runtime { - public: - explicit Runtime(JSGlobalContextRef context) - : state_(std::make_shared(context)) {} - - explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} - - JSGlobalContextRef context() const { return state_->context; } - std::shared_ptr state() const { return state_; } - - Object global(); - Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); - void drainMicrotasks() {} - - private: - std::shared_ptr state_; -}; - -class String { - public: - String() = default; - String(Runtime& runtime, JSStringRef string); - - static String createFromUtf8(Runtime& runtime, const char* value) { - JSStringRef string = jscdirect::makeJSString(value); - String result(runtime, string); - JSStringRelease(string); - return result; - } - - static String createFromUtf8(Runtime& runtime, const std::string& value) { - JSStringRef string = jscdirect::makeJSString(value); - String result(runtime, string); - JSStringRelease(string); - return result; - } - - static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { - std::string text(reinterpret_cast(value), length); - return createFromUtf8(runtime, text); - } - - std::string utf8(Runtime& runtime) const; - JSValueRef local(Runtime& runtime) const { return storage_->value; } - operator Value() const; - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class Value { - public: - Value() - : storage_( - std::make_shared(jscdirect::ValueStorage::Kind::Undefined)) {} - - Value(bool value) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::Bool)) { - storage_->boolValue = value; - } - - Value(double value) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::Number)) { - storage_->numberValue = value; - } - - Value(int value) : Value(static_cast(value)) {} - Value(uint32_t value) : Value(static_cast(value)) {} - - Value(Runtime& runtime, const Value& value) : storage_(value.storage_) {} - Value(Runtime& runtime, Value&& value) : storage_(std::move(value.storage_)) {} - Value(Runtime& runtime, const String& value) : storage_(value.storage_) {} - Value(Runtime& runtime, const Object& object); - Value(Runtime& runtime, const Function& function); - Value(Runtime& runtime, const Array& array); - Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); - Value(Runtime& runtime, const BigInt& bigint); - Value(Runtime& runtime, JSValueRef value) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); - JSValueProtect(runtime.context(), storage_->value); - } - - static Value undefined() { return Value(); } - static Value null() { - Value value; - value.storage_ = std::make_shared(jscdirect::ValueStorage::Kind::Null); - return value; - } - - bool isUndefined() const { - return storage_->kind == jscdirect::ValueStorage::Kind::Undefined || - (storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsUndefined(storage_->context, storage_->value)); - } - bool isNull() const { - return storage_->kind == jscdirect::ValueStorage::Kind::Null || - (storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsNull(storage_->context, storage_->value)); - } - bool isBool() const { - return storage_->kind == jscdirect::ValueStorage::Kind::Bool || - (storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsBoolean(storage_->context, storage_->value)); - } - bool getBool() const { - if (storage_->kind == jscdirect::ValueStorage::Kind::Bool) { - return storage_->boolValue; - } - if (storage_->kind == jscdirect::ValueStorage::Kind::JSC) { - return JSValueToBoolean(storage_->context, storage_->value); - } - return false; - } - bool isNumber() const { - return storage_->kind == jscdirect::ValueStorage::Kind::Number || - (storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsNumber(storage_->context, storage_->value)); - } - double getNumber() const { - if (storage_->kind == jscdirect::ValueStorage::Kind::Number) { - return storage_->numberValue; - } - if (storage_->kind == jscdirect::ValueStorage::Kind::JSC) { - return JSValueToNumber(storage_->context, storage_->value, nullptr); - } - return 0; - } - - bool isObject() const { - return storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsObject(storage_->context, storage_->value); - } - bool isString() const { - return storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsString(storage_->context, storage_->value); - } - bool isBigInt() const { - if (storage_->kind != jscdirect::ValueStorage::Kind::JSC) { - return false; - } - if (__builtin_available(macOS 15.0, iOS 18.0, *)) { - return JSValueIsBigInt(storage_->context, storage_->value); - } - return false; - } - bool isSymbol() const { - return storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsSymbol(storage_->context, storage_->value); - } - - Object asObject(Runtime& runtime) const; - String asString(Runtime& runtime) const; - BigInt getBigInt(Runtime& runtime) const; - - JSValueRef local(Runtime& runtime) const { - switch (storage_->kind) { - case jscdirect::ValueStorage::Kind::Undefined: - return JSValueMakeUndefined(runtime.context()); - case jscdirect::ValueStorage::Kind::Null: - return JSValueMakeNull(runtime.context()); - case jscdirect::ValueStorage::Kind::Bool: - return JSValueMakeBoolean(runtime.context(), storage_->boolValue); - case jscdirect::ValueStorage::Kind::Number: - return JSValueMakeNumber(runtime.context(), storage_->numberValue); - case jscdirect::ValueStorage::Kind::JSC: - return storage_->value; - } - } - - private: - friend class Runtime; - friend class Object; - friend class String; - friend class BigInt; - friend class ArrayBuffer; - friend class Function; - friend class Array; - std::shared_ptr storage_; -}; - -class Object { - public: - Object() = default; - explicit Object(Runtime& runtime) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = JSObjectMake(runtime.context(), nullptr, nullptr); - JSValueProtect(runtime.context(), storage_->value); - } - - static Object fromValueStorage(std::shared_ptr storage) { - Object object; - object.storage_ = std::move(storage); - return object; - } - - template - static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - jscdirect::hostObjectTypeToken()); - } - - Value getProperty(Runtime& runtime, const char* name) const { - JSStringRef property = jscdirect::makeJSString(name); - JSValueRef exception = nullptr; - JSValueRef result = - JSObjectGetProperty(runtime.context(), local(runtime), property, &exception); - JSStringRelease(property); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Value getProperty(Runtime& runtime, const std::string& name) const { - return getProperty(runtime, name.c_str()); - } - - Value getProperty(Runtime& runtime, const Value& key) const { - JSValueRef exception = nullptr; - JSValueRef result = JSObjectGetPropertyForKey(runtime.context(), local(runtime), - key.local(runtime), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Object getPropertyAsObject(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime); - } - - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - void setProperty(Runtime& runtime, const char* name, const Value& value) { - JSStringRef property = jscdirect::makeJSString(name); - JSValueRef exception = nullptr; - JSObjectSetProperty(runtime.context(), local(runtime), property, value.local(runtime), - kJSPropertyAttributeNone, &exception); - JSStringRelease(property); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - } - - void setProperty(Runtime& runtime, const char* name, const String& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Object& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Function& value); - void setProperty(Runtime& runtime, const char* name, const Array& value); - void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); - void setProperty(Runtime& runtime, const char* name, bool value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const char* name, double value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const std::string& name, const Value& value) { - setProperty(runtime, name.c_str(), value); - } - void setProperty(Runtime& runtime, const Value& key, const Value& value) { - JSValueRef exception = nullptr; - JSObjectSetPropertyForKey(runtime.context(), local(runtime), key.local(runtime), - value.local(runtime), kJSPropertyAttributeNone, &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - } - - bool hasProperty(Runtime& runtime, const char* name) const { - JSStringRef property = jscdirect::makeJSString(name); - bool result = JSObjectHasProperty(runtime.context(), local(runtime), property); - JSStringRelease(property); - return result; - } - - bool isFunction(Runtime& runtime) const { - return JSObjectIsFunction(runtime.context(), local(runtime)); - } - - bool isArray(Runtime& runtime) const { - JSStringRef name = jscdirect::makeJSString("Array"); - JSValueRef constructorValue = JSObjectGetProperty( - runtime.context(), JSContextGetGlobalObject(runtime.context()), name, nullptr); - JSStringRelease(name); - if (constructorValue == nullptr || !JSValueIsObject(runtime.context(), constructorValue)) { - return false; - } - JSObjectRef constructor = JSValueToObject(runtime.context(), constructorValue, nullptr); - JSValueRef exception = nullptr; - bool result = - JSValueIsInstanceOfConstructor(runtime.context(), local(runtime), constructor, &exception); - return exception == nullptr && result; - } - - bool isArrayBuffer(Runtime& runtime) const { - JSValueRef exception = nullptr; - JSTypedArrayType type = - JSValueGetTypedArrayType(runtime.context(), storage_->value, &exception); - return exception == nullptr && type == kJSTypedArrayTypeArrayBuffer; - } - - Function asFunction(Runtime& runtime) const; - Array getArray(Runtime& runtime) const; - ArrayBuffer getArrayBuffer(Runtime& runtime) const; - Array getPropertyNames(Runtime& runtime) const; - - template - bool isHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - return holder != nullptr && holder->typeToken == jscdirect::hostObjectTypeToken(); - } - - template - std::shared_ptr getHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - if (holder == nullptr || holder->typeToken != jscdirect::hostObjectTypeToken()) { - return nullptr; - } - return std::static_pointer_cast(holder->hostObject); - } - - JSObjectRef local(Runtime& runtime) const { - return reinterpret_cast(const_cast(storage_->value)); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - protected: - friend class Value; - friend class Runtime; - friend class Function; - friend class Array; - friend class ArrayBuffer; - - explicit Object(std::shared_ptr storage) - : storage_(std::move(storage)) {} - - static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken); - - jscdirect::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { - return static_cast(JSObjectGetPrivate(local(runtime))); - } - - std::shared_ptr storage_; -}; - -class Function : public Object { - public: - Function() = default; - explicit Function(Object object) : Object(std::move(object.storage_)) {} - - static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback); - - Value call(Runtime& runtime, const Value* args, size_t count) const { - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValueRef exception = nullptr; - JSValueRef result = JSObjectCallAsFunction( - runtime.context(), local(runtime), JSContextGetGlobalObject(runtime.context()), argv.size(), - argv.empty() ? nullptr : argv.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Value call(Runtime& runtime) const { - return call(runtime, static_cast(nullptr), 0); - } - Value call(Runtime& runtime, std::nullptr_t, size_t) const { - return call(runtime, static_cast(nullptr), 0); - } - template - Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { - return call(runtime, static_cast(args), count); - } - template - Value call(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return call(runtime, static_cast(argv), sizeof...(Args)); - } - - Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, - size_t count = 0) const { - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValueRef exception = nullptr; - JSValueRef result = - JSObjectCallAsFunction(runtime.context(), local(runtime), thisObject.local(runtime), - argv.size(), argv.empty() ? nullptr : argv.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValueRef exception = nullptr; - JSValueRef result = JSObjectCallAsConstructor(runtime.context(), local(runtime), argv.size(), - argv.empty() ? nullptr : argv.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { - return callAsConstructor(runtime, static_cast(nullptr), 0); - } - template - Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { - return callAsConstructor(runtime, static_cast(args), count); - } - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); - } -}; - -class Array : public Object { - public: - explicit Array(Runtime& runtime, size_t size) - : Object(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - std::vector initial(size, JSValueMakeUndefined(runtime.context())); - JSValueRef exception = nullptr; - storage_->context = runtime.context(); - storage_->value = - JSObjectMakeArray(runtime.context(), initial.size(), initial.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - JSValueProtect(runtime.context(), storage_->value); - } - - explicit Array(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { - Value length = getProperty(runtime, "length"); - return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; - } - - Value getValueAtIndex(Runtime& runtime, size_t index) const { - JSValueRef exception = nullptr; - JSValueRef result = JSObjectGetPropertyAtIndex(runtime.context(), local(runtime), - static_cast(index), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { - JSValueRef exception = nullptr; - JSObjectSetPropertyAtIndex(runtime.context(), local(runtime), static_cast(index), - value.local(runtime), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - } - void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { - setValueAtIndex(runtime, index, Value(runtime, value)); - } -}; - -class BigInt { - public: - BigInt() = default; - BigInt(Runtime& runtime, JSValueRef value) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = value; - JSValueProtect(runtime.context(), storage_->value); - } - - static BigInt fromInt64(Runtime& runtime, int64_t value) { - JSValueRef exception = nullptr; - JSValueRef result = nullptr; - if (__builtin_available(macOS 15.0, iOS 18.0, *)) { - result = JSBigIntCreateWithInt64(runtime.context(), value, &exception); - } - if (result == nullptr || exception != nullptr) { - result = JSValueMakeNumber(runtime.context(), static_cast(value)); - } - return BigInt(runtime, result); - } - - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - JSValueRef exception = nullptr; - JSValueRef result = nullptr; - if (__builtin_available(macOS 15.0, iOS 18.0, *)) { - result = JSBigIntCreateWithUInt64(runtime.context(), value, &exception); - } - if (result == nullptr || exception != nullptr) { - result = JSValueMakeNumber(runtime.context(), static_cast(value)); - } - return BigInt(runtime, result); - } - - String toString(Runtime& runtime, int) const { - JSValueRef exception = nullptr; - JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); - if (string == nullptr || exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - String result(runtime, string); - JSStringRelease(string); - return result; - } - - JSValueRef local(Runtime& runtime) const { return storage_->value; } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class ArrayBuffer : public Object { - public: - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : Object(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - auto* holder = new jscdirect::ArrayBufferHolder(std::move(buffer)); - JSValueRef exception = nullptr; - storage_->context = runtime.context(); - storage_->value = JSObjectMakeArrayBufferWithBytesNoCopy( - runtime.context(), holder->buffer->data(), holder->buffer->size(), - [](void*, void* deallocatorContext) { - delete static_cast(deallocatorContext); - }, - holder, &exception); - if (exception != nullptr) { - delete holder; - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - JSValueProtect(runtime.context(), storage_->value); - } - - explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { - JSValueRef exception = nullptr; - return JSObjectGetArrayBufferByteLength(runtime.context(), local(runtime), &exception); - } - - uint8_t* data(Runtime& runtime) const { - JSValueRef exception = nullptr; - return static_cast( - JSObjectGetArrayBufferBytesPtr(runtime.context(), local(runtime), &exception)); - } -}; -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_JSC - -#endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H diff --git a/NativeScript/ffi/jsc/NativeApiJSCValue.mm b/NativeScript/ffi/jsc/NativeApiJSCValue.mm deleted file mode 100644 index ca4071567..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSCValue.mm +++ /dev/null @@ -1,83 +0,0 @@ -#include "NativeApiJSCRuntime.h" - -#ifdef TARGET_ENGINE_JSC - -namespace facebook { -namespace jsi { - -Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } -void HostObject::set(Runtime&, const PropNameID&, const Value&) {} -std::vector HostObject::getPropertyNames(Runtime&) { return {}; } - -String::String(Runtime& runtime, JSStringRef string) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = JSValueMakeString(runtime.context(), string); - JSValueProtect(runtime.context(), storage_->value); -} - -std::string String::utf8(Runtime& runtime) const { - return jscdirect::valueToUtf8(runtime.context(), storage_->value); -} - -String::operator Value() const { - Value value; - value.storage_ = storage_; - return value; -} - -Value::Value(Runtime&, const Object& object) : storage_(object.storage_) {} -Value::Value(Runtime&, const Function& function) : storage_(function.storage_) {} -Value::Value(Runtime&, const Array& array) : storage_(array.storage_) {} -Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) : storage_(arrayBuffer.storage_) {} -Value::Value(Runtime&, const BigInt& bigint) : storage_(bigint.storage_) {} - -Object Value::asObject(Runtime&) const { return Object::fromValueStorage(storage_); } - -String Value::asString(Runtime& runtime) const { - JSValueRef exception = nullptr; - JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); - if (string == nullptr || exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - String result(runtime, string); - JSStringRelease(string); - return result; -} - -BigInt Value::getBigInt(Runtime& runtime) const { return BigInt(runtime, local(runtime)); } - -Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime).asFunction(runtime); -} -Function Object::asFunction(Runtime&) const { return Function(*this); } -Array Object::getArray(Runtime&) const { return Array(*this); } -ArrayBuffer Object::getArrayBuffer(Runtime&) const { return ArrayBuffer(*this); } - -Array Object::getPropertyNames(Runtime& runtime) const { - JSPropertyNameArrayRef propertyNames = - JSObjectCopyPropertyNames(runtime.context(), local(runtime)); - size_t count = JSPropertyNameArrayGetCount(propertyNames); - Array result(runtime, count); - for (size_t i = 0; i < count; i++) { - JSStringRef name = JSPropertyNameArrayGetNameAtIndex(propertyNames, i); - result.setValueAtIndex(runtime, i, String(runtime, name)); - } - JSPropertyNameArrayRelease(propertyNames); - return result; -} - -void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { - setProperty(runtime, name, Value(runtime, value)); -} -void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { - setProperty(runtime, name, Value(runtime, value)); -} -void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/napi/CallbackThreading.h b/NativeScript/ffi/napi/CallbackThreading.h deleted file mode 100644 index 63c17990f..000000000 --- a/NativeScript/ffi/napi/CallbackThreading.h +++ /dev/null @@ -1,168 +0,0 @@ -#ifndef CALLBACK_THREADING_H -#define CALLBACK_THREADING_H - -#include "js_native_api.h" - -#include -#include - -#if defined(ENABLE_JS_RUNTIME) -#include "jsr.h" -#endif - -namespace nativescript { - -namespace detail { - -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) -inline std::atomic native_call_unlocked_runtime_count{0}; -inline thread_local int native_caller_thread_callback_depth = 0; -#endif - -} // namespace detail - -inline bool isNativeCallRuntimeUnlockedForCallbacks() { -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) - return detail::native_call_unlocked_runtime_count.load( - std::memory_order_acquire) > 0; -#else - return false; -#endif -} - -inline bool isNativeCallerThreadCallbackActive() { -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) - return detail::native_caller_thread_callback_depth > 0; -#else - return false; -#endif -} - -inline bool shouldInvokeCallbackOnNativeCallerThread() { -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) - return isNativeCallRuntimeUnlockedForCallbacks() || - isNativeCallerThreadCallbackActive(); -#else - return false; -#endif -} - -class NativeCallRuntimeUnlockScope final { - public: - explicit NativeCallRuntimeUnlockScope(napi_env env) { -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) - if (isNativeCallerThreadCallbackActive()) { - return; - } - - auto it = JSR::env_to_jsr_cache.find(env); - if (it == JSR::env_to_jsr_cache.end() || it->second == nullptr) { - return; - } - - jsr_ = it->second; - unlockedDepth_ = js_current_env_lock_depth(env); - for (int i = 0; i < unlockedDepth_; i++) { - jsr_->unlock(); - } - if (unlockedDepth_ == 0 && jsr_->runtime != nullptr) { - runtime_ = jsr_->runtime.get(); - runtime_->unlock(); - unlockedRuntime_ = true; - } - if (unlockedDepth_ > 0 || unlockedRuntime_) { - didUnlock_ = true; - detail::native_call_unlocked_runtime_count.fetch_add( - 1, std::memory_order_release); - } -#else - (void)env; -#endif - } - - ~NativeCallRuntimeUnlockScope() { -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) - if (didUnlock_) { - detail::native_call_unlocked_runtime_count.fetch_sub( - 1, std::memory_order_release); - } - if (jsr_ != nullptr) { - for (int i = 0; i < unlockedDepth_; i++) { - jsr_->lock(); - } - } - if (unlockedRuntime_ && runtime_ != nullptr) { - runtime_->lock(); - } -#endif - } - - NativeCallRuntimeUnlockScope(const NativeCallRuntimeUnlockScope&) = delete; - NativeCallRuntimeUnlockScope& operator=(const NativeCallRuntimeUnlockScope&) = - delete; - - private: -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) - JSR* jsr_ = nullptr; - facebook::jsi::ThreadSafeRuntime* runtime_ = nullptr; -#endif - int unlockedDepth_ = 0; - bool unlockedRuntime_ = false; - bool didUnlock_ = false; -}; - -class NativeCallbackScope final { - public: - explicit NativeCallbackScope(napi_env env) : env_(env) { -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) - if (isNativeCallerThreadCallbackActive() || - js_current_env_lock_depth(env_) > 0) { - napi_open_handle_scope(env_, &napiHandleScope_); - return; - } - - auto it = JSR::env_to_jsr_cache.find(env_); - if (it != JSR::env_to_jsr_cache.end() && it->second != nullptr) { - jsr_ = it->second; - jsr_->lock(); - detail::native_caller_thread_callback_depth += 1; - napi_open_handle_scope(env_, &napiHandleScope_); - return; - } -#endif -#if defined(ENABLE_JS_RUNTIME) - napiScope_ = std::make_unique(env_); -#else - (void)env_; -#endif - } - - ~NativeCallbackScope() { -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) - if (napiHandleScope_ != nullptr) { - napi_close_handle_scope(env_, napiHandleScope_); - } - if (jsr_ != nullptr) { - detail::native_caller_thread_callback_depth -= 1; - jsr_->unlock(); - } -#endif - } - - NativeCallbackScope(const NativeCallbackScope&) = delete; - NativeCallbackScope& operator=(const NativeCallbackScope&) = delete; - - private: - napi_env env_; -#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) - JSR* jsr_ = nullptr; - napi_handle_scope napiHandleScope_ = nullptr; -#endif -#if defined(ENABLE_JS_RUNTIME) - std::unique_ptr napiScope_; -#endif -}; - -} // namespace nativescript - -#endif // CALLBACK_THREADING_H diff --git a/NativeScript/ffi/napi/Cif.mm b/NativeScript/ffi/napi/Cif.mm deleted file mode 100644 index dfe18c8c3..000000000 --- a/NativeScript/ffi/napi/Cif.mm +++ /dev/null @@ -1,536 +0,0 @@ -#include "Cif.h" -#include -#include -#include -#include -#include -#include -#include -#include "Metadata.h" -#include "MetadataReader.h" -#include "ObjCBridge.h" -#include "TypeConv.h" -#include "Util.h" - -namespace nativescript { -namespace { - -constexpr uint64_t kFNV64OffsetBasis = 14695981039346656037ull; -constexpr uint64_t kFNV64Prime = 1099511628211ull; - -uint64_t hashBytesFnv1a(const void* data, size_t size, uint64_t seed = kFNV64OffsetBasis) { - const auto* bytes = static_cast(data); - uint64_t hash = seed; - for (size_t i = 0; i < size; i++) { - hash ^= static_cast(bytes[i]); - hash *= kFNV64Prime; - } - return hash; -} - -MDTypeKind canonicalizeSignatureTypeKind(MDTypeKind kind) { - switch (kind) { - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return mdTypeAnyObject; - default: - return kind; - } -} - -template -void appendIntegralToHash(uint64_t* hash, T value) { - using Unsigned = typename std::make_unsigned::type; - Unsigned unsignedValue = static_cast(value); - for (size_t i = 0; i < sizeof(Unsigned); i++) { - const uint8_t byte = static_cast((unsignedValue >> (i * 8)) & 0xFF); - *hash = hashBytesFnv1a(&byte, sizeof(byte), *hash); - } -} - -bool appendMetadataSignatureHash(MDMetadataReader* reader, MDSectionOffset signatureOffset, - std::unordered_set* activeSignatures, - uint64_t* hash); - -inline bool typeRequiresSlowGeneratedNapiDispatch(const std::shared_ptr& type) { - if (type == nullptr) { - return false; - } - - switch (type->kind) { - case mdTypeUChar: - case mdTypeUInt8: - case mdTypeString: - case mdTypePointer: - case mdTypeStruct: - case mdTypeArray: - case mdTypeBlock: - case mdTypeFunctionPointer: - case mdTypeVector: - case mdTypeExtVector: - case mdTypeComplex: - return true; - default: - return false; - } -} - -inline bool typeKindMayUseRoundTripCache(MDTypeKind kind) { - switch (kind) { - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return true; - default: - return false; - } -} - -inline void updateGeneratedNapiDispatchCompatibility(Cif* cif) { - if (cif == nullptr) { - return; - } - - cif->skipGeneratedNapiDispatch = false; - cif->generatedDispatchHasRoundTripCacheArgument = false; - cif->generatedDispatchUsesObjectReturnStorage = false; - - if (cif->returnType != nullptr) { - cif->generatedDispatchUsesObjectReturnStorage = - typeKindMayUseRoundTripCache(cif->returnType->kind); - } - - cif->skipGeneratedNapiDispatch = typeRequiresSlowGeneratedNapiDispatch(cif->returnType); - if (cif->skipGeneratedNapiDispatch) { - return; - } - - for (const auto& argType : cif->argTypes) { - if (argType != nullptr && typeKindMayUseRoundTripCache(argType->kind)) { - cif->generatedDispatchHasRoundTripCacheArgument = true; - } - if (typeRequiresSlowGeneratedNapiDispatch(argType)) { - cif->skipGeneratedNapiDispatch = true; - return; - } - } -} - -bool appendMetadataTypeHash(MDMetadataReader* reader, MDSectionOffset* offset, - std::unordered_set* activeSignatures, uint64_t* hash) { - if (reader == nullptr || offset == nullptr || hash == nullptr || activeSignatures == nullptr) { - return false; - } - - const MDTypeKind kindWithFlags = reader->getTypeKind(*offset); - *offset += sizeof(MDTypeKind); - const MDTypeKind rawKind = - static_cast((kindWithFlags & ~mdTypeFlagNext) & ~mdTypeFlagVariadic); - - appendIntegralToHash(hash, 0xB0); - const MDTypeKind canonicalKind = canonicalizeSignatureTypeKind(rawKind); - appendIntegralToHash(hash, static_cast(canonicalKind)); - - switch (rawKind) { - case mdTypeArray: - case mdTypeVector: - case mdTypeExtVector: - case mdTypeComplex: { - const auto arraySize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - appendIntegralToHash(hash, arraySize); - if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) { - return false; - } - break; - } - - case mdTypeStruct: { - const auto structOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - appendIntegralToHash(hash, structOffset); - break; - } - - case mdTypeClassObject: { - auto classOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool hasNext = (classOffset & mdSectionOffsetNext) != 0; - while (hasNext) { - auto protocolOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - hasNext = (protocolOffset & mdSectionOffsetNext) != 0; - } - break; - } - - case mdTypeProtocolObject: { - bool hasNext = true; - while (hasNext) { - auto protocolOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - hasNext = (protocolOffset & mdSectionOffsetNext) != 0; - } - break; - } - - case mdTypePointer: - if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) { - return false; - } - break; - - case mdTypeBlock: - case mdTypeFunctionPointer: { - const auto nestedSignatureOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - if (nestedSignatureOffset != MD_SECTION_OFFSET_NULL) { - const auto nestedAbsoluteOffset = reader->signaturesOffset + nestedSignatureOffset; - if (!appendMetadataSignatureHash(reader, nestedAbsoluteOffset, activeSignatures, hash)) { - return false; - } - } - break; - } - - default: - break; - } - - appendIntegralToHash(hash, 0xBF); - return true; -} - -bool appendMetadataSignatureHash(MDMetadataReader* reader, MDSectionOffset signatureOffset, - std::unordered_set* activeSignatures, - uint64_t* hash) { - if (reader == nullptr || hash == nullptr || activeSignatures == nullptr) { - return false; - } - - if (activeSignatures->find(signatureOffset) != activeSignatures->end()) { - appendIntegralToHash(hash, 0xEE); - return true; - } - activeSignatures->insert(signatureOffset); - - MDSectionOffset offset = signatureOffset; - const MDTypeKind returnTypeKind = reader->getTypeKind(offset); - bool next = (returnTypeKind & mdTypeFlagNext) != 0; - const bool isVariadic = (returnTypeKind & mdTypeFlagVariadic) != 0; - - appendIntegralToHash(hash, 0xA0); - appendIntegralToHash(hash, isVariadic ? 1 : 0); - - if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) { - activeSignatures->erase(signatureOffset); - return false; - } - - uint32_t argCount = 0; - while (next) { - const MDTypeKind argTypeKind = reader->getTypeKind(offset); - next = (argTypeKind & mdTypeFlagNext) != 0; - if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) { - activeSignatures->erase(signatureOffset); - return false; - } - argCount++; - } - - appendIntegralToHash(hash, argCount); - appendIntegralToHash(hash, 0xAF); - - activeSignatures->erase(signatureOffset); - return true; -} - -} // namespace - -// Essentially, we cache libffi structures per unique method signature, -// this helps us avoid the overhead of creating them on the fly for each -// invocation. -Cif* ObjCBridgeState::getMethodCif(napi_env env, Method method) { - auto encoding = std::string(method_getTypeEncoding(method)); - auto find = this->cifs[encoding]; - if (find != nullptr) { - return find; - } - - auto cif = new Cif(env, method); - this->cifs[encoding] = cif; - - return cif; -} - -Cif* ObjCBridgeState::getMethodCif(napi_env env, MDSectionOffset offset) { - auto find = this->mdMethodSignatureCache[offset]; - if (find != nullptr) { - return find; - } - - auto cif = new Cif(env, metadata, offset, true, false); - this->mdMethodSignatureCache[offset] = cif; - - return cif; -} - -Cif* ObjCBridgeState::getBlockCif(napi_env env, MDSectionOffset offset) { - auto find = this->mdBlockSignatureCache[offset]; - if (find != nullptr) { - return find; - } - - auto cif = new Cif(env, metadata, offset, false, true); - this->mdBlockSignatureCache[offset] = cif; - - return cif; -} - -Cif* ObjCBridgeState::getCFunctionCif(napi_env env, MDSectionOffset offset) { - auto find = this->mdFunctionSignatureCache[offset]; - if (find != nullptr) { - return find; - } - - auto cif = new Cif(env, metadata, offset, false, false); - this->mdFunctionSignatureCache[offset] = cif; - - return cif; -} - -Cif::Cif(napi_env env, std::string encoding, unsigned int implicitArgc) { - auto signature = [NSMethodSignature signatureWithObjCTypes:encoding.c_str()]; - unsigned long numberOfArguments = signature.numberOfArguments; - unsigned long skippedArgs = std::min(numberOfArguments, implicitArgc); - this->argc = (int)(numberOfArguments - skippedArgs); - this->argv = (napi_value*)malloc(sizeof(napi_value) * this->argc); - - unsigned int totalArgc = (unsigned int)numberOfArguments; - - const char* returnType = signature.methodReturnType; - this->returnType = TypeConv::Make(env, &returnType); - - ffi_type* rtype = this->returnType->type; - this->atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); - - unsigned long methodReturnLength = signature.methodReturnLength; - unsigned long frameLength = signature.frameLength; - - this->rvalue = malloc(methodReturnLength); - this->rvalueLength = methodReturnLength; - this->frameLength = frameLength; - - this->avalues = this->argc > 0 ? (void**)malloc(sizeof(void*) * this->argc) : nullptr; - if (this->avalues != nullptr) { - memset(this->avalues, 0, sizeof(void*) * this->argc); - } - this->shouldFree = (bool*)malloc(sizeof(bool) * this->argc); - memset(this->shouldFree, false, sizeof(bool) * this->argc); - this->shouldFreeAny = false; - this->avaluesAllocStart = 0; - this->avaluesAllocCount = 0; - - for (int i = 0; i < numberOfArguments; i++) { - const char* argenc = [signature getArgumentTypeAtIndex:i]; - - auto argTypeInfo = TypeConv::Make(env, &argenc); - this->atypes[i] = argTypeInfo->ffiTypeForArgument(); - - if (i >= skippedArgs) { - this->argTypes.push_back(argTypeInfo); - } - } - - ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgc, rtype, this->atypes); - - if (status != FFI_OK) { - std::cout << "Failed to prepare CIF, libffi returned error:" << status << std::endl; - return; - } - - for (unsigned int i = 0; i < this->argc; i++) { - this->avalues[i] = malloc(cif.arg_types[i + skippedArgs]->size); - this->avaluesAllocCount++; - } - - updateGeneratedNapiDispatchCompatibility(this); -} - -Cif::Cif(napi_env env, Method method) { - const unsigned int totalArgc = method_getNumberOfArguments(method); - this->argc = totalArgc >= 2 ? totalArgc - 2 : 0; - this->argv = this->argc > 0 ? (napi_value*)malloc(sizeof(napi_value) * this->argc) : nullptr; - - char* returnTypeEnc = method_copyReturnType(method); - const char* returnTypePtr = returnTypeEnc; - this->returnType = TypeConv::Make(env, &returnTypePtr); - if (returnTypeEnc != nullptr) { - free(returnTypeEnc); - } - - ffi_type* rtype = this->returnType->type; - this->atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); - - this->rvalueLength = std::max(1, rtype->size); - this->rvalue = malloc(this->rvalueLength); - this->frameLength = 0; - - this->avalues = this->argc > 0 ? (void**)malloc(sizeof(void*) * this->argc) : nullptr; - if (this->avalues != nullptr) { - memset(this->avalues, 0, sizeof(void*) * this->argc); - } - - this->shouldFree = this->argc > 0 ? (bool*)malloc(sizeof(bool) * this->argc) : nullptr; - if (this->shouldFree != nullptr) { - memset(this->shouldFree, false, sizeof(bool) * this->argc); - } - this->shouldFreeAny = false; - this->avaluesAllocStart = 0; - this->avaluesAllocCount = 0; - - for (unsigned int i = 0; i < totalArgc; i++) { - char* argEnc = method_copyArgumentType(method, i); - const char* argEncPtr = argEnc; - auto argTypeInfo = TypeConv::Make(env, &argEncPtr); - if (argEnc != nullptr) { - free(argEnc); - } - - this->atypes[i] = argTypeInfo->ffiTypeForArgument(); - if (i >= 2) { - this->argTypes.push_back(argTypeInfo); - } - } - - ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgc, rtype, this->atypes); - if (status != FFI_OK) { - std::cout << "Failed to prepare CIF, libffi returned error:" << status << std::endl; - return; - } - - for (unsigned int i = 0; i < this->argc; i++) { - this->avalues[i] = malloc(cif.arg_types[i + 2]->size); - this->avaluesAllocCount++; - } - - updateGeneratedNapiDispatchCompatibility(this); -} - -Cif::Cif(napi_env env, MDMetadataReader* reader, MDSectionOffset offset, bool isMethod, - bool isBlock) { - MDSectionOffset signatureStart = offset; - auto returnTypeKind = reader->getTypeKind(offset); - bool next = ((MDTypeFlag)returnTypeKind & mdTypeFlagNext) != 0; - isVariadic = ((MDTypeFlag)returnTypeKind & mdTypeFlagVariadic) != 0; - - returnType = TypeConv::Make(env, reader, &offset); - - auto implicitArgs = isMethod ? 2 : isBlock ? 1 : 0; - - shouldFreeAny = false; - atypes = nullptr; - avaluesAllocStart = 0; - avaluesAllocCount = 0; - - if (next || isMethod || isBlock) { - while (next) { - auto argTypeKind = reader->getTypeKind(offset); - next = ((MDTypeFlag)argTypeKind & mdTypeFlagNext) != 0; - auto argTypeInfo = TypeConv::Make(env, reader, &offset); - std::string enc; - argTypeInfo->encode(&enc); - argTypes.push_back(argTypeInfo); - } - - argc = (int)argTypes.size(); - - auto totalArgc = argc + implicitArgs; - - argv = (napi_value*)malloc(sizeof(napi_value) * argc); - shouldFree = (bool*)malloc(sizeof(bool) * argc); - - atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); - avalues = (void**)malloc(sizeof(void*) * argc); - memset(avalues, 0, sizeof(void*) * argc); - - if (isMethod) { - atypes[0] = &ffi_type_pointer; - atypes[1] = &ffi_type_pointer; - } - - if (isBlock) { - atypes[0] = &ffi_type_pointer; - } - - for (int i = 0; i < argc; i++) { - atypes[i + implicitArgs] = argTypes[i]->ffiTypeForArgument(); - shouldFree[i] = false; - } - } else { - argc = 0; - argv = nullptr; - avalues = nullptr; - shouldFree = nullptr; - } - - ffi_status status = - ffi_prep_cif(&cif, FFI_DEFAULT_ABI, argc + implicitArgs, returnType->type, atypes); - - if (status != FFI_OK) { - std::cout << "Failed to prepare CIF, libffi returned error: " << status << std::endl; - return; - } - - for (int i = 0; i < argc; i++) { - avalues[i] = malloc(cif.arg_types[i + implicitArgs]->size); - avaluesAllocCount++; - } - - rvalue = malloc(cif.rtype->size); - rvalueLength = cif.rtype->size; - - if (signatureStart != MD_SECTION_OFFSET_NULL) { - uint64_t canonicalSignatureHash = kFNV64OffsetBasis; - std::unordered_set activeSignatures; - if (appendMetadataSignatureHash(reader, signatureStart, &activeSignatures, - &canonicalSignatureHash)) { - signatureHash = canonicalSignatureHash; - } - } - - updateGeneratedNapiDispatchCompatibility(this); -} - -Cif::~Cif() { - if (rvalue != nullptr) { - free(rvalue); - } - if (argv != nullptr) { - free(argv); - } - if (avalues != nullptr) { - for (unsigned int i = 0; i < avaluesAllocCount; i++) { - auto index = avaluesAllocStart + i; - if (avalues[index] != nullptr) { - free(avalues[index]); - } - } - free(avalues); - } - if (atypes != nullptr) { - free(atypes); - } - if (shouldFree != nullptr) { - free(shouldFree); - } -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/Class.mm b/NativeScript/ffi/napi/Class.mm deleted file mode 100644 index 12aea8230..000000000 --- a/NativeScript/ffi/napi/Class.mm +++ /dev/null @@ -1,1034 +0,0 @@ -#include "Class.h" -#include "ClassBuilder.h" -#include "ClassMember.h" -#include "Interop.h" -#include "Metadata.h" -#include "MetadataReader.h" -#include "ObjCBridge.h" -#include "Protocol.h" -#include "Util.h" -#include "js_native_api.h" -#include "js_native_api_types.h" -#include "node_api_util.h" - -#import -#include -#include - -namespace nativescript { - -napi_value JS_NSObject_alloc(napi_env env, napi_callback_info cbinfo); - -inline bool tryGetInteropPointerArg(napi_env env, napi_value value, void** out) { - if (out == nullptr || value == nullptr) { - return false; - } - - *out = nullptr; - - napi_valuetype argType = napi_undefined; - napi_typeof(env, value, &argType); - - if (argType == napi_external) { - return napi_get_value_external(env, value, out) == napi_ok && *out != nullptr; - } - - if (argType == napi_bigint) { - uint64_t raw = 0; - bool lossless = false; - if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) { - return false; - } - *out = reinterpret_cast(raw); - return *out != nullptr; - } - - if (argType != napi_object) { - return false; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *out = ptr != nullptr ? ptr->data : nullptr; - return *out != nullptr; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *out = ref != nullptr ? ref->data : nullptr; - return *out != nullptr; - } - - return false; -} - -void ObjCBridgeState::registerClassGlobals(napi_env env, napi_value global) { - MDSectionOffset offset = metadata->classesOffset; - while (offset < metadata->structsOffset) { - MDSectionOffset originalOffset = offset; - auto nameOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - auto runtimeNameOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - auto name = metadata->resolveString(nameOffset); - auto runtimeName = name; - if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { - runtimeName = metadata->resolveString(runtimeNameOffset); - } - while (hasProtocols) { - auto protocolOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & mdSectionOffsetNext) != 0; - } - - auto superclass = metadata->getOffset(offset); - offset += sizeof(superclass); - - bool next = (superclass & mdSectionOffsetNext) != 0; - - while (next) { - auto flags = metadata->getMemberFlag(offset); - next = (flags & mdMemberNext) != 0; - offset += sizeof(flags); - - if ((flags & mdMemberProperty) != 0) { - bool readonly = (flags & mdMemberReadonly) != 0; - offset += sizeof(MDSectionOffset); // name - offset += sizeof(MDSectionOffset); // getterSelector - offset += sizeof(MDSectionOffset); // getterSignature - if (!readonly) { - offset += sizeof(MDSectionOffset); // setterSelector - offset += sizeof(MDSectionOffset); // setterSignature - } - } else { - offset += sizeof(MDSectionOffset); // selector - offset += sizeof(MDSectionOffset); // signature - } - } - - auto nativeClass = objc_getClass(runtimeName); - if (nativeClass != nil) { - mdClassesByPointer[nativeClass] = originalOffset; - } - - napi_property_descriptor prop = { - .utf8name = name, - .method = nullptr, - .getter = JS_classGetter, - .setter = nullptr, - .value = nullptr, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = (void*)((size_t)originalOffset), - }; - - napi_define_properties(env, global, 1, &prop); - } -} - -NAPI_FUNCTION(registerClass) { - NAPI_CALLBACK_BEGIN(1) - - // In case no arguments are passed, we just return the NativeObject class. - // This is to support both @NativeClass and @NativeClass() syntaxes. - // Maybe we should support more in future like - // @NativeClass(NSApplicationDelegate). - if (argc == 0) { - napi_value func; - napi_create_function(env, "NativeClass", NAPI_AUTO_LENGTH, JS_registerClass, nullptr, &func); - return func; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - // bridgeState->registerClass(env, argv[0]); - - ClassBuilder* builder = new ClassBuilder(env, argv[0]); - // It gets lazily built when a static method is called. - // builder->build(); - bridgeState->registerRuntimeClass(builder, builder->nativeClass); - - return nullptr; -} - -const char* ObjCClassDecorator = R"( -globalThis.ObjCClass = function ObjCClass(...protocols) { - return function (target) { - if (target.ObjCProtocols) { - target.ObjCProtocols.push(...protocols); - } else { - target.ObjCProtocols = protocols; - } - - return globalThis.NativeClass(target); - }; -}; -)"; - -void setupObjCClassDecorator(napi_env env) { - napi_value global, script; - napi_get_global(env, &global); - napi_create_string_utf8(env, ObjCClassDecorator, NAPI_AUTO_LENGTH, &script); - napi_run_script(env, script, &global); -} - -char class_name[256]; - -// Get a Bridged Class by metadata offset, creating it if it doesn't exist. -// This is used to cache ObjCClass instances. -ObjCClass* ObjCBridgeState::getClass(napi_env env, MDSectionOffset offset) { - auto find = this->classes[offset]; - if (find != nullptr) { - return find; - } - - auto cls = new ObjCClass(env, offset); - this->classes[offset] = cls; - - return cls; -} - -NAPI_FUNCTION(import) { - NAPI_CALLBACK_BEGIN(1) - - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], class_name, 256, nullptr)) { - NAPI_THROW_LAST_ERROR - return nullptr; - } - - std::string name = class_name; - - if (!name.starts_with("/")) { - name = "/System/Library/Frameworks/" + name + ".framework"; - } - - NSBundle* bundle = [NSBundle bundleWithPath:[NSString stringWithUTF8String:name.c_str()]]; - if (bundle == nil) { - NSLog(@"Could not find bundle: %s", name.c_str()); - return nullptr; - } - - bool loaded = [bundle load]; - - if (!loaded) { - NSLog(@"Could not load bundle: %s", name.c_str()); - return nullptr; - } - - return nullptr; -} - -NAPI_FUNCTION(classGetter) { - void* data; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); - MDSectionOffset offset = (MDSectionOffset)((size_t)data); - auto bridgeState = ObjCBridgeState::InstanceData(env); - - auto cached = bridgeState->mdValueCache[offset]; - if (cached != nullptr) { - return get_ref_value(env, cached); - } - - // std::string name = bridgeState->metadata->getString(offset); - auto cls = bridgeState->getClass(env, offset); - - if (cls != nullptr) { - bridgeState->mdValueCache[offset] = cls->constructor; - } else { - return nullptr; - } - - return get_ref_value(env, cls->constructor); -} - -NAPI_FUNCTION(classSuperclassFallback) { - napi_value jsThis; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, nullptr); - - Class currentClass = nil; - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && jsThis != nullptr) { - bridgeState->tryResolveBridgedClassConstructor(env, jsThis, ¤tClass); - } - if (currentClass == nil) { - napi_unwrap(env, jsThis, (void**)¤tClass); - } - if (currentClass == nil) { - return nullptr; - } - - Class superClass = class_getSuperclass(currentClass); - if (superClass == nil) { - return nullptr; - } - - auto find = bridgeState->classesByPointer.find(superClass); - if (find != bridgeState->classesByPointer.end()) { - return get_ref_value(env, find->second->constructor); - } - - auto mdClass = bridgeState->mdClassesByPointer.find(superClass); - if (mdClass != bridgeState->mdClassesByPointer.end()) { - auto bridgedClass = bridgeState->getClass(env, mdClass->second); - return get_ref_value(env, bridgedClass->constructor); - } - - const char* runtimeName = class_getName(superClass); - if (runtimeName != nullptr && runtimeName[0] != '\0') { - napi_value global = nullptr; - napi_value constructor = nullptr; - bool hasGlobal = false; - napi_get_global(env, &global); - if (napi_has_named_property(env, global, runtimeName, &hasGlobal) == napi_ok && hasGlobal && - napi_get_named_property(env, global, runtimeName, &constructor) == napi_ok && - constructor != nullptr) { - return constructor; - } - } - - return bridgeState->getObject(env, (id)superClass, kUnownedObject, 0, nullptr); -} - -NAPI_FUNCTION(classHasInstance) { - size_t argc = 1; - napi_value argv[1] = {nullptr}; - napi_value jsThis = nullptr; - napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, nullptr); - - Class expectedClass = nil; - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && jsThis != nullptr) { - bridgeState->tryResolveBridgedClassConstructor(env, jsThis, &expectedClass); - } - if (expectedClass == nil) { - napi_unwrap(env, jsThis, (void**)&expectedClass); - } - - bool isInstance = false; - if (expectedClass != nil && argc > 0 && argv[0] != nullptr) { - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, argv[0], &valueType) == napi_ok && - (valueType == napi_object || valueType == napi_function)) { - id instance = nil; - napi_status unwrapStatus = napi_invalid_arg; - if (Pointer::isInstance(env, argv[0])) { - Pointer* pointer = Pointer::unwrap(env, argv[0]); - instance = pointer != nullptr ? static_cast(pointer->data) : nil; - unwrapStatus = instance != nil ? napi_ok : napi_invalid_arg; - } else if (Reference::isInstance(env, argv[0])) { - Reference* reference = Reference::unwrap(env, argv[0]); - instance = reference != nullptr ? static_cast(reference->data) : nil; - unwrapStatus = instance != nil ? napi_ok : napi_invalid_arg; - } else { - unwrapStatus = napi_unwrap(env, argv[0], (void**)&instance); - } - - if (unwrapStatus != napi_ok || instance == nil) { - napi_value nativePointer = nullptr; - if (napi_get_named_property(env, argv[0], "__ns_native_ptr", &nativePointer) == napi_ok && - Pointer::isInstance(env, nativePointer)) { - Pointer* pointer = Pointer::unwrap(env, nativePointer); - instance = pointer != nullptr ? static_cast(pointer->data) : nil; - } - } - - if (instance != nil) { - Class currentClass = object_getClass(instance); - while (currentClass != nil) { - if (currentClass == expectedClass) { - isInstance = true; - break; - } - currentClass = class_getSuperclass(currentClass); - } - } - } - } - - napi_value result = nullptr; - napi_get_boolean(env, isInstance, &result); - return result; -} - -NAPI_FUNCTION(BridgedConstructor) { - NAPI_CALLBACK_BEGIN(16) - - napi_value newTarget = nullptr; - napi_get_new_target(env, cbinfo, &newTarget); - - napi_valuetype thisType = napi_undefined; - if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || - (thisType != napi_object && thisType != napi_function)) { - napi_create_object(env, &jsThis); - - napi_value prototypeOwner = newTarget; - if (prototypeOwner == nullptr || napi_typeof(env, prototypeOwner, &thisType) != napi_ok || - (thisType != napi_function && thisType != napi_object)) { - prototypeOwner = nullptr; - } - - if (prototypeOwner != nullptr) { - napi_value prototype = nullptr; - if (napi_get_named_property(env, prototypeOwner, "prototype", &prototype) == napi_ok && - prototype != nullptr) { - napi_value global = nullptr; - napi_value objectCtor = nullptr; - napi_value setPrototypeOf = nullptr; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &objectCtor); - napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); - napi_value setPrototypeArgs[2] = {jsThis, prototype}; - napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); - } - } - } - - napi_valuetype jsType = napi_undefined; - if (argc > 0) { - napi_typeof(env, argv[0], &jsType); - } - - id object = nil; - - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - auto ensureWrappedThis = [&](id nativeObject) { - if (jsThis == nullptr || nativeObject == nil) { - return; - } - - if (newTarget != nullptr) { - // Fresh constructor receivers should not need an unwrap probe here. - // On V8, reentrant constructor calls from ObjC->JS block callbacks can - // stall inside napi_unwrap() on that receiver. - napi_wrap(env, jsThis, nativeObject, nullptr, nullptr, nullptr); - return; - } - - void* existingWrapped = nullptr; - napi_status unwrapStatus = napi_unwrap(env, jsThis, &existingWrapped); - if (unwrapStatus == napi_ok && existingWrapped != nullptr) { - return; - } - - napi_wrap(env, jsThis, nativeObject, nullptr, nullptr, nullptr); - }; - - Class cls = (Class)data; - - napi_value constructor = newTarget; - if (constructor == nullptr && jsThis != nullptr) { - napi_get_named_property(env, jsThis, "constructor", &constructor); - } - - if (constructor != nullptr) { - Class newTargetCls = nil; - if (!(bridgeState != nullptr && - bridgeState->tryResolveBridgedClassConstructor(env, constructor, &newTargetCls))) { - napi_unwrap(env, constructor, (void**)&newTargetCls); - } - - if (newTargetCls != nil) { - cls = newTargetCls; - } - } - - if (jsType == napi_external) { - return jsThis; - } else { - // Backward compatibility: allow `new Class(pointer)` to wrap an existing - // native instance instead of running initializer resolution. - if (argc == 1 && argv[0] != nullptr) { - void* rawPointer = nullptr; - if (tryGetInteropPointerArg(env, argv[0], &rawPointer) && rawPointer != nullptr) { - if (napi_value cached = bridgeState->getCachedHandleObject(env, rawPointer); - cached != nullptr) { - return cached; - } - - object = (id)rawPointer; - if (napi_value existing = bridgeState->findCachedObjectWrapper(env, object); - existing != nullptr) { - return existing; - } - - ensureWrappedThis(object); - jsThis = bridgeState->proxyNativeObject(env, jsThis, object); - napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr); - return jsThis; - } - } - - bool supercall = class_conformsToProtocol(cls, @protocol(ObjCBridgeClassBuilderProtocol)); - - if (supercall) { - ClassBuilder* builder = (ClassBuilder*)bridgeState->classesByPointer[cls]; - if (!builder->isFinal) builder->build(); - } - - // Allocate first, then run initializer resolution through the bridged - // JS "init" method so constructor arguments participate in selector - // matching (including Swift-style token objects). - object = [cls alloc]; - ensureWrappedThis(object); - jsThis = bridgeState->proxyNativeObject(env, jsThis, object); - } - - napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr); - - napi_value initMethod; - napi_status initStatus = napi_get_named_property(env, jsThis, "init", &initMethod); - if (initStatus != napi_ok || initMethod == nullptr) { - return jsThis; - } - - napi_value initResult; - initStatus = napi_call_function(env, jsThis, initMethod, argc, argv, &initResult); - if (initStatus != napi_ok) { - return nullptr; - } - - return initResult != nullptr ? initResult : jsThis; -} - -// Used to display the description of a native object in console.log. -// It's just implementation of nodejs.util.inspect.custom. -NAPI_FUNCTION(CustomInspect) { - napi_value jsThis; - void* data; - size_t argc = 0; - - napi_get_cb_info(env, cbinfo, &argc, nil, &jsThis, &data); - - id self = nil; - napi_unwrap(env, jsThis, (void**)&self); - - if (self == nil) { - napi_value result; - napi_create_string_utf8(env, "(nil)", NAPI_AUTO_LENGTH, &result); - return result; - } - - auto description = [self description]; - auto descriptionString = [description UTF8String]; - - napi_value result; - napi_create_string_utf8(env, descriptionString, NAPI_AUTO_LENGTH, &result); - - return result; -} - -// Used for Symbol.dispose (using statement support). -NAPI_FUNCTION(releaseObject) { - napi_value jsThis; - void* data; - napi_get_cb_info(env, cbinfo, nil, nil, &jsThis, &data); - id self; - napi_unwrap(env, jsThis, (void**)&self); - auto bridgeState = ObjCBridgeState::InstanceData(env); - bridgeState->unregisterObject(self); - return nullptr; -} - -// Implemented in JS to minimize calls into native. Fast enumeration -// makes use of buffers, so we only fill up the buffer once via -// native call and only do it again if needed via _fillStack. -static const char* FastEnumerationIteratorFactorySource = R"( - (function () { - return { - stack: new Array(16), - stacklen: -1, - stackptr: -1, - done: false, - - next() { - if (this.stackptr < 0 && !this.done) { - this.stacklen = this._fillStack(this.stack); - if (this.stacklen == 0) { - this.done = true; - this.stackptr = -1; - } else { - this.stackptr = 0; - } - } - - if (this.done) { - return { done: true }; - } - - const result = { value: this.stack[this.stackptr++], done: false }; - if (this.stackptr >= this.stacklen) { - this.stackptr = -1; - } - - return result; - }, - }; - }) -)"; - -void initFastEnumeratorIteratorFactory(napi_env env, ObjCBridgeState* bridgeState) { - napi_value result, script; - napi_create_string_utf8(env, FastEnumerationIteratorFactorySource, NAPI_AUTO_LENGTH, &script); - napi_run_script(env, script, &result); - bridgeState->createFastEnumeratorIterator = make_ref(env, result); -} - -class FastEnumerationIterator { - public: - FastEnumerationIterator(id collection) : collection(collection) {} - - static void finalize(napi_env env, void* data, void* hint) { - FastEnumerationIterator* iterator = (FastEnumerationIterator*)data; - delete iterator; - } - - static napi_value fillStack(napi_env env, napi_callback_info cbinfo) { - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - - napi_value jsThis; - void* data; - size_t argc = 1; - napi_value stackArray; - - napi_get_cb_info(env, cbinfo, &argc, &stackArray, &jsThis, &data); - - FastEnumerationIterator* self = nil; - napi_unwrap(env, jsThis, (void**)&self); - - NSUInteger count = [self->collection countByEnumeratingWithState:&self->state - objects:self->stackbuf - count:16]; - - for (uint32_t index = 0; index < static_cast(count); index++) { - id obj = self->state.itemsPtr[index]; - napi_value jsObj = bridgeState->getObject(env, obj); - napi_set_element(env, stackArray, index, jsObj); - } - - napi_value result; - napi_create_uint32(env, static_cast(count), &result); - - return result; - } - - napi_value toJS(napi_env env) { - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - - napi_value createIterator = get_ref_value(env, bridgeState->createFastEnumeratorIterator); - - napi_value result; - napi_call_function(env, createIterator, createIterator, 0, nullptr, &result); - - napi_property_descriptor fillStack = { - .utf8name = "_fillStack", - .name = nil, - .method = FastEnumerationIterator::fillStack, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = napi_enumerable, - .data = nil, - }; - - napi_define_properties(env, result, 1, &fillStack); - - napi_ref ref; - napi_wrap(env, result, this, FastEnumerationIterator::finalize, nullptr, &ref); - - return result; - } - - id collection; - NSFastEnumerationState state = {0}; - id stackbuf[16]; - BOOL firstLoop = YES; - long mutationsPtrValue; -}; - -NAPI_FUNCTION(fastEnumeration) { - napi_value jsThis; - void* data; - size_t argc = 0; - - napi_get_cb_info(env, cbinfo, &argc, nil, &jsThis, &data); - - id self = nil; - napi_unwrap(env, jsThis, (void**)&self); - - if (self == nil) { - napi_value result; - napi_create_string_utf8(env, "(nil)", NAPI_AUTO_LENGTH, &result); - return result; - } - - if (![self conformsToProtocol:@protocol(NSFastEnumeration)]) { - napi_throw_error(env, nil, "Object does not conform to NSFastEnumeration"); - return nullptr; - } - - auto iterator = new FastEnumerationIterator((id)self); - return iterator->toJS(env); -} - -std::string NativeObjectName = "NativeObject"; - -// Bridge an Objective-C class to JavaScript on the fly. Runtime introspection -// is used to determine the class's properties and methods. -// In an overview, we define two versions of same class. One is the "normal" -// one, and the other is the "supercall" one. The supercall one is used to call -// superclasses' methods. The normal one is used to call the class's own -// methods. The supercall one is used automatically when the normal one is -// extended by a JS class. -// Every Bridged Class extends the NativeObject class. - -void defineProtocolMembers(napi_env env, ObjCClassMemberMap& members, napi_value constructor, - ObjCProtocol* protocol, ObjCClass* cls) { - ObjCClassMember::defineMembers(env, members, protocol->membersOffset, constructor, cls); - for (auto protocol : protocol->protocols) { - defineProtocolMembers(env, members, constructor, protocol, cls); - } -} - -ObjCClass::ObjCClass(napi_env env, MDSectionOffset offset) { - NAPI_PREAMBLE - - this->env = env; - - metadataOffset = offset; - - bridgeState = ObjCBridgeState::InstanceData(env); - bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; - - bool isNativeObject = offset == MD_SECTION_OFFSET_NULL; - - std::vector protocolOffsets; - - MDSectionOffset superClassOffset = MD_SECTION_OFFSET_NULL; - bool hasMembers = false; - std::string jsConstructorName; - if (isNativeObject) { - name = NativeObjectName; - jsConstructorName = name; - nativeClass = nil; - } else { - auto nameOffset = bridgeState->metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - auto runtimeNameOffset = bridgeState->metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - name = bridgeState->metadata->resolveString(nameOffset); - const char* runtimeName = name.c_str(); - if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { - runtimeName = bridgeState->metadata->resolveString(runtimeNameOffset); - } - jsConstructorName = runtimeName; - if (jsConstructorName.empty()) { - jsConstructorName = name; - } - nativeClass = objc_getClass(runtimeName); - while (hasProtocols) { - auto protocolOffset = bridgeState->metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & mdSectionOffsetNext) != 0; - protocolOffset &= ~mdSectionOffsetNext; - if (protocolOffset != MD_SECTION_OFFSET_NULL) protocolOffsets.push_back(protocolOffset); - } - superClassOffset = bridgeState->metadata->getOffset(offset); - hasMembers = (superClassOffset & mdSectionOffsetNext) != 0; - superClassOffset &= ~mdSectionOffsetNext; - offset += sizeof(MDSectionOffset); - } - - napi_value constructor, prototype; - - napi_define_class(env, jsConstructorName.c_str(), jsConstructorName.length(), - JS_BridgedConstructor, (void*)nativeClass, 0, nil, &constructor); - - if (nativeClass != nil) { - napi_wrap(env, constructor, (void*)nativeClass, nil, nil, nil); - bridgeState->registerRuntimeClass(this, nativeClass); - } - - napi_get_named_property(env, constructor, "prototype", &prototype); - - napi_value superConstructor = nil, superPrototype = nil; - - // If the class requested isn't NativeObject - the class which every bridged - // class extends - we need to find the super class and inherit from it, if it - // exists. - if (!isNativeObject) { - if (superClassOffset != MD_SECTION_OFFSET_NULL) { - superClassOffset += bridgeState->metadata->classesOffset; - } - - // If class offset is 0, it means that the class doesn't have a super class. - // But we just inherit NativeObject class in that case. - superclass = bridgeState->getClass(env, superClassOffset); - if (superclass != nil) { - superConstructor = get_ref_value(env, superclass->constructor); - superPrototype = get_ref_value(env, superclass->prototype); - napi_inherits(env, constructor, superConstructor); - } - - for (auto protocolOffset : protocolOffsets) { - auto protocol = - bridgeState->getProtocol(env, protocolOffset + bridgeState->metadata->protocolsOffset); - if (protocol == nil) continue; - defineProtocolMembers(env, members, constructor, protocol, this); - } - } else { - superclass = nullptr; - } - - napi_value constructorNameValue = nullptr; - napi_create_string_utf8(env, jsConstructorName.c_str(), jsConstructorName.length(), - &constructorNameValue); - napi_property_descriptor constructorNameProp = { - .utf8name = "name", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = constructorNameValue, - .attributes = napi_default, - .data = nullptr, - }; - napi_define_properties(env, constructor, 1, &constructorNameProp); - - this->constructor = make_ref(env, constructor); - this->prototype = make_ref(env, prototype); - - if (isNativeObject) { - napi_value global, Symbol, SymbolDispose, SymbolIterator; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Symbol", &Symbol); - napi_get_named_property(env, Symbol, "iterator", &SymbolIterator); - napi_get_named_property(env, Symbol, "dispose", &SymbolDispose); - napi_valuetype type; - napi_typeof(env, SymbolDispose, &type); - - napi_value sizeofValue; - napi_create_int32(env, sizeof(id), &sizeofValue); - - napi_property_descriptor properties[] = { - { - .utf8name = nil, - .name = jsSymbolFor(env, "nodejs.util.inspect.custom"), - .method = JS_CustomInspect, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = napi_enumerable, - .data = nil, - }, - { - .utf8name = "toString", - .name = nil, - .method = JS_CustomInspect, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = napi_enumerable, - .data = nil, - }, - { - .utf8name = nil, - .name = jsSymbolFor(env, "sizeof"), - .method = nil, - .getter = nil, - .setter = nil, - .value = sizeofValue, - .attributes = napi_enumerable, - .data = nil, - }, - { - .utf8name = nil, - .name = SymbolIterator, - .method = JS_fastEnumeration, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = napi_enumerable, - .data = nil, - }}; - - napi_define_properties(env, prototype, 4, properties); - - napi_define_properties(env, constructor, 1, &properties[2]); - - bool hasSuperclass = false; - napi_value superclassKey = nullptr; - napi_create_string_utf8(env, "superclass", NAPI_AUTO_LENGTH, &superclassKey); - napi_has_own_property(env, constructor, superclassKey, &hasSuperclass); - if (!hasSuperclass) { - napi_property_descriptor superclassProperty = { - .utf8name = "superclass", - .name = nil, - .method = JS_classSuperclassFallback, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = - (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &superclassProperty); - } - - if (type == napi_symbol) { - properties[0].name = SymbolDispose; - properties[0].method = JS_releaseObject; - - napi_define_properties(env, prototype, 1, properties); - } - - return; - } - - // Add the 'extend' static method to all native classes (not NativeObject) - if (!isNativeObject) { - napi_value extendMethod; - napi_create_function(env, "extend", NAPI_AUTO_LENGTH, ClassBuilder::ExtendCallback, nullptr, - &extendMethod); - napi_set_named_property(env, constructor, "extend", extendMethod); - } - - if (!hasMembers) return; - - ObjCClassMember::defineMembers(env, members, offset, constructor, this); - - auto hasOwnNamedProperty = [&](napi_value object, const char* propertyName) { - napi_value key = nullptr; - napi_create_string_utf8(env, propertyName, NAPI_AUTO_LENGTH, &key); - bool hasOwn = false; - napi_has_own_property(env, object, key, &hasOwn); - return hasOwn; - }; - - if (!hasOwnNamedProperty(constructor, "name")) { - napi_value classNameValue = nullptr; - napi_create_string_utf8(env, jsConstructorName.c_str(), NAPI_AUTO_LENGTH, &classNameValue); - napi_property_descriptor nameProperty = { - .utf8name = "name", - .name = nil, - .method = nil, - .getter = nil, - .setter = nil, - .value = classNameValue, - .attributes = (napi_property_attributes)(napi_configurable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &nameProperty); - } - - if (!hasOwnNamedProperty(constructor, "length")) { - napi_value zeroValue = nullptr; - napi_create_int32(env, 0, &zeroValue); - napi_property_descriptor lengthProperty = { - .utf8name = "length", - .name = nil, - .method = nil, - .getter = nil, - .setter = nil, - .value = zeroValue, - .attributes = (napi_property_attributes)(napi_configurable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &lengthProperty); - } - - if (!hasOwnNamedProperty(constructor, "alloc")) { - napi_property_descriptor allocProperty = { - .utf8name = "alloc", - .name = nil, - .method = JS_NSObject_alloc, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = - (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &allocProperty); - } - - if (!hasOwnNamedProperty(constructor, "arguments") || - !hasOwnNamedProperty(constructor, "caller")) { - napi_value undefinedValue = nullptr; - napi_get_undefined(env, &undefinedValue); - napi_property_descriptor slots[] = { - { - .utf8name = "arguments", - .name = nil, - .method = nil, - .getter = nil, - .setter = nil, - .value = undefinedValue, - .attributes = (napi_property_attributes)(napi_configurable | napi_writable), - .data = nil, - }, - { - .utf8name = "caller", - .name = nil, - .method = nil, - .getter = nil, - .setter = nil, - .value = undefinedValue, - .attributes = (napi_property_attributes)(napi_configurable | napi_writable), - .data = nil, - }, - }; - napi_define_properties(env, constructor, 2, slots); - } - - napi_value global = nullptr; - napi_value symbolCtor = nullptr; - napi_value hasInstanceSymbol = nullptr; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Symbol", &symbolCtor); - napi_get_named_property(env, symbolCtor, "hasInstance", &hasInstanceSymbol); - bool hasOwnHasInstance = false; - napi_has_own_property(env, constructor, hasInstanceSymbol, &hasOwnHasInstance); - if (!hasOwnHasInstance) { - napi_property_descriptor hasInstanceProperty = { - .utf8name = nil, - .name = hasInstanceSymbol, - .method = JS_classHasInstance, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = (napi_property_attributes)(napi_configurable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &hasInstanceProperty); - } - - if (!hasOwnNamedProperty(prototype, "toString")) { - napi_property_descriptor toStringProperty = { - .utf8name = "toString", - .name = nil, - .method = JS_CustomInspect, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = - (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), - .data = nil, - }; - napi_define_properties(env, prototype, 1, &toStringProperty); - } -} - -ObjCClass::~ObjCClass() { - if (env != nullptr && - (bridgeState == nullptr || IsBridgeStateLive(bridgeState, bridgeStateToken))) { - DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, constructor); - DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, prototype); - } - constructor = nullptr; - prototype = nullptr; - bridgeState = nullptr; - bridgeStateToken = 0; -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/ClassBuilder.mm b/NativeScript/ffi/napi/ClassBuilder.mm deleted file mode 100644 index aed65fdfb..000000000 --- a/NativeScript/ffi/napi/ClassBuilder.mm +++ /dev/null @@ -1,1079 +0,0 @@ -#include "ClassBuilder.h" -#import -#include -#include -#include "Closure.h" -#include "Interop.h" -#include "Metadata.h" -#include "ObjCBridge.h" -#include "Protocol.h" -#include "Util.h" -#include "js_native_api.h" -#include "node_api_util.h" - -namespace nativescript { -namespace { -std::unordered_map gKnownExposedMethods; -std::mutex gClassBuilderEnvMutex; -std::unordered_map gClassBuilderEnvs; - -void registerClassBuilderEnv(Class nativeClass, napi_env env) { - if (nativeClass == Nil || env == nullptr) { - return; - } - - std::lock_guard lock(gClassBuilderEnvMutex); - gClassBuilderEnvs[nativeClass] = env; -} - -void unregisterClassBuilderEnv(Class nativeClass) { - if (nativeClass == Nil) { - return; - } - - std::lock_guard lock(gClassBuilderEnvMutex); - gClassBuilderEnvs.erase(nativeClass); -} - -napi_env resolveClassBuilderEnv(id self) { - if (self == nil) { - return nullptr; - } - - Class currentClass = object_getClass(self); - if (currentClass == Nil) { - return nullptr; - } - - std::lock_guard lock(gClassBuilderEnvMutex); - while (currentClass != Nil) { - auto find = gClassBuilderEnvs.find(currentClass); - if (find != gClassBuilderEnvs.end()) { - return find->second; - } - currentClass = class_getSuperclass(currentClass); - } - - return nullptr; -} - -const char* kInstallSuperAccessorSource = R"( - (function (prototype, basePrototype) { - const superCacheKey = Symbol.for("__ns_super_proxy__"); - Object.defineProperty(prototype, "super", { - configurable: true, - enumerable: false, - get: function () { - const self = this; - if (self == null) { - return undefined; - } - - const existing = self[superCacheKey]; - if (existing && existing.base === basePrototype) { - return existing.proxy; - } - - const proxy = new Proxy(basePrototype, { - get(target, key) { - let current = target; - while (current != null) { - const descriptor = Object.getOwnPropertyDescriptor(current, key); - if (descriptor) { - if (typeof descriptor.get === "function") { - return descriptor.get.call(self); - } - const value = descriptor.value; - if (typeof value === "function") { - return value.bind(self); - } - return value; - } - current = Object.getPrototypeOf(current); - } - return undefined; - }, - set(target, key, value) { - let current = target; - while (current != null) { - const descriptor = Object.getOwnPropertyDescriptor(current, key); - if (descriptor) { - if (typeof descriptor.set === "function") { - descriptor.set.call(self, value); - return true; - } - if ("value" in descriptor && descriptor.writable) { - self[key] = value; - return true; - } - return false; - } - current = Object.getPrototypeOf(current); - } - self[key] = value; - return true; - } - }); - - Object.defineProperty(self, superCacheKey, { - configurable: true, - enumerable: false, - writable: true, - value: { base: basePrototype, proxy } - }); - - return proxy; - } - }); - }) - )"; - -const char* kInstallClassFromStringAliasSource = R"( - (function (global) { - if (global.__nsClassFromStringAliasInstalled === true) { - return; - } - - const original = global.NSClassFromString; - if (typeof original !== "function") { - return; - } - - Object.defineProperty(global, "__nsOriginalNSClassFromString", { - configurable: true, - enumerable: false, - writable: false, - value: original - }); - - Object.defineProperty(global, "NSClassFromString", { - configurable: true, - enumerable: true, - writable: true, - value: function (name) { - const cls = original.call(this, name); - if (cls != null) { - try { - const registry = global.__nsConstructorsByObjCClassName; - const stringFromClass = global.NSStringFromClass; - if (registry != null && typeof stringFromClass === "function") { - const runtimeName = stringFromClass(cls); - const constructor = registry[runtimeName]; - if (constructor != null) { - return constructor; - } - } - } catch (_) { - } - } - - return cls; - } - }); - - Object.defineProperty(global, "__nsClassFromStringAliasInstalled", { - configurable: true, - enumerable: false, - writable: false, - value: true - }); - }) -)"; - -const char* NSFastEnumerationMethodEncoding() { - static const char* encoding = nullptr; - if (encoding == nullptr) { - struct objc_method_description desc = protocol_getMethodDescription( - @protocol(NSFastEnumeration), @selector(countByEnumeratingWithState:objects:count:), YES, - YES); - encoding = desc.types; - } - return encoding; -} - -void clearPendingException(napi_env env) { - napi_value ignored = nullptr; - napi_get_and_clear_last_exception(env, &ignored); -} - -Protocol* resolveRuntimeProtocol(napi_env env, napi_value protocolValue) { - if (protocolValue == nullptr) { - return nullptr; - } - - ObjCProtocol* wrappedProtocol = nullptr; - if (napi_unwrap(env, protocolValue, (void**)&wrappedProtocol) == napi_ok && - wrappedProtocol != nullptr) { - Protocol* runtimeProtocol = objc_getProtocol(wrappedProtocol->name.c_str()); - if (runtimeProtocol != nullptr) { - return runtimeProtocol; - } - } - - if (Pointer::isInstance(env, protocolValue)) { - Pointer* pointer = Pointer::unwrap(env, protocolValue); - if (pointer != nullptr && pointer->data != nullptr) { - Protocol* runtimeProtocol = (Protocol*)pointer->data; - if (protocol_getName(runtimeProtocol) != nullptr) { - return runtimeProtocol; - } - } - } - - auto protocolFromName = [](const std::string& protocolName) -> Protocol* { - if (protocolName.empty()) { - return nullptr; - } - - Protocol* runtimeProtocol = objc_getProtocol(protocolName.c_str()); - if (runtimeProtocol != nullptr) { - return runtimeProtocol; - } - - const std::string suffix = "Protocol"; - if (protocolName.length() > suffix.length() && - protocolName.rfind(suffix) == protocolName.length() - suffix.length()) { - std::string strippedName = protocolName.substr(0, protocolName.length() - suffix.length()); - return objc_getProtocol(strippedName.c_str()); - } - - return nullptr; - }; - - napi_valuetype valueType = napi_undefined; - napi_typeof(env, protocolValue, &valueType); - if (valueType == napi_string) { - char protocolNameBuffer[512]; - size_t protocolNameLength = 0; - napi_get_value_string_utf8(env, protocolValue, protocolNameBuffer, sizeof(protocolNameBuffer), - &protocolNameLength); - return protocolFromName(std::string(protocolNameBuffer, protocolNameLength)); - } - - if (valueType == napi_object || valueType == napi_function) { - bool hasName = false; - napi_has_named_property(env, protocolValue, "name", &hasName); - if (hasName) { - napi_value protocolNameValue = nullptr; - napi_get_named_property(env, protocolValue, "name", &protocolNameValue); - napi_valuetype protocolNameType = napi_undefined; - napi_typeof(env, protocolNameValue, &protocolNameType); - if (protocolNameType == napi_string) { - char protocolNameBuffer[512]; - size_t protocolNameLength = 0; - napi_get_value_string_utf8(env, protocolNameValue, protocolNameBuffer, - sizeof(protocolNameBuffer), &protocolNameLength); - return protocolFromName(std::string(protocolNameBuffer, protocolNameLength)); - } - } - } - - return nullptr; -} - -napi_value JS_classConformsToProtocolSafe(napi_env env, napi_callback_info info) { - size_t argc = 1; - napi_value argv[1] = {nullptr}; - napi_value jsThis = nullptr; - napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr); - - Class nativeClass = nullptr; - napi_unwrap(env, jsThis, (void**)&nativeClass); - - bool conforms = false; - if (nativeClass != nullptr && argc > 0) { - Protocol* runtimeProtocol = resolveRuntimeProtocol(env, argv[0]); - if (runtimeProtocol != nullptr) { - conforms = class_conformsToProtocol(nativeClass, runtimeProtocol); - } - } - - napi_value result = nullptr; - napi_get_boolean(env, conforms, &result); - return result; -} - -NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerationState* state, - id __unsafe_unretained stackbuf[], NSUInteger len) { - if (len == 0) { - return 0; - } - - napi_env env = resolveClassBuilderEnv(self); - if (env == nullptr) { - return 0; - } - - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState == nullptr) { - return 0; - } - - napi_value jsSelf = bridgeState->getObject(env, self, kUnownedObject); - if (jsSelf == nullptr) { - return 0; - } - - napi_value global = nullptr; - napi_value symbolCtor = nullptr; - napi_value symbolIterator = nullptr; - if (napi_get_global(env, &global) != napi_ok || - napi_get_named_property(env, global, "Symbol", &symbolCtor) != napi_ok || - napi_get_named_property(env, symbolCtor, "iterator", &symbolIterator) != napi_ok) { - clearPendingException(env); - return 0; - } - - napi_value iteratorMethod = nullptr; - if (napi_get_property(env, jsSelf, symbolIterator, &iteratorMethod) != napi_ok) { - clearPendingException(env); - return 0; - } - - napi_valuetype iteratorMethodType = napi_undefined; - if (napi_typeof(env, iteratorMethod, &iteratorMethodType) != napi_ok || - iteratorMethodType != napi_function) { - return 0; - } - - napi_value iterator = nullptr; - if (napi_call_function(env, jsSelf, iteratorMethod, 0, nullptr, &iterator) != napi_ok) { - clearPendingException(env); - return 0; - } - - napi_valuetype iteratorType = napi_undefined; - if (napi_typeof(env, iterator, &iteratorType) != napi_ok || - (iteratorType != napi_object && iteratorType != napi_function)) { - return 0; - } - - napi_value nextMethod = nullptr; - if (napi_get_named_property(env, iterator, "next", &nextMethod) != napi_ok) { - clearPendingException(env); - return 0; - } - - napi_valuetype nextType = napi_undefined; - if (napi_typeof(env, nextMethod, &nextType) != napi_ok || nextType != napi_function) { - return 0; - } - - auto callNext = [&](napi_value* nextResult) -> bool { - if (napi_call_function(env, iterator, nextMethod, 0, nullptr, nextResult) != napi_ok) { - clearPendingException(env); - return false; - } - return true; - }; - - auto readDoneFlag = [&](napi_value nextResult, bool* done) -> bool { - napi_value doneValue = nullptr; - if (napi_get_named_property(env, nextResult, "done", &doneValue) != napi_ok) { - clearPendingException(env); - return false; - } - - bool isDone = false; - if (napi_get_value_bool(env, doneValue, &isDone) != napi_ok) { - clearPendingException(env); - return false; - } - - *done = isDone; - return true; - }; - - // Rebuild the iterator from the beginning and skip already yielded values. - // This keeps state handling simple and avoids retaining JS iterators in ObjC. - for (unsigned long skipped = 0; skipped < state->state; skipped++) { - napi_value skippedResult = nullptr; - if (!callNext(&skippedResult)) { - return 0; - } - - bool done = false; - if (!readDoneFlag(skippedResult, &done) || done) { - return 0; - } - } - - const char* objectEncoding = "@"; - auto objectConverter = TypeConv::Make(env, &objectEncoding); - - NSUInteger count = 0; - while (count < len) { - napi_value nextResult = nullptr; - if (!callNext(&nextResult)) { - break; - } - - bool done = false; - if (!readDoneFlag(nextResult, &done)) { - break; - } - - if (done) { - break; - } - - napi_value value = nullptr; - if (napi_get_named_property(env, nextResult, "value", &value) != napi_ok) { - clearPendingException(env); - break; - } - - id nativeValue = nil; - bool shouldFree = false; - bool shouldFreeAny = false; - objectConverter->toNative(env, value, &nativeValue, &shouldFree, &shouldFreeAny); - stackbuf[count++] = nativeValue; - } - - state->itemsPtr = stackbuf; - state->mutationsPtr = &state->extra[0]; - state->extra[0] = 0; - state->state += count; - - return count; -} -} // namespace - -ClassBuilder::ClassBuilder(napi_env env, napi_value constructor, Class explicitSuperClass) { - this->env = env; - bridgeState = ObjCBridgeState::InstanceData(env); - - metadataOffset = MD_SECTION_OFFSET_NULL; - - Class superClassNative = explicitSuperClass; - if (superClassNative == nullptr) { - napi_value superConstructor; - napi_get_prototype(env, constructor, &superConstructor); - napi_unwrap(env, superConstructor, (void**)&superClassNative); - } - - if (superClassNative == nullptr) { - // If the class does not inherit from a native class, - // by default it inherits from NSObject. - superClassNative = [NSObject class]; - } - - auto superClassIt = bridgeState->classesByPointer.find(superClassNative); - if (superClassIt != bridgeState->classesByPointer.end()) { - superclass = superClassIt->second; - } else { - auto mdClassIt = bridgeState->mdClassesByPointer.find(superClassNative); - if (mdClassIt != bridgeState->mdClassesByPointer.end()) { - superclass = bridgeState->getClass(env, mdClassIt->second); - } else { - superclass = nullptr; - } - } - - napi_value className; - bool hasNativeClassName = false; - napi_has_named_property(env, constructor, "ObjCClassName", &hasNativeClassName); - if (hasNativeClassName) { - napi_get_named_property(env, constructor, "ObjCClassName", &className); - } else { - napi_get_named_property(env, constructor, "name", &className); - } - static char classNameBuf[512]; - napi_get_value_string_utf8(env, className, classNameBuf, 512, nullptr); - - name = classNameBuf; - if (objc_lookUpClass(name.c_str()) != nullptr) { - std::string baseName = name; - do { - name = baseName + "_" + std::to_string(rand()); - } while (objc_lookUpClass(name.c_str()) != nullptr); - } - nativeClass = objc_allocateClassPair(superClassNative, name.c_str(), 0); - - if (nativeClass == nullptr) { - napi_throw_error(env, nullptr, "Failed to allocate class"); - return; - } - - class_addProtocol(nativeClass, @protocol(ObjCBridgeClassBuilderProtocol)); - - objc_registerClassPair(nativeClass); - registerClassBuilderEnv(nativeClass, env); - - napi_remove_wrap(env, constructor, nullptr); - - napi_wrap(env, constructor, (void*)nativeClass, nullptr, nullptr, nullptr); - - napi_value prototype; - napi_get_named_property(env, constructor, "prototype", &prototype); - - this->constructor = make_ref(env, constructor); - this->prototype = make_ref(env, prototype); -} - -ClassBuilder::~ClassBuilder() { - unregisterClassBuilderEnv(nativeClass); - - if (nativeClass != nullptr) { - objc_disposeClassPair(nativeClass); - napi_delete_reference(env, constructor); - napi_delete_reference(env, prototype); - } -} - -void ClassBuilder::addProtocol(ObjCProtocol* protocol) { - if (!protocols.contains(protocol)) { - protocols.emplace(protocol); - // Not always there is a `Protocol` object for the protocol, so we - // need to check if it exists first. - Protocol* proto = objc_getProtocol(protocol->name.c_str()); - if (proto != nullptr) { - class_addProtocol(nativeClass, proto); - } - } -} - -std::vector ClassBuilder::lookupMethodDescriptors(std::string& name, - bool setter) { - std::vector descriptors; - std::unordered_set selectors; - - auto appendDescriptor = [&](MethodDescriptor* descriptor) { - if (descriptor == nullptr || descriptor->selector == nullptr) { - return; - } - if (!selectors.insert(descriptor->selector).second) { - return; - } - descriptors.emplace_back(descriptor); - }; - - // 1. First we look up if there was a custom definition for the method - // in ObjCExposedMethods static of the custom class. - if (!setter) { - auto findExposedMethod = exposedMethods.find(name); - if (findExposedMethod != exposedMethods.end()) { - appendDescriptor(&findExposedMethod->second); - return descriptors; - } - - auto findKnownExposedMethod = gKnownExposedMethods.find(name); - if (findKnownExposedMethod != gKnownExposedMethods.end()) { - appendDescriptor(&findKnownExposedMethod->second); - return descriptors; - } - } - - auto appendMemberDescriptors = [&](ObjCClassMember& member) { - if (setter) { - if (member.methodOrGetter.isProperty && member.setter.selector != nullptr) { - appendDescriptor(&member.setter); - } - return; - } - - appendDescriptor(&member.methodOrGetter); - for (auto& overload : member.overloads) { - appendDescriptor(&overload.method); - } - }; - - // 2. Then walk through the class hierarchy and see if we can find the - // method in the superclass chain. - ObjCClass* currentClass = superclass; - while (currentClass != nullptr) { - // JS extend() overrides target instance members; fall back to a static - // twin only when no instance member exists under this name. - auto findMethod = currentClass->members.find({name, false}); - if (findMethod == currentClass->members.end()) { - findMethod = currentClass->members.find({name, true}); - } - if (findMethod != currentClass->members.end()) { - appendMemberDescriptors(findMethod->second); - if (!descriptors.empty()) { - return descriptors; - } - } - currentClass = currentClass->superclass; - } - - // 3. And finally, look into all protocols implemented (directly or - // indirectly) and try to find the method there. - std::function&)> processProtocols = - [&](std::unordered_set& protocols) { - for (auto protocol : protocols) { - auto findMethod = protocol->members.find({name, false}); - if (findMethod == protocol->members.end()) { - findMethod = protocol->members.find({name, true}); - } - if (findMethod != protocol->members.end()) { - appendMemberDescriptors(findMethod->second); - } - processProtocols(protocol->protocols); - } - }; - - processProtocols(protocols); - return descriptors; -} - -MethodDescriptor* ClassBuilder::lookupMethodDescriptor(std::string& name, bool setter) { - auto descriptors = lookupMethodDescriptors(name, setter); - return descriptors.empty() ? nullptr : descriptors.front(); -} - -void ClassBuilder::addMethod(std::string& name, MethodDescriptor* desc, napi_value key, - napi_value func) { - switch (desc->kind) { - case kMethodDescEncoding: { - const char* encoding = desc->encoding.c_str(); - auto closure = new Closure(env, encoding, false, true); - if (func != nullptr) - closure->func = make_ref(env, func); - else - closure->propertyName = name; - closure->selector = desc->selector; - closure->thisConstructor = constructor; - class_replaceMethod(nativeClass, desc->selector, (IMP)closure->fnptr, encoding); - break; - } - - case kMethodDescSignatureOffset: { - std::string encoding; - auto closure = new Closure(env, bridgeState->metadata, desc->signatureOffset, false, - &encoding, true, desc->isProperty); - if (func != nullptr) - closure->func = make_ref(env, func); - else - closure->propertyName = name; - closure->selector = desc->selector; - closure->thisConstructor = constructor; - class_replaceMethod(nativeClass, desc->selector, (IMP)closure->fnptr, encoding.c_str()); - break; - } - } -} - -void ClassBuilder::build() { - if (isFinal) return; - - isFinal = true; - - napi_value constructor = get_ref_value(env, this->constructor), - prototype = get_ref_value(env, this->prototype); - - // 1. Extract method definitions from ObjCExposedMethods static - - napi_value exposedMethods, exposedMethodNames; - bool hasExposedMethods = false; - napi_has_named_property(env, constructor, "ObjCExposedMethods", &hasExposedMethods); - - if (hasExposedMethods) { - napi_get_named_property(env, constructor, "ObjCExposedMethods", &exposedMethods); - - napi_get_all_property_names(env, exposedMethods, napi_key_own_only, napi_key_skip_symbols, - napi_key_numbers_to_strings, &exposedMethodNames); - - uint32_t exposedMethodCount = 0; - napi_get_array_length(env, exposedMethodNames, &exposedMethodCount); - - for (uint32_t i = 0; i < exposedMethodCount; i++) { - napi_value exposedMethodName; - napi_get_element(env, exposedMethodNames, i, &exposedMethodName); - static char exposedMethodNameBuf[512]; - napi_get_value_string_utf8(env, exposedMethodName, exposedMethodNameBuf, 512, nullptr); - std::string name = exposedMethodNameBuf; - SEL selector = sel_registerName(name.c_str()); - std::string encoding; - - napi_value def, params = nullptr, returns; - napi_get_named_property(env, exposedMethods, name.c_str(), &def); - napi_get_named_property(env, def, "returns", &returns); - - uint32_t paramCount = 0; - bool hasParams = false; - napi_has_named_property(env, def, "params", &hasParams); - if (hasParams) { - napi_get_named_property(env, def, "params", ¶ms); - napi_valuetype paramsType = napi_undefined; - napi_typeof(env, params, ¶msType); - if (paramsType == napi_object) { - bool isArray = false; - napi_is_array(env, params, &isArray); - if (isArray) { - napi_get_array_length(env, params, ¶mCount); - } else { - napi_throw_type_error(env, nullptr, - "ObjCExposedMethods params must be an array when provided"); - return; - } - } else if (paramsType != napi_undefined && paramsType != napi_null) { - napi_throw_type_error(env, nullptr, - "ObjCExposedMethods params must be an array when provided"); - return; - } - } - - encoding += getEncodedType(env, returns); - encoding += "@:"; - - for (uint32_t j = 0; j < paramCount; j++) { - napi_value param; - napi_get_element(env, params, j, ¶m); - std::string enctype = getEncodedType(env, param); - if (enctype == "v") { - napi_throw_error(env, nullptr, "Void type not allowed in method params"); - return; - } - encoding += enctype; - } - - auto descriptor = MethodDescriptor(selector, encoding); - this->exposedMethods[name] = descriptor; - gKnownExposedMethods[name] = descriptor; - gKnownExposedMethods[jsifySelector(name)] = descriptor; - } - } - - // 2. Extract the protocol references from ObjCProtocols static - - napi_value protocols, protocol; - bool hasProtocols = false; - napi_has_named_property(env, constructor, "ObjCProtocols", &hasProtocols); - - if (hasProtocols) { - napi_get_named_property(env, constructor, "ObjCProtocols", &protocols); - - uint32_t i = 0; - uint32_t length = 0; - napi_get_array_length(env, protocols, &length); - - while (i < length) { - napi_get_element(env, protocols, i, &protocol); - static char protocolNameBuf[512]; - napi_get_value_string_utf8(env, protocol, protocolNameBuf, 512, nullptr); - ObjCProtocol* proto = nullptr; - napi_unwrap(env, protocol, (void**)&proto); - if (proto != nullptr) addProtocol(proto); - i++; - } - } - - // 3. Walk over methods defined on the prototype and add them to the ObjC - // class if we can find the corresponding method descriptor. - - napi_value properties; - - napi_get_all_property_names(env, prototype, napi_key_own_only, napi_key_skip_symbols, - napi_key_numbers_to_strings, &properties); - - uint32_t propertyCount = 0; - napi_get_array_length(env, properties, &propertyCount); - - napi_value global, objectCtor, getOwnPropertyDescriptor; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &objectCtor); - napi_get_named_property(env, objectCtor, "getOwnPropertyDescriptor", &getOwnPropertyDescriptor); - - bool hasIteratorMethod = false; - napi_value symbolCtor = nullptr; - napi_value symbolIterator = nullptr; - napi_get_named_property(env, global, "Symbol", &symbolCtor); - napi_get_named_property(env, symbolCtor, "iterator", &symbolIterator); - napi_has_own_property(env, prototype, symbolIterator, &hasIteratorMethod); - if (hasIteratorMethod) { - class_addProtocol(nativeClass, @protocol(NSFastEnumeration)); - const char* fastEnumerationEncoding = NSFastEnumerationMethodEncoding(); - if (fastEnumerationEncoding != nullptr) { - class_replaceMethod(nativeClass, @selector(countByEnumeratingWithState:objects:count:), - (IMP)JS_SymbolIteratorCountByEnumerating, fastEnumerationEncoding); - } - } - - uint32_t i = 0; - while (i < propertyCount) { - napi_value property; - napi_get_element(env, properties, i, &property); - static char propertyNameBuf[512]; - napi_get_value_string_utf8(env, property, propertyNameBuf, 512, nullptr); - std::string name = propertyNameBuf; - napi_value descriptorArgs[] = {prototype, property}; - napi_value propertyDescriptor; - napi_call_function(env, objectCtor, getOwnPropertyDescriptor, 2, descriptorArgs, - &propertyDescriptor); - - napi_valuetype descriptorType; - napi_typeof(env, propertyDescriptor, &descriptorType); - if (descriptorType == napi_undefined || descriptorType == napi_null) { - i++; - continue; - } - - bool hasValue = false; - napi_has_named_property(env, propertyDescriptor, "value", &hasValue); - if (hasValue) { - napi_value methodFunc; - napi_get_named_property(env, propertyDescriptor, "value", &methodFunc); - napi_valuetype funcType = napi_undefined; - napi_typeof(env, methodFunc, &funcType); - if (funcType == napi_function) { - auto descriptors = lookupMethodDescriptors(name); - for (MethodDescriptor* descriptor : descriptors) { - if (descriptor != nullptr) { - addMethod(name, descriptor, property, methodFunc); - } - } - } - } - - bool hasGetter = false; - napi_has_named_property(env, propertyDescriptor, "get", &hasGetter); - if (hasGetter) { - napi_value getterFunc; - napi_get_named_property(env, propertyDescriptor, "get", &getterFunc); - napi_valuetype getterType = napi_undefined; - napi_typeof(env, getterFunc, &getterType); - if (getterType == napi_function) { - auto getterDescs = lookupMethodDescriptors(name); - for (MethodDescriptor* getterDesc : getterDescs) { - if (getterDesc != nullptr) { - addMethod(name, getterDesc, property, getterFunc); - } - } - } - } - - bool hasSetter = false; - napi_has_named_property(env, propertyDescriptor, "set", &hasSetter); - if (hasSetter) { - napi_value setterFunc; - napi_get_named_property(env, propertyDescriptor, "set", &setterFunc); - napi_valuetype setterType = napi_undefined; - napi_typeof(env, setterFunc, &setterType); - if (setterType == napi_function) { - auto setterDescs = lookupMethodDescriptors(name, true); - for (MethodDescriptor* setterDesc : setterDescs) { - if (setterDesc != nullptr) { - addMethod(name, setterDesc, property, setterFunc); - } - } - } - } - i++; - } -} - -// Static method to extend native classes with JavaScript-defined methods -napi_value ClassBuilder::ExtendCallback(napi_env env, napi_callback_info info) { - size_t argc = 2; - napi_value args[2]; - napi_value thisArg; - - napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr); - - if (argc < 1) { - napi_throw_error(env, nullptr, - "extend() requires at least one parameter with method definitions"); - return nullptr; - } - - // Validate that the first argument is an object - napi_valuetype argType; - napi_typeof(env, args[0], &argType); - if (argType != napi_object) { - napi_throw_error(env, nullptr, "extend() first parameter must be an object"); - return nullptr; - } - - // Get the native class from 'this' (the constructor function) - Class baseNativeClass = nullptr; - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - bridgeState->tryResolveBridgedClassConstructor(env, thisArg, &baseNativeClass); - } - if (baseNativeClass == nullptr) { - napi_unwrap(env, thisArg, (void**)&baseNativeClass); - } - - if (baseNativeClass == nullptr) { - napi_throw_error(env, nullptr, "extend() can only be called on native class constructors"); - return nullptr; - } - - if (class_conformsToProtocol(baseNativeClass, @protocol(ObjCBridgeClassBuilderProtocol))) { - napi_throw_error(env, nullptr, "Cannot extend an already extended class."); - return nullptr; - } - - napi_value options = nullptr; - bool hasOptionsObject = false; - if (argc >= 2) { - napi_valuetype secondArgType; - napi_typeof(env, args[1], &secondArgType); - hasOptionsObject = secondArgType == napi_object; - if (hasOptionsObject) { - options = args[1]; - } - } - - // Create a class name. - napi_value baseClassName; - napi_get_named_property(env, thisArg, "name", &baseClassName); - static char baseClassNameBuf[512]; - napi_get_value_string_utf8(env, baseClassName, baseClassNameBuf, 512, nullptr); - - std::string requestedName; - if (hasOptionsObject) { - bool hasCustomName = false; - napi_has_named_property(env, options, "name", &hasCustomName); - if (hasCustomName) { - napi_value customNameValue = nullptr; - napi_get_named_property(env, options, "name", &customNameValue); - napi_valuetype customNameType = napi_undefined; - napi_typeof(env, customNameValue, &customNameType); - if (customNameType == napi_string) { - static char customNameBuf[512]; - size_t customNameLen = 0; - napi_get_value_string_utf8(env, customNameValue, customNameBuf, sizeof(customNameBuf), - &customNameLen); - if (customNameLen > 0) { - requestedName.assign(customNameBuf, customNameLen); - } - } - } - } - - std::string newClassName; - bool shouldAliasRequestedName = false; - if (!requestedName.empty()) { - newClassName = requestedName; - Class existingClass = objc_lookUpClass(newClassName.c_str()); - auto nextAvailableClassName = [](const std::string& baseName) { - size_t suffix = 1; - std::string candidate; - do { - candidate = baseName + std::to_string(suffix++); - } while (objc_lookUpClass(candidate.c_str()) != nullptr); - return candidate; - }; - if (existingClass != nullptr) { - newClassName = nextAvailableClassName(requestedName); - shouldAliasRequestedName = - !class_conformsToProtocol(existingClass, @protocol(ObjCBridgeClassBuilderProtocol)); - } - } else { - newClassName = baseClassNameBuf; - newClassName += "_Extended_"; - newClassName += std::to_string(rand()); - } - // Create the new constructor function that extends the base - napi_value newConstructor; - napi_define_class(env, newClassName.c_str(), newClassName.length(), JS_BridgedConstructor, - nullptr, 0, nullptr, &newConstructor); - - // Set up JavaScript inheritance from the base class - napi_inherits(env, newConstructor, thisArg); - - napi_value safeConformsToProtocol = nullptr; - napi_create_function(env, "conformsToProtocol", NAPI_AUTO_LENGTH, JS_classConformsToProtocolSafe, - nullptr, &safeConformsToProtocol); - napi_set_named_property(env, newConstructor, "conformsToProtocol", safeConformsToProtocol); - - // Get prototype for adding methods - napi_value newPrototype, basePrototype; - napi_get_named_property(env, newConstructor, "prototype", &newPrototype); - napi_get_named_property(env, thisArg, "prototype", &basePrototype); - - // Copy methods and accessors preserving descriptors. - napi_value global, objectCtor, getOwnPropertyDescriptors, defineProperties; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &objectCtor); - napi_get_named_property(env, objectCtor, "getOwnPropertyDescriptors", &getOwnPropertyDescriptors); - napi_get_named_property(env, objectCtor, "defineProperties", &defineProperties); - - napi_value descriptors; - napi_call_function(env, objectCtor, getOwnPropertyDescriptors, 1, args, &descriptors); - - napi_value defineArgs[] = {newPrototype, descriptors}; - napi_call_function(env, objectCtor, defineProperties, 2, defineArgs, nullptr); - - napi_value installSuperAccessor = nullptr; - napi_value installSuperScript = nullptr; - napi_create_string_utf8(env, kInstallSuperAccessorSource, NAPI_AUTO_LENGTH, &installSuperScript); - napi_run_script(env, installSuperScript, &installSuperAccessor); - napi_value superArgs[] = {newPrototype, basePrototype}; - napi_call_function(env, global, installSuperAccessor, 2, superArgs, nullptr); - - // Handle optional second parameter for metadata/options - if (hasOptionsObject) { - napi_value exposedMethods; - bool hasExposedMethods = false; - napi_has_named_property(env, options, "exposedMethods", &hasExposedMethods); - - if (hasExposedMethods) { - napi_get_named_property(env, options, "exposedMethods", &exposedMethods); - napi_set_named_property(env, newConstructor, "ObjCExposedMethods", exposedMethods); - } - - napi_value protocols; - bool hasProtocols = false; - napi_has_named_property(env, options, "protocols", &hasProtocols); - - if (hasProtocols) { - napi_get_named_property(env, options, "protocols", &protocols); - napi_set_named_property(env, newConstructor, "ObjCProtocols", protocols); - } - } - - napi_value classNameValue = nullptr; - napi_create_string_utf8(env, newClassName.c_str(), newClassName.length(), &classNameValue); - napi_set_named_property(env, newConstructor, "ObjCClassName", classNameValue); - - napi_value registryGlobal = nullptr; - napi_value classRegistry = nullptr; - bool hasClassRegistry = false; - napi_get_global(env, ®istryGlobal); - napi_has_named_property(env, registryGlobal, "__nsConstructorsByObjCClassName", - &hasClassRegistry); - if (!hasClassRegistry) { - napi_create_object(env, &classRegistry); - napi_set_named_property(env, registryGlobal, "__nsConstructorsByObjCClassName", classRegistry); - } else { - napi_get_named_property(env, registryGlobal, "__nsConstructorsByObjCClassName", &classRegistry); - } - - napi_value installClassFromStringAliasScript = nullptr; - napi_value installClassFromStringAlias = nullptr; - if (napi_create_string_utf8(env, kInstallClassFromStringAliasSource, NAPI_AUTO_LENGTH, - &installClassFromStringAliasScript) == napi_ok && - napi_run_script(env, installClassFromStringAliasScript, &installClassFromStringAlias) == - napi_ok && - installClassFromStringAlias != nullptr) { - napi_value aliasArgs[] = {registryGlobal}; - if (napi_call_function(env, registryGlobal, installClassFromStringAlias, 1, aliasArgs, - nullptr) != napi_ok) { - clearPendingException(env); - } - } else { - clearPendingException(env); - } - - if (classRegistry != nullptr) { - napi_set_named_property(env, classRegistry, newClassName.c_str(), newConstructor); - if (shouldAliasRequestedName && !requestedName.empty()) { - napi_set_named_property(env, classRegistry, requestedName.c_str(), newConstructor); - } - } - - // Use ClassBuilder to create the native class and bridge the methods - ClassBuilder* builder = new ClassBuilder(env, newConstructor, baseNativeClass); - builder->build(); - - // Register the builder in the bridge state - bridgeState = ObjCBridgeState::InstanceData(env); - bridgeState->registerRuntimeClass(builder, builder->nativeClass); - - return newConstructor; -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/Closure.h b/NativeScript/ffi/napi/Closure.h deleted file mode 100644 index 7ceafd49c..000000000 --- a/NativeScript/ffi/napi/Closure.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef CLOSURE_H -#define CLOSURE_H - -#include - -#include -#include -#include - -#include "MetadataReader.h" -#include "TypeConv.h" -#include "ffi.h" -#include "node_api_util.h" -#include "objc/runtime.h" - -namespace nativescript { - -class ObjCBridgeState; - -class Closure { - public: - static void callBlockFromMainThread(napi_env env, napi_value js_cb, - void* context, void* data); - static void destroyOnOwningThread(Closure* closure); - - Closure(napi_env env, std::string typeEncoding, bool isBlock, bool isMethod = false); - Closure(napi_env env, MDMetadataReader* reader, MDSectionOffset offset, - bool isBlock = false, std::string* encoding = nullptr, - bool isMethod = false, bool isGetter = false, bool isSetter = false); - - ~Closure(); - void retain(); - void release(); - - napi_env env = nullptr; - ObjCBridgeState* bridgeState = nullptr; - uint64_t bridgeStateToken = 0; - napi_ref thisConstructor; - napi_ref func = nullptr; - bool isGetter = false; - bool isSetter = false; - std::string propertyName; - SEL selector = nullptr; - napi_threadsafe_function tsfn = nullptr; - - std::thread::id jsThreadId = std::this_thread::get_id(); - CFRunLoopRef jsRunLoop = CFRunLoopGetCurrent(); - std::atomic retainCount{1}; - - ffi_cif cif; - ffi_closure* closure; - void* fnptr; - ffi_type** atypes = nullptr; // Track malloc'd atypes array - - std::shared_ptr returnType; - std::vector> argTypes; -}; - -} // namespace nativescript - -#endif /* CLOSURE_H */ diff --git a/NativeScript/ffi/napi/Interop.mm b/NativeScript/ffi/napi/Interop.mm deleted file mode 100644 index ba9d7a8c9..000000000 --- a/NativeScript/ffi/napi/Interop.mm +++ /dev/null @@ -1,2138 +0,0 @@ -#include "Interop.h" -#include "ClassBuilder.h" -#include "Metadata.h" -#include "ObjCBridge.h" -#include "Util.h" -#include "js_native_api.h" -#include "js_native_api_types.h" -#include "node_api_util.h" - -#import -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace nativescript { - -namespace { -std::unordered_map g_pointerCache; -constexpr const char* kPointerMarker = "__ns_pointer"; -constexpr const char* kNativePointerProperty = "__ns_native_ptr"; -constexpr const char* kReferenceMarker = "__ns_reference"; -constexpr const char* kFunctionReferenceMarker = "__ns_function_reference"; -constexpr const char* kFunctionReferenceDataProperty = "__ns_function_reference_data"; -constexpr const char* kReferenceInitValueSymbolKey = "__ns_reference_init_value"; - -inline uintptr_t pointerKey(void* data) { return reinterpret_cast(data); } - -inline napi_value referenceInitValueSymbol(napi_env env) { - return jsSymbolFor(env, kReferenceInitValueSymbolKey); -} - -inline napi_value getReferenceInitValueProperty(napi_env env, napi_value value) { - if (value == nullptr) { - return nullptr; - } - - napi_value initValue = nullptr; - napi_get_property(env, value, referenceInitValueSymbol(env), &initValue); - return initValue; -} - -inline void setReferenceInitValueProperty(napi_env env, napi_value value, napi_value initValue) { - if (value == nullptr || initValue == nullptr) { - return; - } - - napi_set_property(env, value, referenceInitValueSymbol(env), initValue); -} - -inline void clearReferenceInitValueProperty(napi_env env, napi_value value) { - if (value == nullptr) { - return; - } - - bool deleted = false; - napi_delete_property(env, value, referenceInitValueSymbol(env), &deleted); -} - -inline bool isInteropTypeCode(int32_t value) { - switch (value) { - case mdTypeVoid: - case mdTypeBool: - case mdTypeChar: - case mdTypeUInt8: - case mdTypeSShort: - case mdTypeUShort: - case mdTypeUnichar: - case mdTypeSInt: - case mdTypeUInt: - case mdTypeSInt64: - case mdTypeUInt64: - case mdTypeFloat: - case mdTypeDouble: - case mdTypeString: - case mdTypeAnyObject: - case mdTypePointer: - case mdTypeSelector: - return true; - default: - return false; - } -} - -inline size_t referenceElementSize(Reference* ref) { - if (ref == nullptr || ref->type == nullptr || ref->type->type == nullptr || - ref->type->type->size == 0) { - return sizeof(void*); - } - - return ref->type->type->size; -} - -inline bool isArrayIndexProperty(napi_env env, napi_value property, uint32_t* index) { - if (index == nullptr) { - return false; - } - - napi_valuetype propertyType = napi_undefined; - napi_typeof(env, property, &propertyType); - - if (propertyType == napi_number) { - double number = 0; - if (napi_get_value_double(env, property, &number) != napi_ok) { - return false; - } - - if (!std::isfinite(number) || number < 0 || floor(number) != number || - number > static_cast(UINT32_MAX)) { - return false; - } - - *index = static_cast(number); - return true; - } - - if (propertyType != napi_string) { - return false; - } - - char chars[32]; - size_t length = 0; - if (napi_get_value_string_utf8(env, property, chars, sizeof(chars), &length) != napi_ok || - length == 0 || length >= sizeof(chars)) { - return false; - } - - for (size_t i = 0; i < length; i++) { - if (!std::isdigit(static_cast(chars[i]))) { - return false; - } - } - - char* end = nullptr; - unsigned long long parsed = std::strtoull(chars, &end, 10); - if (end == nullptr || *end != '\0' || parsed > static_cast(UINT32_MAX)) { - return false; - } - - *index = static_cast(parsed); - return true; -} - -inline napi_value referenceValueAtIndex(napi_env env, Reference* ref, uint32_t index) { - napi_value undefined; - napi_get_undefined(env, &undefined); - - if (ref == nullptr || ref->type == nullptr || ref->data == nullptr) { - return undefined; - } - - auto slot = - static_cast(ref->data) + (static_cast(index) * referenceElementSize(ref)); - napi_value value = ref->type->toJS(env, slot); - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType == napi_boolean) { - bool boolValue = false; - napi_get_value_bool(env, value, &boolValue); - napi_create_uint32(env, boolValue ? 1 : 0, &value); - } - return value; -} - -inline bool referenceSetValueAtIndex(napi_env env, Reference* ref, uint32_t index, - napi_value value) { - if (ref == nullptr || ref->type == nullptr || ref->data == nullptr) { - napi_throw_error(env, nullptr, "Reference is not initialized"); - return false; - } - - auto slot = - static_cast(ref->data) + (static_cast(index) * referenceElementSize(ref)); - bool shouldFree = false; - ref->type->toNative(env, value, slot, &shouldFree, &shouldFree); - - bool hasPendingException = false; - napi_is_exception_pending(env, &hasPendingException); - return !hasPendingException; -} - -napi_value referenceProxyGetter(napi_env env, napi_callback_info info) { - size_t argc = 3; - napi_value argv[3] = {nullptr, nullptr, nullptr}; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - napi_value target = argc > 0 ? argv[0] : nullptr; - napi_value property = argc > 1 ? argv[1] : nullptr; - - uint32_t index = 0; - if (property != nullptr && isArrayIndexProperty(env, property, &index)) { - return referenceValueAtIndex(env, Reference::unwrap(env, target), index); - } - - napi_value result; - if (napi_get_property(env, target, property, &result) != napi_ok) { - return nullptr; - } - - napi_valuetype resultType = napi_undefined; - napi_typeof(env, result, &resultType); - if (resultType == napi_function) { - napi_value bind; - if (napi_get_named_property(env, result, "bind", &bind) == napi_ok) { - napi_value bound; - napi_value bindArgs[1] = {target}; - if (napi_call_function(env, result, bind, 1, bindArgs, &bound) == napi_ok) { - return bound; - } - } - } - - return result; -} - -napi_value referenceProxySetter(napi_env env, napi_callback_info info) { - size_t argc = 4; - napi_value argv[4] = {nullptr, nullptr, nullptr, nullptr}; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - napi_value target = argc > 0 ? argv[0] : nullptr; - napi_value property = argc > 1 ? argv[1] : nullptr; - napi_value value = argc > 2 ? argv[2] : nullptr; - - uint32_t index = 0; - bool ok = true; - if (property != nullptr && isArrayIndexProperty(env, property, &index)) { - ok = referenceSetValueAtIndex(env, Reference::unwrap(env, target), index, value); - } else { - ok = napi_set_property(env, target, property, value) == napi_ok; - } - - napi_value result; - napi_get_boolean(env, ok, &result); - return result; -} - -inline napi_value createReferenceProxy(napi_env env, napi_value target, Reference* reference) { - napi_value global; - napi_get_global(env, &global); - - napi_value proxyCtor; - if (napi_get_named_property(env, global, "Proxy", &proxyCtor) != napi_ok) { - return target; - } - - napi_value handler; - napi_create_object(env, &handler); - const napi_property_descriptor handlerProperties[] = { - { - .utf8name = "get", - .method = referenceProxyGetter, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_default, - .data = nullptr, - }, - { - .utf8name = "set", - .method = referenceProxySetter, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_default, - .data = nullptr, - }, - }; - napi_define_properties(env, handler, 2, handlerProperties); - - napi_value proxyArgs[2] = {target, handler}; - napi_value proxy; - if (napi_new_instance(env, proxyCtor, 2, proxyArgs, &proxy) != napi_ok) { - return target; - } - - napi_wrap(env, proxy, reference, nullptr, nullptr, nullptr); - - return proxy; -} - -void finalizeReferenceNow(napi_env env, void* data, void* hint) { - (void)env; - (void)hint; - Reference* ref = (Reference*)data; - delete ref; -} - -void finalizeFunctionReferenceNow(napi_env env, void* data, void* hint) { - (void)env; - (void)hint; - FunctionReference* ref = (FunctionReference*)data; - delete ref; -} - -void finalizePointerNow(napi_env env, void* data, void* hint) { - (void)env; - (void)hint; - Pointer* ptr = (Pointer*)data; - if (ptr == nullptr) { - return; - } - auto it = g_pointerCache.find(pointerKey(ptr->data)); - if (it != g_pointerCache.end()) { - g_pointerCache.erase(it); - } - delete ptr; -} - -inline bool getCachedPointer(napi_env env, void* data, napi_value* value) { - auto it = g_pointerCache.find(pointerKey(data)); - if (it == g_pointerCache.end()) { - return false; - } - - *value = get_ref_value(env, it->second); - if (*value == nullptr) { - napi_delete_reference(env, it->second); - g_pointerCache.erase(it); - return false; - } - - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, *value, &valueType) != napi_ok || valueType != napi_object) { - napi_delete_reference(env, it->second); - g_pointerCache.erase(it); - *value = nullptr; - return false; - } - - Pointer* ptr = Pointer::unwrap(env, *value); - if (ptr == nullptr || ptr->data != data) { - napi_delete_reference(env, it->second); - g_pointerCache.erase(it); - *value = nullptr; - return false; - } - - return true; -} - -inline void cachePointer(napi_env env, void* data, napi_value value) { - const uintptr_t key = pointerKey(data); - if (g_pointerCache.find(key) != g_pointerCache.end()) { - return; - } - napi_ref ref = nullptr; - napi_create_reference(env, value, 0, &ref); - g_pointerCache[key] = ref; -} - -inline std::string pointerHexString(void* data) { - uintptr_t value = reinterpret_cast(data); - if (value == 0) { - return "0x0"; - } - - char hex[2 + sizeof(uintptr_t) * 2 + 1]; -#if UINTPTR_MAX == 0xffffffff - snprintf(hex, sizeof(hex), "0x%x", static_cast(value)); -#else - snprintf(hex, sizeof(hex), "0x%llx", static_cast(value)); -#endif - return std::string(hex); -} - -inline void* lookupSymbolByName(ObjCBridgeState* bridgeState, const char* symbolName) { - void* fn = dlsym(bridgeState->self_dl, symbolName); - if (fn == nullptr) { - fn = dlsym(RTLD_DEFAULT, symbolName); - } - if (fn == nullptr) { - std::string underscored = "_"; - underscored += symbolName; - fn = dlsym(bridgeState->self_dl, underscored.c_str()); - if (fn == nullptr) { - fn = dlsym(RTLD_DEFAULT, underscored.c_str()); - } - } - return fn; -} - -inline bool unwrapKnownNativeHandle(napi_env env, napi_value value, void** out) { - if (value == nullptr) { - return false; - } - - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - id bridgedType = nil; - if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && - bridgedType != nil) { - *out = (void*)bridgedType; - return true; - } - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *out = ptr != nullptr ? ptr->data : nullptr; - return true; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *out = ref != nullptr ? ref->data : nullptr; - return true; - } - - if (StructObject* structObject = StructObject::unwrap(env, value)) { - *out = structObject->data; - return structObject->data != nullptr; - } - - void* wrapped = nullptr; - if (napi_unwrap(env, value, &wrapped) != napi_ok || wrapped == nullptr) { - bool hasNativePointer = false; - napi_has_named_property(env, value, kNativePointerProperty, &hasNativePointer); - if (hasNativePointer) { - napi_value nativePointerValue; - if (napi_get_named_property(env, value, kNativePointerProperty, &nativePointerValue) == - napi_ok) { - if (Pointer::isInstance(env, nativePointerValue)) { - Pointer* pointer = Pointer::unwrap(env, nativePointerValue); - if (pointer != nullptr && pointer->data != nullptr) { - *out = pointer->data; - return true; - } - } else { - void* nativePointer = nullptr; - if (napi_get_value_external(env, nativePointerValue, &nativePointer) == napi_ok && - nativePointer != nullptr) { - *out = nativePointer; - return true; - } - } - } - } - - return false; - } - - bridgeState = ObjCBridgeState::InstanceData(env); - for (const auto& entry : bridgeState->classes) { - if (entry.second == wrapped) { - *out = (void*)entry.second->nativeClass; - return true; - } - } - - for (const auto& entry : bridgeState->protocols) { - if (entry.second == wrapped) { - *out = (void*)objc_getProtocol(entry.second->name.c_str()); - return true; - } - } - - for (const auto& entry : bridgeState->cFunctionCache) { - if (entry.second == wrapped) { - *out = entry.second->fnptr; - return true; - } - } - - *out = wrapped; - return true; -} - -inline bool resolveNativePointerFromRegisteredFunction(napi_env env, napi_value value, void** out) { - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState == nullptr || bridgeState->metadata == nullptr) { - return false; - } - - napi_value global; - if (napi_get_global(env, &global) != napi_ok) { - return false; - } - - MDSectionOffset offset = bridgeState->metadata->functionsOffset; - while (offset < bridgeState->metadata->protocolsOffset) { - MDSectionOffset originalOffset = offset; - const char* functionName = bridgeState->metadata->getString(offset); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDFunctionFlag); - - napi_value globalFn; - if (napi_get_named_property(env, global, functionName, &globalFn) != napi_ok) { - continue; - } - - bool isSameFunction = false; - napi_strict_equals(env, value, globalFn, &isSameFunction); - if (!isSameFunction) { - continue; - } - - auto cFunction = bridgeState->getCFunction(env, originalOffset); - if (cFunction != nullptr && cFunction->fnptr != nullptr) { - *out = cFunction->fnptr; - return true; - } - - void* fn = lookupSymbolByName(bridgeState, functionName); - if (fn != nullptr) { - *out = fn; - return true; - } - return false; - } - - return false; -} - -inline bool resolveNativePointerFromFunctionName(napi_env env, napi_value value, void** out) { - napi_valuetype type = napi_undefined; - napi_typeof(env, value, &type); - if (type != napi_function) { - return false; - } - - napi_value nameValue; - if (napi_get_named_property(env, value, "name", &nameValue) != napi_ok) { - return false; - } - - napi_valuetype nameType = napi_undefined; - napi_typeof(env, nameValue, &nameType); - if (nameType != napi_string) { - return false; - } - - char name[512]; - size_t len = 0; - if (napi_get_value_string_utf8(env, nameValue, name, sizeof(name), &len) != napi_ok || len == 0) { - return resolveNativePointerFromRegisteredFunction(env, value, out); - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState == nullptr) { - return resolveNativePointerFromRegisteredFunction(env, value, out); - } - void* fn = lookupSymbolByName(bridgeState, name); - if (fn == nullptr) { - return resolveNativePointerFromRegisteredFunction(env, value, out); - } - - *out = fn; - return true; -} - -inline void setObjectPrototype(napi_env env, napi_value object, napi_value prototype) { - napi_value global; - napi_get_global(env, &global); - - napi_value jsObject; - napi_get_named_property(env, global, "Object", &jsObject); - - napi_value setPrototypeOf; - napi_get_named_property(env, jsObject, "setPrototypeOf", &setPrototypeOf); - - napi_value argv[2] = {object, prototype}; - napi_call_function(env, jsObject, setPrototypeOf, 2, argv, nullptr); -} -} // namespace - -inline napi_value createJSNumber(napi_env env, int32_t ival) { - napi_value value; - napi_create_int32(env, ival, &value); - return value; -} - -napi_value __extends(napi_env env, napi_callback_info info) { - napi_value argv[2]; - size_t argc = 2; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - napi_value constructor = argv[0]; - napi_value superConstructor = argv[1]; - - napi_inherits(env, constructor, superConstructor); - - Class superClassNative = nullptr; - napi_unwrap(env, superConstructor, (void**)&superClassNative); - if (superClassNative != nullptr) { - ClassBuilder* builder = new ClassBuilder(env, constructor); - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - bridgeState->registerRuntimeClass(builder, builder->nativeClass); - } - - return nullptr; -} - -const char* jsHelpersSource = R"( - if (typeof globalThis.__decorate !== "function") { - globalThis.__decorate = function(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - } - - if (typeof globalThis.__param !== "function") { - globalThis.__param = function(paramIndex, decorator) { - return function(target, key) { decorator(target, key, paramIndex); }; - }; - } - - globalThis.ObjCClass = function ObjCClass(...protocols) { - return function(constructor) { - if (constructor.ObjCProtocols) { - constructor.ObjCProtocols.push(...protocols); - } else { - constructor.ObjCProtocols = protocols; - } - - if (typeof globalThis.NativeClass === "function") { - return globalThis.NativeClass(constructor); - } - - return constructor; - }; - }; - - if (typeof WeakRef === "function") { - WeakRef.prototype.get = WeakRef.prototype.deref; - if (!WeakRef.prototype.clear) { - WeakRef.prototype.clear = function() { - console.warn("WeakRef.clear() is non-standard and has been deprecated. It does nothing and the call can be safely removed."); - }; - } - } -)"; - -void registerInterop(napi_env env, napi_value global) { - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - - const napi_property_descriptor __extendsProperty = { - .utf8name = "__extends", - .method = __extends, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }; - - napi_define_properties(env, global, 1, &__extendsProperty); - - napi_value jsHelpers; - napi_create_string_utf8(env, jsHelpersSource, NAPI_AUTO_LENGTH, &jsHelpers); - napi_value result; - napi_run_script(env, jsHelpers, &result); - - napi_value interop; - napi_create_object(env, &interop); - - napi_value types; - napi_create_object(env, &types); - - napi_set_named_property(env, types, "void", createJSNumber(env, mdTypeVoid)); - napi_set_named_property(env, types, "bool", createJSNumber(env, mdTypeBool)); - napi_set_named_property(env, types, "int8", createJSNumber(env, mdTypeChar)); - napi_set_named_property(env, types, "uint8", createJSNumber(env, mdTypeUInt8)); - napi_set_named_property(env, types, "int16", createJSNumber(env, mdTypeSShort)); - napi_set_named_property(env, types, "uint16", createJSNumber(env, mdTypeUShort)); - napi_set_named_property(env, types, "int32", createJSNumber(env, mdTypeSInt)); - napi_set_named_property(env, types, "uint32", createJSNumber(env, mdTypeUInt)); - napi_set_named_property(env, types, "int64", createJSNumber(env, mdTypeSInt64)); - napi_set_named_property(env, types, "uint64", createJSNumber(env, mdTypeUInt64)); - napi_set_named_property(env, types, "float", createJSNumber(env, mdTypeFloat)); - napi_set_named_property(env, types, "double", createJSNumber(env, mdTypeDouble)); - napi_set_named_property(env, types, "UTF8CString", createJSNumber(env, mdTypeString)); - napi_set_named_property(env, types, "unichar", createJSNumber(env, mdTypeUnichar)); - napi_set_named_property(env, types, "id", createJSNumber(env, mdTypeAnyObject)); - napi_set_named_property(env, types, "protocol", createJSNumber(env, mdTypePointer)); - napi_set_named_property(env, types, "class", createJSNumber(env, mdTypeAnyObject)); - napi_set_named_property(env, types, "SEL", createJSNumber(env, mdTypeSelector)); - napi_set_named_property(env, types, "selector", createJSNumber(env, mdTypeSelector)); - napi_set_named_property(env, types, "pointer", createJSNumber(env, mdTypePointer)); - - napi_value Pointer = Pointer::defineJSClass(env); - bridgeState->pointerClass = make_ref(env, Pointer); - - napi_value Reference = Reference::defineJSClass(env); - bridgeState->referenceClass = make_ref(env, Reference); - - napi_value FunctionReference = FunctionReference::defineJSClass(env); - bridgeState->functionReferenceClass = make_ref(env, FunctionReference); - - const napi_property_descriptor properties[] = { - { - .utf8name = "Pointer", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = Pointer, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "Reference", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = Reference, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "FunctionReference", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = FunctionReference, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "types", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = types, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "adopt", - .method = interop_adopt, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "addMethod", - .method = interop_addMethod, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "addProtocol", - .method = interop_addProtocol, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "free", - .method = interop_free, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "sizeof", - .method = interop_sizeof, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "alloc", - .method = interop_alloc, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "handleof", - .method = interop_handleof, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "bufferFromData", - .method = interop_bufferFromData, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "stringFromCString", - .method = interop_stringFromCString, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - }; - - napi_define_properties(env, interop, 13, properties); - - napi_set_named_property(env, global, "interop", interop); -} - -napi_value interop_addMethod(napi_env env, napi_callback_info info) { - napi_value argv[2]; - size_t argc = 2; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - ClassBuilder* builder = nullptr; - napi_unwrap(env, argv[0], (void**)&builder); - if (builder == nullptr) { - napi_throw_error(env, nullptr, "Invalid class"); - return nullptr; - } - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - builder = (ClassBuilder*)bridgeState->classesByPointer[(id)builder]; - - napi_value name; - napi_get_named_property(env, argv[1], "name", &name); - - if (builder->isFinal) { - static char funcNameBuf[512]; - napi_get_value_string_utf8(env, name, funcNameBuf, 512, nullptr); - std::string funcName = funcNameBuf; - - MethodDescriptor* desc = builder->lookupMethodDescriptor(funcName); - if (desc == nullptr) { - napi_throw_error(env, nullptr, "Invalid method, descriptor not found"); - return nullptr; - } - - builder->addMethod(funcName, desc, name, argv[1]); - } else { - napi_set_property(env, get_ref_value(env, builder->prototype), name, argv[1]); - } - - return nullptr; -} - -napi_value interop_addProtocol(napi_env env, napi_callback_info info) { - napi_value argv[2]; - size_t argc = 2; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - ClassBuilder* builder = nullptr; - napi_unwrap(env, argv[0], (void**)&builder); - if (builder == nullptr) { - napi_throw_error(env, nullptr, "Invalid class"); - return nullptr; - } - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - builder = (ClassBuilder*)bridgeState->classesByPointer[(id)builder]; - - ObjCProtocol* proto = nullptr; - napi_unwrap(env, argv[1], (void**)&proto); - if (proto == nullptr) { - napi_throw_error(env, nullptr, "Invalid protocol"); - return nullptr; - } - - builder->addProtocol(proto); - - return nullptr; -} - -napi_value interop_adopt(napi_env env, napi_callback_info info) { - napi_value arg; - size_t argc = 1; - napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); - - Pointer* ptr; - napi_unwrap(env, arg, (void**)&ptr); - - napi_value adopted; - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - napi_new_instance(env, get_ref_value(env, bridgeState->pointerClass), 1, &arg, &adopted); - Pointer* adoptedPtr; - napi_unwrap(env, adopted, (void**)&adoptedPtr); - adoptedPtr->adopted = true; - - return adopted; -} - -napi_value interop_free(napi_env env, napi_callback_info info) { - napi_value arg; - size_t argc = 1; - napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); - - Pointer* ptr = nullptr; - napi_unwrap(env, arg, (void**)&ptr); - - if (ptr != nullptr && ptr->data != nullptr) { - auto it = g_pointerCache.find(pointerKey(ptr->data)); - if (it != g_pointerCache.end()) { - napi_delete_reference(env, it->second); - g_pointerCache.erase(it); - } - free(ptr->data); - ptr->data = nullptr; - } - - return nullptr; -} - -napi_value interop_stringFromCString(napi_env env, napi_callback_info info) { - size_t argc = 2; - napi_value argv[2] = {nullptr, nullptr}; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - napi_value arg = argv[0]; - - napi_valuetype type; - napi_typeof(env, arg, &type); - - if (type == napi_string) { - return arg; - } - - if (type != napi_object) { - napi_throw_type_error(env, "TypeError", "Expected an object"); - return nullptr; - } - - void* data = nullptr; - - if (Pointer::isInstance(env, arg)) { - Pointer* ptr = Pointer::unwrap(env, arg); - data = ptr->data; - } - - if (Reference::isInstance(env, arg)) { - Reference* ref = Reference::unwrap(env, arg); - data = ref->data; - } - - napi_value result; - - if (data == nullptr) { - napi_get_null(env, &result); - } else { - size_t length = NAPI_AUTO_LENGTH; - if (argc >= 2 && argv[1] != nullptr) { - napi_valuetype lengthType = napi_undefined; - napi_typeof(env, argv[1], &lengthType); - if (lengthType != napi_undefined && lengthType != napi_null) { - int64_t explicitLength = 0; - napi_get_value_int64(env, argv[1], &explicitLength); - if (explicitLength < 0) { - explicitLength = 0; - } - length = static_cast(explicitLength); - } - } - napi_create_string_utf8(env, (const char*)data, length, &result); - } - - return result; -} - -size_t jsSizeof(napi_env env, napi_value arg) { - napi_valuetype type; - napi_typeof(env, arg, &type); - - switch (type) { - case napi_number: { - int32_t ival; - napi_get_value_int32(env, arg, &ival); - - switch (ival) { - case mdTypeVoid: - return 0; - - case mdTypeBool: - case mdTypeChar: - case mdTypeUInt8: - return sizeof(uint8_t); - - case mdTypeSShort: - case mdTypeUShort: - case mdTypeUnichar: - return sizeof(uint16_t); - - case mdTypeSInt: - case mdTypeUInt: - return sizeof(uint32_t); - - case mdTypeSInt64: - case mdTypeUInt64: - return sizeof(uint64_t); - - case mdTypeFloat: - return sizeof(float); - - case mdTypeDouble: - return sizeof(double); - - case mdTypeString: - return sizeof(char*); - - case mdTypeAnyObject: - return sizeof(id); - - case mdTypePointer: - return sizeof(void*); - - case mdTypeSelector: - return sizeof(SEL); - - default: - napi_throw_type_error(env, "TypeError", - "Invalid type number for sizeof. Use interop.types.*"); - } - } - - case napi_object: - case napi_function: { - napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); - napi_value result; - if (napi_get_property(env, arg, symbolSizeof, &result) == napi_ok) { - napi_valuetype resultType; - napi_typeof(env, result, &resultType); - if (resultType == napi_number) { - int32_t size; - napi_get_value_int32(env, result, &size); - return size; - } - } - - void* nativeHandle = nullptr; - if (unwrapKnownNativeHandle(env, arg, &nativeHandle)) { - return sizeof(void*); - } - if (resolveNativePointerFromFunctionName(env, arg, &nativeHandle)) { - return sizeof(void*); - } - - napi_throw_type_error(env, "TypeError", "Invalid type for sizeof"); - } - - default: - napi_throw_type_error(env, "TypeError", "Invalid type for sizeof"); - } - - return -1; -} - -napi_value interop_sizeof(napi_env env, napi_callback_info info) { - napi_valuetype type; - napi_value arg; - size_t argc = 1; - napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); - - size_t size = jsSizeof(env, arg); - napi_value result; - napi_create_int32(env, size, &result); - - return result; -} - -napi_value interop_alloc(napi_env env, napi_callback_info info) { - napi_value arg; - size_t argc = 1; - napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); - - int64_t size; - napi_get_value_int64(env, arg, &size); - - void* data = calloc(1, size); - return Pointer::create(env, data); -} - -napi_value interop_handleof(napi_env env, napi_callback_info info) { - napi_value value, result; - size_t argc = 1; - napi_get_cb_info(env, info, &argc, &value, nullptr, nullptr); - - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType == napi_null || valueType == napi_undefined) { - napi_get_null(env, &result); - return result; - } - - if (valueType == napi_string) { - size_t len = 0; - napi_get_value_string_utf8(env, value, nullptr, 0, &len); - char* str = static_cast(malloc(len + 1)); - napi_get_value_string_utf8(env, value, str, len + 1, &len); - str[len] = '\0'; - return Pointer::create(env, str); - } - - if (Pointer::isInstance(env, value)) { - return value; - } else if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - if (ref == nullptr || ref->data == nullptr) { - napi_throw_error(env, nullptr, "Reference is not initialized"); - return nullptr; - } - return Pointer::create(env, ref->data); - } - - if (FunctionReference::isInstance(env, value)) { - FunctionReference* reference = FunctionReference::unwrap(env, value); - if (reference == nullptr || reference->closure == nullptr || - reference->closure->fnptr == nullptr) { - napi_throw_error(env, nullptr, "FunctionReference is not initialized"); - return nullptr; - } - return Pointer::create(env, reference->closure->fnptr); - } - - void* data = nullptr; - if (unwrapKnownNativeHandle(env, value, &data) && data != nullptr) { - if (valueType == napi_object && !Pointer::isInstance(env, value) && - !Reference::isInstance(env, value) && !FunctionReference::isInstance(env, value)) { - ObjCBridgeState::InstanceData(env)->cacheHandleObject(env, data, value); - } - return Pointer::create(env, data); - } - if (resolveNativePointerFromFunctionName(env, value, &data) && data != nullptr) { - return Pointer::create(env, data); - } - - napi_get_null(env, &result); - return result; -} - -napi_value interop_bufferFromData(napi_env env, napi_callback_info info) { - napi_value arg; - size_t argc = 1; - napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); - - bool isArrayBuffer = false; - napi_is_arraybuffer(env, arg, &isArrayBuffer); - if (isArrayBuffer) { - return arg; - } - - NSData* data = nil; - if (napi_unwrap(env, arg, (void**)&data) != napi_ok || data == nil) { - void* native = nullptr; - if (!unwrapKnownNativeHandle(env, arg, &native) || native == nullptr) { - napi_throw_error(env, nullptr, "Invalid data"); - return nullptr; - } - - id candidate = (id)native; - if (![candidate isKindOfClass:[NSData class]]) { - napi_throw_error(env, nullptr, "Invalid data"); - return nullptr; - } - data = (NSData*)candidate; - } - - if (data == nil) { - napi_throw_error(env, nullptr, "Invalid data"); - return nullptr; - } - - napi_value result = nullptr; - void* bufferData = nullptr; - napi_status status = - napi_create_arraybuffer(env, static_cast(data.length), &bufferData, &result); - if (status != napi_ok || result == nullptr) { - napi_throw_error(env, nullptr, "Failed to create ArrayBuffer"); - return nullptr; - } - - if (data.length > 0 && bufferData != nullptr) { - std::memcpy(bufferData, data.bytes, static_cast(data.length)); - } - - return result; -} - -napi_value Pointer::defineJSClass(napi_env env) { - const napi_property_descriptor properties[] = { - { - .utf8name = "add", - .method = Pointer::add, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "subtract", - .method = Pointer::subtract, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "toNumber", - .method = Pointer::toNumber, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "toBigInt", - .method = Pointer::toBigInt, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "toHexString", - .method = Pointer::toHexString, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "toDecimalString", - .method = Pointer::toDecimalString, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "toString", - .method = Pointer::toString, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = nullptr, - .name = jsSymbolFor(env, "nodejs.util.inspect.custom"), - .method = Pointer::customInspect, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - }; - - napi_value constructor; - napi_define_class(env, "Pointer", NAPI_AUTO_LENGTH, Pointer::constructor, nullptr, 8, properties, - &constructor); - - napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); - napi_value sizeValue; - napi_create_int32(env, sizeof(void*), &sizeValue); - napi_set_property(env, constructor, symbolSizeof, sizeValue); - - napi_value prototype; - napi_get_named_property(env, constructor, "prototype", &prototype); - napi_set_property(env, prototype, symbolSizeof, sizeValue); - - napi_value marker; - napi_get_boolean(env, true, &marker); - napi_property_descriptor markerProp = { - .utf8name = kPointerMarker, - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = marker, - .attributes = napi_configurable, - .data = nullptr, - }; - napi_define_properties(env, prototype, 1, &markerProp); - - return constructor; -} - -bool Pointer::isInstance(napi_env env, napi_value value) { - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType != napi_object && valueType != napi_function) { - return false; - } - - bool hasMarker = false; - napi_has_named_property(env, value, kPointerMarker, &hasMarker); - if (!hasMarker) { - return false; - } - - napi_value marker = nullptr; - if (napi_get_named_property(env, value, kPointerMarker, &marker) != napi_ok || - marker == nullptr) { - return false; - } - - bool markerValue = false; - napi_get_value_bool(env, marker, &markerValue); - return markerValue; -} - -napi_value Pointer::create(napi_env env, void* data) { - napi_value cached; - if (getCachedPointer(env, data, &cached)) { - return cached; - } - - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - napi_value jsPointer = get_ref_value(env, bridgeState->pointerClass); - napi_value argv[1]; - napi_create_bigint_uint64(env, static_cast(reinterpret_cast(data)), - &argv[0]); - napi_value result; - napi_status status = napi_new_instance(env, jsPointer, 1, argv, &result); - - if (status != napi_ok) { - return nullptr; - } - return result; -} - -Pointer* Pointer::unwrap(napi_env env, napi_value value) { - if (value == nullptr) { - return nullptr; - } - - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok || valueType != napi_object) { - return nullptr; - } - - Pointer* ptr = nullptr; - if (napi_unwrap(env, value, (void**)&ptr) != napi_ok) { - return nullptr; - } - return ptr; -} - -napi_value Pointer::constructor(napi_env env, napi_callback_info info) { - napi_value jsThis; - size_t argc = 1; - napi_value argv[1]; - napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr); - - napi_valuetype thisType = napi_undefined; - if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || - (thisType != napi_object && thisType != napi_function)) { - napi_create_object(env, &jsThis); - - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && bridgeState->pointerClass != nullptr) { - napi_value pointerCtor = get_ref_value(env, bridgeState->pointerClass); - napi_value pointerPrototype = nullptr; - if (pointerCtor != nullptr && - napi_get_named_property(env, pointerCtor, "prototype", &pointerPrototype) == napi_ok && - pointerPrototype != nullptr) { - napi_value global = nullptr; - napi_value objectCtor = nullptr; - napi_value setPrototypeOf = nullptr; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &objectCtor); - napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); - napi_value setPrototypeArgs[2] = {jsThis, pointerPrototype}; - napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); - } - } - } - - napi_value arg; - if (argc == 0) { - napi_get_undefined(env, &arg); - } else { - arg = argv[0]; - } - - napi_valuetype type = napi_undefined; - napi_typeof(env, arg, &type); - - void* data; - - switch (type) { - case napi_number: { - double number = 0; - napi_get_value_double(env, arg, &number); - if (!std::isfinite(number)) { - napi_throw_error(env, nullptr, "Invalid type"); - return nullptr; - } - data = (void*)((intptr_t)number); - break; - } - - case napi_bigint: { - int64_t value = 0; - bool lossless = false; - napi_get_value_bigint_int64(env, arg, &value, &lossless); - data = (void*)((intptr_t)value); - break; - } - - case napi_object: { - bool isInstance = Pointer::isInstance(env, arg); - if (isInstance) { - Pointer* ptr; - napi_unwrap(env, arg, (void**)&ptr); - if (ptr == nullptr) { - napi_throw_error(env, nullptr, "Invalid type"); - return nullptr; - } - return arg; - } else { - napi_value numberValue; - napi_coerce_to_number(env, arg, &numberValue); - double number = 0; - napi_get_value_double(env, numberValue, &number); - if (!std::isfinite(number)) { - napi_throw_error(env, nullptr, "Invalid type"); - return nullptr; - } - data = (void*)((intptr_t)number); - } - break; - } - - case napi_null: - case napi_undefined: { - data = nullptr; - break; - } - - default: - napi_throw_error(env, nullptr, "Invalid type"); - return nullptr; - } - - napi_value cached; - if (getCachedPointer(env, data, &cached)) { - return cached; - } - - Pointer* ptr = new Pointer(data); - napi_wrap(env, jsThis, ptr, Pointer::finalize, nullptr, nullptr); - cachePointer(env, data, jsThis); - - return jsThis; -} - -napi_value Pointer::add(napi_env env, napi_callback_info info) { - napi_value arg, jsThis; - size_t argc = 1; - napi_get_cb_info(env, info, &argc, &arg, &jsThis, nullptr); - - Pointer* ptr = Pointer::unwrap(env, jsThis); - - int64_t ival; - napi_get_value_int64(env, arg, &ival); - - void* newData = (void*)((intptr_t)((intptr_t)ptr->data + (intptr_t)ival)); - return Pointer::create(env, newData); -} - -napi_value Pointer::subtract(napi_env env, napi_callback_info info) { - napi_value arg, jsThis; - size_t argc = 1; - napi_get_cb_info(env, info, &argc, &arg, &jsThis, nullptr); - - Pointer* ptr = Pointer::unwrap(env, jsThis); - - int64_t ival; - napi_get_value_int64(env, arg, &ival); - - void* newData = (void*)((intptr_t)((intptr_t)ptr->data - (intptr_t)ival)); - return Pointer::create(env, newData); -} - -napi_value Pointer::toNumber(napi_env env, napi_callback_info info) { - napi_value jsThis; - napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); - - Pointer* ptr = Pointer::unwrap(env, jsThis); - - napi_value result; - double value = static_cast(reinterpret_cast(ptr->data)); - napi_create_double(env, value, &result); - return result; -} - -napi_value Pointer::toBigInt(napi_env env, napi_callback_info info) { - napi_value jsThis; - napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); - - Pointer* ptr = Pointer::unwrap(env, jsThis); - - napi_value result; - napi_create_bigint_uint64(env, static_cast(reinterpret_cast(ptr->data)), - &result); - return result; -} - -napi_value Pointer::toHexString(napi_env env, napi_callback_info info) { - napi_value jsThis; - napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); - - Pointer* ptr = Pointer::unwrap(env, jsThis); - std::string hex = pointerHexString(ptr->data); - - napi_value result; - napi_create_string_utf8(env, hex.c_str(), NAPI_AUTO_LENGTH, &result); - return result; -} - -napi_value Pointer::toDecimalString(napi_env env, napi_callback_info info) { - napi_value jsThis; - napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); - - Pointer* ptr = Pointer::unwrap(env, jsThis); - std::string decimal; - if (sizeof(void*) == 4) { - decimal = std::to_string(static_cast(reinterpret_cast(ptr->data))); - } else { - decimal = std::to_string(static_cast(reinterpret_cast(ptr->data))); - } - - napi_value result; - napi_create_string_utf8(env, decimal.c_str(), NAPI_AUTO_LENGTH, &result); - return result; -} - -napi_value Pointer::toString(napi_env env, napi_callback_info info) { - return Pointer::customInspect(env, info); -} - -napi_value Pointer::customInspect(napi_env env, napi_callback_info info) { - napi_value jsThis; - napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); - - Pointer* ptr = Pointer::unwrap(env, jsThis); - std::string str = "data) + ">"; - - napi_value result; - napi_create_string_utf8(env, str.c_str(), NAPI_AUTO_LENGTH, &result); - - return result; -} - -void Pointer::finalize(napi_env env, void* data, void* hint) { - if (PostFinalizer(env, finalizePointerNow, data, hint)) { - return; - } - - finalizePointerNow(env, data, hint); -} - -Pointer::Pointer(void* data) { this->data = data; } - -Pointer::~Pointer() { - if (adopted && data != nullptr) { - free(data); - } -} - -napi_value Reference::defineJSClass(napi_env env) { - const napi_property_descriptor properties[] = { - { - .utf8name = "value", - .method = nullptr, - .getter = Reference::get_value, - .setter = Reference::set_value, - .value = nullptr, - .attributes = (napi_property_attributes)(napi_enumerable | napi_writable), - .data = nullptr, - }, - { - .utf8name = nullptr, - .name = jsSymbolFor(env, "nodejs.util.inspect.custom"), - .method = Reference::customInspect, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "toString", - .method = Reference::customInspect, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }, - }; - - napi_value constructor; - napi_define_class(env, "Reference", NAPI_AUTO_LENGTH, Reference::constructor, nullptr, 3, - properties, &constructor); - - napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); - napi_value sizeValue; - napi_create_int32(env, sizeof(void*), &sizeValue); - napi_set_property(env, constructor, symbolSizeof, sizeValue); - - napi_value prototype; - napi_get_named_property(env, constructor, "prototype", &prototype); - napi_set_property(env, prototype, symbolSizeof, sizeValue); - - napi_value marker; - napi_get_boolean(env, true, &marker); - napi_property_descriptor markerProp = { - .utf8name = kReferenceMarker, - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = marker, - .attributes = napi_configurable, - .data = nullptr, - }; - napi_define_properties(env, prototype, 1, &markerProp); - - return constructor; -} - -bool Reference::isInstance(napi_env env, napi_value value) { - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType != napi_object && valueType != napi_function) { - return false; - } - - bool hasMarker = false; - napi_has_named_property(env, value, kReferenceMarker, &hasMarker); - if (!hasMarker) { - return false; - } - - napi_value marker = nullptr; - if (napi_get_named_property(env, value, kReferenceMarker, &marker) != napi_ok || - marker == nullptr) { - return false; - } - - bool markerValue = false; - napi_get_value_bool(env, marker, &markerValue); - return markerValue; -} - -napi_value Reference::create(napi_env env, std::shared_ptr type, void* data, - bool ownsData) { - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - napi_value referenceCtor = get_ref_value(env, bridgeState->referenceClass); - napi_value jsReference = nullptr; - if (napi_new_instance(env, referenceCtor, 0, nullptr, &jsReference) != napi_ok) { - return nullptr; - } - - Reference* reference = Reference::unwrap(env, jsReference); - if (reference == nullptr) { - return nullptr; - } - - reference->env = env; - reference->bridgeState = ObjCBridgeState::InstanceData(env); - reference->bridgeStateToken = - reference->bridgeState != nullptr ? reference->bridgeState->lifetimeToken : 0; - reference->type = std::move(type); - reference->data = data; - reference->ownsData = ownsData; - - return jsReference; -} - -Reference* Reference::unwrap(napi_env env, napi_value value) { - Reference* ref = nullptr; - if (napi_unwrap(env, value, (void**)&ref) != napi_ok) { - return nullptr; - } - return ref; -} - -napi_value Reference::getInitValue(napi_env env, napi_value value, Reference* ref) { - napi_value initValue = getReferenceInitValueProperty(env, value); - napi_valuetype initType = napi_undefined; - if (initValue != nullptr && napi_typeof(env, initValue, &initType) == napi_ok && - initType != napi_undefined) { - return initValue; - } - - if (ref != nullptr && ref->initValue != nullptr) { - return get_ref_value(env, ref->initValue); - } - - return nullptr; -} - -void Reference::setInitValue(napi_env env, napi_value value, Reference* ref, napi_value initValue) { - setReferenceInitValueProperty(env, value, initValue); - - if (ref == nullptr) { - return; - } - - if (ref->initValue != nullptr) { - napi_delete_reference(env, ref->initValue); - ref->initValue = nullptr; - } - - if (initValue != nullptr) { - napi_create_reference(env, initValue, 1, &ref->initValue); - } -} - -void Reference::clearInitValue(napi_env env, napi_value value, Reference* ref) { - clearReferenceInitValueProperty(env, value); - - if (ref != nullptr && ref->initValue != nullptr) { - napi_delete_reference(env, ref->initValue); - ref->initValue = nullptr; - } -} - -napi_value Reference::constructor(napi_env env, napi_callback_info info) { - napi_value jsThis; - size_t argc = 2; - napi_value argv[2]; - napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr); - - napi_valuetype thisType = napi_undefined; - if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || - (thisType != napi_object && thisType != napi_function)) { - napi_create_object(env, &jsThis); - - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && bridgeState->referenceClass != nullptr) { - napi_value referenceCtor = get_ref_value(env, bridgeState->referenceClass); - napi_value referencePrototype = nullptr; - if (referenceCtor != nullptr && - napi_get_named_property(env, referenceCtor, "prototype", &referencePrototype) == - napi_ok && - referencePrototype != nullptr) { - napi_value global = nullptr; - napi_value objectCtor = nullptr; - napi_value setPrototypeOf = nullptr; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &objectCtor); - napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); - napi_value setPrototypeArgs[2] = {jsThis, referencePrototype}; - napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); - } - } - } - - Reference* reference = new Reference(); - reference->env = env; - reference->bridgeState = ObjCBridgeState::InstanceData(env); - reference->bridgeStateToken = - reference->bridgeState != nullptr ? reference->bridgeState->lifetimeToken : 0; - - if (argc == 0) { - // Leave it uninitialized. It can be materialized later by pointer marshalling. - } else if (argc == 1) { - napi_valuetype argType = napi_undefined; - napi_typeof(env, argv[0], &argType); - - bool isTypeArg = false; - if (argType == napi_function) { - isTypeArg = true; - } else if (argType == napi_number) { - double numericValue = 0; - if (napi_get_value_double(env, argv[0], &numericValue) == napi_ok && - std::isfinite(numericValue) && floor(numericValue) == numericValue && - numericValue >= static_cast(INT32_MIN) && - numericValue <= static_cast(INT32_MAX)) { - int32_t typeCode = static_cast(numericValue); - if (isInteropTypeCode(typeCode)) { - isTypeArg = true; - } - } - } - - if (isTypeArg) { - std::string typeEncoding = getEncodedType(env, argv[0]); - const char* typestr = typeEncoding.c_str(); - reference->type = TypeConv::Make(env, &typestr); - - size_t size = reference->type != nullptr && reference->type->type != nullptr && - reference->type->type->size > 0 - ? reference->type->type->size - : sizeof(void*); - reference->data = calloc(1, size); - reference->ownsData = true; - } else { - Reference::setInitValue(env, jsThis, reference, argv[0]); - } - } else if (argc == 2) { - std::string type = getEncodedType(env, argv[0]); - const char* typestr = type.c_str(); - reference->type = TypeConv::Make(env, &typestr); - - napi_valuetype argtype; - napi_typeof(env, argv[1], &argtype); - - if (argtype == napi_object && Pointer::isInstance(env, argv[1])) { - reference->data = Pointer::unwrap(env, argv[1])->data; - reference->ownsData = false; - } else if (reference->type != nullptr && reference->type->kind == mdTypeStruct && - argtype == napi_object && StructObject::isInstance(env, argv[1])) { - StructObject* structObject = StructObject::unwrap(env, argv[1]); - if (structObject != nullptr) { - reference->data = structObject->data; - reference->ownsData = false; - } - } else if (argtype == napi_object && Reference::isInstance(env, argv[1])) { - Reference* other = Reference::unwrap(env, argv[1]); - if (other != nullptr && other->data != nullptr) { - reference->data = other->data; - reference->ownsData = false; - } else if (other != nullptr) { - size_t size = reference->type != nullptr && reference->type->type != nullptr && - reference->type->type->size > 0 - ? reference->type->type->size - : sizeof(void*); - reference->data = calloc(1, size); - reference->ownsData = true; - bool shouldFree = false; - napi_value initValue = Reference::getInitValue(env, argv[1], other); - if (initValue != nullptr) { - reference->type->toNative(env, initValue, reference->data, &shouldFree, &shouldFree); - } - } - } else { - size_t size = reference->type != nullptr && reference->type->type != nullptr && - reference->type->type->size > 0 - ? reference->type->type->size - : sizeof(void*); - reference->data = malloc(size); - reference->ownsData = true; - bool shouldFree; - reference->type->toNative(env, argv[1], reference->data, &shouldFree, &shouldFree); - } - } else { - napi_throw_error(env, nullptr, "Invalid number of arguments"); - return nullptr; - } - - napi_wrap(env, jsThis, reference, Reference::finalize, nullptr, nullptr); - - return createReferenceProxy(env, jsThis, reference); -} - -napi_value Reference::get_value(napi_env env, napi_callback_info info) { - napi_value jsThis; - napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); - - Reference* ref = Reference::unwrap(env, jsThis); - - if (ref == nullptr) { - napi_value undefined; - napi_get_undefined(env, &undefined); - return undefined; - } - - if (ref->data == nullptr) { - napi_value initValue = Reference::getInitValue(env, jsThis, ref); - if (initValue != nullptr) { - return initValue; - } - - napi_value undefined; - napi_get_undefined(env, &undefined); - return undefined; - } - - return ref->type->toJS(env, ref->data); -} - -napi_value Reference::set_value(napi_env env, napi_callback_info info) { - napi_value jsThis; - size_t argc = 1; - napi_value arg; - napi_get_cb_info(env, info, &argc, &arg, &jsThis, nullptr); - - Reference* ref = Reference::unwrap(env, jsThis); - - if (ref == nullptr) { - return nullptr; - } - - if (ref->data == nullptr) { - if (ref->type == nullptr) { - Reference::setInitValue(env, jsThis, ref, arg); - return nullptr; - } - - size_t size = ref->type->type != nullptr && ref->type->type->size > 0 ? ref->type->type->size - : sizeof(void*); - ref->data = calloc(1, size); - ref->ownsData = true; - } - - bool shouldFree = false; - ref->type->toNative(env, arg, ref->data, &shouldFree, &shouldFree); - Reference::clearInitValue(env, jsThis, ref); - - return nullptr; -} - -napi_value Reference::customInspect(napi_env env, napi_callback_info info) { - napi_value jsThis; - napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); - - Reference* ref = Reference::unwrap(env, jsThis); - - napi_value result; - if (ref == nullptr) { - napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &result); - return result; - } - std::string str = "data) + ">"; - napi_create_string_utf8(env, str.c_str(), NAPI_AUTO_LENGTH, &result); - - return result; -} - -void Reference::finalize(napi_env env, void* data, void* hint) { - if (PostFinalizer(env, finalizeReferenceNow, data, hint)) { - return; - } - - finalizeReferenceNow(env, data, hint); -} - -Reference::~Reference() { - if (initValue != nullptr && env != nullptr && - (bridgeState == nullptr || IsBridgeStateLive(bridgeState, bridgeStateToken))) { - DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, initValue); - initValue = nullptr; - } - bridgeState = nullptr; - bridgeStateToken = 0; - if (data != nullptr && ownsData) { - free(data); - data = nullptr; - } -} - -napi_value FunctionReference::defineJSClass(napi_env env) { - napi_value constructor; - napi_define_class(env, "FunctionReference", NAPI_AUTO_LENGTH, FunctionReference::constructor, - nullptr, 0, nullptr, &constructor); - - napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); - napi_value sizeValue; - napi_create_int32(env, sizeof(void*), &sizeValue); - napi_set_property(env, constructor, symbolSizeof, sizeValue); - - napi_value prototype; - napi_get_named_property(env, constructor, "prototype", &prototype); - napi_set_property(env, prototype, symbolSizeof, sizeValue); - - return constructor; -} - -bool FunctionReference::isInstance(napi_env env, napi_value value) { - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType != napi_object && valueType != napi_function) { - return false; - } - - bool hasMarker = false; - napi_has_named_property(env, value, kFunctionReferenceMarker, &hasMarker); - if (!hasMarker) { - return false; - } - - napi_value marker; - napi_get_named_property(env, value, kFunctionReferenceMarker, &marker); - bool markerValue = false; - napi_get_value_bool(env, marker, &markerValue); - return markerValue; -} - -FunctionReference* FunctionReference::unwrap(napi_env env, napi_value value) { - FunctionReference* ref = nullptr; - if (napi_unwrap(env, value, (void**)&ref) == napi_ok && ref != nullptr) { - return ref; - } - - bool hasNativeRef = false; - napi_has_named_property(env, value, kFunctionReferenceDataProperty, &hasNativeRef); - if (!hasNativeRef) { - return nullptr; - } - - napi_value nativeRefValue; - if (napi_get_named_property(env, value, kFunctionReferenceDataProperty, &nativeRefValue) != - napi_ok) { - return nullptr; - } - - void* nativeRef = nullptr; - if (napi_get_value_external(env, nativeRefValue, &nativeRef) != napi_ok) { - return nullptr; - } - - return static_cast(nativeRef); -} - -void FunctionReference::finalize(napi_env env, void* data, void* hint) { - if (PostFinalizer(env, finalizeFunctionReferenceNow, data, hint)) { - return; - } - - finalizeFunctionReferenceNow(env, data, hint); -} - -FunctionReference::FunctionReference(napi_env env, napi_ref ref) : env(env), ref(ref) { - bridgeState = ObjCBridgeState::InstanceData(env); - bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; -} - -napi_value FunctionReference::constructor(napi_env env, napi_callback_info info) { - napi_value jsThis; - size_t argc = 1; - napi_value argv[1]; - napi_value newTarget = nullptr; - napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr); - napi_get_new_target(env, info, &newTarget); - - napi_value arg; - if (argc == 0) { - napi_throw_type_error(env, nullptr, "FunctionReference constructor expects a function"); - return nullptr; - } - arg = argv[0]; - - napi_valuetype argType = napi_undefined; - napi_typeof(env, arg, &argType); - if (argType != napi_function) { - napi_throw_type_error(env, nullptr, "FunctionReference constructor expects a function"); - return nullptr; - } - - if (FunctionReference::isInstance(env, arg)) { - return arg; - } - - FunctionReference* reference = new FunctionReference(env, make_ref(env, arg)); - napi_value nativeRef; - napi_create_external(env, reference, nullptr, nullptr, &nativeRef); - napi_property_descriptor nativeRefProp = { - .utf8name = kFunctionReferenceDataProperty, - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = nativeRef, - .attributes = napi_configurable, - .data = nullptr, - }; - napi_define_properties(env, arg, 1, &nativeRefProp); - napi_ref finalizerRef = nullptr; - napi_add_finalizer(env, arg, reference, FunctionReference::finalize, nullptr, &finalizerRef); - - napi_value marker; - napi_get_boolean(env, true, &marker); - napi_property_descriptor markerProp = { - .utf8name = kFunctionReferenceMarker, - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = marker, - .attributes = napi_configurable, - .data = nullptr, - }; - napi_define_properties(env, arg, 1, &markerProp); - - // Keep function prototype chain untouched to avoid side effects on JS function - // semantics; expose sizeof directly on the function instance instead. - napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); - napi_value sizeValue; - napi_create_int32(env, sizeof(void*), &sizeValue); - napi_set_property(env, arg, symbolSizeof, sizeValue); - - (void)newTarget; - return arg; -} - -FunctionReference::~FunctionReference() { - // If closure is already created, it shares the same JS function ref. - // Clear it there and delete exactly once here. - if (closure != nullptr && closure->func == ref) { - closure->func = nullptr; - } - if (ref != nullptr && env != nullptr && - (bridgeState == nullptr || IsBridgeStateLive(bridgeState, bridgeStateToken))) { - DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref); - ref = nullptr; - } - bridgeState = nullptr; - bridgeStateToken = 0; -} - -void* FunctionReference::getFunctionPointer(MDSectionOffset offset, bool isBlock) { - if (closure == nullptr) { - closure = std::make_shared(env, ObjCBridgeState::InstanceData(env)->metadata, offset, - isBlock); - closure->func = ref; - } - - return closure->fnptr; -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/ObjCBridge.mm b/NativeScript/ffi/napi/ObjCBridge.mm deleted file mode 100644 index 0c7ebd76a..000000000 --- a/NativeScript/ffi/napi/ObjCBridge.mm +++ /dev/null @@ -1,1142 +0,0 @@ -#include "ObjCBridge.h" -#include "AutoreleasePool.h" -#include "Block.h" -#include "Class.h" -#include "ClassMember.h" -#include "Enum.h" -#include "InlineFunctions.h" -#include "Interop.h" -#include "Metadata.h" -#include "MetadataReader.h" -#include "NativeScript.h" -#include "Object.h" -#include "ObjectRef.h" -#include "Struct.h" -#include "TypeConv.h" -#include "Util.h" -#include "Variable.h" -#include "js_native_api.h" -#include "js_native_api_types.h" -#include "node_api_util.h" - -#import -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef EMBED_METADATA_SIZE -const unsigned char __attribute__((section("__objc_metadata,__objc_metadata"))) -#if defined(__aarch64__) -embedded_metadata[EMBED_METADATA_SIZE] = "NSMDSectionHeaderARM"; -#else -embedded_metadata[EMBED_METADATA_SIZE] = "NSMDSectionHeaderX86"; -#endif -#endif - -namespace nativescript { -namespace { -std::mutex gLiveBridgeStatesMutex; -std::unordered_map gLiveBridgeStates; -std::atomic gNextBridgeStateToken{1}; -constexpr const char* kNativePointerProperty = "__ns_native_ptr"; - -inline void deleteReferenceNow(napi_env env, napi_ref ref, bool unrefFirst) { - if (env == nullptr || ref == nullptr) { - return; - } - - if (unrefFirst) { - uint32_t remaining = 0; - napi_reference_unref(env, ref, &remaining); - } - - napi_delete_reference(env, ref); -} - -inline void deleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, - uint64_t bridgeStateToken, napi_ref ref, - bool unrefFirst) { - if (env == nullptr || ref == nullptr) { - return; - } - - if (bridgeState == nullptr) { - deleteReferenceNow(env, ref, unrefFirst); - return; - } - - if (!IsBridgeStateLive(bridgeState, bridgeStateToken)) { - return; - } - - if (bridgeState->jsThreadId == std::this_thread::get_id()) { -#if !defined(TARGET_ENGINE_QUICKJS) - deleteReferenceNow(env, ref, unrefFirst); - return; -#endif - } - - CFRunLoopRef runLoop = bridgeState->jsRunLoop; - if (runLoop == nullptr) { - runLoop = CFRunLoopGetMain(); - } - - if (runLoop == nullptr) { - if (bridgeState->jsThreadId == std::this_thread::get_id()) { - deleteReferenceNow(env, ref, unrefFirst); - } - return; - } - - CFRetain(runLoop); - CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ - if (IsBridgeStateLive(bridgeState, bridgeStateToken)) { - deleteReferenceNow(env, ref, unrefFirst); - } - CFRelease(runLoop); - }); - CFRunLoopWakeUp(runLoop); -} - -uint64_t RegisterBridgeState(const ObjCBridgeState* bridgeState) { - if (bridgeState == nullptr) { - return 0; - } - - uint64_t token = gNextBridgeStateToken.fetch_add(1, std::memory_order_relaxed); - std::lock_guard lock(gLiveBridgeStatesMutex); - gLiveBridgeStates[bridgeState] = token; - return token; -} - -void UnregisterBridgeState(const ObjCBridgeState* bridgeState) { - if (bridgeState == nullptr) { - return; - } - - std::lock_guard lock(gLiveBridgeStatesMutex); - gLiveBridgeStates.erase(bridgeState); -} -} // namespace - -bool IsBridgeStateLive(const ObjCBridgeState* bridgeState, uint64_t token) noexcept { - if (bridgeState == nullptr || token == 0) { - return false; - } - - std::lock_guard lock(gLiveBridgeStatesMutex); - auto find = gLiveBridgeStates.find(bridgeState); - return find != gLiveBridgeStates.end() && find->second == token; -} - -void DeleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, - uint64_t bridgeStateToken, napi_ref ref) { - deleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref, false); -} - -void ReleaseAndDeleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, - uint64_t bridgeStateToken, napi_ref ref) { - deleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref, true); -} - -bool PostFinalizer(napi_env env, napi_finalize finalize_cb, void* finalize_data, - void* finalize_hint) { - if (env == nullptr || finalize_cb == nullptr) { - return false; - } - - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && bridgeState->jsThreadId == std::this_thread::get_id()) { -#if !defined(TARGET_ENGINE_QUICKJS) - finalize_cb(env, finalize_data, finalize_hint); - return true; -#endif - } - - CFRunLoopRef runLoop = bridgeState != nullptr ? bridgeState->jsRunLoop : CFRunLoopGetMain(); - if (runLoop == nullptr) { - return false; - } - - if (bridgeState == nullptr && [NSThread isMainThread]) { -#if !defined(TARGET_ENGINE_QUICKJS) - finalize_cb(env, finalize_data, finalize_hint); - return true; -#endif - } - - CFRetain(runLoop); - CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ - finalize_cb(env, finalize_data, finalize_hint); - CFRelease(runLoop); - }); - CFRunLoopWakeUp(runLoop); - return true; -} - -void finalize_bridge_data(napi_env env, void* data, void* hint) { - auto bridgeState = (ObjCBridgeState*)data; - delete bridgeState; -} - -MDMetadataReader* loadMetadataFromFile(const char* metadata_path) { - if (metadata_path == nullptr) { - metadata_path = "metadata.nsmd"; - } - - auto f = fopen(metadata_path == nullptr ? "metadata.nsmd" : metadata_path, "r"); - if (f == nullptr) { - fprintf(stderr, "metadata.nsmd not found\n"); - exit(1); - } - fseek(f, 0, SEEK_END); - auto size = ftell(f); - fseek(f, 0, SEEK_SET); - auto buffer = (uint8_t*)malloc(size); - fread(buffer, 1, size, f); - fclose(f); - return new MDMetadataReader(buffer); -} - -inline bool hasNamedProperty(napi_env env, napi_value object, const char* name) { - bool hasProperty = false; - napi_has_named_property(env, object, name, &hasProperty); - return hasProperty; -} - -inline bool isFunctionValue(napi_env env, napi_value value) { - if (value == nullptr) { - return false; - } - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok) { - return false; - } - if (valueType == napi_function) { - return true; - } - - if (valueType != napi_object) { - return false; - } - - napi_value instance = nullptr; - napi_status status = napi_new_instance(env, value, 0, nullptr, &instance); - if (status == napi_ok) { - return true; - } - - bool hasPendingException = false; - if (napi_is_exception_pending(env, &hasPendingException) == napi_ok && hasPendingException) { - napi_value exception = nullptr; - napi_get_and_clear_last_exception(env, &exception); - } - return false; -} - -inline void clearPendingException(napi_env env) { - bool hasPendingException = false; - if (napi_is_exception_pending(env, &hasPendingException) == napi_ok && hasPendingException) { - napi_value exception = nullptr; - napi_get_and_clear_last_exception(env, &exception); - } -} - -inline bool isConstructableValue(napi_env env, napi_value value) { - napi_value instance = nullptr; - napi_status status = napi_new_instance(env, value, 0, nullptr, &instance); - if (status == napi_ok) { - return true; - } - - clearPendingException(env); - return false; -} - -inline bool hasConstructableNamedProperty(napi_env env, napi_value global, const char* name) { - if (!hasNamedProperty(env, global, name)) { - return false; - } - - napi_value value = nullptr; - if (napi_get_named_property(env, global, name, &value) != napi_ok || value == nullptr) { - clearPendingException(env); - return false; - } - - return isConstructableValue(env, value); -} - -inline void defineGlobalValue(napi_env env, napi_value global, const char* name, napi_value value) { - if (name == nullptr || value == nullptr) { - return; - } - - if (napi_set_named_property(env, global, name, value) == napi_ok) { - return; - } - clearPendingException(env); - - napi_property_descriptor prop = { - .utf8name = name, - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = value, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = nullptr, - }; - if (napi_define_properties(env, global, 1, &prop) != napi_ok) { - clearPendingException(env); - } -} - -inline bool defineConstructableGlobalValue(napi_env env, napi_value global, const char* name, - napi_value value) { - if (!isConstructableValue(env, value)) { - return false; - } - - if (napi_set_named_property(env, global, name, value) == napi_ok && - hasConstructableNamedProperty(env, global, name)) { - return true; - } - clearPendingException(env); - - napi_property_descriptor prop = { - .utf8name = name, - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = value, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = nullptr, - }; - if (napi_define_properties(env, global, 1, &prop) == napi_ok && - hasConstructableNamedProperty(env, global, name)) { - return true; - } - clearPendingException(env); - - napi_value key = nullptr; - napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &key); - if (key != nullptr) { - bool deleted = false; - if (napi_delete_property(env, global, key, &deleted) == napi_ok && deleted) { - if (napi_define_properties(env, global, 1, &prop) == napi_ok && - hasConstructableNamedProperty(env, global, name)) { - return true; - } - clearPendingException(env); - if (napi_set_named_property(env, global, name, value) == napi_ok && - hasConstructableNamedProperty(env, global, name)) { - return true; - } - clearPendingException(env); - } else { - clearPendingException(env); - } - } - - return hasConstructableNamedProperty(env, global, name); -} - -inline std::string buildStructEncoding(StructInfo* info) { - if (info == nullptr || info->name == nullptr) { - return ""; - } - - std::string encoding = "{"; - encoding += info->name; - encoding += "="; - for (const auto& field : info->fields) { - if (field.type == nullptr) { - return ""; - } - field.type->encode(&encoding); - } - encoding += "}"; - return encoding; -} - -inline void setTypeEncodingSymbol(napi_env env, napi_value value, const std::string& encoding) { - if (value == nullptr || encoding.empty()) { - return; - } - - napi_value typeSymbol = jsSymbolFor(env, "type"); - napi_value encodedValue = nullptr; - napi_create_string_utf8(env, encoding.c_str(), NAPI_AUTO_LENGTH, &encodedValue); - if (typeSymbol != nullptr && encodedValue != nullptr) { - napi_set_property(env, value, typeSymbol, encodedValue); - } -} - -inline void registerStructAlias(napi_env env, napi_value global, ObjCBridgeState* bridgeState, - const char* aliasName, - std::initializer_list candidates) { - if (bridgeState == nullptr || aliasName == nullptr) { - return; - } - - if (hasNamedProperty(env, global, aliasName)) { - napi_value existing = nullptr; - if (napi_get_named_property(env, global, aliasName, &existing) == napi_ok && - isFunctionValue(env, existing)) { - return; - } - } - - for (const char* candidate : candidates) { - if (candidate == nullptr || candidate[0] == '\0') { - continue; - } - - auto structIt = bridgeState->structOffsets.find(candidate); - if (structIt != bridgeState->structOffsets.end()) { - StructInfo* info = bridgeState->getStructInfo(env, structIt->second); - if (info != nullptr) { - napi_value cls = StructObject::getJSClass(env, info); - if (isFunctionValue(env, cls)) { - setTypeEncodingSymbol(env, cls, buildStructEncoding(info)); - defineGlobalValue(env, global, aliasName, cls); - return; - } - } - } - - if (hasNamedProperty(env, global, candidate)) { - napi_value source = nullptr; - if (napi_get_named_property(env, global, candidate, &source) == napi_ok && - isFunctionValue(env, source)) { - defineGlobalValue(env, global, aliasName, source); - return; - } - } - } -} - -inline void ensureSyntheticCGPoint(napi_env env, napi_value global) { - if (hasConstructableNamedProperty(env, global, "CGPoint")) { - return; - } - - static StructInfo* syntheticInfo = nullptr; - if (syntheticInfo == nullptr) { - syntheticInfo = new StructInfo(); - syntheticInfo->name = strdup("CGPoint"); - syntheticInfo->size = sizeof(double) * 2; - syntheticInfo->jsClass = nullptr; - - const char* doubleEncodingX = "d"; - const char* doubleEncodingY = "d"; - - StructFieldInfo fieldX; - fieldX.name = strdup("x"); - fieldX.offset = 0; - fieldX.type = TypeConv::Make(env, &doubleEncodingX); - syntheticInfo->fields.push_back(fieldX); - - StructFieldInfo fieldY; - fieldY.name = strdup("y"); - fieldY.offset = sizeof(double); - fieldY.type = TypeConv::Make(env, &doubleEncodingY); - syntheticInfo->fields.push_back(fieldY); - } - - napi_value cls = StructObject::getJSClass(env, syntheticInfo); - if (!isFunctionValue(env, cls)) { - return; - } - - setTypeEncodingSymbol(env, cls, "{CGPoint=dd}"); - defineConstructableGlobalValue(env, global, "CGPoint", cls); -} - -inline void ensureConstructableStructAlias(napi_env env, napi_value global, - ObjCBridgeState* bridgeState, const char* aliasName, - std::initializer_list candidates) { - if (bridgeState == nullptr || aliasName == nullptr) { - return; - } - - if (hasConstructableNamedProperty(env, global, aliasName)) { - return; - } - - for (const char* candidate : candidates) { - if (candidate == nullptr || candidate[0] == '\0') { - continue; - } - - if (hasNamedProperty(env, global, candidate)) { - napi_value value = nullptr; - if (napi_get_named_property(env, global, candidate, &value) == napi_ok && - defineConstructableGlobalValue(env, global, aliasName, value)) { - return; - } - clearPendingException(env); - } - - auto structIt = bridgeState->structOffsets.find(candidate); - if (structIt != bridgeState->structOffsets.end()) { - StructInfo* info = bridgeState->getStructInfo(env, structIt->second); - if (info != nullptr) { - napi_value cls = StructObject::getJSClass(env, info); - if (defineConstructableGlobalValue(env, global, aliasName, cls)) { - setTypeEncodingSymbol(env, cls, buildStructEncoding(info)); - return; - } - } - } - } -} - -inline void installMacUIColorCompatShim(napi_env env) { - const char* script = R"( - (function (globalObject) { - if (typeof globalObject.UIColor === "undefined" && - typeof globalObject.NSColor !== "undefined") { - globalObject.UIColor = globalObject.NSColor; - } - - const colorCtor = globalObject.UIColor || globalObject.NSColor; - if (!colorCtor || !colorCtor.prototype) { - return; - } - - if (typeof colorCtor.prototype.initWithRedGreenBlueAlpha === "function") { - return; - } - - colorCtor.prototype.initWithRedGreenBlueAlpha = function (red, green, blue, alpha) { - if (typeof this.initWithSRGBRedGreenBlueAlpha === "function") { - return this.initWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof this.initWithCalibratedRedGreenBlueAlpha === "function") { - return this.initWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof colorCtor.colorWithSRGBRedGreenBlueAlpha === "function") { - return colorCtor.colorWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof colorCtor.colorWithCalibratedRedGreenBlueAlpha === "function") { - return colorCtor.colorWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); - } - return this; - }; - })(globalThis); - )"; - - napi_value shim = nullptr; - napi_create_string_utf8(env, script, NAPI_AUTO_LENGTH, &shim); - if (shim != nullptr) { - napi_value result = nullptr; - napi_run_script(env, shim, &result); - } -} - -inline void* resolveSymbolPointer(ObjCBridgeState* bridgeState, const char* symbolName) { - if (bridgeState == nullptr || symbolName == nullptr || symbolName[0] == '\0') { - return nullptr; - } - - void* symbol = dlsym(bridgeState->self_dl, symbolName); - if (symbol == nullptr) { - symbol = dlsym(RTLD_DEFAULT, symbolName); - } - if (symbol == nullptr) { - std::string underscored = "_"; - underscored += symbolName; - symbol = dlsym(bridgeState->self_dl, underscored.c_str()); - if (symbol == nullptr) { - symbol = dlsym(RTLD_DEFAULT, underscored.c_str()); - } - } - - return symbol; -} - -inline bool unwrapCompatNativeHandle(napi_env env, napi_value value, void** out) { - if (value == nullptr || out == nullptr) { - return false; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *out = ptr != nullptr ? ptr->data : nullptr; - return ptr != nullptr; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *out = ref != nullptr ? ref->data : nullptr; - return ref != nullptr; - } - - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok) { - return false; - } - - if (valueType == napi_bigint) { - uint64_t raw = 0; - bool lossless = false; - if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) { - return false; - } - *out = reinterpret_cast(static_cast(raw)); - return true; - } - - if (valueType == napi_external) { - return napi_get_value_external(env, value, out) == napi_ok; - } - - if (valueType != napi_object && valueType != napi_function) { - return false; - } - - bool hasNativePointer = false; - if (napi_has_named_property(env, value, "__ns_native_ptr", &hasNativePointer) == napi_ok && - hasNativePointer) { - napi_value nativePointerValue = nullptr; - if (napi_get_named_property(env, value, "__ns_native_ptr", &nativePointerValue) == napi_ok && - napi_get_value_external(env, nativePointerValue, out) == napi_ok && *out != nullptr) { - return true; - } - } - - return napi_unwrap(env, value, out) == napi_ok && *out != nullptr; -} - -inline napi_value createCompatDispatchQueueWrapper(napi_env env, dispatch_queue_t queue) { - if (queue == nullptr) { - napi_value nullValue = nullptr; - napi_get_null(env, &nullValue); - return nullValue; - } - - return Pointer::create(env, reinterpret_cast(queue)); -} - -inline napi_value compat_dispatch_get_global_queue(napi_env env, napi_callback_info info) { - size_t argc = 2; - napi_value argv[2] = {nullptr, nullptr}; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - int64_t identifier = 0; - if (argc > 0) { - napi_valuetype identifierType = napi_undefined; - if (napi_typeof(env, argv[0], &identifierType) == napi_ok && identifierType == napi_bigint) { - bool lossless = false; - if (napi_get_value_bigint_int64(env, argv[0], &identifier, &lossless) != napi_ok) { - napi_throw_type_error(env, nullptr, - "dispatch_get_global_queue expects a numeric identifier."); - return nullptr; - } - } else { - napi_value coercedIdentifier = nullptr; - if (napi_coerce_to_number(env, argv[0], &coercedIdentifier) != napi_ok || - napi_get_value_int64(env, coercedIdentifier, &identifier) != napi_ok) { - napi_throw_type_error(env, nullptr, - "dispatch_get_global_queue expects a numeric identifier."); - return nullptr; - } - } - } - - uint64_t flags = 0; - if (argc > 1) { - napi_valuetype flagsType = napi_undefined; - if (napi_typeof(env, argv[1], &flagsType) == napi_ok && flagsType == napi_bigint) { - bool lossless = false; - if (napi_get_value_bigint_uint64(env, argv[1], &flags, &lossless) != napi_ok) { - napi_throw_type_error(env, nullptr, "dispatch_get_global_queue expects numeric flags."); - return nullptr; - } - } else { - napi_value coercedFlags = nullptr; - int64_t signedFlags = 0; - if (napi_coerce_to_number(env, argv[1], &coercedFlags) != napi_ok || - napi_get_value_int64(env, coercedFlags, &signedFlags) != napi_ok) { - napi_throw_type_error(env, nullptr, "dispatch_get_global_queue expects numeric flags."); - return nullptr; - } - flags = static_cast(signedFlags); - } - } - - return createCompatDispatchQueueWrapper(env, dispatch_get_global_queue(identifier, flags)); -} - -inline napi_value compat_dispatch_get_current_queue(napi_env env, napi_callback_info info) { - (void)info; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return createCompatDispatchQueueWrapper(env, dispatch_get_current_queue()); -#pragma clang diagnostic pop -} - -inline napi_value compat_dispatch_async(napi_env env, napi_callback_info info) { - size_t argc = 2; - napi_value argv[2] = {nullptr, nullptr}; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - if (argc < 2) { - napi_throw_type_error(env, nullptr, "dispatch_async expects a queue and callback."); - return nullptr; - } - - void* queueHandle = nullptr; - if (!unwrapCompatNativeHandle(env, argv[0], &queueHandle) || queueHandle == nullptr) { - napi_throw_type_error(env, nullptr, "dispatch_async expects a native queue handle."); - return nullptr; - } - - napi_valuetype callbackType = napi_undefined; - if (napi_typeof(env, argv[1], &callbackType) != napi_ok || callbackType != napi_function) { - napi_throw_type_error(env, nullptr, "dispatch_async expects a function callback."); - return nullptr; - } - - auto closure = new Closure(env, std::string("v"), true); - id block = registerBlock(env, closure, argv[1]); - dispatch_block_t dispatchBlock = (dispatch_block_t)block; - - dispatch_async(reinterpret_cast(queueHandle), dispatchBlock); - [block release]; - - napi_value undefinedValue = nullptr; - napi_get_undefined(env, &undefinedValue); - return undefinedValue; -} - -inline void registerCompatFunctionIfMissing(napi_env env, napi_value global, - ObjCBridgeState* bridgeState, const char* functionName, - const char* encoding) { - if (hasNamedProperty(env, global, functionName)) { - return; - } - - void* fn = resolveSymbolPointer(bridgeState, functionName); - if (fn == nullptr && strcmp(functionName, "CC_SHA256") == 0) { - void* commonCrypto = dlopen("/usr/lib/system/libcommonCrypto.dylib", RTLD_NOW | RTLD_LOCAL); - if (commonCrypto != nullptr) { - fn = dlsym(commonCrypto, functionName); - if (fn == nullptr) { - fn = dlsym(commonCrypto, "_CC_SHA256"); - } - } - } - - if (fn == nullptr) { - return; - } - - napi_value wrapper = FunctionPointer::wrapWithEncoding(env, fn, encoding, false); - if (wrapper != nullptr) { - napi_set_named_property(env, global, functionName, wrapper); - } -} - -inline void registerCompatFunction(napi_env env, napi_value global, const char* functionName, - napi_callback callback) { - napi_value wrapper = nullptr; - napi_create_function(env, functionName, NAPI_AUTO_LENGTH, callback, nullptr, &wrapper); - if (wrapper != nullptr) { - napi_value key = nullptr; - napi_create_string_utf8(env, functionName, NAPI_AUTO_LENGTH, &key); - if (key != nullptr) { - bool deleted = false; - napi_delete_property(env, global, key, &deleted); - clearPendingException(env); - } - defineGlobalValue(env, global, functionName, wrapper); - } -} - -void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeState* bridgeState) { -#if TARGET_OS_OSX - registerStructAlias(env, global, bridgeState, "CGPoint", - {"CGPoint", "_CGPoint", "NSPoint", "_NSPoint"}); - registerStructAlias(env, global, bridgeState, "CGSize", - {"CGSize", "_CGSize", "NSSize", "_NSSize"}); - registerStructAlias(env, global, bridgeState, "CGRect", - {"CGRect", "_CGRect", "NSRect", "_NSRect"}); - ensureSyntheticCGPoint(env, global); - ensureConstructableStructAlias( - env, global, bridgeState, "CGPoint", - {"CGPointStruct", "NSPoint", "NSPointStruct", "_CGPoint", "_NSPoint", "CGPoint"}); - installMacUIColorCompatShim(env); -#endif - - // CommonCrypto compatibility used by historical runtime tests and apps. - registerCompatFunctionIfMissing(env, global, bridgeState, "CC_SHA256", "^C^vQ^C"); - registerCompatFunctionIfMissing(env, global, bridgeState, "CGColorGetComponents", "^d^v"); - - // Force known-good libdispatch globals on macOS. The metadata path can resolve these with an - // incompatible call shape, which crashes when tests dispatch timers from a background queue. - registerCompatFunction(env, global, "dispatch_async", compat_dispatch_async); - registerCompatFunction(env, global, "dispatch_get_current_queue", - compat_dispatch_get_current_queue); - registerCompatFunction(env, global, "dispatch_get_global_queue", - compat_dispatch_get_global_queue); -} - -ObjCBridgeState::ObjCBridgeState(napi_env env, const char* metadata_path, - const void* metadata_ptr) { - this->env = env; - napi_set_instance_data(env, this, finalize_bridge_data, nil); - lifetimeToken = RegisterBridgeState(this); - trackedObjectLiveness = [[NSMutableSet alloc] init]; - - self_dl = dlopen(nullptr, RTLD_NOW); - - if (metadata_ptr && *((const char*)metadata_ptr) != '\0') { -#ifdef EMBED_METADATA_SIZE - // NSLog(@"Ignoring metadata pointer due to embedded metadata"); - metadata = new MDMetadataReader((void*)embedded_metadata); -#else - // NSLog(@"Using metadata from pointer: %p", metadata_ptr); - metadata = new MDMetadataReader((void*)metadata_ptr); -#endif - } else { -#ifdef EMBED_METADATA_SIZE - if (metadata_path != nullptr) { - // NSLog(@"Loading metadata from file: %s", metadata_path); - metadata = loadMetadataFromFile(metadata_path); - } else { - // NSLog(@"Using embedded metadata"); - metadata = new MDMetadataReader((void*)embedded_metadata); - } -#else - unsigned long segmentSize = 0; - auto segmentData = getsegmentdata((const mach_header_64*)_dyld_get_image_header(0), - "__objc_metadata", &segmentSize); - if (segmentData != nullptr) { - metadata = new MDMetadataReader(segmentData); - } else { - metadata = loadMetadataFromFile(metadata_path); - } -#endif - } - - // objc_autoreleasePool = objc_autoreleasePoolPush(); -} - -ObjCBridgeState::~ObjCBridgeState() { - UnregisterBridgeState(this); - - auto deleteRef = [&](napi_ref& ref) { - if (env != nullptr && ref != nullptr) { - napi_delete_reference(env, ref); - ref = nullptr; - } - }; - - for (auto& pair : constructorsByPointer) { - deleteRef(pair.second); - } - constructorsByPointer.clear(); - - for (auto& frame : roundTripCacheFrames) { - for (auto& entry : frame) { - ObjCBridgeState::releaseRoundTripEntry(env, entry.second); - } - } - roundTripCacheFrames.clear(); - - for (auto& entry : recentRoundTripCache) { - ObjCBridgeState::releaseRoundTripEntry(env, entry.second); - } - recentRoundTripCache.clear(); - - for (auto& entry : handleObjectRefs) { - if (entry.second.ownsRef) { - deleteRef(entry.second.ref); - } - } - handleObjectRefs.clear(); - - for (auto& entry : recentObjectWrappers) { - deleteRef(entry.ref); - } - recentObjectWrappers.clear(); - - std::unordered_set classAndProtocolConstructorRefs; - classAndProtocolConstructorRefs.reserve(classes.size() + protocols.size()); - for (const auto& pair : classes) { - if (pair.second != nullptr && pair.second->constructor != nullptr) { - classAndProtocolConstructorRefs.insert(pair.second->constructor); - } - } - for (const auto& pair : protocols) { - if (pair.second != nullptr && pair.second->constructor != nullptr) { - classAndProtocolConstructorRefs.insert(pair.second->constructor); - } - } - for (auto& pair : mdValueCache) { - napi_ref& ref = pair.second; - if (ref != nullptr && - classAndProtocolConstructorRefs.find(ref) == classAndProtocolConstructorRefs.end()) { - deleteRef(ref); - } - } - mdValueCache.clear(); - - deleteRef(pointerClass); - deleteRef(referenceClass); - deleteRef(functionReferenceClass); - deleteRef(createNativeProxy); - deleteRef(createFastEnumeratorIterator); - deleteRef(transferOwnershipToNative); - - // Clean up cached Cif objects - for (auto& pair : cifs) { - delete pair.second; - } - cifs.clear(); - - for (auto& pair : mdMethodSignatureCache) { - delete pair.second; - } - mdMethodSignatureCache.clear(); - - for (auto& pair : mdBlockSignatureCache) { - delete pair.second; - } - mdBlockSignatureCache.clear(); - - // Clean up ObjCClass objects - for (auto& pair : classes) { - delete pair.second; - } - classes.clear(); - - // Clean up ObjCProtocol objects - for (auto& pair : protocols) { - delete pair.second; - } - protocols.clear(); - - // Clean up StructInfo objects - for (auto& pair : structInfoCache) { - delete pair.second; - } - structInfoCache.clear(); - - // Clean up CFunction objects - for (auto& pair : cFunctionCache) { - delete pair.second; - } - cFunctionCache.clear(); - - for (auto& pair : mdFunctionSignatureCache) { - delete pair.second; - } - mdFunctionSignatureCache.clear(); - - NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); - trackedObjectLiveness = nullptr; - [trackedObjectTable release]; - - // if (objc_autoreleasePool != nullptr) - // objc_autoreleasePoolPop(objc_autoreleasePool); - - delete metadata; - dlclose(self_dl); -} - -napi_value ObjCBridgeState::proxyNativeObject(napi_env env, napi_value object, id nativeObject) { - NAPI_PREAMBLE - - napi_value result = object; - const bool nativeIsArray = [nativeObject isKindOfClass:NSArray.class]; - bool shouldProxyArray = nativeIsArray; - if (shouldProxyArray) { - napi_value factory = get_ref_value(env, createNativeProxy); - napi_value transferOwnershipFunc = get_ref_value(env, this->transferOwnershipToNative); - napi_value global; - napi_value args[3] = {object, nullptr, transferOwnershipFunc}; - napi_get_boolean(env, true, &args[1]); - napi_get_global(env, &global); - napi_call_function(env, global, factory, 3, args, &result); - } - - napi_value nativePointer = Pointer::create(env, nativeObject); - if (nativePointer != nullptr) { - napi_set_named_property(env, result, kNativePointerProperty, nativePointer); - } - napi_wrap(env, result, nativeObject, nullptr, nullptr, nullptr); - - napi_ref ref = nullptr; - auto* finalizerContext = new JSObjectFinalizerContext{ - .bridgeState = this, - .bridgeStateToken = lifetimeToken, - .object = nativeObject, - .ref = nullptr, - }; - NAPI_GUARD( - napi_add_finalizer(env, result, finalizerContext, finalize_objc_object, nullptr, &ref)) { - delete finalizerContext; - NAPI_THROW_LAST_ERROR - return nullptr; - } - finalizerContext->ref = ref; - - storeObjectRef(nativeObject, ref); - cacheHandleObjectRef(env, nativeObject, ref); - cacheRecentObjectWrapper(env, nativeObject, result); - attachObjectLifecycleAssociation(env, nativeObject); - trackObject(nativeObject); - - return result; -} - -void ObjCBridgeState::trackObject(id object) noexcept { - if (object == nil) { - return; - } - - NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); - if (trackedObjectTable == nil) { - return; - } - - NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; - std::lock_guard lock(objectRefsMutex); - [trackedObjectTable addObject:objectKey]; -} - -bool ObjCBridgeState::isTrackedObjectAlive(id object) const noexcept { - if (object == nil) { - return false; - } - - NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); - if (trackedObjectTable == nil) { - return false; - } - - NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; - std::lock_guard lock(objectRefsMutex); - return [trackedObjectTable containsObject:objectKey]; -} - -} // namespace nativescript - -using namespace nativescript; - -NAPI_FUNCTION(getArrayBuffer) { - NAPI_CALLBACK_BEGIN(2) - - void* ptr = Pointer::unwrap(env, argv[0])->data; - int64_t length; - napi_get_value_int64(env, argv[1], &length); - - napi_value arrayBuffer; - if (length < 0) { - napi_throw_error(env, nullptr, "Invalid ArrayBuffer length"); - return nullptr; - } - - napi_create_external_arraybuffer(env, ptr, static_cast(length), nullptr, nullptr, - &arrayBuffer); - - return arrayBuffer; -} - -NAPI_FUNCTION(init) { - NAPI_CALLBACK_BEGIN(1) - napi_valuetype type; - napi_typeof(env, argv[0], &type); - const char* metadata_path = nullptr; - if (type == napi_string) { - size_t len; - napi_get_value_string_utf8(env, argv[0], nullptr, 0, &len); - metadata_path = (char*)malloc(len + 1); - napi_get_value_string_utf8(env, argv[0], (char*)metadata_path, len + 1, &len); - } - nativescript_init(env, metadata_path, nullptr); - return nullptr; -} - -NAPI_EXPORT NAPI_MODULE_REGISTER { - const napi_property_descriptor property = NAPI_FUNCTION_DESC(init); - napi_define_properties(env, exports, 1, &property); - return exports; -} - -NAPI_EXPORT void nativescript_init(void* _env, const char* metadata_path, - const void* metadata_ptr) { - napi_env env = (napi_env)_env; - - ObjCBridgeState* bridgeState = new ObjCBridgeState(env, metadata_path, metadata_ptr); - - napi_value objc; - napi_create_object(env, &objc); - - const napi_property_descriptor objcProperties[] = { - NAPI_FUNCTION_DESC(registerClass), NAPI_FUNCTION_DESC(registerBlock), - NAPI_FUNCTION_DESC(import), NAPI_FUNCTION_DESC(autoreleasepool), - NAPI_FUNCTION_DESC(getArrayBuffer), - }; - - napi_define_properties(env, objc, 5, objcProperties); - - napi_value global; - napi_get_global(env, &global); - - const napi_property_descriptor globalProperties[] = {{ - .utf8name = "objc", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = objc, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "ObjectRef", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = defineObjectRefClass(env), - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "NativeClass", - .method = JS_registerClass, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }}; - - napi_define_properties(env, global, 3, globalProperties); - - setupObjCClassDecorator(env); - - initProxyFactory(env, bridgeState); - initFastEnumeratorIteratorFactory(env, bridgeState); - - registerInterop(env, global); - registerInlineFunctions(env); - - bridgeState->registerVarGlobals(env, global); - bridgeState->registerEnumGlobals(env, global); - bridgeState->registerStructGlobals(env, global); - bridgeState->registerUnionGlobals(env, global); - bridgeState->registerFunctionGlobals(env, global); - bridgeState->registerClassGlobals(env, global); - bridgeState->registerProtocolGlobals(env, global); - registerLegacyCompatGlobals(env, global, bridgeState); -} diff --git a/NativeScript/ffi/napi/Object.mm b/NativeScript/ffi/napi/Object.mm deleted file mode 100644 index 5283280ed..000000000 --- a/NativeScript/ffi/napi/Object.mm +++ /dev/null @@ -1,826 +0,0 @@ -#include "Object.h" -#include -#include "Interop.h" -#include "JSObject.h" -#include "ObjCBridge.h" -#include "js_native_api.h" -#include "node_api_util.h" - -#import -#include - -static SEL JSWrapperObjectAssociationKey = @selector(JSWrapperObjectAssociationKey); -static SEL ObjCLifecycleAssociationKey = @selector(ObjCLifecycleAssociationKey); - -@interface JSWrapperObjectAssociation : NSObject - -@property(nonatomic) napi_env env; -@property(nonatomic) napi_ref ref; -@property(nonatomic) nativescript::ObjCBridgeState* bridgeState; -@property(nonatomic) uint64_t bridgeStateToken; - -+ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object; - -+ (instancetype)associationFor:(id)object; - -- (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref; - -@end - -@interface ObjCLifecycleAssociation : NSObject { - nativescript::ObjCBridgeState* _bridgeState; - uint64_t _bridgeStateToken; - uintptr_t _objectAddress; -} - -- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState object:(id)object; - -@end - -@implementation JSWrapperObjectAssociation - -- (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref { - self = [super init]; - if (self) { - self.env = env; - self.ref = ref; - self.bridgeState = nativescript::ObjCBridgeState::InstanceData(env); - self.bridgeStateToken = self.bridgeState != nullptr ? self.bridgeState->lifetimeToken : 0; - } - return self; -} - -+ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object { - napi_ref ref = nativescript::make_ref(env, value); - JSWrapperObjectAssociation* association = [[JSWrapperObjectAssociation alloc] initWithEnv:env - ref:ref]; - objc_setAssociatedObject(object, JSWrapperObjectAssociationKey, association, - OBJC_ASSOCIATION_RETAIN_NONATOMIC); - [association release]; -} - -+ (instancetype)associationFor:(id)object { - return objc_getAssociatedObject(object, JSWrapperObjectAssociationKey); -} - -- (void)dealloc { - if (self.env != nullptr && self.ref != nullptr && - (self.bridgeState == nullptr || - nativescript::IsBridgeStateLive(self.bridgeState, self.bridgeStateToken))) { - nativescript::DeleteReferenceOnOwningThread(self.env, self.bridgeState, - self.bridgeStateToken, self.ref); - self.ref = nullptr; - } - self.bridgeState = nullptr; - self.bridgeStateToken = 0; - self.env = nullptr; - [super dealloc]; -} - -@end - -@implementation ObjCLifecycleAssociation - -- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState object:(id)object { - self = [super init]; - if (self) { - _bridgeState = bridgeState; - _bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; - _objectAddress = (uintptr_t)object; - } - - return self; -} - -- (void)dealloc { - nativescript::ObjCBridgeState* bridgeState = _bridgeState; - uint64_t bridgeStateToken = _bridgeStateToken; - uintptr_t objectAddress = _objectAddress; - if (nativescript::IsBridgeStateLive(bridgeState, bridgeStateToken)) { - bridgeState->detachObject((id)objectAddress); - } - - [super dealloc]; -} - -@end - -napi_value JS_transferOwnershipToNative(napi_env env, napi_callback_info cbinfo) { - size_t argc = 1; - napi_value arg = nullptr; - if (napi_get_cb_info(env, cbinfo, &argc, &arg, nullptr, nullptr) != napi_ok || argc == 0 || - arg == nullptr) { - return nullptr; - } - - id obj = nil; - if (napi_unwrap(env, arg, (void**)&obj) != napi_ok || obj == nil) { - return nullptr; - } - - [JSWrapperObjectAssociation transferOwnership:env of:arg toNative:obj]; - - return nullptr; -} - -namespace nativescript { - -napi_value findConstructorForObject(napi_env env, ObjCBridgeState* bridgeState, id object, - Class cls = nil); - -void transferOwnershipToNative(napi_env env, napi_value value, id object) { - if (env == nullptr || value == nullptr || object == nil) { - return; - } - - [JSWrapperObjectAssociation transferOwnership:env of:value toNative:object]; -} - -namespace { -constexpr const char* kNativePointerProperty = "__ns_native_ptr"; - -napi_value findConstructorForClassObject(napi_env env, ObjCBridgeState* bridgeState, Class cls, - napi_value fallback = nullptr) { - if (bridgeState == nullptr || cls == nil) { - return fallback; - } - - auto bridgedClass = bridgeState->classesByPointer.find(cls); - if (bridgedClass != bridgeState->classesByPointer.end() && bridgedClass->second != nullptr) { - return get_ref_value(env, bridgedClass->second->constructor); - } - - auto constructedClass = bridgeState->constructorsByPointer.find(cls); - if (constructedClass != bridgeState->constructorsByPointer.end()) { - napi_value constructor = get_ref_value(env, constructedClass->second); - if (constructor != nullptr) { - return constructor; - } - } - - auto metadataClass = bridgeState->mdClassesByPointer.find(cls); - if (metadataClass != bridgeState->mdClassesByPointer.end()) { - auto bridgedMetadataClass = bridgeState->getClass(env, metadataClass->second); - if (bridgedMetadataClass != nullptr) { - return get_ref_value(env, bridgedMetadataClass->constructor); - } - } - - const char* runtimeName = class_getName(cls); - if (runtimeName != nullptr && runtimeName[0] != '\0') { - napi_value global = nullptr; - napi_value constructor = nullptr; - bool hasGlobal = false; - napi_get_global(env, &global); - if (napi_has_named_property(env, global, runtimeName, &hasGlobal) == napi_ok && hasGlobal && - napi_get_named_property(env, global, runtimeName, &constructor) == napi_ok && - constructor != nullptr) { - return constructor; - } - } - - napi_value resolved = findConstructorForObject(env, bridgeState, (id)cls, cls); - return resolved != nullptr ? resolved : fallback; -} -} // namespace - -const char* nativeObjectProxySource = R"( - (function (object, isArray, transferOwnershipToNative) { - let isTransfered = false; - const boundMethods = Object.create(null); - let objectAtIndexMethod; - let addObjectMethod; - let removeObjectAtIndexMethod; - let setObjectAtIndexedSubscriptMethod; - - function bindTargetMethod(target, name) { - const cached = boundMethods[name]; - if (cached !== undefined) { - return cached; - } - - const value = target[name]; - if (typeof value !== "function") { - return value; - } - - if (value.__ns_proxy_bound === true) { - boundMethods[name] = value; - return value; - } - - const wrapper = value.bind(target); - Object.defineProperty(wrapper, "__ns_proxy_bound", { value: true }); - boundMethods[name] = wrapper; - try { - Object.defineProperty(target, name, { - value: wrapper, - configurable: true, - writable: true - }); - } catch (_) { - } - return wrapper; - } - - return new Proxy(object, { - get (target, name, receiver) { - if (name === "superclass" && typeof target.class === "function") { - return target.class().superclass(); - } - - if (isArray && name === "count") { - return target.count; - } - - if (isArray) { - switch (name) { - case "objectAtIndex": - return objectAtIndexMethod !== undefined - ? objectAtIndexMethod - : (objectAtIndexMethod = bindTargetMethod(target, name)); - case "addObject": - return addObjectMethod !== undefined - ? addObjectMethod - : (addObjectMethod = bindTargetMethod(target, name)); - case "removeObjectAtIndex": - return removeObjectAtIndexMethod !== undefined - ? removeObjectAtIndexMethod - : (removeObjectAtIndexMethod = bindTargetMethod(target, name)); - case "setObjectAtIndexedSubscript": - return setObjectAtIndexedSubscriptMethod !== undefined - ? setObjectAtIndexedSubscriptMethod - : (setObjectAtIndexedSubscriptMethod = bindTargetMethod(target, name)); - } - } - - if (typeof name === "string") { - const boundMethod = boundMethods[name]; - if (boundMethod !== undefined) { - return boundMethod; - } - } - - const value = target[name]; - if (value !== undefined || name in target) { - if (typeof value === "function" && name !== "constructor") { - if (value.__ns_proxy_bound === true) { - return value; - } - - let wrapper; - if ((name === "isKindOfClass" || name === "isMemberOfClass")) { - wrapper = function (cls, a1, a2, a3) { - let resolvedClass = cls; - const resolvedClassType = typeof resolvedClass; - if (resolvedClass != null && - (resolvedClassType === "object" || resolvedClassType === "function")) { - try { - const runtimeName = typeof NSStringFromClass === "function" - ? NSStringFromClass(resolvedClass) - : null; - if (typeof runtimeName === "string" && runtimeName.length > 0) { - const registry = globalThis.__nsConstructorsByObjCClassName; - if (registry && registry[runtimeName]) { - resolvedClass = registry[runtimeName]; - } else if (typeof globalThis[runtimeName] !== "undefined") { - resolvedClass = globalThis[runtimeName]; - } - } - } catch (_) { - } - } - - switch (arguments.length) { - case 0: - case 1: - return value.call(target, resolvedClass); - case 2: - return value.call(target, resolvedClass, a1); - case 3: - return value.call(target, resolvedClass, a1, a2); - case 4: - return value.call(target, resolvedClass, a1, a2, a3); - default: { - const args = Array.prototype.slice.call(arguments); - args[0] = resolvedClass; - return Reflect.apply(value, target, args); - } - } - }; - } else { - wrapper = value.bind(target); - } - - Object.defineProperty(wrapper, "__ns_proxy_bound", { value: true }); - if (typeof name === "string") { - boundMethods[name] = wrapper; - } - try { - Object.defineProperty(target, name, { - value: wrapper, - configurable: true, - writable: true - }); - } catch (_) { - } - return wrapper; - } - return value; - } - - if (typeof name === 'symbol') { - return undefined; - } - - if (isArray) { - const index = Number(name); - if (!isNaN(index)) { - return target.objectAtIndex(index); - } - } - }, - - set (target, name, value) { - const isInternalProperty = typeof name === 'string' && - (name === 'napi_external' || name === 'napi_typetag' || name === '__ns_native_ptr'); - - if (typeof name === 'symbol') { - target[name] = value; - return true; - } - - if (typeof name === "string" && boundMethods[name] !== undefined) { - delete boundMethods[name]; - } - - if (isArray) { - switch (name) { - case "objectAtIndex": - objectAtIndexMethod = undefined; - break; - case "addObject": - addObjectMethod = undefined; - break; - case "removeObjectAtIndex": - removeObjectAtIndexMethod = undefined; - break; - case "setObjectAtIndexedSubscript": - setObjectAtIndexedSubscriptMethod = undefined; - break; - } - } - - if (isArray) { - const index = Number(name); - if (!isNaN(index)) { - target.setObjectAtIndexedSubscript(value, index); - return true; - } - } - - if (!isInternalProperty && !(name in target) && !isTransfered) { - isTransfered = true; - transferOwnershipToNative(target); - } - - target[name] = value; - - return true; - }, - }); - }) -)"; - -void initProxyFactory(napi_env env, ObjCBridgeState* state) { - napi_value script, result; - napi_create_string_utf8(env, nativeObjectProxySource, NAPI_AUTO_LENGTH, &script); - napi_run_script(env, script, &result); - state->createNativeProxy = make_ref(env, result); - - napi_value transferOwnershipToNative; - napi_create_function(env, "transferOwnershipToNative", NAPI_AUTO_LENGTH, - JS_transferOwnershipToNative, nullptr, &transferOwnershipToNative); - state->transferOwnershipToNative = make_ref(env, transferOwnershipToNative); -} - -void attachObjectLifecycleAssociation(napi_env env, id object) { - if (object == nil) { - return; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState == nullptr) { - return; - } - - if (objc_getAssociatedObject(object, ObjCLifecycleAssociationKey) != nil) { - return; - } - - ObjCLifecycleAssociation* association = - [[ObjCLifecycleAssociation alloc] initWithBridgeState:bridgeState object:object]; - objc_setAssociatedObject(object, ObjCLifecycleAssociationKey, association, - OBJC_ASSOCIATION_RETAIN_NONATOMIC); - [association release]; -} - -namespace { -void finalize_objc_object_now(napi_env /*env*/, void* data, void* hint) { - (void)hint; - JSObjectFinalizerContext* context = static_cast(data); - if (context == nullptr) { - return; - } - - ObjCBridgeState* bridgeState = context->bridgeState; - if (IsBridgeStateLive(bridgeState, context->bridgeStateToken)) { - bridgeState->unregisterObjectIfRefMatches(context->object, context->ref); - } - - delete context; -} -} // namespace - -void finalize_objc_object(napi_env env, void* data, void* hint) { - if (PostFinalizer(env, finalize_objc_object_now, data, hint)) { - return; - } - - finalize_objc_object_now(env, data, hint); -} - -napi_value ObjCBridgeState::getObject(napi_env env, id obj, napi_value constructor, - ObjectOwnership ownership) { - if (obj == nil) { - return nullptr; - } - - NAPI_PREAMBLE - - Class cls = object_getClass(obj); - - if (cls == nullptr) { - return nullptr; - } - - if (class_isMetaClass(cls)) { - return findConstructorForClassObject(env, this, (Class)obj, constructor); - } - - napi_value resolvedConstructor = constructor; - if (resolvedConstructor != nullptr) { - Class hintedClass = nil; - tryResolveBridgedClassConstructor(env, resolvedConstructor, &hintedClass); - } - - napi_value actualConstructor = findConstructorForObject(env, this, obj, cls); - if (actualConstructor != nullptr) { - resolvedConstructor = actualConstructor; - } - - if (napi_value cached = findCachedObjectWrapper(env, obj); cached != nullptr) { - return cached; - } - - napi_value result = nil; - napi_value prototype; - NAPI_GUARD(napi_get_named_property(env, resolvedConstructor, "prototype", &prototype)) { - NAPI_THROW_LAST_ERROR - return nullptr; - } - - NAPI_GUARD(napi_create_object(env, &result)) { - NAPI_THROW_LAST_ERROR - return nullptr; - } - - napi_value global; - napi_value objectCtor; - napi_value setPrototypeOf; - napi_value argv[2] = {result, prototype}; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &objectCtor); - napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); - - NAPI_GUARD(napi_call_function(env, objectCtor, setPrototypeOf, 2, argv, nullptr)) { - NAPI_THROW_LAST_ERROR - return nullptr; - } - - napi_wrap(env, result, obj, nullptr, nullptr, nullptr); - napi_value nativePointer = Pointer::create(env, obj); - if (nativePointer != nullptr) { - napi_set_named_property(env, result, kNativePointerProperty, nativePointer); - } - - if (ownership == kUnownedObject) { - [obj retain]; - } - - result = proxyNativeObject(env, result, obj); - - // #if DEBUG - // napi_value global, Error, error, stack; - // napi_get_global(env, &global); - // napi_get_named_property(env, global, "Error", &Error); - // napi_new_instance(env, Error, 0, nullptr, &error); - // napi_get_named_property(env, error, "stack", &stack); - - // size_t stackSize; - // napi_get_value_string_utf8(env, stack, nullptr, 0, &stackSize); - // char *stackStr = new char[stackSize + 1]; - // napi_get_value_string_utf8(env, stack, stackStr, stackSize + 1, nullptr); - - // NSString *str = [NSString stringWithFormat:@"Wrapped object <%s: %p> @ %ld # %s", - // class_getName(cls), obj, [obj retainCount], stackStr]; - // dbglog([str UTF8String]); - - // delete[] stackStr; - // #endif - - return result; -} - -napi_value ObjCBridgeState::findCachedObjectWrapper(napi_env env, id obj) { - if (obj == nil) { - return nullptr; - } - - if (napi_value jsObject = idToJsObject(env, obj); jsObject != nullptr) { - return jsObject; - } - - auto roundTrip = getRoundTripObject(env, obj); - if (roundTrip != nullptr) { - return roundTrip; - } - - if (napi_value recentWrapper = getRecentObjectWrapper(env, obj); recentWrapper != nullptr) { - return recentWrapper; - } - - if (napi_value handleCached = getCachedHandleObject(env, (void*)obj); handleCached != nullptr) { - bool isRawHandleRoundTrip = ownsCachedHandleObjectRef((void*)obj); - void* wrapped = nullptr; - if (napi_unwrap(env, handleCached, &wrapped) == napi_ok && - NormalizeHandleKey(wrapped) == NormalizeHandleKey((void*)obj)) { - return handleCached; - } - - bool hasNativePointer = false; - if (napi_has_named_property(env, handleCached, kNativePointerProperty, &hasNativePointer) == - napi_ok && - hasNativePointer) { - napi_value nativePointerValue = nullptr; - if (napi_get_named_property(env, handleCached, kNativePointerProperty, &nativePointerValue) == - napi_ok && - Pointer::isInstance(env, nativePointerValue)) { - Pointer* pointer = Pointer::unwrap(env, nativePointerValue); - if (pointer != nullptr && - NormalizeHandleKey(pointer->data) == NormalizeHandleKey((void*)obj)) { - return handleCached; - } - } - } - - if (isRawHandleRoundTrip) { - return handleCached; - } - - removeCachedHandleObject(env, (void*)obj); - } - - if (napi_value existing = getNormalizedObjectRef(env, obj); existing != nullptr) { - return existing; - } - - JSWrapperObjectAssociation* association = [JSWrapperObjectAssociation associationFor:obj]; - if (association != nil) { - napi_value jsObject = get_ref_value(env, association.ref); - if (jsObject != nullptr) { - bool isArrayBuffer = false; - bool isTypedArray = false; - bool isDataView = false; - if ((napi_is_arraybuffer(env, jsObject, &isArrayBuffer) == napi_ok && isArrayBuffer) || - (napi_is_typedarray(env, jsObject, &isTypedArray) == napi_ok && isTypedArray) || - (napi_is_dataview(env, jsObject, &isDataView) == napi_ok && isDataView)) { - return jsObject; - } - - [obj retain]; - return proxyNativeObject(env, jsObject, obj); - } - } - - return nullptr; -} - -napi_value findConstructorForObject(napi_env env, ObjCBridgeState* bridgeState, id object, - Class cls) { - if (cls == nil) { - cls = object_getClass(object); - } - - // Look up if there is a custom class for it already - { - auto find = bridgeState->classesByPointer.find(cls); - if (find != bridgeState->classesByPointer.end()) { - return get_ref_value(env, find->second->constructor); - } - } - - // Look up if there is a custom constructor for it already - { - auto find = bridgeState->constructorsByPointer.find(cls); - if (find != bridgeState->constructorsByPointer.end()) { - return get_ref_value(env, find->second); - } - } - - // Look up if there is a metadata-defined class - { - auto find = bridgeState->mdClassesByPointer.find(cls); - if (find != bridgeState->mdClassesByPointer.end()) { - auto cls = bridgeState->getClass(env, find->second); - return get_ref_value(env, cls->constructor); - } - } - - Class superclass = class_getSuperclass(cls); - if (superclass != nullptr) { - napi_value superclassConstructor = - findConstructorForObject(env, bridgeState, object, superclass); - if (superclassConstructor != nullptr) { - return superclassConstructor; - } - } - - // Look up the protocols implemented by this class if no class-based - // constructor could be resolved. For private runtime subclasses we prefer - // inheriting the public superclass surface over exposing a protocol-only - // shell that drops concrete class members. - { - unsigned int count; - auto protocols = class_copyProtocolList(cls, &count); - std::unordered_set impls; - - std::function processProtocolList = [&](Protocol** list, - unsigned int count) { - for (unsigned int i = 0; i < count; i++) { - auto protocol = list[i]; - auto find = bridgeState->mdProtocolsByPointer.find(protocol); - if (find != bridgeState->mdProtocolsByPointer.end()) { - impls.insert(bridgeState->getProtocol(env, find->second)); - } - list = protocol_copyProtocolList(protocol, &count); - processProtocolList(list, count); - } - }; - - processProtocolList(protocols, count); - - if (!impls.empty()) { - napi_value constructor; - napi_define_class(env, class_getName(cls), NAPI_AUTO_LENGTH, ObjCProtocol::jsConstructor, - nullptr, 0, nullptr, &constructor); - for (auto impl : impls) { - ObjCClassMember::defineMembers(env, impl->members, impl->membersOffset, constructor); - } - - bridgeState->constructorsByPointer[cls] = make_ref(env, constructor); - - return constructor; - } - } - - return nullptr; -} - -// Get a napi_value for an Objective-C object, creating it if it doesn't exist. -// Here we also ensure that the native object always points to the same -// JS object, this makes sure that we only ever finalize it once. -// Might want to consider using associated objects instead of a hashtable. -napi_value ObjCBridgeState::getObject(napi_env env, id obj, ObjectOwnership ownership, - MDSectionOffset classOffset, - std::vector* protocolOffsets) { - NAPI_PREAMBLE - - if (obj == nullptr) { - return nullptr; - } - - Class objectClass = object_getClass(obj); - if (objectClass != nil && class_isMetaClass(objectClass)) { - return findConstructorForClassObject(env, this, (Class)obj, nullptr); - } - - auto roundTrip = getRoundTripObject(env, obj); - if (roundTrip != nullptr) { - return roundTrip; - } - - if (napi_value existing = getNormalizedObjectRef(env, obj); existing != nullptr) { - return existing; - } - - auto findClass = classesByPointer.find(obj); - if (findClass != classesByPointer.end()) { - return get_ref_value(env, findClass->second->constructor); - } - - auto mdFindClassByPointer = mdClassesByPointer.find((Class)obj); - if (mdFindClassByPointer != mdClassesByPointer.end()) { - auto bridgedClass = getClass(env, mdFindClassByPointer->second); - return bridgedClass != nullptr ? get_ref_value(env, bridgedClass->constructor) : nullptr; - } - - auto cls = objectClass; - - auto mdFindByPointer = mdClassesByPointer.find(cls); - if (mdFindByPointer != mdClassesByPointer.end()) { - classOffset = mdFindByPointer->second; - } - - auto findByPointer = classesByPointer.find(cls); - if (findByPointer != classesByPointer.end()) { - return getObject(env, obj, get_ref_value(env, findByPointer->second->constructor), ownership); - } - - napi_value constructor = nullptr; - if (classOffset != 0) { - auto bridgedCls = getClass(env, classOffset); - - if (bridgedCls == nullptr) { - return nullptr; - } - - constructor = get_ref_value(env, bridgedCls->constructor); - } else if (protocolOffsets != nullptr && !protocolOffsets->empty()) { - auto proto = getProtocol(env, protocolOffsets->front()); - - if (proto == nullptr) { - return nullptr; - } - - constructor = get_ref_value(env, proto->constructor); - } else { - constructor = findConstructorForObject(env, this, obj, cls); - } - - if (constructor == nullptr) { - return nullptr; - } - - return getObject(env, obj, constructor, ownership); -} - -void ObjCBridgeState::unregisterObject(id object) noexcept { - // #if DEBUG - // NSString *string = [NSString stringWithFormat: @"Unregistering object <%s: %p> @ %ld # success: - // %d, finalized: %d", - // class_getName(object_getClass(object)), object, [object retainCount], - // (int)objectRefs.contains(object), (int)finalized]; - - // dbglog([string UTF8String]); - // #endif - - if (takeObjectRef(object) != nullptr) { - removeRecentObjectWrapper(env, object); - removeCachedHandleObject(env, (void*)object); - [object release]; - } -} - -bool ObjCBridgeState::unregisterObjectIfRefMatches(id object, napi_ref ref) noexcept { - if (takeObjectRef(object, ref) == nullptr) { - return false; - } - - removeRecentObjectWrapper(env, object); - removeCachedHandleObject(env, (void*)object); - [object release]; - return true; -} - -void ObjCBridgeState::detachObject(id object) noexcept { - takeObjectRef(object); - removeRoundTripObject(object); - removeRecentObjectWrapper(env, object); - removeCachedHandleObject(env, (void*)object); - - if (object == nil) { - return; - } - - NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); - if (trackedObjectTable == nil) { - return; - } - - NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; - std::lock_guard lock(objectRefsMutex); - [trackedObjectTable removeObject:objectKey]; -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/SignatureDispatch.h b/NativeScript/ffi/napi/SignatureDispatch.h deleted file mode 100644 index c42a82376..000000000 --- a/NativeScript/ffi/napi/SignatureDispatch.h +++ /dev/null @@ -1,223 +0,0 @@ -#ifndef NS_FFI_NAPI_SIGNATURE_DISPATCH_H -#define NS_FFI_NAPI_SIGNATURE_DISPATCH_H - -#include - -#include -#include -#include - -#include "Cif.h" -#include "js_native_api.h" - -namespace nativescript { - -enum class SignatureCallKind : uint8_t { - ObjCMethod = 1, - CFunction = 2, - BlockInvoke = 3, -}; - -using ObjCPreparedInvoker = void (*)(void* fnptr, void** avalues, void* rvalue); -using CFunctionPreparedInvoker = void (*)(void* fnptr, void** avalues, - void* rvalue); -using BlockPreparedInvoker = void (*)(void* fnptr, void** avalues, - void* rvalue); -using ObjCNapiInvoker = bool (*)(napi_env env, Cif* cif, void* fnptr, id self, - SEL selector, const napi_value* argv, - void* rvalue); -using CFunctionNapiInvoker = bool (*)(napi_env env, Cif* cif, void* fnptr, - const napi_value* argv, void* rvalue); - -struct ObjCDispatchEntry { - uint64_t dispatchId; - ObjCPreparedInvoker invoker; -}; - -struct CFunctionDispatchEntry { - uint64_t dispatchId; - CFunctionPreparedInvoker invoker; -}; - -struct BlockDispatchEntry { - uint64_t dispatchId; - BlockPreparedInvoker invoker; -}; - -struct ObjCNapiDispatchEntry { - uint64_t dispatchId; - ObjCNapiInvoker invoker; -}; - -struct CFunctionNapiDispatchEntry { - uint64_t dispatchId; - CFunctionNapiInvoker invoker; -}; - -inline constexpr uint64_t kSignatureHashOffsetBasis = 14695981039346656037ull; -inline constexpr uint64_t kSignatureHashPrime = 1099511628211ull; - -inline uint64_t hashBytesFnv1a(const void* data, size_t size, - uint64_t seed = kSignatureHashOffsetBasis) { - const auto* bytes = static_cast(data); - uint64_t hash = seed; - for (size_t i = 0; i < size; i++) { - hash ^= static_cast(bytes[i]); - hash *= kSignatureHashPrime; - } - return hash; -} - -inline uint64_t composeSignatureDispatchId(uint64_t signatureHash, - SignatureCallKind kind, - uint8_t flags) { - const uint8_t kindByte = static_cast(kind); - uint64_t hash = hashBytesFnv1a(&kindByte, sizeof(kindByte)); - hash = hashBytesFnv1a(&flags, sizeof(flags), hash); - return hashBytesFnv1a(&signatureHash, sizeof(signatureHash), hash); -} - -} // namespace nativescript - -#ifndef NS_GSD_BACKEND_NAPI -#define NS_GSD_BACKEND_NAPI 1 -#endif - -#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH -#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 -#endif - -#ifndef NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH -#define NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH 0 -#endif - -#ifndef NS_GSD_BACKEND_V8 -#define NS_GSD_BACKEND_V8 0 -#endif - -#ifndef NS_GSD_BACKEND_JSC -#define NS_GSD_BACKEND_JSC 0 -#endif - -#ifndef NS_GSD_BACKEND_QUICKJS -#define NS_GSD_BACKEND_QUICKJS 0 -#endif - -#ifndef NS_GSD_BACKEND_HERMES -#define NS_GSD_BACKEND_HERMES 0 -#endif - -#ifndef NS_GSD_BACKEND_ENGINE_DIRECT -#define NS_GSD_BACKEND_ENGINE_DIRECT 0 -#endif - -#if defined(__has_include) -#if __has_include("GeneratedSignatureDispatch.inc") -#include "GeneratedSignatureDispatch.inc" -#endif -#endif - -#if !NS_HAS_GENERATED_SIGNATURE_DISPATCH -namespace nativescript { -inline constexpr ObjCDispatchEntry kGeneratedObjCDispatchEntries[] = { - {0, nullptr}}; -inline constexpr CFunctionDispatchEntry kGeneratedCFunctionDispatchEntries[] = { - {0, nullptr}}; -inline constexpr BlockDispatchEntry kGeneratedBlockDispatchEntries[] = { - {0, nullptr}}; -} // namespace nativescript -#endif - -#if !NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH -namespace nativescript { -inline constexpr ObjCNapiDispatchEntry kGeneratedObjCNapiDispatchEntries[] = { - {0, nullptr}}; -inline constexpr CFunctionNapiDispatchEntry - kGeneratedCFunctionNapiDispatchEntries[] = {{0, nullptr}}; -} // namespace nativescript -#endif - -namespace nativescript { - -template -inline Invoker lookupDispatchInvoker(const Entry (&entries)[N], - uint64_t dispatchId) { - if (dispatchId == 0 || N <= 1) { - return nullptr; - } - - size_t low = 1; - size_t high = N; - while (low < high) { - const size_t mid = low + ((high - low) >> 1); - const uint64_t midId = entries[mid].dispatchId; - if (midId < dispatchId) { - low = mid + 1; - } else { - high = mid; - } - } - - if (low < N && entries[low].dispatchId == dispatchId) { - return entries[low].invoker; - } - return nullptr; -} - -inline bool isGeneratedDispatchEnabled() { - static const bool enabled = []() { - const char* disableFlag = std::getenv("NS_DISABLE_GSD"); - if (disableFlag == nullptr || disableFlag[0] == '\0') { - return true; - } - return !(disableFlag[0] == '0' && disableFlag[1] == '\0'); - }(); - return enabled; -} - -inline ObjCPreparedInvoker lookupObjCPreparedInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedObjCDispatchEntries, dispatchId); -} - -inline CFunctionPreparedInvoker lookupCFunctionPreparedInvoker( - uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedCFunctionDispatchEntries, dispatchId); -} - -inline BlockPreparedInvoker lookupBlockPreparedInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedBlockDispatchEntries, dispatchId); -} - -inline ObjCNapiInvoker lookupObjCNapiInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedObjCNapiDispatchEntries, dispatchId); -} - -inline CFunctionNapiInvoker lookupCFunctionNapiInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedCFunctionNapiDispatchEntries, dispatchId); -} - -} // namespace nativescript - -#endif // NS_FFI_NAPI_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/napi/Struct.mm b/NativeScript/ffi/napi/Struct.mm deleted file mode 100644 index 6d19ab857..000000000 --- a/NativeScript/ffi/napi/Struct.mm +++ /dev/null @@ -1,648 +0,0 @@ -#include "Struct.h" -#include -#include -#include -#include "Interop.h" -#include "ObjCBridge.h" -#include "TypeConv.h" -#include "Util.h" -#include "js_native_api.h" -#include "js_native_api_types.h" -#include "node_api_util.h" - -#import - -namespace nativescript { - -namespace { -std::string buildStructTypeEncoding(StructInfo* info) { - if (info == nullptr || info->name == nullptr) { - return ""; - } - - std::string encoding = "{"; - encoding += info->name; - encoding += "="; - for (const auto& field : info->fields) { - if (field.type == nullptr) { - return ""; - } - field.type->encode(&encoding); - } - encoding += "}"; - return encoding; -} -} // namespace - -void ObjCBridgeState::registerStructGlobals(napi_env env, napi_value global) { - MDSectionOffset offset = metadata->structsOffset; - while (offset < metadata->unionsOffset) { - // Sometimes there is padding after file ends. - if (metadata->getOffset(offset) == 0) break; - MDSectionOffset originalOffset = offset; - auto name = metadata->getString(offset); - offset += sizeof(MDSectionOffset); - auto size = metadata->getArraySize(offset); - offset += sizeof(uint16_t); - - std::string nameStr = name; - structOffsets[nameStr] = originalOffset; - - bool next = true; - while (next) { - MDSectionOffset nameOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - if (nameOffset == MD_SECTION_OFFSET_NULL) break; - auto name = metadata->resolveString(nameOffset); - offset += sizeof(uint16_t); - TypeConv::Make(env, metadata, &offset); - } - - bool hasPrimaryName = false; - napi_has_named_property(env, global, name, &hasPrimaryName); - if (!hasPrimaryName) { - napi_property_descriptor prop = { - .utf8name = name, - .method = nullptr, - .getter = JS_structGetter, - .setter = nullptr, - .value = nullptr, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = (void*)((size_t)originalOffset), - }; - napi_define_properties(env, global, 1, &prop); - } - - if (!nameStr.empty() && nameStr[0] == '_') { - std::string alias = nameStr.substr(1); - if (!alias.empty()) { - bool hasAlias = false; - napi_has_named_property(env, global, alias.c_str(), &hasAlias); - if (!hasAlias) { - napi_property_descriptor aliasProp = { - .utf8name = alias.c_str(), - .method = nullptr, - .getter = JS_structGetter, - .setter = nullptr, - .value = nullptr, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = (void*)((size_t)originalOffset), - }; - napi_define_properties(env, global, 1, &aliasProp); - } - } - } - - if (nameStr.size() < 6 || nameStr.compare(nameStr.size() - 6, 6, "Struct") != 0) { - std::string alias = nameStr + "Struct"; - bool hasAlias = false; - napi_has_named_property(env, global, alias.c_str(), &hasAlias); - if (!hasAlias) { - napi_property_descriptor aliasProp = { - .utf8name = alias.c_str(), - .method = nullptr, - .getter = JS_structGetter, - .setter = nullptr, - .value = nullptr, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = (void*)((size_t)originalOffset), - }; - napi_define_properties(env, global, 1, &aliasProp); - } - } - } -} - -void ObjCBridgeState::registerUnionGlobals(napi_env env, napi_value global) { - MDSectionOffset offset = metadata->unionsOffset; - while (true) { - // It's the last section, always ends with 4 bytes of 0. - if (metadata->getOffset(offset) == 0) break; - MDSectionOffset originalOffset = offset; - auto name = metadata->getString(offset); - offset += sizeof(MDSectionOffset); - auto size = metadata->getArraySize(offset); - offset += sizeof(uint16_t); - - std::string nameStr = name; - unionOffsets[nameStr] = originalOffset; - - bool next = true; - while (next) { - MDSectionOffset nameOffset = metadata->getOffset(offset); - next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - auto name = metadata->resolveString(nameOffset); - offset += sizeof(MDSectionOffset); - TypeConv::Make(env, metadata, &offset); - } - - napi_property_descriptor prop = { - .utf8name = name, - .method = nullptr, - .getter = JS_unionGetter, - .setter = nullptr, - .value = nullptr, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = (void*)((size_t)originalOffset), - }; - napi_define_properties(env, global, 1, &prop); - } -} - -NAPI_FUNCTION(structGetter) { - NAPI_PREAMBLE - - void* data; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); - MDSectionOffset offset = (MDSectionOffset)((size_t)data); - - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto structInfo = bridgeState->getStructInfo(env, offset); - return StructObject::getJSClass(env, structInfo); -} - -NAPI_FUNCTION(unionGetter) { - NAPI_PREAMBLE - - void* data; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); - MDSectionOffset offset = (MDSectionOffset)((size_t)data); - - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto structInfo = bridgeState->getUnionInfo(env, offset); - return StructObject::getJSClass(env, structInfo); -} - -StructInfo* getStructInfoFromMetadata(napi_env env, MDMetadataReader* metadata, - MDSectionOffset offset) { - auto originalOffset = offset; - - auto structInfo = new StructInfo(); - auto name = metadata->getString(offset); - structInfo->name = name; - offset += sizeof(MDSectionOffset); - structInfo->size = metadata->getArraySize(offset); - offset += sizeof(uint16_t); - - bool next = true; - while (next) { - MDSectionOffset nameOffset = metadata->getOffset(offset); - next = (nameOffset & mdSectionOffsetNext) != 0; - if (next) { - nameOffset &= ~mdSectionOffsetNext; - } - if (nameOffset == MD_SECTION_OFFSET_NULL) { - break; - } - auto fieldInfo = StructFieldInfo(); - fieldInfo.name = metadata->resolveString(nameOffset); - offset += sizeof(MDSectionOffset); - fieldInfo.offset = metadata->getArraySize(offset); - offset += sizeof(uint16_t); - fieldInfo.type = TypeConv::Make(env, metadata, &offset); - structInfo->fields.push_back(fieldInfo); - } - - return structInfo; -} - -StructInfo* getStructInfoFromUnionMetadata(napi_env env, MDMetadataReader* metadata, - MDSectionOffset offset) { - auto originalOffset = offset; - - auto structInfo = new StructInfo(); - auto name = metadata->getString(offset); - structInfo->name = name; - offset += sizeof(MDSectionOffset); - structInfo->size = metadata->getArraySize(offset); - offset += sizeof(uint16_t); - - bool next = true; - while (next) { - MDSectionOffset nameOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (nameOffset & mdSectionOffsetNext) != 0; - if (next) { - nameOffset &= ~mdSectionOffsetNext; - } - if (nameOffset == 0) { - break; - } - auto fieldInfo = StructFieldInfo(); - fieldInfo.name = metadata->resolveString(nameOffset); - fieldInfo.offset = 0; - fieldInfo.type = TypeConv::Make(env, metadata, &offset); - structInfo->fields.push_back(fieldInfo); - } - - return structInfo; -} - -namespace { -void StructObject_finalize_now(napi_env env, void* data, void* hint) { - auto structObject = (StructObject*)data; - delete structObject; -} -} // namespace - -void StructObject_finalize(napi_env env, void* data, void* hint) { - if (PostFinalizer(env, StructObject_finalize_now, data, hint)) { - return; - } - - StructObject_finalize_now(env, data, hint); -} - -NAPI_FUNCTION(StructConstructor) { - NAPI_PREAMBLE - - napi_value jsThis; - napi_value argv[1]; - StructInfo* info; - size_t argc = 1; - - napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, (void**)&info); - - napi_valuetype thisType = napi_undefined; - if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || - (thisType != napi_object && thisType != napi_function)) { - napi_create_object(env, &jsThis); - - if (info != nullptr) { - napi_value structCtor = StructObject::getJSClass(env, info); - napi_value structPrototype = nullptr; - if (structCtor != nullptr && - napi_get_named_property(env, structCtor, "prototype", &structPrototype) == napi_ok && - structPrototype != nullptr) { - napi_value global = nullptr; - napi_value objectCtor = nullptr; - napi_value setPrototypeOf = nullptr; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &objectCtor); - napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); - napi_value setPrototypeArgs[2] = {jsThis, structPrototype}; - napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); - } - } - } - - napi_value arg; - if (argc > 0) { - arg = argv[0]; - } else { - napi_get_undefined(env, &arg); - } - - napi_valuetype argType; - napi_typeof(env, arg, &argType); - - StructObject* object; - - if (argType == napi_object) { - if (Pointer::isInstance(env, arg)) { - Pointer* pointer = Pointer::unwrap(env, arg); - if (pointer == nullptr) { - napi_throw_error(env, nullptr, "Invalid pointer-backed struct argument"); - return nullptr; - } - object = new StructObject(info, pointer->data, env, arg); - } else if (Reference::isInstance(env, arg)) { - Reference* reference = Reference::unwrap(env, arg); - if (reference == nullptr || reference->data == nullptr) { - napi_throw_error(env, nullptr, "Reference is not initialized"); - return nullptr; - } - object = new StructObject(info, reference->data, env, arg); - } else { - object = new StructObject(env, info, arg); - } - } else { - object = new StructObject(info); - } - - napi_ref ref; - napi_wrap(env, jsThis, object, StructObject_finalize, nullptr, &ref); - - return jsThis; -} - -NAPI_FUNCTION(StructPropertyGetter) { - NAPI_PREAMBLE - - napi_value jsThis; - StructFieldInfo* info; - - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, (void**)&info); - - auto object = StructObject::unwrap(env, jsThis); - auto value = object->get(env, info); - - if (StructObject::isInstance(env, value)) { - napi_set_named_property(env, value, "__ns_parent_struct", jsThis); - } else { - bool isArray = false; - if (napi_is_array(env, value, &isArray) == napi_ok && isArray) { - napi_set_named_property(env, value, "__ns_parent_struct", jsThis); - } - } - - return value; -} - -NAPI_FUNCTION(StructPropertySetter) { - NAPI_PREAMBLE - - napi_value jsThis, arg; - StructFieldInfo* info; - size_t argc = 1; - - napi_get_cb_info(env, cbinfo, &argc, &arg, &jsThis, (void**)&info); - - auto object = StructObject::unwrap(env, jsThis); - object->set(env, info, arg); - - return nullptr; -} - -NAPI_FUNCTION(StructCustomInspect) { - napi_value jsThis; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, nullptr); - - auto object = StructObject::unwrap(env, jsThis); - std::string str = "struct "; - str += object->info->name; - str += " {}"; - - napi_value result; - napi_create_string_utf8(env, str.c_str(), str.length(), &result); - return result; -} - -NAPI_FUNCTION(StructEquals) { - napi_value jsThis, argv[2]; - size_t argc = 2; - void* data = nullptr; - napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, &data); - - StructInfo* info = static_cast(data); - if (info == nullptr || argc < 2) { - napi_value result; - napi_get_boolean(env, false, &result); - return result; - } - - auto serialize = [&](napi_value value, std::vector& out) -> bool { - out.assign(info->size, 0); - - StructObject* structObject = StructObject::unwrap(env, value); - if (structObject != nullptr) { - size_t copySize = - std::min(static_cast(info->size), static_cast(structObject->info->size)); - memcpy(out.data(), structObject->data, copySize); - return true; - } - - napi_valuetype type = napi_undefined; - napi_typeof(env, value, &type); - if (type != napi_object) { - return false; - } - - StructObject(env, info, value, out.data()); - bool pending = false; - napi_is_exception_pending(env, &pending); - return !pending; - }; - - std::vector left; - std::vector right; - bool okLeft = serialize(argv[0], left); - bool okRight = serialize(argv[1], right); - - napi_value result; - napi_get_boolean(env, okLeft && okRight && memcmp(left.data(), right.data(), info->size) == 0, - &result); - return result; -} - -inline StructObject::StructObject(StructInfo* info, void* data, napi_env env, - napi_value backingValue) { - this->info = info; - this->env = env; - this->bridgeState = env != nullptr ? ObjCBridgeState::InstanceData(env) : nullptr; - this->bridgeStateToken = this->bridgeState != nullptr ? this->bridgeState->lifetimeToken : 0; - if (data == nullptr) { - this->data = malloc(info->size); - memset(this->data, 0, this->info->size); - this->owned = true; - } else { - this->data = data; - this->owned = false; - if (env != nullptr && backingValue != nullptr) { - napi_create_reference(env, backingValue, 1, &this->backingRef); - } - } -} - -StructObject::StructObject(napi_env env, StructInfo* info, napi_value object, void* memory) { - this->info = info; - this->env = env; - this->bridgeState = env != nullptr ? ObjCBridgeState::InstanceData(env) : nullptr; - this->bridgeStateToken = this->bridgeState != nullptr ? this->bridgeState->lifetimeToken : 0; - - if (memory == nullptr) { - this->owned = true; - this->data = malloc(info->size); - } else { - this->owned = false; - this->data = memory; - } - - memset(this->data, 0, this->info->size); - - for (auto& field : info->fields) { - bool hasProp = false; - napi_has_named_property(env, object, field.name, &hasProp); - if (!hasProp) { - continue; - } - napi_value property; - napi_get_named_property(env, object, field.name, &property); - set(env, &field, property); - } -} - -StructObject::~StructObject() { - if (this->backingRef != nullptr && this->env != nullptr && - (this->bridgeState == nullptr || - IsBridgeStateLive(this->bridgeState, this->bridgeStateToken))) { - DeleteReferenceOnOwningThread(this->env, this->bridgeState, this->bridgeStateToken, - this->backingRef); - } - this->backingRef = nullptr; - this->bridgeState = nullptr; - this->bridgeStateToken = 0; - if (this->owned) free(this->data); -} - -napi_value StructObject::get(napi_env env, StructFieldInfo* field) { - auto data = (char*)this->data + field->offset; - return field->type->toJS(env, data, kStructZeroCopy); -} - -void StructObject::set(napi_env env, StructFieldInfo* field, napi_value value) { - auto data = (char*)this->data + field->offset; - bool shouldFree = false; - field->type->toNative(env, value, data, &shouldFree, &shouldFree); -} - -StructObject* StructObject::unwrap(napi_env env, napi_value object) { - StructObject* result; - auto status = napi_unwrap(env, object, (void**)&result); - if (status != napi_ok) return nullptr; - return result; -} - -napi_value StructObject::defineJSClass(napi_env env, StructInfo* info) { - auto properties = (napi_property_descriptor*)malloc((info->fields.size() + 2) * - sizeof(napi_property_descriptor)); - - for (int i = 0; i < info->fields.size(); i++) { - auto field = info->fields[i]; - auto prop = &properties[i]; - prop->utf8name = field.name; - prop->name = nullptr; - prop->method = nullptr; - prop->value = nullptr; - prop->attributes = (napi_property_attributes)(napi_enumerable | napi_writable); - prop->data = &info->fields[i]; - prop->getter = JS_StructPropertyGetter; - prop->setter = JS_StructPropertySetter; - } - - auto prop = &properties[info->fields.size()]; - prop->utf8name = nullptr; - prop->name = jsSymbolFor(env, "nodejs.util.inspect.custom"); - prop->method = JS_StructCustomInspect; - prop->getter = nullptr; - prop->setter = nullptr; - prop->value = nullptr; - prop->attributes = napi_default; - prop->data = nullptr; - - napi_value size; - napi_create_int32(env, info->size, &size); - - auto sizeofProp = &properties[info->fields.size() + 1]; - sizeofProp->utf8name = nullptr; - sizeofProp->name = jsSymbolFor(env, "sizeof"); - sizeofProp->method = nullptr; - sizeofProp->getter = nullptr; - sizeofProp->setter = nullptr; - sizeofProp->value = size; - sizeofProp->attributes = napi_enumerable; - sizeofProp->data = nullptr; - - napi_value result; - napi_define_class(env, info->name, NAPI_AUTO_LENGTH, JS_StructConstructor, (void*)info, - info->fields.size() + 2, properties, &result); - - const napi_property_descriptor classProps[] = { - { - .utf8name = "equals", - .method = JS_StructEquals, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_default, - .data = info, - }, - { - .utf8name = nullptr, - .name = jsSymbolFor(env, "sizeof"), - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = size, - .attributes = napi_enumerable, - .data = nullptr, - }, - }; - napi_define_properties(env, result, 2, classProps); - - std::string typeEncoding = buildStructTypeEncoding(info); - if (!typeEncoding.empty()) { - napi_value typeSymbol = jsSymbolFor(env, "type"); - napi_value typeValue = nullptr; - napi_create_string_utf8(env, typeEncoding.c_str(), NAPI_AUTO_LENGTH, &typeValue); - napi_set_property(env, result, typeSymbol, typeValue); - - napi_value prototype = nullptr; - if (napi_get_named_property(env, result, "prototype", &prototype) == napi_ok && - prototype != nullptr) { - napi_set_property(env, prototype, typeSymbol, typeValue); - } - } - - free(properties); - - return result; -} - -bool StructObject::isInstance(napi_env env, napi_value object) { - napi_valuetype valueType = napi_undefined; - napi_typeof(env, object, &valueType); - if (valueType != napi_object && valueType != napi_function) { - return false; - } - - napi_value sizeofSymbol = jsSymbolFor(env, "sizeof"); - bool hasProp = false; - if (napi_has_property(env, object, sizeofSymbol, &hasProp) != napi_ok) { - return false; - } - return hasProp; -} - -napi_value StructObject::getJSClass(napi_env env, StructInfo* info) { - if (info->jsClass != nullptr) { - return get_ref_value(env, info->jsClass); - } - - auto result = defineJSClass(env, info); - info->jsClass = make_ref(env, result); - - return result; -} - -napi_value StructObject::fromNative(napi_env env, StructInfo* info, void* data, bool owned) { - napi_value result; - napi_value cls = getJSClass(env, info); - napi_new_instance(env, cls, 0, nullptr, &result); - auto object = StructObject::unwrap(env, result); - if (object == nullptr) { - return result; - } - - if (owned) { - if (object->owned) { - memcpy(object->data, data, info->size); - } else { - object->data = malloc(info->size); - memcpy(object->data, data, info->size); - object->owned = true; - } - } else { - if (object->owned && object->data != nullptr) { - free(object->data); - } - object->data = data; - object->owned = false; - } - return result; -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/TypeConv.mm b/NativeScript/ffi/napi/TypeConv.mm deleted file mode 100644 index be22ba6c6..000000000 --- a/NativeScript/ffi/napi/TypeConv.mm +++ /dev/null @@ -1,4166 +0,0 @@ -#include "TypeConv.h" -#include "Block.h" -#include "Class.h" -#include "Closure.h" -#include "Interop.h" -#include "JSObject.h" -#include "Metadata.h" -#include "MetadataReader.h" -#include "ObjCBridge.h" -#include "ffi.h" -#include "Struct.h" -#include "js_native_api.h" -#include "js_native_api_types.h" -#include "node_api_util.h" - -#import -#import -#include -#if defined(__has_include) -#if __has_include() -#include -#endif -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -@interface JSWrapperObjectAssociation : NSObject -+ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object; -@end - -namespace { - -static napi_value findRegisteredClassConstructor(napi_env env, Class cls) { - if (env == nullptr || cls == nil) { - return nullptr; - } - - const char* runtimeName = class_getName(cls); - if (runtimeName == nullptr || runtimeName[0] == '\0') { - return nullptr; - } - - napi_value global = nullptr; - napi_value classRegistry = nullptr; - bool hasClassRegistry = false; - if (napi_get_global(env, &global) != napi_ok || global == nullptr || - napi_has_named_property(env, global, "__nsConstructorsByObjCClassName", - &hasClassRegistry) != napi_ok || - !hasClassRegistry || - napi_get_named_property(env, global, "__nsConstructorsByObjCClassName", - &classRegistry) != napi_ok || - classRegistry == nullptr) { - return nullptr; - } - - bool hasConstructor = false; - napi_value constructor = nullptr; - if (napi_has_named_property(env, classRegistry, runtimeName, &hasConstructor) == napi_ok && - hasConstructor && - napi_get_named_property(env, classRegistry, runtimeName, &constructor) == napi_ok && - constructor != nullptr) { - return constructor; - } - - return nullptr; -} - -static size_t getBufferElementSize(napi_typedarray_type type) { - switch (type) { - case napi_int8_array: - case napi_uint8_array: - case napi_uint8_clamped_array: - return 1; - case napi_int16_array: - case napi_uint16_array: - return 2; - case napi_int32_array: - case napi_uint32_array: - case napi_float32_array: - return 4; - case napi_float64_array: - case napi_bigint64_array: - case napi_biguint64_array: - return 8; - default: - return 1; - } -} - -static bool getJSBufferData(napi_env env, napi_value value, void** data, size_t* byteLength) { - if (data == nullptr || byteLength == nullptr) { - return false; - } - - bool isArrayBuffer = false; - if (napi_is_arraybuffer(env, value, &isArrayBuffer) == napi_ok && isArrayBuffer) { - return napi_get_arraybuffer_info(env, value, data, byteLength) == napi_ok; - } - - bool isTypedArray = false; - if (napi_is_typedarray(env, value, &isTypedArray) == napi_ok && isTypedArray) { - napi_typedarray_type type; - napi_value arrayBuffer; - size_t byteOffset = 0; - size_t elementLength = 0; - if (napi_get_typedarray_info(env, value, &type, &elementLength, data, &arrayBuffer, - &byteOffset) != napi_ok) { - return false; - } - - *byteLength = elementLength * getBufferElementSize(type); - return true; - } - - bool isDataView = false; - if (napi_is_dataview(env, value, &isDataView) == napi_ok && isDataView) { - napi_value arrayBuffer; - size_t byteOffset = 0; - return napi_get_dataview_info(env, value, byteLength, data, &arrayBuffer, &byteOffset) == - napi_ok; - } - - return false; -} - -static uint16_t encodeFloat16(double value) { - if (std::isnan(value)) { - return 0x7e00; - } - - if (std::isinf(value)) { - return std::signbit(value) ? 0xfc00 : 0x7c00; - } - - union { - float f; - uint32_t bits; - } input = {static_cast(value)}; - - const uint32_t sign = (input.bits >> 16) & 0x8000; - uint32_t exponent = (input.bits >> 23) & 0xff; - uint32_t mantissa = input.bits & 0x007fffff; - - if (exponent == 0) { - return static_cast(sign); - } - - int32_t halfExponent = static_cast(exponent) - 127 + 15; - if (halfExponent >= 0x1f) { - return static_cast(sign | 0x7c00); - } - - if (halfExponent <= 0) { - if (halfExponent < -10) { - return static_cast(sign); - } - - mantissa |= 0x00800000; - const uint32_t shift = static_cast(14 - halfExponent); - uint32_t halfMantissa = mantissa >> shift; - if (((mantissa >> (shift - 1)) & 1u) != 0) { - halfMantissa += 1; - } - return static_cast(sign | halfMantissa); - } - - uint32_t halfMantissa = mantissa >> 13; - if ((mantissa & 0x00001000) != 0) { - halfMantissa += 1; - if ((halfMantissa & 0x00000400) != 0) { - halfMantissa = 0; - halfExponent += 1; - if (halfExponent >= 0x1f) { - return static_cast(sign | 0x7c00); - } - } - } - - return static_cast(sign | (static_cast(halfExponent) << 10) | - (halfMantissa & 0x03ff)); -} - -static double decodeFloat16(uint16_t bits) { - const uint32_t sign = (bits & 0x8000u) << 16; - const uint32_t exponent = (bits >> 10) & 0x1fu; - const uint32_t mantissa = bits & 0x03ffu; - - union { - uint32_t bits; - float f; - } output = {0}; - - if (exponent == 0) { - if (mantissa == 0) { - output.bits = sign; - return static_cast(output.f); - } - - uint32_t normalizedMantissa = mantissa; - int32_t normalizedExponent = -14; - while ((normalizedMantissa & 0x0400u) == 0) { - normalizedMantissa <<= 1; - normalizedExponent -= 1; - } - normalizedMantissa &= 0x03ffu; - output.bits = - sign | (static_cast(normalizedExponent + 127) << 23) | (normalizedMantissa << 13); - return static_cast(output.f); - } - - if (exponent == 0x1fu) { - output.bits = sign | 0x7f800000u | (mantissa << 13); - return static_cast(output.f); - } - - output.bits = sign | ((exponent - 15 + 127) << 23) | (mantissa << 13); - return static_cast(output.f); -} - -static id resolveCachedHandleObject(napi_env env, void* handle) { - if (env == nullptr || handle == nullptr) { - return nil; - } - - auto bridgeState = nativescript::ObjCBridgeState::InstanceData(env); - if (bridgeState == nullptr) { - return nil; - } - - napi_value cachedValue = bridgeState->getCachedHandleObject(env, handle); - if (cachedValue == nullptr) { - return nil; - } - - void* wrapped = nullptr; - if (napi_unwrap(env, cachedValue, &wrapped) == napi_ok && wrapped != nullptr) { - bridgeState->cacheRoundTripObject(env, static_cast(wrapped), cachedValue); - return static_cast(wrapped); - } - - bool hasNativePointer = false; - if (napi_has_named_property(env, cachedValue, "__ns_native_ptr", &hasNativePointer) == napi_ok && - hasNativePointer) { - napi_value nativePointerValue = nullptr; - if (napi_get_named_property(env, cachedValue, "__ns_native_ptr", &nativePointerValue) == - napi_ok) { - if (nativescript::Pointer::isInstance(env, nativePointerValue)) { - nativescript::Pointer* pointer = nativescript::Pointer::unwrap(env, nativePointerValue); - if (pointer != nullptr && pointer->data != nullptr) { - bridgeState->cacheRoundTripObject(env, static_cast(pointer->data), cachedValue); - return static_cast(pointer->data); - } - } else { - void* nativePointer = nullptr; - if (napi_get_value_external(env, nativePointerValue, &nativePointer) == napi_ok && - nativePointer != nullptr) { - bridgeState->cacheRoundTripObject(env, static_cast(nativePointer), cachedValue); - return static_cast(nativePointer); - } - } - } - } - - return nil; -} - -} // namespace - -namespace nativescript { - -namespace { -constexpr const char* kProtocolSuffix = "Protocol"; - -NSData* createNSDataWrapper(napi_env env, napi_value value, ObjCBridgeState* bridgeState) { - void* data = nullptr; - size_t byteLength = 0; - if (!getJSBufferData(env, value, &data, &byteLength)) { - return nil; - } - - NSData* wrappedData = [NSData dataWithBytes:data length:byteLength]; - if (wrappedData == nil) { - return nil; - } - - if (bridgeState != nullptr && bridgeState->hasRoundTripCacheFrame()) { - bridgeState->cacheRoundTripObject(env, wrappedData, value); - } - - return wrappedData; -} - -inline size_t alignUp(size_t value, size_t alignment) { - if (alignment == 0) { - return value; - } - return ((value + alignment - 1) / alignment) * alignment; -} - -inline uintptr_t normalizeRuntimePointer(uintptr_t ptr) { -#if INTPTR_MAX == INT64_MAX - return ptr & 0x0000FFFFFFFFFFFFULL; -#else - return ptr; -#endif -} - -inline bool isKindOfClassFast(id obj, Class expectedClass) { - if (obj == nil || expectedClass == Nil) { - return false; - } - - return [obj isKindOfClass:expectedClass]; -} - -bool stripProtocolSuffix(const char* name, std::string* out) { - if (name == nullptr || out == nullptr) { - return false; - } - - const size_t nameLen = std::strlen(name); - const size_t suffixLen = std::strlen(kProtocolSuffix); - if (nameLen <= suffixLen) { - return false; - } - - if (std::strcmp(name + (nameLen - suffixLen), kProtocolSuffix) != 0) { - return false; - } - - *out = std::string(name, nameLen - suffixLen); - return !out->empty(); -} - -bool protocolNamesMatch(const char* metadataName, const char* runtimeName) { - if (metadataName == nullptr || runtimeName == nullptr) { - return false; - } - - if (std::strcmp(metadataName, runtimeName) == 0) { - return true; - } - - std::string metadataBase(metadataName); - std::string runtimeBase(runtimeName); - stripProtocolSuffix(metadataName, &metadataBase); - stripProtocolSuffix(runtimeName, &runtimeBase); - - return metadataBase == runtimeBase; -} - -MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const char* protocolName) { - if (metadata == nullptr || protocolName == nullptr) { - return MD_SECTION_OFFSET_NULL; - } - - MDSectionOffset offset = metadata->protocolsOffset; - while (offset < metadata->classesOffset) { - MDSectionOffset originalOffset = offset; - - auto nameOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - - auto name = metadata->resolveString(nameOffset); - if (protocolNamesMatch(name, protocolName)) { - return originalOffset; - } - - while (next) { - auto protocolImpl = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (protocolImpl & mdSectionOffsetNext) != 0; - } - - next = true; - while (next) { - auto flags = metadata->getMemberFlag(offset); - next = (flags & mdMemberNext) != 0; - offset += sizeof(flags); - - if (flags == mdMemberFlagNull) { - break; - } - - if ((flags & mdMemberProperty) != 0) { - bool readonly = (flags & mdMemberReadonly) != 0; - offset += sizeof(MDSectionOffset); // name - offset += sizeof(MDSectionOffset); // getter selector - offset += sizeof(MDSectionOffset); // getter signature - if (!readonly) { - offset += sizeof(MDSectionOffset); // setter selector - offset += sizeof(MDSectionOffset); // setter signature - } - } else { - offset += sizeof(MDSectionOffset); // selector - offset += sizeof(MDSectionOffset); // signature - } - } - } - - return MD_SECTION_OFFSET_NULL; -} -} // namespace - -// Forward declaration -class StructTypeConv; - -// Thread-local storage for tracking structs currently being processed to detect cycles -thread_local std::unordered_set processingStructs; -thread_local std::unordered_set processingEncodingStructs; - -// Cache for forward-declared struct types that need deferred resolution -thread_local std::unordered_map forwardDeclaredStructs; -thread_local std::unordered_map forwardDeclaredEncodingStructs; - -// Cache for StructTypeConv instances to avoid recreating them and handle recursion -thread_local std::unordered_map> structTypeCache; - -// Cache for encoding-based structs to handle recursion -thread_local std::unordered_map> encodingStructCache; - -ffi_type* typeFromStruct(napi_env env, const char** encoding) { - // Extract struct name for cycle detection - std::string structname; - const char* nameStart = *encoding + 1; // skip '{' - const char* c = nameStart; - while (*c != '\0' && *c != '=') { - structname += *c; - c++; - } - if (*c != '=') { - // Malformed struct encoding. Advance to the end of this token and - // fallback to pointer conversion to avoid reading past the buffer. - while (**encoding != '\0' && **encoding != '}') { - (*encoding)++; - } - if (**encoding == '}') { - (*encoding)++; - } - return &ffi_type_pointer; - } - - // Check if we're already processing this struct (cycle detection) - if (processingEncodingStructs.find(structname) != processingEncodingStructs.end()) { - // Create a forward declaration placeholder - ffi_type* forwardType = new ffi_type; - forwardType->type = FFI_TYPE_STRUCT; - forwardType->size = 0; - forwardType->alignment = 0; - forwardType->elements = nullptr; - - // Cache this forward declaration for later resolution - forwardDeclaredEncodingStructs[structname] = forwardType; - - // Skip the struct encoding - (*encoding)++; // skip '{' - while (**encoding != '}') { - (*encoding)++; - } - (*encoding)++; // skip '}' - - return forwardType; - } - - // Check if we already have a forward declaration for this struct - auto existingForwardIt = forwardDeclaredEncodingStructs.find(structname); - if (existingForwardIt != forwardDeclaredEncodingStructs.end()) { - // Skip the struct encoding - (*encoding)++; // skip '{' - while (**encoding != '\0' && **encoding != '}') { - (*encoding)++; - } - if (**encoding == '}') { - (*encoding)++; // skip '}' - } - - return existingForwardIt->second; - } - - // Mark this struct as being processed - processingEncodingStructs.insert(structname); - - ffi_type* type = new ffi_type; - type->type = FFI_TYPE_STRUCT; - type->size = 0; - type->alignment = 0; - type->elements = nullptr; - - std::vector elements; - - (*encoding)++; // skip '{' - - while (**encoding != '\0' && **encoding != '=') { - (*encoding)++; - } // skip name - if (**encoding == '\0') { - processingEncodingStructs.erase(structname); - delete type; - return &ffi_type_pointer; - } - - (*encoding)++; // skip '=' - - while (**encoding != '\0' && **encoding != '}') { - ffi_type* elementType = TypeConv::Make(env, encoding)->type; - elements.push_back(elementType); - } - - if (**encoding == '}') { - (*encoding)++; // skip '}' - } - - type->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (elements.size() + 1)); - for (int i = 0; i < elements.size(); i++) { - type->elements[i] = elements[i]; - } - // null-terminate the array - type->elements[elements.size()] = nullptr; - - // If this was a forward declaration, update it with the real layout - auto resolvedForwardIt = forwardDeclaredEncodingStructs.find(structname); - if (resolvedForwardIt != forwardDeclaredEncodingStructs.end()) { - ffi_type* forwardType = resolvedForwardIt->second; - forwardType->type = type->type; - forwardType->size = type->size; - forwardType->alignment = type->alignment; - forwardType->elements = type->elements; - - // Clean up the temporary type and use the forward declaration - delete type; - type = forwardType; - forwardDeclaredEncodingStructs.erase(resolvedForwardIt); - } - - // Remove from processing set - processingEncodingStructs.erase(structname); - - return type; -} - -ffi_type* typeFromStruct(napi_env env, MDMetadataReader* reader, MDSectionOffset structOffset, - bool isUnion) { - // Check if we're already processing this struct (cycle detection) - if (processingStructs.find(structOffset) != processingStructs.end()) { - // Create a forward declaration placeholder - ffi_type* forwardType = new ffi_type; - forwardType->type = FFI_TYPE_STRUCT; - forwardType->size = 0; - forwardType->alignment = 0; - forwardType->elements = nullptr; - - // Cache this forward declaration for later resolution - forwardDeclaredStructs[structOffset] = forwardType; - return forwardType; - } - - // Check if we already have a forward declaration for this struct - auto existingForwardIt = forwardDeclaredStructs.find(structOffset); - if (existingForwardIt != forwardDeclaredStructs.end()) { - return existingForwardIt->second; - } - - // Mark this struct as being processed - processingStructs.insert(structOffset); - - ffi_type* type = new ffi_type; - type->type = FFI_TYPE_STRUCT; - type->size = 0; - type->alignment = 0; - type->elements = nullptr; - - MDSectionOffset nameOffset = reader->getOffset(structOffset); - auto name = reader->resolveString(nameOffset); - bool next = true; - MDSectionOffset currentOffset = structOffset + sizeof(MDSectionOffset); // skip name - currentOffset += sizeof(uint16_t); // skip size - - std::vector elements; - - while (next) { - nameOffset = reader->getOffset(currentOffset); - next = nameOffset & mdSectionOffsetNext; - nameOffset &= ~mdSectionOffsetNext; - if (nameOffset == MD_SECTION_OFFSET_NULL) { - break; - } - currentOffset += sizeof(MDSectionOffset); // skip name - if (!isUnion) currentOffset += sizeof(uint16_t); // skip offset - ffi_type* elementType = TypeConv::Make(env, reader, ¤tOffset, 1)->type; - elements.push_back(elementType); - } - - type->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (elements.size() + 1)); - for (int i = 0; i < elements.size(); i++) { - type->elements[i] = elements[i]; - } - // null-terminate the array - type->elements[elements.size()] = nullptr; - - // If this was a forward declaration, update it with the real layout - auto resolvedForwardIt = forwardDeclaredStructs.find(structOffset); - if (resolvedForwardIt != forwardDeclaredStructs.end()) { - ffi_type* forwardType = resolvedForwardIt->second; - forwardType->type = type->type; - forwardType->size = type->size; - forwardType->alignment = type->alignment; - forwardType->elements = type->elements; - - // Clean up the temporary type and use the forward declaration - delete type; - type = forwardType; - forwardDeclaredStructs.erase(resolvedForwardIt); - } - - // Remove from processing set - processingStructs.erase(structOffset); - - return type; -} - -static inline size_t getTypedArrayUnitLength(napi_typedarray_type type) { - switch (type) { - case napi_int8_array: - case napi_uint8_array: - case napi_uint8_clamped_array: - return 1; - case napi_int16_array: - case napi_uint16_array: - return 2; - case napi_int32_array: - case napi_uint32_array: - case napi_float32_array: - return 4; - case napi_float64_array: - case napi_bigint64_array: - case napi_biguint64_array: - return 8; - default: - return 0; - } -} - -class VoidTypeConv : public TypeConv { - public: - VoidTypeConv() { - type = &ffi_type_void; - kind = mdTypeVoid; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_get_null(env, &result); - return result; - } - - void encode(std::string* encoding) override { *encoding += "v"; } -}; - -static const std::shared_ptr voidTypeConv = std::make_shared(); - -class SCharTypeConv : public TypeConv { - public: - SCharTypeConv() { - type = &ffi_type_schar; - kind = mdTypeChar; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - int8_t raw = *(int8_t*)value; - if (raw == 0 || raw == 1) { - napi_value result; - napi_get_boolean(env, raw == 1, &result); - return result; - } - - napi_value result; - napi_create_int32(env, raw, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - int32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_int32(env, value, &val); - *(int8_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "c"; } -}; - -static const std::shared_ptr scharTypeConv = std::make_shared(); - -class UCharTypeConv : public TypeConv { - public: - UCharTypeConv() { - type = &ffi_type_uchar; - kind = mdTypeUChar; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - uint8_t raw = *(uint8_t*)value; - if (raw == 0 || raw == 1) { - napi_value result; - napi_get_boolean(env, raw == 1, &result); - return result; - } - - napi_value result; - napi_create_uint32(env, raw, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - uint32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint8_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "C"; } -}; - -static const std::shared_ptr ucharTypeConv = std::make_shared(); - -class UInt8TypeConv : public TypeConv { - public: - UInt8TypeConv() { - type = &ffi_type_uint8; - kind = mdTypeUInt8; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - uint8_t raw = *(uint8_t*)value; - if (raw == 0 || raw == 1) { - napi_value result; - napi_get_boolean(env, raw == 1, &result); - return result; - } - - napi_value result; - napi_create_uint32(env, raw, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - uint32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint8_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "C"; } -}; - -static const std::shared_ptr uint8TypeConv = std::make_shared(); - -class SInt16TypeConv : public TypeConv { - public: - SInt16TypeConv() { - type = &ffi_type_sshort; - kind = mdTypeSShort; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_int32(env, *(int16_t*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - int32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_int32(env, value, &val); - *(int16_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "s"; } -}; - -static const std::shared_ptr sint16TypeConv = std::make_shared(); - -class UInt16TypeConv : public TypeConv { - public: - UInt16TypeConv() { - type = &ffi_type_ushort; - kind = mdTypeUShort; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_uint32(env, *(uint16_t*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - - if (valueType == napi_string) { - size_t strLen = 0; - napi_get_value_string_utf16(env, value, nullptr, 0, &strLen); - if (strLen != 1) { - napi_throw_type_error(env, nullptr, "Expected a single-character string."); - *(uint16_t*)result = 0; - return; - } - - char16_t chars[2] = {0, 0}; - napi_get_value_string_utf16(env, value, chars, 2, &strLen); - *(uint16_t*)result = static_cast(chars[0]); - return; - } - - uint32_t val = 0; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint16_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "S"; } -}; - -static const std::shared_ptr uint16TypeConv = std::make_shared(); - -// unichar/UniChar (mdTypeUnichar): u16 width, but projected to JS as a -// single-character string for any code unit — not just printable ASCII. -class UnicharTypeConv : public UInt16TypeConv { - public: - UnicharTypeConv() { kind = mdTypeUnichar; } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - char16_t unit = *(char16_t*)value; - napi_value result; - napi_create_string_utf16(env, &unit, 1, &result); - return result; - } -}; - -static const std::shared_ptr unicharTypeConv = std::make_shared(); - -class SInt32TypeConv : public TypeConv { - public: - SInt32TypeConv() { - type = &ffi_type_sint; - kind = mdTypeSInt; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_int32(env, *(int32_t*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - int32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_int32(env, value, &val); - *(int32_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "i"; } -}; - -static const std::shared_ptr sint32TypeConv = std::make_shared(); - -class UInt32TypeConv : public TypeConv { - public: - UInt32TypeConv() { - type = &ffi_type_uint; - kind = mdTypeUInt; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_uint32(env, *(uint32_t*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - uint32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint32_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "I"; } -}; - -static const std::shared_ptr uint32TypeConv = std::make_shared(); - -class SInt64TypeConv : public TypeConv { - public: - SInt64TypeConv() { - type = &ffi_type_sint64; - kind = mdTypeSInt64; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - int64_t val = *(int64_t*)value; - constexpr int64_t kMaxSafeInteger = 9007199254740991LL; - if (val > kMaxSafeInteger || val < -kMaxSafeInteger) { - napi_create_bigint_int64(env, val, &result); - } else { - napi_create_int64(env, val, &result); - } - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valuetype; - napi_typeof(env, value, &valuetype); - - switch (valuetype) { - case napi_number: - napi_get_value_int64(env, value, (int64_t*)result); - break; - case napi_bigint: { - bool lossless; - napi_get_value_bigint_int64(env, value, (int64_t*)result, &lossless); - break; - } - case napi_undefined: - case napi_null: - *(int64_t*)result = 0; - break; - case napi_string: - *(int64_t*)result = 0; - break; - default: - napi_throw_type_error(env, nullptr, "Expected a number or bigint"); - break; - } - } - - void encode(std::string* encoding) override { *encoding += "q"; } -}; - -static const std::shared_ptr sint64TypeConv = std::make_shared(); - -class UInt64TypeConv : public TypeConv { - public: - UInt64TypeConv() { - type = &ffi_type_uint64; - kind = mdTypeUInt64; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - uint64_t val = *(uint64_t*)value; - constexpr uint64_t kMaxSafeInteger = 9007199254740991ULL; - if (val > kMaxSafeInteger) { - napi_create_bigint_uint64(env, val, &result); - } else { - napi_create_int64(env, static_cast(val), &result); - } - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valuetype; - napi_typeof(env, value, &valuetype); - - switch (valuetype) { - case napi_number: - napi_get_value_int64(env, value, (int64_t*)result); - break; - case napi_bigint: { - bool lossless; - napi_get_value_bigint_uint64(env, value, (uint64_t*)result, &lossless); - break; - } - case napi_undefined: - case napi_null: - *(int64_t*)result = 0; - break; - default: - napi_throw_type_error(env, nullptr, "Expected a number or bigint"); - break; - } - } - - void encode(std::string* encoding) override { *encoding += "Q"; } -}; - -static const std::shared_ptr uint64TypeConv = std::make_shared(); - -class UInt128TypeConv : public TypeConv { - private: - ffi_type _type = {.size = 0, - .alignment = 0, - .type = FFI_TYPE_STRUCT, - .elements = (ffi_type*[]){ - &ffi_type_uint64, - &ffi_type_uint64, - nullptr, - }}; - - public: - UInt128TypeConv() { - type = &_type; - kind = mdTypeUInt128; - } - - // TODO - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - uint64_t val = *(uint64_t*)value; - napi_create_int64(env, (int64_t)val, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valuetype; - napi_typeof(env, value, &valuetype); - - switch (valuetype) { - case napi_number: - napi_get_value_int64(env, value, (int64_t*)result); - break; - case napi_bigint: { - bool lossless; - napi_get_value_bigint_uint64(env, value, (uint64_t*)result, &lossless); - break; - } - default: - napi_throw_type_error(env, nullptr, "Expected a number or bigint"); - break; - } - } -}; - -static const std::shared_ptr uint128TypeConv = std::make_shared(); - -class Float32TypeConv : public TypeConv { - public: - Float32TypeConv() { - type = &ffi_type_float; - kind = mdTypeFloat; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_double(env, *(float*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - double val; - napi_coerce_to_number(env, value, &value); - napi_get_value_double(env, value, &val); - *(float*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "f"; } -}; - -static const std::shared_ptr float32TypeConv = std::make_shared(); - -class Float16TypeConv : public TypeConv { - public: - Float16TypeConv() { - type = &ffi_type_uint16; - kind = mdTypeF16; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_double(env, decodeFloat16(*(uint16_t*)value), &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - double val = 0; - napi_coerce_to_number(env, value, &value); - napi_get_value_double(env, value, &val); - *(uint16_t*)result = encodeFloat16(val); - } - - void encode(std::string* encoding) override { *encoding += "H"; } -}; - -static const std::shared_ptr float16TypeConv = std::make_shared(); - -class Float64TypeConv : public TypeConv { - public: - Float64TypeConv() { - type = &ffi_type_double; - kind = mdTypeDouble; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_double(env, *(double*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - double val; - napi_coerce_to_number(env, value, &value); - napi_get_value_double(env, value, &val); - if (std::isnan(val) || std::isinf(val)) { - val = 0.0; - } - *(double*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "d"; } -}; - -static const std::shared_ptr float64TypeConv = std::make_shared(); - -class BoolTypeConv : public TypeConv { - public: - BoolTypeConv() { - type = &ffi_type_uint8; - kind = mdTypeBool; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - uint8_t raw = *(uint8_t*)value; - if (raw == 0 || raw == 1) { - napi_value result; - napi_get_boolean(env, raw == 1, &result); - return result; - } - - napi_value result; - napi_create_uint32(env, raw, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - - if (valueType == napi_number) { - uint32_t val = 0; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint8_t*)result = static_cast(val); - return; - } - - if (valueType == napi_bigint) { - uint64_t val = 0; - bool lossless = false; - napi_get_value_bigint_uint64(env, value, &val, &lossless); - *(uint8_t*)result = static_cast(val); - return; - } - - bool val = false; - napi_coerce_to_bool(env, value, &value); - napi_get_value_bool(env, value, &val); - *(uint8_t*)result = static_cast(val ? 1 : 0); - } - - void encode(std::string* encoding) override { *encoding += "B"; } -}; - -static const std::shared_ptr boolTypeConv = std::make_shared(); - -class PointerTypeConv : public TypeConv { - public: - std::shared_ptr pointeeType = nullptr; - - PointerTypeConv() { - type = &ffi_type_pointer; - kind = mdTypePointer; - } - - PointerTypeConv(std::shared_ptr pointeeType) : pointeeType(pointeeType) { - type = &ffi_type_pointer; - kind = mdTypePointer; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - void* raw = *((void**)value); - if (raw == nullptr) { - napi_value nullValue; - napi_get_null(env, &nullValue); - return nullValue; - } - - auto normalizePtr = [](void* ptr) -> uintptr_t { -#if INTPTR_MAX == INT64_MAX - // Objective-C pointers may carry auth/tag bits on some runtimes. - // Compare using canonical lower bits for stable lookups. - return reinterpret_cast(ptr) & 0x0000FFFFFFFFFFFFULL; -#else - return reinterpret_cast(ptr); -#endif - }; - - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - auto classIt = bridgeState->mdClassesByPointer.find((Class)raw); - if (classIt != bridgeState->mdClassesByPointer.end()) { - auto cls = bridgeState->getClass(env, classIt->second); - if (cls != nullptr) { - return get_ref_value(env, cls->constructor); - } - } else { - const uintptr_t rawNormalized = normalizePtr(raw); - for (const auto& entry : bridgeState->mdClassesByPointer) { - if (normalizePtr((void*)entry.first) != rawNormalized) { - continue; - } - - auto cls = bridgeState->getClass(env, entry.second); - if (cls != nullptr) { - return get_ref_value(env, cls->constructor); - } - } - } - - auto protocolIt = bridgeState->mdProtocolsByPointer.find((Protocol*)raw); - if (protocolIt != bridgeState->mdProtocolsByPointer.end()) { - auto proto = bridgeState->getProtocol(env, protocolIt->second); - if (proto != nullptr) { - return get_ref_value(env, proto->constructor); - } - } else { - const uintptr_t rawNormalized = normalizePtr(raw); - for (const auto& entry : bridgeState->mdProtocolsByPointer) { - if (normalizePtr((void*)entry.first) != rawNormalized) { - continue; - } - - auto proto = bridgeState->getProtocol(env, entry.second); - if (proto != nullptr) { - return get_ref_value(env, proto->constructor); - } - } - - // Some protocol pointers come from compile-time @protocol() references - // and don't always match objc_getProtocol() pointer identity. - // Resolve them by scanning runtime protocol list and matching by address. - unsigned int protocolCount = 0; - Protocol** protocols = objc_copyProtocolList(&protocolCount); - if (protocols != nullptr) { - for (unsigned int i = 0; i < protocolCount; i++) { - Protocol* runtimeProto = protocols[i]; - if (normalizePtr((void*)runtimeProto) != rawNormalized) { - continue; - } - - const char* runtimeName = protocol_getName(runtimeProto); - MDSectionOffset metadataOffset = - findProtocolMetadataOffset(bridgeState->metadata, runtimeName); - if (metadataOffset != MD_SECTION_OFFSET_NULL) { - bridgeState->registerProtocolMetadata(runtimeProto, metadataOffset); - auto proto = bridgeState->getProtocol(env, metadataOffset); - bridgeState->registerRuntimeProtocol(proto, runtimeProto); - if (proto != nullptr) { - ::free(protocols); - return get_ref_value(env, proto->constructor); - } - } - - break; - } - ::free(protocols); - } - } - } - - if (pointeeType != nullptr && pointeeType->kind != mdTypeVoid) { - napi_value referenceValue = Reference::create(env, pointeeType, raw, false); - if (referenceValue != nullptr) { - return referenceValue; - } - } - - return Pointer::create(env, raw); - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - void** res = (void**)result; - - auto unwrapKnownNativeHandle = [&](napi_value input, void** out) -> bool { - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - napi_valuetype inputType = napi_undefined; - if (napi_typeof(env, input, &inputType) == napi_ok && - (inputType == napi_function || inputType == napi_object)) { - id bridgedType = nil; - if (bridgeState->tryResolveBridgedTypeConstructor(env, input, &bridgedType) && - bridgedType != nil) { - *out = (void*)bridgedType; - return true; - } - } - } - - void* wrapped = nullptr; - napi_status unwrapStatus = napi_unwrap(env, input, &wrapped); - if (unwrapStatus != napi_ok) { - bool hasNativePointer = false; - if (napi_has_named_property(env, input, "__ns_native_ptr", &hasNativePointer) == - napi_ok && - hasNativePointer) { - napi_value nativePointerValue = nullptr; - if (napi_get_named_property(env, input, "__ns_native_ptr", &nativePointerValue) == - napi_ok && - Pointer::isInstance(env, nativePointerValue)) { - Pointer* pointer = Pointer::unwrap(env, nativePointerValue); - if (pointer != nullptr && pointer->data != nullptr) { - *out = pointer->data; - return true; - } - } - } - return false; - } - - if (bridgeState != nullptr) { - for (const auto& entry : bridgeState->classes) { - auto bridgedClass = entry.second; - if (bridgedClass == wrapped) { - *out = (void*)bridgedClass->nativeClass; - return true; - } - } - - for (const auto& entry : bridgeState->protocols) { - auto bridgedProtocol = entry.second; - if (bridgedProtocol == wrapped) { - *out = (void*)objc_getProtocol(bridgedProtocol->name.c_str()); - return true; - } - } - } - - *out = wrapped; - return true; - }; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_null: - case napi_undefined: - *res = nullptr; - return; - - case napi_bigint: { - uint64_t val = 0; - bool lossless = false; - NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - *res = (void*)val; - return; - } - - case napi_string: { - size_t len = 0; - NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, len, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - char* str = (char*)malloc(len + 1); - - NAPI_GUARD(napi_get_value_string_utf8(env, value, str, len + 1, &len)) { - NAPI_THROW_LAST_ERROR - ::free(str); - return; - } - - str[len] = '\0'; - - bool shouldCreateCFString = - pointeeType != nullptr && (pointeeType->kind == mdTypeNSStringObject || - pointeeType->kind == mdTypeNSMutableStringObject); - - if (shouldCreateCFString) { - CFStringRef cfStr = - CFStringCreateWithCString(kCFAllocatorDefault, str, kCFStringEncodingUTF8); - ::free(str); - *res = (void*)cfStr; - *shouldFree = true; - *shouldFreeAny = true; - } else { - *res = (void*)str; - *shouldFree = true; - *shouldFreeAny = true; - } - return; - } - - case napi_external: { - NAPI_GUARD(napi_get_value_external(env, value, res)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - return; - } - - case napi_object: { - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - id bridgedType = nil; - if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && - bridgedType != nil) { - *res = (void*)bridgedType; - return; - } - } - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *res = ptr->data; - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - if (ref == nullptr) { - napi_throw_error(env, nullptr, "Invalid Reference"); - *res = nullptr; - return; - } - if (ref->data == nullptr) { - std::shared_ptr resolvedType = pointeeType; - if (resolvedType == nullptr) { - resolvedType = ref->type; - } - - napi_value pendingInitValue = Reference::getInitValue(env, value, ref); - napi_valuetype pendingInitType = napi_undefined; - if (pendingInitValue != nullptr) { - napi_typeof(env, pendingInitValue, &pendingInitType); - } - if (resolvedType == nullptr && pendingInitValue != nullptr) { - if (pendingInitValue != nullptr) { - napi_valuetype initType = napi_undefined; - if (napi_typeof(env, pendingInitValue, &initType) == napi_ok) { - auto makeStructType = [&](StructInfo* info) -> std::shared_ptr { - if (info == nullptr || info->name == nullptr) { - return nullptr; - } - - std::string encoding = "{"; - encoding += info->name; - encoding += "="; - for (const auto& field : info->fields) { - if (field.type == nullptr) { - return nullptr; - } - field.type->encode(&encoding); - } - encoding += "}"; - - const char* encodingPtr = encoding.c_str(); - return TypeConv::Make(env, &encodingPtr); - }; - - if (initType == napi_object) { - if (StructObject::isInstance(env, pendingInitValue)) { - StructObject* structObj = StructObject::unwrap(env, pendingInitValue); - if (structObj != nullptr) { - resolvedType = makeStructType(structObj->info); - } - } - - if (resolvedType == nullptr) { - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - bool isArray = false; - bool isTypedArray = false; - bool isArrayBuffer = false; - bool isDataView = false; - napi_is_array(env, pendingInitValue, &isArray); - napi_is_typedarray(env, pendingInitValue, &isTypedArray); - napi_is_arraybuffer(env, pendingInitValue, &isArrayBuffer); - napi_is_dataview(env, pendingInitValue, &isDataView); - if (!isArray && !isTypedArray && !isArrayBuffer && !isDataView) { - napi_value propertyNames = nullptr; - if (napi_get_property_names(env, pendingInitValue, &propertyNames) == - napi_ok && - propertyNames != nullptr) { - uint32_t propertyCount = 0; - napi_get_array_length(env, propertyNames, &propertyCount); - std::unordered_set keys; - std::unordered_map keyIsInteger; - keys.reserve(propertyCount); - for (uint32_t i = 0; i < propertyCount; i++) { - napi_value keyValue = nullptr; - if (napi_get_element(env, propertyNames, i, &keyValue) != napi_ok || - keyValue == nullptr) { - continue; - } - napi_valuetype keyType = napi_undefined; - if (napi_typeof(env, keyValue, &keyType) != napi_ok || - keyType != napi_string) { - continue; - } - size_t keyLength = 0; - if (napi_get_value_string_utf8(env, keyValue, nullptr, 0, - &keyLength) != napi_ok) { - continue; - } - std::vector keyBuffer(keyLength + 1, '\0'); - if (napi_get_value_string_utf8(env, keyValue, keyBuffer.data(), - keyBuffer.size(), - &keyLength) != napi_ok) { - continue; - } - std::string key(keyBuffer.data(), keyLength); - keys.insert(key); - - napi_value propertyValue = nullptr; - if (napi_get_property(env, pendingInitValue, keyValue, - &propertyValue) == napi_ok && - propertyValue != nullptr) { - napi_valuetype propertyType = napi_undefined; - if (napi_typeof(env, propertyValue, &propertyType) == napi_ok) { - bool isInteger = false; - if (propertyType == napi_bigint) { - isInteger = true; - } else if (propertyType == napi_number) { - double numericValue = 0; - if (napi_get_value_double(env, propertyValue, &numericValue) == - napi_ok) { - int64_t truncated = static_cast(numericValue); - isInteger = static_cast(truncated) == numericValue; - } - } - keyIsInteger[key] = isInteger; - } - } - } - - if (!keys.empty()) { - auto isIntegerKind = [](MDTypeKind kind) -> bool { - switch (kind) { - case mdTypeChar: - case mdTypeSInt: - case mdTypeSShort: - case mdTypeSLong: - case mdTypeSInt64: - case mdTypeUChar: - case mdTypeUInt: - case mdTypeUShort: - case mdTypeUnichar: - case mdTypeULong: - case mdTypeUInt64: - case mdTypeUInt8: - case mdTypeBool: - return true; - default: - return false; - } - }; - - auto isFloatingKind = [](MDTypeKind kind) -> bool { - return kind == mdTypeFloat || kind == mdTypeDouble || - kind == mdTypeLongDouble || kind == mdTypeF16; - }; - - StructInfo* bestMatch = nullptr; - int bestScore = std::numeric_limits::min(); - uint16_t bestSize = std::numeric_limits::max(); - - for (const auto& entry : bridgeState->structOffsets) { - StructInfo* info = bridgeState->getStructInfo(env, entry.second); - if (info == nullptr || info->fields.size() != keys.size()) { - continue; - } - - bool match = true; - for (const auto& field : info->fields) { - if (field.name == nullptr || - keys.find(field.name) == keys.end()) { - match = false; - break; - } - } - if (!match) { - continue; - } - - int score = 0; - bool hasOnlyNumericFields = true; - for (const auto& field : info->fields) { - if (field.type == nullptr) { - hasOnlyNumericFields = false; - break; - } - - MDTypeKind fieldKind = field.type->kind; - if (isIntegerKind(fieldKind)) { - auto integerEntry = - keyIsInteger.find(field.name != nullptr ? field.name : ""); - score += - (integerEntry != keyIsInteger.end() && integerEntry->second) - ? 3 - : 1; - } else if (isFloatingKind(fieldKind)) { - score += 2; - } else { - hasOnlyNumericFields = false; - break; - } - } - - if (!hasOnlyNumericFields) { - continue; - } - - if (score > bestScore || - (score == bestScore && info->size < bestSize)) { - bestScore = score; - bestSize = info->size; - bestMatch = info; - } - } - - if (bestMatch != nullptr) { - resolvedType = makeStructType(bestMatch); - } - } - } - } - } - } - } - - if (resolvedType == nullptr) { - const char* inferredEncoding = "@"; - if (initType == napi_number || initType == napi_bigint) { - inferredEncoding = "q"; - } else if (initType == napi_boolean) { - inferredEncoding = "B"; - } - resolvedType = TypeConv::Make(env, &inferredEncoding); - } - } - } - } - - if (resolvedType == nullptr) { - const char* defaultEncoding = "@"; - resolvedType = TypeConv::Make(env, &defaultEncoding); - } - - ref->type = resolvedType; - size_t pointeeSize = sizeof(void*); - if (resolvedType != nullptr && resolvedType->type != nullptr && - resolvedType->type->size > 0) { - pointeeSize = resolvedType->type->size; - } - ref->data = calloc(1, pointeeSize); - if (ref->data == nullptr) { - napi_throw_error(env, nullptr, "Out of memory while allocating out parameter"); - *res = nullptr; - return; - } - ref->ownsData = true; - napi_value initValue = Reference::getInitValue(env, value, ref); - if (initValue != nullptr) { - bool shouldFree; - ref->type->toNative(env, initValue, ref->data, &shouldFree, &shouldFree); - Reference::clearInitValue(env, value, ref); - } - } - *res = ref->data; - return; - } - - if (StructObject::isInstance(env, value)) { - StructObject* structObj = StructObject::unwrap(env, value); - if (structObj != nullptr) { - *res = structObj->data; - } else - *res = nullptr; - return; - } - - bool isTypedArray = false; - napi_is_typedarray(env, value, &isTypedArray); - if (isTypedArray) { - void* data; - size_t length = 0; - napi_typedarray_type type; - NAPI_GUARD( - napi_get_typedarray_info(env, value, &type, &length, &data, nullptr, nullptr)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - - *res = data; - return; - } - - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - void* data = nullptr; - size_t byteLength = 0; - napi_get_arraybuffer_info(env, value, &data, &byteLength); - *res = data; - return; - } - break; - } - - case napi_function: { - if (unwrapKnownNativeHandle(value, res)) { - return; - } - break; - } - - default: - napi_throw_error(env, nullptr, "Invalid pointer type"); - *res = nullptr; - return; - } - - napi_throw_error(env, nullptr, "Invalid pointer type"); - *res = nullptr; - } - - void free(napi_env env, void* value) override { - if (value == nullptr) { - return; - } - - bool isCFString = pointeeType != nullptr && (pointeeType->kind == mdTypeNSStringObject || - pointeeType->kind == mdTypeNSMutableStringObject); - - if (isCFString) { - CFRelease((CFStringRef)value); - } else { - ::free(value); - } - } - - void encode(std::string* encoding) override { *encoding += "^v"; } -}; - -static const std::shared_ptr pointerTypeConv = std::make_shared(); - -class BlockTypeConv : public TypeConv { - public: - MDSectionOffset signatureOffset; - - BlockTypeConv(MDSectionOffset signatureOffset) : signatureOffset(signatureOffset) { - type = &ffi_type_pointer; - kind = mdTypeBlock; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - void* fn = *((void**)value); - if (fn == nullptr) { - napi_value nullValue; - napi_get_null(env, &nullValue); - return nullValue; - } - return FunctionPointer::wrap(env, fn, signatureOffset, true); - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - void** res = (void**)result; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_null: - case napi_undefined: - *res = nullptr; - return; - - case napi_bigint: { - uint64_t val = 0; - bool lossless = false; - NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - *res = (void*)val; - return; - } - - case napi_external: { - NAPI_GUARD(napi_get_value_external(env, value, res)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - return; - } - - case napi_object: { - NAPI_GUARD(napi_unwrap(env, value, res)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - return; - } - - case napi_function: { - if (FunctionReference::isInstance(env, value)) { - FunctionReference* ref = FunctionReference::unwrap(env, value); - if (ref == nullptr) { - napi_throw_error(env, nullptr, "Invalid FunctionReference"); - *res = nullptr; - return; - } - *res = ref->getFunctionPointer(signatureOffset, true); - return; - } - - void* wrapped; - status = napi_unwrap(env, value, &wrapped); - if (status == napi_ok) { - *res = wrapped; - return; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto closure = new Closure(env, bridgeState->metadata, signatureOffset, true); - id block = registerBlock(env, closure, value); - *res = (void*)block; - *shouldFree = true; - *shouldFreeAny = true; - return; - } - - default: - napi_throw_error(env, nullptr, "Invalid block pointer type"); - *res = nullptr; - return; - } - } - - void free(napi_env env, void* value) override { - if (value != nullptr) { - [(id)value release]; - } - } - - void encode(std::string* encoding) override { *encoding += "^v"; } -}; - -namespace { -void function_pointer_finalize_now(napi_env env, void* finalize_data, void* finalize_hint) { - Closure* closure = static_cast(finalize_hint); - if (closure != nullptr) { - Closure::destroyOnOwningThread(closure); - } -} -} // namespace - -void function_pointer_finalize(napi_env env, void* finalize_data, void* finalize_hint) { - if (PostFinalizer(env, function_pointer_finalize_now, finalize_data, finalize_hint)) { - return; - } - - function_pointer_finalize_now(env, finalize_data, finalize_hint); -} - -class FunctionPointerTypeConv : public TypeConv { - public: - MDSectionOffset signatureOffset; - - FunctionPointerTypeConv(MDSectionOffset signatureOffset) : signatureOffset(signatureOffset) { - type = &ffi_type_pointer; - kind = mdTypeFunctionPointer; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - void* fn = *((void**)value); - if (fn == nullptr) { - napi_value nullValue; - napi_get_null(env, &nullValue); - return nullValue; - } - return FunctionPointer::wrap(env, fn, signatureOffset, false); - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - void** res = (void**)result; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_null: - case napi_undefined: - *res = nullptr; - return; - - case napi_bigint: { - uint64_t val = 0; - bool lossless = false; - NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - *res = (void*)val; - return; - } - - case napi_external: { - NAPI_GUARD(napi_get_value_external(env, value, res)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - return; - } - - case napi_object: { - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *res = ptr->data; - } else if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *res = ref->data; - } else if (FunctionReference::isInstance(env, value)) { - FunctionReference* ref = FunctionReference::unwrap(env, value); - if (ref == nullptr) { - napi_throw_error(env, nullptr, "Invalid FunctionReference"); - *res = nullptr; - return; - } - *res = ref->getFunctionPointer(signatureOffset, false); - } else { - napi_throw_error(env, nullptr, "Invalid function pointer object"); - *res = nullptr; - } - return; - } - - case napi_function: { - if (FunctionReference::isInstance(env, value)) { - FunctionReference* ref = FunctionReference::unwrap(env, value); - if (ref == nullptr) { - napi_throw_error(env, nullptr, "Invalid FunctionReference"); - *res = nullptr; - return; - } - *res = ref->getFunctionPointer(signatureOffset, false); - return; - } - - void* wrapped; - status = napi_unwrap(env, value, &wrapped); - if (status == napi_ok) { - *res = wrapped; - return; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto closure = new Closure(env, bridgeState->metadata, signatureOffset, false); - closure->func = make_ref(env, value); - napi_remove_wrap(env, value, nullptr); - napi_ref ref; - napi_wrap(env, value, closure->fnptr, function_pointer_finalize, closure, &ref); - *res = (void*)closure->fnptr; - return; - } - - default: - napi_throw_error(env, nullptr, "Invalid block pointer type"); - *res = nullptr; - return; - } - } - - void encode(std::string* encoding) override { *encoding += "^v"; } -}; - -class StringTypeConv : public TypeConv { - public: - StringTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeString; - } - - napi_value toJS(napi_env env, void* cont, uint32_t flags) override { - void* value = *((void**)cont); - if (value == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - if ((flags & kCStringAsReference) != 0) { - return Reference::create(env, scharTypeConv, value, false); - } - - napi_value result; - napi_create_string_utf8(env, (char*)value, NAPI_AUTO_LENGTH, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - napi_valuetype valuetype; - napi_typeof(env, value, &valuetype); - - if (valuetype == napi_null || valuetype == napi_undefined) { - *(char**)result = nullptr; - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - if (valuetype == napi_object) { - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *(char**)result = (char*)ptr->data; - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *(char**)result = (char*)ref->data; - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - bool isTypedArray = false; - napi_is_typedarray(env, value, &isTypedArray); - if (isTypedArray) { - void* data = nullptr; - size_t length = 0; - napi_typedarray_type typedArrayType; - napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); - *(char**)result = static_cast(data); - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - void* data = nullptr; - size_t byteLength = 0; - napi_get_arraybuffer_info(env, value, &data, &byteLength); - *(char**)result = static_cast(data); - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - bool isDataView = false; - napi_is_dataview(env, value, &isDataView); - if (isDataView) { - void* data = nullptr; - size_t byteLength = 0; - napi_value arrayBuffer = nullptr; - size_t byteOffset = 0; - napi_get_dataview_info(env, value, &byteLength, &data, &arrayBuffer, &byteOffset); - *(char**)result = static_cast(data); - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - *(char**)result = nullptr; - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - char** res = (char**)result; - - *res = nullptr; - size_t len = 0; - - NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, len, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - *res = (char*)malloc(len + 1); - - NAPI_GUARD(napi_get_value_string_utf8(env, value, *res, len + 1, &len)) { - NAPI_THROW_LAST_ERROR - ::free(*res); - return; - } - - (*res)[len] = '\0'; - - *shouldFree = true; - *shouldFreeAny = true; - } - - void free(napi_env env, void* value) override { ::free(value); } - - void encode(std::string* encoding) override { *encoding += "*"; } -}; - -static const std::shared_ptr stringTypeConv = std::make_shared(); - -class ObjCObjectTypeConv : public TypeConv { - public: - MDSectionOffset classOffset = 0; - std::vector protocolOffsets; - - ObjCObjectTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeAnyObject; - } - - ObjCObjectTypeConv(MDSectionOffset classOffset, std::vector protocolOffsets) - : classOffset(classOffset), protocolOffsets(protocolOffsets) { - type = &ffi_type_pointer; - if (classOffset != 0) { - kind = mdTypeClassObject; - } else { - kind = protocolOffsets.empty() ? mdTypeAnyObject : mdTypeProtocolObject; - } - } - - ObjCObjectTypeConv(std::vector protocolOffsets) - : protocolOffsets(protocolOffsets) { - type = &ffi_type_pointer; - kind = protocolOffsets.empty() ? mdTypeAnyObject : mdTypeProtocolObject; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - void* rawPtr = *((void**)value); - id obj = (__bridge id)rawPtr; - - if (obj == nil) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - - if (bridgeState != nullptr) { - if (object_isClass(obj)) { - if (napi_value constructor = findRegisteredClassConstructor(env, (Class)obj); - constructor != nullptr) { - return constructor; - } - } - - auto normalizePtr = [](void* ptr) -> uintptr_t { - return normalizeRuntimePointer(reinterpret_cast(ptr)); - }; - - auto protocolIt = bridgeState->mdProtocolsByPointer.find((Protocol*)obj); - if (protocolIt != bridgeState->mdProtocolsByPointer.end()) { - auto proto = bridgeState->getProtocol(env, protocolIt->second); - if (proto != nullptr) { - return get_ref_value(env, proto->constructor); - } - } else { - const uintptr_t objNormalized = normalizePtr((void*)obj); - for (const auto& entry : bridgeState->mdProtocolsByPointer) { - if (normalizePtr((void*)entry.first) != objNormalized) { - continue; - } - - auto proto = bridgeState->getProtocol(env, entry.second); - if (proto != nullptr) { - return get_ref_value(env, proto->constructor); - } - } - } - } - - // Always unbox NSNull and CFBoolean/NSNumber values (except NSDecimalNumber), - // so primitive round-trips match historical runtime behavior. - if (isKindOfClassFast(obj, [NSNull class])) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - if (isKindOfClassFast(obj, [NSNumber class]) && - !isKindOfClassFast(obj, [NSDecimalNumber class])) { - if (CFGetTypeID((CFTypeRef)obj) == CFBooleanGetTypeID()) { - napi_value result; - napi_get_boolean(env, [obj boolValue], &result); - return result; - } - - napi_value result; - napi_create_double(env, [obj doubleValue], &result); - return result; - } - - // Untyped id values that are actually Objective-C blocks should be - // callable from JS. Preserve callback identity when we already have one. - if (isObjCBlockObject(obj)) { - napi_value cached = getCachedBlockCallback(env, (void*)obj); - if (cached != nullptr) { - return cached; - } - - const char* signature = getObjCBlockSignature((void*)obj); - if (signature != nullptr) { - return FunctionPointer::wrapWithEncoding(env, (void*)obj, signature, true); - } - } - - // Auto-unbox plain id string values. - const bool isUntypedObject = classOffset == 0 && protocolOffsets.empty(); - if (isUntypedObject && isKindOfClassFast(obj, [NSString class])) { - NSUInteger length = [obj length]; - std::vector chars(length > 0 ? length : 1); - if (length > 0) { - [((NSString*)obj) getCharacters:(unichar*)chars.data() range:NSMakeRange(0, length)]; - } - napi_value result; - napi_create_string_utf16(env, length > 0 ? chars.data() : nullptr, length, &result); - return result; - } - - if (bridgeState == nullptr) { - return Pointer::create(env, (void*)obj); - } - - if (napi_value existing = bridgeState->findCachedObjectWrapper(env, obj); existing != nullptr) { - return existing; - } - - ObjectOwnership ownership; - if ((flags & kReturnOwned) != 0) { - ownership = kOwnedObject; - } else { - ownership = kUnownedObject; - } - - auto object = bridgeState->getObject(env, obj, ownership, classOffset, &protocolOffsets); - if (object == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - return object; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - id* res = (id*)result; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_null: - case napi_undefined: - *res = nil; - return; - - case napi_string: { - size_t len = 0; - NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, 0, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - std::vector chars(len + 1); - NAPI_GUARD(napi_get_value_string_utf8(env, value, chars.data(), len + 1, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - *res = [[[NSString alloc] initWithBytes:chars.data() - length:len - encoding:NSUTF8StringEncoding] autorelease]; - if (*res == nil) { - *res = [NSString string]; - } - break; - } - - case napi_number: { - double val = 0; - NAPI_GUARD(napi_get_value_double(env, value, &val)) { - NAPI_THROW_LAST_ERROR - return; - } - *res = [NSNumber numberWithDouble:val]; - break; - } - - case napi_boolean: { - bool val = false; - NAPI_GUARD(napi_get_value_bool(env, value, &val)) { - NAPI_THROW_LAST_ERROR - return; - } - *res = [NSNumber numberWithBool:val]; - break; - } - - case napi_bigint: { - int64_t val = 0; - bool lossless = false; - NAPI_GUARD(napi_get_value_bigint_int64(env, value, &val, &lossless)) { - NAPI_THROW_LAST_ERROR - return; - } - *res = [NSNumber numberWithLongLong:val]; - break; - } - - case napi_external: - NAPI_GUARD(napi_get_value_external(env, value, (void**)res)) { - NAPI_THROW_LAST_ERROR - *res = nil; - return; - } - break; - - case napi_object: - case napi_function: { - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto cacheRoundTrip = [&](id nativeObj) { - if (nativeObj == nil || bridgeState == nullptr || - !bridgeState->hasRoundTripCacheFrame()) { - return; - } - - bridgeState->cacheRoundTripObject(env, nativeObj, value); - }; - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - void* pointerData = ptr != nullptr ? ptr->data : nullptr; - if (id cachedObject = resolveCachedHandleObject(env, pointerData); cachedObject != nil) { - *res = cachedObject; - return; - } - *res = (id)pointerData; - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - void* referenceData = ref != nullptr ? ref->data : nullptr; - if (id cachedObject = resolveCachedHandleObject(env, referenceData); - cachedObject != nil) { - *res = cachedObject; - return; - } - *res = (id)referenceData; - return; - } - - if (bridgeState != nullptr) { - id bridgedType = nil; - if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && - bridgedType != nil) { - *res = bridgedType; - return; - } - } - - void* wrapped = nullptr; - status = napi_unwrap(env, value, &wrapped); - - if (status != napi_ok) { - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - *res = createNSDataWrapper(env, value, bridgeState); - if (*res != nil) { - cacheRoundTrip(*res); - return; - } - } - - bool isTypedArray = false; - napi_is_typedarray(env, value, &isTypedArray); - if (isTypedArray) { - *res = createNSDataWrapper(env, value, bridgeState); - if (*res != nil) { - cacheRoundTrip(*res); - return; - } - } - - bool isDataView = false; - napi_is_dataview(env, value, &isDataView); - if (isDataView) { - *res = createNSDataWrapper(env, value, bridgeState); - if (*res != nil) { - cacheRoundTrip(*res); - return; - } - } - - bool isArray = false; - napi_is_array(env, value, &isArray); - if (isArray) { - uint32_t len = 0; - napi_get_array_length(env, value, &len); - *res = [NSMutableArray arrayWithCapacity:len]; - - for (uint32_t i = 0; i < len; i++) { - napi_value elem; - napi_get_element(env, value, i, &elem); - id obj = nil; - toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); - [(*res) addObject:obj != nil ? obj : [NSNull null]]; - } - - cacheRoundTrip(*res); - return; - } else { - napi_value global, jsObject, valueConstructor, DateConstructor, MapConstructor; - napi_value StringConstructor, NumberConstructor, BooleanConstructor; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &jsObject); - napi_get_named_property(env, global, "Date", &DateConstructor); - napi_get_named_property(env, global, "Map", &MapConstructor); - napi_get_named_property(env, global, "String", &StringConstructor); - napi_get_named_property(env, global, "Number", &NumberConstructor); - napi_get_named_property(env, global, "Boolean", &BooleanConstructor); - napi_get_named_property(env, value, "constructor", &valueConstructor); - bool isEqual; - napi_strict_equals(env, jsObject, valueConstructor, &isEqual); - bool isDate; - napi_strict_equals(env, DateConstructor, valueConstructor, &isDate); - bool isMap; - napi_strict_equals(env, MapConstructor, valueConstructor, &isMap); - bool isStringObject = false; - bool isNumberObject = false; - bool isBooleanObject = false; - napi_strict_equals(env, StringConstructor, valueConstructor, &isStringObject); - napi_strict_equals(env, NumberConstructor, valueConstructor, &isNumberObject); - napi_strict_equals(env, BooleanConstructor, valueConstructor, &isBooleanObject); - - if (isStringObject || isNumberObject || isBooleanObject) { - napi_value valueOfMethod; - napi_get_named_property(env, value, "valueOf", &valueOfMethod); - napi_value primitiveValue; - napi_call_function(env, value, valueOfMethod, 0, nullptr, &primitiveValue); - toNative(env, primitiveValue, result, shouldFree, shouldFreeAny); - return; - } - - if (isDate) { - // Get the timestamp from the JavaScript Date object - napi_value getTimeMethod; - napi_get_named_property(env, value, "getTime", &getTimeMethod); - napi_value timestamp; - napi_call_function(env, value, getTimeMethod, 0, nullptr, ×tamp); - - double timeInMilliseconds; - napi_get_value_double(env, timestamp, &timeInMilliseconds); - - // Convert milliseconds to seconds for NSDate - NSTimeInterval timeInSeconds = timeInMilliseconds / 1000.0; - *res = [NSDate dateWithTimeIntervalSince1970:timeInSeconds]; - cacheRoundTrip(*res); - return; - } - - if (isMap) { - *res = [NSMutableDictionary dictionary]; - - napi_value entriesMethod; - napi_get_named_property(env, value, "entries", &entriesMethod); - napi_value iterator; - napi_call_function(env, value, entriesMethod, 0, nullptr, &iterator); - - napi_value nextMethod; - napi_get_named_property(env, iterator, "next", &nextMethod); - - while (true) { - napi_value step; - napi_call_function(env, iterator, nextMethod, 0, nullptr, &step); - - napi_value doneValue; - napi_get_named_property(env, step, "done", &doneValue); - bool done = false; - napi_get_value_bool(env, doneValue, &done); - if (done) { - break; - } - - napi_value tuple; - napi_get_named_property(env, step, "value", &tuple); - napi_value keyValue; - napi_value elementValue; - napi_get_element(env, tuple, 0, &keyValue); - napi_get_element(env, tuple, 1, &elementValue); - - id keyObject = nil; - id valueObject = nil; - toNative(env, keyValue, (void*)&keyObject, shouldFree, shouldFreeAny); - toNative(env, elementValue, (void*)&valueObject, shouldFree, shouldFreeAny); - - if (keyObject != nil && valueObject != nil) { - [(*res) setObject:valueObject forKey:keyObject]; - } - } - - cacheRoundTrip(*res); - return; - } - - if (!isEqual) { - *res = jsObjectToId(env, value); - return; - } - - bool hasLength = false; - napi_has_named_property(env, value, "length", &hasLength); - if (hasLength) { - napi_value lengthValue; - napi_get_named_property(env, value, "length", &lengthValue); - napi_valuetype lengthType = napi_undefined; - napi_typeof(env, lengthValue, &lengthType); - if (lengthType == napi_number) { - uint32_t len = 0; - napi_get_value_uint32(env, lengthValue, &len); - *res = [NSMutableArray arrayWithCapacity:len]; - for (uint32_t i = 0; i < len; i++) { - bool hasElement = false; - napi_has_element(env, value, i, &hasElement); - if (!hasElement) { - [(*res) addObject:[NSNull null]]; - continue; - } - napi_value elem; - napi_get_element(env, value, i, &elem); - id obj = nil; - toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); - [(*res) addObject:obj != nil ? obj : [NSNull null]]; - } - cacheRoundTrip(*res); - return; - } - } - - *res = [NSMutableDictionary dictionary]; - napi_value objectKeysMethod = nullptr; - napi_get_named_property(env, jsObject, "keys", &objectKeysMethod); - napi_value keys = nullptr; - napi_call_function(env, jsObject, objectKeysMethod, 1, &value, &keys); - uint32_t len = 0; - napi_get_array_length(env, keys, &len); - - for (uint32_t i = 0; i < len; i++) { - napi_value key = nullptr; - napi_get_element(env, keys, i, &key); - - if (key == nullptr) { - continue; - } - - napi_value keyString = key; - napi_valuetype keyType = napi_undefined; - if (napi_typeof(env, key, &keyType) != napi_ok) { - continue; - } - - if (keyType == napi_symbol) { - continue; - } - - if (keyType != napi_string) { - if (napi_coerce_to_string(env, key, &keyString) != napi_ok || - keyString == nullptr) { - continue; - } - } - - size_t keyLength = 0; - if (napi_get_value_string_utf8(env, keyString, nullptr, 0, &keyLength) != napi_ok) { - continue; - } - - std::vector keyBuffer(keyLength + 1, '\0'); - if (napi_get_value_string_utf8(env, keyString, keyBuffer.data(), keyBuffer.size(), - &keyLength) != napi_ok) { - continue; - } - - NSString* nsKey = [NSString stringWithUTF8String:keyBuffer.data()]; - if (nsKey == nil) { - continue; - } - - id obj = nil; - napi_value elem = nullptr; - if (napi_get_property(env, value, key, &elem) != napi_ok) { - continue; - } - toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); - if (obj != nil) { - [(*res) setObject:obj forKey:nsKey]; - } - } - - cacheRoundTrip(*res); - return; - } - } - - if (bridgeState != nullptr && wrapped != nullptr) { - for (const auto& entry : bridgeState->classes) { - auto bridgedClass = entry.second; - if (bridgedClass == wrapped) { - *res = (id)bridgedClass->nativeClass; - return; - } - } - - for (const auto& entry : bridgeState->protocols) { - auto bridgedProtocol = entry.second; - if (bridgedProtocol != wrapped) { - continue; - } - - Protocol* runtimeProtocol = objc_getProtocol(bridgedProtocol->name.c_str()); - if (runtimeProtocol == nil) { - std::string baseName; - if (stripProtocolSuffix(bridgedProtocol->name.c_str(), &baseName)) { - runtimeProtocol = objc_getProtocol(baseName.c_str()); - } - } - - if (runtimeProtocol != nil) { - *res = (id)runtimeProtocol; - return; - } - } - } - - *res = (id)wrapped; - cacheRoundTrip(*res); - return; - - break; - } - - default: - napi_throw_error(env, nullptr, "Invalid object type"); - *res = nil; - break; - } - } - - void free(napi_env env, void* value) override { - id obj = *((id*)value); - auto bridgeState = ObjCBridgeState::InstanceData(env); - bridgeState->unregisterObject(obj); - } - - void encode(std::string* encoding) override { *encoding += "@"; } -}; - -static const std::shared_ptr objcObjectTypeConv = - std::make_shared(); - -class ObjCInstanceObjectTypeConv : public ObjCObjectTypeConv { - public: - ObjCInstanceObjectTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeInstanceObject; - } -}; - -static const auto objcInstanceObjectTypeConv = std::make_shared(); - -class ObjCNSStringObjectTypeConv : public TypeConv { - public: - ObjCNSStringObjectTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeNSStringObject; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - NSString* str = *((NSString**)value); - - if (str == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - NSUInteger length = [str length]; - std::vector chars(length > 0 ? length : 1); - if (length > 0) { - [str getCharacters:(unichar*)chars.data() range:NSMakeRange(0, length)]; - } - napi_value result; - napi_create_string_utf16(env, chars.data(), length, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - ObjCObjectTypeConv typeConv; - typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); - } - - void encode(std::string* encoding) override { *encoding += "@"; } -}; - -static const std::shared_ptr objcNSStringObjectTypeConv = - std::make_shared(); - -class ObjCNSMutableStringObjectTypeConv : public TypeConv { - public: - ObjCNSMutableStringObjectTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeNSMutableStringObject; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - NSMutableString* str = *((NSMutableString**)value); - - if (str == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (napi_value existing = bridgeState->findCachedObjectWrapper(env, str); existing != nullptr) { - return existing; - } - - ObjectOwnership ownership = (flags & kReturnOwned) != 0 ? kOwnedObject : kUnownedObject; - return bridgeState->getObject(env, str, ownership); - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - napi_valuetype type; - napi_typeof(env, value, &type); - if (type == napi_string) { - NSMutableString** res = (NSMutableString**)result; - - size_t len = 0; - NAPI_GUARD(napi_get_value_string_utf16(env, value, nullptr, len, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - std::vector chars(len + 1); - - NAPI_GUARD(napi_get_value_string_utf16(env, value, chars.data(), len + 1, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - *res = [[NSMutableString alloc] initWithCharacters:(unichar*)chars.data() length:len]; - return; - } - - ObjCObjectTypeConv typeConv; - typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); - } - - void encode(std::string* encoding) override { *encoding += "@"; } -}; - -static const std::shared_ptr objcNSMutableStringObjectTypeConv = - std::make_shared(); - -class ObjCClassTypeConv : public TypeConv { - public: - ObjCClassTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeClass; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - Class cls = *((Class*)value); - - if (cls == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - if (napi_value constructor = findRegisteredClassConstructor(env, cls); - constructor != nullptr) { - return constructor; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - return bridgeState != nullptr ? bridgeState->getObject(env, (id)cls, kUnownedObject, 0, nullptr) - : nullptr; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - ObjCObjectTypeConv typeConv; - typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); - } - - void encode(std::string* encoding) override { *encoding += "#"; } -}; - -static const std::shared_ptr objcClassTypeConv = - std::make_shared(); - -char selector_name_buf[256]; - -class SelectorTypeConv : public TypeConv { - public: - SelectorTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeSelector; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - SEL val = *((SEL*)value); - napi_create_string_utf8(env, sel_getName(val), NAPI_AUTO_LENGTH, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - SEL* res = (SEL*)result; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_string: - NAPI_GUARD(napi_get_value_string_utf8(env, value, selector_name_buf, 256, NULL)) { - NAPI_THROW_LAST_ERROR - *res = NULL; - return; - } - *res = sel_registerName(selector_name_buf); - break; - - case napi_undefined: - case napi_null: - *res = NULL; - return; - - default: - napi_throw_error(env, nullptr, "Invalid selector type"); - *res = NULL; - return; - } - } - - void encode(std::string* encoding) override { *encoding += ":"; } -}; - -static const std::shared_ptr selectorTypeConv = - std::make_shared(); - -class StructTypeConv : public TypeConv { - public: - MDSectionOffset structOffset; - StructInfo* info = nullptr; - bool structInfoSearched = false; - - StructTypeConv(MDSectionOffset structOffset, ffi_type* type) : structOffset(structOffset) { - this->type = type; - kind = mdTypeStruct; - } - - // ~StructTypeConv() { delete type; } - - inline StructInfo* getInfo(napi_env env) { - if (!structInfoSearched) { - auto bridgeState = ObjCBridgeState::InstanceData(env); - info = bridgeState->getStructInfo(env, structOffset); - structInfoSearched = true; - } - - return info; - } - - inline size_t getStructSize(napi_env env) { - if (this->type != nullptr && this->type->size > 0) { - return this->type->size; - } - - auto info = getInfo(env); - return info != nullptr ? info->size : 0; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - auto info = getInfo(env); - - if (info == nullptr) { - napi_value result; - void* data; - napi_create_arraybuffer(env, type->size, &data, &result); - memcpy(data, value, type->size); - return result; - } else { - return StructObject::fromNative(env, info, value, (flags & kStructZeroCopy) == 0); - } - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - const size_t structSize = getStructSize(env); - if (structSize == 0) { - napi_throw_type_error(env, "TypeError", "Invalid struct size"); - return; - } - - bool isTypedArray = false; - napi_is_typedarray(env, value, &isTypedArray); - - if (isTypedArray) { - void* data; - size_t length = 0; - napi_typedarray_type type; - NAPI_GUARD(napi_get_typedarray_info(env, value, &type, &length, &data, nullptr, nullptr)) { - NAPI_THROW_LAST_ERROR - return; - } - const size_t unitLength = getTypedArrayUnitLength(type); - const size_t byteLength = length * unitLength; - memset(result, 0, structSize); - memcpy(result, data, std::min(byteLength, structSize)); - - return; - } - - napi_valuetype type; - napi_typeof(env, value, &type); - - if (type == napi_null || type == napi_undefined) { - auto info = getInfo(env); - - if (info == nullptr) { - napi_throw_type_error(env, "TypeError", - "Invalid struct type, must be Struct Object, " - "Struct Object Descriptor or TypedArray"); - return; - } - - memset(result, 0, info->size); - return; - } else if (type != napi_object) { - napi_throw_type_error(env, "TypeError", - "Invalid struct type, must be Struct Object, " - "Struct Object Descriptor or TypedArray"); - return; - } - - auto structObject = StructObject::unwrap(env, value); - if (structObject != nullptr) { - const size_t copySize = std::min(static_cast(structObject->info->size), structSize); - memset(result, 0, structSize); - memcpy(result, structObject->data, copySize); - return; - } - - auto info = getInfo(env); - - if (info == nullptr) { - napi_throw_type_error(env, "TypeError", - "Invalid struct type, must be Struct Object or TypedArray"); - return; - } - - if (structSize < info->size) { - std::vector storage(info->size, 0); - StructObject(env, info, value, storage.data()); - memcpy(result, storage.data(), structSize); - return; - } - - // Serialize directly to previously allocated memory. - StructObject(env, info, value, result); - } -}; - -class ArrayTypeConv : public TypeConv { - public: - int arraySize; - std::shared_ptr elementType; - bool decayToPointerForArguments = false; - - ArrayTypeConv(int arraySize, std::shared_ptr elementType) - : arraySize(arraySize), elementType(elementType) { - auto arrayType = new ffi_type(); - arrayType->type = FFI_TYPE_STRUCT; - arrayType->size = 0; - arrayType->alignment = 0; - arrayType->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (arraySize + 1)); - for (int i = 0; i < arraySize; i++) { - arrayType->elements[i] = elementType->type; - } - arrayType->elements[arraySize] = nullptr; - type = arrayType; - kind = mdTypeArray; - } - - ffi_type* ffiTypeForArgument() override { - decayToPointerForArguments = true; - return &ffi_type_pointer; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - if (decayToPointerForArguments) { - void* raw = *((void**)value); - if (raw == nullptr) { - napi_value nullValue; - napi_get_null(env, &nullValue); - return nullValue; - } - return Pointer::create(env, raw); - } - - napi_value result; - napi_create_array_with_length(env, arraySize, &result); - - size_t elementSize = getElementSize(); - - auto base = static_cast(value); - for (int i = 0; i < arraySize; i++) { - void* slot = base + (i * elementSize); - napi_value elementValue = elementType->toJS(env, slot, flags); - napi_valuetype elementValueType = napi_undefined; - napi_typeof(env, elementValue, &elementValueType); - if (elementValueType == napi_boolean) { - bool boolValue = false; - napi_get_value_bool(env, elementValue, &boolValue); - napi_create_uint32(env, boolValue ? 1 : 0, &elementValue); - } - napi_set_element(env, result, i, elementValue); - if (StructObject::isInstance(env, elementValue)) { - napi_set_named_property(env, elementValue, "__ns_parent_struct_array", result); - } - } - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - if (decayToPointerForArguments) { - void** pointerResult = static_cast(result); - *pointerResult = nullptr; - - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType == napi_null || valueType == napi_undefined) { - return; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *pointerResult = ptr != nullptr ? ptr->data : nullptr; - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *pointerResult = ref != nullptr ? ref->data : nullptr; - return; - } - - if (StructObject::isInstance(env, value)) { - StructObject* structObject = StructObject::unwrap(env, value); - *pointerResult = structObject != nullptr ? structObject->data : nullptr; - return; - } - - size_t arrayByteSize = getArrayByteSize(); - if (arrayByteSize == 0) { - return; - } - void* copiedBuffer = malloc(arrayByteSize); - if (copiedBuffer == nullptr) { - napi_throw_error(env, nullptr, "Out of memory while converting C array argument"); - return; - } - - copyToInlineArrayStorage(env, value, copiedBuffer, shouldFree, shouldFreeAny); - - bool hasPendingException = false; - napi_is_exception_pending(env, &hasPendingException); - if (hasPendingException) { - ::free(copiedBuffer); - return; - } - - *pointerResult = copiedBuffer; - if (shouldFree != nullptr) { - *shouldFree = true; - } - if (shouldFreeAny != nullptr) { - *shouldFreeAny = true; - } - return; - } - - copyToInlineArrayStorage(env, value, result, shouldFree, shouldFreeAny); - } - - void free(napi_env env, void* value) override { - if (value != nullptr) { - ::free(value); - } - } - - void encode(std::string* encoding) override { - *encoding += "["; - *encoding += std::to_string(arraySize); - elementType->encode(encoding); - *encoding += "]"; - } - - private: - size_t getElementSize() const { - size_t elementSize = - elementType != nullptr && elementType->type != nullptr ? elementType->type->size : 0; - if (elementSize == 0 && type != nullptr && arraySize > 0 && type->size >= (size_t)arraySize) { - elementSize = type->size / static_cast(arraySize); - } - if (elementSize == 0) { - elementSize = sizeof(void*); - } - return elementSize; - } - - size_t getArrayByteSize() const { return getElementSize() * static_cast(arraySize); } - - void copyToInlineArrayStorage(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) { - size_t elementSize = getElementSize(); - size_t arrayByteSize = getArrayByteSize(); - memset(result, 0, arrayByteSize); - - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType == napi_null || valueType == napi_undefined) { - return; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - if (ptr == nullptr || ptr->data == nullptr) { - return; - } - memcpy(result, ptr->data, arrayByteSize); - return; - } - - bool isArray = false; - napi_is_array(env, value, &isArray); - if (isArray) { - for (int i = 0; i < arraySize; i++) { - bool hasElement = false; - napi_has_element(env, value, i, &hasElement); - if (!hasElement) { - continue; - } - - napi_value elementValue; - napi_get_element(env, value, i, &elementValue); - void* slot = static_cast(result) + (i * elementSize); - elementType->toNative(env, elementValue, slot, shouldFree, shouldFreeAny); - } - return; - } - - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - void* data = nullptr; - size_t byteLength = 0; - napi_get_arraybuffer_info(env, value, &data, &byteLength); - memcpy(result, data, std::min(byteLength, arrayByteSize)); - return; - } - - void* data; - size_t length = 0; - napi_typedarray_type typedArrayType; - napi_status typedArrayStatus = - napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); - if (typedArrayStatus != napi_ok) { - NAPI_THROW_LAST_ERROR - return; - } - - size_t copyLength = length * getTypedArrayUnitLength(typedArrayType); - memcpy(result, data, std::min(copyLength, arrayByteSize)); - } -}; - -class VectorTypeConv : public TypeConv { - public: - uint16_t vectorSize; - std::shared_ptr elementType; - MDTypeKind vectorKind; - - VectorTypeConv(MDTypeKind vectorKind, uint16_t vectorSize, std::shared_ptr elementType) - : vectorSize(vectorSize), elementType(elementType), vectorKind(vectorKind) { - auto vectorType = new ffi_type(); -#if defined(FFI_TYPE_EXT_VECTOR) - vectorType->type = vectorKind == mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_EXT_VECTOR; -#else - vectorType->type = vectorKind == mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_STRUCT; -#endif - size_t lanes = std::max(vectorSize, 1); - // 3-lane vectors are ABI-lowered to 4-lane storage on Apple platforms. - size_t abiLanes = lanes == 3 ? 4 : lanes; - vectorType->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (abiLanes + 1)); - - ffi_type* elementFfiType = elementType != nullptr && elementType->type != nullptr - ? elementType->type - : &ffi_type_float; - const size_t elementSize = std::max(elementFfiType->size, sizeof(float)); - const size_t elementAlignment = - std::max(elementFfiType->alignment, static_cast(1)); - - for (size_t i = 0; i < abiLanes; i++) { - vectorType->elements[i] = elementFfiType; - } - vectorType->elements[abiLanes] = nullptr; - - size_t vectorAlignment = elementAlignment; - if (vectorKind != mdTypeComplex) { - size_t packedSize = abiLanes * elementSize; - size_t preferredAlignment = packedSize >= 16 ? 16 : packedSize; - vectorAlignment = std::max(vectorAlignment, preferredAlignment); - } - vectorAlignment = std::min(vectorAlignment, 16); - vectorType->alignment = static_cast(vectorAlignment); - vectorType->size = alignUp(abiLanes * elementSize, vectorAlignment); - - type = vectorType; - kind = vectorKind; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_array_with_length(env, vectorSize, &result); - - size_t elementSize = getElementSize(); - auto base = static_cast(value); - for (uint16_t i = 0; i < vectorSize; i++) { - void* slot = base + (static_cast(i) * elementSize); - napi_value elementValue = elementType->toJS(env, slot, flags); - napi_valuetype elementValueType = napi_undefined; - napi_typeof(env, elementValue, &elementValueType); - if (elementValueType == napi_boolean) { - bool boolValue = false; - napi_get_value_bool(env, elementValue, &boolValue); - napi_create_uint32(env, boolValue ? 1 : 0, &elementValue); - } - napi_set_element(env, result, i, elementValue); - } - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - memset(result, 0, getVectorByteSize()); - - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType == napi_null || valueType == napi_undefined) { - return; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - if (ptr != nullptr && ptr->data != nullptr) { - copyFromContiguousBuffer(ptr->data, getVectorByteSize(), result); - } - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - if (ref != nullptr && ref->data != nullptr) { - copyFromContiguousBuffer(ref->data, getVectorByteSize(), result); - } - return; - } - - if (StructObject::isInstance(env, value)) { - StructObject* structObject = StructObject::unwrap(env, value); - if (structObject != nullptr && structObject->data != nullptr) { - copyFromContiguousBuffer(structObject->data, structObject->info->size, result); - } - return; - } - - bool isArray = false; - napi_is_array(env, value, &isArray); - if (isArray) { - writeFromArrayElements(env, value, result, shouldFree, shouldFreeAny); - return; - } - - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - void* data = nullptr; - size_t byteLength = 0; - napi_get_arraybuffer_info(env, value, &data, &byteLength); - copyFromContiguousBuffer(data, byteLength, result); - return; - } - - void* data = nullptr; - size_t length = 0; - napi_typedarray_type typedArrayType = napi_int8_array; - napi_status typedArrayStatus = - napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); - if (typedArrayStatus == napi_ok) { - size_t copyLength = length * getTypedArrayUnitLength(typedArrayType); - copyFromContiguousBuffer(data, copyLength, result); - return; - } - - napi_throw_type_error( - env, "TypeError", - "Invalid vector type, expected array, typed array, array buffer, pointer or reference."); - } - - void encode(std::string* encoding) override { - *encoding += "V"; - *encoding += std::to_string(vectorSize); - if (elementType != nullptr) { - elementType->encode(encoding); - } - } - - private: - inline size_t getElementSize() const { - size_t elementSize = - elementType != nullptr && elementType->type != nullptr ? elementType->type->size : 0; - if (elementSize == 0) { - elementSize = sizeof(float); - } - return elementSize; - } - - inline size_t getVectorByteSize() const { - size_t expectedSize = static_cast(vectorSize) * getElementSize(); - if (type != nullptr && type->size > expectedSize) { - expectedSize = type->size; - } - return expectedSize; - } - - inline void copyFromContiguousBuffer(void* source, size_t sourceLength, void* destination) const { - memcpy(destination, source, std::min(sourceLength, getVectorByteSize())); - } - - void writeFromArrayElements(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) { - size_t elementSize = getElementSize(); - auto base = static_cast(result); - - for (uint16_t i = 0; i < vectorSize; i++) { - bool hasElement = false; - napi_has_element(env, value, i, &hasElement); - if (!hasElement) { - continue; - } - - napi_value elementValue; - napi_get_element(env, value, i, &elementValue); - void* slot = base + (static_cast(i) * elementSize); - elementType->toNative(env, elementValue, slot, shouldFree, shouldFreeAny); - } - } -}; - -std::shared_ptr TypeConv::Make(napi_env env, const char** encoding) { - char first = **encoding; - bool readonly = false; - if (first == 'r') { - readonly = true; - first = *(++(*encoding)); - } - - switch (first) { - case 'c': - (*encoding)++; - return scharTypeConv; - case 'i': - (*encoding)++; - return sint32TypeConv; - case 's': - (*encoding)++; - return sint16TypeConv; - case 'l': - case 'q': - (*encoding)++; - return sint64TypeConv; - case 'C': - (*encoding)++; - return uint8TypeConv; - case 'I': - (*encoding)++; - return uint32TypeConv; - case 'S': - (*encoding)++; - return uint16TypeConv; - case 'L': - case 'Q': - (*encoding)++; - return uint64TypeConv; - case 'f': - (*encoding)++; - return float32TypeConv; - case 'd': - (*encoding)++; - return float64TypeConv; - case 'B': - (*encoding)++; - return boolTypeConv; - case 'v': - (*encoding)++; - return voidTypeConv; - case '*': - (*encoding)++; - return stringTypeConv; - case '@': - (*encoding)++; - return objcObjectTypeConv; - case '#': - (*encoding)++; - return objcClassTypeConv; - case ':': - (*encoding)++; - return selectorTypeConv; - case '[': { - char c = **encoding; - std::string num; - while ((c = **encoding) >= '0' && c <= '9') { - num += c; - (*encoding)++; - } - auto arraySize = std::stoi(num); - auto elementType = TypeConv::Make(env, encoding); - while (**encoding != ']') { - (*encoding)++; - } // skip array type - (*encoding)++; // skip ']' - return std::make_shared(ArrayTypeConv(arraySize, elementType)); - } - case '{': { - std::string structname; - const char* c = *encoding + 1; - while (*c != '\0' && *c != '=') { - structname += *c; - c++; - } - if (*c != '=') { - while (**encoding != '\0' && **encoding != '}') { - (*encoding)++; - } - if (**encoding == '}') { - (*encoding)++; - } - return pointerTypeConv; - } - - // Check if we already have a cached StructTypeConv for this encoding-based struct - auto cacheIt = encodingStructCache.find(structname); - if (cacheIt != encodingStructCache.end()) { - return cacheIt->second; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - MDSectionOffset structOffset = MD_SECTION_OFFSET_NULL; - if (bridgeState != nullptr) { - auto structOffsetIt = bridgeState->structOffsets.find(structname); - if (structOffsetIt != bridgeState->structOffsets.end()) { - structOffset = structOffsetIt->second; - } - } - auto type = typeFromStruct(env, encoding); - auto structTypeConv = std::make_shared(StructTypeConv(structOffset, type)); - - // Cache the StructTypeConv - encodingStructCache[structname] = structTypeConv; - - return structTypeConv; - } - case 'b': { - (*encoding)++; - char c = **encoding; - while ((c = **encoding) >= '0' && c <= '9') { - (*encoding)++; - } // skip bits - return uint64TypeConv; - } - case '^': - (*encoding)++; - TypeConv::Make(env, encoding); - return pointerTypeConv; - case '?': - // unknown type - return pointerTypeConv; - default: - std::cout << "getTypeInfo unknown encoding: " << *encoding << std::endl; - return pointerTypeConv; - } -} - -std::shared_ptr TypeConv::Make(napi_env env, MDMetadataReader* reader, - MDSectionOffset* offset, uint8_t opaquePointers) { - auto kind = reader->getTypeKind(*offset); - bool next = (MDTypeFlag)kind & mdTypeFlagNext; - kind = (MDTypeKind)((kind & ~mdTypeFlagNext) & ~mdTypeFlagVariadic); - *offset += sizeof(MDTypeKind); - - switch (kind) { - case mdTypeChar: { - return scharTypeConv; - } - - case mdTypeSInt: { - return sint32TypeConv; - } - - case mdTypeSShort: { - return sint16TypeConv; - } - - case mdTypeSLong: - case mdTypeSInt64: { - return sint64TypeConv; - } - - case mdTypeUInt8: { - return uint8TypeConv; - } - - case mdTypeUChar: { - return ucharTypeConv; - } - - case mdTypeUInt: { - return uint32TypeConv; - } - - case mdTypeUShort: { - return uint16TypeConv; - } - - case mdTypeUnichar: { - return unicharTypeConv; - } - - case mdTypeULong: - case mdTypeUInt64: { - return uint64TypeConv; - } - - case mdTypeFloat: { - return float32TypeConv; - } - - case mdTypeF16: { - return float16TypeConv; - } - - case mdTypeDouble: { - return float64TypeConv; - } - - case mdTypeBool: { - return boolTypeConv; - } - - case mdTypeVoid: { - return voidTypeConv; - } - - case mdTypeString: { - return stringTypeConv; - } - - case mdTypeAnyObject: { - return objcObjectTypeConv; - } - - case mdTypeInstanceObject: { - return objcInstanceObjectTypeConv; - } - - case mdTypeClassObject: { - auto classOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool next = (classOffset & mdSectionOffsetNext) != 0; - classOffset &= ~mdSectionOffsetNext; - if (classOffset == MD_SECTION_OFFSET_NULL) { - classOffset = 0; - } else { - classOffset += reader->classesOffset; - } - std::vector protocolOffsets; - while (next) { - auto protocolOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & mdSectionOffsetNext) != 0; - protocolOffset &= ~mdSectionOffsetNext; - if (protocolOffset == MD_SECTION_OFFSET_NULL) { - protocolOffset = 0; - } else { - protocolOffset += reader->protocolsOffset; - protocolOffsets.push_back(protocolOffset); - } - } - return std::make_shared(classOffset, protocolOffsets); - } - - case mdTypeProtocolObject: { - std::vector protocolOffsets; - bool next = true; - while (next) { - auto protocolOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & mdSectionOffsetNext) != 0; - protocolOffset &= ~mdSectionOffsetNext; - if (protocolOffset == MD_SECTION_OFFSET_NULL) { - protocolOffset = 0; - } else { - protocolOffset += reader->protocolsOffset; - protocolOffsets.push_back(protocolOffset); - } - } - return std::make_shared(protocolOffsets); - } - - case mdTypeNSStringObject: { - return objcNSStringObjectTypeConv; - } - - case mdTypeNSMutableStringObject: { - return objcNSMutableStringObjectTypeConv; - } - - case mdTypeClass: { - return objcClassTypeConv; - } - - case mdTypeSelector: { - return selectorTypeConv; - } - - case mdTypeArray: { - auto arraySize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - auto elementType = TypeConv::Make(env, reader, offset); - return std::make_shared(ArrayTypeConv(arraySize, elementType)); - } - - case mdTypeStruct: { - auto structOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - auto isUnion = (structOffset & mdSectionOffsetNext) != 0; - structOffset &= ~mdSectionOffsetNext; - if (structOffset == MD_SECTION_OFFSET_NULL) { - return pointerTypeConv; - } - structOffset += isUnion ? reader->unionsOffset : reader->structsOffset; - auto structName = reader->getString(structOffset); - - // Check if we already have a cached StructTypeConv for this struct - auto cacheIt = structTypeCache.find(structOffset); - if (cacheIt != structTypeCache.end()) { - return cacheIt->second; - } - - // Check if we're currently processing this struct (recursion detection) - bool isRecursive = processingStructs.find(structOffset) != processingStructs.end(); - - ffi_type* type = nullptr; - if (opaquePointers != 2 && !isRecursive) { - type = typeFromStruct(env, reader, structOffset, isUnion); - } - - auto structTypeConv = std::make_shared(structOffset, type); - - // Cache the StructTypeConv to handle recursion and avoid duplicates - structTypeCache[structOffset] = structTypeConv; - - return structTypeConv; - } - - case mdTypePointer: { - auto pointeeType = TypeConv::Make(env, reader, offset, opaquePointers == 1 ? 2 : 0); - return std::make_shared(pointeeType); - } - - case mdTypeOpaquePointer: { - return pointerTypeConv; - } - - case mdTypeVector: { - auto vectorSize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); - return std::make_shared(kind, vectorSize, elementType); - } - - case mdTypeBlock: { - auto blockSignature = reader->getOffset(*offset) + reader->signaturesOffset; - *offset += sizeof(MDSectionOffset); - return std::make_shared(blockSignature); - } - - case mdTypeFunctionPointer: { - auto blockSignature = reader->getOffset(*offset) + reader->signaturesOffset; - *offset += sizeof(MDSectionOffset); - return std::make_shared(blockSignature); - } - - case mdTypeUInt128: { - return uint128TypeConv; - } - - case mdTypeExtVector: { - auto vectorSize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); - return std::make_shared(kind, vectorSize, elementType); - } - - case mdTypeComplex: { - auto vectorSize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); - return std::make_shared(kind, vectorSize, elementType); - } - - default: - return pointerTypeConv; - } -} - -namespace { - -bool tryFastConvertStringToNSString(napi_env env, napi_value value, id* out, bool mutableString) { - if (out == nullptr) { - return false; - } - - if (mutableString) { - constexpr size_t kStackUtf16Capacity = 128; - char16_t utf16Stack[kStackUtf16Capacity]; - char16_t* utf16Buffer = utf16Stack; - size_t utf16Capacity = kStackUtf16Capacity; - size_t utf16Length = 0; - if (napi_get_value_string_utf16(env, value, utf16Buffer, utf16Capacity, &utf16Length) != - napi_ok) { - return false; - } - - std::vector utf16Heap; - if (utf16Length + 1 >= utf16Capacity) { - if (napi_get_value_string_utf16(env, value, nullptr, 0, &utf16Length) != napi_ok) { - return false; - } - utf16Heap.resize(utf16Length + 1, 0); - utf16Buffer = utf16Heap.data(); - utf16Capacity = utf16Heap.size(); - if (napi_get_value_string_utf16(env, value, utf16Buffer, utf16Capacity, &utf16Length) != - napi_ok) { - return false; - } - } - - *out = [[NSMutableString alloc] initWithCharacters:reinterpret_cast(utf16Buffer) - length:utf16Length]; - return true; - } - - constexpr size_t kStackUtf8Capacity = 256; - char utf8Stack[kStackUtf8Capacity]; - char* utf8Buffer = utf8Stack; - size_t utf8Capacity = kStackUtf8Capacity; - size_t utf8Length = 0; - if (napi_get_value_string_utf8(env, value, utf8Buffer, utf8Capacity, &utf8Length) != napi_ok) { - return false; - } - - std::vector utf8Heap; - if (utf8Length + 1 >= utf8Capacity) { - if (napi_get_value_string_utf8(env, value, nullptr, 0, &utf8Length) != napi_ok) { - return false; - } - utf8Heap.resize(utf8Length + 1, '\0'); - utf8Buffer = utf8Heap.data(); - utf8Capacity = utf8Heap.size(); - if (napi_get_value_string_utf8(env, value, utf8Buffer, utf8Capacity, &utf8Length) != napi_ok) { - return false; - } - } - - id stringValue = [[[NSString alloc] initWithBytes:utf8Buffer - length:utf8Length - encoding:NSUTF8StringEncoding] autorelease]; - *out = stringValue != nil ? stringValue : [NSString string]; - return true; -} - -bool tryFastConvertObjCObjectValue(napi_env env, napi_value value, napi_valuetype valueType, - MDTypeKind kind, id* out) { - if (out == nullptr) { - return false; - } - - switch (valueType) { - case napi_null: - case napi_undefined: - *out = nil; - return true; - - case napi_external: { - void* external = nullptr; - if (napi_get_value_external(env, value, &external) != napi_ok) { - return false; - } - *out = static_cast(external); - return true; - } - - case napi_number: { - double numericValue = 0; - if (napi_get_value_double(env, value, &numericValue) != napi_ok) { - return false; - } - *out = [NSNumber numberWithDouble:numericValue]; - return true; - } - - case napi_boolean: { - bool boolValue = false; - if (napi_get_value_bool(env, value, &boolValue) != napi_ok) { - return false; - } - *out = [NSNumber numberWithBool:boolValue]; - return true; - } - - case napi_bigint: { - int64_t bigintValue = 0; - bool lossless = false; - if (napi_get_value_bigint_int64(env, value, &bigintValue, &lossless) != napi_ok) { - return false; - } - *out = [NSNumber numberWithLongLong:bigintValue]; - return true; - } - - case napi_string: - return tryFastConvertStringToNSString(env, value, out, kind == mdTypeNSMutableStringObject); - - case napi_object: - case napi_function: { - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto cacheRoundTrip = [&](id nativeObj) { - if (nativeObj == nil || bridgeState == nullptr || !bridgeState->hasRoundTripCacheFrame()) { - return; - } - - bridgeState->cacheRoundTripObject(env, nativeObj, value); - }; - - if (valueType == napi_object) { - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - void* pointerData = ptr != nullptr ? ptr->data : nullptr; - if (id cachedObject = resolveCachedHandleObject(env, pointerData); cachedObject != nil) { - *out = cachedObject; - return true; - } - *out = (id)pointerData; - return true; - } - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - void* referenceData = ref != nullptr ? ref->data : nullptr; - if (id cachedObject = resolveCachedHandleObject(env, referenceData); - cachedObject != nil) { - *out = cachedObject; - return true; - } - *out = (id)referenceData; - return true; - } - } - - if (bridgeState != nullptr) { - id bridgedType = nil; - if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && - bridgedType != nil) { - *out = bridgedType; - return true; - } - } - - void* wrapped = nullptr; - if (napi_unwrap(env, value, &wrapped) == napi_ok) { - if (valueType == napi_function || valueType == napi_object) { - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && wrapped != nullptr) { - for (const auto& entry : bridgeState->classes) { - auto bridgedClass = entry.second; - if (bridgedClass == wrapped) { - *out = (id)bridgedClass->nativeClass; - return true; - } - } - - for (const auto& entry : bridgeState->protocols) { - auto bridgedProtocol = entry.second; - if (bridgedProtocol == wrapped) { - Protocol* runtimeProtocol = objc_getProtocol(bridgedProtocol->name.c_str()); - if (runtimeProtocol == nil) { - std::string baseName; - if (stripProtocolSuffix(bridgedProtocol->name.c_str(), &baseName)) { - runtimeProtocol = objc_getProtocol(baseName.c_str()); - } - } - if (runtimeProtocol != nil) { - *out = (id)runtimeProtocol; - return true; - } - } - } - } - } - - *out = (id)wrapped; - cacheRoundTrip(*out); - return true; - } - - bool isTypedArray = false; - if (napi_is_typedarray(env, value, &isTypedArray) == napi_ok && isTypedArray) { - *out = createNSDataWrapper(env, value, bridgeState); - if (*out != nil) { - cacheRoundTrip(*out); - return true; - } - return false; - } - - bool isArrayBuffer = false; - if (napi_is_arraybuffer(env, value, &isArrayBuffer) == napi_ok && isArrayBuffer) { - *out = createNSDataWrapper(env, value, bridgeState); - if (*out != nil) { - cacheRoundTrip(*out); - return true; - } - return false; - } - - bool isDataView = false; - if (napi_is_dataview(env, value, &isDataView) == napi_ok && isDataView) { - *out = createNSDataWrapper(env, value, bridgeState); - if (*out != nil) { - cacheRoundTrip(*out); - return true; - } - return false; - } - - return false; - } - - default: - return false; - } -} - -} // namespace - -bool TryFastConvertNapiArgument(napi_env env, MDTypeKind kind, napi_value value, void* result) { - if (result == nullptr || value == nullptr) { - return false; - } - - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok) { - return false; - } - - switch (kind) { - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return tryFastConvertObjCObjectValue(env, value, valueType, static_cast(kind), - reinterpret_cast(result)); - - case mdTypeSelector: { - SEL* selector = reinterpret_cast(result); - switch (valueType) { - case napi_null: - case napi_undefined: - *selector = nullptr; - return true; - - case napi_string: { - constexpr size_t kStackSelectorCapacity = 128; - char selectorStack[kStackSelectorCapacity]; - size_t selectorLength = 0; - if (napi_get_value_string_utf8(env, value, selectorStack, kStackSelectorCapacity, - &selectorLength) != napi_ok) { - return false; - } - const char* selectorName = selectorStack; - std::vector selectorHeap; - if (selectorLength + 1 >= kStackSelectorCapacity) { - if (napi_get_value_string_utf8(env, value, nullptr, 0, &selectorLength) != napi_ok) { - return false; - } - selectorHeap.resize(selectorLength + 1, '\0'); - if (napi_get_value_string_utf8(env, value, selectorHeap.data(), selectorHeap.size(), - &selectorLength) != napi_ok) { - return false; - } - selectorName = selectorHeap.data(); - } - *selector = sel_registerName(selectorName); - return true; - } - - default: - return false; - } - } - - default: - return false; - } -} - -bool TryFastConvertNapiUInt16Argument(napi_env env, napi_value value, uint16_t* result) { - if (result == nullptr || value == nullptr) { - return false; - } - - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok) { - return false; - } - - if (valueType == napi_string) { - size_t strLen = 0; - if (napi_get_value_string_utf16(env, value, nullptr, 0, &strLen) != napi_ok) { - return false; - } - if (strLen != 1) { - napi_throw_type_error(env, nullptr, "Expected a single-character string."); - *result = 0; - return false; - } - - char16_t chars[2] = {0, 0}; - if (napi_get_value_string_utf16(env, value, chars, 2, &strLen) != napi_ok) { - return false; - } - - *result = static_cast(chars[0]); - return true; - } - - napi_value coerced = value; - if (napi_coerce_to_number(env, value, &coerced) != napi_ok) { - return false; - } - - uint32_t converted = 0; - if (napi_get_value_uint32(env, coerced, &converted) != napi_ok) { - return false; - } - - *result = static_cast(converted); - return true; -} - -// Cleanup function to clear thread-local caches -void clearStructTypeCaches() { - processingStructs.clear(); - processingEncodingStructs.clear(); - forwardDeclaredStructs.clear(); - forwardDeclaredEncodingStructs.clear(); - structTypeCache.clear(); - encodingStructCache.clear(); -} - -} // namespace nativescript diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsi.h b/NativeScript/ffi/objc/hermes/NativeApiJsi.h new file mode 100644 index 000000000..f2bd4d1f1 --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsi.h @@ -0,0 +1,62 @@ +#ifndef NATIVE_API_JSI_H +#define NATIVE_API_JSI_H + +#include + +#include "ffi/objc/shared/NativeApiBackendConfig.h" + +namespace nativescript { + +using NativeApiJsiScheduler = NativeApiBackendScheduler; +using NativeApiJsiConfig = NativeApiBackendConfig; + +facebook::jsi::Object CreateNativeApiJSI( + facebook::jsi::Runtime& runtime, + const NativeApiJsiConfig& config = NativeApiJsiConfig{}); + +void InstallNativeApiJSI( + facebook::jsi::Runtime& runtime, + const NativeApiJsiConfig& config = NativeApiJsiConfig{}); + +// M1 (ARCHITECTURE.md §4.3): the two ObjC<->JSI helpers that make the +// by-reference Fabric handoff possible; "the Fabric boundary must hand JS +// a real bridge-wrapped object, not a string handle" +// (CLEANUP_AND_REARCHITECTURE_PLAN.md §2.0). Both wrap the SAME +// NativeApiObjectHostObject mechanism every other native object crossing in +// this bridge already uses (Object.mm/Class.mm); so a wrapped value +// round-trips through the identical `nativeValue(...)`-style method dispatch +// as any other bridged object, not a bespoke RPC. +// +// `object`/the return value are `void*`-typed ObjC `id`s, kept untyped here +// (not `id`) so this header stays includable from a plain C++ translation +// unit that never imports Objective-C (e.g. runtime/apple/Runtime.cpp, +// which includes this header under `#ifdef TARGET_ENGINE_HERMES` without +// itself being compiled as Objective-C++); the same convention +// NativeApiBackendConfig.h already follows. +// +// Only implemented for the Hermes backend (this header/its .mm are +// Hermes-only, ffi/objc/hermes/); RN only ever uses Hermes, so this does not +// touch the V8/JSC/QuickJS engine backends or their standalone builds. +// +// If `ownsObject` is true, the wrapper takes over a +1 retain already held +// by the caller (matching `makeNativeObjectValue`'s `ownsObject` semantics +// used everywhere else in the bridge); if false, the wrapper retains its own +// reference and the caller's reference is untouched. +facebook::jsi::Value NativeScriptWrapNativeObject(facebook::jsi::Runtime& runtime, + void* object, + bool ownsObject = false); + +// Reverse direction: given a JSI value produced by NativeScriptWrapNativeObject +// (or any other native-object-wrapping mechanism the bridge already uses -- +// NativeApiObjectHostObject, NativeApiPointerHostObject, +// NativeApiReferenceHostObject), returns the underlying native pointer, or +// nullptr if `value` does not wrap a live native object. +void* NativeScriptUnwrapNativeObject(facebook::jsi::Runtime& runtime, + const facebook::jsi::Value& value); + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApiJSI( + facebook::jsi::Runtime* runtime, const char* metadataPath); + +#endif // NATIVE_API_JSI_H diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsi.mm b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm new file mode 100644 index 000000000..d446956b8 --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm @@ -0,0 +1,305 @@ +#include "NativeApiJsi.h" + +#ifdef TARGET_ENGINE_HERMES + +#import +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Metadata.h" +#include "MetadataReader.h" +#include "ffi.h" +#include "NativeApiJsiSignatureDispatch.h" + +@protocol NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +namespace nativescript { +namespace { + +using facebook::jsi::Array; +using facebook::jsi::ArrayBuffer; +using facebook::jsi::BigInt; +using facebook::jsi::Function; +using facebook::jsi::HostObject; +using facebook::jsi::MutableBuffer; +using NativeApiNativeState = facebook::jsi::NativeState; +using facebook::jsi::Object; +using facebook::jsi::PropNameID; +using facebook::jsi::Runtime; +using facebook::jsi::String; +using facebook::jsi::StringBuffer; +using facebook::jsi::Value; +using facebook::jsi::JSError; + +using NativeApiConfig = NativeApiJsiConfig; +using NativeApiScheduler = NativeApiJsiScheduler; +using metagen::MDMemberFlag; +using metagen::MDMetadataReader; +using metagen::MDSectionOffset; +using metagen::MDTypeKind; + +void SetNativeApiObjectPrototype(Runtime& runtime, Object& object, + const Object& prototype) { + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function setPrototypeOf = + objectConstructor.getPropertyAsFunction(runtime, "setPrototypeOf"); + setPrototypeOf.call(runtime, Value(runtime, object), Value(runtime, prototype)); +} + +// clang-format off +#define NATIVESCRIPT_NATIVE_API_RUNTIME_NAME "jsi" +#define NATIVESCRIPT_NATIVE_API_BACKEND_NAME "hermes" +#define NATIVESCRIPT_NATIVE_API_HOST_SET_VOID 1 +#define NATIVESCRIPT_NATIVE_API_HOST_EXPLICIT_OVERRIDE 1 +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION 1 +#include "../shared/bridge/ObjCBridge.mm" +#include "../shared/bridge/HostObjects.mm" +#include "../shared/bridge/Callbacks.mm" +#include "../shared/bridge/TypeConv.mm" +#include "../shared/bridge/Invocation.mm" +#include "../shared/bridge/ClassBuilder.mm" +#include "../shared/bridge/HostObject.mm" +// clang-format on + +#include "NativeApiJsiGsd.mm" + +#include "../shared/bridge/SelectorGroupCall.h" + + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId) { + return reinterpret_cast(lookupObjCGsdInvoker(dispatchId)); +} + +bool tryCallGeneratedEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result) { + if (result == nullptr || receiver == nil || + !prepared.gsdEngineCallable || dispatchSuperClass != Nil || + count != prepared.gsdEngineArgumentCount) { + return false; + } + + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, args, + prepared.signature.returnType}; + if (!invoker(ctx)) { + return false; + } + *result = std::move(ctx.result); + return true; +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState) { + NativeApiSelectorGroupState state( + std::move(bridge), lookupClass, receiverIsClass, std::move(selectors), + std::move(preparedInvocations), std::move(boundReceiver), + std::move(boundReceiverState)); + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__nativeSelectorGroup"), 0, + [state = std::move(state)]( + Runtime& runtime, const Value& thisValue, const Value* args, + size_t count) mutable -> Value { + NativeApiRoundTripCacheFrameGuard roundTripFrame(state.bridge); + std::shared_ptr receiverHostObject; + auto resolveReceiverHost = [&]() { + if (receiverHostObject) { + return receiverHostObject; + } + if (state.boundReceiverState != nullptr) { + receiverHostObject = state.boundReceiver.lock(); + if (receiverHostObject) { + return receiverHostObject; + } + // The bound receiver's wrapper has already been torn down (its + // owning JS proxy was collected) since this selector-group + // function was minted and cached as a native-object expando + // (Object.mm's `bridge_->setObjectExpando(..., methodFunction)`). + // The expando itself is keyed by the native pointer and survives + // wrapper churn, so a LATER crossing that re-wraps the SAME + // native object in a fresh `NativeApiObjectHostObject` (this + // runtime mints a new wrapper per crossing) finds the stale + // cached function still bound to the dead original -- every call + // through it then resolves a nil receiver and throws "Objective-C + // selector requires a native receiver" even though the method is + // being invoked on a perfectly live object right now. Fall + // through to `thisValue` exactly like the unbound path below: + // this IS a method call (`receiver.method(...)`), so `thisValue` + // is always the correct, live receiver for this invocation. + } + if (thisValue.isObject()) { + Object receiverObject = thisValue.asObject(runtime); + if (receiverObject.isHostObject( + runtime)) { + receiverHostObject = + receiverObject.getHostObject( + runtime); + } + } + return receiverHostObject; + }; + auto call = resolveNativeApiSelectorGroupCall( + runtime, state, count, + [&]() -> id { + auto host = resolveReceiverHost(); + return host != nullptr ? host->object() : nil; + }, + resolveReceiverHost, + [](uint64_t dispatchId) { + return lookupObjCGsdInvoker(dispatchId); + }); + if (call.hasImmediateResult) { + return std::move(call.immediateResult); + } + + // GSD fast path: read jsi args directly, call objc_msgSend with a + // typed cast, produce the jsi return value , bypassing all generic + // marshalling. Only engages for plain calls (no super dispatch, init + // disown handling, implicit NSError-out argument, or appearance + // static selector — those need the generic path's proxy tagging). + if (call.prepared->gsdEngineCallable && call.dispatchClass == Nil && + count == call.prepared->gsdEngineArgumentCount && + !(!state.receiverIsClass && call.prepared->isInitMethod) && + call.gsdAllowed) { + auto invoker = + reinterpret_cast(call.prepared->engineInvoker); + GsdObjCContext ctx{runtime, state.bridge, call.receiver, + call.prepared->selector, args, + call.prepared->signature.returnType}; + if (invoker(ctx)) { + cachePreparedAppearanceProxySetterValue( + runtime, state.bridge, call.receiver, *call.prepared, args, + count); + return std::move(ctx.result); + } + } + + if (state.receiverIsClass) { + return callPreparedObjCSelector(runtime, state.bridge, call.receiver, + true, *call.prepared, args, count, + Nil); + } + if (!receiverHostObject) { + receiverHostObject = resolveReceiverHost(); + } + if (!receiverHostObject) { + throw JSError(runtime, + "Objective-C selector requires a native receiver."); + } + Value result = receiverHostObject->callPreparedObjectSelector( + runtime, *call.prepared, args, count, call.dispatchClass); + if (!state.receiverIsClass && call.prepared->isInitMethod) { + if (auto preserved = preservedNativeApiInitializerSelfReturn( + runtime, state.bridge, call.receiver, result, thisValue)) { + return std::move(*preserved); + } + } + return result; + }); +} + +} // namespace + +#include "../shared/bridge/Install.mm" + +Object CreateNativeApiJSI(Runtime& runtime, const NativeApiJsiConfig& config) { + return CreateNativeApi(runtime, config); +} + +void InstallNativeApiJSI(Runtime& runtime, const NativeApiJsiConfig& config) { + InstallNativeApi(runtime, config); +} + +namespace { +// The bridge for a given runtime is reached the same way every other +// caller finds it: the `NativeApiHostObject` stashed under the well-known +// global name every InstallNativeApi call uses by default +// (NativeApiBackendConfig::globalName, "__nativeScriptNativeApi") -- +// identical to how `nativeApiInstalled()` in NativeScriptNativeApiModule.mm +// already probes for this same global. +std::shared_ptr NativeScriptBridgeForRuntime(Runtime& runtime) { + Value apiValue = runtime.global().getProperty(runtime, "__nativeScriptNativeApi"); + if (!apiValue.isObject()) { + return nullptr; + } + Object apiObject = apiValue.asObject(runtime); + if (!apiObject.isHostObject(runtime)) { + return nullptr; + } + return apiObject.getHostObject(runtime)->bridge(); +} +} // namespace + +Value NativeScriptWrapNativeObject(Runtime& runtime, void* object, bool ownsObject) { + if (object == nullptr) { + return Value::null(); + } + auto bridge = NativeScriptBridgeForRuntime(runtime); + if (bridge == nullptr) { + return Value::null(); + } + // __bridge: a plain ownership-neutral cast, valid identically whether this + // translation unit is compiled ARC or MRC (unlike a raw C-style cast, + // which ARC rejects for void* <-> id without an explicit bridge + // annotation). + return makeNativeObjectValue(runtime, bridge, (__bridge id)object, ownsObject); +} + +void* NativeScriptUnwrapNativeObject(Runtime& runtime, const Value& value) { + void* pointer = nullptr; + if (readPointerLikeValue(runtime, value, &pointer)) { + return pointer; + } + return nullptr; +} + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApiJSI(facebook::jsi::Runtime* runtime, + const char* metadataPath) { + if (runtime == nullptr) { + return; + } + nativescript::NativeApiJsiConfig config; + config.metadataPath = metadataPath; + nativescript::InstallNativeApiJSI(*runtime, config); +} + +#endif // TARGET_ENGINE_HERMES diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsiGsd.mm b/NativeScript/ffi/objc/hermes/NativeApiJsiGsd.mm new file mode 100644 index 000000000..2b2c5d53a --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsiGsd.mm @@ -0,0 +1,165 @@ +// --- GSD (Generated Signature Dispatch) for Hermes/JSI --- +// GsdObjCContext is the engine-neutral interface the generated invokers use: +// it reads jsi::Value arguments and writes the jsi::Value return value using +// the shared engine-neutral conversion helpers (which already operate on the +// jsi value type). Readers require the fast representation; anything else +// makes a reader return false so the invoker falls back to the generic path. +struct GsdObjCContext; +using ObjCGsdInvoker = bool (*)(GsdObjCContext&); +struct ObjCGsdDispatchEntry { + uint64_t dispatchId; + ObjCGsdInvoker invoker; +}; + +struct GsdObjCContext { + Runtime& runtime; + const std::shared_ptr& bridge; + id self; + SEL selector; + const Value* arguments; + const NativeApiType& returnType; + Value result = Value::undefined(); + + template + void invokeNative(Invocation&& invocation) { + performGeneratedObjCInvocation(runtime, bridge, [&]() { invocation(); }); + } + + bool readNumber(size_t i, double* out) { + const Value& v = arguments[i]; + if (!v.isNumber()) return false; + *out = v.asNumber(); + return true; + } + bool readBool(size_t i, uint8_t* out) { + const Value& v = arguments[i]; + if (!v.isBool()) return false; + *out = v.getBool() ? 1 : 0; + return true; + } + template + bool readSigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + template + bool readUnsigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readFloat(size_t i, float* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readDouble(size_t i, double* out) { return readNumber(i, out); } + bool readSelector(size_t i, SEL* out) { + return readFastEngineSelectorArgument(runtime, arguments[i], out); + } + bool readClass(size_t i, Class* out) { + Class cls = classFromEngineValue(runtime, arguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + bool readObject(size_t i, id* out) { + const Value& v = arguments[i]; + if (v.isNull() || v.isUndefined()) { + *out = nil; + return true; + } + if (!v.isObject()) return false; + Object o = v.getObject(runtime); + if (o.isHostObject(runtime)) { + *out = o.getHostObject(runtime)->object(); + return true; + } + if (o.isHostObject(runtime)) { + *out = static_cast( + o.getHostObject(runtime)->nativeClass()); + return true; + } + Class cls = classFromEngineValue(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + if (o.isHostObject(runtime)) { + *out = static_cast( + o.getHostObject(runtime) + ->nativeProtocol()); + return true; + } + return false; + } + + void setVoid() { result = Value::undefined(); } + void setBool(bool v) { result = Value(v); } + void setInt32(int32_t v) { result = Value(static_cast(v)); } + void setUInt32(uint32_t v) { result = Value(static_cast(v)); } + void setUInt16(uint16_t v) { + result = Value(static_cast(v)); + } + void setInt64(int64_t v) { result = signedInteger64ToEngineValue(runtime, v); } + void setUInt64(uint64_t v) { + result = unsignedInteger64ToEngineValue(runtime, v); + } + void setDouble(double v) { result = Value(v); } + void setSelector(SEL v) { + const char* name = v != nullptr ? sel_getName(v) : nullptr; + result = name != nullptr ? makeString(runtime, name) : Value::null(); + } + void setClass(Class v) { + if (v == nil) { + result = Value::null(); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + } + void setObject(id obj) { + result = convertNativeReturnValue(runtime, bridge, returnType, &obj); + } +}; + +// Close the anonymous namespace so the generated dispatch table lives in +// namespace nativescript; GsdObjCContext/ObjCGsdDispatchEntry stay reachable +// via the unnamed namespace's implicit using-directive. +} // namespace (temporary close for GSD .inc) + +#if defined(__has_include) +#if __has_include("../shared/GeneratedGsdSignatureDispatch.inc") +#include "../shared/GeneratedGsdSignatureDispatch.inc" +#endif +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +inline constexpr ObjCGsdDispatchEntry kGeneratedObjCGsdDispatchEntries[] = { + {0, nullptr}}; +#endif + +ObjCGsdInvoker lookupObjCGsdInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCGsdDispatchEntries, dispatchId); +} + +namespace { // reopen anonymous namespace + +// --- End GSD --- diff --git a/NativeScript/ffi/hermes/jsi/NativeApiJsiReactNative.h b/NativeScript/ffi/objc/hermes/NativeApiJsiReactNative.h similarity index 92% rename from NativeScript/ffi/hermes/jsi/NativeApiJsiReactNative.h rename to NativeScript/ffi/objc/hermes/NativeApiJsiReactNative.h index e7619bb0a..66fb752cc 100644 --- a/NativeScript/ffi/hermes/jsi/NativeApiJsiReactNative.h +++ b/NativeScript/ffi/objc/hermes/NativeApiJsiReactNative.h @@ -62,7 +62,10 @@ inline NativeApiJsiConfig MakeReactNativeNativeApiJsiConfig( config.metadataPath = metadataPath; config.metadataPtr = metadataPtr; config.globalName = globalName; - config.installGlobalSymbols = true; + // RN launch cost: don't eagerly install the aggregate global surface or + // realize every class/protocol runtime pointer at symbol-index time. + config.installGlobalSymbols = false; + config.indexRuntimePointers = false; config.invokeCallbacksOnNativeCallerThread = true; config.scheduler = std::make_shared( std::move(jsInvoker), std::move(uiInvoker)); diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsiSignatureDispatch.h b/NativeScript/ffi/objc/hermes/NativeApiJsiSignatureDispatch.h new file mode 100644 index 000000000..dc3a95969 --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsiSignatureDispatch.h @@ -0,0 +1,14 @@ +#ifndef NS_FFI_HERMES_NATIVE_API_JSI_SIGNATURE_DISPATCH_H +#define NS_FFI_HERMES_NATIVE_API_JSI_SIGNATURE_DISPATCH_H + +#include "ffi/objc/shared/SignatureDispatchCore.h" + +#if defined(__has_include) +#if __has_include("../shared/GeneratedSignatureDispatch.inc") +#include "../shared/GeneratedSignatureDispatch.inc" +#endif +#endif + +#include "ffi/objc/shared/PreparedSignatureDispatch.h" + +#endif // NS_FFI_HERMES_NATIVE_API_JSI_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/objc/hermes/README.md b/NativeScript/ffi/objc/hermes/README.md new file mode 100644 index 000000000..35c04d97a --- /dev/null +++ b/NativeScript/ffi/objc/hermes/README.md @@ -0,0 +1,23 @@ +# Native API Hermes JSI backend + +This directory owns the Hermes-facing Native API entrypoint: + +- `NativeApiJsi.h` exposes the public JSI install/create API. +- `NativeApiJsi.mm` binds Hermes JSI types to the Hermes-owned bridge + implementation files in this directory. +- `NativeApiJsiReactNative.h` adapts React Native `CallInvoker`s to the JSI + scheduler config used by the TurboModule. +- `NativeApiJsiSignatureDispatch.h` wires Hermes generated signature dispatch + tables into native invocation. + +Hermes is the only backend that exposes the real `facebook::jsi` API. V8, JSC, +and QuickJS own their bridge implementations in their respective engine +directories. + +React Native integrations should include `NativeApiJsiReactNative.h` from a +TurboModule implementation and pass the module's JS/UI `CallInvoker`s: + +```cpp +nativescript::InstallReactNativeNativeApiJSI( + runtime, jsInvoker, uiInvoker, metadataPath, metadataPtr); +``` diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSC.h b/NativeScript/ffi/objc/jsc/NativeApiJSC.h new file mode 100644 index 000000000..2199dd502 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSC.h @@ -0,0 +1,21 @@ +#ifndef NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H +#define NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H + +#include "ffi/objc/shared/NativeApiBackendConfig.h" +#include + +namespace nativescript { + +using NativeApiScheduler = NativeApiBackendScheduler; +using NativeApiConfig = NativeApiBackendConfig; + +void InstallNativeApi(JSGlobalContextRef context, + const NativeApiConfig& config = NativeApiConfig{}); +void CleanupNativeApi(JSGlobalContextRef context); + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(JSGlobalContextRef context, + const char* metadataPath); + +#endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSC.mm b/NativeScript/ffi/objc/jsc/NativeApiJSC.mm new file mode 100644 index 000000000..bd20cd8c6 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSC.mm @@ -0,0 +1,77 @@ +#include "NativeApiJSC.h" + +#ifdef TARGET_ENGINE_JSC + +#include "NativeApiJSCRuntime.h" +#include "SignatureDispatch.h" + +namespace nativescript { + +namespace { + +using nativescript::engine::Array; +using nativescript::engine::ArrayBuffer; +using nativescript::engine::BigInt; +using nativescript::engine::Function; +using nativescript::engine::HostObject; +using nativescript::engine::MutableBuffer; +using nativescript::engine::Object; +using nativescript::engine::PropNameID; +using nativescript::engine::Runtime; +using nativescript::engine::String; +using nativescript::engine::StringBuffer; +using nativescript::engine::Value; +using nativescript::engine::JSError; +using metagen::MDMemberFlag; +using metagen::MDMetadataReader; +using metagen::MDSectionOffset; +using metagen::MDTypeKind; + +// clang-format off +#define NATIVESCRIPT_NATIVE_API_BACKEND_NAME "jsc" +#include "../shared/bridge/ObjCBridge.mm" +// clang-format on +#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION 1 + +#include "NativeApiJSCRuntimeSupport.mm" + +// clang-format off +#include "../shared/bridge/HostObjects.mm" +#include "../shared/bridge/Callbacks.mm" +#include "../shared/bridge/TypeConv.mm" +#include "../shared/bridge/Invocation.mm" +#include "../shared/bridge/ClassBuilder.mm" +#include "../shared/bridge/HostObject.mm" +// clang-format on + +#include "NativeApiJSCSelectorGroups.mm" + +} // namespace + +#include "../shared/bridge/Install.mm" + +void InstallNativeApi(JSGlobalContextRef context, const NativeApiConfig& config) { + if (context == nullptr) { + return; + } + Runtime runtime(context); + InstallNativeApi(runtime, config); +} + +void CleanupNativeApi(JSGlobalContextRef context) { + if (context != nullptr) { + engine::jscengine::releaseStateForContext(context); + } +} + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(JSGlobalContextRef context, + const char* metadataPath) { + nativescript::NativeApiConfig config; + config.metadataPath = metadataPath; + nativescript::InstallNativeApi(context, config); +} + +#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCGsd.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCGsd.mm new file mode 100644 index 000000000..13ee42c38 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCGsd.mm @@ -0,0 +1,305 @@ +// --- GSD (Generated Signature Dispatch) for JSC --- +// GsdObjCContext is the engine-neutral interface the generated invokers use: +// it reads JS arguments and writes the JS return value via the JSC API. The +// readers/setters mirror JSC's generic conversions exactly; any value not in +// the fast representation makes a reader return false so the invoker falls +// back to the fully correct generic path. Number readers require an actual +// JS number so coercion edge cases (numeric strings, single-char unichar +// arguments) defer to the generic path. +struct GsdObjCContext; +using ObjCGsdInvoker = bool (*)(GsdObjCContext&); +struct ObjCGsdDispatchEntry { + uint64_t dispatchId; + ObjCGsdInvoker invoker; +}; + +struct GsdObjCContext { + Runtime& runtime; + const std::shared_ptr& bridge; + id self; + SEL selector; + JSContextRef context; + const JSValueRef* arguments; + const NativeApiType& returnType; + JSValueRef result = nullptr; + const Value* valueArguments = nullptr; + bool materializeValueResult = false; + Value valueResult = Value::undefined(); + + template + void invokeNative(Invocation&& invocation) { + performGeneratedObjCInvocation(runtime, bridge, [&]() { invocation(); }); + } + + bool readNumber(size_t i, double* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (!v.isNumber()) return false; + *out = v.getNumber(); + return true; + } + JSValueRef v = arguments[i]; + if (!JSValueIsNumber(context, v)) return false; + JSValueRef exception = nullptr; + double converted = JSValueToNumber(context, v, &exception); + if (exception != nullptr) return false; + *out = converted; + return true; + } + bool readBool(size_t i, uint8_t* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (!v.isBool()) return false; + *out = v.getBool() ? 1 : 0; + return true; + } + JSValueRef v = arguments[i]; + if (!JSValueIsBoolean(context, v)) return false; + *out = JSValueToBoolean(context, v) ? 1 : 0; + return true; + } + template + bool readSigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + template + bool readUnsigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readFloat(size_t i, float* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readDouble(size_t i, double* out) { return readNumber(i, out); } + bool readSelector(size_t i, SEL* out) { + if (valueArguments != nullptr) { + return readFastEngineSelectorArgument(runtime, valueArguments[i], out); + } + return readJSCEngineSelectorArgument(runtime, arguments[i], out); + } + bool readClass(size_t i, Class* out) { + if (valueArguments != nullptr) { + Class cls = classFromEngineValue(runtime, valueArguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + if (auto* c = jscHostObjectRaw( + runtime, arguments[i])) { + *out = c->nativeClass(); + return true; + } + Class cls = jscNativeClassArgument(runtime, arguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + bool readObject(size_t i, id* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (v.isNull() || v.isUndefined()) { + *out = nil; + return true; + } + if (!v.isObject()) return false; + Object object = v.asObject(runtime); + if (object.isHostObject(runtime)) { + *out = object.getHostObject(runtime)->object(); + return true; + } + if (object.isHostObject(runtime)) { + *out = static_cast( + object.getHostObject(runtime)->nativeClass()); + return true; + } + Class cls = classFromEngineValue(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + if (object.isHostObject(runtime)) { + *out = static_cast( + object.getHostObject(runtime) + ->nativeProtocol()); + return true; + } + return false; + } + JSValueRef v = arguments[i]; + if (v == nullptr || JSValueIsNull(context, v) || + JSValueIsUndefined(context, v)) { + *out = nil; + return true; + } + if (auto* h = jscHostObjectRaw(runtime, v)) { + *out = h->object(); + return true; + } + if (auto* c = jscHostObjectRaw(runtime, v)) { + *out = static_cast(c->nativeClass()); + return true; + } + if (JSValueIsObject(context, v)) { + Class cls = jscNativeClassArgument(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + } + if (auto* p = jscHostObjectRaw(runtime, v)) { + *out = static_cast(p->nativeProtocol()); + return true; + } + return false; + } + + void setVoid() { + if (materializeValueResult) { + valueResult = Value::undefined(); + return; + } + result = JSValueMakeUndefined(context); + } + void setBool(bool v) { + if (materializeValueResult) { + valueResult = Value(v); + return; + } + result = JSValueMakeBoolean(context, v); + } + void setInt32(int32_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JSValueMakeNumber(context, v); + } + void setUInt32(uint32_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JSValueMakeNumber(context, v); + } + void setUInt16(uint16_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JSValueMakeNumber(context, v); + } + void setInt64(int64_t v) { + if (materializeValueResult) { + valueResult = signedInteger64ToEngineValue(runtime, v); + return; + } + result = jscInteger64Value(runtime, v); + } + void setUInt64(uint64_t v) { + if (materializeValueResult) { + valueResult = unsignedInteger64ToEngineValue(runtime, v); + return; + } + result = jscUnsignedInteger64Value(runtime, v); + } + void setDouble(double v) { + if (materializeValueResult) { + valueResult = Value(v); + return; + } + result = JSValueMakeNumber(context, v); + } + void setSelector(SEL v) { + const char* name = v != nullptr ? sel_getName(v) : nullptr; + if (materializeValueResult) { + valueResult = name != nullptr ? makeString(runtime, name) : Value::null(); + return; + } + if (name == nullptr) { + result = JSValueMakeNull(context); + return; + } + JSStringRef string = engine::jscengine::makeJSString(name); + result = JSValueMakeString(context, string); + JSStringRelease(string); + } + void setClass(Class v) { + if (materializeValueResult) { + if (v == nil) { + valueResult = Value::null(); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + valueResult = makeNativeClassValue(runtime, bridge, std::move(symbol)); + return; + } + if (v == nil) { + result = JSValueMakeNull(context); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value classValue = makeNativeClassValue(runtime, bridge, std::move(symbol)); + result = classValue.local(runtime); + } + void setObject(id obj) { + if (materializeValueResult) { + valueResult = convertNativeReturnValue(runtime, bridge, returnType, &obj); + return; + } + result = setJSCEngineObjectReturn(runtime, bridge, returnType, obj); + } +}; + +// Close the anonymous namespace so the generated dispatch table lives in +// namespace nativescript. GsdObjCContext/ObjCGsdDispatchEntry remain reachable +// via the unnamed namespace's implicit using-directive. +} // namespace (temporary close for GSD .inc) + +#if defined(__has_include) +#if __has_include("../shared/GeneratedGsdSignatureDispatch.inc") +#include "../shared/GeneratedGsdSignatureDispatch.inc" +#endif +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +inline constexpr ObjCGsdDispatchEntry kGeneratedObjCGsdDispatchEntries[] = { + {0, nullptr}}; +#endif + +ObjCGsdInvoker lookupObjCGsdInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCGsdDispatchEntries, dispatchId); +} + +namespace { // reopen anonymous namespace + +// --- End GSD --- diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCHostObjects.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCHostObjects.mm new file mode 100644 index 000000000..d839e855c --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCHostObjects.mm @@ -0,0 +1,293 @@ +#include "NativeApiJSCRuntime.h" +#include "../shared/NativeApiStackValueArray.h" + +#ifdef TARGET_ENGINE_JSC + +namespace nativescript { +class NativeApiObjectHostObject; +} + +namespace nativescript { +namespace engine { + +namespace jscengine { + +namespace { +std::mutex& runtimeStatesMutex() { + static auto* mutex = new std::mutex(); + return *mutex; +} + +std::unordered_map>& +runtimeStates() { + static auto* states = + new std::unordered_map>(); + return *states; +} +} // namespace + +std::shared_ptr stateForContext(JSGlobalContextRef context) { + std::lock_guard lock(runtimeStatesMutex()); + auto& states = runtimeStates(); + auto it = states.find(context); + if (it != states.end()) { + return it->second; + } + auto state = std::make_shared(context); + states[context] = state; + return state; +} + +void releaseStateForContext(JSGlobalContextRef context) { + std::shared_ptr state; + { + std::lock_guard lock(runtimeStatesMutex()); + auto& states = runtimeStates(); + auto it = states.find(context); + if (it == states.end()) { + return; + } + state = std::move(it->second); + states.erase(it); + } + state->cleanup(); +} + +JSClassRef hostClass(Runtime& runtime); +JSClassRef functionClass(Runtime& runtime); +void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function); + +bool isNativeInstancePrototypeBypassExcluded(JSStringRef propertyName) { + return JSStringIsEqualToUTF8CString(propertyName, "kind") || + JSStringIsEqualToUTF8CString(propertyName, "className") || + JSStringIsEqualToUTF8CString(propertyName, "nativeAddress") || + JSStringIsEqualToUTF8CString(propertyName, "class") || + JSStringIsEqualToUTF8CString(propertyName, "constructor") || + JSStringIsEqualToUTF8CString(propertyName, "super") || + JSStringIsEqualToUTF8CString(propertyName, "invoke") || + JSStringIsEqualToUTF8CString(propertyName, "send") || + JSStringIsEqualToUTF8CString(propertyName, "takeRetainedValue") || + JSStringIsEqualToUTF8CString(propertyName, "takeUnretainedValue") || + JSStringIsEqualToUTF8CString(propertyName, "toString"); +} + +bool shouldDeferToNativeInstancePrototype(JSContextRef context, + JSObjectRef object, + JSStringRef propertyName, + HostObjectHolder* holder) { + if (context == nullptr || object == nullptr || propertyName == nullptr || + holder == nullptr || + holder->typeToken != hostObjectTypeToken() || + isNativeInstancePrototypeBypassExcluded(propertyName)) { + return false; + } + + JSValueRef prototypeValue = JSObjectGetPrototype(context, object); + if (prototypeValue == nullptr || !JSValueIsObject(context, prototypeValue)) { + return false; + } + + JSValueRef exception = nullptr; + JSObjectRef prototypeObject = + JSValueToObject(context, prototypeValue, &exception); + if (exception != nullptr || prototypeObject == nullptr) { + return false; + } + + exception = nullptr; + bool found = JSObjectHasProperty(context, prototypeObject, propertyName); + return exception == nullptr && found; +} + +JSValueRef hostGetProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, + JSValueRef* exception) { + auto* holder = static_cast(JSObjectGetPrivate(object)); + if (holder == nullptr || holder->hostObject == nullptr) { + return nullptr; + } + if (shouldDeferToNativeInstancePrototype(context, object, propertyName, + holder)) { + return nullptr; + } + Runtime runtime(holder->state); + try { + Value result = holder->hostObject->get(runtime, PropNameID(stringToUtf8(propertyName))); + return result.isUndefined() ? nullptr : result.local(runtime); + } catch (const std::exception& error) { + setException(context, exception, error); + return JSValueMakeUndefined(context); + } +} + +bool hostSetProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, + JSValueRef value, JSValueRef* exception) { + auto* holder = static_cast(JSObjectGetPrivate(object)); + if (holder == nullptr || holder->hostObject == nullptr) { + return false; + } + Runtime runtime(holder->state); + try { + return holder->hostObject->set(runtime, PropNameID(stringToUtf8(propertyName)), + Value::borrowed(runtime, value)); + } catch (const std::exception& error) { + setException(context, exception, error); + return true; + } +} + +void hostGetPropertyNames(JSContextRef, JSObjectRef object, + JSPropertyNameAccumulatorRef propertyNames) { + auto* holder = static_cast(JSObjectGetPrivate(object)); + if (holder == nullptr || holder->hostObject == nullptr) { + return; + } + Runtime runtime(holder->state); + try { + for (const auto& property : holder->hostObject->getPropertyNames(runtime)) { + JSStringRef name = makeJSString(property.utf8(runtime)); + JSPropertyNameAccumulatorAddName(propertyNames, name); + JSStringRelease(name); + } + } catch (const std::exception&) { + } +} + +void hostFinalize(JSObjectRef object) { + auto* holder = static_cast(JSObjectGetPrivate(object)); + if (holder != nullptr && holder->state != nullptr) { + holder->state->untrack(holder); + } + delete holder; +} + +JSValueRef functionCall(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, + size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { + auto* holder = static_cast(JSObjectGetPrivate(function)); + if (holder == nullptr || !holder->callback) { + return JSValueMakeUndefined(context); + } + Runtime runtime(holder->state); + StackValueArray args(argumentCount); + for (size_t i = 0; i < argumentCount; i++) { + args.emplace(i, Value::borrowed(runtime, arguments[i])); + } + try { + Value thisValue = Value::borrowed(runtime, thisObject); + Value result = + holder->callback(runtime, thisValue, args.size() == 0 ? nullptr : args.data(), + args.size()); + return result.local(runtime); + } catch (const std::exception& error) { + setException(context, exception, error); + return JSValueMakeUndefined(context); + } +} + +void functionFinalize(JSObjectRef object) { + auto* holder = static_cast(JSObjectGetPrivate(object)); + if (holder != nullptr && holder->state != nullptr) { + holder->state->untrack(holder); + } + delete holder; +} + +JSClassRef hostClass(Runtime& runtime) { + auto state = runtime.state(); + if (state->hostClass == nullptr) { + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.className = "NativeScriptEngineHostObject"; + definition.getProperty = hostGetProperty; + definition.setProperty = hostSetProperty; + definition.getPropertyNames = hostGetPropertyNames; + definition.finalize = hostFinalize; + state->hostClass = JSClassCreate(&definition); + } + return state->hostClass; +} + +JSClassRef functionClass(Runtime& runtime) { + auto state = runtime.state(); + if (state->functionClass == nullptr) { + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.className = "NativeScriptEngineFunction"; + definition.callAsFunction = functionCall; + definition.finalize = functionFinalize; + state->functionClass = JSClassCreate(&definition); + } + return state->functionClass; +} + +void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function) { + if (context == nullptr || function == nullptr) { + return; + } + + JSValueRef exception = nullptr; + JSStringRef functionName = makeJSString("Function"); + JSValueRef functionValue = + JSObjectGetProperty(context, JSContextGetGlobalObject(context), functionName, &exception); + JSStringRelease(functionName); + if (exception != nullptr || functionValue == nullptr || + !JSValueIsObject(context, functionValue)) { + return; + } + + exception = nullptr; + JSObjectRef functionConstructor = JSValueToObject(context, functionValue, &exception); + if (exception != nullptr || functionConstructor == nullptr) { + return; + } + + JSStringRef prototypeName = makeJSString("prototype"); + JSValueRef prototypeValue = + JSObjectGetProperty(context, functionConstructor, prototypeName, &exception); + JSStringRelease(prototypeName); + if (exception != nullptr || prototypeValue == nullptr || + !JSValueIsObject(context, prototypeValue)) { + return; + } + + JSObjectSetPrototype(context, function, prototypeValue); +} + +} // namespace jscengine + +Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken) { + auto* holder = new jscengine::HostObjectHolder(runtime.state(), std::move(host), typeToken); + runtime.state()->track(holder, [](void* pointer) { + auto* tracked = static_cast(pointer); + tracked->hostObject.reset(); + tracked->state.reset(); + }); + JSObjectRef object = JSObjectMake(runtime.context(), jscengine::hostClass(runtime), holder); + return Object::fromValueStorage(Value(runtime, object).storage_); +} + +Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback) { + auto* holder = new jscengine::FunctionHolder(runtime.state(), std::move(callback)); + runtime.state()->track(holder, [](void* pointer) { + auto* tracked = static_cast(pointer); + tracked->callback = {}; + tracked->state.reset(); + }); + JSObjectRef function = JSObjectMake(runtime.context(), jscengine::functionClass(runtime), holder); + jscengine::setFunctionPrototype(runtime.context(), function); + std::string functionName = name.utf8(runtime); + if (!functionName.empty()) { + JSStringRef property = jscengine::makeJSString("name"); + JSStringRef valueString = jscengine::makeJSString(functionName); + JSValueRef value = JSValueMakeString(runtime.context(), valueString); + JSObjectSetProperty(runtime.context(), function, property, value, kJSPropertyAttributeReadOnly, + nullptr); + JSStringRelease(valueString); + JSStringRelease(property); + } + return Function(Object::fromValueStorage(Value(runtime, function).storage_)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCMarshalling.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCMarshalling.mm new file mode 100644 index 000000000..327d90236 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCMarshalling.mm @@ -0,0 +1,478 @@ +// Included by NativeApiJSCSelectorGroups.mm inside the NativeScript anonymous namespace. + +std::string jscValueToUtf8(Runtime& runtime, JSValueRef value) { + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(runtime.context(), value, &exception); + if (string == nullptr || exception != nullptr) { + if (string != nullptr) { + JSStringRelease(string); + } + return {}; + } + std::string result = engine::jscengine::stringToUtf8(string); + JSStringRelease(string); + return result; +} + +bool jscNumberValue(Runtime& runtime, JSValueRef value, double* result) { + if (result == nullptr) { + return false; + } + JSValueRef exception = nullptr; + double converted = JSValueToNumber(runtime.context(), value, &exception); + if (exception != nullptr) { + return false; + } + *result = converted; + return true; +} + +template +std::shared_ptr jscHostObject(Runtime& runtime, JSValueRef value) { + if (value == nullptr || !JSValueIsObject(runtime.context(), value)) { + return nullptr; + } + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(runtime.context(), value, &exception); + if (exception != nullptr || object == nullptr) { + return nullptr; + } + auto* holder = static_cast( + JSObjectGetPrivate(object)); + if (holder == nullptr || + holder->typeToken != engine::jscengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); +} + +template +T* jscHostObjectRaw(Runtime& runtime, JSValueRef value) { + if (value == nullptr || !JSValueIsObject(runtime.context(), value)) { + return nullptr; + } + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(runtime.context(), value, &exception); + if (exception != nullptr || object == nullptr) { + return nullptr; + } + auto* holder = static_cast( + JSObjectGetPrivate(object)); + if (holder == nullptr || + holder->typeToken != engine::jscengine::hostObjectTypeToken()) { + return nullptr; + } + return static_cast(holder->hostObject.get()); +} + +id jscNativeObjectArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, JSValueRef value, + NativeApiArgumentFrame& frame) { + if (value == nullptr || JSValueIsNull(runtime.context(), value) || + JSValueIsUndefined(runtime.context(), value)) { + return nil; + } + if (JSValueIsString(runtime.context(), value)) { + std::string utf8 = jscValueToUtf8(runtime, value); + id string = type.kind == metagen::mdTypeNSMutableStringObject + ? [[NSMutableString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding] + : [[NSString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding]; + if (string != nil) { + frame.addObject(string); + } + return string; + } + if (JSValueIsBoolean(runtime.context(), value)) { + return [NSNumber numberWithBool:JSValueToBoolean(runtime.context(), value)]; + } + if (JSValueIsNumber(runtime.context(), value)) { + double converted = 0; + if (jscNumberValue(runtime, value, &converted)) { + return [NSNumber numberWithDouble:converted]; + } + } + if (!JSValueIsObject(runtime.context(), value)) { + return nil; + } + if (auto objectHost = jscHostObject(runtime, value)) { + return objectHost->object(); + } + if (auto classHost = jscHostObject(runtime, value)) { + return static_cast(classHost->nativeClass()); + } + if (auto protocolHost = + jscHostObject(runtime, value)) { + return static_cast(protocolHost->nativeProtocol()); + } + if (auto pointerHost = + jscHostObject(runtime, value)) { + return static_cast(pointerHost->pointer()); + } + if (auto referenceHost = + jscHostObject(runtime, value)) { + return static_cast(referenceHost->data()); + } + if (auto structHost = + jscHostObject(runtime, value)) { + return static_cast(structHost->data()); + } + + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(runtime.context(), value, &exception); + if (exception == nullptr && object != nullptr) { + JSStringRef property = engine::jscengine::makeJSString("__nativeApiClass"); + JSValueRef wrappedClassValue = + JSObjectGetProperty(runtime.context(), object, property, nullptr); + JSStringRelease(property); + if (auto classHost = + jscHostObject(runtime, + wrappedClassValue)) { + return static_cast(classHost->nativeClass()); + } + } + + Value wrapped = Value::borrowed(runtime, value); + return objectFromEngineValue(runtime, bridge, wrapped, frame, + type.kind == + metagen::mdTypeNSMutableStringObject); +} + +Class jscNativeClassArgument(Runtime& runtime, JSValueRef value) { + if (value == nullptr || JSValueIsNull(runtime.context(), value) || + JSValueIsUndefined(runtime.context(), value)) { + return Nil; + } + if (auto classHost = jscHostObject(runtime, value)) { + return classHost->nativeClass(); + } + if (JSValueIsObject(runtime.context(), value)) { + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(runtime.context(), value, &exception); + if (exception == nullptr && object != nullptr) { + JSStringRef property = engine::jscengine::makeJSString("__nativeApiClass"); + JSValueRef wrappedClassValue = + JSObjectGetProperty(runtime.context(), object, property, nullptr); + JSStringRelease(property); + if (auto classHost = + jscHostObject(runtime, + wrappedClassValue)) { + return classHost->nativeClass(); + } + } + } + Value wrapped = Value::borrowed(runtime, value); + return classFromEngineValue(runtime, wrapped); +} + +bool readJSCEngineSelectorArgument(Runtime& runtime, JSValueRef value, + SEL* result) { + if (result == nullptr) { + return false; + } + if (value == nullptr || JSValueIsNull(runtime.context(), value) || + JSValueIsUndefined(runtime.context(), value)) { + *result = nullptr; + return true; + } + if (!JSValueIsString(runtime.context(), value)) { + return false; + } + std::string selectorName = jscValueToUtf8(runtime, value); + *result = sel_registerName(selectorName.c_str()); + return true; +} + +template +bool writeJSCNumber(Runtime& runtime, JSValueRef value, void* target) { + double converted = 0; + if (!jscNumberValue(runtime, value, &converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; +} + +bool prepareJSCEngineArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, JSValueRef value, + NativeApiArgumentFrame& frame, size_t index) { + ffi_type* ffiType = ffiTypeForEngineArgument(type); + size_t size = + ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); + void* target = frame.storageAt(index, size); + + switch (type.kind) { + case metagen::mdTypeBool: + if (!JSValueIsBoolean(runtime.context(), value)) { + return false; + } + *static_cast(target) = + JSValueToBoolean(runtime.context(), value) ? 1 : 0; + return true; + case metagen::mdTypeChar: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeSShort: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + if (JSValueIsString(runtime.context(), value)) { + std::string text = jscValueToUtf8(runtime, value); + if (text.size() != 1) { + return false; + } + *static_cast(target) = + static_cast(static_cast(text[0])); + return true; + } + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeSInt: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeUInt: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeFloat: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeDouble: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeSelector: + return readJSCEngineSelectorArgument(runtime, value, + static_cast(target)); + case metagen::mdTypeClass: { + Class cls = jscNativeClassArgument(runtime, value); + if (cls == Nil) { + return false; + } + *static_cast(target) = cls; + return true; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + *static_cast(target) = + jscNativeObjectArgument(runtime, bridge, type, value, frame); + return true; + default: + break; + } + + Value wrapped = Value::borrowed(runtime, value); + convertEngineFfiArgument(runtime, bridge, type, wrapped, target, frame); + return true; +} + +JSValueRef jscInteger64Value(Runtime& runtime, int64_t value) { + constexpr int64_t maxSafeInteger = 9007199254740991LL; + constexpr int64_t minSafeInteger = -9007199254740991LL; + if (value >= minSafeInteger && value <= maxSafeInteger) { + return JSValueMakeNumber(runtime.context(), static_cast(value)); + } + Value bigint = BigInt::fromInt64(runtime, value); + return bigint.local(runtime); +} + +JSValueRef jscUnsignedInteger64Value(Runtime& runtime, uint64_t value) { + constexpr uint64_t maxSafeInteger = 9007199254740991ULL; + if (value <= maxSafeInteger) { + return JSValueMakeNumber(runtime.context(), static_cast(value)); + } + Value bigint = BigInt::fromUint64(runtime, value); + return bigint.local(runtime); +} + +JSValueRef setJSCEngineObjectReturn( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, id object) { + if (object == nil) { + return JSValueMakeNull(runtime.context()); + } + Value roundTrip = + findCachedNativeObjectReturn(runtime, bridge, type, object); + if (!roundTrip.isUndefined()) { + JSValueRef result = roundTrip.local(runtime); + if (type.returnOwned) { + [object release]; + } + return result; + } + if (nativeObjectReturnMayCoerceToString(type) && + nativeObjectIsStringLike(object)) { + std::string utf8 = utf8StringFromNSString(static_cast(object)); + if (type.returnOwned) { + [object release]; + } + JSStringRef string = engine::jscengine::makeJSString(utf8); + JSValueRef result = JSValueMakeString(runtime.context(), string); + JSStringRelease(string); + return result; + } + if ([object isKindOfClass:[NSNull class]]) { + if (type.returnOwned) { + [object release]; + } + return JSValueMakeNull(runtime.context()); + } + if ([object isKindOfClass:[NSNumber class]] && + ![object isKindOfClass:[NSDecimalNumber class]]) { + NSNumber* number = static_cast(object); + const char* objCType = [number objCType]; + bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == + CFBooleanGetTypeID() || + (objCType != nullptr && + std::strcmp(objCType, @encode(BOOL)) == 0); + JSValueRef result = + isBool ? JSValueMakeBoolean(runtime.context(), [number boolValue]) + : JSValueMakeNumber(runtime.context(), [number doubleValue]); + if (type.returnOwned) { + [object release]; + } + return result; + } + + if (const NativeApiSymbol* classSymbol = + bridge->findClassForRuntimePointer((void*)object)) { + Value result = makeNativeClassValue(runtime, bridge, *classSymbol); + if (type.returnOwned) { + [object release]; + } + return result.local(runtime); + } + if (const NativeApiSymbol* protocolSymbol = + bridge->findProtocolForRuntimePointer((void*)object)) { + Value result = makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + if (type.returnOwned) { + [object release]; + } + return result.local(runtime); + } + Value result = makeNativeObjectValue(runtime, bridge, object, type.returnOwned); + return result.local(runtime); +} + +JSValueRef setJSCEngineReturnValue( + Runtime& runtime, const std::shared_ptr& bridge, + NativeApiType type, void* value, const std::string& selectorName) { + switch (type.kind) { + case metagen::mdTypeVoid: + return JSValueMakeUndefined(runtime.context()); + case metagen::mdTypeBool: + return JSValueMakeBoolean(runtime.context(), + *static_cast(value) != 0); + case metagen::mdTypeChar: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeSShort: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeUShort: + return JSValueMakeNumber(runtime.context(), *static_cast(value)); + case metagen::mdTypeUnichar: { + const char16_t unit = *static_cast(value); + // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates + // fall back to U+FFFD). + char buffer[4] = {0}; + size_t length = 0; + if (unit < 0x80) { + buffer[length++] = static_cast(unit); + } else if (unit < 0x800) { + buffer[length++] = static_cast(0xC0 | (unit >> 6)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } else if (unit >= 0xD800 && unit <= 0xDFFF) { + buffer[length++] = static_cast(0xEF); + buffer[length++] = static_cast(0xBF); + buffer[length++] = static_cast(0xBD); + } else { + buffer[length++] = static_cast(0xE0 | (unit >> 12)); + buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } + JSStringRef string = engine::jscengine::makeJSString(std::string(buffer, length)); + JSValueRef result = JSValueMakeString(runtime.context(), string); + JSStringRelease(string); + return result; + } + case metagen::mdTypeSInt: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeUInt: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return jscInteger64Value(runtime, *static_cast(value)); + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return jscUnsignedInteger64Value(runtime, + *static_cast(value)); + case metagen::mdTypeFloat: + return JSValueMakeNumber(runtime.context(), *static_cast(value)); + case metagen::mdTypeDouble: + return JSValueMakeNumber(runtime.context(), *static_cast(value)); + case metagen::mdTypeClass: { + Class cls = *static_cast(value); + if (cls == nil) { + return JSValueMakeNull(runtime.context()); + } + const char* name = class_getName(cls); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + return result.local(runtime); + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + if ((selectorName == "valueForKey:" || + selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(type)) { + type.kind = metagen::mdTypeAnyObject; + } + return setJSCEngineObjectReturn(runtime, bridge, type, + *static_cast(value)); + case metagen::mdTypeSelector: { + SEL selector = *static_cast(value); + const char* selectorNameValue = + selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorNameValue == nullptr) { + return JSValueMakeNull(runtime.context()); + } + JSStringRef string = engine::jscengine::makeJSString(selectorNameValue); + JSValueRef result = JSValueMakeString(runtime.context(), string); + JSStringRelease(string); + return result; + } + default: + break; + } + Value result = convertNativeReturnValue(runtime, bridge, type, value); + return result.local(runtime); +} diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h new file mode 100644 index 000000000..817daed17 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h @@ -0,0 +1,887 @@ +#ifndef NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H +#define NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H + +#ifdef TARGET_ENGINE_JSC + +#import +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../shared/RuntimeCleanupRegistry.h" + +#include "Metadata.h" +#include "MetadataReader.h" +#include "ffi.h" + +@protocol NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class BigInt; +class ArrayBuffer; + +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} +}; + +class StringBuffer { + public: + explicit StringBuffer(std::string value) : value_(std::move(value)) {} + const char* data() const { return value_.data(); } + size_t size() const { return value_.size(); } + + private: + std::string value_; +}; + +class MutableBuffer { + public: + virtual ~MutableBuffer() = default; + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) : value_(std::move(value)) {} + + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? value : ""); + } + + static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } + + std::string utf8(Runtime&) const { return value_; } + + private: + std::string value_; +}; + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); +}; + +using HostFunctionType = std::function; + +namespace jscengine { + +inline std::string stringToUtf8(JSStringRef string) { + if (string == nullptr) { + return {}; + } + size_t capacity = JSStringGetMaximumUTF8CStringSize(string); + std::string result(capacity, '\0'); + size_t written = JSStringGetUTF8CString(string, result.data(), capacity); + if (written == 0) { + return {}; + } + result.resize(written - 1); + return result; +} + +inline JSStringRef makeJSString(const std::string& value) { + NSString* string = [[NSString alloc] initWithBytes:value.data() + length:value.size() + encoding:NSUTF8StringEncoding]; + if (string == nil) { + return JSStringCreateWithUTF8CString(value.c_str()); + } + + NSUInteger length = [string length]; + std::vector characters(length); + if (length > 0) { + [string getCharacters:characters.data() range:NSMakeRange(0, length)]; + } + [string release]; + return JSStringCreateWithCharacters(characters.data(), length); +} + +inline JSStringRef makeJSString(const char* value) { + return JSStringCreateWithUTF8CString(value != nullptr ? value : ""); +} + +inline std::string valueToUtf8(JSContextRef context, JSValueRef value) { + if (value == nullptr) { + return {}; + } + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(context, value, &exception); + if (string == nullptr || exception != nullptr) { + if (string != nullptr) { + JSStringRelease(string); + } + return {}; + } + std::string result = stringToUtf8(string); + JSStringRelease(string); + return result; +} + +inline JSValueRef makeError(JSContextRef context, const std::string& message) { + JSStringRef string = makeJSString(message); + JSValueRef argument = JSValueMakeString(context, string); + JSStringRelease(string); + JSValueRef exception = nullptr; + JSObjectRef error = JSObjectMakeError(context, 1, &argument, &exception); + if (error != nullptr && exception == nullptr) { + return error; + } + return argument; +} + +inline void setException(JSContextRef context, JSValueRef* exception, const std::exception& error) { + if (exception != nullptr) { + *exception = makeError(context, error.what()); + } +} + +struct RuntimeState : RuntimeCleanupRegistry { + explicit RuntimeState(JSGlobalContextRef context) : context(context) {} + + ~RuntimeState() { + if (hostClass != nullptr) { + JSClassRelease(hostClass); + } + if (functionClass != nullptr) { + JSClassRelease(functionClass); + } + if (selectorGroupFunctionClass != nullptr) { + JSClassRelease(selectorGroupFunctionClass); + } + } + + JSGlobalContextRef context = nullptr; + JSClassRef hostClass = nullptr; + JSClassRef functionClass = nullptr; + JSClassRef selectorGroupFunctionClass = nullptr; +}; + +std::shared_ptr stateForContext(JSGlobalContextRef context); +void releaseStateForContext(JSGlobalContextRef context); + +struct ValueStorage { + enum class Kind { + Undefined, + Null, + Bool, + Number, + JSC, + JSCBorrowed, + }; + + explicit ValueStorage(Kind kind) : kind(kind) {} + + ~ValueStorage() { + if (kind == Kind::JSC && context != nullptr && value != nullptr) { + JSValueUnprotect(context, value); + } + } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + JSGlobalContextRef context = nullptr; + JSValueRef value = nullptr; +}; + +template +const void* hostObjectTypeToken() { + static int token = 0; + return &token; +} + +struct HostObjectHolder { + HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, + const void* typeToken) + : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} + + std::shared_ptr state; + std::shared_ptr hostObject; + const void* typeToken = nullptr; +}; + +struct FunctionHolder { + FunctionHolder(std::shared_ptr state, HostFunctionType callback) + : state(std::move(state)), callback(std::move(callback)) {} + + std::shared_ptr state; + HostFunctionType callback; +}; + +struct ArrayBufferHolder { + explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + + std::shared_ptr buffer; +}; + +void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function); + +} // namespace jscengine + +class Runtime { + public: + explicit Runtime(JSGlobalContextRef context) + : state_(jscengine::stateForContext(context)) {} + + explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} + + JSGlobalContextRef context() const { return state_->context; } + std::shared_ptr state() const { return state_; } + void detachState() { state_.reset(); } + + Object global(); + Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); + void drainMicrotasks() {} + + private: + std::shared_ptr state_; +}; + +class String { + public: + String() = default; + String(Runtime& runtime, JSStringRef string); + + static String createFromUtf8(Runtime& runtime, const char* value) { + JSStringRef string = jscengine::makeJSString(value); + String result(runtime, string); + JSStringRelease(string); + return result; + } + + static String createFromUtf8(Runtime& runtime, const std::string& value) { + JSStringRef string = jscengine::makeJSString(value); + String result(runtime, string); + JSStringRelease(string); + return result; + } + + static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { + std::string text(reinterpret_cast(value), length); + return createFromUtf8(runtime, text); + } + + std::string utf8(Runtime& runtime) const; + JSValueRef local(Runtime& runtime) const { return storage_->value; } + operator Value() const; + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class Value { + public: + Value() : kind_(jscengine::ValueStorage::Kind::Undefined) {} + + Value(bool value) : kind_(jscengine::ValueStorage::Kind::Bool), boolValue_(value) {} + + Value(double value) : kind_(jscengine::ValueStorage::Kind::Number), numberValue_(value) {} + + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + Value(Runtime& runtime, const Value& value) { + if (value.kind_ == jscengine::ValueStorage::Kind::JSCBorrowed) { + // Promote borrowed to owned + storage_ = std::make_shared(jscengine::ValueStorage::Kind::JSC); + storage_->context = runtime.context(); + storage_->value = value.borrowedValue_ != nullptr ? value.borrowedValue_ + : JSValueMakeUndefined(runtime.context()); + JSValueProtect(runtime.context(), storage_->value); + kind_ = jscengine::ValueStorage::Kind::JSC; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + borrowedContext_ = value.borrowedContext_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + borrowedContext_(value.borrowedContext_), + borrowedValue_(value.borrowedValue_), + storage_(std::move(value.storage_)) {} + Value(Runtime& runtime, const String& value); + Value(Runtime& runtime, const Object& object); + Value(Runtime& runtime, const Function& function); + Value(Runtime& runtime, const Array& array); + Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); + Value(Runtime& runtime, const BigInt& bigint); + Value(Runtime& runtime, JSValueRef value) + : kind_(jscengine::ValueStorage::Kind::JSC), + storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); + JSValueProtect(runtime.context(), storage_->value); + } + + static Value borrowed(Runtime& runtime, JSValueRef value) { + Value result; + result.kind_ = jscengine::ValueStorage::Kind::JSCBorrowed; + result.borrowedContext_ = runtime.context(); + result.borrowedValue_ = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); + return result; + } + + static Value undefined() { return Value(); } + static Value null() { + Value value; + value.kind_ = jscengine::ValueStorage::Kind::Null; + return value; + } + + static bool strictEquals(Runtime& runtime, const Value& lhs, + const Value& rhs) { + return JSValueIsStrictEqual(runtime.context(), lhs.local(runtime), + rhs.local(runtime)); + } + + bool isUndefined() const { + return kind_ == jscengine::ValueStorage::Kind::Undefined || + (isJSC() && JSValueIsUndefined(jscContext(), jscValue())); + } + bool isNull() const { + return kind_ == jscengine::ValueStorage::Kind::Null || + (isJSC() && JSValueIsNull(jscContext(), jscValue())); + } + bool isBool() const { + return kind_ == jscengine::ValueStorage::Kind::Bool || + (isJSC() && JSValueIsBoolean(jscContext(), jscValue())); + } + bool getBool() const { + if (kind_ == jscengine::ValueStorage::Kind::Bool) { + return boolValue_; + } + return isJSC() && JSValueToBoolean(jscContext(), jscValue()); + } + bool isNumber() const { + return kind_ == jscengine::ValueStorage::Kind::Number || + (isJSC() && JSValueIsNumber(jscContext(), jscValue())); + } + double getNumber() const { + if (kind_ == jscengine::ValueStorage::Kind::Number) { + return numberValue_; + } + return isJSC() ? JSValueToNumber(jscContext(), jscValue(), nullptr) : 0; + } + + bool isObject() const { return isJSC() && JSValueIsObject(jscContext(), jscValue()); } + bool isString() const { return isJSC() && JSValueIsString(jscContext(), jscValue()); } + bool isBigInt() const { + if (!isJSC()) { + return false; + } + if (__builtin_available(macOS 15.0, iOS 18.0, *)) { + return JSValueIsBigInt(jscContext(), jscValue()); + } + return false; + } + bool isSymbol() const { return isJSC() && JSValueIsSymbol(jscContext(), jscValue()); } + + Object asObject(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + JSValueRef local(Runtime& runtime) const { + switch (kind_) { + case jscengine::ValueStorage::Kind::Undefined: + return JSValueMakeUndefined(runtime.context()); + case jscengine::ValueStorage::Kind::Null: + return JSValueMakeNull(runtime.context()); + case jscengine::ValueStorage::Kind::Bool: + return JSValueMakeBoolean(runtime.context(), boolValue_); + case jscengine::ValueStorage::Kind::Number: + return JSValueMakeNumber(runtime.context(), numberValue_); + case jscengine::ValueStorage::Kind::JSC: + return storage_->value; + case jscengine::ValueStorage::Kind::JSCBorrowed: + return borrowedValue_; + } + } + + // Access the shared storage (for Object/Function/Array interop) + std::shared_ptr storage() const { return storage_; } + + static Value fromStorage(std::shared_ptr s) { + Value v; + v.kind_ = s->kind; + v.boolValue_ = s->boolValue; + v.numberValue_ = s->numberValue; + v.storage_ = std::move(s); + return v; + } + + private: + friend class Runtime; + friend class Object; + friend class String; + friend class BigInt; + friend class ArrayBuffer; + friend class Function; + friend class Array; + + bool isJSC() const { + return kind_ == jscengine::ValueStorage::Kind::JSC || + kind_ == jscengine::ValueStorage::Kind::JSCBorrowed; + } + JSContextRef jscContext() const { + return kind_ == jscengine::ValueStorage::Kind::JSCBorrowed ? borrowedContext_ + : storage_->context; + } + JSValueRef jscValue() const { + return kind_ == jscengine::ValueStorage::Kind::JSCBorrowed ? borrowedValue_ : storage_->value; + } + + jscengine::ValueStorage::Kind kind_ = jscengine::ValueStorage::Kind::Undefined; + bool boolValue_ = false; + double numberValue_ = 0; + JSGlobalContextRef borrowedContext_ = nullptr; + JSValueRef borrowedValue_ = nullptr; + std::shared_ptr storage_; +}; + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = JSObjectMake(runtime.context(), nullptr, nullptr); + JSValueProtect(runtime.context(), storage_->value); + } + + static Object fromValueStorage(std::shared_ptr storage) { + Object object; + object.storage_ = std::move(storage); + return object; + } + + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + jscengine::hostObjectTypeToken()); + } + + Value getProperty(Runtime& runtime, const char* name) const { + JSStringRef property = jscengine::makeJSString(name); + JSValueRef exception = nullptr; + JSValueRef result = + JSObjectGetProperty(runtime.context(), local(runtime), property, &exception); + JSStringRelease(property); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + + Value getProperty(Runtime& runtime, const Value& key) const { + JSValueRef exception = nullptr; + JSValueRef result = JSObjectGetPropertyForKey(runtime.context(), local(runtime), + key.local(runtime), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + Object getPropertyAsObject(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime); + } + + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value) { + JSStringRef property = jscengine::makeJSString(name); + JSValueRef exception = nullptr; + JSObjectSetProperty(runtime.context(), local(runtime), property, value.local(runtime), + kJSPropertyAttributeNone, &exception); + JSStringRelease(property); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + } + + void setProperty(Runtime& runtime, const char* name, const String& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Object& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Function& value); + void setProperty(Runtime& runtime, const char* name, const Array& value); + void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const std::string& name, const Value& value) { + setProperty(runtime, name.c_str(), value); + } + void setProperty(Runtime& runtime, const Value& key, const Value& value) { + JSValueRef exception = nullptr; + JSObjectSetPropertyForKey(runtime.context(), local(runtime), key.local(runtime), + value.local(runtime), kJSPropertyAttributeNone, &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + } + + bool hasProperty(Runtime& runtime, const char* name) const { + JSStringRef property = jscengine::makeJSString(name); + bool result = JSObjectHasProperty(runtime.context(), local(runtime), property); + JSStringRelease(property); + return result; + } + + bool isFunction(Runtime& runtime) const { + return JSObjectIsFunction(runtime.context(), local(runtime)); + } + + bool isArray(Runtime& runtime) const { + JSStringRef name = jscengine::makeJSString("Array"); + JSValueRef constructorValue = JSObjectGetProperty( + runtime.context(), JSContextGetGlobalObject(runtime.context()), name, nullptr); + JSStringRelease(name); + if (constructorValue == nullptr || !JSValueIsObject(runtime.context(), constructorValue)) { + return false; + } + JSObjectRef constructor = JSValueToObject(runtime.context(), constructorValue, nullptr); + JSValueRef exception = nullptr; + bool result = + JSValueIsInstanceOfConstructor(runtime.context(), local(runtime), constructor, &exception); + return exception == nullptr && result; + } + + bool isArrayBuffer(Runtime& runtime) const { + JSValueRef exception = nullptr; + JSTypedArrayType type = + JSValueGetTypedArrayType(runtime.context(), storage_->value, &exception); + return exception == nullptr && type == kJSTypedArrayTypeArrayBuffer; + } + + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + return holder != nullptr && holder->typeToken == jscengine::hostObjectTypeToken(); + } + + template + std::shared_ptr getHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + if (holder == nullptr || holder->typeToken != jscengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); + } + + JSObjectRef local(Runtime& runtime) const { + return reinterpret_cast(const_cast(storage_->value)); + } + + operator Value() const { return Value::fromStorage(storage_); } + + protected: + friend class Value; + friend class Runtime; + friend class Function; + friend class Array; + friend class ArrayBuffer; + + explicit Object(std::shared_ptr storage) + : storage_(std::move(storage)) {} + + static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + + jscengine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { + return static_cast(JSObjectGetPrivate(local(runtime))); + } + + std::shared_ptr storage_; +}; + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object.storage_)) {} + + static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback); + + Value call(Runtime& runtime, const Value* args, size_t count) const { + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValueRef exception = nullptr; + JSValueRef result = JSObjectCallAsFunction( + runtime.context(), local(runtime), JSContextGetGlobalObject(runtime.context()), argv.size(), + argv.empty() ? nullptr : argv.data(), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + Value call(Runtime& runtime) const { + return call(runtime, static_cast(nullptr), 0); + } + Value call(Runtime& runtime, std::nullptr_t, size_t) const { + return call(runtime, static_cast(nullptr), 0); + } + template + Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { + return call(runtime, static_cast(args), count); + } + template + Value call(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return call(runtime, static_cast(argv), sizeof...(Args)); + } + + Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, + size_t count = 0) const { + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValueRef exception = nullptr; + JSValueRef result = + JSObjectCallAsFunction(runtime.context(), local(runtime), thisObject.local(runtime), + argv.size(), argv.empty() ? nullptr : argv.data(), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValueRef exception = nullptr; + JSValueRef result = JSObjectCallAsConstructor(runtime.context(), local(runtime), argv.size(), + argv.empty() ? nullptr : argv.data(), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { + return callAsConstructor(runtime, static_cast(nullptr), 0); + } + template + Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { + return callAsConstructor(runtime, static_cast(args), count); + } + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); + } +}; + +class Array : public Object { + public: + explicit Array(Runtime& runtime, size_t size) + : Object(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + std::vector initial(size, JSValueMakeUndefined(runtime.context())); + JSValueRef exception = nullptr; + storage_->context = runtime.context(); + storage_->value = + JSObjectMakeArray(runtime.context(), initial.size(), initial.data(), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + JSValueProtect(runtime.context(), storage_->value); + } + + explicit Array(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { + Value length = getProperty(runtime, "length"); + return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; + } + + Value getValueAtIndex(Runtime& runtime, size_t index) const { + JSValueRef exception = nullptr; + JSValueRef result = JSObjectGetPropertyAtIndex(runtime.context(), local(runtime), + static_cast(index), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + return Value(runtime, result); + } + + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { + JSValueRef exception = nullptr; + JSObjectSetPropertyAtIndex(runtime.context(), local(runtime), static_cast(index), + value.local(runtime), &exception); + if (exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + } + void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { + setValueAtIndex(runtime, index, Value(runtime, value)); + } +}; + +class BigInt { + public: + BigInt() = default; + BigInt(Runtime& runtime, JSValueRef value) + : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = value; + JSValueProtect(runtime.context(), storage_->value); + } + + static BigInt fromInt64(Runtime& runtime, int64_t value) { + JSValueRef exception = nullptr; + JSValueRef result = nullptr; + if (__builtin_available(macOS 15.0, iOS 18.0, *)) { + result = JSBigIntCreateWithInt64(runtime.context(), value, &exception); + } + if (result == nullptr || exception != nullptr) { + result = JSValueMakeNumber(runtime.context(), static_cast(value)); + } + return BigInt(runtime, result); + } + + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + JSValueRef exception = nullptr; + JSValueRef result = nullptr; + if (__builtin_available(macOS 15.0, iOS 18.0, *)) { + result = JSBigIntCreateWithUInt64(runtime.context(), value, &exception); + } + if (result == nullptr || exception != nullptr) { + result = JSValueMakeNumber(runtime.context(), static_cast(value)); + } + return BigInt(runtime, result); + } + + String toString(Runtime& runtime, int) const { + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); + if (string == nullptr || exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + String result(runtime, string); + JSStringRelease(string); + return result; + } + + JSValueRef local(Runtime& runtime) const { return storage_->value; } + + operator Value() const { return Value::fromStorage(storage_); } + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : Object(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + auto* holder = new jscengine::ArrayBufferHolder(std::move(buffer)); + JSValueRef exception = nullptr; + storage_->context = runtime.context(); + storage_->value = JSObjectMakeArrayBufferWithBytesNoCopy( + runtime.context(), holder->buffer->data(), holder->buffer->size(), + [](void*, void* deallocatorContext) { + delete static_cast(deallocatorContext); + }, + holder, &exception); + if (exception != nullptr) { + delete holder; + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + JSValueProtect(runtime.context(), storage_->value); + } + + explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { + JSValueRef exception = nullptr; + return JSObjectGetArrayBufferByteLength(runtime.context(), local(runtime), &exception); + } + + uint8_t* data(Runtime& runtime) const { + JSValueRef exception = nullptr; + return static_cast( + JSObjectGetArrayBufferBytesPtr(runtime.context(), local(runtime), &exception)); + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC + +#endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H diff --git a/NativeScript/ffi/jsc/NativeApiJSCRuntime.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.mm similarity index 77% rename from NativeScript/ffi/jsc/NativeApiJSCRuntime.mm rename to NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.mm index 3396af697..da5aa2ce8 100644 --- a/NativeScript/ffi/jsc/NativeApiJSCRuntime.mm +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.mm @@ -2,8 +2,8 @@ #ifdef TARGET_ENGINE_JSC -namespace facebook { -namespace jsi { +namespace nativescript { +namespace engine { Object Runtime::global() { return Object::fromValueStorage(Value(*this, JSContextGetGlobalObject(context())).storage_); @@ -13,18 +13,18 @@ const std::string& sourceURL) { JSStringRef source = JSStringCreateWithUTF8CString( buffer != nullptr ? std::string(buffer->data(), buffer->size()).c_str() : ""); - JSStringRef url = jscdirect::makeJSString(sourceURL); + JSStringRef url = jscengine::makeJSString(sourceURL); JSValueRef exception = nullptr; JSValueRef result = JSEvaluateScript(context(), source, nullptr, url, 1, &exception); JSStringRelease(source); JSStringRelease(url); if (exception != nullptr) { - throw JSError(*this, jscdirect::valueToUtf8(context(), exception)); + throw JSError(*this, jscengine::valueToUtf8(context(), exception)); } return Value(*this, result); } -} // namespace jsi -} // namespace facebook +} // namespace engine +} // namespace nativescript #endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntimeSupport.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntimeSupport.mm new file mode 100644 index 000000000..8d1849d19 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntimeSupport.mm @@ -0,0 +1,12 @@ +// Included by NativeApiJSC.mm inside the NativeScript anonymous namespace. + +std::shared_ptr retainNativeApiRuntime(Runtime& runtime) { + return std::make_shared(runtime.state()); +} + +void SetNativeApiObjectPrototype(Runtime& runtime, Object& object, + const Object& prototype) { + JSObjectSetPrototype(runtime.context(), object.local(runtime), + prototype.local(runtime)); +} + diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCSelectorGroups.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCSelectorGroups.mm new file mode 100644 index 000000000..bdc82ea53 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCSelectorGroups.mm @@ -0,0 +1,327 @@ +// Included by NativeApiJSC.mm inside the NativeScript anonymous namespace. + +#include "../shared/bridge/SelectorGroupData.h" + +#include "NativeApiJSCMarshalling.mm" + +#include "NativeApiJSCGsd.mm" + +#include "../shared/bridge/SelectorGroupCall.h" + + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId) { + return reinterpret_cast(lookupObjCGsdInvoker(dispatchId)); +} + +bool tryCallGeneratedEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result) { + if (result == nullptr || receiver == nil || + !prepared.gsdEngineCallable || dispatchSuperClass != Nil || + count != prepared.gsdEngineArgumentCount) { + return false; + } + + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, + runtime.context(), nullptr, prepared.signature.returnType}; + ctx.valueArguments = args; + ctx.materializeValueResult = true; + if (!invoker(ctx)) { + return false; + } + *result = std::move(ctx.valueResult); + return true; +} + +JSValueRef setJSCEnginePreparedObjCResult( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const std::shared_ptr& receiverHostObject, + const std::optional& initializerClassWrapper, + size_t providedCount, const JSValueRef arguments[], + Class dispatchSuperClass) { + const NativeApiSignature& signature = prepared.signature; + if (receiver == nil || signature.variadic || + unsupportedEngineType(signature.returnType)) { + throw JSError(runtime, + "Objective-C selector is not supported by JSC engine: " + + prepared.selectorName); + } + + const bool isNSErrorOutMethod = prepared.isNSErrorOutMethod; + if (isNSErrorOutMethod) { + size_t expected = signature.argumentTypes.size(); + if (providedCount > expected || providedCount + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } else if (providedCount != signature.argumentTypes.size()) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + + std::to_string(signature.argumentTypes.size()) + "\"."); + } + + // GSD fast path: the generated invoker reads args directly from the JSC + // arguments, calls objc_msgSend with a typed cast, and produces the JS + // return value — bypassing all generic marshalling. Excludes appearance + // static selectors — those need the generic path's proxy tagging. + if (prepared.gsdEngineCallable && dispatchSuperClass == Nil && + providedCount == prepared.gsdEngineArgumentCount && + !initializerClassWrapper && !isNSErrorOutMethod && + !isPreparedStaticAppearanceSelector(prepared)) { + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, + runtime.context(), arguments, signature.returnType}; + if (invoker(ctx)) { + if (providedCount > 0) { + Value setterValue = Value::borrowed(runtime, arguments[0]); + cachePreparedAppearanceProxySetterValue(runtime, bridge, receiver, + prepared, &setterValue, 1); + } + return ctx.result; + } + } + + if (dispatchSuperClass == Nil && !initializerClassWrapper && + providedCount <= 2) { + Value fastArgs[2]; + for (size_t i = 0; i < providedCount; i++) { + fastArgs[i] = Value::borrowed(runtime, arguments[i]); + } + Value fastResult; + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, prepared, + fastArgs, providedCount, Nil, + &fastResult)) { + cachePreparedAppearanceProxySetterValue(runtime, bridge, receiver, + prepared, fastArgs, + providedCount); + fastResult = tagPreparedStaticAppearanceSelectorResult( + runtime, bridge, receiver, prepared, std::move(fastResult)); + return fastResult.local(runtime); + } + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + for (size_t i = 0; i < providedCount; i++) { + if (!prepareJSCEngineArgument(runtime, bridge, signature.argumentTypes[i], + arguments[i], frame, i)) { + throw JSError(runtime, + "Objective-C argument is not supported by JSC engine: " + + prepared.selectorName); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && providedCount + 1 == signature.argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + size_t outArgIndex = signature.argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } + + NativeApiPointerFrame values(signature.argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, const_cast(&prepared.selector)); + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared.preparedInvoker != nullptr && dispatchSuperClass == Nil) { + prepared.preparedInvoker(reinterpret_cast(objc_msgSend), + values.data(), returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature.returnType.ffiType->size > 16 && + signature.returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(const_cast(&signature.cif), target, + returnStorage.data(), values.data()); +#else + ffi_call(const_cast(&signature.cif), + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature.returnType; + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + if (providedCount > 0) { + Value setterValue = Value::borrowed(runtime, arguments[0]); + cachePreparedAppearanceProxySetterValue(runtime, bridge, receiver, + prepared, &setterValue, 1); + } + if (initializerClassWrapper) { + id resultObject = nil; + if (isObjectiveCObjectType(returnType)) { + resultObject = *static_cast(returnStorage.data()); + } + if (receiverHostObject != nullptr && resultObject != receiver) { + receiverHostObject->disownObject(receiver); + } + if (resultObject != nil) { + bridge->setObjectExpando(runtime, resultObject, + "__nativeApiClassWrapper", + Value(runtime, *initializerClassWrapper)); + } + } + tagPreparedStaticAppearanceNativeReturn( + runtime, bridge, receiver, prepared, returnType, returnStorage.data()); + return setJSCEngineReturnValue(runtime, bridge, returnType, + returnStorage.data(), prepared.selectorName); +} + +JSValueRef NativeApiSelectorGroupCall( + JSContextRef context, JSObjectRef function, JSObjectRef thisObject, + size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { + auto* data = + static_cast(JSObjectGetPrivate(function)); + if (data == nullptr || data->selectors == nullptr || + data->preparedInvocations == nullptr) { + return JSValueMakeUndefined(context); + } + + Runtime& runtime = data->runtime; + try { + NativeApiRoundTripCacheFrameGuard roundTripFrame(data->bridge); + auto resolveObjectHost = [&]() + -> engine::jscengine::HostObjectHolder* { + if (thisObject == nullptr) { + return nullptr; + } + auto* holder = static_cast( + JSObjectGetPrivate(thisObject)); + return holder != nullptr && + holder->typeToken == + engine::jscengine::hostObjectTypeToken< + NativeApiObjectHostObject>() + ? holder + : nullptr; + }; + auto call = resolveNativeApiSelectorGroupCall( + runtime, *data, argumentCount, + [&]() -> id { + auto* holder = resolveObjectHost(); + return holder != nullptr + ? static_cast( + holder->hostObject.get())->object() + : nil; + }, + [&]() -> std::shared_ptr { + if (data->boundReceiverState != nullptr) { + return nullptr; + } + auto* holder = resolveObjectHost(); + return holder != nullptr + ? std::static_pointer_cast( + holder->hostObject) + : nullptr; + }, + [](uint64_t dispatchId) { return lookupObjCGsdInvoker(dispatchId); }); + if (call.hasImmediateResult) { + return call.immediateResult.local(runtime); + } + JSValueRef result = setJSCEnginePreparedObjCResult( + runtime, data->bridge, call.receiver, *call.prepared, + call.receiverHostObject, call.initializerClassWrapper, argumentCount, + arguments, call.dispatchClass); + if (!data->receiverIsClass && call.prepared->isInitMethod && + thisObject != nullptr) { + if (auto preserved = preservedNativeApiInitializerSelfReturn( + runtime, data->bridge, call.receiver, Value::borrowed(runtime, result), + Value::borrowed(runtime, thisObject))) { + return preserved->local(runtime); + } + } + return result; + } catch (const std::exception& error) { + engine::jscengine::setException(context, exception, error); + return JSValueMakeUndefined(context); + } +} + +void NativeApiSelectorGroupFinalize(JSObjectRef function) { + auto* data = static_cast( + JSObjectGetPrivate(function)); + if (data != nullptr && data->runtime.state() != nullptr) { + data->runtime.state()->untrack(data); + } + delete data; +} + +JSClassRef NativeApiSelectorGroupFunctionClass(Runtime& runtime) { + auto state = runtime.state(); + if (state->selectorGroupFunctionClass == nullptr) { + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.className = "NativeScriptEngineSelectorGroupFunction"; + definition.callAsFunction = NativeApiSelectorGroupCall; + definition.finalize = NativeApiSelectorGroupFinalize; + state->selectorGroupFunctionClass = JSClassCreate(&definition); + } + return state->selectorGroupFunctionClass; +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState) { + auto* data = new NativeApiSelectorGroupData( + runtime.state(), std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), + std::move(boundReceiver), std::move(boundReceiverState)); + runtime.state()->track(data, [](void* pointer) { + auto* tracked = static_cast(pointer); + tracked->runtime.detachState(); + tracked->bridge.reset(); + tracked->selectors.reset(); + tracked->preparedInvocations.reset(); + tracked->boundReceiver.reset(); + tracked->boundReceiverState.reset(); + }); + JSObjectRef function = + JSObjectMake(runtime.context(), + NativeApiSelectorGroupFunctionClass(runtime), data); + engine::jscengine::setFunctionPrototype(runtime.context(), function); + + JSStringRef property = engine::jscengine::makeJSString("name"); + JSStringRef functionName = + engine::jscengine::makeJSString("__nativeSelectorGroup"); + JSValueRef value = JSValueMakeString(runtime.context(), functionName); + JSObjectSetProperty(runtime.context(), function, property, value, + kJSPropertyAttributeReadOnly, nullptr); + JSStringRelease(functionName); + JSStringRelease(property); + + Value functionValue(runtime, function); + return functionValue.asObject(runtime).asFunction(runtime); +} diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCValue.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCValue.mm new file mode 100644 index 000000000..f66913761 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCValue.mm @@ -0,0 +1,108 @@ +#include "NativeApiJSCRuntime.h" + +#ifdef TARGET_ENGINE_JSC + +namespace nativescript { +namespace engine { + +Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } +bool HostObject::set(Runtime&, const PropNameID&, const Value&) { return true; } +std::vector HostObject::getPropertyNames(Runtime&) { return {}; } + +String::String(Runtime& runtime, JSStringRef string) + : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = JSValueMakeString(runtime.context(), string); + JSValueProtect(runtime.context(), storage_->value); +} + +std::string String::utf8(Runtime& runtime) const { + return jscengine::valueToUtf8(runtime.context(), storage_->value); +} + +String::operator Value() const { return Value::fromStorage(storage_); } + +Value::Value(Runtime&, const String& value) { + storage_ = value.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Object& object) { + storage_ = object.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Function& function) { + storage_ = function.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Array& array) { + storage_ = array.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) { + storage_ = arrayBuffer.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const BigInt& bigint) { + storage_ = bigint.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} + +Object Value::asObject(Runtime& runtime) const { + if (storage_) { + return Object::fromValueStorage(storage_); + } + // Promote borrowed to owned storage for Object. + auto s = std::make_shared(jscengine::ValueStorage::Kind::JSC); + s->context = runtime.context(); + s->value = borrowedValue_ != nullptr ? borrowedValue_ : JSValueMakeUndefined(runtime.context()); + JSValueProtect(runtime.context(), s->value); + return Object::fromValueStorage(std::move(s)); +} + +String Value::asString(Runtime& runtime) const { + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); + if (string == nullptr || exception != nullptr) { + throw JSError(runtime, jscengine::valueToUtf8(runtime.context(), exception)); + } + String result(runtime, string); + JSStringRelease(string); + return result; +} + +BigInt Value::getBigInt(Runtime& runtime) const { return BigInt(runtime, local(runtime)); } + +Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime).asFunction(runtime); +} +Function Object::asFunction(Runtime&) const { return Function(*this); } +Array Object::getArray(Runtime&) const { return Array(*this); } +ArrayBuffer Object::getArrayBuffer(Runtime&) const { return ArrayBuffer(*this); } + +Array Object::getPropertyNames(Runtime& runtime) const { + JSPropertyNameArrayRef propertyNames = + JSObjectCopyPropertyNames(runtime.context(), local(runtime)); + size_t count = JSPropertyNameArrayGetCount(propertyNames); + Array result(runtime, count); + for (size_t i = 0; i < count; i++) { + JSStringRef name = JSPropertyNameArrayGetNameAtIndex(propertyNames, i); + result.setValueAtIndex(runtime, i, String(runtime, name)); + } + JSPropertyNameArrayRelease(propertyNames); + return result; +} + +void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { + setProperty(runtime, name, Value(runtime, value)); +} +void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { + setProperty(runtime, name, Value(runtime, value)); +} +void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/objc/jsc/SignatureDispatch.h b/NativeScript/ffi/objc/jsc/SignatureDispatch.h new file mode 100644 index 000000000..1204cd120 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/SignatureDispatch.h @@ -0,0 +1,14 @@ +#ifndef NATIVESCRIPT_FFI_JSC_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_FFI_JSC_SIGNATURE_DISPATCH_H + +#include "ffi/objc/shared/SignatureDispatchCore.h" + +#if defined(__has_include) +#if __has_include("../shared/GeneratedSignatureDispatch.inc") +#include "../shared/GeneratedSignatureDispatch.inc" +#endif +#endif + +#include "ffi/objc/shared/PreparedSignatureDispatch.h" + +#endif // NATIVESCRIPT_FFI_JSC_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/napi/AutoreleasePool.h b/NativeScript/ffi/objc/napi/AutoreleasePool.h similarity index 100% rename from NativeScript/ffi/napi/AutoreleasePool.h rename to NativeScript/ffi/objc/napi/AutoreleasePool.h diff --git a/NativeScript/ffi/napi/AutoreleasePool.mm b/NativeScript/ffi/objc/napi/AutoreleasePool.mm similarity index 100% rename from NativeScript/ffi/napi/AutoreleasePool.mm rename to NativeScript/ffi/objc/napi/AutoreleasePool.mm diff --git a/NativeScript/ffi/napi/Block.h b/NativeScript/ffi/objc/napi/Block.h similarity index 100% rename from NativeScript/ffi/napi/Block.h rename to NativeScript/ffi/objc/napi/Block.h diff --git a/NativeScript/ffi/napi/Block.mm b/NativeScript/ffi/objc/napi/Block.mm similarity index 97% rename from NativeScript/ffi/napi/Block.mm rename to NativeScript/ffi/objc/napi/Block.mm index 3b8697afc..2cc3887d2 100644 --- a/NativeScript/ffi/napi/Block.mm +++ b/NativeScript/ffi/objc/napi/Block.mm @@ -9,7 +9,7 @@ #include #include #include "Interop.h" -#include "runtime/NativeScriptException.h" +#include "runtime/apple/NativeScriptException.h" #include "ObjCBridge.h" #include "SignatureDispatch.h" #include "TypeConv.h" @@ -174,6 +174,14 @@ napi_value callFunctionPointerAsCFunctionDirect(napi_env env, nativescript::Func cif->argTypes[i]->toNative(env, invocationArgs[i], avalues[i], &shouldFreeArg, &shouldFreeAny); shouldFree[i] = shouldFreeArg ? 1 : 0; + if (nativescript::ConsumeNapiArgumentConversionFailure(env)) { + for (unsigned int converted = 0; converted <= i; converted++) { + if (shouldFree[converted]) { + cif->argTypes[converted]->free(env, *((void**)avalues[converted])); + } + } + return nullptr; + } } void* rvalue = cif->rvalue; @@ -248,6 +256,14 @@ napi_value callFunctionPointerAsBlockDirect(napi_env env, nativescript::Function cif->argTypes[i]->toNative(env, invocationArgs[i], avalues[i + 1], &shouldFreeArg, &shouldFreeAny); shouldFree[i] = shouldFreeArg ? 1 : 0; + if (nativescript::ConsumeNapiArgumentConversionFailure(env)) { + for (unsigned int converted = 0; converted <= i; converted++) { + if (shouldFree[converted]) { + cif->argTypes[converted]->free(env, *((void**)avalues[converted + 1])); + } + } + return nullptr; + } } void* rvalue = cif->rvalue; diff --git a/NativeScript/ffi/napi/CFunction.h b/NativeScript/ffi/objc/napi/CFunction.h similarity index 100% rename from NativeScript/ffi/napi/CFunction.h rename to NativeScript/ffi/objc/napi/CFunction.h diff --git a/NativeScript/ffi/napi/CFunction.mm b/NativeScript/ffi/objc/napi/CFunction.mm similarity index 91% rename from NativeScript/ffi/napi/CFunction.mm rename to NativeScript/ffi/objc/napi/CFunction.mm index 3f9d26714..44d934e91 100644 --- a/NativeScript/ffi/napi/CFunction.mm +++ b/NativeScript/ffi/objc/napi/CFunction.mm @@ -12,8 +12,8 @@ #include "Interop.h" #include "ObjCBridge.h" #include "SignatureDispatch.h" -#include "runtime/NativeScriptException.h" -#include "Tasks.h" +#include "runtime/apple/NativeScriptException.h" +#include "ffi/objc/shared/Tasks.h" #ifdef ENABLE_JS_RUNTIME #include "jsr.h" #endif @@ -124,56 +124,7 @@ explicit CFunctionInvocationFrame(Cif* cif) }; inline bool unwrapCompatNativeHandleForCFunction(napi_env env, napi_value value, void** out) { - if (value == nullptr || out == nullptr) { - return false; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *out = ptr != nullptr ? ptr->data : nullptr; - return ptr != nullptr; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *out = ref != nullptr ? ref->data : nullptr; - return ref != nullptr; - } - - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok) { - return false; - } - - if (valueType == napi_bigint) { - uint64_t raw = 0; - bool lossless = false; - if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) { - return false; - } - *out = reinterpret_cast(static_cast(raw)); - return true; - } - - if (valueType == napi_external) { - return napi_get_value_external(env, value, out) == napi_ok; - } - - if (valueType != napi_object && valueType != napi_function) { - return false; - } - - bool hasNativePointer = false; - if (napi_has_named_property(env, value, "__ns_native_ptr", &hasNativePointer) == napi_ok && - hasNativePointer) { - napi_value nativePointerValue = nullptr; - if (napi_get_named_property(env, value, "__ns_native_ptr", &nativePointerValue) == napi_ok && - napi_get_value_external(env, nativePointerValue, out) == napi_ok && *out != nullptr) { - return true; - } - } - - return napi_unwrap(env, value, out) == napi_ok && *out != nullptr; + return unwrapKnownNativeHandle(env, value, out); } inline napi_value createCompatDispatchQueueWrapperForCFunction(napi_env env, @@ -463,6 +414,14 @@ inline void ensureCFunctionDispatchLookup(CFunction* function, Cif* cif) { cif->argTypes[i]->toNative(env, invocationArgs[i], avalues[i], &argShouldFree, &shouldFreeAny); shouldFree[i] = argShouldFree ? 1 : 0; + if (ConsumeNapiArgumentConversionFailure(env)) { + for (unsigned int converted = 0; converted <= i; converted++) { + if (shouldFree[converted]) { + cif->argTypes[converted]->free(env, *((void**)avalues[converted])); + } + } + return nullptr; + } } } diff --git a/NativeScript/ffi/objc/napi/CallbackThreading.h b/NativeScript/ffi/objc/napi/CallbackThreading.h new file mode 100644 index 000000000..d4f15a94a --- /dev/null +++ b/NativeScript/ffi/objc/napi/CallbackThreading.h @@ -0,0 +1,148 @@ +#ifndef CALLBACK_THREADING_H +#define CALLBACK_THREADING_H + +#include "js_native_api.h" + +#include +#include + +#if defined(ENABLE_JS_RUNTIME) +#include "jsr.h" +#endif + +namespace nativescript { + +namespace detail { + +#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) +inline thread_local int native_caller_thread_callback_depth = 0; +#endif + +} // namespace detail + +inline bool isNativeCallerThreadCallbackActive() { +#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) + return detail::native_caller_thread_callback_depth > 0; +#else + return false; +#endif +} + +inline bool shouldInvokeCallbackOnNativeCallerThread() { +#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) + return isNativeCallerThreadCallbackActive(); +#else + return false; +#endif +} + +class NativeCallRuntimeUnlockScope final { + public: + explicit NativeCallRuntimeUnlockScope(napi_env env) { +#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) + if (isNativeCallerThreadCallbackActive()) { + return; + } + + JSR* runtime = JSR::ForEnv(env); + if (runtime == nullptr) { + return; + } + + jsr_ = runtime; + unlockedDepth_ = js_current_env_lock_depth(env); + for (int i = 0; i < unlockedDepth_; i++) { + jsr_->unlock(); + } + if (unlockedDepth_ == 0 && jsr_->runtime != nullptr) { + auto* runtime = jsr_->runtime.get(); + runtime->unlock(); + relockRuntime_ = [runtime]() { runtime->lock(); }; + unlockedRuntime_ = true; + } +#else + (void)env; +#endif + } + + ~NativeCallRuntimeUnlockScope() { +#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) + if (jsr_ != nullptr) { + for (int i = 0; i < unlockedDepth_; i++) { + jsr_->lock(); + } + } + if (unlockedRuntime_ && relockRuntime_) { + relockRuntime_(); + } +#endif + } + + NativeCallRuntimeUnlockScope(const NativeCallRuntimeUnlockScope&) = delete; + NativeCallRuntimeUnlockScope& operator=(const NativeCallRuntimeUnlockScope&) = + delete; + + private: +#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) + JSR* jsr_ = nullptr; + std::function relockRuntime_; +#endif + int unlockedDepth_ = 0; + bool unlockedRuntime_ = false; +}; + +class NativeCallbackScope final { + public: + explicit NativeCallbackScope(napi_env env) : env_(env) { +#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) + if (isNativeCallerThreadCallbackActive() || + js_current_env_lock_depth(env_) > 0) { + napi_open_handle_scope(env_, &napiHandleScope_); + return; + } + + JSR* runtime = JSR::ForEnv(env_); + if (runtime != nullptr) { + jsr_ = runtime; + jsr_->lock(); + detail::native_caller_thread_callback_depth += 1; + napi_open_handle_scope(env_, &napiHandleScope_); + return; + } +#endif +#if defined(ENABLE_JS_RUNTIME) + napiScope_ = std::make_unique(env_); +#else + (void)env_; +#endif + } + + ~NativeCallbackScope() { +#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) + if (napiHandleScope_ != nullptr) { + napi_close_handle_scope(env_, napiHandleScope_); + } + if (jsr_ != nullptr) { + detail::native_caller_thread_callback_depth -= 1; + jsr_->unlock(); + } +#endif + } + + NativeCallbackScope(const NativeCallbackScope&) = delete; + NativeCallbackScope& operator=(const NativeCallbackScope&) = delete; + + private: + napi_env env_; +#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) + JSR* jsr_ = nullptr; + napi_handle_scope napiHandleScope_ = nullptr; +#endif +#if defined(ENABLE_JS_RUNTIME) + std::unique_ptr napiScope_; +#endif +}; + +} // namespace nativescript + +#endif // CALLBACK_THREADING_H diff --git a/NativeScript/ffi/napi/Cif.h b/NativeScript/ffi/objc/napi/Cif.h similarity index 100% rename from NativeScript/ffi/napi/Cif.h rename to NativeScript/ffi/objc/napi/Cif.h diff --git a/NativeScript/ffi/objc/napi/Cif.mm b/NativeScript/ffi/objc/napi/Cif.mm new file mode 100644 index 000000000..4e1fe2004 --- /dev/null +++ b/NativeScript/ffi/objc/napi/Cif.mm @@ -0,0 +1,360 @@ +#include "Cif.h" +#include +#include +#include +#include +#include +#include +#include +#include "Metadata.h" +#include "MetadataReader.h" +#include "ObjCBridge.h" +#include "ffi/objc/shared/SignatureDispatchCore.h" +#include "TypeConv.h" +#include "Util.h" + +namespace nativescript { +namespace { + +inline bool typeRequiresSlowGeneratedNapiDispatch(const std::shared_ptr& type) { + if (type == nullptr) { + return false; + } + + switch (type->kind) { + case mdTypeUChar: + case mdTypeUInt8: + case mdTypeString: + case mdTypePointer: + case mdTypeStruct: + case mdTypeArray: + case mdTypeBlock: + case mdTypeFunctionPointer: + case mdTypeVector: + case mdTypeExtVector: + case mdTypeComplex: + return true; + default: + return false; + } +} + +inline bool typeKindMayUseRoundTripCache(MDTypeKind kind) { + switch (kind) { + case mdTypeAnyObject: + case mdTypeProtocolObject: + case mdTypeClassObject: + case mdTypeInstanceObject: + case mdTypeNSStringObject: + case mdTypeNSMutableStringObject: + return true; + default: + return false; + } +} + +inline void updateGeneratedNapiDispatchCompatibility(Cif* cif) { + if (cif == nullptr) { + return; + } + + cif->skipGeneratedNapiDispatch = false; + cif->generatedDispatchHasRoundTripCacheArgument = false; + cif->generatedDispatchUsesObjectReturnStorage = false; + + if (cif->returnType != nullptr) { + cif->generatedDispatchUsesObjectReturnStorage = + typeKindMayUseRoundTripCache(cif->returnType->kind); + } + + cif->skipGeneratedNapiDispatch = typeRequiresSlowGeneratedNapiDispatch(cif->returnType); + if (cif->skipGeneratedNapiDispatch) { + return; + } + + for (const auto& argType : cif->argTypes) { + if (argType != nullptr && typeKindMayUseRoundTripCache(argType->kind)) { + cif->generatedDispatchHasRoundTripCacheArgument = true; + } + if (typeRequiresSlowGeneratedNapiDispatch(argType)) { + cif->skipGeneratedNapiDispatch = true; + return; + } + } +} + +} // namespace + +// Essentially, we cache libffi structures per unique method signature, +// this helps us avoid the overhead of creating them on the fly for each +// invocation. +Cif* ObjCBridgeState::getMethodCif(napi_env env, Method method) { + auto encoding = std::string(method_getTypeEncoding(method)); + auto find = this->cifs[encoding]; + if (find != nullptr) { + return find; + } + + auto cif = new Cif(env, method); + this->cifs[encoding] = cif; + + return cif; +} + +Cif* ObjCBridgeState::getMethodCif(napi_env env, MDSectionOffset offset) { + auto find = this->mdMethodSignatureCache[offset]; + if (find != nullptr) { + return find; + } + + auto cif = new Cif(env, metadata, offset, true, false); + this->mdMethodSignatureCache[offset] = cif; + + return cif; +} + +Cif* ObjCBridgeState::getBlockCif(napi_env env, MDSectionOffset offset) { + auto find = this->mdBlockSignatureCache[offset]; + if (find != nullptr) { + return find; + } + + auto cif = new Cif(env, metadata, offset, false, true); + this->mdBlockSignatureCache[offset] = cif; + + return cif; +} + +Cif* ObjCBridgeState::getCFunctionCif(napi_env env, MDSectionOffset offset) { + auto find = this->mdFunctionSignatureCache[offset]; + if (find != nullptr) { + return find; + } + + auto cif = new Cif(env, metadata, offset, false, false); + this->mdFunctionSignatureCache[offset] = cif; + + return cif; +} + +Cif::Cif(napi_env env, std::string encoding, unsigned int implicitArgc) { + auto signature = [NSMethodSignature signatureWithObjCTypes:encoding.c_str()]; + unsigned long numberOfArguments = signature.numberOfArguments; + unsigned long skippedArgs = std::min(numberOfArguments, implicitArgc); + this->argc = (int)(numberOfArguments - skippedArgs); + this->argv = (napi_value*)malloc(sizeof(napi_value) * this->argc); + + unsigned int totalArgc = (unsigned int)numberOfArguments; + + const char* returnType = signature.methodReturnType; + this->returnType = TypeConv::Make(env, &returnType); + + ffi_type* rtype = this->returnType->type; + this->atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); + + unsigned long methodReturnLength = signature.methodReturnLength; + unsigned long frameLength = signature.frameLength; + + this->rvalue = malloc(methodReturnLength); + this->rvalueLength = methodReturnLength; + this->frameLength = frameLength; + + this->avalues = this->argc > 0 ? (void**)malloc(sizeof(void*) * this->argc) : nullptr; + if (this->avalues != nullptr) { + memset(this->avalues, 0, sizeof(void*) * this->argc); + } + this->shouldFree = (bool*)malloc(sizeof(bool) * this->argc); + memset(this->shouldFree, false, sizeof(bool) * this->argc); + this->shouldFreeAny = false; + this->avaluesAllocStart = 0; + this->avaluesAllocCount = 0; + + for (int i = 0; i < numberOfArguments; i++) { + const char* argenc = [signature getArgumentTypeAtIndex:i]; + + auto argTypeInfo = TypeConv::Make(env, &argenc); + this->atypes[i] = argTypeInfo->ffiTypeForArgument(); + + if (i >= skippedArgs) { + this->argTypes.push_back(argTypeInfo); + } + } + + ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgc, rtype, this->atypes); + + if (status != FFI_OK) { + std::cout << "Failed to prepare CIF, libffi returned error:" << status << std::endl; + return; + } + + for (unsigned int i = 0; i < this->argc; i++) { + this->avalues[i] = malloc(cif.arg_types[i + skippedArgs]->size); + this->avaluesAllocCount++; + } + + updateGeneratedNapiDispatchCompatibility(this); +} + +Cif::Cif(napi_env env, Method method) { + const unsigned int totalArgc = method_getNumberOfArguments(method); + this->argc = totalArgc >= 2 ? totalArgc - 2 : 0; + this->argv = this->argc > 0 ? (napi_value*)malloc(sizeof(napi_value) * this->argc) : nullptr; + + char* returnTypeEnc = method_copyReturnType(method); + const char* returnTypePtr = returnTypeEnc; + this->returnType = TypeConv::Make(env, &returnTypePtr); + if (returnTypeEnc != nullptr) { + free(returnTypeEnc); + } + + ffi_type* rtype = this->returnType->type; + this->atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); + + this->rvalueLength = std::max(1, rtype->size); + this->rvalue = malloc(this->rvalueLength); + this->frameLength = 0; + + this->avalues = this->argc > 0 ? (void**)malloc(sizeof(void*) * this->argc) : nullptr; + if (this->avalues != nullptr) { + memset(this->avalues, 0, sizeof(void*) * this->argc); + } + + this->shouldFree = this->argc > 0 ? (bool*)malloc(sizeof(bool) * this->argc) : nullptr; + if (this->shouldFree != nullptr) { + memset(this->shouldFree, false, sizeof(bool) * this->argc); + } + this->shouldFreeAny = false; + this->avaluesAllocStart = 0; + this->avaluesAllocCount = 0; + + for (unsigned int i = 0; i < totalArgc; i++) { + char* argEnc = method_copyArgumentType(method, i); + const char* argEncPtr = argEnc; + auto argTypeInfo = TypeConv::Make(env, &argEncPtr); + if (argEnc != nullptr) { + free(argEnc); + } + + this->atypes[i] = argTypeInfo->ffiTypeForArgument(); + if (i >= 2) { + this->argTypes.push_back(argTypeInfo); + } + } + + ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgc, rtype, this->atypes); + if (status != FFI_OK) { + std::cout << "Failed to prepare CIF, libffi returned error:" << status << std::endl; + return; + } + + for (unsigned int i = 0; i < this->argc; i++) { + this->avalues[i] = malloc(cif.arg_types[i + 2]->size); + this->avaluesAllocCount++; + } + + updateGeneratedNapiDispatchCompatibility(this); +} + +Cif::Cif(napi_env env, MDMetadataReader* reader, MDSectionOffset offset, bool isMethod, + bool isBlock) { + MDSectionOffset signatureStart = offset; + auto returnTypeKind = reader->getTypeKind(offset); + bool next = ((MDTypeFlag)returnTypeKind & mdTypeFlagNext) != 0; + isVariadic = ((MDTypeFlag)returnTypeKind & mdTypeFlagVariadic) != 0; + + returnType = TypeConv::Make(env, reader, &offset); + + auto implicitArgs = isMethod ? 2 : isBlock ? 1 : 0; + + shouldFreeAny = false; + atypes = nullptr; + avaluesAllocStart = 0; + avaluesAllocCount = 0; + + if (next || isMethod || isBlock) { + while (next) { + auto argTypeKind = reader->getTypeKind(offset); + next = ((MDTypeFlag)argTypeKind & mdTypeFlagNext) != 0; + auto argTypeInfo = TypeConv::Make(env, reader, &offset); + std::string enc; + argTypeInfo->encode(&enc); + argTypes.push_back(argTypeInfo); + } + + argc = (int)argTypes.size(); + + auto totalArgc = argc + implicitArgs; + + argv = (napi_value*)malloc(sizeof(napi_value) * argc); + shouldFree = (bool*)malloc(sizeof(bool) * argc); + + atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); + avalues = (void**)malloc(sizeof(void*) * argc); + memset(avalues, 0, sizeof(void*) * argc); + + if (isMethod) { + atypes[0] = &ffi_type_pointer; + atypes[1] = &ffi_type_pointer; + } + + if (isBlock) { + atypes[0] = &ffi_type_pointer; + } + + for (int i = 0; i < argc; i++) { + atypes[i + implicitArgs] = argTypes[i]->ffiTypeForArgument(); + shouldFree[i] = false; + } + } else { + argc = 0; + argv = nullptr; + avalues = nullptr; + shouldFree = nullptr; + } + + ffi_status status = + ffi_prep_cif(&cif, FFI_DEFAULT_ABI, argc + implicitArgs, returnType->type, atypes); + + if (status != FFI_OK) { + std::cout << "Failed to prepare CIF, libffi returned error: " << status << std::endl; + return; + } + + for (int i = 0; i < argc; i++) { + avalues[i] = malloc(cif.arg_types[i + implicitArgs]->size); + avaluesAllocCount++; + } + + rvalue = malloc(cif.rtype->size); + rvalueLength = cif.rtype->size; + + signatureHash = metadataSignatureHash(reader, signatureStart); + + updateGeneratedNapiDispatchCompatibility(this); +} + +Cif::~Cif() { + if (rvalue != nullptr) { + free(rvalue); + } + if (argv != nullptr) { + free(argv); + } + if (avalues != nullptr) { + for (unsigned int i = 0; i < avaluesAllocCount; i++) { + auto index = avaluesAllocStart + i; + if (avalues[index] != nullptr) { + free(avalues[index]); + } + } + free(avalues); + } + if (atypes != nullptr) { + free(atypes); + } + if (shouldFree != nullptr) { + free(shouldFree); + } +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/Class.h b/NativeScript/ffi/objc/napi/Class.h similarity index 100% rename from NativeScript/ffi/napi/Class.h rename to NativeScript/ffi/objc/napi/Class.h diff --git a/NativeScript/ffi/objc/napi/Class.mm b/NativeScript/ffi/objc/napi/Class.mm new file mode 100644 index 000000000..d8b1021a6 --- /dev/null +++ b/NativeScript/ffi/objc/napi/Class.mm @@ -0,0 +1,1034 @@ +#include "Class.h" +#include "ClassBuilder.h" +#include "ClassMember.h" +#include "Interop.h" +#include "Metadata.h" +#include "MetadataReader.h" +#include "ObjCBridge.h" +#include "Protocol.h" +#include "Util.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "node_api_util.h" + +#import +#include +#include + +namespace nativescript { + +napi_value JS_NSObject_alloc(napi_env env, napi_callback_info cbinfo); + +inline bool tryGetInteropPointerArg(napi_env env, napi_value value, void** out) { + if (out == nullptr || value == nullptr) { + return false; + } + + *out = nullptr; + + napi_valuetype argType = napi_undefined; + napi_typeof(env, value, &argType); + + if (argType == napi_external) { + return napi_get_value_external(env, value, out) == napi_ok && *out != nullptr; + } + + if (argType == napi_bigint) { + uint64_t raw = 0; + bool lossless = false; + if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) { + return false; + } + *out = reinterpret_cast(raw); + return *out != nullptr; + } + + if (argType != napi_object) { + return false; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *out = ptr != nullptr ? ptr->data : nullptr; + return *out != nullptr; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *out = ref != nullptr ? ref->data : nullptr; + return *out != nullptr; + } + + return false; +} + +void ObjCBridgeState::registerClassGlobals(napi_env env, napi_value global) { + MDSectionOffset offset = metadata->classesOffset; + while (offset < metadata->structsOffset) { + MDSectionOffset originalOffset = offset; + auto nameOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + auto runtimeNameOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & mdSectionOffsetNext) != 0; + nameOffset &= ~mdSectionOffsetNext; + auto name = metadata->resolveString(nameOffset); + auto runtimeName = name; + if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { + runtimeName = metadata->resolveString(runtimeNameOffset); + } + while (hasProtocols) { + auto protocolOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & mdSectionOffsetNext) != 0; + } + + auto superclass = metadata->getOffset(offset); + offset += sizeof(superclass); + + bool next = (superclass & mdSectionOffsetNext) != 0; + + while (next) { + auto flags = metadata->getMemberFlag(offset); + next = (flags & mdMemberNext) != 0; + offset += sizeof(flags); + + if ((flags & mdMemberProperty) != 0) { + bool readonly = (flags & mdMemberReadonly) != 0; + offset += sizeof(MDSectionOffset); // name + offset += sizeof(MDSectionOffset); // getterSelector + offset += sizeof(MDSectionOffset); // getterSignature + if (!readonly) { + offset += sizeof(MDSectionOffset); // setterSelector + offset += sizeof(MDSectionOffset); // setterSignature + } + } else { + offset += sizeof(MDSectionOffset); // selector + offset += sizeof(MDSectionOffset); // signature + } + } + + auto nativeClass = objc_getClass(runtimeName); + if (nativeClass != nil) { + mdClassesByPointer[nativeClass] = originalOffset; + } + + napi_property_descriptor prop = { + .utf8name = name, + .method = nullptr, + .getter = JS_classGetter, + .setter = nullptr, + .value = nullptr, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = (void*)((size_t)originalOffset), + }; + + napi_define_properties(env, global, 1, &prop); + } +} + +NAPI_FUNCTION(registerClass) { + NAPI_CALLBACK_BEGIN(1) + + // In case no arguments are passed, we just return the NativeObject class. + // This is to support both @NativeClass and @NativeClass() syntaxes. + // Maybe we should support more in future like + // @NativeClass(NSApplicationDelegate). + if (argc == 0) { + napi_value func; + napi_create_function(env, "NativeClass", NAPI_AUTO_LENGTH, JS_registerClass, nullptr, &func); + return func; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + // bridgeState->registerClass(env, argv[0]); + + ClassBuilder* builder = new ClassBuilder(env, argv[0]); + // It gets lazily built when a static method is called. + // builder->build(); + bridgeState->registerRuntimeClass(builder, builder->nativeClass); + + return nullptr; +} + +const char* ObjCClassDecorator = R"( +globalThis.ObjCClass = function ObjCClass(...protocols) { + return function (target) { + if (target.ObjCProtocols) { + target.ObjCProtocols.push(...protocols); + } else { + target.ObjCProtocols = protocols; + } + + return globalThis.NativeClass(target); + }; +}; +)"; + +void setupObjCClassDecorator(napi_env env) { + napi_value global, script; + napi_get_global(env, &global); + napi_create_string_utf8(env, ObjCClassDecorator, NAPI_AUTO_LENGTH, &script); + napi_run_script(env, script, &global); +} + +char class_name[256]; + +// Get a Bridged Class by metadata offset, creating it if it doesn't exist. +// This is used to cache ObjCClass instances. +ObjCClass* ObjCBridgeState::getClass(napi_env env, MDSectionOffset offset) { + auto find = this->classes[offset]; + if (find != nullptr) { + return find; + } + + auto cls = new ObjCClass(env, offset); + this->classes[offset] = cls; + + return cls; +} + +NAPI_FUNCTION(import) { + NAPI_CALLBACK_BEGIN(1) + + NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], class_name, 256, nullptr)) { + NAPI_THROW_LAST_ERROR + return nullptr; + } + + std::string name = class_name; + + if (!name.starts_with("/")) { + name = "/System/Library/Frameworks/" + name + ".framework"; + } + + NSBundle* bundle = [NSBundle bundleWithPath:[NSString stringWithUTF8String:name.c_str()]]; + if (bundle == nil) { + NSLog(@"Could not find bundle: %s", name.c_str()); + return nullptr; + } + + bool loaded = [bundle load]; + + if (!loaded) { + NSLog(@"Could not load bundle: %s", name.c_str()); + return nullptr; + } + + return nullptr; +} + +NAPI_FUNCTION(classGetter) { + void* data; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); + MDSectionOffset offset = (MDSectionOffset)((size_t)data); + auto bridgeState = ObjCBridgeState::InstanceData(env); + + auto cached = bridgeState->mdValueCache[offset]; + if (cached != nullptr) { + return get_ref_value(env, cached); + } + + // std::string name = bridgeState->metadata->getString(offset); + auto cls = bridgeState->getClass(env, offset); + + if (cls != nullptr) { + bridgeState->mdValueCache[offset] = cls->constructor; + } else { + return nullptr; + } + + return get_ref_value(env, cls->constructor); +} + +NAPI_FUNCTION(classSuperclassFallback) { + napi_value jsThis; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, nullptr); + + Class currentClass = nil; + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && jsThis != nullptr) { + bridgeState->tryResolveBridgedClassConstructor(env, jsThis, ¤tClass); + } + if (currentClass == nil) { + napi_unwrap(env, jsThis, (void**)¤tClass); + } + if (currentClass == nil) { + return nullptr; + } + + Class superClass = class_getSuperclass(currentClass); + if (superClass == nil) { + return nullptr; + } + + auto find = bridgeState->classesByPointer.find(superClass); + if (find != bridgeState->classesByPointer.end()) { + return get_ref_value(env, find->second->constructor); + } + + auto mdClass = bridgeState->mdClassesByPointer.find(superClass); + if (mdClass != bridgeState->mdClassesByPointer.end()) { + auto bridgedClass = bridgeState->getClass(env, mdClass->second); + return get_ref_value(env, bridgedClass->constructor); + } + + const char* runtimeName = class_getName(superClass); + if (runtimeName != nullptr && runtimeName[0] != '\0') { + napi_value global = nullptr; + napi_value constructor = nullptr; + bool hasGlobal = false; + napi_get_global(env, &global); + if (napi_has_named_property(env, global, runtimeName, &hasGlobal) == napi_ok && hasGlobal && + napi_get_named_property(env, global, runtimeName, &constructor) == napi_ok && + constructor != nullptr) { + return constructor; + } + } + + return bridgeState->getObject(env, (id)superClass, kUnownedObject, 0, nullptr); +} + +NAPI_FUNCTION(classHasInstance) { + size_t argc = 1; + napi_value argv[1] = {nullptr}; + napi_value jsThis = nullptr; + napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, nullptr); + + Class expectedClass = nil; + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && jsThis != nullptr) { + bridgeState->tryResolveBridgedClassConstructor(env, jsThis, &expectedClass); + } + if (expectedClass == nil) { + napi_unwrap(env, jsThis, (void**)&expectedClass); + } + + bool isInstance = false; + if (expectedClass != nil && argc > 0 && argv[0] != nullptr) { + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, argv[0], &valueType) == napi_ok && + (valueType == napi_object || valueType == napi_function)) { + id instance = nil; + napi_status unwrapStatus = napi_invalid_arg; + if (Pointer::isInstance(env, argv[0])) { + Pointer* pointer = Pointer::unwrap(env, argv[0]); + instance = pointer != nullptr ? static_cast(pointer->data) : nil; + unwrapStatus = instance != nil ? napi_ok : napi_invalid_arg; + } else if (Reference::isInstance(env, argv[0])) { + Reference* reference = Reference::unwrap(env, argv[0]); + instance = reference != nullptr ? static_cast(reference->data) : nil; + unwrapStatus = instance != nil ? napi_ok : napi_invalid_arg; + } else { + unwrapStatus = napi_unwrap(env, argv[0], (void**)&instance); + } + + if (unwrapStatus != napi_ok || instance == nil) { + napi_value nativePointer = nullptr; + if (napi_get_named_property(env, argv[0], "__ns_native_ptr", &nativePointer) == napi_ok && + Pointer::isInstance(env, nativePointer)) { + Pointer* pointer = Pointer::unwrap(env, nativePointer); + instance = pointer != nullptr ? static_cast(pointer->data) : nil; + } + } + + if (instance != nil) { + Class currentClass = object_getClass(instance); + while (currentClass != nil) { + if (currentClass == expectedClass) { + isInstance = true; + break; + } + currentClass = class_getSuperclass(currentClass); + } + } + } + } + + napi_value result = nullptr; + napi_get_boolean(env, isInstance, &result); + return result; +} + +NAPI_FUNCTION(BridgedConstructor) { + NAPI_CALLBACK_BEGIN(16) + + napi_value newTarget = nullptr; + napi_get_new_target(env, cbinfo, &newTarget); + + napi_valuetype thisType = napi_undefined; + if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || + (thisType != napi_object && thisType != napi_function)) { + napi_create_object(env, &jsThis); + + napi_value prototypeOwner = newTarget; + if (prototypeOwner == nullptr || napi_typeof(env, prototypeOwner, &thisType) != napi_ok || + (thisType != napi_function && thisType != napi_object)) { + prototypeOwner = nullptr; + } + + if (prototypeOwner != nullptr) { + napi_value prototype = nullptr; + if (napi_get_named_property(env, prototypeOwner, "prototype", &prototype) == napi_ok && + prototype != nullptr) { + napi_value global = nullptr; + napi_value objectCtor = nullptr; + napi_value setPrototypeOf = nullptr; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &objectCtor); + napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); + napi_value setPrototypeArgs[2] = {jsThis, prototype}; + napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); + } + } + } + + napi_valuetype jsType = napi_undefined; + if (argc > 0) { + napi_typeof(env, argv[0], &jsType); + } + + id object = nil; + + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + auto ensureWrappedThis = [&](id nativeObject) { + if (jsThis == nullptr || nativeObject == nil) { + return; + } + + if (newTarget != nullptr) { + // Fresh constructor receivers should not need an unwrap probe here. + // On V8, reentrant constructor calls from ObjC->JS block callbacks can + // stall inside napi_unwrap() on that receiver. + napi_wrap(env, jsThis, nativeObject, nullptr, nullptr, nullptr); + return; + } + + void* existingWrapped = nullptr; + napi_status unwrapStatus = napi_unwrap(env, jsThis, &existingWrapped); + if (unwrapStatus == napi_ok && existingWrapped != nullptr) { + return; + } + + napi_wrap(env, jsThis, nativeObject, nullptr, nullptr, nullptr); + }; + + Class cls = (Class)data; + + napi_value constructor = newTarget; + if (constructor == nullptr && jsThis != nullptr) { + napi_get_named_property(env, jsThis, "constructor", &constructor); + } + + if (constructor != nullptr) { + Class newTargetCls = nil; + if (!(bridgeState != nullptr && + bridgeState->tryResolveBridgedClassConstructor(env, constructor, &newTargetCls))) { + napi_unwrap(env, constructor, (void**)&newTargetCls); + } + + if (newTargetCls != nil) { + cls = newTargetCls; + } + } + + if (jsType == napi_external) { + return jsThis; + } else { + // Backward compatibility: allow `new Class(pointer)` to wrap an existing + // native instance instead of running initializer resolution. + if (argc == 1 && argv[0] != nullptr) { + void* rawPointer = nullptr; + if (tryGetInteropPointerArg(env, argv[0], &rawPointer) && rawPointer != nullptr) { + if (napi_value cached = bridgeState->getCachedHandleObject(env, rawPointer); + cached != nullptr) { + return cached; + } + + object = (id)rawPointer; + if (napi_value existing = bridgeState->findCachedObjectWrapper(env, object); + existing != nullptr) { + return existing; + } + + ensureWrappedThis(object); + jsThis = bridgeState->proxyNativeObject(env, jsThis, object); + napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr); + return jsThis; + } + } + + bool supercall = class_conformsToProtocol(cls, @protocol(ObjCBridgeClassBuilderProtocol)); + + if (supercall) { + ClassBuilder* builder = (ClassBuilder*)bridgeState->classesByPointer[cls]; + if (!builder->isFinal) builder->build(); + } + + // Allocate first, then run initializer resolution through the bridged + // JS "init" method so constructor arguments participate in selector + // matching (including Swift-style token objects). + object = [cls alloc]; + ensureWrappedThis(object); + jsThis = bridgeState->proxyNativeObject(env, jsThis, object); + } + + napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr); + + napi_value initMethod; + napi_status initStatus = napi_get_named_property(env, jsThis, "init", &initMethod); + if (initStatus != napi_ok || initMethod == nullptr) { + return jsThis; + } + + napi_value initResult; + initStatus = napi_call_function(env, jsThis, initMethod, argc, argv, &initResult); + if (initStatus != napi_ok) { + return nullptr; + } + + return initResult != nullptr ? initResult : jsThis; +} + +// Used to display the description of a native object in console.log. +// It's just implementation of nodejs.util.inspect.custom. +NAPI_FUNCTION(CustomInspect) { + napi_value jsThis; + void* data; + size_t argc = 0; + + napi_get_cb_info(env, cbinfo, &argc, nil, &jsThis, &data); + + id self = nil; + napi_unwrap(env, jsThis, (void**)&self); + + if (self == nil) { + napi_value result; + napi_create_string_utf8(env, "(nil)", NAPI_AUTO_LENGTH, &result); + return result; + } + + auto description = [self description]; + auto descriptionString = [description UTF8String]; + + napi_value result; + napi_create_string_utf8(env, descriptionString, NAPI_AUTO_LENGTH, &result); + + return result; +} + +// Used for Symbol.dispose (using statement support). +NAPI_FUNCTION(releaseObject) { + napi_value jsThis; + void* data; + napi_get_cb_info(env, cbinfo, nil, nil, &jsThis, &data); + id self; + napi_unwrap(env, jsThis, (void**)&self); + auto bridgeState = ObjCBridgeState::InstanceData(env); + bridgeState->unregisterObject(self); + return nullptr; +} + +// Implemented in JS to minimize calls into native. Fast enumeration +// makes use of buffers, so we only fill up the buffer once via +// native call and only do it again if needed via _fillStack. +static const char* FastEnumerationIteratorFactorySource = R"( + (function () { + return { + stack: new Array(16), + stacklen: -1, + stackptr: -1, + done: false, + + next() { + if (this.stackptr < 0 && !this.done) { + this.stacklen = this._fillStack(this.stack); + if (this.stacklen == 0) { + this.done = true; + this.stackptr = -1; + } else { + this.stackptr = 0; + } + } + + if (this.done) { + return { done: true }; + } + + const result = { value: this.stack[this.stackptr++], done: false }; + if (this.stackptr >= this.stacklen) { + this.stackptr = -1; + } + + return result; + }, + }; + }) +)"; + +void initFastEnumeratorIteratorFactory(napi_env env, ObjCBridgeState* bridgeState) { + napi_value result, script; + napi_create_string_utf8(env, FastEnumerationIteratorFactorySource, NAPI_AUTO_LENGTH, &script); + napi_run_script(env, script, &result); + bridgeState->createFastEnumeratorIterator = make_ref(env, result); +} + +class FastEnumerationIterator { + public: + FastEnumerationIterator(id collection) : collection(collection) {} + + static void finalize(napi_env env, void* data, void* hint) { + FastEnumerationIterator* iterator = (FastEnumerationIterator*)data; + delete iterator; + } + + static napi_value fillStack(napi_env env, napi_callback_info cbinfo) { + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + + napi_value jsThis; + void* data; + size_t argc = 1; + napi_value stackArray; + + napi_get_cb_info(env, cbinfo, &argc, &stackArray, &jsThis, &data); + + FastEnumerationIterator* self = nil; + napi_unwrap(env, jsThis, (void**)&self); + + NSUInteger count = [self->collection countByEnumeratingWithState:&self->state + objects:self->stackbuf + count:16]; + + for (uint32_t index = 0; index < static_cast(count); index++) { + id obj = self->state.itemsPtr[index]; + napi_value jsObj = bridgeState->getObject(env, obj); + napi_set_element(env, stackArray, index, jsObj); + } + + napi_value result; + napi_create_uint32(env, static_cast(count), &result); + + return result; + } + + napi_value toJS(napi_env env) { + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + + napi_value createIterator = get_ref_value(env, bridgeState->createFastEnumeratorIterator); + + napi_value result; + napi_call_function(env, createIterator, createIterator, 0, nullptr, &result); + + napi_property_descriptor fillStack = { + .utf8name = "_fillStack", + .name = nil, + .method = FastEnumerationIterator::fillStack, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = napi_enumerable, + .data = nil, + }; + + napi_define_properties(env, result, 1, &fillStack); + + napi_ref ref; + napi_wrap(env, result, this, FastEnumerationIterator::finalize, nullptr, &ref); + + return result; + } + + id collection; + NSFastEnumerationState state = {0}; + id stackbuf[16]; + BOOL firstLoop = YES; + long mutationsPtrValue; +}; + +NAPI_FUNCTION(fastEnumeration) { + napi_value jsThis; + void* data; + size_t argc = 0; + + napi_get_cb_info(env, cbinfo, &argc, nil, &jsThis, &data); + + id self = nil; + napi_unwrap(env, jsThis, (void**)&self); + + if (self == nil) { + napi_value result; + napi_create_string_utf8(env, "(nil)", NAPI_AUTO_LENGTH, &result); + return result; + } + + if (![self conformsToProtocol:@protocol(NSFastEnumeration)]) { + napi_throw_error(env, nil, "Object does not conform to NSFastEnumeration"); + return nullptr; + } + + auto iterator = new FastEnumerationIterator((id)self); + return iterator->toJS(env); +} + +std::string NativeObjectName = "NativeObject"; + +// Bridge an Objective-C class to JavaScript on the fly. Runtime introspection +// is used to determine the class's properties and methods. +// In an overview, we define two versions of same class. One is the "normal" +// one, and the other is the "supercall" one. The supercall one is used to call +// superclasses' methods. The normal one is used to call the class's own +// methods. The supercall one is used automatically when the normal one is +// extended by a JS class. +// Every Bridged Class extends the NativeObject class. + +void defineProtocolMembers(napi_env env, ObjCClassMemberMap& members, napi_value constructor, + ObjCProtocol* protocol, ObjCClass* cls) { + ObjCClassMember::defineMembers(env, members, protocol->membersOffset, constructor, cls); + for (auto protocol : protocol->protocols) { + defineProtocolMembers(env, members, constructor, protocol, cls); + } +} + +ObjCClass::ObjCClass(napi_env env, MDSectionOffset offset) { + NS_OBJC_NAPI_PREAMBLE + + this->env = env; + + metadataOffset = offset; + + bridgeState = ObjCBridgeState::InstanceData(env); + bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; + + bool isNativeObject = offset == MD_SECTION_OFFSET_NULL; + + std::vector protocolOffsets; + + MDSectionOffset superClassOffset = MD_SECTION_OFFSET_NULL; + bool hasMembers = false; + std::string jsConstructorName; + if (isNativeObject) { + name = NativeObjectName; + jsConstructorName = name; + nativeClass = nil; + } else { + auto nameOffset = bridgeState->metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + auto runtimeNameOffset = bridgeState->metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & mdSectionOffsetNext) != 0; + nameOffset &= ~mdSectionOffsetNext; + name = bridgeState->metadata->resolveString(nameOffset); + const char* runtimeName = name.c_str(); + if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { + runtimeName = bridgeState->metadata->resolveString(runtimeNameOffset); + } + jsConstructorName = runtimeName; + if (jsConstructorName.empty()) { + jsConstructorName = name; + } + nativeClass = objc_getClass(runtimeName); + while (hasProtocols) { + auto protocolOffset = bridgeState->metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & mdSectionOffsetNext) != 0; + protocolOffset &= ~mdSectionOffsetNext; + if (protocolOffset != MD_SECTION_OFFSET_NULL) protocolOffsets.push_back(protocolOffset); + } + superClassOffset = bridgeState->metadata->getOffset(offset); + hasMembers = (superClassOffset & mdSectionOffsetNext) != 0; + superClassOffset &= ~mdSectionOffsetNext; + offset += sizeof(MDSectionOffset); + } + + napi_value constructor, prototype; + + napi_define_class(env, jsConstructorName.c_str(), jsConstructorName.length(), + JS_BridgedConstructor, (void*)nativeClass, 0, nil, &constructor); + + if (nativeClass != nil) { + napi_wrap(env, constructor, (void*)nativeClass, nil, nil, nil); + bridgeState->registerRuntimeClass(this, nativeClass); + } + + napi_get_named_property(env, constructor, "prototype", &prototype); + + napi_value superConstructor = nil, superPrototype = nil; + + // If the class requested isn't NativeObject - the class which every bridged + // class extends - we need to find the super class and inherit from it, if it + // exists. + if (!isNativeObject) { + if (superClassOffset != MD_SECTION_OFFSET_NULL) { + superClassOffset += bridgeState->metadata->classesOffset; + } + + // If class offset is 0, it means that the class doesn't have a super class. + // But we just inherit NativeObject class in that case. + superclass = bridgeState->getClass(env, superClassOffset); + if (superclass != nil) { + superConstructor = get_ref_value(env, superclass->constructor); + superPrototype = get_ref_value(env, superclass->prototype); + napi_inherits(env, constructor, superConstructor); + } + + for (auto protocolOffset : protocolOffsets) { + auto protocol = + bridgeState->getProtocol(env, protocolOffset + bridgeState->metadata->protocolsOffset); + if (protocol == nil) continue; + defineProtocolMembers(env, members, constructor, protocol, this); + } + } else { + superclass = nullptr; + } + + napi_value constructorNameValue = nullptr; + napi_create_string_utf8(env, jsConstructorName.c_str(), jsConstructorName.length(), + &constructorNameValue); + napi_property_descriptor constructorNameProp = { + .utf8name = "name", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = constructorNameValue, + .attributes = napi_default, + .data = nullptr, + }; + napi_define_properties(env, constructor, 1, &constructorNameProp); + + this->constructor = make_ref(env, constructor); + this->prototype = make_ref(env, prototype); + + if (isNativeObject) { + napi_value global, Symbol, SymbolDispose, SymbolIterator; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Symbol", &Symbol); + napi_get_named_property(env, Symbol, "iterator", &SymbolIterator); + napi_get_named_property(env, Symbol, "dispose", &SymbolDispose); + napi_valuetype type; + napi_typeof(env, SymbolDispose, &type); + + napi_value sizeofValue; + napi_create_int32(env, sizeof(id), &sizeofValue); + + napi_property_descriptor properties[] = { + { + .utf8name = nil, + .name = jsSymbolFor(env, "nodejs.util.inspect.custom"), + .method = JS_CustomInspect, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = napi_enumerable, + .data = nil, + }, + { + .utf8name = "toString", + .name = nil, + .method = JS_CustomInspect, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = napi_enumerable, + .data = nil, + }, + { + .utf8name = nil, + .name = jsSymbolFor(env, "sizeof"), + .method = nil, + .getter = nil, + .setter = nil, + .value = sizeofValue, + .attributes = napi_enumerable, + .data = nil, + }, + { + .utf8name = nil, + .name = SymbolIterator, + .method = JS_fastEnumeration, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = napi_enumerable, + .data = nil, + }}; + + napi_define_properties(env, prototype, 4, properties); + + napi_define_properties(env, constructor, 1, &properties[2]); + + bool hasSuperclass = false; + napi_value superclassKey = nullptr; + napi_create_string_utf8(env, "superclass", NAPI_AUTO_LENGTH, &superclassKey); + napi_has_own_property(env, constructor, superclassKey, &hasSuperclass); + if (!hasSuperclass) { + napi_property_descriptor superclassProperty = { + .utf8name = "superclass", + .name = nil, + .method = JS_classSuperclassFallback, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = + (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &superclassProperty); + } + + if (type == napi_symbol) { + properties[0].name = SymbolDispose; + properties[0].method = JS_releaseObject; + + napi_define_properties(env, prototype, 1, properties); + } + + return; + } + + // Add the 'extend' static method to all native classes (not NativeObject) + if (!isNativeObject) { + napi_value extendMethod; + napi_create_function(env, "extend", NAPI_AUTO_LENGTH, ClassBuilder::ExtendCallback, nullptr, + &extendMethod); + napi_set_named_property(env, constructor, "extend", extendMethod); + } + + if (!hasMembers) return; + + ObjCClassMember::defineMembers(env, members, offset, constructor, this); + + auto hasOwnNamedProperty = [&](napi_value object, const char* propertyName) { + napi_value key = nullptr; + napi_create_string_utf8(env, propertyName, NAPI_AUTO_LENGTH, &key); + bool hasOwn = false; + napi_has_own_property(env, object, key, &hasOwn); + return hasOwn; + }; + + if (!hasOwnNamedProperty(constructor, "name")) { + napi_value classNameValue = nullptr; + napi_create_string_utf8(env, jsConstructorName.c_str(), NAPI_AUTO_LENGTH, &classNameValue); + napi_property_descriptor nameProperty = { + .utf8name = "name", + .name = nil, + .method = nil, + .getter = nil, + .setter = nil, + .value = classNameValue, + .attributes = (napi_property_attributes)(napi_configurable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &nameProperty); + } + + if (!hasOwnNamedProperty(constructor, "length")) { + napi_value zeroValue = nullptr; + napi_create_int32(env, 0, &zeroValue); + napi_property_descriptor lengthProperty = { + .utf8name = "length", + .name = nil, + .method = nil, + .getter = nil, + .setter = nil, + .value = zeroValue, + .attributes = (napi_property_attributes)(napi_configurable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &lengthProperty); + } + + if (!hasOwnNamedProperty(constructor, "alloc")) { + napi_property_descriptor allocProperty = { + .utf8name = "alloc", + .name = nil, + .method = JS_NSObject_alloc, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = + (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &allocProperty); + } + + if (!hasOwnNamedProperty(constructor, "arguments") || + !hasOwnNamedProperty(constructor, "caller")) { + napi_value undefinedValue = nullptr; + napi_get_undefined(env, &undefinedValue); + napi_property_descriptor slots[] = { + { + .utf8name = "arguments", + .name = nil, + .method = nil, + .getter = nil, + .setter = nil, + .value = undefinedValue, + .attributes = (napi_property_attributes)(napi_configurable | napi_writable), + .data = nil, + }, + { + .utf8name = "caller", + .name = nil, + .method = nil, + .getter = nil, + .setter = nil, + .value = undefinedValue, + .attributes = (napi_property_attributes)(napi_configurable | napi_writable), + .data = nil, + }, + }; + napi_define_properties(env, constructor, 2, slots); + } + + napi_value global = nullptr; + napi_value symbolCtor = nullptr; + napi_value hasInstanceSymbol = nullptr; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Symbol", &symbolCtor); + napi_get_named_property(env, symbolCtor, "hasInstance", &hasInstanceSymbol); + bool hasOwnHasInstance = false; + napi_has_own_property(env, constructor, hasInstanceSymbol, &hasOwnHasInstance); + if (!hasOwnHasInstance) { + napi_property_descriptor hasInstanceProperty = { + .utf8name = nil, + .name = hasInstanceSymbol, + .method = JS_classHasInstance, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = (napi_property_attributes)(napi_configurable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &hasInstanceProperty); + } + + if (!hasOwnNamedProperty(prototype, "toString")) { + napi_property_descriptor toStringProperty = { + .utf8name = "toString", + .name = nil, + .method = JS_CustomInspect, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = + (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), + .data = nil, + }; + napi_define_properties(env, prototype, 1, &toStringProperty); + } +} + +ObjCClass::~ObjCClass() { + if (env != nullptr && + (bridgeState == nullptr || IsBridgeStateLive(bridgeState, bridgeStateToken))) { + DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, constructor); + DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, prototype); + } + constructor = nullptr; + prototype = nullptr; + bridgeState = nullptr; + bridgeStateToken = 0; +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/ClassBuilder.h b/NativeScript/ffi/objc/napi/ClassBuilder.h similarity index 100% rename from NativeScript/ffi/napi/ClassBuilder.h rename to NativeScript/ffi/objc/napi/ClassBuilder.h diff --git a/NativeScript/ffi/objc/napi/ClassBuilder.mm b/NativeScript/ffi/objc/napi/ClassBuilder.mm new file mode 100644 index 000000000..bf6ed1745 --- /dev/null +++ b/NativeScript/ffi/objc/napi/ClassBuilder.mm @@ -0,0 +1,1083 @@ +#include "ClassBuilder.h" +#import +#include +#include +#include "Closure.h" +#include "Interop.h" +#include "Metadata.h" +#include "ObjCBridge.h" +#include "Protocol.h" +#include "TypeConv.h" +#include "Util.h" +#include "js_native_api.h" +#include "node_api_util.h" + +namespace nativescript { +namespace { +std::unordered_map gKnownExposedMethods; +std::mutex gClassBuilderEnvMutex; +std::unordered_map gClassBuilderEnvs; + +void registerClassBuilderEnv(Class nativeClass, napi_env env) { + if (nativeClass == Nil || env == nullptr) { + return; + } + + std::lock_guard lock(gClassBuilderEnvMutex); + gClassBuilderEnvs[nativeClass] = env; +} + +void unregisterClassBuilderEnv(Class nativeClass) { + if (nativeClass == Nil) { + return; + } + + std::lock_guard lock(gClassBuilderEnvMutex); + gClassBuilderEnvs.erase(nativeClass); +} + +napi_env resolveClassBuilderEnv(id self) { + if (self == nil) { + return nullptr; + } + + Class currentClass = object_getClass(self); + if (currentClass == Nil) { + return nullptr; + } + + std::lock_guard lock(gClassBuilderEnvMutex); + while (currentClass != Nil) { + auto find = gClassBuilderEnvs.find(currentClass); + if (find != gClassBuilderEnvs.end()) { + return find->second; + } + currentClass = class_getSuperclass(currentClass); + } + + return nullptr; +} + +const char* kInstallSuperAccessorSource = R"( + (function (prototype, basePrototype) { + const superCacheKey = Symbol.for("__ns_super_proxy__"); + Object.defineProperty(prototype, "super", { + configurable: true, + enumerable: false, + get: function () { + const self = this; + if (self == null) { + return undefined; + } + + const existing = self[superCacheKey]; + if (existing && existing.base === basePrototype) { + return existing.proxy; + } + + const proxy = new Proxy(basePrototype, { + get(target, key) { + let current = target; + while (current != null) { + const descriptor = Object.getOwnPropertyDescriptor(current, key); + if (descriptor) { + if (typeof descriptor.get === "function") { + return descriptor.get.call(self); + } + const value = descriptor.value; + if (typeof value === "function") { + return value.bind(self); + } + return value; + } + current = Object.getPrototypeOf(current); + } + return undefined; + }, + set(target, key, value) { + let current = target; + while (current != null) { + const descriptor = Object.getOwnPropertyDescriptor(current, key); + if (descriptor) { + if (typeof descriptor.set === "function") { + descriptor.set.call(self, value); + return true; + } + if ("value" in descriptor && descriptor.writable) { + self[key] = value; + return true; + } + return false; + } + current = Object.getPrototypeOf(current); + } + self[key] = value; + return true; + } + }); + + Object.defineProperty(self, superCacheKey, { + configurable: true, + enumerable: false, + writable: true, + value: { base: basePrototype, proxy } + }); + + return proxy; + } + }); + }) + )"; + +const char* kInstallClassFromStringAliasSource = R"( + (function (global) { + if (global.__nsClassFromStringAliasInstalled === true) { + return; + } + + const original = global.NSClassFromString; + if (typeof original !== "function") { + return; + } + + Object.defineProperty(global, "__nsOriginalNSClassFromString", { + configurable: true, + enumerable: false, + writable: false, + value: original + }); + + Object.defineProperty(global, "NSClassFromString", { + configurable: true, + enumerable: true, + writable: true, + value: function (name) { + const cls = original.call(this, name); + if (cls != null) { + try { + const registry = global.__nsConstructorsByObjCClassName; + const stringFromClass = global.NSStringFromClass; + if (registry != null && typeof stringFromClass === "function") { + const runtimeName = stringFromClass(cls); + const constructor = registry[runtimeName]; + if (constructor != null) { + return constructor; + } + } + } catch (_) { + } + } + + return cls; + } + }); + + Object.defineProperty(global, "__nsClassFromStringAliasInstalled", { + configurable: true, + enumerable: false, + writable: false, + value: true + }); + }) +)"; + +const char* NSFastEnumerationMethodEncoding() { + static const char* encoding = nullptr; + if (encoding == nullptr) { + struct objc_method_description desc = protocol_getMethodDescription( + @protocol(NSFastEnumeration), @selector(countByEnumeratingWithState:objects:count:), YES, + YES); + encoding = desc.types; + } + return encoding; +} + +void clearPendingException(napi_env env) { + napi_value ignored = nullptr; + napi_get_and_clear_last_exception(env, &ignored); +} + +Protocol* resolveRuntimeProtocol(napi_env env, napi_value protocolValue) { + if (protocolValue == nullptr) { + return nullptr; + } + + ObjCProtocol* wrappedProtocol = nullptr; + if (napi_unwrap(env, protocolValue, (void**)&wrappedProtocol) == napi_ok && + wrappedProtocol != nullptr) { + Protocol* runtimeProtocol = objc_getProtocol(wrappedProtocol->name.c_str()); + if (runtimeProtocol != nullptr) { + return runtimeProtocol; + } + } + + if (Pointer::isInstance(env, protocolValue)) { + Pointer* pointer = Pointer::unwrap(env, protocolValue); + if (pointer != nullptr && pointer->data != nullptr) { + Protocol* runtimeProtocol = (Protocol*)pointer->data; + if (protocol_getName(runtimeProtocol) != nullptr) { + return runtimeProtocol; + } + } + } + + auto protocolFromName = [](const std::string& protocolName) -> Protocol* { + if (protocolName.empty()) { + return nullptr; + } + + Protocol* runtimeProtocol = objc_getProtocol(protocolName.c_str()); + if (runtimeProtocol != nullptr) { + return runtimeProtocol; + } + + const std::string suffix = "Protocol"; + if (protocolName.length() > suffix.length() && + protocolName.rfind(suffix) == protocolName.length() - suffix.length()) { + std::string strippedName = protocolName.substr(0, protocolName.length() - suffix.length()); + return objc_getProtocol(strippedName.c_str()); + } + + return nullptr; + }; + + napi_valuetype valueType = napi_undefined; + napi_typeof(env, protocolValue, &valueType); + if (valueType == napi_string) { + char protocolNameBuffer[512]; + size_t protocolNameLength = 0; + napi_get_value_string_utf8(env, protocolValue, protocolNameBuffer, sizeof(protocolNameBuffer), + &protocolNameLength); + return protocolFromName(std::string(protocolNameBuffer, protocolNameLength)); + } + + if (valueType == napi_object || valueType == napi_function) { + bool hasName = false; + napi_has_named_property(env, protocolValue, "name", &hasName); + if (hasName) { + napi_value protocolNameValue = nullptr; + napi_get_named_property(env, protocolValue, "name", &protocolNameValue); + napi_valuetype protocolNameType = napi_undefined; + napi_typeof(env, protocolNameValue, &protocolNameType); + if (protocolNameType == napi_string) { + char protocolNameBuffer[512]; + size_t protocolNameLength = 0; + napi_get_value_string_utf8(env, protocolNameValue, protocolNameBuffer, + sizeof(protocolNameBuffer), &protocolNameLength); + return protocolFromName(std::string(protocolNameBuffer, protocolNameLength)); + } + } + } + + return nullptr; +} + +napi_value JS_classConformsToProtocolSafe(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1] = {nullptr}; + napi_value jsThis = nullptr; + napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr); + + Class nativeClass = nullptr; + napi_unwrap(env, jsThis, (void**)&nativeClass); + + bool conforms = false; + if (nativeClass != nullptr && argc > 0) { + Protocol* runtimeProtocol = resolveRuntimeProtocol(env, argv[0]); + if (runtimeProtocol != nullptr) { + conforms = class_conformsToProtocol(nativeClass, runtimeProtocol); + } + } + + napi_value result = nullptr; + napi_get_boolean(env, conforms, &result); + return result; +} + +NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerationState* state, + id __unsafe_unretained stackbuf[], NSUInteger len) { + if (len == 0) { + return 0; + } + + napi_env env = resolveClassBuilderEnv(self); + if (env == nullptr) { + return 0; + } + + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr) { + return 0; + } + + napi_value jsSelf = bridgeState->getObject(env, self, kUnownedObject); + if (jsSelf == nullptr) { + return 0; + } + + napi_value global = nullptr; + napi_value symbolCtor = nullptr; + napi_value symbolIterator = nullptr; + if (napi_get_global(env, &global) != napi_ok || + napi_get_named_property(env, global, "Symbol", &symbolCtor) != napi_ok || + napi_get_named_property(env, symbolCtor, "iterator", &symbolIterator) != napi_ok) { + clearPendingException(env); + return 0; + } + + napi_value iteratorMethod = nullptr; + if (napi_get_property(env, jsSelf, symbolIterator, &iteratorMethod) != napi_ok) { + clearPendingException(env); + return 0; + } + + napi_valuetype iteratorMethodType = napi_undefined; + if (napi_typeof(env, iteratorMethod, &iteratorMethodType) != napi_ok || + iteratorMethodType != napi_function) { + return 0; + } + + napi_value iterator = nullptr; + if (napi_call_function(env, jsSelf, iteratorMethod, 0, nullptr, &iterator) != napi_ok) { + clearPendingException(env); + return 0; + } + + napi_valuetype iteratorType = napi_undefined; + if (napi_typeof(env, iterator, &iteratorType) != napi_ok || + (iteratorType != napi_object && iteratorType != napi_function)) { + return 0; + } + + napi_value nextMethod = nullptr; + if (napi_get_named_property(env, iterator, "next", &nextMethod) != napi_ok) { + clearPendingException(env); + return 0; + } + + napi_valuetype nextType = napi_undefined; + if (napi_typeof(env, nextMethod, &nextType) != napi_ok || nextType != napi_function) { + return 0; + } + + auto callNext = [&](napi_value* nextResult) -> bool { + if (napi_call_function(env, iterator, nextMethod, 0, nullptr, nextResult) != napi_ok) { + clearPendingException(env); + return false; + } + return true; + }; + + auto readDoneFlag = [&](napi_value nextResult, bool* done) -> bool { + napi_value doneValue = nullptr; + if (napi_get_named_property(env, nextResult, "done", &doneValue) != napi_ok) { + clearPendingException(env); + return false; + } + + bool isDone = false; + if (napi_get_value_bool(env, doneValue, &isDone) != napi_ok) { + clearPendingException(env); + return false; + } + + *done = isDone; + return true; + }; + + // Rebuild the iterator from the beginning and skip already yielded values. + // This keeps state handling simple and avoids retaining JS iterators in ObjC. + for (unsigned long skipped = 0; skipped < state->state; skipped++) { + napi_value skippedResult = nullptr; + if (!callNext(&skippedResult)) { + return 0; + } + + bool done = false; + if (!readDoneFlag(skippedResult, &done) || done) { + return 0; + } + } + + const char* objectEncoding = "@"; + auto objectConverter = TypeConv::Make(env, &objectEncoding); + + NSUInteger count = 0; + while (count < len) { + napi_value nextResult = nullptr; + if (!callNext(&nextResult)) { + break; + } + + bool done = false; + if (!readDoneFlag(nextResult, &done)) { + break; + } + + if (done) { + break; + } + + napi_value value = nullptr; + if (napi_get_named_property(env, nextResult, "value", &value) != napi_ok) { + clearPendingException(env); + break; + } + + id nativeValue = nil; + bool shouldFree = false; + bool shouldFreeAny = false; + objectConverter->toNative(env, value, &nativeValue, &shouldFree, &shouldFreeAny); + if (ConsumeNapiArgumentConversionFailure(env)) { + break; + } + stackbuf[count++] = nativeValue; + } + + state->itemsPtr = stackbuf; + state->mutationsPtr = &state->extra[0]; + state->extra[0] = 0; + state->state += count; + + return count; +} +} // namespace + +ClassBuilder::ClassBuilder(napi_env env, napi_value constructor, Class explicitSuperClass) { + this->env = env; + bridgeState = ObjCBridgeState::InstanceData(env); + + metadataOffset = MD_SECTION_OFFSET_NULL; + + Class superClassNative = explicitSuperClass; + if (superClassNative == nullptr) { + napi_value superConstructor; + napi_get_prototype(env, constructor, &superConstructor); + napi_unwrap(env, superConstructor, (void**)&superClassNative); + } + + if (superClassNative == nullptr) { + // If the class does not inherit from a native class, + // by default it inherits from NSObject. + superClassNative = [NSObject class]; + } + + auto superClassIt = bridgeState->classesByPointer.find(superClassNative); + if (superClassIt != bridgeState->classesByPointer.end()) { + superclass = superClassIt->second; + } else { + auto mdClassIt = bridgeState->mdClassesByPointer.find(superClassNative); + if (mdClassIt != bridgeState->mdClassesByPointer.end()) { + superclass = bridgeState->getClass(env, mdClassIt->second); + } else { + superclass = nullptr; + } + } + + napi_value className; + bool hasNativeClassName = false; + napi_has_named_property(env, constructor, "ObjCClassName", &hasNativeClassName); + if (hasNativeClassName) { + napi_get_named_property(env, constructor, "ObjCClassName", &className); + } else { + napi_get_named_property(env, constructor, "name", &className); + } + static char classNameBuf[512]; + napi_get_value_string_utf8(env, className, classNameBuf, 512, nullptr); + + name = classNameBuf; + if (objc_lookUpClass(name.c_str()) != nullptr) { + std::string baseName = name; + do { + name = baseName + "_" + std::to_string(rand()); + } while (objc_lookUpClass(name.c_str()) != nullptr); + } + nativeClass = objc_allocateClassPair(superClassNative, name.c_str(), 0); + + if (nativeClass == nullptr) { + napi_throw_error(env, nullptr, "Failed to allocate class"); + return; + } + + class_addProtocol(nativeClass, @protocol(ObjCBridgeClassBuilderProtocol)); + + objc_registerClassPair(nativeClass); + registerClassBuilderEnv(nativeClass, env); + + napi_remove_wrap(env, constructor, nullptr); + + napi_wrap(env, constructor, (void*)nativeClass, nullptr, nullptr, nullptr); + + napi_value prototype; + napi_get_named_property(env, constructor, "prototype", &prototype); + + this->constructor = make_ref(env, constructor); + this->prototype = make_ref(env, prototype); +} + +ClassBuilder::~ClassBuilder() { + unregisterClassBuilderEnv(nativeClass); + + if (nativeClass != nullptr) { + objc_disposeClassPair(nativeClass); + napi_delete_reference(env, constructor); + napi_delete_reference(env, prototype); + } +} + +void ClassBuilder::addProtocol(ObjCProtocol* protocol) { + if (!protocols.contains(protocol)) { + protocols.emplace(protocol); + // Not always there is a `Protocol` object for the protocol, so we + // need to check if it exists first. + Protocol* proto = objc_getProtocol(protocol->name.c_str()); + if (proto != nullptr) { + class_addProtocol(nativeClass, proto); + } + } +} + +std::vector ClassBuilder::lookupMethodDescriptors(std::string& name, + bool setter) { + std::vector descriptors; + std::unordered_set selectors; + + auto appendDescriptor = [&](MethodDescriptor* descriptor) { + if (descriptor == nullptr || descriptor->selector == nullptr) { + return; + } + if (!selectors.insert(descriptor->selector).second) { + return; + } + descriptors.emplace_back(descriptor); + }; + + // 1. First we look up if there was a custom definition for the method + // in ObjCExposedMethods static of the custom class. + if (!setter) { + auto findExposedMethod = exposedMethods.find(name); + if (findExposedMethod != exposedMethods.end()) { + appendDescriptor(&findExposedMethod->second); + return descriptors; + } + + auto findKnownExposedMethod = gKnownExposedMethods.find(name); + if (findKnownExposedMethod != gKnownExposedMethods.end()) { + appendDescriptor(&findKnownExposedMethod->second); + return descriptors; + } + } + + auto appendMemberDescriptors = [&](ObjCClassMember& member) { + if (setter) { + if (member.methodOrGetter.isProperty && member.setter.selector != nullptr) { + appendDescriptor(&member.setter); + } + return; + } + + appendDescriptor(&member.methodOrGetter); + for (auto& overload : member.overloads) { + appendDescriptor(&overload.method); + } + }; + + // 2. Then walk through the class hierarchy and see if we can find the + // method in the superclass chain. + ObjCClass* currentClass = superclass; + while (currentClass != nullptr) { + // JS extend() overrides target instance members; fall back to a static + // twin only when no instance member exists under this name. + auto findMethod = currentClass->members.find({name, false}); + if (findMethod == currentClass->members.end()) { + findMethod = currentClass->members.find({name, true}); + } + if (findMethod != currentClass->members.end()) { + appendMemberDescriptors(findMethod->second); + if (!descriptors.empty()) { + return descriptors; + } + } + currentClass = currentClass->superclass; + } + + // 3. And finally, look into all protocols implemented (directly or + // indirectly) and try to find the method there. + std::function&)> processProtocols = + [&](std::unordered_set& protocols) { + for (auto protocol : protocols) { + auto findMethod = protocol->members.find({name, false}); + if (findMethod == protocol->members.end()) { + findMethod = protocol->members.find({name, true}); + } + if (findMethod != protocol->members.end()) { + appendMemberDescriptors(findMethod->second); + } + processProtocols(protocol->protocols); + } + }; + + processProtocols(protocols); + return descriptors; +} + +MethodDescriptor* ClassBuilder::lookupMethodDescriptor(std::string& name, bool setter) { + auto descriptors = lookupMethodDescriptors(name, setter); + return descriptors.empty() ? nullptr : descriptors.front(); +} + +void ClassBuilder::addMethod(std::string& name, MethodDescriptor* desc, napi_value key, + napi_value func) { + switch (desc->kind) { + case kMethodDescEncoding: { + const char* encoding = desc->encoding.c_str(); + auto closure = new Closure(env, encoding, false, true); + if (func != nullptr) + closure->func = make_ref(env, func); + else + closure->propertyName = name; + closure->selector = desc->selector; + closure->thisConstructor = constructor; + class_replaceMethod(nativeClass, desc->selector, (IMP)closure->fnptr, encoding); + break; + } + + case kMethodDescSignatureOffset: { + std::string encoding; + auto closure = new Closure(env, bridgeState->metadata, desc->signatureOffset, false, + &encoding, true, desc->isProperty); + if (func != nullptr) + closure->func = make_ref(env, func); + else + closure->propertyName = name; + closure->selector = desc->selector; + closure->thisConstructor = constructor; + class_replaceMethod(nativeClass, desc->selector, (IMP)closure->fnptr, encoding.c_str()); + break; + } + } +} + +void ClassBuilder::build() { + if (isFinal) return; + + isFinal = true; + + napi_value constructor = get_ref_value(env, this->constructor), + prototype = get_ref_value(env, this->prototype); + + // 1. Extract method definitions from ObjCExposedMethods static + + napi_value exposedMethods, exposedMethodNames; + bool hasExposedMethods = false; + napi_has_named_property(env, constructor, "ObjCExposedMethods", &hasExposedMethods); + + if (hasExposedMethods) { + napi_get_named_property(env, constructor, "ObjCExposedMethods", &exposedMethods); + + napi_get_all_property_names(env, exposedMethods, napi_key_own_only, napi_key_skip_symbols, + napi_key_numbers_to_strings, &exposedMethodNames); + + uint32_t exposedMethodCount = 0; + napi_get_array_length(env, exposedMethodNames, &exposedMethodCount); + + for (uint32_t i = 0; i < exposedMethodCount; i++) { + napi_value exposedMethodName; + napi_get_element(env, exposedMethodNames, i, &exposedMethodName); + static char exposedMethodNameBuf[512]; + napi_get_value_string_utf8(env, exposedMethodName, exposedMethodNameBuf, 512, nullptr); + std::string name = exposedMethodNameBuf; + SEL selector = sel_registerName(name.c_str()); + std::string encoding; + + napi_value def, params = nullptr, returns; + napi_get_named_property(env, exposedMethods, name.c_str(), &def); + napi_get_named_property(env, def, "returns", &returns); + + uint32_t paramCount = 0; + bool hasParams = false; + napi_has_named_property(env, def, "params", &hasParams); + if (hasParams) { + napi_get_named_property(env, def, "params", ¶ms); + napi_valuetype paramsType = napi_undefined; + napi_typeof(env, params, ¶msType); + if (paramsType == napi_object) { + bool isArray = false; + napi_is_array(env, params, &isArray); + if (isArray) { + napi_get_array_length(env, params, ¶mCount); + } else { + napi_throw_type_error(env, nullptr, + "ObjCExposedMethods params must be an array when provided"); + return; + } + } else if (paramsType != napi_undefined && paramsType != napi_null) { + napi_throw_type_error(env, nullptr, + "ObjCExposedMethods params must be an array when provided"); + return; + } + } + + encoding += getEncodedType(env, returns); + encoding += "@:"; + + for (uint32_t j = 0; j < paramCount; j++) { + napi_value param; + napi_get_element(env, params, j, ¶m); + std::string enctype = getEncodedType(env, param); + if (enctype == "v") { + napi_throw_error(env, nullptr, "Void type not allowed in method params"); + return; + } + encoding += enctype; + } + + auto descriptor = MethodDescriptor(selector, encoding); + this->exposedMethods[name] = descriptor; + gKnownExposedMethods[name] = descriptor; + gKnownExposedMethods[jsifySelector(name)] = descriptor; + } + } + + // 2. Extract the protocol references from ObjCProtocols static + + napi_value protocols, protocol; + bool hasProtocols = false; + napi_has_named_property(env, constructor, "ObjCProtocols", &hasProtocols); + + if (hasProtocols) { + napi_get_named_property(env, constructor, "ObjCProtocols", &protocols); + + uint32_t i = 0; + uint32_t length = 0; + napi_get_array_length(env, protocols, &length); + + while (i < length) { + napi_get_element(env, protocols, i, &protocol); + static char protocolNameBuf[512]; + napi_get_value_string_utf8(env, protocol, protocolNameBuf, 512, nullptr); + ObjCProtocol* proto = nullptr; + napi_unwrap(env, protocol, (void**)&proto); + if (proto != nullptr) addProtocol(proto); + i++; + } + } + + // 3. Walk over methods defined on the prototype and add them to the ObjC + // class if we can find the corresponding method descriptor. + + napi_value properties; + + napi_get_all_property_names(env, prototype, napi_key_own_only, napi_key_skip_symbols, + napi_key_numbers_to_strings, &properties); + + uint32_t propertyCount = 0; + napi_get_array_length(env, properties, &propertyCount); + + napi_value global, objectCtor, getOwnPropertyDescriptor; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &objectCtor); + napi_get_named_property(env, objectCtor, "getOwnPropertyDescriptor", &getOwnPropertyDescriptor); + + bool hasIteratorMethod = false; + napi_value symbolCtor = nullptr; + napi_value symbolIterator = nullptr; + napi_get_named_property(env, global, "Symbol", &symbolCtor); + napi_get_named_property(env, symbolCtor, "iterator", &symbolIterator); + napi_has_own_property(env, prototype, symbolIterator, &hasIteratorMethod); + if (hasIteratorMethod) { + class_addProtocol(nativeClass, @protocol(NSFastEnumeration)); + const char* fastEnumerationEncoding = NSFastEnumerationMethodEncoding(); + if (fastEnumerationEncoding != nullptr) { + class_replaceMethod(nativeClass, @selector(countByEnumeratingWithState:objects:count:), + (IMP)JS_SymbolIteratorCountByEnumerating, fastEnumerationEncoding); + } + } + + uint32_t i = 0; + while (i < propertyCount) { + napi_value property; + napi_get_element(env, properties, i, &property); + static char propertyNameBuf[512]; + napi_get_value_string_utf8(env, property, propertyNameBuf, 512, nullptr); + std::string name = propertyNameBuf; + napi_value descriptorArgs[] = {prototype, property}; + napi_value propertyDescriptor; + napi_call_function(env, objectCtor, getOwnPropertyDescriptor, 2, descriptorArgs, + &propertyDescriptor); + + napi_valuetype descriptorType; + napi_typeof(env, propertyDescriptor, &descriptorType); + if (descriptorType == napi_undefined || descriptorType == napi_null) { + i++; + continue; + } + + bool hasValue = false; + napi_has_named_property(env, propertyDescriptor, "value", &hasValue); + if (hasValue) { + napi_value methodFunc; + napi_get_named_property(env, propertyDescriptor, "value", &methodFunc); + napi_valuetype funcType = napi_undefined; + napi_typeof(env, methodFunc, &funcType); + if (funcType == napi_function) { + auto descriptors = lookupMethodDescriptors(name); + for (MethodDescriptor* descriptor : descriptors) { + if (descriptor != nullptr) { + addMethod(name, descriptor, property, methodFunc); + } + } + } + } + + bool hasGetter = false; + napi_has_named_property(env, propertyDescriptor, "get", &hasGetter); + if (hasGetter) { + napi_value getterFunc; + napi_get_named_property(env, propertyDescriptor, "get", &getterFunc); + napi_valuetype getterType = napi_undefined; + napi_typeof(env, getterFunc, &getterType); + if (getterType == napi_function) { + auto getterDescs = lookupMethodDescriptors(name); + for (MethodDescriptor* getterDesc : getterDescs) { + if (getterDesc != nullptr) { + addMethod(name, getterDesc, property, getterFunc); + } + } + } + } + + bool hasSetter = false; + napi_has_named_property(env, propertyDescriptor, "set", &hasSetter); + if (hasSetter) { + napi_value setterFunc; + napi_get_named_property(env, propertyDescriptor, "set", &setterFunc); + napi_valuetype setterType = napi_undefined; + napi_typeof(env, setterFunc, &setterType); + if (setterType == napi_function) { + auto setterDescs = lookupMethodDescriptors(name, true); + for (MethodDescriptor* setterDesc : setterDescs) { + if (setterDesc != nullptr) { + addMethod(name, setterDesc, property, setterFunc); + } + } + } + } + i++; + } +} + +// Static method to extend native classes with JavaScript-defined methods +napi_value ClassBuilder::ExtendCallback(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + napi_value thisArg; + + napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr); + + if (argc < 1) { + napi_throw_error(env, nullptr, + "extend() requires at least one parameter with method definitions"); + return nullptr; + } + + // Validate that the first argument is an object + napi_valuetype argType; + napi_typeof(env, args[0], &argType); + if (argType != napi_object) { + napi_throw_error(env, nullptr, "extend() first parameter must be an object"); + return nullptr; + } + + // Get the native class from 'this' (the constructor function) + Class baseNativeClass = nullptr; + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + bridgeState->tryResolveBridgedClassConstructor(env, thisArg, &baseNativeClass); + } + if (baseNativeClass == nullptr) { + napi_unwrap(env, thisArg, (void**)&baseNativeClass); + } + + if (baseNativeClass == nullptr) { + napi_throw_error(env, nullptr, "extend() can only be called on native class constructors"); + return nullptr; + } + + if (class_conformsToProtocol(baseNativeClass, @protocol(ObjCBridgeClassBuilderProtocol))) { + napi_throw_error(env, nullptr, "Cannot extend an already extended class."); + return nullptr; + } + + napi_value options = nullptr; + bool hasOptionsObject = false; + if (argc >= 2) { + napi_valuetype secondArgType; + napi_typeof(env, args[1], &secondArgType); + hasOptionsObject = secondArgType == napi_object; + if (hasOptionsObject) { + options = args[1]; + } + } + + // Create a class name. + napi_value baseClassName; + napi_get_named_property(env, thisArg, "name", &baseClassName); + static char baseClassNameBuf[512]; + napi_get_value_string_utf8(env, baseClassName, baseClassNameBuf, 512, nullptr); + + std::string requestedName; + if (hasOptionsObject) { + bool hasCustomName = false; + napi_has_named_property(env, options, "name", &hasCustomName); + if (hasCustomName) { + napi_value customNameValue = nullptr; + napi_get_named_property(env, options, "name", &customNameValue); + napi_valuetype customNameType = napi_undefined; + napi_typeof(env, customNameValue, &customNameType); + if (customNameType == napi_string) { + static char customNameBuf[512]; + size_t customNameLen = 0; + napi_get_value_string_utf8(env, customNameValue, customNameBuf, sizeof(customNameBuf), + &customNameLen); + if (customNameLen > 0) { + requestedName.assign(customNameBuf, customNameLen); + } + } + } + } + + std::string newClassName; + bool shouldAliasRequestedName = false; + if (!requestedName.empty()) { + newClassName = requestedName; + Class existingClass = objc_lookUpClass(newClassName.c_str()); + auto nextAvailableClassName = [](const std::string& baseName) { + size_t suffix = 1; + std::string candidate; + do { + candidate = baseName + std::to_string(suffix++); + } while (objc_lookUpClass(candidate.c_str()) != nullptr); + return candidate; + }; + if (existingClass != nullptr) { + newClassName = nextAvailableClassName(requestedName); + shouldAliasRequestedName = + !class_conformsToProtocol(existingClass, @protocol(ObjCBridgeClassBuilderProtocol)); + } + } else { + newClassName = baseClassNameBuf; + newClassName += "_Extended_"; + newClassName += std::to_string(rand()); + } + // Create the new constructor function that extends the base + napi_value newConstructor; + napi_define_class(env, newClassName.c_str(), newClassName.length(), JS_BridgedConstructor, + nullptr, 0, nullptr, &newConstructor); + + // Set up JavaScript inheritance from the base class + napi_inherits(env, newConstructor, thisArg); + + napi_value safeConformsToProtocol = nullptr; + napi_create_function(env, "conformsToProtocol", NAPI_AUTO_LENGTH, JS_classConformsToProtocolSafe, + nullptr, &safeConformsToProtocol); + napi_set_named_property(env, newConstructor, "conformsToProtocol", safeConformsToProtocol); + + // Get prototype for adding methods + napi_value newPrototype, basePrototype; + napi_get_named_property(env, newConstructor, "prototype", &newPrototype); + napi_get_named_property(env, thisArg, "prototype", &basePrototype); + + // Copy methods and accessors preserving descriptors. + napi_value global, objectCtor, getOwnPropertyDescriptors, defineProperties; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &objectCtor); + napi_get_named_property(env, objectCtor, "getOwnPropertyDescriptors", &getOwnPropertyDescriptors); + napi_get_named_property(env, objectCtor, "defineProperties", &defineProperties); + + napi_value descriptors; + napi_call_function(env, objectCtor, getOwnPropertyDescriptors, 1, args, &descriptors); + + napi_value defineArgs[] = {newPrototype, descriptors}; + napi_call_function(env, objectCtor, defineProperties, 2, defineArgs, nullptr); + + napi_value installSuperAccessor = nullptr; + napi_value installSuperScript = nullptr; + napi_create_string_utf8(env, kInstallSuperAccessorSource, NAPI_AUTO_LENGTH, &installSuperScript); + napi_run_script(env, installSuperScript, &installSuperAccessor); + napi_value superArgs[] = {newPrototype, basePrototype}; + napi_call_function(env, global, installSuperAccessor, 2, superArgs, nullptr); + + // Handle optional second parameter for metadata/options + if (hasOptionsObject) { + napi_value exposedMethods; + bool hasExposedMethods = false; + napi_has_named_property(env, options, "exposedMethods", &hasExposedMethods); + + if (hasExposedMethods) { + napi_get_named_property(env, options, "exposedMethods", &exposedMethods); + napi_set_named_property(env, newConstructor, "ObjCExposedMethods", exposedMethods); + } + + napi_value protocols; + bool hasProtocols = false; + napi_has_named_property(env, options, "protocols", &hasProtocols); + + if (hasProtocols) { + napi_get_named_property(env, options, "protocols", &protocols); + napi_set_named_property(env, newConstructor, "ObjCProtocols", protocols); + } + } + + napi_value classNameValue = nullptr; + napi_create_string_utf8(env, newClassName.c_str(), newClassName.length(), &classNameValue); + napi_set_named_property(env, newConstructor, "ObjCClassName", classNameValue); + + napi_value registryGlobal = nullptr; + napi_value classRegistry = nullptr; + bool hasClassRegistry = false; + napi_get_global(env, ®istryGlobal); + napi_has_named_property(env, registryGlobal, "__nsConstructorsByObjCClassName", + &hasClassRegistry); + if (!hasClassRegistry) { + napi_create_object(env, &classRegistry); + napi_set_named_property(env, registryGlobal, "__nsConstructorsByObjCClassName", classRegistry); + } else { + napi_get_named_property(env, registryGlobal, "__nsConstructorsByObjCClassName", &classRegistry); + } + + napi_value installClassFromStringAliasScript = nullptr; + napi_value installClassFromStringAlias = nullptr; + if (napi_create_string_utf8(env, kInstallClassFromStringAliasSource, NAPI_AUTO_LENGTH, + &installClassFromStringAliasScript) == napi_ok && + napi_run_script(env, installClassFromStringAliasScript, &installClassFromStringAlias) == + napi_ok && + installClassFromStringAlias != nullptr) { + napi_value aliasArgs[] = {registryGlobal}; + if (napi_call_function(env, registryGlobal, installClassFromStringAlias, 1, aliasArgs, + nullptr) != napi_ok) { + clearPendingException(env); + } + } else { + clearPendingException(env); + } + + if (classRegistry != nullptr) { + napi_set_named_property(env, classRegistry, newClassName.c_str(), newConstructor); + if (shouldAliasRequestedName && !requestedName.empty()) { + napi_set_named_property(env, classRegistry, requestedName.c_str(), newConstructor); + } + } + + // Use ClassBuilder to create the native class and bridge the methods + ClassBuilder* builder = new ClassBuilder(env, newConstructor, baseNativeClass); + builder->build(); + + // Register the builder in the bridge state + bridgeState = ObjCBridgeState::InstanceData(env); + bridgeState->registerRuntimeClass(builder, builder->nativeClass); + + return newConstructor; +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/ClassMember.h b/NativeScript/ffi/objc/napi/ClassMember.h similarity index 100% rename from NativeScript/ffi/napi/ClassMember.h rename to NativeScript/ffi/objc/napi/ClassMember.h diff --git a/NativeScript/ffi/napi/ClassMember.mm b/NativeScript/ffi/objc/napi/ClassMember.mm similarity index 97% rename from NativeScript/ffi/napi/ClassMember.mm rename to NativeScript/ffi/objc/napi/ClassMember.mm index 772901273..e7c102bcb 100644 --- a/NativeScript/ffi/napi/ClassMember.mm +++ b/NativeScript/ffi/objc/napi/ClassMember.mm @@ -22,10 +22,10 @@ #include "SignatureDispatch.h" #include "TypeConv.h" #include "Util.h" +#include "runtime/apple/NativeScriptException.h" #include "js_native_api.h" #include "js_native_api_types.h" #include "node_api_util.h" -#include "runtime/NativeScriptException.h" namespace nativescript { @@ -410,8 +410,13 @@ inline bool objcNativeCall(napi_env env, Cif* cif, id self, bool classMethod, bool isStret = cif->returnType->type->size > 16 && cif->returnType->type->type == FFI_TYPE_STRUCT; #endif + NapiNativeCallbackExceptionCapture callbackException; + ScopedNapiNativeCallbackExceptionCapture callbackExceptionCapture( + &callbackException); + @try { if (!supercall) { + bool preparedInvoked = false; if (cif != nullptr && cif->signatureHash != 0) { if (descriptor != nullptr && (!descriptor->dispatchLookupCached || @@ -435,22 +440,24 @@ inline bool objcNativeCall(napi_env env, Cif* cif, id self, bool classMethod, if (invoker != nullptr) { NativeCallRuntimeUnlockScope unlockRuntime(env); invoker((void*)objc_msgSend, avalues, rvalue); - return true; + preparedInvoked = true; } } + if (!preparedInvoked) { #if defined(__x86_64__) - if (isStret) { - NativeCallRuntimeUnlockScope unlockRuntime(env); - ffi_call(&cif->cif, FFI_FN(objc_msgSend_stret), rvalue, avalues); - } else { + if (isStret) { + NativeCallRuntimeUnlockScope unlockRuntime(env); + ffi_call(&cif->cif, FFI_FN(objc_msgSend_stret), rvalue, avalues); + } else { + NativeCallRuntimeUnlockScope unlockRuntime(env); + ffi_call(&cif->cif, FFI_FN(objc_msgSend), rvalue, avalues); + } +#else NativeCallRuntimeUnlockScope unlockRuntime(env); ffi_call(&cif->cif, FFI_FN(objc_msgSend), rvalue, avalues); - } -#else - NativeCallRuntimeUnlockScope unlockRuntime(env); - ffi_call(&cif->cif, FFI_FN(objc_msgSend), rvalue, avalues); #endif + } } else { Class superClass = classMethod ? class_getSuperclass(object_getClass((id)receiverClass)) : class_getSuperclass(receiverClass); @@ -478,6 +485,10 @@ inline bool objcNativeCall(napi_env env, Cif* cif, id self, bool classMethod, return false; } + if (rethrowNapiNativeCallbackException(env, callbackException)) { + return false; + } + return true; } @@ -1393,6 +1404,17 @@ explicit CifReturnStorage(Cif* cif) { shouldFree[i] = false; avalues[i + 2] = argStorage.at(i); cif->argTypes[i]->toNative(env, cif->argv[i], avalues[i + 2], &shouldFree[i], &shouldFreeAny); + if (ConsumeNapiArgumentConversionFailure(env)) { + for (unsigned int converted = 0; converted <= i; converted++) { + if (shouldFree[converted]) { + cif->argTypes[converted]->free(env, *((void**)avalues[converted + 2])); + } + } + if (retainedReceiver) { + [self release]; + } + return nullptr; + } } } @@ -1741,12 +1763,28 @@ explicit CifReturnStorage(Cif* cif) { bool shouldFreeAny = false; bool shouldFree[cif->argc]; + memset(shouldFree, 0, sizeof(shouldFree)); std::vector fallbackBlocksToRelease; NSError* implicitNSError = nil; + auto cleanupArguments = [&]() { + for (id block : fallbackBlocksToRelease) { + [block release]; + } + fallbackBlocksToRelease.clear(); + + if (!shouldFreeAny) { + return; + } + for (unsigned int i = 0; i < cif->argc; i++) { + if (shouldFree[i]) { + cif->argTypes[i]->free(env, *reinterpret_cast(avalues[i + 2])); + } + } + }; + if (cif->argc > 0) { for (unsigned int i = 0; i < cif->argc; i++) { - shouldFree[i] = false; avalues[i + 2] = argStorage.at(i); const char* blockEncoding = blockEncodingForSelector(selectedSelectorName, i); @@ -1772,6 +1810,12 @@ explicit CifReturnStorage(Cif* cif) { if (!convertedViaBlockFallback) { cif->argTypes[i]->toNative(env, invocationArgs[i], avalues[i + 2], &shouldFree[i], &shouldFreeAny); + bool conversionFailed = false; + if (ConsumeNapiArgumentConversionFailure(env) || + napi_is_exception_pending(env, &conversionFailed) != napi_ok || conversionFailed) { + cleanupArguments(); + return nullptr; + } } } } @@ -1780,23 +1824,11 @@ explicit CifReturnStorage(Cif* cif) { if (!objcNativeCall(env, cif, self, receiverIsClass, selectedMethod, selectedMethod->dispatchFlags, avalues, rvalue)) { - for (id block : fallbackBlocksToRelease) { - [block release]; - } + cleanupArguments(); return nullptr; } - for (id block : fallbackBlocksToRelease) { - [block release]; - } - - if (shouldFreeAny) { - for (unsigned int i = 0; i < cif->argc; i++) { - if (shouldFree[i]) { - cif->argTypes[i]->free(env, *((void**)avalues[i + 2])); - } - } - } + cleanupArguments(); if (hasImplicitNSErrorOutArg && implicitNSError != nil) { const char* errorMessage = [[implicitNSError description] UTF8String]; @@ -1965,6 +1997,12 @@ NativeScriptException nativeScriptException(errorMessage != nullptr ? errorMessa bool shouldFree = false; cif->argTypes[0]->toNative(env, value, avalues[2], &shouldFree, &shouldFree); + if (ConsumeNapiArgumentConversionFailure(env)) { + if (shouldFree) { + cif->argTypes[0]->free(env, *((void**)avalues[2])); + } + return nullptr; + } if (!objcNativeCall(env, cif, self, receiverIsClass, &method->setter, method->setter.dispatchFlags, avalues, rvalue)) { diff --git a/NativeScript/ffi/objc/napi/Closure.h b/NativeScript/ffi/objc/napi/Closure.h new file mode 100644 index 000000000..d81a43f4d --- /dev/null +++ b/NativeScript/ffi/objc/napi/Closure.h @@ -0,0 +1,88 @@ +#ifndef CLOSURE_H +#define CLOSURE_H + +#include + +#include +#include +#include + +#include "MetadataReader.h" +#include "TypeConv.h" +#include "ffi.h" +#include "node_api_util.h" +#include "objc/runtime.h" + +namespace nativescript { + +class ObjCBridgeState; + +struct NapiNativeCallbackExceptionCapture { + napi_env env = nullptr; + napi_ref errorRef = nullptr; + + ~NapiNativeCallbackExceptionCapture(); + void clear(); +}; + +class ScopedNapiNativeCallbackExceptionCapture { + public: + explicit ScopedNapiNativeCallbackExceptionCapture( + NapiNativeCallbackExceptionCapture* capture); + ~ScopedNapiNativeCallbackExceptionCapture(); + + ScopedNapiNativeCallbackExceptionCapture( + const ScopedNapiNativeCallbackExceptionCapture&) = delete; + ScopedNapiNativeCallbackExceptionCapture& operator=( + const ScopedNapiNativeCallbackExceptionCapture&) = delete; + + private: + NapiNativeCallbackExceptionCapture* capture_ = nullptr; +}; + +bool recordNapiNativeCallbackException(napi_env env, napi_value error); +bool rethrowNapiNativeCallbackException( + napi_env env, NapiNativeCallbackExceptionCapture& capture); + +class Closure { + public: + static void callBlockFromMainThread(napi_env env, napi_value js_cb, + void* context, void* data); + static void destroyOnOwningThread(Closure* closure); + + Closure(napi_env env, std::string typeEncoding, bool isBlock, bool isMethod = false); + Closure(napi_env env, MDMetadataReader* reader, MDSectionOffset offset, + bool isBlock = false, std::string* encoding = nullptr, + bool isMethod = false, bool isGetter = false, bool isSetter = false); + + ~Closure(); + void retain(); + void release(); + + napi_env env = nullptr; + ObjCBridgeState* bridgeState = nullptr; + uint64_t bridgeStateToken = 0; + napi_ref thisConstructor; + napi_ref func = nullptr; + bool isGetter = false; + bool isSetter = false; + std::string propertyName; + SEL selector = nullptr; + napi_threadsafe_function tsfn = nullptr; + + std::thread::id jsThreadId = std::this_thread::get_id(); + CFRunLoopRef jsRunLoop = CFRunLoopGetCurrent(); + std::atomic retainCount{1}; + + ffi_cif cif; + ffi_closure* closure; + void* fnptr; + ffi_type** atypes = nullptr; // Track malloc'd atypes array + + std::shared_ptr returnType; + std::vector> argTypes; +}; + +} // namespace nativescript + +#endif /* CLOSURE_H */ diff --git a/NativeScript/ffi/napi/Closure.mm b/NativeScript/ffi/objc/napi/Closure.mm similarity index 85% rename from NativeScript/ffi/napi/Closure.mm rename to NativeScript/ffi/objc/napi/Closure.mm index ab90659ca..a04380486 100644 --- a/NativeScript/ffi/napi/Closure.mm +++ b/NativeScript/ffi/objc/napi/Closure.mm @@ -6,7 +6,7 @@ #include "ObjCBridge.h" #include "TypeConv.h" #include "Util.h" -#include "runtime/NativeScriptException.h" +#include "runtime/apple/NativeScriptException.h" #include "js_native_api.h" #include "js_native_api_types.h" #ifdef ENABLE_JS_RUNTIME @@ -28,6 +28,9 @@ namespace { +thread_local std::vector + gNativeCallbackExceptionCaptureStack; + inline void deleteClosureOnOwningThread(Closure* closure) { if (closure == nullptr) { return; @@ -61,6 +64,73 @@ inline void deleteClosureOnOwningThread(Closure* closure) { } // namespace +NapiNativeCallbackExceptionCapture::~NapiNativeCallbackExceptionCapture() { + clear(); +} + +void NapiNativeCallbackExceptionCapture::clear() { + if (env != nullptr && errorRef != nullptr) { + napi_delete_reference(env, errorRef); + } + env = nullptr; + errorRef = nullptr; +} + +ScopedNapiNativeCallbackExceptionCapture:: + ScopedNapiNativeCallbackExceptionCapture( + NapiNativeCallbackExceptionCapture* capture) + : capture_(capture) { + gNativeCallbackExceptionCaptureStack.push_back(capture_); +} + +ScopedNapiNativeCallbackExceptionCapture:: + ~ScopedNapiNativeCallbackExceptionCapture() { + if (!gNativeCallbackExceptionCaptureStack.empty() && + gNativeCallbackExceptionCaptureStack.back() == capture_) { + gNativeCallbackExceptionCaptureStack.pop_back(); + } +} + +bool recordNapiNativeCallbackException(napi_env env, napi_value error) { + if (env == nullptr || error == nullptr || + gNativeCallbackExceptionCaptureStack.empty()) { + return false; + } + + auto* capture = gNativeCallbackExceptionCaptureStack.back(); + if (capture == nullptr || capture->errorRef != nullptr) { + return capture != nullptr; + } + + capture->env = env; + return napi_create_reference(env, error, 1, &capture->errorRef) == napi_ok; +} + +bool rethrowNapiNativeCallbackException( + napi_env env, NapiNativeCallbackExceptionCapture& capture) { + if (env == nullptr || capture.errorRef == nullptr) { + return false; + } + + napi_value error = nullptr; + napi_ref errorRef = capture.errorRef; + capture.errorRef = nullptr; + napi_get_reference_value(env, errorRef, &error); + napi_delete_reference(env, errorRef); + capture.env = nullptr; + + if (error != nullptr) { + NativeScriptException nativeScriptException( + env, error, "JS implemented closure threw an exception"); + nativeScriptException.ReThrowToJS(env); + } else { + NativeScriptException nativeScriptException( + "Unable to obtain the error thrown by the JS implemented closure"); + nativeScriptException.ReThrowToJS(env); + } + return true; +} + void Closure::destroyOnOwningThread(Closure* closure) { deleteClosureOnOwningThread(closure); } inline bool selectorEndsWithErrorParam(SEL selector) { @@ -135,13 +205,18 @@ inline void JSCallbackInner(Closure* closure, napi_value func, napi_value thisAr napi_create_error(env, code, msg, &result); } - NativeScriptException::OnUncaughtError(env, result); + if (recordNapiNativeCallbackException(env, result)) { + napi_get_undefined(env, &result); + } else { + NativeScriptException::OnUncaughtError(env, result); + } } // Even if call was failed and result is just undefined, let's still try to // fill the return value memory with something so that it doesn't crash. bool shouldFree; closure->returnType->toNative(env, result, ret, &shouldFree, &shouldFree); + ConsumeNapiArgumentConversionFailure(env); } // Bridge calls from Objective-C to JavaScript. @@ -251,11 +326,16 @@ void JSMethodCallback(ffi_cif* cif, void* ret, void* args[], void* data) { napi_create_error(env, code, msg, &result); } - NativeScriptException::OnUncaughtError(env, result); + if (recordNapiNativeCallbackException(env, result)) { + napi_get_undefined(env, &result); + } else { + NativeScriptException::OnUncaughtError(env, result); + } } bool shouldFree; closure->returnType->toNative(env, result, ret, &shouldFree, &shouldFree); + ConsumeNapiArgumentConversionFailure(env); } void JSFunctionCallback(ffi_cif* cif, void* ret, void* args[], void* data) { @@ -479,7 +559,9 @@ void JSBlockCallback(ffi_cif* cif, void* ret, void* args[], void* data) { } ffi_status status = - ffi_prep_cif(&cif, FFI_DEFAULT_ABI, argTypes.size() + skipArgs, rtype, this->atypes); + ffi_prep_cif(&cif, FFI_DEFAULT_ABI, + static_cast(argTypes.size() + skipArgs), rtype, + this->atypes); if (status != FFI_OK) { std::cout << "Failed to prepare CIF, libffi returned error:" << status << std::endl; diff --git a/NativeScript/ffi/napi/Enum.h b/NativeScript/ffi/objc/napi/Enum.h similarity index 100% rename from NativeScript/ffi/napi/Enum.h rename to NativeScript/ffi/objc/napi/Enum.h diff --git a/NativeScript/ffi/napi/Enum.mm b/NativeScript/ffi/objc/napi/Enum.mm similarity index 100% rename from NativeScript/ffi/napi/Enum.mm rename to NativeScript/ffi/objc/napi/Enum.mm diff --git a/NativeScript/ffi/napi/InlineFunctions.h b/NativeScript/ffi/objc/napi/InlineFunctions.h similarity index 100% rename from NativeScript/ffi/napi/InlineFunctions.h rename to NativeScript/ffi/objc/napi/InlineFunctions.h diff --git a/NativeScript/ffi/napi/InlineFunctions.mm b/NativeScript/ffi/objc/napi/InlineFunctions.mm similarity index 100% rename from NativeScript/ffi/napi/InlineFunctions.mm rename to NativeScript/ffi/objc/napi/InlineFunctions.mm diff --git a/NativeScript/ffi/napi/Interop.h b/NativeScript/ffi/objc/napi/Interop.h similarity index 97% rename from NativeScript/ffi/napi/Interop.h rename to NativeScript/ffi/objc/napi/Interop.h index e2c095296..bff58896f 100644 --- a/NativeScript/ffi/napi/Interop.h +++ b/NativeScript/ffi/objc/napi/Interop.h @@ -10,6 +10,7 @@ namespace nativescript { class ObjCBridgeState; void registerInterop(napi_env env, napi_value global); +bool unwrapKnownNativeHandle(napi_env env, napi_value value, void** out); napi_value interop_addMethod(napi_env env, napi_callback_info info); napi_value interop_addProtocol(napi_env env, napi_callback_info info); @@ -46,6 +47,7 @@ class Pointer { void* data; bool adopted = false; + bool cached = false; }; class Reference { diff --git a/NativeScript/ffi/objc/napi/Interop.mm b/NativeScript/ffi/objc/napi/Interop.mm new file mode 100644 index 000000000..012ebe624 --- /dev/null +++ b/NativeScript/ffi/objc/napi/Interop.mm @@ -0,0 +1,2220 @@ +#include "Interop.h" +#include "ClassBuilder.h" +#include "Metadata.h" +#include "ObjCBridge.h" +#include "Util.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "node_api_util.h" + +#import +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nativescript { + +namespace { +constexpr const char* kPointerMarker = "__ns_pointer"; +constexpr const char* kNativePointerProperty = "__ns_native_ptr"; +constexpr const char* kReferenceMarker = "__ns_reference"; +constexpr const char* kFunctionReferenceMarker = "__ns_function_reference"; +constexpr const char* kFunctionReferenceDataProperty = "__ns_function_reference_data"; +constexpr const char* kReferenceInitValueSymbolKey = "__ns_reference_init_value"; + +inline uintptr_t pointerKey(void* data) { return reinterpret_cast(data); } + +inline napi_value referenceInitValueSymbol(napi_env env) { + return jsSymbolFor(env, kReferenceInitValueSymbolKey); +} + +inline napi_value getReferenceInitValueProperty(napi_env env, napi_value value) { + if (value == nullptr) { + return nullptr; + } + + napi_value initValue = nullptr; + napi_get_property(env, value, referenceInitValueSymbol(env), &initValue); + return initValue; +} + +inline void setReferenceInitValueProperty(napi_env env, napi_value value, napi_value initValue) { + if (value == nullptr || initValue == nullptr) { + return; + } + + napi_set_property(env, value, referenceInitValueSymbol(env), initValue); +} + +inline void clearReferenceInitValueProperty(napi_env env, napi_value value) { + if (value == nullptr) { + return; + } + + bool deleted = false; + napi_delete_property(env, value, referenceInitValueSymbol(env), &deleted); +} + +inline bool isInteropTypeCode(int32_t value) { + switch (value) { + case mdTypeVoid: + case mdTypeBool: + case mdTypeChar: + case mdTypeUInt8: + case mdTypeSShort: + case mdTypeUShort: + case mdTypeUnichar: + case mdTypeSInt: + case mdTypeUInt: + case mdTypeSInt64: + case mdTypeUInt64: + case mdTypeFloat: + case mdTypeDouble: + case mdTypeString: + case mdTypeAnyObject: + case mdTypePointer: + case mdTypeSelector: + return true; + default: + return false; + } +} + +inline size_t referenceElementSize(Reference* ref) { + if (ref == nullptr || ref->type == nullptr || ref->type->type == nullptr || + ref->type->type->size == 0) { + return sizeof(void*); + } + + return ref->type->type->size; +} + +inline bool isArrayIndexProperty(napi_env env, napi_value property, uint32_t* index) { + if (index == nullptr) { + return false; + } + + napi_valuetype propertyType = napi_undefined; + napi_typeof(env, property, &propertyType); + + if (propertyType == napi_number) { + double number = 0; + if (napi_get_value_double(env, property, &number) != napi_ok) { + return false; + } + + if (!std::isfinite(number) || number < 0 || floor(number) != number || + number > static_cast(UINT32_MAX)) { + return false; + } + + *index = static_cast(number); + return true; + } + + if (propertyType != napi_string) { + return false; + } + + char chars[32]; + size_t length = 0; + if (napi_get_value_string_utf8(env, property, chars, sizeof(chars), &length) != napi_ok || + length == 0 || length >= sizeof(chars)) { + return false; + } + + for (size_t i = 0; i < length; i++) { + if (!std::isdigit(static_cast(chars[i]))) { + return false; + } + } + + char* end = nullptr; + unsigned long long parsed = std::strtoull(chars, &end, 10); + if (end == nullptr || *end != '\0' || parsed > static_cast(UINT32_MAX)) { + return false; + } + + *index = static_cast(parsed); + return true; +} + +inline napi_value referenceValueAtIndex(napi_env env, Reference* ref, uint32_t index) { + napi_value undefined; + napi_get_undefined(env, &undefined); + + if (ref == nullptr || ref->type == nullptr || ref->data == nullptr) { + return undefined; + } + + auto slot = + static_cast(ref->data) + (static_cast(index) * referenceElementSize(ref)); + napi_value value = ref->type->toJS(env, slot); + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType == napi_boolean) { + bool boolValue = false; + napi_get_value_bool(env, value, &boolValue); + napi_create_uint32(env, boolValue ? 1 : 0, &value); + } + return value; +} + +inline bool referenceSetValueAtIndex(napi_env env, Reference* ref, uint32_t index, + napi_value value) { + if (ref == nullptr || ref->type == nullptr || ref->data == nullptr) { + napi_throw_error(env, nullptr, "Reference is not initialized"); + return false; + } + + auto slot = + static_cast(ref->data) + (static_cast(index) * referenceElementSize(ref)); + bool shouldFree = false; + ref->type->toNative(env, value, slot, &shouldFree, &shouldFree); + + if (ConsumeNapiArgumentConversionFailure(env)) { + return false; + } + bool hasPendingException = false; + napi_is_exception_pending(env, &hasPendingException); + return !hasPendingException; +} + +napi_value referenceProxyGetter(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value argv[3] = {nullptr, nullptr, nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + napi_value target = argc > 0 ? argv[0] : nullptr; + napi_value property = argc > 1 ? argv[1] : nullptr; + + uint32_t index = 0; + if (property != nullptr && isArrayIndexProperty(env, property, &index)) { + return referenceValueAtIndex(env, Reference::unwrap(env, target), index); + } + + napi_value result; + if (napi_get_property(env, target, property, &result) != napi_ok) { + return nullptr; + } + + napi_valuetype resultType = napi_undefined; + napi_typeof(env, result, &resultType); + if (resultType == napi_function) { + napi_value bind; + if (napi_get_named_property(env, result, "bind", &bind) == napi_ok) { + napi_value bound; + napi_value bindArgs[1] = {target}; + if (napi_call_function(env, result, bind, 1, bindArgs, &bound) == napi_ok) { + return bound; + } + } + } + + return result; +} + +napi_value referenceProxySetter(napi_env env, napi_callback_info info) { + size_t argc = 4; + napi_value argv[4] = {nullptr, nullptr, nullptr, nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + napi_value target = argc > 0 ? argv[0] : nullptr; + napi_value property = argc > 1 ? argv[1] : nullptr; + napi_value value = argc > 2 ? argv[2] : nullptr; + + uint32_t index = 0; + bool ok = true; + if (property != nullptr && isArrayIndexProperty(env, property, &index)) { + ok = referenceSetValueAtIndex(env, Reference::unwrap(env, target), index, value); + } else { + ok = napi_set_property(env, target, property, value) == napi_ok; + } + + napi_value result; + napi_get_boolean(env, ok, &result); + return result; +} + +inline napi_value createReferenceProxy(napi_env env, napi_value target, Reference* reference) { + napi_value global; + napi_get_global(env, &global); + + napi_value proxyCtor; + if (napi_get_named_property(env, global, "Proxy", &proxyCtor) != napi_ok) { + return target; + } + + napi_value handler; + napi_create_object(env, &handler); + const napi_property_descriptor handlerProperties[] = { + { + .utf8name = "get", + .method = referenceProxyGetter, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_default, + .data = nullptr, + }, + { + .utf8name = "set", + .method = referenceProxySetter, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_default, + .data = nullptr, + }, + }; + napi_define_properties(env, handler, 2, handlerProperties); + + napi_value proxyArgs[2] = {target, handler}; + napi_value proxy; + if (napi_new_instance(env, proxyCtor, 2, proxyArgs, &proxy) != napi_ok) { + return target; + } + + napi_wrap(env, proxy, reference, nullptr, nullptr, nullptr); + + return proxy; +} + +void finalizeReferenceNow(napi_env env, void* data, void* hint) { + (void)env; + (void)hint; + Reference* ref = (Reference*)data; + delete ref; +} + +void finalizeFunctionReferenceNow(napi_env env, void* data, void* hint) { + (void)env; + (void)hint; + FunctionReference* ref = (FunctionReference*)data; + delete ref; +} + +void finalizePointerNow(napi_env env, void* data, void* hint) { + (void)env; + (void)hint; + Pointer* ptr = (Pointer*)data; + if (ptr == nullptr) { + return; + } + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && ptr->cached) { + auto& cache = bridgeState->pointerCache; + auto it = cache.find(pointerKey(ptr->data)); + if (it != cache.end() && it->second.owner == ptr) { + napi_ref ref = it->second.ref; + cache.erase(it); + napi_delete_reference(env, ref); + } + } + delete ptr; +} + +inline bool getCachedPointer(napi_env env, void* data, napi_value* value) { + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr) { + return false; + } + + auto& cache = bridgeState->pointerCache; + auto it = cache.find(pointerKey(data)); + if (it == cache.end()) { + return false; + } + + *value = get_ref_value(env, it->second.ref); + if (*value == nullptr) { + napi_delete_reference(env, it->second.ref); + cache.erase(it); + return false; + } + + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, *value, &valueType) != napi_ok || valueType != napi_object) { + napi_delete_reference(env, it->second.ref); + cache.erase(it); + *value = nullptr; + return false; + } + + Pointer* ptr = Pointer::unwrap(env, *value); + if (ptr == nullptr || ptr->data != data) { + napi_delete_reference(env, it->second.ref); + cache.erase(it); + *value = nullptr; + return false; + } + + return true; +} + +inline bool cachePointer(napi_env env, Pointer* owner, napi_value value) { + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr) { + return false; + } + + auto& cache = bridgeState->pointerCache; + const uintptr_t key = pointerKey(owner->data); + if (cache.find(key) != cache.end()) { + return false; + } + napi_ref ref = nullptr; + if (napi_create_reference(env, value, 0, &ref) != napi_ok) { + return false; + } + cache[key] = {owner, ref}; + return true; +} + +inline std::string pointerHexString(void* data) { + uintptr_t value = reinterpret_cast(data); + if (value == 0) { + return "0x0"; + } + + char hex[2 + sizeof(uintptr_t) * 2 + 1]; +#if UINTPTR_MAX == 0xffffffff + snprintf(hex, sizeof(hex), "0x%x", static_cast(value)); +#else + snprintf(hex, sizeof(hex), "0x%llx", static_cast(value)); +#endif + return std::string(hex); +} + +inline void* lookupSymbolByName(ObjCBridgeState* bridgeState, const char* symbolName) { + void* fn = dlsym(bridgeState->self_dl, symbolName); + if (fn == nullptr) { + fn = dlsym(RTLD_DEFAULT, symbolName); + } + if (fn == nullptr) { + std::string underscored = "_"; + underscored += symbolName; + fn = dlsym(bridgeState->self_dl, underscored.c_str()); + if (fn == nullptr) { + fn = dlsym(RTLD_DEFAULT, underscored.c_str()); + } + } + return fn; +} + +inline bool unwrapKnownNativeHandleImpl(napi_env env, napi_value value, void** out) { + if (env == nullptr || value == nullptr || out == nullptr) { + return false; + } + *out = nullptr; + + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, value, &valueType) != napi_ok) { + return false; + } + + if (valueType == napi_bigint) { + uint64_t raw = 0; + bool lossless = false; + if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) { + return false; + } + *out = reinterpret_cast(static_cast(raw)); + return true; + } + + if (valueType == napi_external) { + return napi_get_value_external(env, value, out) == napi_ok; + } + + if (valueType != napi_object && valueType != napi_function) { + return false; + } + + bool isArray = false; + if (valueType == napi_object && napi_is_array(env, value, &isArray) == napi_ok && isArray) { + return false; + } + + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + id bridgedType = nil; + if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && + bridgedType != nil) { + *out = (void*)bridgedType; + return true; + } + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *out = ptr != nullptr ? ptr->data : nullptr; + return true; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *out = ref != nullptr ? ref->data : nullptr; + return true; + } + + if (valueType == napi_object && StructObject::isInstance(env, value)) { + StructObject* object = StructObject::unwrap(env, value); + *out = object != nullptr ? object->data : nullptr; + return object != nullptr; + } + + bool hasNativePointer = false; + napi_has_named_property(env, value, kNativePointerProperty, &hasNativePointer); + if (hasNativePointer) { + napi_value nativePointerValue = nullptr; + if (napi_get_named_property(env, value, kNativePointerProperty, &nativePointerValue) == + napi_ok) { + if (Pointer::isInstance(env, nativePointerValue)) { + Pointer* pointer = Pointer::unwrap(env, nativePointerValue); + if (pointer != nullptr && pointer->data != nullptr) { + *out = pointer->data; + return true; + } + } else { + void* nativePointer = nullptr; + if (napi_get_value_external(env, nativePointerValue, &nativePointer) == napi_ok && + nativePointer != nullptr) { + *out = nativePointer; + return true; + } + } + } + } + + if (valueType != napi_function) { + return false; + } + + void* wrapped = nullptr; + if (napi_unwrap(env, value, &wrapped) != napi_ok || wrapped == nullptr) { + return false; + } + + bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + for (const auto& entry : bridgeState->classes) { + if (entry.second == wrapped) { + *out = (void*)entry.second->nativeClass; + return true; + } + } + + for (const auto& entry : bridgeState->protocols) { + if (entry.second == wrapped) { + *out = (void*)objc_getProtocol(entry.second->name.c_str()); + return true; + } + } + + for (const auto& entry : bridgeState->cFunctionCache) { + if (entry.second == wrapped) { + *out = entry.second->fnptr; + return true; + } + } + } + + return false; +} + +inline bool resolveNativePointerFromRegisteredFunction(napi_env env, napi_value value, void** out) { + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr || bridgeState->metadata == nullptr) { + return false; + } + + napi_value global; + if (napi_get_global(env, &global) != napi_ok) { + return false; + } + + MDSectionOffset offset = bridgeState->metadata->functionsOffset; + while (offset < bridgeState->metadata->protocolsOffset) { + MDSectionOffset originalOffset = offset; + const char* functionName = bridgeState->metadata->getString(offset); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDFunctionFlag); + + napi_value globalFn; + if (napi_get_named_property(env, global, functionName, &globalFn) != napi_ok) { + continue; + } + + bool isSameFunction = false; + napi_strict_equals(env, value, globalFn, &isSameFunction); + if (!isSameFunction) { + continue; + } + + auto cFunction = bridgeState->getCFunction(env, originalOffset); + if (cFunction != nullptr && cFunction->fnptr != nullptr) { + *out = cFunction->fnptr; + return true; + } + + void* fn = lookupSymbolByName(bridgeState, functionName); + if (fn != nullptr) { + *out = fn; + return true; + } + return false; + } + + return false; +} + +inline bool resolveNativePointerFromFunctionName(napi_env env, napi_value value, void** out) { + napi_valuetype type = napi_undefined; + napi_typeof(env, value, &type); + if (type != napi_function) { + return false; + } + + napi_value nameValue; + if (napi_get_named_property(env, value, "name", &nameValue) != napi_ok) { + return false; + } + + napi_valuetype nameType = napi_undefined; + napi_typeof(env, nameValue, &nameType); + if (nameType != napi_string) { + return false; + } + + char name[512]; + size_t len = 0; + if (napi_get_value_string_utf8(env, nameValue, name, sizeof(name), &len) != napi_ok || len == 0) { + return resolveNativePointerFromRegisteredFunction(env, value, out); + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr) { + return resolveNativePointerFromRegisteredFunction(env, value, out); + } + void* fn = lookupSymbolByName(bridgeState, name); + if (fn == nullptr) { + return resolveNativePointerFromRegisteredFunction(env, value, out); + } + + *out = fn; + return true; +} + +inline void setObjectPrototype(napi_env env, napi_value object, napi_value prototype) { + napi_value global; + napi_get_global(env, &global); + + napi_value jsObject; + napi_get_named_property(env, global, "Object", &jsObject); + + napi_value setPrototypeOf; + napi_get_named_property(env, jsObject, "setPrototypeOf", &setPrototypeOf); + + napi_value argv[2] = {object, prototype}; + napi_call_function(env, jsObject, setPrototypeOf, 2, argv, nullptr); +} +} // namespace + +bool unwrapKnownNativeHandle(napi_env env, napi_value value, void** out) { + return unwrapKnownNativeHandleImpl(env, value, out); +} + +inline napi_value createJSNumber(napi_env env, int32_t ival) { + napi_value value; + napi_create_int32(env, ival, &value); + return value; +} + +napi_value __extends(napi_env env, napi_callback_info info) { + napi_value argv[2]; + size_t argc = 2; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + napi_value constructor = argv[0]; + napi_value superConstructor = argv[1]; + + napi_inherits(env, constructor, superConstructor); + + Class superClassNative = nullptr; + napi_unwrap(env, superConstructor, (void**)&superClassNative); + if (superClassNative != nullptr) { + ClassBuilder* builder = new ClassBuilder(env, constructor); + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + bridgeState->registerRuntimeClass(builder, builder->nativeClass); + } + + return nullptr; +} + +const char* jsHelpersSource = R"( + if (typeof globalThis.__decorate !== "function") { + globalThis.__decorate = function(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; + }; + } + + if (typeof globalThis.__param !== "function") { + globalThis.__param = function(paramIndex, decorator) { + return function(target, key) { decorator(target, key, paramIndex); }; + }; + } + + globalThis.ObjCClass = function ObjCClass(...protocols) { + return function(constructor) { + if (constructor.ObjCProtocols) { + constructor.ObjCProtocols.push(...protocols); + } else { + constructor.ObjCProtocols = protocols; + } + + if (typeof globalThis.NativeClass === "function") { + return globalThis.NativeClass(constructor); + } + + return constructor; + }; + }; + + if (typeof WeakRef === "function") { + WeakRef.prototype.get = WeakRef.prototype.deref; + if (!WeakRef.prototype.clear) { + WeakRef.prototype.clear = function() { + console.warn("WeakRef.clear() is non-standard and has been deprecated. It does nothing and the call can be safely removed."); + }; + } + } +)"; + +void registerInterop(napi_env env, napi_value global) { + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + + const napi_property_descriptor __extendsProperty = { + .utf8name = "__extends", + .method = __extends, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }; + + napi_define_properties(env, global, 1, &__extendsProperty); + + napi_value jsHelpers; + napi_create_string_utf8(env, jsHelpersSource, NAPI_AUTO_LENGTH, &jsHelpers); + napi_value result; + napi_run_script(env, jsHelpers, &result); + + napi_value interop; + napi_create_object(env, &interop); + + napi_value types; + napi_create_object(env, &types); + + napi_set_named_property(env, types, "void", createJSNumber(env, mdTypeVoid)); + napi_set_named_property(env, types, "bool", createJSNumber(env, mdTypeBool)); + napi_set_named_property(env, types, "int8", createJSNumber(env, mdTypeChar)); + napi_set_named_property(env, types, "uint8", createJSNumber(env, mdTypeUInt8)); + napi_set_named_property(env, types, "int16", createJSNumber(env, mdTypeSShort)); + napi_set_named_property(env, types, "uint16", createJSNumber(env, mdTypeUShort)); + napi_set_named_property(env, types, "int32", createJSNumber(env, mdTypeSInt)); + napi_set_named_property(env, types, "uint32", createJSNumber(env, mdTypeUInt)); + napi_set_named_property(env, types, "int64", createJSNumber(env, mdTypeSInt64)); + napi_set_named_property(env, types, "uint64", createJSNumber(env, mdTypeUInt64)); + napi_set_named_property(env, types, "float", createJSNumber(env, mdTypeFloat)); + napi_set_named_property(env, types, "double", createJSNumber(env, mdTypeDouble)); + napi_set_named_property(env, types, "UTF8CString", createJSNumber(env, mdTypeString)); + napi_set_named_property(env, types, "unichar", createJSNumber(env, mdTypeUnichar)); + napi_set_named_property(env, types, "id", createJSNumber(env, mdTypeAnyObject)); + napi_set_named_property(env, types, "protocol", createJSNumber(env, mdTypePointer)); + napi_set_named_property(env, types, "class", createJSNumber(env, mdTypeAnyObject)); + napi_set_named_property(env, types, "SEL", createJSNumber(env, mdTypeSelector)); + napi_set_named_property(env, types, "selector", createJSNumber(env, mdTypeSelector)); + napi_set_named_property(env, types, "pointer", createJSNumber(env, mdTypePointer)); + + napi_value Pointer = Pointer::defineJSClass(env); + bridgeState->pointerClass = make_ref(env, Pointer); + + napi_value Reference = Reference::defineJSClass(env); + bridgeState->referenceClass = make_ref(env, Reference); + + napi_value FunctionReference = FunctionReference::defineJSClass(env); + bridgeState->functionReferenceClass = make_ref(env, FunctionReference); + + const napi_property_descriptor properties[] = { + { + .utf8name = "Pointer", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = Pointer, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "Reference", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = Reference, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "FunctionReference", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = FunctionReference, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "types", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = types, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "adopt", + .method = interop_adopt, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "addMethod", + .method = interop_addMethod, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "addProtocol", + .method = interop_addProtocol, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "free", + .method = interop_free, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "sizeof", + .method = interop_sizeof, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "alloc", + .method = interop_alloc, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "handleof", + .method = interop_handleof, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "bufferFromData", + .method = interop_bufferFromData, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "stringFromCString", + .method = interop_stringFromCString, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + }; + + napi_define_properties(env, interop, 13, properties); + + napi_set_named_property(env, global, "interop", interop); +} + +napi_value interop_addMethod(napi_env env, napi_callback_info info) { + napi_value argv[2]; + size_t argc = 2; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + ClassBuilder* builder = nullptr; + napi_unwrap(env, argv[0], (void**)&builder); + if (builder == nullptr) { + napi_throw_error(env, nullptr, "Invalid class"); + return nullptr; + } + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + builder = (ClassBuilder*)bridgeState->classesByPointer[(id)builder]; + + napi_value name; + napi_get_named_property(env, argv[1], "name", &name); + + if (builder->isFinal) { + static char funcNameBuf[512]; + napi_get_value_string_utf8(env, name, funcNameBuf, 512, nullptr); + std::string funcName = funcNameBuf; + + MethodDescriptor* desc = builder->lookupMethodDescriptor(funcName); + if (desc == nullptr) { + napi_throw_error(env, nullptr, "Invalid method, descriptor not found"); + return nullptr; + } + + builder->addMethod(funcName, desc, name, argv[1]); + } else { + napi_set_property(env, get_ref_value(env, builder->prototype), name, argv[1]); + } + + return nullptr; +} + +napi_value interop_addProtocol(napi_env env, napi_callback_info info) { + napi_value argv[2]; + size_t argc = 2; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + ClassBuilder* builder = nullptr; + napi_unwrap(env, argv[0], (void**)&builder); + if (builder == nullptr) { + napi_throw_error(env, nullptr, "Invalid class"); + return nullptr; + } + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + builder = (ClassBuilder*)bridgeState->classesByPointer[(id)builder]; + + ObjCProtocol* proto = nullptr; + napi_unwrap(env, argv[1], (void**)&proto); + if (proto == nullptr) { + napi_throw_error(env, nullptr, "Invalid protocol"); + return nullptr; + } + + builder->addProtocol(proto); + + return nullptr; +} + +napi_value interop_adopt(napi_env env, napi_callback_info info) { + napi_value arg; + size_t argc = 1; + napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); + + Pointer* ptr; + napi_unwrap(env, arg, (void**)&ptr); + + napi_value adopted; + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + napi_new_instance(env, get_ref_value(env, bridgeState->pointerClass), 1, &arg, &adopted); + Pointer* adoptedPtr; + napi_unwrap(env, adopted, (void**)&adoptedPtr); + adoptedPtr->adopted = true; + + return adopted; +} + +napi_value interop_free(napi_env env, napi_callback_info info) { + napi_value arg; + size_t argc = 1; + napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); + + Pointer* ptr = nullptr; + napi_unwrap(env, arg, (void**)&ptr); + + if (ptr != nullptr && ptr->data != nullptr) { + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + auto& cache = bridgeState->pointerCache; + auto it = cache.find(pointerKey(ptr->data)); + if (it != cache.end() && it->second.owner == ptr) { + napi_delete_reference(env, it->second.ref); + cache.erase(it); + } + } + ptr->cached = false; + free(ptr->data); + ptr->data = nullptr; + } + + return nullptr; +} + +napi_value interop_stringFromCString(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2] = {nullptr, nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + napi_value arg = argv[0]; + + napi_valuetype type; + napi_typeof(env, arg, &type); + + if (type == napi_string) { + return arg; + } + + if (type != napi_object) { + napi_throw_type_error(env, "TypeError", "Expected an object"); + return nullptr; + } + + void* data = nullptr; + + if (Pointer::isInstance(env, arg)) { + Pointer* ptr = Pointer::unwrap(env, arg); + data = ptr->data; + } + + if (Reference::isInstance(env, arg)) { + Reference* ref = Reference::unwrap(env, arg); + data = ref->data; + } + + napi_value result; + + if (data == nullptr) { + napi_get_null(env, &result); + } else { + size_t length = NAPI_AUTO_LENGTH; + if (argc >= 2 && argv[1] != nullptr) { + napi_valuetype lengthType = napi_undefined; + napi_typeof(env, argv[1], &lengthType); + if (lengthType != napi_undefined && lengthType != napi_null) { + int64_t explicitLength = 0; + napi_get_value_int64(env, argv[1], &explicitLength); + if (explicitLength < 0) { + explicitLength = 0; + } + length = static_cast(explicitLength); + } + } + napi_create_string_utf8(env, (const char*)data, length, &result); + } + + return result; +} + +size_t jsSizeof(napi_env env, napi_value arg) { + napi_valuetype type; + napi_typeof(env, arg, &type); + + switch (type) { + case napi_number: { + int32_t ival; + napi_get_value_int32(env, arg, &ival); + + switch (ival) { + case mdTypeVoid: + return 0; + + case mdTypeBool: + case mdTypeChar: + case mdTypeUInt8: + return sizeof(uint8_t); + + case mdTypeSShort: + case mdTypeUShort: + case mdTypeUnichar: + return sizeof(uint16_t); + + case mdTypeSInt: + case mdTypeUInt: + return sizeof(uint32_t); + + case mdTypeSInt64: + case mdTypeUInt64: + return sizeof(uint64_t); + + case mdTypeFloat: + return sizeof(float); + + case mdTypeDouble: + return sizeof(double); + + case mdTypeString: + return sizeof(char*); + + case mdTypeAnyObject: + return sizeof(id); + + case mdTypePointer: + return sizeof(void*); + + case mdTypeSelector: + return sizeof(SEL); + + default: + napi_throw_type_error(env, "TypeError", + "Invalid type number for sizeof. Use interop.types.*"); + } + } + + case napi_object: + case napi_function: { + napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); + napi_value result; + if (napi_get_property(env, arg, symbolSizeof, &result) == napi_ok) { + napi_valuetype resultType; + napi_typeof(env, result, &resultType); + if (resultType == napi_number) { + int32_t size; + napi_get_value_int32(env, result, &size); + return size; + } + } + + void* nativeHandle = nullptr; + if (unwrapKnownNativeHandle(env, arg, &nativeHandle)) { + return sizeof(void*); + } + if (resolveNativePointerFromFunctionName(env, arg, &nativeHandle)) { + return sizeof(void*); + } + + napi_throw_type_error(env, "TypeError", "Invalid type for sizeof"); + } + + default: + napi_throw_type_error(env, "TypeError", "Invalid type for sizeof"); + } + + return -1; +} + +napi_value interop_sizeof(napi_env env, napi_callback_info info) { + napi_valuetype type; + napi_value arg; + size_t argc = 1; + napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); + + size_t size = jsSizeof(env, arg); + napi_value result; + napi_create_int32(env, size, &result); + + return result; +} + +napi_value interop_alloc(napi_env env, napi_callback_info info) { + napi_value arg; + size_t argc = 1; + napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); + + int64_t size; + napi_get_value_int64(env, arg, &size); + + void* data = calloc(1, size); + return Pointer::create(env, data); +} + +napi_value interop_handleof(napi_env env, napi_callback_info info) { + napi_value value, result; + size_t argc = 1; + napi_get_cb_info(env, info, &argc, &value, nullptr, nullptr); + + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType == napi_null || valueType == napi_undefined) { + napi_get_null(env, &result); + return result; + } + + if (valueType == napi_string) { + size_t len = 0; + napi_get_value_string_utf8(env, value, nullptr, 0, &len); + char* str = static_cast(malloc(len + 1)); + napi_get_value_string_utf8(env, value, str, len + 1, &len); + str[len] = '\0'; + return Pointer::create(env, str); + } + + if (Pointer::isInstance(env, value)) { + return value; + } else if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + if (ref == nullptr || ref->data == nullptr) { + napi_throw_error(env, nullptr, "Reference is not initialized"); + return nullptr; + } + return Pointer::create(env, ref->data); + } + + if (FunctionReference::isInstance(env, value)) { + FunctionReference* reference = FunctionReference::unwrap(env, value); + if (reference == nullptr || reference->closure == nullptr || + reference->closure->fnptr == nullptr) { + napi_throw_error(env, nullptr, "FunctionReference is not initialized"); + return nullptr; + } + return Pointer::create(env, reference->closure->fnptr); + } + + void* data = nullptr; + if (unwrapKnownNativeHandle(env, value, &data) && data != nullptr) { + if (valueType == napi_object && !Pointer::isInstance(env, value) && + !Reference::isInstance(env, value) && !FunctionReference::isInstance(env, value)) { + ObjCBridgeState::InstanceData(env)->cacheHandleObject(env, data, value); + } + return Pointer::create(env, data); + } + if (resolveNativePointerFromFunctionName(env, value, &data) && data != nullptr) { + return Pointer::create(env, data); + } + + napi_get_null(env, &result); + return result; +} + +napi_value interop_bufferFromData(napi_env env, napi_callback_info info) { + napi_value arg; + size_t argc = 1; + napi_get_cb_info(env, info, &argc, &arg, nullptr, nullptr); + + bool isArrayBuffer = false; + napi_is_arraybuffer(env, arg, &isArrayBuffer); + if (isArrayBuffer) { + return arg; + } + + NSData* data = nil; + if (napi_unwrap(env, arg, (void**)&data) != napi_ok || data == nil) { + void* native = nullptr; + if (!unwrapKnownNativeHandle(env, arg, &native) || native == nullptr) { + napi_throw_error(env, nullptr, "Invalid data"); + return nullptr; + } + + id candidate = (id)native; + if (![candidate isKindOfClass:[NSData class]]) { + napi_throw_error(env, nullptr, "Invalid data"); + return nullptr; + } + data = (NSData*)candidate; + } + + if (data == nil) { + napi_throw_error(env, nullptr, "Invalid data"); + return nullptr; + } + + napi_value result = nullptr; + void* bufferData = nullptr; + napi_status status = + napi_create_arraybuffer(env, static_cast(data.length), &bufferData, &result); + if (status != napi_ok || result == nullptr) { + napi_throw_error(env, nullptr, "Failed to create ArrayBuffer"); + return nullptr; + } + + if (data.length > 0 && bufferData != nullptr) { + std::memcpy(bufferData, data.bytes, static_cast(data.length)); + } + + return result; +} + +napi_value Pointer::defineJSClass(napi_env env) { + const napi_property_descriptor properties[] = { + { + .utf8name = "add", + .method = Pointer::add, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "subtract", + .method = Pointer::subtract, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "toNumber", + .method = Pointer::toNumber, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "toBigInt", + .method = Pointer::toBigInt, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "toHexString", + .method = Pointer::toHexString, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "toDecimalString", + .method = Pointer::toDecimalString, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "toString", + .method = Pointer::toString, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = nullptr, + .name = jsSymbolFor(env, "nodejs.util.inspect.custom"), + .method = Pointer::customInspect, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + }; + + napi_value constructor; + napi_define_class(env, "Pointer", NAPI_AUTO_LENGTH, Pointer::constructor, nullptr, 8, properties, + &constructor); + + napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); + napi_value sizeValue; + napi_create_int32(env, sizeof(void*), &sizeValue); + napi_set_property(env, constructor, symbolSizeof, sizeValue); + + napi_value prototype; + napi_get_named_property(env, constructor, "prototype", &prototype); + napi_set_property(env, prototype, symbolSizeof, sizeValue); + + napi_value marker; + napi_get_boolean(env, true, &marker); + napi_property_descriptor markerProp = { + .utf8name = kPointerMarker, + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = marker, + .attributes = napi_configurable, + .data = nullptr, + }; + napi_define_properties(env, prototype, 1, &markerProp); + + return constructor; +} + +bool Pointer::isInstance(napi_env env, napi_value value) { + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType != napi_object && valueType != napi_function) { + return false; + } + + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr || bridgeState->pointerClass == nullptr) { + return false; + } + + napi_value constructor = get_ref_value(env, bridgeState->pointerClass); + bool result = false; + if (constructor == nullptr || + napi_instanceof(env, value, constructor, &result) != napi_ok) { + return false; + } + return result; +} + +napi_value Pointer::create(napi_env env, void* data) { + napi_value cached; + if (getCachedPointer(env, data, &cached)) { + return cached; + } + + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + napi_value jsPointer = get_ref_value(env, bridgeState->pointerClass); + napi_value argv[1]; + napi_create_bigint_uint64(env, static_cast(reinterpret_cast(data)), + &argv[0]); + napi_value result; + napi_status status = napi_new_instance(env, jsPointer, 1, argv, &result); + + if (status != napi_ok) { + return nullptr; + } + return result; +} + +Pointer* Pointer::unwrap(napi_env env, napi_value value) { + if (value == nullptr) { + return nullptr; + } + + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, value, &valueType) != napi_ok || valueType != napi_object) { + return nullptr; + } + + Pointer* ptr = nullptr; + if (napi_unwrap(env, value, (void**)&ptr) != napi_ok) { + return nullptr; + } + return ptr; +} + +napi_value Pointer::constructor(napi_env env, napi_callback_info info) { + napi_value jsThis; + size_t argc = 1; + napi_value argv[1]; + napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr); + + napi_valuetype thisType = napi_undefined; + if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || + (thisType != napi_object && thisType != napi_function)) { + napi_create_object(env, &jsThis); + + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && bridgeState->pointerClass != nullptr) { + napi_value pointerCtor = get_ref_value(env, bridgeState->pointerClass); + napi_value pointerPrototype = nullptr; + if (pointerCtor != nullptr && + napi_get_named_property(env, pointerCtor, "prototype", &pointerPrototype) == napi_ok && + pointerPrototype != nullptr) { + napi_value global = nullptr; + napi_value objectCtor = nullptr; + napi_value setPrototypeOf = nullptr; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &objectCtor); + napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); + napi_value setPrototypeArgs[2] = {jsThis, pointerPrototype}; + napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); + } + } + } + + napi_value arg; + if (argc == 0) { + napi_get_undefined(env, &arg); + } else { + arg = argv[0]; + } + + napi_valuetype type = napi_undefined; + napi_typeof(env, arg, &type); + + void* data; + + switch (type) { + case napi_number: { + double number = 0; + napi_get_value_double(env, arg, &number); + if (!std::isfinite(number)) { + napi_throw_error(env, nullptr, "Invalid type"); + return nullptr; + } + data = (void*)((intptr_t)number); + break; + } + + case napi_bigint: { + uint64_t value = 0; + bool lossless = false; + napi_get_value_bigint_uint64(env, arg, &value, &lossless); + data = reinterpret_cast(static_cast(value)); + break; + } + + case napi_object: { + bool isInstance = Pointer::isInstance(env, arg); + if (isInstance) { + Pointer* ptr; + napi_unwrap(env, arg, (void**)&ptr); + if (ptr == nullptr) { + napi_throw_error(env, nullptr, "Invalid type"); + return nullptr; + } + return arg; + } else { + napi_value numberValue; + napi_coerce_to_number(env, arg, &numberValue); + double number = 0; + napi_get_value_double(env, numberValue, &number); + if (!std::isfinite(number)) { + napi_throw_error(env, nullptr, "Invalid type"); + return nullptr; + } + data = (void*)((intptr_t)number); + } + break; + } + + case napi_null: + case napi_undefined: { + data = nullptr; + break; + } + + default: + napi_throw_error(env, nullptr, "Invalid type"); + return nullptr; + } + + napi_value cached; + if (getCachedPointer(env, data, &cached)) { + return cached; + } + + Pointer* ptr = new Pointer(data); + napi_wrap(env, jsThis, ptr, Pointer::finalize, nullptr, nullptr); + ptr->cached = cachePointer(env, ptr, jsThis); + + return jsThis; +} + +napi_value Pointer::add(napi_env env, napi_callback_info info) { + napi_value arg, jsThis; + size_t argc = 1; + napi_get_cb_info(env, info, &argc, &arg, &jsThis, nullptr); + + Pointer* ptr = Pointer::unwrap(env, jsThis); + + int64_t ival; + napi_get_value_int64(env, arg, &ival); + + void* newData = (void*)((intptr_t)((intptr_t)ptr->data + (intptr_t)ival)); + return Pointer::create(env, newData); +} + +napi_value Pointer::subtract(napi_env env, napi_callback_info info) { + napi_value arg, jsThis; + size_t argc = 1; + napi_get_cb_info(env, info, &argc, &arg, &jsThis, nullptr); + + Pointer* ptr = Pointer::unwrap(env, jsThis); + + int64_t ival; + napi_get_value_int64(env, arg, &ival); + + void* newData = (void*)((intptr_t)((intptr_t)ptr->data - (intptr_t)ival)); + return Pointer::create(env, newData); +} + +napi_value Pointer::toNumber(napi_env env, napi_callback_info info) { + napi_value jsThis; + napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); + + Pointer* ptr = Pointer::unwrap(env, jsThis); + + napi_value result; + double value = static_cast(reinterpret_cast(ptr->data)); + napi_create_double(env, value, &result); + return result; +} + +napi_value Pointer::toBigInt(napi_env env, napi_callback_info info) { + napi_value jsThis; + napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); + + Pointer* ptr = Pointer::unwrap(env, jsThis); + + napi_value result; + napi_create_bigint_uint64(env, static_cast(reinterpret_cast(ptr->data)), + &result); + return result; +} + +napi_value Pointer::toHexString(napi_env env, napi_callback_info info) { + napi_value jsThis; + napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); + + Pointer* ptr = Pointer::unwrap(env, jsThis); + std::string hex = pointerHexString(ptr->data); + + napi_value result; + napi_create_string_utf8(env, hex.c_str(), NAPI_AUTO_LENGTH, &result); + return result; +} + +napi_value Pointer::toDecimalString(napi_env env, napi_callback_info info) { + napi_value jsThis; + napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); + + Pointer* ptr = Pointer::unwrap(env, jsThis); + std::string decimal; + if (sizeof(void*) == 4) { + decimal = std::to_string(static_cast(reinterpret_cast(ptr->data))); + } else { + decimal = std::to_string(static_cast(reinterpret_cast(ptr->data))); + } + + napi_value result; + napi_create_string_utf8(env, decimal.c_str(), NAPI_AUTO_LENGTH, &result); + return result; +} + +napi_value Pointer::toString(napi_env env, napi_callback_info info) { + return Pointer::customInspect(env, info); +} + +napi_value Pointer::customInspect(napi_env env, napi_callback_info info) { + napi_value jsThis; + napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); + + Pointer* ptr = Pointer::unwrap(env, jsThis); + std::string str = "data) + ">"; + + napi_value result; + napi_create_string_utf8(env, str.c_str(), NAPI_AUTO_LENGTH, &result); + + return result; +} + +void Pointer::finalize(napi_env env, void* data, void* hint) { + if (PostFinalizer(env, finalizePointerNow, data, hint)) { + return; + } + + finalizePointerNow(env, data, hint); +} + +Pointer::Pointer(void* data) { this->data = data; } + +Pointer::~Pointer() { + if (adopted && data != nullptr) { + free(data); + } +} + +napi_value Reference::defineJSClass(napi_env env) { + const napi_property_descriptor properties[] = { + { + .utf8name = "value", + .method = nullptr, + .getter = Reference::get_value, + .setter = Reference::set_value, + .value = nullptr, + .attributes = (napi_property_attributes)(napi_enumerable | napi_writable), + .data = nullptr, + }, + { + .utf8name = nullptr, + .name = jsSymbolFor(env, "nodejs.util.inspect.custom"), + .method = Reference::customInspect, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "toString", + .method = Reference::customInspect, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }, + }; + + napi_value constructor; + napi_define_class(env, "Reference", NAPI_AUTO_LENGTH, Reference::constructor, nullptr, 3, + properties, &constructor); + + napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); + napi_value sizeValue; + napi_create_int32(env, sizeof(void*), &sizeValue); + napi_set_property(env, constructor, symbolSizeof, sizeValue); + + napi_value prototype; + napi_get_named_property(env, constructor, "prototype", &prototype); + napi_set_property(env, prototype, symbolSizeof, sizeValue); + + napi_value marker; + napi_get_boolean(env, true, &marker); + napi_property_descriptor markerProp = { + .utf8name = kReferenceMarker, + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = marker, + .attributes = napi_configurable, + .data = nullptr, + }; + napi_define_properties(env, prototype, 1, &markerProp); + + return constructor; +} + +bool Reference::isInstance(napi_env env, napi_value value) { + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType != napi_object && valueType != napi_function) { + return false; + } + + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr || bridgeState->referenceClass == nullptr) { + return false; + } + + napi_value constructor = get_ref_value(env, bridgeState->referenceClass); + bool result = false; + if (constructor == nullptr || + napi_instanceof(env, value, constructor, &result) != napi_ok) { + return false; + } + return result; +} + +napi_value Reference::create(napi_env env, std::shared_ptr type, void* data, + bool ownsData) { + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + napi_value referenceCtor = get_ref_value(env, bridgeState->referenceClass); + napi_value jsReference = nullptr; + if (napi_new_instance(env, referenceCtor, 0, nullptr, &jsReference) != napi_ok) { + return nullptr; + } + + Reference* reference = Reference::unwrap(env, jsReference); + if (reference == nullptr) { + return nullptr; + } + + reference->env = env; + reference->bridgeState = ObjCBridgeState::InstanceData(env); + reference->bridgeStateToken = + reference->bridgeState != nullptr ? reference->bridgeState->lifetimeToken : 0; + reference->type = std::move(type); + reference->data = data; + reference->ownsData = ownsData; + + return jsReference; +} + +Reference* Reference::unwrap(napi_env env, napi_value value) { + Reference* ref = nullptr; + if (napi_unwrap(env, value, (void**)&ref) != napi_ok) { + return nullptr; + } + return ref; +} + +napi_value Reference::getInitValue(napi_env env, napi_value value, Reference* ref) { + napi_value initValue = getReferenceInitValueProperty(env, value); + napi_valuetype initType = napi_undefined; + if (initValue != nullptr && napi_typeof(env, initValue, &initType) == napi_ok && + initType != napi_undefined) { + return initValue; + } + + if (ref != nullptr && ref->initValue != nullptr) { + return get_ref_value(env, ref->initValue); + } + + return nullptr; +} + +void Reference::setInitValue(napi_env env, napi_value value, Reference* ref, napi_value initValue) { + setReferenceInitValueProperty(env, value, initValue); + + if (ref == nullptr) { + return; + } + + if (ref->initValue != nullptr) { + napi_delete_reference(env, ref->initValue); + ref->initValue = nullptr; + } + + if (initValue != nullptr) { + napi_create_reference(env, initValue, 1, &ref->initValue); + } +} + +void Reference::clearInitValue(napi_env env, napi_value value, Reference* ref) { + clearReferenceInitValueProperty(env, value); + + if (ref != nullptr && ref->initValue != nullptr) { + napi_delete_reference(env, ref->initValue); + ref->initValue = nullptr; + } +} + +napi_value Reference::constructor(napi_env env, napi_callback_info info) { + napi_value jsThis; + size_t argc = 2; + napi_value argv[2]; + napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr); + + napi_valuetype thisType = napi_undefined; + if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || + (thisType != napi_object && thisType != napi_function)) { + napi_create_object(env, &jsThis); + + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && bridgeState->referenceClass != nullptr) { + napi_value referenceCtor = get_ref_value(env, bridgeState->referenceClass); + napi_value referencePrototype = nullptr; + if (referenceCtor != nullptr && + napi_get_named_property(env, referenceCtor, "prototype", &referencePrototype) == + napi_ok && + referencePrototype != nullptr) { + napi_value global = nullptr; + napi_value objectCtor = nullptr; + napi_value setPrototypeOf = nullptr; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &objectCtor); + napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); + napi_value setPrototypeArgs[2] = {jsThis, referencePrototype}; + napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); + } + } + } + + Reference* reference = new Reference(); + reference->env = env; + reference->bridgeState = ObjCBridgeState::InstanceData(env); + reference->bridgeStateToken = + reference->bridgeState != nullptr ? reference->bridgeState->lifetimeToken : 0; + + if (argc == 0) { + // Leave it uninitialized. It can be materialized later by pointer marshalling. + } else if (argc == 1) { + napi_valuetype argType = napi_undefined; + napi_typeof(env, argv[0], &argType); + + bool isTypeArg = false; + if (argType == napi_function) { + isTypeArg = true; + } else if (argType == napi_number) { + double numericValue = 0; + if (napi_get_value_double(env, argv[0], &numericValue) == napi_ok && + std::isfinite(numericValue) && floor(numericValue) == numericValue && + numericValue >= static_cast(INT32_MIN) && + numericValue <= static_cast(INT32_MAX)) { + int32_t typeCode = static_cast(numericValue); + if (isInteropTypeCode(typeCode)) { + isTypeArg = true; + } + } + } + + if (isTypeArg) { + std::string typeEncoding = getEncodedType(env, argv[0]); + const char* typestr = typeEncoding.c_str(); + reference->type = TypeConv::Make(env, &typestr); + + size_t size = reference->type != nullptr && reference->type->type != nullptr && + reference->type->type->size > 0 + ? reference->type->type->size + : sizeof(void*); + reference->data = calloc(1, size); + reference->ownsData = true; + } else { + Reference::setInitValue(env, jsThis, reference, argv[0]); + } + } else if (argc == 2) { + std::string type = getEncodedType(env, argv[0]); + const char* typestr = type.c_str(); + reference->type = TypeConv::Make(env, &typestr); + + napi_valuetype argtype; + napi_typeof(env, argv[1], &argtype); + + if (argtype == napi_object && Pointer::isInstance(env, argv[1])) { + reference->data = Pointer::unwrap(env, argv[1])->data; + reference->ownsData = false; + } else if (reference->type != nullptr && reference->type->kind == mdTypeStruct && + argtype == napi_object && StructObject::isInstance(env, argv[1])) { + StructObject* structObject = StructObject::unwrap(env, argv[1]); + if (structObject != nullptr) { + reference->data = structObject->data; + reference->ownsData = false; + } + } else if (argtype == napi_object && Reference::isInstance(env, argv[1])) { + Reference* other = Reference::unwrap(env, argv[1]); + if (other != nullptr && other->data != nullptr) { + reference->data = other->data; + reference->ownsData = false; + } else if (other != nullptr) { + size_t size = reference->type != nullptr && reference->type->type != nullptr && + reference->type->type->size > 0 + ? reference->type->type->size + : sizeof(void*); + reference->data = calloc(1, size); + reference->ownsData = true; + bool shouldFree = false; + napi_value initValue = Reference::getInitValue(env, argv[1], other); + if (initValue != nullptr) { + reference->type->toNative(env, initValue, reference->data, &shouldFree, &shouldFree); + if (ConsumeNapiArgumentConversionFailure(env)) { + delete reference; + return nullptr; + } + } + } + } else { + size_t size = reference->type != nullptr && reference->type->type != nullptr && + reference->type->type->size > 0 + ? reference->type->type->size + : sizeof(void*); + reference->data = malloc(size); + reference->ownsData = true; + bool shouldFree; + reference->type->toNative(env, argv[1], reference->data, &shouldFree, &shouldFree); + if (ConsumeNapiArgumentConversionFailure(env)) { + delete reference; + return nullptr; + } + } + } else { + napi_throw_error(env, nullptr, "Invalid number of arguments"); + return nullptr; + } + + napi_wrap(env, jsThis, reference, Reference::finalize, nullptr, nullptr); + + return createReferenceProxy(env, jsThis, reference); +} + +napi_value Reference::get_value(napi_env env, napi_callback_info info) { + napi_value jsThis; + napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); + + Reference* ref = Reference::unwrap(env, jsThis); + + if (ref == nullptr) { + napi_value undefined; + napi_get_undefined(env, &undefined); + return undefined; + } + + if (ref->data == nullptr) { + napi_value initValue = Reference::getInitValue(env, jsThis, ref); + if (initValue != nullptr) { + return initValue; + } + + napi_value undefined; + napi_get_undefined(env, &undefined); + return undefined; + } + + return ref->type->toJS(env, ref->data); +} + +napi_value Reference::set_value(napi_env env, napi_callback_info info) { + napi_value jsThis; + size_t argc = 1; + napi_value arg; + napi_get_cb_info(env, info, &argc, &arg, &jsThis, nullptr); + + Reference* ref = Reference::unwrap(env, jsThis); + + if (ref == nullptr) { + return nullptr; + } + + if (ref->data == nullptr) { + if (ref->type == nullptr) { + Reference::setInitValue(env, jsThis, ref, arg); + return nullptr; + } + + size_t size = ref->type->type != nullptr && ref->type->type->size > 0 ? ref->type->type->size + : sizeof(void*); + ref->data = calloc(1, size); + ref->ownsData = true; + } + + bool shouldFree = false; + ref->type->toNative(env, arg, ref->data, &shouldFree, &shouldFree); + if (ConsumeNapiArgumentConversionFailure(env)) { + return nullptr; + } + Reference::clearInitValue(env, jsThis, ref); + + return nullptr; +} + +napi_value Reference::customInspect(napi_env env, napi_callback_info info) { + napi_value jsThis; + napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); + + Reference* ref = Reference::unwrap(env, jsThis); + + napi_value result; + if (ref == nullptr) { + napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &result); + return result; + } + std::string str = "data) + ">"; + napi_create_string_utf8(env, str.c_str(), NAPI_AUTO_LENGTH, &result); + + return result; +} + +void Reference::finalize(napi_env env, void* data, void* hint) { + if (PostFinalizer(env, finalizeReferenceNow, data, hint)) { + return; + } + + finalizeReferenceNow(env, data, hint); +} + +Reference::~Reference() { + if (initValue != nullptr && env != nullptr && + (bridgeState == nullptr || IsBridgeStateLive(bridgeState, bridgeStateToken))) { + DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, initValue); + initValue = nullptr; + } + bridgeState = nullptr; + bridgeStateToken = 0; + if (data != nullptr && ownsData) { + free(data); + data = nullptr; + } +} + +napi_value FunctionReference::defineJSClass(napi_env env) { + napi_value constructor; + napi_define_class(env, "FunctionReference", NAPI_AUTO_LENGTH, FunctionReference::constructor, + nullptr, 0, nullptr, &constructor); + + napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); + napi_value sizeValue; + napi_create_int32(env, sizeof(void*), &sizeValue); + napi_set_property(env, constructor, symbolSizeof, sizeValue); + + napi_value prototype; + napi_get_named_property(env, constructor, "prototype", &prototype); + napi_set_property(env, prototype, symbolSizeof, sizeValue); + + return constructor; +} + +bool FunctionReference::isInstance(napi_env env, napi_value value) { + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType != napi_object && valueType != napi_function) { + return false; + } + + bool hasMarker = false; + bool hasNativeRef = false; + if (napi_has_named_property(env, value, kFunctionReferenceMarker, &hasMarker) != napi_ok || + !hasMarker || + napi_has_named_property(env, value, kFunctionReferenceDataProperty, &hasNativeRef) != + napi_ok || + !hasNativeRef) { + return false; + } + + napi_value marker = nullptr; + napi_value nativeRef = nullptr; + bool markerValue = false; + void* nativeData = nullptr; + if (napi_get_named_property(env, value, kFunctionReferenceMarker, &marker) != napi_ok || + napi_get_value_bool(env, marker, &markerValue) != napi_ok || !markerValue || + napi_get_named_property(env, value, kFunctionReferenceDataProperty, &nativeRef) != napi_ok || + napi_get_value_external(env, nativeRef, &nativeData) != napi_ok) { + return false; + } + return nativeData != nullptr; +} + +FunctionReference* FunctionReference::unwrap(napi_env env, napi_value value) { + FunctionReference* ref = nullptr; + if (napi_unwrap(env, value, (void**)&ref) == napi_ok && ref != nullptr) { + return ref; + } + + bool hasNativeRef = false; + napi_has_named_property(env, value, kFunctionReferenceDataProperty, &hasNativeRef); + if (!hasNativeRef) { + return nullptr; + } + + napi_value nativeRefValue; + if (napi_get_named_property(env, value, kFunctionReferenceDataProperty, &nativeRefValue) != + napi_ok) { + return nullptr; + } + + void* nativeRef = nullptr; + if (napi_get_value_external(env, nativeRefValue, &nativeRef) != napi_ok) { + return nullptr; + } + + return static_cast(nativeRef); +} + +void FunctionReference::finalize(napi_env env, void* data, void* hint) { + if (PostFinalizer(env, finalizeFunctionReferenceNow, data, hint)) { + return; + } + + finalizeFunctionReferenceNow(env, data, hint); +} + +FunctionReference::FunctionReference(napi_env env, napi_ref ref) : env(env), ref(ref) { + bridgeState = ObjCBridgeState::InstanceData(env); + bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; +} + +napi_value FunctionReference::constructor(napi_env env, napi_callback_info info) { + napi_value jsThis; + size_t argc = 1; + napi_value argv[1]; + napi_value newTarget = nullptr; + napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr); + napi_get_new_target(env, info, &newTarget); + + napi_value arg; + if (argc == 0) { + napi_throw_type_error(env, nullptr, "FunctionReference constructor expects a function"); + return nullptr; + } + arg = argv[0]; + + napi_valuetype argType = napi_undefined; + napi_typeof(env, arg, &argType); + if (argType != napi_function) { + napi_throw_type_error(env, nullptr, "FunctionReference constructor expects a function"); + return nullptr; + } + + if (FunctionReference::isInstance(env, arg)) { + return arg; + } + + FunctionReference* reference = new FunctionReference(env, make_ref(env, arg)); + napi_value nativeRef; + napi_create_external(env, reference, nullptr, nullptr, &nativeRef); + napi_property_descriptor nativeRefProp = { + .utf8name = kFunctionReferenceDataProperty, + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = nativeRef, + .attributes = napi_configurable, + .data = nullptr, + }; + napi_define_properties(env, arg, 1, &nativeRefProp); + napi_ref finalizerRef = nullptr; + napi_add_finalizer(env, arg, reference, FunctionReference::finalize, nullptr, &finalizerRef); + + napi_value marker; + napi_get_boolean(env, true, &marker); + napi_property_descriptor markerProp = { + .utf8name = kFunctionReferenceMarker, + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = marker, + .attributes = napi_configurable, + .data = nullptr, + }; + napi_define_properties(env, arg, 1, &markerProp); + + // Keep function prototype chain untouched to avoid side effects on JS function + // semantics; expose sizeof directly on the function instance instead. + napi_value symbolSizeof = jsSymbolFor(env, "sizeof"); + napi_value sizeValue; + napi_create_int32(env, sizeof(void*), &sizeValue); + napi_set_property(env, arg, symbolSizeof, sizeValue); + + (void)newTarget; + return arg; +} + +FunctionReference::~FunctionReference() { + // If closure is already created, it shares the same JS function ref. + // Clear it there and delete exactly once here. + if (closure != nullptr && closure->func == ref) { + closure->func = nullptr; + } + if (ref != nullptr && env != nullptr && + (bridgeState == nullptr || IsBridgeStateLive(bridgeState, bridgeStateToken))) { + DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref); + ref = nullptr; + } + bridgeState = nullptr; + bridgeStateToken = 0; +} + +void* FunctionReference::getFunctionPointer(MDSectionOffset offset, bool isBlock) { + if (closure == nullptr) { + closure = std::make_shared(env, ObjCBridgeState::InstanceData(env)->metadata, offset, + isBlock); + closure->func = ref; + } + + return closure->fnptr; +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/JSObject.h b/NativeScript/ffi/objc/napi/JSObject.h similarity index 100% rename from NativeScript/ffi/napi/JSObject.h rename to NativeScript/ffi/objc/napi/JSObject.h diff --git a/NativeScript/ffi/napi/JSObject.mm b/NativeScript/ffi/objc/napi/JSObject.mm similarity index 100% rename from NativeScript/ffi/napi/JSObject.mm rename to NativeScript/ffi/objc/napi/JSObject.mm diff --git a/NativeScript/ffi/napi/ObjCBridge.h b/NativeScript/ffi/objc/napi/ObjCBridge.h similarity index 95% rename from NativeScript/ffi/napi/ObjCBridge.h rename to NativeScript/ffi/objc/napi/ObjCBridge.h index 6e7eb19a0..96a55ecf9 100644 --- a/NativeScript/ffi/napi/ObjCBridge.h +++ b/NativeScript/ffi/objc/napi/ObjCBridge.h @@ -50,7 +50,7 @@ struct HandleObjectRef { struct RecentObjectWrapperRef { uintptr_t objectKey = 0; uintptr_t objectClassKey = 0; - napi_ref ref = nullptr; + napi_ref borrowedRef = nullptr; }; void finalize_objc_object(napi_env /*env*/, void* data, void* hint); @@ -90,6 +90,9 @@ class ObjCBridgeState { ~ObjCBridgeState(); static inline ObjCBridgeState* InstanceData(napi_env env) { + if (env == nullptr) { + return nullptr; + } ObjCBridgeState* bridgeState; napi_status status = napi_get_instance_data(env, (void**)&bridgeState); if (status != napi_ok) { @@ -98,6 +101,9 @@ class ObjCBridgeState { return bridgeState; } + bool enqueueFinalizer(napi_finalize callback, void* data, void* hint); + void drainFinalizers(bool stop = false); + static inline uintptr_t NormalizeHandleKey(void* handle) { if (handle == nullptr) { return 0; @@ -170,6 +176,11 @@ class ObjCBridgeState { napi_value proxyNativeObject(napi_env env, napi_value object, id nativeObject); +#if defined(TARGET_ENGINE_HERMES) + bool registerObjectFinalizer(napi_env env, napi_value object, + JSObjectFinalizerContext* context); + bool takeObjectFinalizer(JSObjectFinalizerContext* context); +#endif napi_value getObject(napi_env env, id object, napi_value constructor, ObjectOwnership ownership = kUnownedObject); @@ -330,16 +341,9 @@ class ObjCBridgeState { handleObjectRefs.erase(it); bumpHandleObjectRefsGeneration(); } - inline void deleteRecentObjectWrapperRef(napi_env env, - RecentObjectWrapperRef& entry) { - if (env != nullptr && entry.ref != nullptr) { - napi_delete_reference(env, entry.ref); - } - entry = {}; - } inline void cacheRecentObjectWrapper(napi_env env, id object, - napi_value value) { - if (env == nullptr || object == nil || value == nullptr) { + napi_value value, napi_ref ref) { + if (env == nullptr || object == nil || value == nullptr || ref == nullptr) { return; } @@ -350,7 +354,7 @@ class ObjCBridgeState { continue; } - napi_value existing = get_ref_value(env, entry.ref); + napi_value existing = get_ref_value(env, entry.borrowedRef); if (existing != nullptr) { bool isSameValue = false; if (napi_strict_equals(env, existing, value, &isSameValue) == napi_ok && @@ -359,8 +363,7 @@ class ObjCBridgeState { } } - deleteRecentObjectWrapperRef(env, entry); - napi_create_reference(env, value, 1, &entry.ref); + entry.borrowedRef = ref; entry.objectKey = objectKey; entry.objectClassKey = objectClassKey; return; @@ -369,9 +372,8 @@ class ObjCBridgeState { RecentObjectWrapperRef entry{ .objectKey = objectKey, .objectClassKey = objectClassKey, - .ref = nullptr, + .borrowedRef = ref, }; - napi_create_reference(env, value, 1, &entry.ref); static constexpr size_t kRecentObjectWrapperLimit = 16; if (recentObjectWrappers.size() < kRecentObjectWrapperLimit) { @@ -381,7 +383,6 @@ class ObjCBridgeState { RecentObjectWrapperRef& replaced = recentObjectWrappers[nextRecentObjectWrapperSlot++ % kRecentObjectWrapperLimit]; - deleteRecentObjectWrapperRef(env, replaced); replaced = entry; } inline napi_value getRecentObjectWrapper(napi_env env, id object) { @@ -397,12 +398,11 @@ class ObjCBridgeState { continue; } - napi_value value = get_ref_value(env, it->ref); + napi_value value = get_ref_value(env, it->borrowedRef); if (value != nullptr) { return value; } - deleteRecentObjectWrapperRef(env, *it); it = recentObjectWrappers.erase(it); } @@ -417,7 +417,6 @@ class ObjCBridgeState { const uintptr_t objectClassKey = NormalizeHandleKey((void*)object_getClass(object)); for (auto it = recentObjectWrappers.begin(); it != recentObjectWrappers.end();) { if (it->objectKey == objectKey && it->objectClassKey == objectClassKey) { - deleteRecentObjectWrapperRef(env, *it); it = recentObjectWrappers.erase(it); } else { ++it; @@ -939,7 +938,21 @@ class ObjCBridgeState { uint64_t lifetimeToken = 0; std::thread::id jsThreadId = std::this_thread::get_id(); CFRunLoopRef jsRunLoop = CFRunLoopGetCurrent(); + struct PendingFinalizer { + napi_finalize callback; + void* data; + void* hint; + }; + std::mutex pendingFinalizersMutex; + std::vector pendingFinalizers; + bool finalizerDrainScheduled = false; + bool acceptingFinalizers = true; std::unordered_map objectRefs; + struct PointerCacheEntry { + void* owner = nullptr; + napi_ref ref = nullptr; + }; + std::unordered_map pointerCache; std::unordered_map handleObjectRefs; std::vector recentObjectWrappers; size_t nextRecentObjectWrapperSlot = 0; @@ -950,6 +963,10 @@ class ObjCBridgeState { napi_ref createNativeProxy = nullptr; napi_ref createFastEnumeratorIterator = nullptr; napi_ref transferOwnershipToNative = nullptr; +#if defined(TARGET_ENGINE_HERMES) + napi_ref objectFinalizationRegistry = nullptr; + std::unordered_set objectFinalizers; +#endif std::unordered_map classes; std::unordered_map protocols; @@ -958,6 +975,7 @@ class ObjCBridgeState { std::unordered_map mdProtocolsByPointer; std::unordered_map nativeObjectsByBridgeWrapper; std::unordered_map constructorsByPointer; + StructInfo* syntheticCGPointInfo = nullptr; std::unordered_map cifs; std::unordered_map mdValueCache; diff --git a/NativeScript/ffi/objc/napi/ObjCBridge.mm b/NativeScript/ffi/objc/napi/ObjCBridge.mm new file mode 100644 index 000000000..fe330f374 --- /dev/null +++ b/NativeScript/ffi/objc/napi/ObjCBridge.mm @@ -0,0 +1,1302 @@ +#include "ObjCBridge.h" +#include "AutoreleasePool.h" +#include "Block.h" +#include "Class.h" +#include "ClassMember.h" +#include "Enum.h" +#include "InlineFunctions.h" +#include "Interop.h" +#include "Metadata.h" +#include "MetadataReader.h" +#include "NativeScript.h" +#include "Object.h" +#include "ObjectRef.h" +#include "Struct.h" +#include "TypeConv.h" +#include "Util.h" +#include "Variable.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "node_api_util.h" + +#import + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef EMBED_METADATA_SIZE +const unsigned char __attribute__((section("__objc_metadata,__objc_metadata"))) +#if defined(__aarch64__) +embedded_metadata[EMBED_METADATA_SIZE] = "NSMDSectionHeaderARM"; +#else +embedded_metadata[EMBED_METADATA_SIZE] = "NSMDSectionHeaderX86"; +#endif +#endif + +namespace nativescript { +namespace { +std::mutex gLiveBridgeStatesMutex; +std::unordered_map gLiveBridgeStates; +std::atomic gNextBridgeStateToken{1}; +constexpr const char* kNativePointerProperty = "__ns_native_ptr"; + +bool envFlagEnabled(const char* name) { + const char* value = std::getenv(name); + if (value == nullptr || value[0] == '\0') { + return false; + } + + return std::strcmp(value, "0") != 0 && std::strcmp(value, "false") != 0 && + std::strcmp(value, "FALSE") != 0 && std::strcmp(value, "False") != 0 && + std::strcmp(value, "off") != 0 && std::strcmp(value, "OFF") != 0 && + std::strcmp(value, "no") != 0 && std::strcmp(value, "NO") != 0; +} + +inline void deleteReferenceNow(napi_env env, napi_ref ref, bool unrefFirst) { + if (env == nullptr || ref == nullptr) { + return; + } + + if (unrefFirst) { + uint32_t remaining = 0; + napi_reference_unref(env, ref, &remaining); + } + + napi_delete_reference(env, ref); +} + +inline void deleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, + uint64_t bridgeStateToken, napi_ref ref, + bool unrefFirst) { + if (env == nullptr || ref == nullptr) { + return; + } + + if (bridgeState == nullptr) { + deleteReferenceNow(env, ref, unrefFirst); + return; + } + + if (!IsBridgeStateLive(bridgeState, bridgeStateToken)) { + return; + } + + if (bridgeState->jsThreadId == std::this_thread::get_id()) { +#if !defined(TARGET_ENGINE_QUICKJS) + deleteReferenceNow(env, ref, unrefFirst); + return; +#endif + } + + CFRunLoopRef runLoop = bridgeState->jsRunLoop; + if (runLoop == nullptr) { + runLoop = CFRunLoopGetMain(); + } + + if (runLoop == nullptr) { + if (bridgeState->jsThreadId == std::this_thread::get_id()) { + deleteReferenceNow(env, ref, unrefFirst); + } + return; + } + + CFRetain(runLoop); + CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ + if (IsBridgeStateLive(bridgeState, bridgeStateToken)) { + deleteReferenceNow(env, ref, unrefFirst); + } + CFRelease(runLoop); + }); + CFRunLoopWakeUp(runLoop); +} + +uint64_t RegisterBridgeState(const ObjCBridgeState* bridgeState) { + if (bridgeState == nullptr) { + return 0; + } + + uint64_t token = gNextBridgeStateToken.fetch_add(1, std::memory_order_relaxed); + std::lock_guard lock(gLiveBridgeStatesMutex); + gLiveBridgeStates[bridgeState] = token; + return token; +} + +void UnregisterBridgeState(const ObjCBridgeState* bridgeState) { + if (bridgeState == nullptr) { + return; + } + + std::lock_guard lock(gLiveBridgeStatesMutex); + gLiveBridgeStates.erase(bridgeState); +} +} // namespace + +bool IsBridgeStateLive(const ObjCBridgeState* bridgeState, uint64_t token) noexcept { + if (bridgeState == nullptr || token == 0) { + return false; + } + + std::lock_guard lock(gLiveBridgeStatesMutex); + auto find = gLiveBridgeStates.find(bridgeState); + return find != gLiveBridgeStates.end() && find->second == token; +} + +void DeleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, + uint64_t bridgeStateToken, napi_ref ref) { + deleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref, false); +} + +void ReleaseAndDeleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, + uint64_t bridgeStateToken, napi_ref ref) { + deleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref, true); +} + +bool PostFinalizer(napi_env env, napi_finalize finalize_cb, void* finalize_data, + void* finalize_hint) { + if (env == nullptr || finalize_cb == nullptr) { + return false; + } + + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && bridgeState->jsThreadId == std::this_thread::get_id()) { +#if !defined(TARGET_ENGINE_QUICKJS) && !defined(TARGET_ENGINE_V8) + finalize_cb(env, finalize_data, finalize_hint); + return true; +#endif + } + + if (bridgeState != nullptr) { + return bridgeState->enqueueFinalizer(finalize_cb, finalize_data, finalize_hint); + } + + CFRunLoopRef runLoop = CFRunLoopGetMain(); + if (runLoop == nullptr) { + return false; + } + + if (bridgeState == nullptr && [NSThread isMainThread]) { +#if !defined(TARGET_ENGINE_QUICKJS) && !defined(TARGET_ENGINE_V8) + finalize_cb(env, finalize_data, finalize_hint); + return true; +#endif + } + + CFRetain(runLoop); + CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ + finalize_cb(env, finalize_data, finalize_hint); + CFRelease(runLoop); + }); + CFRunLoopWakeUp(runLoop); + return true; +} + +bool ObjCBridgeState::enqueueFinalizer(napi_finalize callback, void* data, void* hint) { + if (callback == nullptr || jsRunLoop == nullptr) { + return false; + } + + bool scheduleDrain = false; + { + std::lock_guard lock(pendingFinalizersMutex); + if (!acceptingFinalizers) { + return false; + } + pendingFinalizers.push_back({callback, data, hint}); + if (!finalizerDrainScheduled) { + finalizerDrainScheduled = true; + scheduleDrain = true; + } + } + + if (!scheduleDrain) { + return true; + } + + CFRunLoopRef runLoop = jsRunLoop; + const uint64_t token = lifetimeToken; + CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ + if (IsBridgeStateLive(this, token)) { + drainFinalizers(); + } + }); + CFRunLoopWakeUp(runLoop); + return true; +} + +void ObjCBridgeState::drainFinalizers(bool stop) { + std::vector pending; + { + std::lock_guard lock(pendingFinalizersMutex); + if (stop) { + acceptingFinalizers = false; + } + pending.swap(pendingFinalizers); + finalizerDrainScheduled = false; + } + + for (const auto& finalizer : pending) { + finalizer.callback(env, finalizer.data, finalizer.hint); + } +} + +void finalize_bridge_data(napi_env env, void* data, void* hint) { + auto bridgeState = (ObjCBridgeState*)data; + delete bridgeState; +} + +#if defined(TARGET_ENGINE_HERMES) +napi_value runObjectFinalizer(napi_env env, napi_callback_info info) { + napi_value token = nullptr; + size_t argc = 1; + void* data = nullptr; + napi_get_cb_info(env, info, &argc, &token, nullptr, &data); + + uint64_t rawContext = 0; + bool lossless = false; + if (argc == 1 && napi_get_value_bigint_uint64(env, token, &rawContext, &lossless) == napi_ok && + lossless) { + auto* bridgeState = static_cast(data); + auto* context = reinterpret_cast( + static_cast(rawContext)); + if (IsBridgeStateLive(bridgeState, bridgeState != nullptr ? bridgeState->lifetimeToken : 0) && + bridgeState->takeObjectFinalizer(context)) { + finalize_objc_object(env, context, nullptr); + } + } + + napi_value undefined = nullptr; + napi_get_undefined(env, &undefined); + return undefined; +} +#endif + +MDMetadataReader* loadMetadataFromFile(const char* metadata_path) { + const char* path = metadata_path != nullptr ? metadata_path : "metadata.nsmd"; + auto f = fopen(path, "rb"); + if (f == nullptr) { + fprintf(stderr, "metadata.nsmd not found: %s\n", path); + exit(1); + } + fseek(f, 0, SEEK_END); + const long size = ftell(f); + fseek(f, 0, SEEK_SET); + if (size <= 0) { + fclose(f); + fprintf(stderr, "metadata.nsmd is empty: %s\n", path); + exit(1); + } + + auto buffer = static_cast(malloc(static_cast(size))); + if (buffer == nullptr) { + fclose(f); + fprintf(stderr, "failed to allocate metadata buffer: %s\n", path); + exit(1); + } + + const size_t bytesRead = fread(buffer, 1, static_cast(size), f); + fclose(f); + if (bytesRead != static_cast(size)) { + free(buffer); + fprintf(stderr, "failed to read metadata: %s\n", path); + exit(1); + } + + return new MDMetadataReader(buffer, true); +} + +inline bool hasNamedProperty(napi_env env, napi_value object, const char* name) { + bool hasProperty = false; + napi_has_named_property(env, object, name, &hasProperty); + return hasProperty; +} + +inline bool isFunctionValue(napi_env env, napi_value value) { + if (value == nullptr) { + return false; + } + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, value, &valueType) != napi_ok) { + return false; + } + if (valueType == napi_function) { + return true; + } + + if (valueType != napi_object) { + return false; + } + + napi_value instance = nullptr; + napi_status status = napi_new_instance(env, value, 0, nullptr, &instance); + if (status == napi_ok) { + return true; + } + + bool hasPendingException = false; + if (napi_is_exception_pending(env, &hasPendingException) == napi_ok && hasPendingException) { + napi_value exception = nullptr; + napi_get_and_clear_last_exception(env, &exception); + } + return false; +} + +inline void clearPendingException(napi_env env) { + bool hasPendingException = false; + if (napi_is_exception_pending(env, &hasPendingException) == napi_ok && hasPendingException) { + napi_value exception = nullptr; + napi_get_and_clear_last_exception(env, &exception); + } +} + +inline bool isConstructableValue(napi_env env, napi_value value) { + napi_value instance = nullptr; + napi_status status = napi_new_instance(env, value, 0, nullptr, &instance); + if (status == napi_ok) { + return true; + } + + clearPendingException(env); + return false; +} + +inline bool hasConstructableNamedProperty(napi_env env, napi_value global, const char* name) { + if (!hasNamedProperty(env, global, name)) { + return false; + } + + napi_value value = nullptr; + if (napi_get_named_property(env, global, name, &value) != napi_ok || value == nullptr) { + clearPendingException(env); + return false; + } + + return isConstructableValue(env, value); +} + +inline void defineGlobalValue(napi_env env, napi_value global, const char* name, napi_value value) { + if (name == nullptr || value == nullptr) { + return; + } + + if (napi_set_named_property(env, global, name, value) == napi_ok) { + return; + } + clearPendingException(env); + + napi_property_descriptor prop = { + .utf8name = name, + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = value, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = nullptr, + }; + if (napi_define_properties(env, global, 1, &prop) != napi_ok) { + clearPendingException(env); + } +} + +inline bool defineConstructableGlobalValue(napi_env env, napi_value global, const char* name, + napi_value value) { + if (!isConstructableValue(env, value)) { + return false; + } + + if (napi_set_named_property(env, global, name, value) == napi_ok && + hasConstructableNamedProperty(env, global, name)) { + return true; + } + clearPendingException(env); + + napi_property_descriptor prop = { + .utf8name = name, + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = value, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = nullptr, + }; + if (napi_define_properties(env, global, 1, &prop) == napi_ok && + hasConstructableNamedProperty(env, global, name)) { + return true; + } + clearPendingException(env); + + napi_value key = nullptr; + napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &key); + if (key != nullptr) { + bool deleted = false; + if (napi_delete_property(env, global, key, &deleted) == napi_ok && deleted) { + if (napi_define_properties(env, global, 1, &prop) == napi_ok && + hasConstructableNamedProperty(env, global, name)) { + return true; + } + clearPendingException(env); + if (napi_set_named_property(env, global, name, value) == napi_ok && + hasConstructableNamedProperty(env, global, name)) { + return true; + } + clearPendingException(env); + } else { + clearPendingException(env); + } + } + + return hasConstructableNamedProperty(env, global, name); +} + +inline std::string buildStructEncoding(StructInfo* info) { + if (info == nullptr || info->name == nullptr) { + return ""; + } + + std::string encoding = "{"; + encoding += info->name; + encoding += "="; + for (const auto& field : info->fields) { + if (field.type == nullptr) { + return ""; + } + field.type->encode(&encoding); + } + encoding += "}"; + return encoding; +} + +inline void setTypeEncodingSymbol(napi_env env, napi_value value, const std::string& encoding) { + if (value == nullptr || encoding.empty()) { + return; + } + + napi_value typeSymbol = jsSymbolFor(env, "type"); + napi_value encodedValue = nullptr; + napi_create_string_utf8(env, encoding.c_str(), NAPI_AUTO_LENGTH, &encodedValue); + if (typeSymbol != nullptr && encodedValue != nullptr) { + napi_set_property(env, value, typeSymbol, encodedValue); + } +} + +inline void registerStructAlias(napi_env env, napi_value global, ObjCBridgeState* bridgeState, + const char* aliasName, + std::initializer_list candidates) { + if (bridgeState == nullptr || aliasName == nullptr) { + return; + } + + if (hasNamedProperty(env, global, aliasName)) { + napi_value existing = nullptr; + if (napi_get_named_property(env, global, aliasName, &existing) == napi_ok && + isFunctionValue(env, existing)) { + return; + } + } + + for (const char* candidate : candidates) { + if (candidate == nullptr || candidate[0] == '\0') { + continue; + } + + auto structIt = bridgeState->structOffsets.find(candidate); + if (structIt != bridgeState->structOffsets.end()) { + StructInfo* info = bridgeState->getStructInfo(env, structIt->second); + if (info != nullptr) { + napi_value cls = StructObject::getJSClass(env, info); + if (isFunctionValue(env, cls)) { + setTypeEncodingSymbol(env, cls, buildStructEncoding(info)); + defineGlobalValue(env, global, aliasName, cls); + return; + } + } + } + + if (hasNamedProperty(env, global, candidate)) { + napi_value source = nullptr; + if (napi_get_named_property(env, global, candidate, &source) == napi_ok && + isFunctionValue(env, source)) { + defineGlobalValue(env, global, aliasName, source); + return; + } + } + } +} + +inline void ensureSyntheticCGPoint(napi_env env, napi_value global, + ObjCBridgeState* bridgeState) { + if (hasConstructableNamedProperty(env, global, "CGPoint")) { + return; + } + + StructInfo*& syntheticInfo = bridgeState->syntheticCGPointInfo; + if (syntheticInfo == nullptr) { + syntheticInfo = new StructInfo(); + syntheticInfo->name = strdup("CGPoint"); + syntheticInfo->size = sizeof(double) * 2; + syntheticInfo->jsClass = nullptr; + + const char* doubleEncodingX = "d"; + const char* doubleEncodingY = "d"; + + StructFieldInfo fieldX; + fieldX.name = strdup("x"); + fieldX.offset = 0; + fieldX.type = TypeConv::Make(env, &doubleEncodingX); + syntheticInfo->fields.push_back(fieldX); + + StructFieldInfo fieldY; + fieldY.name = strdup("y"); + fieldY.offset = sizeof(double); + fieldY.type = TypeConv::Make(env, &doubleEncodingY); + syntheticInfo->fields.push_back(fieldY); + } + + napi_value cls = StructObject::getJSClass(env, syntheticInfo); + if (!isFunctionValue(env, cls)) { + return; + } + + setTypeEncodingSymbol(env, cls, "{CGPoint=dd}"); + defineConstructableGlobalValue(env, global, "CGPoint", cls); +} + +inline void ensureConstructableStructAlias(napi_env env, napi_value global, + ObjCBridgeState* bridgeState, const char* aliasName, + std::initializer_list candidates) { + if (bridgeState == nullptr || aliasName == nullptr) { + return; + } + + if (hasConstructableNamedProperty(env, global, aliasName)) { + return; + } + + for (const char* candidate : candidates) { + if (candidate == nullptr || candidate[0] == '\0') { + continue; + } + + if (hasNamedProperty(env, global, candidate)) { + napi_value value = nullptr; + if (napi_get_named_property(env, global, candidate, &value) == napi_ok && + defineConstructableGlobalValue(env, global, aliasName, value)) { + return; + } + clearPendingException(env); + } + + auto structIt = bridgeState->structOffsets.find(candidate); + if (structIt != bridgeState->structOffsets.end()) { + StructInfo* info = bridgeState->getStructInfo(env, structIt->second); + if (info != nullptr) { + napi_value cls = StructObject::getJSClass(env, info); + if (defineConstructableGlobalValue(env, global, aliasName, cls)) { + setTypeEncodingSymbol(env, cls, buildStructEncoding(info)); + return; + } + } + } + } +} + +inline void installMacCompatShim(napi_env env) { + const char* script = R"( + (function (globalObject) { + ["CGPoint", "CGSize", "CGRect"].forEach(function (name) { + const constructor = globalObject[name + "Struct"]; + if (typeof globalObject[name] !== "function" && + typeof constructor === "function") { + Object.defineProperty(globalObject, name, { + configurable: true, + enumerable: true, + value: constructor + }); + } + }); + + if (typeof globalObject.UIColor === "undefined" && + typeof globalObject.NSColor !== "undefined") { + globalObject.UIColor = globalObject.NSColor; + } + + const colorCtor = globalObject.UIColor || globalObject.NSColor; + if (!colorCtor || !colorCtor.prototype) { + return; + } + + if (typeof colorCtor.prototype.initWithRedGreenBlueAlpha === "function") { + return; + } + + colorCtor.prototype.initWithRedGreenBlueAlpha = function (red, green, blue, alpha) { + if (typeof this.initWithSRGBRedGreenBlueAlpha === "function") { + return this.initWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof this.initWithCalibratedRedGreenBlueAlpha === "function") { + return this.initWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof colorCtor.colorWithSRGBRedGreenBlueAlpha === "function") { + return colorCtor.colorWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof colorCtor.colorWithCalibratedRedGreenBlueAlpha === "function") { + return colorCtor.colorWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); + } + return this; + }; + })(globalThis); + )"; + + napi_value shim = nullptr; + napi_create_string_utf8(env, script, NAPI_AUTO_LENGTH, &shim); + if (shim != nullptr) { + napi_value result = nullptr; + napi_run_script(env, shim, &result); + } +} + +inline void* resolveSymbolPointer(ObjCBridgeState* bridgeState, const char* symbolName) { + if (bridgeState == nullptr || symbolName == nullptr || symbolName[0] == '\0') { + return nullptr; + } + + void* symbol = dlsym(bridgeState->self_dl, symbolName); + if (symbol == nullptr) { + symbol = dlsym(RTLD_DEFAULT, symbolName); + } + if (symbol == nullptr) { + std::string underscored = "_"; + underscored += symbolName; + symbol = dlsym(bridgeState->self_dl, underscored.c_str()); + if (symbol == nullptr) { + symbol = dlsym(RTLD_DEFAULT, underscored.c_str()); + } + } + + return symbol; +} + +inline bool unwrapCompatNativeHandle(napi_env env, napi_value value, void** out) { + return unwrapKnownNativeHandle(env, value, out); +} + +inline napi_value createCompatDispatchQueueWrapper(napi_env env, dispatch_queue_t queue) { + if (queue == nullptr) { + napi_value nullValue = nullptr; + napi_get_null(env, &nullValue); + return nullValue; + } + + return Pointer::create(env, reinterpret_cast(queue)); +} + +inline napi_value compat_dispatch_get_global_queue(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2] = {nullptr, nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + int64_t identifier = 0; + if (argc > 0) { + napi_valuetype identifierType = napi_undefined; + if (napi_typeof(env, argv[0], &identifierType) == napi_ok && identifierType == napi_bigint) { + bool lossless = false; + if (napi_get_value_bigint_int64(env, argv[0], &identifier, &lossless) != napi_ok) { + napi_throw_type_error(env, nullptr, + "dispatch_get_global_queue expects a numeric identifier."); + return nullptr; + } + } else { + napi_value coercedIdentifier = nullptr; + if (napi_coerce_to_number(env, argv[0], &coercedIdentifier) != napi_ok || + napi_get_value_int64(env, coercedIdentifier, &identifier) != napi_ok) { + napi_throw_type_error(env, nullptr, + "dispatch_get_global_queue expects a numeric identifier."); + return nullptr; + } + } + } + + uint64_t flags = 0; + if (argc > 1) { + napi_valuetype flagsType = napi_undefined; + if (napi_typeof(env, argv[1], &flagsType) == napi_ok && flagsType == napi_bigint) { + bool lossless = false; + if (napi_get_value_bigint_uint64(env, argv[1], &flags, &lossless) != napi_ok) { + napi_throw_type_error(env, nullptr, "dispatch_get_global_queue expects numeric flags."); + return nullptr; + } + } else { + napi_value coercedFlags = nullptr; + int64_t signedFlags = 0; + if (napi_coerce_to_number(env, argv[1], &coercedFlags) != napi_ok || + napi_get_value_int64(env, coercedFlags, &signedFlags) != napi_ok) { + napi_throw_type_error(env, nullptr, "dispatch_get_global_queue expects numeric flags."); + return nullptr; + } + flags = static_cast(signedFlags); + } + } + + return createCompatDispatchQueueWrapper(env, dispatch_get_global_queue(identifier, flags)); +} + +inline napi_value compat_dispatch_get_current_queue(napi_env env, napi_callback_info info) { + (void)info; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return createCompatDispatchQueueWrapper(env, dispatch_get_current_queue()); +#pragma clang diagnostic pop +} + +inline napi_value compat_dispatch_async(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2] = {nullptr, nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + if (argc < 2) { + napi_throw_type_error(env, nullptr, "dispatch_async expects a queue and callback."); + return nullptr; + } + + void* queueHandle = nullptr; + if (!unwrapCompatNativeHandle(env, argv[0], &queueHandle) || queueHandle == nullptr) { + napi_throw_type_error(env, nullptr, "dispatch_async expects a native queue handle."); + return nullptr; + } + + napi_valuetype callbackType = napi_undefined; + if (napi_typeof(env, argv[1], &callbackType) != napi_ok || callbackType != napi_function) { + napi_throw_type_error(env, nullptr, "dispatch_async expects a function callback."); + return nullptr; + } + + auto closure = new Closure(env, std::string("v"), true); + id block = registerBlock(env, closure, argv[1]); + dispatch_block_t dispatchBlock = (dispatch_block_t)block; + + dispatch_async(reinterpret_cast(queueHandle), dispatchBlock); + [block release]; + + napi_value undefinedValue = nullptr; + napi_get_undefined(env, &undefinedValue); + return undefinedValue; +} + +inline void registerCompatFunctionIfMissing(napi_env env, napi_value global, + ObjCBridgeState* bridgeState, const char* functionName, + const char* encoding) { + if (hasNamedProperty(env, global, functionName)) { + return; + } + + void* fn = resolveSymbolPointer(bridgeState, functionName); + if (fn == nullptr && strcmp(functionName, "CC_SHA256") == 0) { + void* commonCrypto = dlopen("/usr/lib/system/libcommonCrypto.dylib", RTLD_NOW | RTLD_LOCAL); + if (commonCrypto != nullptr) { + fn = dlsym(commonCrypto, functionName); + if (fn == nullptr) { + fn = dlsym(commonCrypto, "_CC_SHA256"); + } + } + } + + if (fn == nullptr) { + return; + } + + napi_value wrapper = FunctionPointer::wrapWithEncoding(env, fn, encoding, false); + if (wrapper != nullptr) { + napi_set_named_property(env, global, functionName, wrapper); + } +} + +inline void registerCompatFunction(napi_env env, napi_value global, const char* functionName, + napi_callback callback) { + napi_value wrapper = nullptr; + napi_create_function(env, functionName, NAPI_AUTO_LENGTH, callback, nullptr, &wrapper); + if (wrapper != nullptr) { + napi_value key = nullptr; + napi_create_string_utf8(env, functionName, NAPI_AUTO_LENGTH, &key); + if (key != nullptr) { + bool deleted = false; + napi_delete_property(env, global, key, &deleted); + clearPendingException(env); + } + defineGlobalValue(env, global, functionName, wrapper); + } +} + +void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeState* bridgeState) { +#if TARGET_OS_OSX + registerStructAlias(env, global, bridgeState, "CGPoint", + {"CGPoint", "_CGPoint", "NSPoint", "_NSPoint"}); + registerStructAlias(env, global, bridgeState, "CGSize", + {"CGSize", "_CGSize", "NSSize", "_NSSize"}); + registerStructAlias(env, global, bridgeState, "CGRect", + {"CGRect", "_CGRect", "NSRect", "_NSRect"}); + ensureSyntheticCGPoint(env, global, bridgeState); + ensureConstructableStructAlias( + env, global, bridgeState, "CGPoint", + {"CGPointStruct", "NSPoint", "NSPointStruct", "_CGPoint", "_NSPoint", "CGPoint"}); + installMacCompatShim(env); +#endif + + // CommonCrypto compatibility used by historical runtime tests and apps. + registerCompatFunctionIfMissing(env, global, bridgeState, "CC_SHA256", "^C^vQ^C"); + registerCompatFunctionIfMissing(env, global, bridgeState, "CGColorGetComponents", "^d^v"); + + // Force known-good libdispatch globals on macOS. The metadata path can resolve these with an + // incompatible call shape, which crashes when tests dispatch timers from a background queue. + registerCompatFunction(env, global, "dispatch_async", compat_dispatch_async); + registerCompatFunction(env, global, "dispatch_get_current_queue", + compat_dispatch_get_current_queue); + registerCompatFunction(env, global, "dispatch_get_global_queue", + compat_dispatch_get_global_queue); +} + +ObjCBridgeState::ObjCBridgeState(napi_env env, const char* metadata_path, + const void* metadata_ptr) { + this->env = env; + napi_set_instance_data(env, this, finalize_bridge_data, nil); + lifetimeToken = RegisterBridgeState(this); + trackedObjectLiveness = [[NSMutableSet alloc] init]; + + self_dl = dlopen(nullptr, RTLD_NOW); + + if (metadata_ptr && *((const char*)metadata_ptr) != '\0') { +#ifdef EMBED_METADATA_SIZE + // NSLog(@"Ignoring metadata pointer due to embedded metadata"); + metadata = new MDMetadataReader((void*)embedded_metadata); +#else + // NSLog(@"Using metadata from pointer: %p", metadata_ptr); + metadata = new MDMetadataReader((void*)metadata_ptr); +#endif + } else { +#ifdef EMBED_METADATA_SIZE + if (metadata_path != nullptr) { + // NSLog(@"Loading metadata from file: %s", metadata_path); + metadata = loadMetadataFromFile(metadata_path); + } else { + // NSLog(@"Using embedded metadata"); + metadata = new MDMetadataReader((void*)embedded_metadata); + } +#else + unsigned long segmentSize = 0; + auto segmentData = getsegmentdata((const mach_header_64*)_dyld_get_image_header(0), + "__objc_metadata", &segmentSize); + if (segmentData != nullptr) { + metadata = new MDMetadataReader(segmentData); + } else { + metadata = loadMetadataFromFile(metadata_path); + } +#endif + } + + // objc_autoreleasePool = objc_autoreleasePoolPush(); +} + +ObjCBridgeState::~ObjCBridgeState() { + drainFinalizers(true); +#if defined(TARGET_ENGINE_HERMES) + auto pendingObjectFinalizers = std::move(objectFinalizers); + objectFinalizers.clear(); + if (env != nullptr && objectFinalizationRegistry != nullptr) { + napi_delete_reference(env, objectFinalizationRegistry); + objectFinalizationRegistry = nullptr; + } + for (auto* context : pendingObjectFinalizers) { + if (context == nullptr) { + continue; + } + unregisterObjectIfRefMatches(context->object, context->ref); + if (env != nullptr && context->ref != nullptr) { + napi_delete_reference(env, context->ref); + } + delete context; + } +#endif + UnregisterBridgeState(this); + + auto deleteRef = [&](napi_ref& ref) { + if (env != nullptr && ref != nullptr) { + napi_delete_reference(env, ref); + ref = nullptr; + } + }; + + for (auto& pair : constructorsByPointer) { + deleteRef(pair.second); + } + constructorsByPointer.clear(); + + for (auto& pair : pointerCache) { + deleteRef(pair.second.ref); + } + pointerCache.clear(); + + for (auto& frame : roundTripCacheFrames) { + for (auto& entry : frame) { + ObjCBridgeState::releaseRoundTripEntry(env, entry.second); + } + } + roundTripCacheFrames.clear(); + + for (auto& entry : recentRoundTripCache) { + ObjCBridgeState::releaseRoundTripEntry(env, entry.second); + } + recentRoundTripCache.clear(); + + for (auto& entry : handleObjectRefs) { + if (entry.second.ownsRef) { + deleteRef(entry.second.ref); + } + } + handleObjectRefs.clear(); + + recentObjectWrappers.clear(); + + std::unordered_set classAndProtocolConstructorRefs; + classAndProtocolConstructorRefs.reserve(classes.size() + protocols.size()); + for (const auto& pair : classes) { + if (pair.second != nullptr && pair.second->constructor != nullptr) { + classAndProtocolConstructorRefs.insert(pair.second->constructor); + } + } + for (const auto& pair : protocols) { + if (pair.second != nullptr && pair.second->constructor != nullptr) { + classAndProtocolConstructorRefs.insert(pair.second->constructor); + } + } + for (auto& pair : mdValueCache) { + napi_ref& ref = pair.second; + if (ref != nullptr && + classAndProtocolConstructorRefs.find(ref) == classAndProtocolConstructorRefs.end()) { + deleteRef(ref); + } + } + mdValueCache.clear(); + + deleteRef(pointerClass); + deleteRef(referenceClass); + deleteRef(functionReferenceClass); + deleteRef(createNativeProxy); + deleteRef(createFastEnumeratorIterator); + deleteRef(transferOwnershipToNative); + + // Clean up cached Cif objects + for (auto& pair : cifs) { + delete pair.second; + } + cifs.clear(); + + for (auto& pair : mdMethodSignatureCache) { + delete pair.second; + } + mdMethodSignatureCache.clear(); + + for (auto& pair : mdBlockSignatureCache) { + delete pair.second; + } + mdBlockSignatureCache.clear(); + + // Clean up ObjCClass objects + for (auto& pair : classes) { + delete pair.second; + } + classes.clear(); + + // Clean up ObjCProtocol objects + for (auto& pair : protocols) { + delete pair.second; + } + protocols.clear(); + + // Clean up StructInfo objects + for (auto& pair : structInfoCache) { + deleteRef(pair.second->jsClass); + delete pair.second; + } + structInfoCache.clear(); + + if (syntheticCGPointInfo != nullptr) { + deleteRef(syntheticCGPointInfo->jsClass); + std::free(syntheticCGPointInfo->name); + for (auto& field : syntheticCGPointInfo->fields) { + std::free(field.name); + } + delete syntheticCGPointInfo; + syntheticCGPointInfo = nullptr; + } + + // Clean up CFunction objects + for (auto& pair : cFunctionCache) { + delete pair.second; + } + cFunctionCache.clear(); + + for (auto& pair : mdFunctionSignatureCache) { + delete pair.second; + } + mdFunctionSignatureCache.clear(); + + NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); + trackedObjectLiveness = nullptr; + [trackedObjectTable release]; + + clearStructTypeCaches(env); + + // if (objc_autoreleasePool != nullptr) + // objc_autoreleasePoolPop(objc_autoreleasePool); + + delete metadata; + dlclose(self_dl); +} + +napi_value ObjCBridgeState::proxyNativeObject(napi_env env, napi_value object, id nativeObject) { + NS_OBJC_NAPI_PREAMBLE + + napi_value result = object; + const bool nativeIsArray = [nativeObject isKindOfClass:NSArray.class]; + bool shouldProxyArray = nativeIsArray && !envFlagEnabled("NS_DISABLE_NAPI_ARRAY_PROXY"); + if (shouldProxyArray) { + napi_value factory = get_ref_value(env, createNativeProxy); + napi_value transferOwnershipFunc = get_ref_value(env, this->transferOwnershipToNative); + napi_value global; + napi_value args[3] = {object, nullptr, transferOwnershipFunc}; + napi_get_boolean(env, true, &args[1]); + napi_get_global(env, &global); + napi_call_function(env, global, factory, 3, args, &result); + } + + napi_value nativePointer = Pointer::create(env, nativeObject); + if (nativePointer != nullptr) { + napi_set_named_property(env, result, kNativePointerProperty, nativePointer); + } + napi_ref ref = nullptr; + napi_wrap(env, result, nativeObject, nullptr, nullptr, nullptr); + + auto* finalizerContext = new JSObjectFinalizerContext{ + .bridgeState = this, + .bridgeStateToken = lifetimeToken, + .object = nativeObject, + .ref = nullptr, + }; +#if defined(TARGET_ENGINE_HERMES) + NAPI_GUARD(napi_create_reference(env, result, 0, &ref)) { + delete finalizerContext; + NAPI_THROW_LAST_ERROR + return nullptr; + } + finalizerContext->ref = ref; + if (!registerObjectFinalizer(env, result, finalizerContext)) { + napi_delete_reference(env, ref); + delete finalizerContext; + return nullptr; + } +#else + NAPI_GUARD( + napi_add_finalizer(env, result, finalizerContext, finalize_objc_object, nullptr, &ref)) { + delete finalizerContext; + NAPI_THROW_LAST_ERROR + return nullptr; + } + finalizerContext->ref = ref; +#endif + + storeObjectRef(nativeObject, ref); + cacheHandleObjectRef(env, nativeObject, ref); + cacheRecentObjectWrapper(env, nativeObject, result, ref); + attachObjectLifecycleAssociation(env, nativeObject); + trackObject(nativeObject); + + return result; +} + +#if defined(TARGET_ENGINE_HERMES) +bool ObjCBridgeState::registerObjectFinalizer(napi_env env, napi_value object, + JSObjectFinalizerContext* context) { + if (objectFinalizationRegistry == nullptr) { + napi_value global = nullptr; + napi_value constructor = nullptr; + napi_value callback = nullptr; + napi_value registry = nullptr; + if (napi_get_global(env, &global) != napi_ok || + napi_get_named_property(env, global, "FinalizationRegistry", &constructor) != napi_ok || + napi_create_function(env, "", 0, runObjectFinalizer, this, &callback) != napi_ok || + napi_new_instance(env, constructor, 1, &callback, ®istry) != napi_ok || + napi_create_reference(env, registry, 1, &objectFinalizationRegistry) != napi_ok) { + return false; + } + } + + napi_value registry = get_ref_value(env, objectFinalizationRegistry); + napi_value registerFunction = nullptr; + napi_value token = nullptr; + napi_value args[2] = {object, nullptr}; + if (registry == nullptr || + napi_get_named_property(env, registry, "register", ®isterFunction) != napi_ok || + napi_create_bigint_uint64(env, reinterpret_cast(context), &token) != napi_ok) { + return false; + } + args[1] = token; + if (napi_call_function(env, registry, registerFunction, 2, args, nullptr) != napi_ok) { + return false; + } + + objectFinalizers.insert(context); + return true; +} + +bool ObjCBridgeState::takeObjectFinalizer(JSObjectFinalizerContext* context) { + return context != nullptr && objectFinalizers.erase(context) != 0; +} +#endif + +void ObjCBridgeState::trackObject(id object) noexcept { + if (object == nil) { + return; + } + + NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); + if (trackedObjectTable == nil) { + return; + } + + NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; + std::lock_guard lock(objectRefsMutex); + [trackedObjectTable addObject:objectKey]; +} + +bool ObjCBridgeState::isTrackedObjectAlive(id object) const noexcept { + if (object == nil) { + return false; + } + + NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); + if (trackedObjectTable == nil) { + return false; + } + + NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; + std::lock_guard lock(objectRefsMutex); + return [trackedObjectTable containsObject:objectKey]; +} + +} // namespace nativescript + +using namespace nativescript; + +NAPI_FUNCTION(getArrayBuffer) { + NAPI_CALLBACK_BEGIN(2) + + void* ptr = Pointer::unwrap(env, argv[0])->data; + int64_t length; + napi_get_value_int64(env, argv[1], &length); + + napi_value arrayBuffer; + if (length < 0) { + napi_throw_error(env, nullptr, "Invalid ArrayBuffer length"); + return nullptr; + } + + napi_create_external_arraybuffer(env, ptr, static_cast(length), nullptr, nullptr, + &arrayBuffer); + + return arrayBuffer; +} + +NAPI_FUNCTION(init) { + NAPI_CALLBACK_BEGIN(1) + napi_valuetype type; + napi_typeof(env, argv[0], &type); + const char* metadata_path = nullptr; + if (type == napi_string) { + size_t len; + napi_get_value_string_utf8(env, argv[0], nullptr, 0, &len); + metadata_path = (char*)malloc(len + 1); + napi_get_value_string_utf8(env, argv[0], (char*)metadata_path, len + 1, &len); + } + nativescript_init(env, metadata_path, nullptr); + return nullptr; +} + +NAPI_EXPORT NAPI_MODULE_REGISTER { + const napi_property_descriptor property = NAPI_FUNCTION_DESC(init); + napi_define_properties(env, exports, 1, &property); + return exports; +} + +NAPI_EXPORT void nativescript_init(void* _env, const char* metadata_path, + const void* metadata_ptr) { + napi_env env = (napi_env)_env; + + ObjCBridgeState* bridgeState = new ObjCBridgeState(env, metadata_path, metadata_ptr); + + napi_value objc; + napi_create_object(env, &objc); + + const napi_property_descriptor objcProperties[] = { + NAPI_FUNCTION_DESC(registerClass), NAPI_FUNCTION_DESC(registerBlock), + NAPI_FUNCTION_DESC(import), NAPI_FUNCTION_DESC(autoreleasepool), + NAPI_FUNCTION_DESC(getArrayBuffer), + }; + + napi_define_properties(env, objc, 5, objcProperties); + + napi_value global; + napi_get_global(env, &global); + + const napi_property_descriptor globalProperties[] = {{ + .utf8name = "objc", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = objc, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "ObjectRef", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = defineObjectRefClass(env), + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "NativeClass", + .method = JS_registerClass, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }}; + + napi_define_properties(env, global, 3, globalProperties); + + setupObjCClassDecorator(env); + + initProxyFactory(env, bridgeState); + initFastEnumeratorIteratorFactory(env, bridgeState); + + registerInterop(env, global); + registerInlineFunctions(env); + + bridgeState->registerVarGlobals(env, global); + bridgeState->registerEnumGlobals(env, global); + bridgeState->registerStructGlobals(env, global); + bridgeState->registerUnionGlobals(env, global); + bridgeState->registerFunctionGlobals(env, global); + bridgeState->registerClassGlobals(env, global); + bridgeState->registerProtocolGlobals(env, global); + registerLegacyCompatGlobals(env, global, bridgeState); +} diff --git a/NativeScript/ffi/napi/Object.h b/NativeScript/ffi/objc/napi/Object.h similarity index 100% rename from NativeScript/ffi/napi/Object.h rename to NativeScript/ffi/objc/napi/Object.h diff --git a/NativeScript/ffi/objc/napi/Object.mm b/NativeScript/ffi/objc/napi/Object.mm new file mode 100644 index 000000000..7058a4e78 --- /dev/null +++ b/NativeScript/ffi/objc/napi/Object.mm @@ -0,0 +1,834 @@ +#include "Object.h" +#include +#include "Interop.h" +#include "JSObject.h" +#include "ObjCBridge.h" +#include "js_native_api.h" +#include "node_api_util.h" + +#import +#include + +static SEL JSWrapperObjectAssociationKey = @selector(JSWrapperObjectAssociationKey); +static SEL ObjCLifecycleAssociationKey = @selector(ObjCLifecycleAssociationKey); + +@interface JSWrapperObjectAssociation : NSObject + +@property(nonatomic) napi_env env; +@property(nonatomic) napi_ref ref; +@property(nonatomic) nativescript::ObjCBridgeState* bridgeState; +@property(nonatomic) uint64_t bridgeStateToken; + ++ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object; + ++ (instancetype)associationFor:(id)object; + +- (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref; + +@end + +@interface ObjCLifecycleAssociation : NSObject { + nativescript::ObjCBridgeState* _bridgeState; + uint64_t _bridgeStateToken; + uintptr_t _objectAddress; +} + +- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState object:(id)object; + +@end + +@implementation JSWrapperObjectAssociation + +- (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref { + self = [super init]; + if (self) { + self.env = env; + self.ref = ref; + self.bridgeState = nativescript::ObjCBridgeState::InstanceData(env); + self.bridgeStateToken = self.bridgeState != nullptr ? self.bridgeState->lifetimeToken : 0; + } + return self; +} + ++ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object { + napi_ref ref = nativescript::make_ref(env, value); + JSWrapperObjectAssociation* association = [[JSWrapperObjectAssociation alloc] initWithEnv:env + ref:ref]; + objc_setAssociatedObject(object, JSWrapperObjectAssociationKey, association, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [association release]; +} + ++ (instancetype)associationFor:(id)object { + return objc_getAssociatedObject(object, JSWrapperObjectAssociationKey); +} + +- (void)dealloc { + if (self.env != nullptr && self.ref != nullptr && + (self.bridgeState == nullptr || + nativescript::IsBridgeStateLive(self.bridgeState, self.bridgeStateToken))) { + nativescript::DeleteReferenceOnOwningThread(self.env, self.bridgeState, + self.bridgeStateToken, self.ref); + self.ref = nullptr; + } + self.bridgeState = nullptr; + self.bridgeStateToken = 0; + self.env = nullptr; + [super dealloc]; +} + +@end + +@implementation ObjCLifecycleAssociation + +- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState object:(id)object { + self = [super init]; + if (self) { + _bridgeState = bridgeState; + _bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; + _objectAddress = (uintptr_t)object; + } + + return self; +} + +- (void)dealloc { + nativescript::ObjCBridgeState* bridgeState = _bridgeState; + uint64_t bridgeStateToken = _bridgeStateToken; + uintptr_t objectAddress = _objectAddress; + if (nativescript::IsBridgeStateLive(bridgeState, bridgeStateToken)) { + bridgeState->detachObject((id)objectAddress); + } + + [super dealloc]; +} + +@end + +napi_value JS_transferOwnershipToNative(napi_env env, napi_callback_info cbinfo) { + size_t argc = 1; + napi_value arg = nullptr; + if (napi_get_cb_info(env, cbinfo, &argc, &arg, nullptr, nullptr) != napi_ok || argc == 0 || + arg == nullptr) { + return nullptr; + } + + id obj = nil; + if (napi_unwrap(env, arg, (void**)&obj) != napi_ok || obj == nil) { + return nullptr; + } + + [JSWrapperObjectAssociation transferOwnership:env of:arg toNative:obj]; + + return nullptr; +} + +namespace nativescript { + +napi_value findConstructorForObject(napi_env env, ObjCBridgeState* bridgeState, id object, + Class cls = nil); + +void transferOwnershipToNative(napi_env env, napi_value value, id object) { + if (env == nullptr || value == nullptr || object == nil) { + return; + } + + [JSWrapperObjectAssociation transferOwnership:env of:value toNative:object]; +} + +namespace { +constexpr const char* kNativePointerProperty = "__ns_native_ptr"; + +napi_value findConstructorForClassObject(napi_env env, ObjCBridgeState* bridgeState, Class cls, + napi_value fallback = nullptr) { + if (bridgeState == nullptr || cls == nil) { + return fallback; + } + + auto bridgedClass = bridgeState->classesByPointer.find(cls); + if (bridgedClass != bridgeState->classesByPointer.end() && bridgedClass->second != nullptr) { + return get_ref_value(env, bridgedClass->second->constructor); + } + + auto constructedClass = bridgeState->constructorsByPointer.find(cls); + if (constructedClass != bridgeState->constructorsByPointer.end()) { + napi_value constructor = get_ref_value(env, constructedClass->second); + if (constructor != nullptr) { + return constructor; + } + } + + auto metadataClass = bridgeState->mdClassesByPointer.find(cls); + if (metadataClass != bridgeState->mdClassesByPointer.end()) { + auto bridgedMetadataClass = bridgeState->getClass(env, metadataClass->second); + if (bridgedMetadataClass != nullptr) { + return get_ref_value(env, bridgedMetadataClass->constructor); + } + } + + const char* runtimeName = class_getName(cls); + if (runtimeName != nullptr && runtimeName[0] != '\0') { + napi_value global = nullptr; + napi_value constructor = nullptr; + bool hasGlobal = false; + napi_get_global(env, &global); + if (napi_has_named_property(env, global, runtimeName, &hasGlobal) == napi_ok && hasGlobal && + napi_get_named_property(env, global, runtimeName, &constructor) == napi_ok && + constructor != nullptr) { + return constructor; + } + } + + napi_value resolved = findConstructorForObject(env, bridgeState, (id)cls, cls); + return resolved != nullptr ? resolved : fallback; +} +} // namespace + +const char* nativeObjectProxySource = R"( + (function (object, isArray, transferOwnershipToNative) { + let isTransfered = false; + const boundMethods = Object.create(null); + let objectAtIndexMethod; + let addObjectMethod; + let removeObjectAtIndexMethod; + let setObjectAtIndexedSubscriptMethod; + + function bindTargetMethod(target, name) { + const cached = boundMethods[name]; + if (cached !== undefined) { + return cached; + } + + const value = target[name]; + if (typeof value !== "function") { + return value; + } + + if (value.__ns_proxy_bound === true) { + boundMethods[name] = value; + return value; + } + + const wrapper = value.bind(target); + Object.defineProperty(wrapper, "__ns_proxy_bound", { value: true }); + boundMethods[name] = wrapper; + try { + Object.defineProperty(target, name, { + value: wrapper, + configurable: true, + writable: true + }); + } catch (_) { + } + return wrapper; + } + + return new Proxy(object, { + get (target, name, receiver) { + if (name === "superclass" && typeof target.class === "function") { + return target.class().superclass(); + } + + if (isArray && name === "count") { + return target.count; + } + + if (isArray) { + switch (name) { + case "objectAtIndex": + return objectAtIndexMethod !== undefined + ? objectAtIndexMethod + : (objectAtIndexMethod = bindTargetMethod(target, name)); + case "addObject": + return addObjectMethod !== undefined + ? addObjectMethod + : (addObjectMethod = bindTargetMethod(target, name)); + case "removeObjectAtIndex": + return removeObjectAtIndexMethod !== undefined + ? removeObjectAtIndexMethod + : (removeObjectAtIndexMethod = bindTargetMethod(target, name)); + case "setObjectAtIndexedSubscript": + return setObjectAtIndexedSubscriptMethod !== undefined + ? setObjectAtIndexedSubscriptMethod + : (setObjectAtIndexedSubscriptMethod = bindTargetMethod(target, name)); + } + } + + if (typeof name === "string") { + const boundMethod = boundMethods[name]; + if (boundMethod !== undefined) { + return boundMethod; + } + } + + const value = target[name]; + if (value !== undefined || name in target) { + if (typeof value === "function" && name !== "constructor") { + if (value.__ns_proxy_bound === true) { + return value; + } + + let wrapper; + if ((name === "isKindOfClass" || name === "isMemberOfClass")) { + wrapper = function (cls, a1, a2, a3) { + let resolvedClass = cls; + const resolvedClassType = typeof resolvedClass; + if (resolvedClass != null && + (resolvedClassType === "object" || resolvedClassType === "function")) { + try { + const runtimeName = typeof NSStringFromClass === "function" + ? NSStringFromClass(resolvedClass) + : null; + if (typeof runtimeName === "string" && runtimeName.length > 0) { + const registry = globalThis.__nsConstructorsByObjCClassName; + if (registry && registry[runtimeName]) { + resolvedClass = registry[runtimeName]; + } else if (typeof globalThis[runtimeName] !== "undefined") { + resolvedClass = globalThis[runtimeName]; + } + } + } catch (_) { + } + } + + switch (arguments.length) { + case 0: + case 1: + return value.call(target, resolvedClass); + case 2: + return value.call(target, resolvedClass, a1); + case 3: + return value.call(target, resolvedClass, a1, a2); + case 4: + return value.call(target, resolvedClass, a1, a2, a3); + default: { + const args = Array.prototype.slice.call(arguments); + args[0] = resolvedClass; + return Reflect.apply(value, target, args); + } + } + }; + } else { + wrapper = value.bind(target); + } + + Object.defineProperty(wrapper, "__ns_proxy_bound", { value: true }); + if (typeof name === "string") { + boundMethods[name] = wrapper; + } + try { + Object.defineProperty(target, name, { + value: wrapper, + configurable: true, + writable: true + }); + } catch (_) { + } + return wrapper; + } + return value; + } + + if (typeof name === 'symbol') { + return undefined; + } + + if (isArray) { + const index = Number(name); + if (!isNaN(index)) { + return target.objectAtIndex(index); + } + } + }, + + set (target, name, value) { + const isInternalProperty = typeof name === 'string' && + (name === 'napi_external' || name === 'napi_typetag' || name === '__ns_native_ptr'); + + if (typeof name === 'symbol') { + target[name] = value; + return true; + } + + if (typeof name === "string" && boundMethods[name] !== undefined) { + delete boundMethods[name]; + } + + if (isArray) { + switch (name) { + case "objectAtIndex": + objectAtIndexMethod = undefined; + break; + case "addObject": + addObjectMethod = undefined; + break; + case "removeObjectAtIndex": + removeObjectAtIndexMethod = undefined; + break; + case "setObjectAtIndexedSubscript": + setObjectAtIndexedSubscriptMethod = undefined; + break; + } + } + + if (isArray) { + const index = Number(name); + if (!isNaN(index)) { + target.setObjectAtIndexedSubscript(value, index); + return true; + } + } + + if (!isInternalProperty && !(name in target) && !isTransfered) { + isTransfered = true; + transferOwnershipToNative(target); + } + + target[name] = value; + + return true; + }, + }); + }) +)"; + +void initProxyFactory(napi_env env, ObjCBridgeState* state) { + napi_value script, result; + napi_create_string_utf8(env, nativeObjectProxySource, NAPI_AUTO_LENGTH, &script); + napi_run_script(env, script, &result); + state->createNativeProxy = make_ref(env, result); + + napi_value transferOwnershipToNative; + napi_create_function(env, "transferOwnershipToNative", NAPI_AUTO_LENGTH, + JS_transferOwnershipToNative, nullptr, &transferOwnershipToNative); + state->transferOwnershipToNative = make_ref(env, transferOwnershipToNative); +} + +void attachObjectLifecycleAssociation(napi_env env, id object) { + if (object == nil) { + return; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr) { + return; + } + + if (objc_getAssociatedObject(object, ObjCLifecycleAssociationKey) != nil) { + return; + } + + ObjCLifecycleAssociation* association = + [[ObjCLifecycleAssociation alloc] initWithBridgeState:bridgeState object:object]; + objc_setAssociatedObject(object, ObjCLifecycleAssociationKey, association, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [association release]; +} + +namespace { +void finalize_objc_object_now(napi_env env, void* data, void* hint) { + (void)hint; + JSObjectFinalizerContext* context = static_cast(data); + if (context == nullptr) { + return; + } + + ObjCBridgeState* bridgeState = context->bridgeState; + if (IsBridgeStateLive(bridgeState, context->bridgeStateToken)) { + bridgeState->unregisterObjectIfRefMatches(context->object, context->ref); + } + +#if defined(TARGET_ENGINE_HERMES) + if (env != nullptr && context->ref != nullptr) { + napi_delete_reference(env, context->ref); + context->ref = nullptr; + } +#endif + + delete context; +} +} // namespace + +void finalize_objc_object(napi_env env, void* data, void* hint) { + if (PostFinalizer(env, finalize_objc_object_now, data, hint)) { + return; + } + + finalize_objc_object_now(env, data, hint); +} + +napi_value ObjCBridgeState::getObject(napi_env env, id obj, napi_value constructor, + ObjectOwnership ownership) { + if (obj == nil) { + return nullptr; + } + + NS_OBJC_NAPI_PREAMBLE + + Class cls = object_getClass(obj); + + if (cls == nullptr) { + return nullptr; + } + + if (class_isMetaClass(cls)) { + return findConstructorForClassObject(env, this, (Class)obj, constructor); + } + + napi_value resolvedConstructor = constructor; + if (resolvedConstructor != nullptr) { + Class hintedClass = nil; + tryResolveBridgedClassConstructor(env, resolvedConstructor, &hintedClass); + } + + napi_value actualConstructor = findConstructorForObject(env, this, obj, cls); + if (actualConstructor != nullptr) { + resolvedConstructor = actualConstructor; + } + + if (napi_value cached = findCachedObjectWrapper(env, obj); cached != nullptr) { + return cached; + } + + napi_value result = nil; + napi_value prototype; + NAPI_GUARD(napi_get_named_property(env, resolvedConstructor, "prototype", &prototype)) { + NAPI_THROW_LAST_ERROR + return nullptr; + } + + NAPI_GUARD(napi_create_object(env, &result)) { + NAPI_THROW_LAST_ERROR + return nullptr; + } + + napi_value global; + napi_value objectCtor; + napi_value setPrototypeOf; + napi_value argv[2] = {result, prototype}; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &objectCtor); + napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); + + NAPI_GUARD(napi_call_function(env, objectCtor, setPrototypeOf, 2, argv, nullptr)) { + NAPI_THROW_LAST_ERROR + return nullptr; + } + + napi_wrap(env, result, obj, nullptr, nullptr, nullptr); + napi_value nativePointer = Pointer::create(env, obj); + if (nativePointer != nullptr) { + napi_set_named_property(env, result, kNativePointerProperty, nativePointer); + } + + if (ownership == kUnownedObject) { + [obj retain]; + } + + result = proxyNativeObject(env, result, obj); + + // #if DEBUG + // napi_value global, Error, error, stack; + // napi_get_global(env, &global); + // napi_get_named_property(env, global, "Error", &Error); + // napi_new_instance(env, Error, 0, nullptr, &error); + // napi_get_named_property(env, error, "stack", &stack); + + // size_t stackSize; + // napi_get_value_string_utf8(env, stack, nullptr, 0, &stackSize); + // char *stackStr = new char[stackSize + 1]; + // napi_get_value_string_utf8(env, stack, stackStr, stackSize + 1, nullptr); + + // NSString *str = [NSString stringWithFormat:@"Wrapped object <%s: %p> @ %ld # %s", + // class_getName(cls), obj, [obj retainCount], stackStr]; + // dbglog([str UTF8String]); + + // delete[] stackStr; + // #endif + + return result; +} + +napi_value ObjCBridgeState::findCachedObjectWrapper(napi_env env, id obj) { + if (obj == nil) { + return nullptr; + } + + if (napi_value jsObject = idToJsObject(env, obj); jsObject != nullptr) { + return jsObject; + } + + auto roundTrip = getRoundTripObject(env, obj); + if (roundTrip != nullptr) { + return roundTrip; + } + + if (napi_value recentWrapper = getRecentObjectWrapper(env, obj); recentWrapper != nullptr) { + return recentWrapper; + } + + if (napi_value handleCached = getCachedHandleObject(env, (void*)obj); handleCached != nullptr) { + bool isRawHandleRoundTrip = ownsCachedHandleObjectRef((void*)obj); + void* wrapped = nullptr; + if (napi_unwrap(env, handleCached, &wrapped) == napi_ok && + NormalizeHandleKey(wrapped) == NormalizeHandleKey((void*)obj)) { + return handleCached; + } + + bool hasNativePointer = false; + if (napi_has_named_property(env, handleCached, kNativePointerProperty, &hasNativePointer) == + napi_ok && + hasNativePointer) { + napi_value nativePointerValue = nullptr; + if (napi_get_named_property(env, handleCached, kNativePointerProperty, &nativePointerValue) == + napi_ok && + Pointer::isInstance(env, nativePointerValue)) { + Pointer* pointer = Pointer::unwrap(env, nativePointerValue); + if (pointer != nullptr && + NormalizeHandleKey(pointer->data) == NormalizeHandleKey((void*)obj)) { + return handleCached; + } + } + } + + if (isRawHandleRoundTrip) { + return handleCached; + } + + removeCachedHandleObject(env, (void*)obj); + } + + if (napi_value existing = getNormalizedObjectRef(env, obj); existing != nullptr) { + return existing; + } + + JSWrapperObjectAssociation* association = [JSWrapperObjectAssociation associationFor:obj]; + if (association != nil && association.env == env && association.bridgeState == this && + IsBridgeStateLive(association.bridgeState, association.bridgeStateToken)) { + napi_value jsObject = get_ref_value(env, association.ref); + if (jsObject != nullptr) { + bool isArrayBuffer = false; + bool isTypedArray = false; + bool isDataView = false; + if ((napi_is_arraybuffer(env, jsObject, &isArrayBuffer) == napi_ok && isArrayBuffer) || + (napi_is_typedarray(env, jsObject, &isTypedArray) == napi_ok && isTypedArray) || + (napi_is_dataview(env, jsObject, &isDataView) == napi_ok && isDataView)) { + return jsObject; + } + + [obj retain]; + return proxyNativeObject(env, jsObject, obj); + } + } + + return nullptr; +} + +napi_value findConstructorForObject(napi_env env, ObjCBridgeState* bridgeState, id object, + Class cls) { + if (cls == nil) { + cls = object_getClass(object); + } + + // Look up if there is a custom class for it already + { + auto find = bridgeState->classesByPointer.find(cls); + if (find != bridgeState->classesByPointer.end()) { + return get_ref_value(env, find->second->constructor); + } + } + + // Look up if there is a custom constructor for it already + { + auto find = bridgeState->constructorsByPointer.find(cls); + if (find != bridgeState->constructorsByPointer.end()) { + return get_ref_value(env, find->second); + } + } + + // Look up if there is a metadata-defined class + { + auto find = bridgeState->mdClassesByPointer.find(cls); + if (find != bridgeState->mdClassesByPointer.end()) { + auto cls = bridgeState->getClass(env, find->second); + return get_ref_value(env, cls->constructor); + } + } + + Class superclass = class_getSuperclass(cls); + if (superclass != nullptr) { + napi_value superclassConstructor = + findConstructorForObject(env, bridgeState, object, superclass); + if (superclassConstructor != nullptr) { + return superclassConstructor; + } + } + + // Look up the protocols implemented by this class if no class-based + // constructor could be resolved. For private runtime subclasses we prefer + // inheriting the public superclass surface over exposing a protocol-only + // shell that drops concrete class members. + { + unsigned int count; + auto protocols = class_copyProtocolList(cls, &count); + std::unordered_set impls; + + std::function processProtocolList = [&](Protocol** list, + unsigned int count) { + for (unsigned int i = 0; i < count; i++) { + auto protocol = list[i]; + auto find = bridgeState->mdProtocolsByPointer.find(protocol); + if (find != bridgeState->mdProtocolsByPointer.end()) { + impls.insert(bridgeState->getProtocol(env, find->second)); + } + list = protocol_copyProtocolList(protocol, &count); + processProtocolList(list, count); + } + }; + + processProtocolList(protocols, count); + + if (!impls.empty()) { + napi_value constructor; + napi_define_class(env, class_getName(cls), NAPI_AUTO_LENGTH, ObjCProtocol::jsConstructor, + nullptr, 0, nullptr, &constructor); + for (auto impl : impls) { + ObjCClassMember::defineMembers(env, impl->members, impl->membersOffset, constructor); + } + + bridgeState->constructorsByPointer[cls] = make_ref(env, constructor); + + return constructor; + } + } + + return nullptr; +} + +// Get a napi_value for an Objective-C object, creating it if it doesn't exist. +// Here we also ensure that the native object always points to the same +// JS object, this makes sure that we only ever finalize it once. +// Might want to consider using associated objects instead of a hashtable. +napi_value ObjCBridgeState::getObject(napi_env env, id obj, ObjectOwnership ownership, + MDSectionOffset classOffset, + std::vector* protocolOffsets) { + NS_OBJC_NAPI_PREAMBLE + + if (obj == nullptr) { + return nullptr; + } + + Class objectClass = object_getClass(obj); + if (objectClass != nil && class_isMetaClass(objectClass)) { + return findConstructorForClassObject(env, this, (Class)obj, nullptr); + } + + auto roundTrip = getRoundTripObject(env, obj); + if (roundTrip != nullptr) { + return roundTrip; + } + + if (napi_value existing = getNormalizedObjectRef(env, obj); existing != nullptr) { + return existing; + } + + auto findClass = classesByPointer.find(obj); + if (findClass != classesByPointer.end()) { + return get_ref_value(env, findClass->second->constructor); + } + + auto mdFindClassByPointer = mdClassesByPointer.find((Class)obj); + if (mdFindClassByPointer != mdClassesByPointer.end()) { + auto bridgedClass = getClass(env, mdFindClassByPointer->second); + return bridgedClass != nullptr ? get_ref_value(env, bridgedClass->constructor) : nullptr; + } + + auto cls = objectClass; + + auto mdFindByPointer = mdClassesByPointer.find(cls); + if (mdFindByPointer != mdClassesByPointer.end()) { + classOffset = mdFindByPointer->second; + } + + auto findByPointer = classesByPointer.find(cls); + if (findByPointer != classesByPointer.end()) { + return getObject(env, obj, get_ref_value(env, findByPointer->second->constructor), ownership); + } + + napi_value constructor = nullptr; + if (classOffset != 0) { + auto bridgedCls = getClass(env, classOffset); + + if (bridgedCls == nullptr) { + return nullptr; + } + + constructor = get_ref_value(env, bridgedCls->constructor); + } else if (protocolOffsets != nullptr && !protocolOffsets->empty()) { + auto proto = getProtocol(env, protocolOffsets->front()); + + if (proto == nullptr) { + return nullptr; + } + + constructor = get_ref_value(env, proto->constructor); + } else { + constructor = findConstructorForObject(env, this, obj, cls); + } + + if (constructor == nullptr) { + return nullptr; + } + + return getObject(env, obj, constructor, ownership); +} + +void ObjCBridgeState::unregisterObject(id object) noexcept { + // #if DEBUG + // NSString *string = [NSString stringWithFormat: @"Unregistering object <%s: %p> @ %ld # success: + // %d, finalized: %d", + // class_getName(object_getClass(object)), object, [object retainCount], + // (int)objectRefs.contains(object), (int)finalized]; + + // dbglog([string UTF8String]); + // #endif + + if (takeObjectRef(object) != nullptr) { + removeRecentObjectWrapper(env, object); + removeCachedHandleObject(env, (void*)object); + [object release]; + } +} + +bool ObjCBridgeState::unregisterObjectIfRefMatches(id object, napi_ref ref) noexcept { + if (takeObjectRef(object, ref) == nullptr) { + return false; + } + + removeRecentObjectWrapper(env, object); + removeCachedHandleObject(env, (void*)object); + [object release]; + return true; +} + +void ObjCBridgeState::detachObject(id object) noexcept { + takeObjectRef(object); + removeRoundTripObject(object); + removeRecentObjectWrapper(env, object); + removeCachedHandleObject(env, (void*)object); + + if (object == nil) { + return; + } + + NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); + if (trackedObjectTable == nil) { + return; + } + + NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; + std::lock_guard lock(objectRefsMutex); + [trackedObjectTable removeObject:objectKey]; +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/ObjectRef.h b/NativeScript/ffi/objc/napi/ObjectRef.h similarity index 100% rename from NativeScript/ffi/napi/ObjectRef.h rename to NativeScript/ffi/objc/napi/ObjectRef.h diff --git a/NativeScript/ffi/napi/ObjectRef.mm b/NativeScript/ffi/objc/napi/ObjectRef.mm similarity index 96% rename from NativeScript/ffi/napi/ObjectRef.mm rename to NativeScript/ffi/objc/napi/ObjectRef.mm index ccd6cb4bf..160f97b1d 100644 --- a/NativeScript/ffi/napi/ObjectRef.mm +++ b/NativeScript/ffi/objc/napi/ObjectRef.mm @@ -41,6 +41,9 @@ void ObjectRef_finalize(napi_env env, void* data, void* hint) { auto conv = TypeConv::Make(env, &argenc); bool shouldFree; conv->toNative(env, arg, data, &shouldFree, &shouldFree); + if (ConsumeNapiArgumentConversionFailure(env)) { + return nullptr; + } } else { *(id*)data = nil; } @@ -91,6 +94,7 @@ void ObjectRef_finalize(napi_env env, void* data, void* hint) { auto conv = TypeConv::Make(env, &argenc); bool shouldFree; conv->toNative(env, arg, data, &shouldFree, &shouldFree); + ConsumeNapiArgumentConversionFailure(env); return nullptr; } diff --git a/NativeScript/ffi/napi/Protocol.h b/NativeScript/ffi/objc/napi/Protocol.h similarity index 100% rename from NativeScript/ffi/napi/Protocol.h rename to NativeScript/ffi/objc/napi/Protocol.h diff --git a/NativeScript/ffi/napi/Protocol.mm b/NativeScript/ffi/objc/napi/Protocol.mm similarity index 100% rename from NativeScript/ffi/napi/Protocol.mm rename to NativeScript/ffi/objc/napi/Protocol.mm diff --git a/NativeScript/ffi/objc/napi/SignatureDispatch.h b/NativeScript/ffi/objc/napi/SignatureDispatch.h new file mode 100644 index 000000000..66a393831 --- /dev/null +++ b/NativeScript/ffi/objc/napi/SignatureDispatch.h @@ -0,0 +1,138 @@ +#ifndef NS_FFI_NAPI_SIGNATURE_DISPATCH_H +#define NS_FFI_NAPI_SIGNATURE_DISPATCH_H + +#include + +#include "Cif.h" +#include "ffi/objc/shared/SignatureDispatchCore.h" +#include "js_native_api.h" + +namespace nativescript { + +using ObjCNapiInvoker = bool (*)(napi_env env, Cif* cif, void* fnptr, id self, + SEL selector, const napi_value* argv, + void* rvalue); +using CFunctionNapiInvoker = bool (*)(napi_env env, Cif* cif, void* fnptr, + const napi_value* argv, void* rvalue); + +struct ObjCNapiDispatchEntry { + uint64_t dispatchId; + ObjCNapiInvoker invoker; +}; + +struct CFunctionNapiDispatchEntry { + uint64_t dispatchId; + CFunctionNapiInvoker invoker; +}; + +} // namespace nativescript + +#ifndef NS_GSD_BACKEND_NAPI +#define NS_GSD_BACKEND_NAPI 1 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH 0 +#endif + +#ifndef NS_GSD_BACKEND_HERMES +#define NS_GSD_BACKEND_HERMES 0 +#endif + +#ifndef NS_GSD_BACKEND_PREPARED +#define NS_GSD_BACKEND_PREPARED 0 +#endif + +#define NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH \ + (NS_GSD_BACKEND_HERMES || NS_GSD_BACKEND_NAPI || NS_GSD_BACKEND_PREPARED) + +#if defined(__has_include) +#if __has_include("../shared/GeneratedSignatureDispatch.inc") +#include "../shared/GeneratedSignatureDispatch.inc" +#elif NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH +#error GeneratedSignatureDispatch.inc is required when generated signature dispatch is enabled. +#endif +#elif NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH +#error __has_include is required to validate GeneratedSignatureDispatch.inc. +#endif + +#if NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH && !NS_HAS_GENERATED_SIGNATURE_DISPATCH +#error GeneratedSignatureDispatch.inc did not enable this generated signature dispatch backend. +#endif + +#if NS_GSD_BACKEND_NAPI && !NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH +#error GeneratedSignatureDispatch.inc did not enable Node-API generated signature dispatch. +#endif + +#if !NS_HAS_GENERATED_SIGNATURE_DISPATCH +namespace nativescript { +inline constexpr ObjCDispatchEntry kGeneratedObjCDispatchEntries[] = { + {0, nullptr}}; +inline constexpr CFunctionDispatchEntry kGeneratedCFunctionDispatchEntries[] = { + {0, nullptr}}; +inline constexpr BlockDispatchEntry kGeneratedBlockDispatchEntries[] = { + {0, nullptr}}; +} // namespace nativescript +#endif + +#if !NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH +namespace nativescript { +inline constexpr ObjCNapiDispatchEntry kGeneratedObjCNapiDispatchEntries[] = { + {0, nullptr}}; +inline constexpr CFunctionNapiDispatchEntry + kGeneratedCFunctionNapiDispatchEntries[] = {{0, nullptr}}; +} // namespace nativescript +#endif + +namespace nativescript { + +inline ObjCPreparedInvoker lookupObjCPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCDispatchEntries, dispatchId); +} + +inline CFunctionPreparedInvoker lookupCFunctionPreparedInvoker( + uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedCFunctionDispatchEntries, dispatchId); +} + +inline BlockPreparedInvoker lookupBlockPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedBlockDispatchEntries, dispatchId); +} + +inline ObjCNapiInvoker lookupObjCNapiInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCNapiDispatchEntries, dispatchId); +} + +inline CFunctionNapiInvoker lookupCFunctionNapiInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedCFunctionNapiDispatchEntries, dispatchId); +} + +} // namespace nativescript + +#endif // NS_FFI_NAPI_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/napi/Struct.h b/NativeScript/ffi/objc/napi/Struct.h similarity index 96% rename from NativeScript/ffi/napi/Struct.h rename to NativeScript/ffi/objc/napi/Struct.h index edb89a3f0..21249d059 100644 --- a/NativeScript/ffi/napi/Struct.h +++ b/NativeScript/ffi/objc/napi/Struct.h @@ -40,6 +40,9 @@ class StructObject { bool owned; napi_env env = nullptr; napi_ref backingRef = nullptr; +#if defined(TARGET_ENGINE_HERMES) + napi_ref wrapperRef = nullptr; +#endif ObjCBridgeState* bridgeState = nullptr; uint64_t bridgeStateToken = 0; diff --git a/NativeScript/ffi/objc/napi/Struct.mm b/NativeScript/ffi/objc/napi/Struct.mm new file mode 100644 index 000000000..ee798478b --- /dev/null +++ b/NativeScript/ffi/objc/napi/Struct.mm @@ -0,0 +1,699 @@ +#include "Struct.h" +#include +#include +#include +#include "Interop.h" +#include "ObjCBridge.h" +#include "TypeConv.h" +#include "Util.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "node_api_util.h" + +#import + +namespace nativescript { + +namespace { +std::string buildStructTypeEncoding(StructInfo* info) { + if (info == nullptr || info->name == nullptr) { + return ""; + } + + std::string encoding = "{"; + encoding += info->name; + encoding += "="; + for (const auto& field : info->fields) { + if (field.type == nullptr) { + return ""; + } + field.type->encode(&encoding); + } + encoding += "}"; + return encoding; +} +} // namespace + +void ObjCBridgeState::registerStructGlobals(napi_env env, napi_value global) { + MDSectionOffset offset = metadata->structsOffset; + while (offset < metadata->unionsOffset) { + // Sometimes there is padding after file ends. + if (metadata->getOffset(offset) == 0) break; + MDSectionOffset originalOffset = offset; + auto name = metadata->getString(offset); + offset += sizeof(MDSectionOffset); + auto size = metadata->getArraySize(offset); + offset += sizeof(uint16_t); + + std::string nameStr = name; + structOffsets[nameStr] = originalOffset; + + bool next = true; + while (next) { + MDSectionOffset nameOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (nameOffset & mdSectionOffsetNext) != 0; + nameOffset &= ~mdSectionOffsetNext; + if (nameOffset == MD_SECTION_OFFSET_NULL) break; + auto name = metadata->resolveString(nameOffset); + offset += sizeof(uint16_t); + TypeConv::Make(env, metadata, &offset); + } + + bool hasPrimaryName = false; + napi_has_named_property(env, global, name, &hasPrimaryName); + if (!hasPrimaryName) { + napi_property_descriptor prop = { + .utf8name = name, + .method = nullptr, + .getter = JS_structGetter, + .setter = nullptr, + .value = nullptr, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = (void*)((size_t)originalOffset), + }; + napi_define_properties(env, global, 1, &prop); + } + + if (!nameStr.empty() && nameStr[0] == '_') { + std::string alias = nameStr.substr(1); + if (!alias.empty()) { + bool hasAlias = false; + napi_has_named_property(env, global, alias.c_str(), &hasAlias); + if (!hasAlias) { + napi_property_descriptor aliasProp = { + .utf8name = alias.c_str(), + .method = nullptr, + .getter = JS_structGetter, + .setter = nullptr, + .value = nullptr, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = (void*)((size_t)originalOffset), + }; + napi_define_properties(env, global, 1, &aliasProp); + } + } + } + + if (nameStr.size() < 6 || nameStr.compare(nameStr.size() - 6, 6, "Struct") != 0) { + std::string alias = nameStr + "Struct"; + bool hasAlias = false; + napi_has_named_property(env, global, alias.c_str(), &hasAlias); + if (!hasAlias) { + napi_property_descriptor aliasProp = { + .utf8name = alias.c_str(), + .method = nullptr, + .getter = JS_structGetter, + .setter = nullptr, + .value = nullptr, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = (void*)((size_t)originalOffset), + }; + napi_define_properties(env, global, 1, &aliasProp); + } + } + } +} + +void ObjCBridgeState::registerUnionGlobals(napi_env env, napi_value global) { + MDSectionOffset offset = metadata->unionsOffset; + while (true) { + // It's the last section, always ends with 4 bytes of 0. + if (metadata->getOffset(offset) == 0) break; + MDSectionOffset originalOffset = offset; + auto name = metadata->getString(offset); + offset += sizeof(MDSectionOffset); + auto size = metadata->getArraySize(offset); + offset += sizeof(uint16_t); + + std::string nameStr = name; + unionOffsets[nameStr] = originalOffset; + + bool next = true; + while (next) { + MDSectionOffset nameOffset = metadata->getOffset(offset); + next = (nameOffset & mdSectionOffsetNext) != 0; + nameOffset &= ~mdSectionOffsetNext; + auto name = metadata->resolveString(nameOffset); + offset += sizeof(MDSectionOffset); + TypeConv::Make(env, metadata, &offset); + } + + napi_property_descriptor prop = { + .utf8name = name, + .method = nullptr, + .getter = JS_unionGetter, + .setter = nullptr, + .value = nullptr, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = (void*)((size_t)originalOffset), + }; + napi_define_properties(env, global, 1, &prop); + } +} + +NAPI_FUNCTION(structGetter) { + NS_OBJC_NAPI_PREAMBLE + + void* data; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); + MDSectionOffset offset = (MDSectionOffset)((size_t)data); + + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto structInfo = bridgeState->getStructInfo(env, offset); + return StructObject::getJSClass(env, structInfo); +} + +NAPI_FUNCTION(unionGetter) { + NS_OBJC_NAPI_PREAMBLE + + void* data; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); + MDSectionOffset offset = (MDSectionOffset)((size_t)data); + + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto structInfo = bridgeState->getUnionInfo(env, offset); + return StructObject::getJSClass(env, structInfo); +} + +StructInfo* getStructInfoFromMetadata(napi_env env, MDMetadataReader* metadata, + MDSectionOffset offset) { + auto originalOffset = offset; + + auto structInfo = new StructInfo(); + auto name = metadata->getString(offset); + structInfo->name = name; + offset += sizeof(MDSectionOffset); + structInfo->size = metadata->getArraySize(offset); + offset += sizeof(uint16_t); + + bool next = true; + while (next) { + MDSectionOffset nameOffset = metadata->getOffset(offset); + next = (nameOffset & mdSectionOffsetNext) != 0; + if (next) { + nameOffset &= ~mdSectionOffsetNext; + } + if (nameOffset == MD_SECTION_OFFSET_NULL) { + break; + } + auto fieldInfo = StructFieldInfo(); + fieldInfo.name = metadata->resolveString(nameOffset); + offset += sizeof(MDSectionOffset); + fieldInfo.offset = metadata->getArraySize(offset); + offset += sizeof(uint16_t); + fieldInfo.type = TypeConv::Make(env, metadata, &offset); + structInfo->fields.push_back(fieldInfo); + } + + return structInfo; +} + +StructInfo* getStructInfoFromUnionMetadata(napi_env env, MDMetadataReader* metadata, + MDSectionOffset offset) { + auto originalOffset = offset; + + auto structInfo = new StructInfo(); + auto name = metadata->getString(offset); + structInfo->name = name; + offset += sizeof(MDSectionOffset); + structInfo->size = metadata->getArraySize(offset); + offset += sizeof(uint16_t); + + bool next = true; + while (next) { + MDSectionOffset nameOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (nameOffset & mdSectionOffsetNext) != 0; + if (next) { + nameOffset &= ~mdSectionOffsetNext; + } + if (nameOffset == 0) { + break; + } + auto fieldInfo = StructFieldInfo(); + fieldInfo.name = metadata->resolveString(nameOffset); + fieldInfo.offset = 0; + fieldInfo.type = TypeConv::Make(env, metadata, &offset); + structInfo->fields.push_back(fieldInfo); + } + + return structInfo; +} + +namespace { +constexpr napi_type_tag kStructObjectTypeTag = { + 0x93d4bc8be2d74d2dULL, + 0xa3c150244f745f84ULL, +}; + +void StructObject_finalize_now(napi_env env, void* data, void* hint) { + auto structObject = (StructObject*)data; + delete structObject; +} +} // namespace + +void StructObject_finalize(napi_env env, void* data, void* hint) { + if (PostFinalizer(env, StructObject_finalize_now, data, hint)) { + return; + } + + StructObject_finalize_now(env, data, hint); +} + +NAPI_FUNCTION(StructConstructor) { + NS_OBJC_NAPI_PREAMBLE + + napi_value jsThis; + napi_value argv[1]; + StructInfo* info; + size_t argc = 1; + + napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, (void**)&info); + + napi_valuetype thisType = napi_undefined; + if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || + (thisType != napi_object && thisType != napi_function)) { + napi_create_object(env, &jsThis); + + if (info != nullptr) { + napi_value structCtor = StructObject::getJSClass(env, info); + napi_value structPrototype = nullptr; + if (structCtor != nullptr && + napi_get_named_property(env, structCtor, "prototype", &structPrototype) == napi_ok && + structPrototype != nullptr) { + napi_value global = nullptr; + napi_value objectCtor = nullptr; + napi_value setPrototypeOf = nullptr; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &objectCtor); + napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); + napi_value setPrototypeArgs[2] = {jsThis, structPrototype}; + napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); + } + } + } + + napi_value arg; + if (argc > 0) { + arg = argv[0]; + } else { + napi_get_undefined(env, &arg); + } + + napi_valuetype argType; + napi_typeof(env, arg, &argType); + + StructObject* object; + + if (argType == napi_object) { + if (Pointer::isInstance(env, arg)) { + Pointer* pointer = Pointer::unwrap(env, arg); + if (pointer == nullptr) { + napi_throw_error(env, nullptr, "Invalid pointer-backed struct argument"); + return nullptr; + } + object = new StructObject(info, pointer->data, env, arg); + } else if (Reference::isInstance(env, arg)) { + Reference* reference = Reference::unwrap(env, arg); + if (reference == nullptr || reference->data == nullptr) { + napi_throw_error(env, nullptr, "Reference is not initialized"); + return nullptr; + } + object = new StructObject(info, reference->data, env, arg); + } else { + object = new StructObject(env, info, arg); + } + } else { + object = new StructObject(info); + } + + napi_ref* wrapperRef = nullptr; +#if defined(TARGET_ENGINE_HERMES) + wrapperRef = &object->wrapperRef; +#endif + if (napi_wrap(env, jsThis, object, StructObject_finalize, nullptr, wrapperRef) != napi_ok) { + delete object; + return nullptr; + } + if (napi_type_tag_object(env, jsThis, &kStructObjectTypeTag) != napi_ok) { + void* removed = nullptr; + napi_remove_wrap(env, jsThis, &removed); + delete static_cast(removed); + return nullptr; + } + + return jsThis; +} + +NAPI_FUNCTION(StructPropertyGetter) { + NS_OBJC_NAPI_PREAMBLE + + napi_value jsThis; + StructFieldInfo* info; + + napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, (void**)&info); + + auto object = StructObject::unwrap(env, jsThis); + if (object == nullptr) { + napi_throw_type_error(env, nullptr, "Invalid struct receiver"); + return nullptr; + } + auto value = object->get(env, info); + + if (StructObject::isInstance(env, value)) { + napi_set_named_property(env, value, "__ns_parent_struct", jsThis); + } else { + bool isArray = false; + if (napi_is_array(env, value, &isArray) == napi_ok && isArray) { + napi_set_named_property(env, value, "__ns_parent_struct", jsThis); + } + } + + return value; +} + +NAPI_FUNCTION(StructPropertySetter) { + NS_OBJC_NAPI_PREAMBLE + + napi_value jsThis, arg; + StructFieldInfo* info; + size_t argc = 1; + + napi_get_cb_info(env, cbinfo, &argc, &arg, &jsThis, (void**)&info); + + auto object = StructObject::unwrap(env, jsThis); + if (object == nullptr) { + napi_throw_type_error(env, nullptr, "Invalid struct receiver"); + return nullptr; + } + object->set(env, info, arg); + + return nullptr; +} + +NAPI_FUNCTION(StructCustomInspect) { + napi_value jsThis; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, nullptr); + + auto object = StructObject::unwrap(env, jsThis); + if (object == nullptr) { + napi_throw_type_error(env, nullptr, "Invalid struct receiver"); + return nullptr; + } + std::string str = "struct "; + str += object->info->name; + str += " {}"; + + napi_value result; + napi_create_string_utf8(env, str.c_str(), str.length(), &result); + return result; +} + +NAPI_FUNCTION(StructEquals) { + napi_value jsThis, argv[2]; + size_t argc = 2; + void* data = nullptr; + napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, &data); + + StructInfo* info = static_cast(data); + if (info == nullptr || argc < 2) { + napi_value result; + napi_get_boolean(env, false, &result); + return result; + } + + auto serialize = [&](napi_value value, std::vector& out) -> bool { + out.assign(info->size, 0); + + StructObject* structObject = StructObject::unwrap(env, value); + if (structObject != nullptr) { + size_t copySize = + std::min(static_cast(info->size), static_cast(structObject->info->size)); + memcpy(out.data(), structObject->data, copySize); + return true; + } + + napi_valuetype type = napi_undefined; + napi_typeof(env, value, &type); + if (type != napi_object) { + return false; + } + + StructObject(env, info, value, out.data()); + bool pending = false; + napi_is_exception_pending(env, &pending); + return !pending; + }; + + std::vector left; + std::vector right; + bool okLeft = serialize(argv[0], left); + bool okRight = serialize(argv[1], right); + + napi_value result; + napi_get_boolean(env, okLeft && okRight && memcmp(left.data(), right.data(), info->size) == 0, + &result); + return result; +} + +inline StructObject::StructObject(StructInfo* info, void* data, napi_env env, + napi_value backingValue) { + this->info = info; + this->env = env; + this->bridgeState = env != nullptr ? ObjCBridgeState::InstanceData(env) : nullptr; + this->bridgeStateToken = this->bridgeState != nullptr ? this->bridgeState->lifetimeToken : 0; + if (data == nullptr) { + this->data = malloc(info->size); + memset(this->data, 0, this->info->size); + this->owned = true; + } else { + this->data = data; + this->owned = false; + if (env != nullptr && backingValue != nullptr) { + napi_create_reference(env, backingValue, 1, &this->backingRef); + } + } +} + +StructObject::StructObject(napi_env env, StructInfo* info, napi_value object, void* memory) { + this->info = info; + this->env = env; + this->bridgeState = env != nullptr ? ObjCBridgeState::InstanceData(env) : nullptr; + this->bridgeStateToken = this->bridgeState != nullptr ? this->bridgeState->lifetimeToken : 0; + + if (memory == nullptr) { + this->owned = true; + this->data = malloc(info->size); + } else { + this->owned = false; + this->data = memory; + } + + memset(this->data, 0, this->info->size); + + for (auto& field : info->fields) { + bool hasProp = false; + napi_has_named_property(env, object, field.name, &hasProp); + if (!hasProp) { + continue; + } + napi_value property; + napi_get_named_property(env, object, field.name, &property); + set(env, &field, property); + } +} + +StructObject::~StructObject() { +#if defined(TARGET_ENGINE_HERMES) + if (this->wrapperRef != nullptr && this->env != nullptr && + (this->bridgeState == nullptr || + IsBridgeStateLive(this->bridgeState, this->bridgeStateToken))) { + DeleteReferenceOnOwningThread(this->env, this->bridgeState, this->bridgeStateToken, + this->wrapperRef); + } + this->wrapperRef = nullptr; +#endif + if (this->backingRef != nullptr && this->env != nullptr && + (this->bridgeState == nullptr || + IsBridgeStateLive(this->bridgeState, this->bridgeStateToken))) { + DeleteReferenceOnOwningThread(this->env, this->bridgeState, this->bridgeStateToken, + this->backingRef); + } + this->backingRef = nullptr; + this->bridgeState = nullptr; + this->bridgeStateToken = 0; + if (this->owned) free(this->data); +} + +napi_value StructObject::get(napi_env env, StructFieldInfo* field) { + auto data = (char*)this->data + field->offset; + return field->type->toJS(env, data, kStructZeroCopy); +} + +void StructObject::set(napi_env env, StructFieldInfo* field, napi_value value) { + auto data = (char*)this->data + field->offset; + bool shouldFree = false; + field->type->toNative(env, value, data, &shouldFree, &shouldFree); + ConsumeNapiArgumentConversionFailure(env); +} + +StructObject* StructObject::unwrap(napi_env env, napi_value object) { + bool matches = false; + if (napi_check_object_type_tag(env, object, &kStructObjectTypeTag, &matches) != napi_ok || + !matches) { + return nullptr; + } + + StructObject* result = nullptr; + auto status = napi_unwrap(env, object, (void**)&result); + if (status != napi_ok) return nullptr; +#if defined(TARGET_ENGINE_HERMES) + if (result == nullptr || result->wrapperRef == nullptr) { + return nullptr; + } + + napi_value wrapper = nullptr; + bool same = false; + if (napi_get_reference_value(env, result->wrapperRef, &wrapper) != napi_ok || + wrapper == nullptr || napi_strict_equals(env, object, wrapper, &same) != napi_ok || + !same) { + return nullptr; + } +#endif + return result; +} + +napi_value StructObject::defineJSClass(napi_env env, StructInfo* info) { + auto properties = (napi_property_descriptor*)malloc((info->fields.size() + 2) * + sizeof(napi_property_descriptor)); + + for (int i = 0; i < info->fields.size(); i++) { + auto field = info->fields[i]; + auto prop = &properties[i]; + prop->utf8name = field.name; + prop->name = nullptr; + prop->method = nullptr; + prop->value = nullptr; + prop->attributes = (napi_property_attributes)(napi_enumerable | napi_writable); + prop->data = &info->fields[i]; + prop->getter = JS_StructPropertyGetter; + prop->setter = JS_StructPropertySetter; + } + + auto prop = &properties[info->fields.size()]; + prop->utf8name = nullptr; + prop->name = jsSymbolFor(env, "nodejs.util.inspect.custom"); + prop->method = JS_StructCustomInspect; + prop->getter = nullptr; + prop->setter = nullptr; + prop->value = nullptr; + prop->attributes = napi_default; + prop->data = nullptr; + + napi_value size; + napi_create_int32(env, info->size, &size); + + auto sizeofProp = &properties[info->fields.size() + 1]; + sizeofProp->utf8name = nullptr; + sizeofProp->name = jsSymbolFor(env, "sizeof"); + sizeofProp->method = nullptr; + sizeofProp->getter = nullptr; + sizeofProp->setter = nullptr; + sizeofProp->value = size; + sizeofProp->attributes = napi_enumerable; + sizeofProp->data = nullptr; + + napi_value result; + napi_define_class(env, info->name, NAPI_AUTO_LENGTH, JS_StructConstructor, (void*)info, + info->fields.size() + 2, properties, &result); + + const napi_property_descriptor classProps[] = { + { + .utf8name = "equals", + .method = JS_StructEquals, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_default, + .data = info, + }, + { + .utf8name = nullptr, + .name = jsSymbolFor(env, "sizeof"), + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = size, + .attributes = napi_enumerable, + .data = nullptr, + }, + }; + napi_define_properties(env, result, 2, classProps); + + std::string typeEncoding = buildStructTypeEncoding(info); + if (!typeEncoding.empty()) { + napi_value typeSymbol = jsSymbolFor(env, "type"); + napi_value typeValue = nullptr; + napi_create_string_utf8(env, typeEncoding.c_str(), NAPI_AUTO_LENGTH, &typeValue); + napi_set_property(env, result, typeSymbol, typeValue); + + napi_value prototype = nullptr; + if (napi_get_named_property(env, result, "prototype", &prototype) == napi_ok && + prototype != nullptr) { + napi_set_property(env, prototype, typeSymbol, typeValue); + } + } + + free(properties); + + return result; +} + +bool StructObject::isInstance(napi_env env, napi_value object) { + bool matches = false; + return object != nullptr && + napi_check_object_type_tag(env, object, &kStructObjectTypeTag, + &matches) == napi_ok && + matches && StructObject::unwrap(env, object) != nullptr; +} + +napi_value StructObject::getJSClass(napi_env env, StructInfo* info) { + if (info->jsClass != nullptr) { + return get_ref_value(env, info->jsClass); + } + + auto result = defineJSClass(env, info); + info->jsClass = make_ref(env, result); + + return result; +} + +napi_value StructObject::fromNative(napi_env env, StructInfo* info, void* data, bool owned) { + napi_value result; + napi_value cls = getJSClass(env, info); + napi_new_instance(env, cls, 0, nullptr, &result); + auto object = StructObject::unwrap(env, result); + if (object == nullptr) { + return result; + } + + if (owned) { + if (object->owned) { + memcpy(object->data, data, info->size); + } else { + object->data = malloc(info->size); + memcpy(object->data, data, info->size); + object->owned = true; + } + } else { + if (object->owned && object->data != nullptr) { + free(object->data); + } + object->data = data; + object->owned = false; + } + return result; +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/TypeConv.h b/NativeScript/ffi/objc/napi/TypeConv.h similarity index 87% rename from NativeScript/ffi/napi/TypeConv.h rename to NativeScript/ffi/objc/napi/TypeConv.h index e3d024f27..a6133621b 100644 --- a/NativeScript/ffi/napi/TypeConv.h +++ b/NativeScript/ffi/objc/napi/TypeConv.h @@ -29,9 +29,11 @@ class TypeConv { MDSectionOffset* offset, uint8_t opaquePointers = 0); - ffi_type* type; + ffi_type* type = nullptr; MDTypeKind kind = mdTypeChar; + virtual ~TypeConv() = default; + virtual napi_value toJS(napi_env env, void* value, uint32_t flags = 0) { return nullptr; } @@ -52,6 +54,10 @@ class TypeConv { bool TryFastConvertNapiArgument(napi_env env, MDTypeKind kind, napi_value value, void* result); +// Returns and clears conversion failures that cannot be observed through +// napi_is_exception_pending on every backend. +bool ConsumeNapiArgumentConversionFailure(napi_env env); + // Fast direct conversion for uint16_t / unichar arguments used by generated // dispatch wrappers. Supports both numeric values and single-character JS // strings. @@ -59,7 +65,7 @@ bool TryFastConvertNapiUInt16Argument(napi_env env, napi_value value, uint16_t* result); // Cleanup function to clear thread-local struct type caches -void clearStructTypeCaches(); +void clearStructTypeCaches(napi_env env); } // namespace nativescript diff --git a/NativeScript/ffi/objc/napi/TypeConv.mm b/NativeScript/ffi/objc/napi/TypeConv.mm new file mode 100644 index 000000000..eff1aaf49 --- /dev/null +++ b/NativeScript/ffi/objc/napi/TypeConv.mm @@ -0,0 +1,4255 @@ +#include "TypeConv.h" +#include "Block.h" +#include "Class.h" +#include "Closure.h" +#include "Interop.h" +#include "JSObject.h" +#include "Metadata.h" +#include "MetadataReader.h" +#include "ObjCBridge.h" +#include "ffi.h" +#include "Struct.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "node_api_util.h" + +#import +#import +#include +#if defined(__has_include) +#if __has_include() +#include +#endif +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +@interface JSWrapperObjectAssociation : NSObject ++ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object; +@end + +namespace { + +static napi_value findRegisteredClassConstructor(napi_env env, Class cls) { + if (env == nullptr || cls == nil) { + return nullptr; + } + + const char* runtimeName = class_getName(cls); + if (runtimeName == nullptr || runtimeName[0] == '\0') { + return nullptr; + } + + napi_value global = nullptr; + napi_value classRegistry = nullptr; + bool hasClassRegistry = false; + if (napi_get_global(env, &global) != napi_ok || global == nullptr || + napi_has_named_property(env, global, "__nsConstructorsByObjCClassName", + &hasClassRegistry) != napi_ok || + !hasClassRegistry || + napi_get_named_property(env, global, "__nsConstructorsByObjCClassName", + &classRegistry) != napi_ok || + classRegistry == nullptr) { + return nullptr; + } + + bool hasConstructor = false; + napi_value constructor = nullptr; + if (napi_has_named_property(env, classRegistry, runtimeName, &hasConstructor) == napi_ok && + hasConstructor && + napi_get_named_property(env, classRegistry, runtimeName, &constructor) == napi_ok && + constructor != nullptr) { + return constructor; + } + + return nullptr; +} + +static size_t getBufferElementSize(napi_typedarray_type type) { + switch (type) { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + return 1; + case napi_int16_array: + case napi_uint16_array: + return 2; + case napi_int32_array: + case napi_uint32_array: + case napi_float32_array: + return 4; + case napi_float64_array: + case napi_bigint64_array: + case napi_biguint64_array: + return 8; + default: + return 1; + } +} + +static bool getJSBufferData(napi_env env, napi_value value, void** data, size_t* byteLength) { + if (data == nullptr || byteLength == nullptr) { + return false; + } + + bool isArrayBuffer = false; + if (napi_is_arraybuffer(env, value, &isArrayBuffer) == napi_ok && isArrayBuffer) { + return napi_get_arraybuffer_info(env, value, data, byteLength) == napi_ok; + } + + bool isTypedArray = false; + if (napi_is_typedarray(env, value, &isTypedArray) == napi_ok && isTypedArray) { + napi_typedarray_type type; + napi_value arrayBuffer; + size_t byteOffset = 0; + size_t elementLength = 0; + if (napi_get_typedarray_info(env, value, &type, &elementLength, data, &arrayBuffer, + &byteOffset) != napi_ok) { + return false; + } + + *byteLength = elementLength * getBufferElementSize(type); + return true; + } + + bool isDataView = false; + if (napi_is_dataview(env, value, &isDataView) == napi_ok && isDataView) { + napi_value arrayBuffer; + size_t byteOffset = 0; + return napi_get_dataview_info(env, value, byteLength, data, &arrayBuffer, &byteOffset) == + napi_ok; + } + + return false; +} + +struct ActiveObjectConversion { + napi_env env; + napi_value value; +}; + +thread_local std::vector activeObjectConversions; +thread_local napi_env failedObjectConversionEnv = nullptr; + +class ScopedObjectConversion { + public: + ScopedObjectConversion(napi_env env, napi_value value) { + for (const auto& active : activeObjectConversions) { + if (active.env != env) { + continue; + } + + bool isSameObject = false; + status_ = napi_strict_equals(env, active.value, value, &isSameObject); + if (status_ != napi_ok) { + return; + } + + if (isSameObject) { + failedObjectConversionEnv = env; + napi_throw_error( + env, nullptr, + "Circular JavaScript object graphs cannot be converted to Objective-C collections."); + return; + } + } + + activeObjectConversions.push_back({env, value}); + entered_ = true; + } + + ~ScopedObjectConversion() { + if (entered_) { + activeObjectConversions.pop_back(); + } + } + + bool entered() const { return entered_; } + napi_status status() const { return status_; } + + private: + bool entered_ = false; + napi_status status_ = napi_ok; +}; + +static bool hasPendingException(napi_env env) { + if (failedObjectConversionEnv == env) { + return true; + } + bool pending = false; + return napi_is_exception_pending(env, &pending) == napi_ok && pending; +} + +static uint16_t encodeFloat16(double value) { + if (std::isnan(value)) { + return 0x7e00; + } + + if (std::isinf(value)) { + return std::signbit(value) ? 0xfc00 : 0x7c00; + } + + union { + float f; + uint32_t bits; + } input = {static_cast(value)}; + + const uint32_t sign = (input.bits >> 16) & 0x8000; + uint32_t exponent = (input.bits >> 23) & 0xff; + uint32_t mantissa = input.bits & 0x007fffff; + + if (exponent == 0) { + return static_cast(sign); + } + + int32_t halfExponent = static_cast(exponent) - 127 + 15; + if (halfExponent >= 0x1f) { + return static_cast(sign | 0x7c00); + } + + if (halfExponent <= 0) { + if (halfExponent < -10) { + return static_cast(sign); + } + + mantissa |= 0x00800000; + const uint32_t shift = static_cast(14 - halfExponent); + uint32_t halfMantissa = mantissa >> shift; + if (((mantissa >> (shift - 1)) & 1u) != 0) { + halfMantissa += 1; + } + return static_cast(sign | halfMantissa); + } + + uint32_t halfMantissa = mantissa >> 13; + if ((mantissa & 0x00001000) != 0) { + halfMantissa += 1; + if ((halfMantissa & 0x00000400) != 0) { + halfMantissa = 0; + halfExponent += 1; + if (halfExponent >= 0x1f) { + return static_cast(sign | 0x7c00); + } + } + } + + return static_cast(sign | (static_cast(halfExponent) << 10) | + (halfMantissa & 0x03ff)); +} + +static double decodeFloat16(uint16_t bits) { + const uint32_t sign = (bits & 0x8000u) << 16; + const uint32_t exponent = (bits >> 10) & 0x1fu; + const uint32_t mantissa = bits & 0x03ffu; + + union { + uint32_t bits; + float f; + } output = {0}; + + if (exponent == 0) { + if (mantissa == 0) { + output.bits = sign; + return static_cast(output.f); + } + + uint32_t normalizedMantissa = mantissa; + int32_t normalizedExponent = -14; + while ((normalizedMantissa & 0x0400u) == 0) { + normalizedMantissa <<= 1; + normalizedExponent -= 1; + } + normalizedMantissa &= 0x03ffu; + output.bits = + sign | (static_cast(normalizedExponent + 127) << 23) | (normalizedMantissa << 13); + return static_cast(output.f); + } + + if (exponent == 0x1fu) { + output.bits = sign | 0x7f800000u | (mantissa << 13); + return static_cast(output.f); + } + + output.bits = sign | ((exponent - 15 + 127) << 23) | (mantissa << 13); + return static_cast(output.f); +} + +static id resolveCachedHandleObject(napi_env env, void* handle) { + if (env == nullptr || handle == nullptr) { + return nil; + } + + auto bridgeState = nativescript::ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr) { + return nil; + } + + napi_value cachedValue = bridgeState->getCachedHandleObject(env, handle); + if (cachedValue == nullptr) { + return nil; + } + + void* wrapped = nullptr; + if (napi_unwrap(env, cachedValue, &wrapped) == napi_ok && wrapped != nullptr) { + bridgeState->cacheRoundTripObject(env, static_cast(wrapped), cachedValue); + return static_cast(wrapped); + } + + bool hasNativePointer = false; + if (napi_has_named_property(env, cachedValue, "__ns_native_ptr", &hasNativePointer) == napi_ok && + hasNativePointer) { + napi_value nativePointerValue = nullptr; + if (napi_get_named_property(env, cachedValue, "__ns_native_ptr", &nativePointerValue) == + napi_ok) { + if (nativescript::Pointer::isInstance(env, nativePointerValue)) { + nativescript::Pointer* pointer = nativescript::Pointer::unwrap(env, nativePointerValue); + if (pointer != nullptr && pointer->data != nullptr) { + bridgeState->cacheRoundTripObject(env, static_cast(pointer->data), cachedValue); + return static_cast(pointer->data); + } + } else { + void* nativePointer = nullptr; + if (napi_get_value_external(env, nativePointerValue, &nativePointer) == napi_ok && + nativePointer != nullptr) { + bridgeState->cacheRoundTripObject(env, static_cast(nativePointer), cachedValue); + return static_cast(nativePointer); + } + } + } + } + + return nil; +} + +} // namespace + +namespace nativescript { + +namespace { +constexpr const char* kProtocolSuffix = "Protocol"; + +NSData* createNSDataWrapper(napi_env env, napi_value value, ObjCBridgeState* bridgeState) { + void* data = nullptr; + size_t byteLength = 0; + if (!getJSBufferData(env, value, &data, &byteLength)) { + return nil; + } + + NSData* wrappedData = [NSData dataWithBytes:data length:byteLength]; + if (wrappedData == nil) { + return nil; + } + + if (bridgeState != nullptr && bridgeState->hasRoundTripCacheFrame()) { + bridgeState->cacheRoundTripObject(env, wrappedData, value); + } + + return wrappedData; +} + +inline size_t alignUp(size_t value, size_t alignment) { + if (alignment == 0) { + return value; + } + return ((value + alignment - 1) / alignment) * alignment; +} + +inline uintptr_t normalizeRuntimePointer(uintptr_t ptr) { +#if INTPTR_MAX == INT64_MAX + return ptr & 0x0000FFFFFFFFFFFFULL; +#else + return ptr; +#endif +} + +inline bool isKindOfClassFast(id obj, Class expectedClass) { + if (obj == nil || expectedClass == Nil) { + return false; + } + + return [obj isKindOfClass:expectedClass]; +} + +bool stripProtocolSuffix(const char* name, std::string* out) { + if (name == nullptr || out == nullptr) { + return false; + } + + const size_t nameLen = std::strlen(name); + const size_t suffixLen = std::strlen(kProtocolSuffix); + if (nameLen <= suffixLen) { + return false; + } + + if (std::strcmp(name + (nameLen - suffixLen), kProtocolSuffix) != 0) { + return false; + } + + *out = std::string(name, nameLen - suffixLen); + return !out->empty(); +} + +bool protocolNamesMatch(const char* metadataName, const char* runtimeName) { + if (metadataName == nullptr || runtimeName == nullptr) { + return false; + } + + if (std::strcmp(metadataName, runtimeName) == 0) { + return true; + } + + std::string metadataBase(metadataName); + std::string runtimeBase(runtimeName); + stripProtocolSuffix(metadataName, &metadataBase); + stripProtocolSuffix(runtimeName, &runtimeBase); + + return metadataBase == runtimeBase; +} + +MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const char* protocolName) { + if (metadata == nullptr || protocolName == nullptr) { + return MD_SECTION_OFFSET_NULL; + } + + MDSectionOffset offset = metadata->protocolsOffset; + while (offset < metadata->classesOffset) { + MDSectionOffset originalOffset = offset; + + auto nameOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool next = (nameOffset & mdSectionOffsetNext) != 0; + nameOffset &= ~mdSectionOffsetNext; + + auto name = metadata->resolveString(nameOffset); + if (protocolNamesMatch(name, protocolName)) { + return originalOffset; + } + + while (next) { + auto protocolImpl = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (protocolImpl & mdSectionOffsetNext) != 0; + } + + next = true; + while (next) { + auto flags = metadata->getMemberFlag(offset); + next = (flags & mdMemberNext) != 0; + offset += sizeof(flags); + + if (flags == mdMemberFlagNull) { + break; + } + + if ((flags & mdMemberProperty) != 0) { + bool readonly = (flags & mdMemberReadonly) != 0; + offset += sizeof(MDSectionOffset); // name + offset += sizeof(MDSectionOffset); // getter selector + offset += sizeof(MDSectionOffset); // getter signature + if (!readonly) { + offset += sizeof(MDSectionOffset); // setter selector + offset += sizeof(MDSectionOffset); // setter signature + } + } else { + offset += sizeof(MDSectionOffset); // selector + offset += sizeof(MDSectionOffset); // signature + } + } + } + + return MD_SECTION_OFFSET_NULL; +} +} // namespace + +// Forward declaration +class StructTypeConv; + +struct StructTypeCaches { + std::unordered_set processingStructs; + std::unordered_set processingEncodingStructs; + std::unordered_map forwardDeclaredStructs; + std::unordered_map forwardDeclaredEncodingStructs; + std::unordered_map> structTypes; + std::unordered_map> encodingStructTypes; +}; + +thread_local std::unordered_map structTypeCachesByEnv; + +inline StructTypeCaches& structTypeCaches(napi_env env) { + return structTypeCachesByEnv[env]; +} + +ffi_type* typeFromStruct(napi_env env, const char** encoding, bool* ownsType, + std::vector>* retainedElementTypes) { + auto& caches = structTypeCaches(env); + auto& processingEncodingStructs = caches.processingEncodingStructs; + auto& forwardDeclaredEncodingStructs = caches.forwardDeclaredEncodingStructs; + *ownsType = false; + // Extract struct name for cycle detection + std::string structname; + const char* nameStart = *encoding + 1; // skip '{' + const char* c = nameStart; + while (*c != '\0' && *c != '=') { + structname += *c; + c++; + } + if (*c != '=') { + // Malformed struct encoding. Advance to the end of this token and + // fallback to pointer conversion to avoid reading past the buffer. + while (**encoding != '\0' && **encoding != '}') { + (*encoding)++; + } + if (**encoding == '}') { + (*encoding)++; + } + return &ffi_type_pointer; + } + + // Reuse a single placeholder when a recursive layout references the same + // struct more than once. + auto existingForwardIt = forwardDeclaredEncodingStructs.find(structname); + if (existingForwardIt != forwardDeclaredEncodingStructs.end()) { + (*encoding)++; // skip '{' + while (**encoding != '\0' && **encoding != '}') { + (*encoding)++; + } + if (**encoding == '}') { + (*encoding)++; + } + return existingForwardIt->second; + } + + // Check if we're already processing this struct (cycle detection) + if (processingEncodingStructs.find(structname) != processingEncodingStructs.end()) { + // Create a forward declaration placeholder + ffi_type* forwardType = new ffi_type; + forwardType->type = FFI_TYPE_STRUCT; + forwardType->size = 0; + forwardType->alignment = 0; + forwardType->elements = nullptr; + + // Cache this forward declaration for later resolution + forwardDeclaredEncodingStructs[structname] = forwardType; + + // Skip the struct encoding + (*encoding)++; // skip '{' + while (**encoding != '\0' && **encoding != '}') { + (*encoding)++; + } + if (**encoding == '}') { + (*encoding)++; // skip '}' + } + + return forwardType; + } + + // Mark this struct as being processed + processingEncodingStructs.insert(structname); + + ffi_type* type = new ffi_type; + type->type = FFI_TYPE_STRUCT; + type->size = 0; + type->alignment = 0; + type->elements = nullptr; + + std::vector elements; + + (*encoding)++; // skip '{' + + while (**encoding != '\0' && **encoding != '=') { + (*encoding)++; + } // skip name + if (**encoding == '\0') { + processingEncodingStructs.erase(structname); + delete type; + return &ffi_type_pointer; + } + + (*encoding)++; // skip '=' + + while (**encoding != '\0' && **encoding != '}') { + auto elementType = TypeConv::Make(env, encoding); + elements.push_back(elementType->type); + retainedElementTypes->push_back(std::move(elementType)); + } + + if (**encoding == '}') { + (*encoding)++; // skip '}' + } + + type->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (elements.size() + 1)); + for (int i = 0; i < elements.size(); i++) { + type->elements[i] = elements[i]; + } + // null-terminate the array + type->elements[elements.size()] = nullptr; + + // If this was a forward declaration, update it with the real layout + auto resolvedForwardIt = forwardDeclaredEncodingStructs.find(structname); + if (resolvedForwardIt != forwardDeclaredEncodingStructs.end()) { + ffi_type* forwardType = resolvedForwardIt->second; + forwardType->type = type->type; + forwardType->size = type->size; + forwardType->alignment = type->alignment; + forwardType->elements = type->elements; + + // Clean up the temporary type and use the forward declaration + delete type; + type = forwardType; + forwardDeclaredEncodingStructs.erase(resolvedForwardIt); + } + + // Remove from processing set + processingEncodingStructs.erase(structname); + *ownsType = true; + + return type; +} + +ffi_type* typeFromStruct(napi_env env, MDMetadataReader* reader, MDSectionOffset structOffset, + bool isUnion, bool* ownsType, + std::vector>* retainedElementTypes) { + auto& caches = structTypeCaches(env); + auto& processingStructs = caches.processingStructs; + auto& forwardDeclaredStructs = caches.forwardDeclaredStructs; + *ownsType = false; + auto existingForwardIt = forwardDeclaredStructs.find(structOffset); + if (existingForwardIt != forwardDeclaredStructs.end()) { + return existingForwardIt->second; + } + + // Check if we're already processing this struct (cycle detection) + if (processingStructs.find(structOffset) != processingStructs.end()) { + // Create a forward declaration placeholder + ffi_type* forwardType = new ffi_type; + forwardType->type = FFI_TYPE_STRUCT; + forwardType->size = 0; + forwardType->alignment = 0; + forwardType->elements = nullptr; + + // Cache this forward declaration for later resolution + forwardDeclaredStructs[structOffset] = forwardType; + return forwardType; + } + + // Mark this struct as being processed + processingStructs.insert(structOffset); + + ffi_type* type = new ffi_type; + type->type = FFI_TYPE_STRUCT; + type->size = 0; + type->alignment = 0; + type->elements = nullptr; + + MDSectionOffset nameOffset = reader->getOffset(structOffset); + auto name = reader->resolveString(nameOffset); + bool next = true; + MDSectionOffset currentOffset = structOffset + sizeof(MDSectionOffset); // skip name + currentOffset += sizeof(uint16_t); // skip size + + std::vector elements; + + while (next) { + nameOffset = reader->getOffset(currentOffset); + next = nameOffset & mdSectionOffsetNext; + nameOffset &= ~mdSectionOffsetNext; + if (nameOffset == MD_SECTION_OFFSET_NULL) { + break; + } + currentOffset += sizeof(MDSectionOffset); // skip name + if (!isUnion) currentOffset += sizeof(uint16_t); // skip offset + auto elementType = TypeConv::Make(env, reader, ¤tOffset, 1); + elements.push_back(elementType->type); + retainedElementTypes->push_back(std::move(elementType)); + } + + type->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (elements.size() + 1)); + for (int i = 0; i < elements.size(); i++) { + type->elements[i] = elements[i]; + } + // null-terminate the array + type->elements[elements.size()] = nullptr; + + // If this was a forward declaration, update it with the real layout + auto resolvedForwardIt = forwardDeclaredStructs.find(structOffset); + if (resolvedForwardIt != forwardDeclaredStructs.end()) { + ffi_type* forwardType = resolvedForwardIt->second; + forwardType->type = type->type; + forwardType->size = type->size; + forwardType->alignment = type->alignment; + forwardType->elements = type->elements; + + // Clean up the temporary type and use the forward declaration + delete type; + type = forwardType; + forwardDeclaredStructs.erase(resolvedForwardIt); + } + + // Remove from processing set + processingStructs.erase(structOffset); + *ownsType = true; + + return type; +} + +static inline size_t getTypedArrayUnitLength(napi_typedarray_type type) { + switch (type) { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + return 1; + case napi_int16_array: + case napi_uint16_array: + return 2; + case napi_int32_array: + case napi_uint32_array: + case napi_float32_array: + return 4; + case napi_float64_array: + case napi_bigint64_array: + case napi_biguint64_array: + return 8; + default: + return 0; + } +} + +class VoidTypeConv : public TypeConv { + public: + VoidTypeConv() { + type = &ffi_type_void; + kind = mdTypeVoid; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_get_null(env, &result); + return result; + } + + void encode(std::string* encoding) override { *encoding += "v"; } +}; + +static const std::shared_ptr voidTypeConv = std::make_shared(); + +class SCharTypeConv : public TypeConv { + public: + SCharTypeConv() { + type = &ffi_type_schar; + kind = mdTypeChar; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + int8_t raw = *(int8_t*)value; + if (raw == 0 || raw == 1) { + napi_value result; + napi_get_boolean(env, raw == 1, &result); + return result; + } + + napi_value result; + napi_create_int32(env, raw, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + int32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_int32(env, value, &val); + *(int8_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "c"; } +}; + +static const std::shared_ptr scharTypeConv = std::make_shared(); + +class UCharTypeConv : public TypeConv { + public: + UCharTypeConv() { + type = &ffi_type_uchar; + kind = mdTypeUChar; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + uint8_t raw = *(uint8_t*)value; + if (raw == 0 || raw == 1) { + napi_value result; + napi_get_boolean(env, raw == 1, &result); + return result; + } + + napi_value result; + napi_create_uint32(env, raw, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + uint32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint8_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "C"; } +}; + +static const std::shared_ptr ucharTypeConv = std::make_shared(); + +class UInt8TypeConv : public TypeConv { + public: + UInt8TypeConv() { + type = &ffi_type_uint8; + kind = mdTypeUInt8; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + uint8_t raw = *(uint8_t*)value; + if (raw == 0 || raw == 1) { + napi_value result; + napi_get_boolean(env, raw == 1, &result); + return result; + } + + napi_value result; + napi_create_uint32(env, raw, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + uint32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint8_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "C"; } +}; + +static const std::shared_ptr uint8TypeConv = std::make_shared(); + +class SInt16TypeConv : public TypeConv { + public: + SInt16TypeConv() { + type = &ffi_type_sshort; + kind = mdTypeSShort; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_int32(env, *(int16_t*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + int32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_int32(env, value, &val); + *(int16_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "s"; } +}; + +static const std::shared_ptr sint16TypeConv = std::make_shared(); + +class UInt16TypeConv : public TypeConv { + public: + UInt16TypeConv() { + type = &ffi_type_ushort; + kind = mdTypeUShort; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_uint32(env, *(uint16_t*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + + if (valueType == napi_string) { + size_t strLen = 0; + napi_get_value_string_utf16(env, value, nullptr, 0, &strLen); + if (strLen != 1) { + napi_throw_type_error(env, nullptr, "Expected a single-character string."); + *(uint16_t*)result = 0; + return; + } + + char16_t chars[2] = {0, 0}; + napi_get_value_string_utf16(env, value, chars, 2, &strLen); + *(uint16_t*)result = static_cast(chars[0]); + return; + } + + uint32_t val = 0; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint16_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "S"; } +}; + +static const std::shared_ptr uint16TypeConv = std::make_shared(); + +// unichar/UniChar (mdTypeUnichar): u16 width, but projected to JS as a +// single-character string for any code unit — not just printable ASCII. +class UnicharTypeConv : public UInt16TypeConv { + public: + UnicharTypeConv() { kind = mdTypeUnichar; } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + char16_t unit = *(char16_t*)value; + napi_value result; + napi_create_string_utf16(env, &unit, 1, &result); + return result; + } +}; + +static const std::shared_ptr unicharTypeConv = std::make_shared(); + +class SInt32TypeConv : public TypeConv { + public: + SInt32TypeConv() { + type = &ffi_type_sint; + kind = mdTypeSInt; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_int32(env, *(int32_t*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + int32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_int32(env, value, &val); + *(int32_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "i"; } +}; + +static const std::shared_ptr sint32TypeConv = std::make_shared(); + +class UInt32TypeConv : public TypeConv { + public: + UInt32TypeConv() { + type = &ffi_type_uint; + kind = mdTypeUInt; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_uint32(env, *(uint32_t*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + uint32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint32_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "I"; } +}; + +static const std::shared_ptr uint32TypeConv = std::make_shared(); + +class SInt64TypeConv : public TypeConv { + public: + SInt64TypeConv() { + type = &ffi_type_sint64; + kind = mdTypeSInt64; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + int64_t val = *(int64_t*)value; + constexpr int64_t kMaxSafeInteger = 9007199254740991LL; + if (val > kMaxSafeInteger || val < -kMaxSafeInteger) { + napi_create_bigint_int64(env, val, &result); + } else { + napi_create_int64(env, val, &result); + } + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valuetype; + napi_typeof(env, value, &valuetype); + + switch (valuetype) { + case napi_number: + napi_get_value_int64(env, value, (int64_t*)result); + break; + case napi_bigint: { + bool lossless; + napi_get_value_bigint_int64(env, value, (int64_t*)result, &lossless); + break; + } + case napi_undefined: + case napi_null: + *(int64_t*)result = 0; + break; + case napi_string: + *(int64_t*)result = 0; + break; + default: + napi_throw_type_error(env, nullptr, "Expected a number or bigint"); + break; + } + } + + void encode(std::string* encoding) override { *encoding += "q"; } +}; + +static const std::shared_ptr sint64TypeConv = std::make_shared(); + +class UInt64TypeConv : public TypeConv { + public: + UInt64TypeConv() { + type = &ffi_type_uint64; + kind = mdTypeUInt64; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + uint64_t val = *(uint64_t*)value; + constexpr uint64_t kMaxSafeInteger = 9007199254740991ULL; + if (val > kMaxSafeInteger) { + napi_create_bigint_uint64(env, val, &result); + } else { + napi_create_int64(env, static_cast(val), &result); + } + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valuetype; + napi_typeof(env, value, &valuetype); + + switch (valuetype) { + case napi_number: + napi_get_value_int64(env, value, (int64_t*)result); + break; + case napi_bigint: { + bool lossless; + napi_get_value_bigint_uint64(env, value, (uint64_t*)result, &lossless); + break; + } + case napi_undefined: + case napi_null: + *(int64_t*)result = 0; + break; + default: + napi_throw_type_error(env, nullptr, "Expected a number or bigint"); + break; + } + } + + void encode(std::string* encoding) override { *encoding += "Q"; } +}; + +static const std::shared_ptr uint64TypeConv = std::make_shared(); + +class UInt128TypeConv : public TypeConv { + private: + ffi_type _type = {.size = 0, + .alignment = 0, + .type = FFI_TYPE_STRUCT, + .elements = (ffi_type*[]){ + &ffi_type_uint64, + &ffi_type_uint64, + nullptr, + }}; + + public: + UInt128TypeConv() { + type = &_type; + kind = mdTypeUInt128; + } + + // TODO + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + uint64_t val = *(uint64_t*)value; + napi_create_int64(env, (int64_t)val, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valuetype; + napi_typeof(env, value, &valuetype); + + switch (valuetype) { + case napi_number: + napi_get_value_int64(env, value, (int64_t*)result); + break; + case napi_bigint: { + bool lossless; + napi_get_value_bigint_uint64(env, value, (uint64_t*)result, &lossless); + break; + } + default: + napi_throw_type_error(env, nullptr, "Expected a number or bigint"); + break; + } + } +}; + +static const std::shared_ptr uint128TypeConv = std::make_shared(); + +class Float32TypeConv : public TypeConv { + public: + Float32TypeConv() { + type = &ffi_type_float; + kind = mdTypeFloat; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_double(env, *(float*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + double val; + napi_coerce_to_number(env, value, &value); + napi_get_value_double(env, value, &val); + *(float*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "f"; } +}; + +static const std::shared_ptr float32TypeConv = std::make_shared(); + +class Float16TypeConv : public TypeConv { + public: + Float16TypeConv() { + type = &ffi_type_uint16; + kind = mdTypeF16; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_double(env, decodeFloat16(*(uint16_t*)value), &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + double val = 0; + napi_coerce_to_number(env, value, &value); + napi_get_value_double(env, value, &val); + *(uint16_t*)result = encodeFloat16(val); + } + + void encode(std::string* encoding) override { *encoding += "H"; } +}; + +static const std::shared_ptr float16TypeConv = std::make_shared(); + +class Float64TypeConv : public TypeConv { + public: + Float64TypeConv() { + type = &ffi_type_double; + kind = mdTypeDouble; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_double(env, *(double*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + double val; + napi_coerce_to_number(env, value, &value); + napi_get_value_double(env, value, &val); + if (std::isnan(val) || std::isinf(val)) { + val = 0.0; + } + *(double*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "d"; } +}; + +static const std::shared_ptr float64TypeConv = std::make_shared(); + +class BoolTypeConv : public TypeConv { + public: + BoolTypeConv() { + type = &ffi_type_uint8; + kind = mdTypeBool; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + uint8_t raw = *(uint8_t*)value; + if (raw == 0 || raw == 1) { + napi_value result; + napi_get_boolean(env, raw == 1, &result); + return result; + } + + napi_value result; + napi_create_uint32(env, raw, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + + if (valueType == napi_number) { + uint32_t val = 0; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint8_t*)result = static_cast(val); + return; + } + + if (valueType == napi_bigint) { + uint64_t val = 0; + bool lossless = false; + napi_get_value_bigint_uint64(env, value, &val, &lossless); + *(uint8_t*)result = static_cast(val); + return; + } + + bool val = false; + napi_coerce_to_bool(env, value, &value); + napi_get_value_bool(env, value, &val); + *(uint8_t*)result = static_cast(val ? 1 : 0); + } + + void encode(std::string* encoding) override { *encoding += "B"; } +}; + +static const std::shared_ptr boolTypeConv = std::make_shared(); + +class PointerTypeConv : public TypeConv { + public: + std::shared_ptr pointeeType = nullptr; + + PointerTypeConv() { + type = &ffi_type_pointer; + kind = mdTypePointer; + } + + PointerTypeConv(std::shared_ptr pointeeType) : pointeeType(pointeeType) { + type = &ffi_type_pointer; + kind = mdTypePointer; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + void* raw = *((void**)value); + if (raw == nullptr) { + napi_value nullValue; + napi_get_null(env, &nullValue); + return nullValue; + } + + auto normalizePtr = [](void* ptr) -> uintptr_t { +#if INTPTR_MAX == INT64_MAX + // Objective-C pointers may carry auth/tag bits on some runtimes. + // Compare using canonical lower bits for stable lookups. + return reinterpret_cast(ptr) & 0x0000FFFFFFFFFFFFULL; +#else + return reinterpret_cast(ptr); +#endif + }; + + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + auto classIt = bridgeState->mdClassesByPointer.find((Class)raw); + if (classIt != bridgeState->mdClassesByPointer.end()) { + auto cls = bridgeState->getClass(env, classIt->second); + if (cls != nullptr) { + return get_ref_value(env, cls->constructor); + } + } else { + const uintptr_t rawNormalized = normalizePtr(raw); + for (const auto& entry : bridgeState->mdClassesByPointer) { + if (normalizePtr((void*)entry.first) != rawNormalized) { + continue; + } + + auto cls = bridgeState->getClass(env, entry.second); + if (cls != nullptr) { + return get_ref_value(env, cls->constructor); + } + } + } + + auto protocolIt = bridgeState->mdProtocolsByPointer.find((Protocol*)raw); + if (protocolIt != bridgeState->mdProtocolsByPointer.end()) { + auto proto = bridgeState->getProtocol(env, protocolIt->second); + if (proto != nullptr) { + return get_ref_value(env, proto->constructor); + } + } else { + const uintptr_t rawNormalized = normalizePtr(raw); + for (const auto& entry : bridgeState->mdProtocolsByPointer) { + if (normalizePtr((void*)entry.first) != rawNormalized) { + continue; + } + + auto proto = bridgeState->getProtocol(env, entry.second); + if (proto != nullptr) { + return get_ref_value(env, proto->constructor); + } + } + + // Some protocol pointers come from compile-time @protocol() references + // and don't always match objc_getProtocol() pointer identity. + // Resolve them by scanning runtime protocol list and matching by address. + unsigned int protocolCount = 0; + Protocol** protocols = objc_copyProtocolList(&protocolCount); + if (protocols != nullptr) { + for (unsigned int i = 0; i < protocolCount; i++) { + Protocol* runtimeProto = protocols[i]; + if (normalizePtr((void*)runtimeProto) != rawNormalized) { + continue; + } + + const char* runtimeName = protocol_getName(runtimeProto); + MDSectionOffset metadataOffset = + findProtocolMetadataOffset(bridgeState->metadata, runtimeName); + if (metadataOffset != MD_SECTION_OFFSET_NULL) { + bridgeState->registerProtocolMetadata(runtimeProto, metadataOffset); + auto proto = bridgeState->getProtocol(env, metadataOffset); + bridgeState->registerRuntimeProtocol(proto, runtimeProto); + if (proto != nullptr) { + ::free(protocols); + return get_ref_value(env, proto->constructor); + } + } + + break; + } + ::free(protocols); + } + } + } + + if (pointeeType != nullptr && pointeeType->kind != mdTypeVoid) { + napi_value referenceValue = Reference::create(env, pointeeType, raw, false); + if (referenceValue != nullptr) { + return referenceValue; + } + } + + return Pointer::create(env, raw); + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + void** res = (void**)result; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_null: + case napi_undefined: + *res = nullptr; + return; + + case napi_bigint: { + uint64_t val = 0; + bool lossless = false; + NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + *res = (void*)val; + return; + } + + case napi_string: { + size_t len = 0; + NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, len, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + char* str = (char*)malloc(len + 1); + + NAPI_GUARD(napi_get_value_string_utf8(env, value, str, len + 1, &len)) { + NAPI_THROW_LAST_ERROR + ::free(str); + return; + } + + str[len] = '\0'; + + bool shouldCreateCFString = + pointeeType != nullptr && (pointeeType->kind == mdTypeNSStringObject || + pointeeType->kind == mdTypeNSMutableStringObject); + + if (shouldCreateCFString) { + CFStringRef cfStr = + CFStringCreateWithCString(kCFAllocatorDefault, str, kCFStringEncodingUTF8); + ::free(str); + *res = (void*)cfStr; + *shouldFree = true; + *shouldFreeAny = true; + } else { + *res = (void*)str; + *shouldFree = true; + *shouldFreeAny = true; + } + return; + } + + case napi_external: { + NAPI_GUARD(napi_get_value_external(env, value, res)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + return; + } + + case napi_object: { + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + id bridgedType = nil; + if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && + bridgedType != nil) { + *res = (void*)bridgedType; + return; + } + } + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *res = ptr->data; + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + if (ref == nullptr) { + napi_throw_error(env, nullptr, "Invalid Reference"); + *res = nullptr; + return; + } + if (ref->data == nullptr) { + std::shared_ptr resolvedType = pointeeType; + if (resolvedType == nullptr) { + resolvedType = ref->type; + } + + napi_value pendingInitValue = Reference::getInitValue(env, value, ref); + napi_valuetype pendingInitType = napi_undefined; + if (pendingInitValue != nullptr) { + napi_typeof(env, pendingInitValue, &pendingInitType); + } + if (resolvedType == nullptr && pendingInitValue != nullptr) { + if (pendingInitValue != nullptr) { + napi_valuetype initType = napi_undefined; + if (napi_typeof(env, pendingInitValue, &initType) == napi_ok) { + auto makeStructType = [&](StructInfo* info) -> std::shared_ptr { + if (info == nullptr || info->name == nullptr) { + return nullptr; + } + + std::string encoding = "{"; + encoding += info->name; + encoding += "="; + for (const auto& field : info->fields) { + if (field.type == nullptr) { + return nullptr; + } + field.type->encode(&encoding); + } + encoding += "}"; + + const char* encodingPtr = encoding.c_str(); + return TypeConv::Make(env, &encodingPtr); + }; + + if (initType == napi_object) { + if (StructObject::isInstance(env, pendingInitValue)) { + StructObject* structObj = StructObject::unwrap(env, pendingInitValue); + if (structObj != nullptr) { + resolvedType = makeStructType(structObj->info); + } + } + + if (resolvedType == nullptr) { + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + bool isArray = false; + bool isTypedArray = false; + bool isArrayBuffer = false; + bool isDataView = false; + napi_is_array(env, pendingInitValue, &isArray); + napi_is_typedarray(env, pendingInitValue, &isTypedArray); + napi_is_arraybuffer(env, pendingInitValue, &isArrayBuffer); + napi_is_dataview(env, pendingInitValue, &isDataView); + if (!isArray && !isTypedArray && !isArrayBuffer && !isDataView) { + napi_value propertyNames = nullptr; + if (napi_get_property_names(env, pendingInitValue, &propertyNames) == + napi_ok && + propertyNames != nullptr) { + uint32_t propertyCount = 0; + napi_get_array_length(env, propertyNames, &propertyCount); + std::unordered_set keys; + std::unordered_map keyIsInteger; + keys.reserve(propertyCount); + for (uint32_t i = 0; i < propertyCount; i++) { + napi_value keyValue = nullptr; + if (napi_get_element(env, propertyNames, i, &keyValue) != napi_ok || + keyValue == nullptr) { + continue; + } + napi_valuetype keyType = napi_undefined; + if (napi_typeof(env, keyValue, &keyType) != napi_ok || + keyType != napi_string) { + continue; + } + size_t keyLength = 0; + if (napi_get_value_string_utf8(env, keyValue, nullptr, 0, + &keyLength) != napi_ok) { + continue; + } + std::vector keyBuffer(keyLength + 1, '\0'); + if (napi_get_value_string_utf8(env, keyValue, keyBuffer.data(), + keyBuffer.size(), + &keyLength) != napi_ok) { + continue; + } + std::string key(keyBuffer.data(), keyLength); + keys.insert(key); + + napi_value propertyValue = nullptr; + if (napi_get_property(env, pendingInitValue, keyValue, + &propertyValue) == napi_ok && + propertyValue != nullptr) { + napi_valuetype propertyType = napi_undefined; + if (napi_typeof(env, propertyValue, &propertyType) == napi_ok) { + bool isInteger = false; + if (propertyType == napi_bigint) { + isInteger = true; + } else if (propertyType == napi_number) { + double numericValue = 0; + if (napi_get_value_double(env, propertyValue, &numericValue) == + napi_ok) { + int64_t truncated = static_cast(numericValue); + isInteger = static_cast(truncated) == numericValue; + } + } + keyIsInteger[key] = isInteger; + } + } + } + + if (!keys.empty()) { + auto isIntegerKind = [](MDTypeKind kind) -> bool { + switch (kind) { + case mdTypeChar: + case mdTypeSInt: + case mdTypeSShort: + case mdTypeSLong: + case mdTypeSInt64: + case mdTypeUChar: + case mdTypeUInt: + case mdTypeUShort: + case mdTypeUnichar: + case mdTypeULong: + case mdTypeUInt64: + case mdTypeUInt8: + case mdTypeBool: + return true; + default: + return false; + } + }; + + auto isFloatingKind = [](MDTypeKind kind) -> bool { + return kind == mdTypeFloat || kind == mdTypeDouble || + kind == mdTypeLongDouble || kind == mdTypeF16; + }; + + StructInfo* bestMatch = nullptr; + int bestScore = std::numeric_limits::min(); + uint16_t bestSize = std::numeric_limits::max(); + + for (const auto& entry : bridgeState->structOffsets) { + StructInfo* info = bridgeState->getStructInfo(env, entry.second); + if (info == nullptr || info->fields.size() != keys.size()) { + continue; + } + + bool match = true; + for (const auto& field : info->fields) { + if (field.name == nullptr || + keys.find(field.name) == keys.end()) { + match = false; + break; + } + } + if (!match) { + continue; + } + + int score = 0; + bool hasOnlyNumericFields = true; + for (const auto& field : info->fields) { + if (field.type == nullptr) { + hasOnlyNumericFields = false; + break; + } + + MDTypeKind fieldKind = field.type->kind; + if (isIntegerKind(fieldKind)) { + auto integerEntry = + keyIsInteger.find(field.name != nullptr ? field.name : ""); + score += + (integerEntry != keyIsInteger.end() && integerEntry->second) + ? 3 + : 1; + } else if (isFloatingKind(fieldKind)) { + score += 2; + } else { + hasOnlyNumericFields = false; + break; + } + } + + if (!hasOnlyNumericFields) { + continue; + } + + if (score > bestScore || + (score == bestScore && info->size < bestSize)) { + bestScore = score; + bestSize = info->size; + bestMatch = info; + } + } + + if (bestMatch != nullptr) { + resolvedType = makeStructType(bestMatch); + } + } + } + } + } + } + } + + if (resolvedType == nullptr) { + const char* inferredEncoding = "@"; + if (initType == napi_number || initType == napi_bigint) { + inferredEncoding = "q"; + } else if (initType == napi_boolean) { + inferredEncoding = "B"; + } + resolvedType = TypeConv::Make(env, &inferredEncoding); + } + } + } + } + + if (resolvedType == nullptr) { + const char* defaultEncoding = "@"; + resolvedType = TypeConv::Make(env, &defaultEncoding); + } + + ref->type = resolvedType; + size_t pointeeSize = sizeof(void*); + if (resolvedType != nullptr && resolvedType->type != nullptr && + resolvedType->type->size > 0) { + pointeeSize = resolvedType->type->size; + } + ref->data = calloc(1, pointeeSize); + if (ref->data == nullptr) { + napi_throw_error(env, nullptr, "Out of memory while allocating out parameter"); + *res = nullptr; + return; + } + ref->ownsData = true; + napi_value initValue = Reference::getInitValue(env, value, ref); + if (initValue != nullptr) { + bool shouldFree; + ref->type->toNative(env, initValue, ref->data, &shouldFree, &shouldFree); + Reference::clearInitValue(env, value, ref); + } + } + *res = ref->data; + return; + } + + if (StructObject::isInstance(env, value)) { + StructObject* structObj = StructObject::unwrap(env, value); + if (structObj != nullptr) { + *res = structObj->data; + } else + *res = nullptr; + return; + } + + bool isTypedArray = false; + napi_is_typedarray(env, value, &isTypedArray); + if (isTypedArray) { + void* data; + size_t length = 0; + napi_typedarray_type type; + NAPI_GUARD( + napi_get_typedarray_info(env, value, &type, &length, &data, nullptr, nullptr)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + + *res = data; + return; + } + + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + void* data = nullptr; + size_t byteLength = 0; + napi_get_arraybuffer_info(env, value, &data, &byteLength); + *res = data; + return; + } + if (unwrapKnownNativeHandle(env, value, res)) { + return; + } + break; + } + + case napi_function: { + if (unwrapKnownNativeHandle(env, value, res)) { + return; + } + break; + } + + default: + napi_throw_error(env, nullptr, "Invalid pointer type"); + *res = nullptr; + return; + } + + napi_throw_error(env, nullptr, "Invalid pointer type"); + *res = nullptr; + } + + void free(napi_env env, void* value) override { + if (value == nullptr) { + return; + } + + bool isCFString = pointeeType != nullptr && (pointeeType->kind == mdTypeNSStringObject || + pointeeType->kind == mdTypeNSMutableStringObject); + + if (isCFString) { + CFRelease((CFStringRef)value); + } else { + ::free(value); + } + } + + void encode(std::string* encoding) override { *encoding += "^v"; } +}; + +static const std::shared_ptr pointerTypeConv = std::make_shared(); + +class BlockTypeConv : public TypeConv { + public: + MDSectionOffset signatureOffset; + + BlockTypeConv(MDSectionOffset signatureOffset) : signatureOffset(signatureOffset) { + type = &ffi_type_pointer; + kind = mdTypeBlock; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + void* fn = *((void**)value); + if (fn == nullptr) { + napi_value nullValue; + napi_get_null(env, &nullValue); + return nullValue; + } + return FunctionPointer::wrap(env, fn, signatureOffset, true); + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + void** res = (void**)result; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_null: + case napi_undefined: + *res = nullptr; + return; + + case napi_bigint: { + uint64_t val = 0; + bool lossless = false; + NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + *res = (void*)val; + return; + } + + case napi_external: { + NAPI_GUARD(napi_get_value_external(env, value, res)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + return; + } + + case napi_object: { + NAPI_GUARD(napi_unwrap(env, value, res)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + return; + } + + case napi_function: { + if (FunctionReference::isInstance(env, value)) { + FunctionReference* ref = FunctionReference::unwrap(env, value); + if (ref == nullptr) { + napi_throw_error(env, nullptr, "Invalid FunctionReference"); + *res = nullptr; + return; + } + *res = ref->getFunctionPointer(signatureOffset, true); + return; + } + + void* wrapped; + status = napi_unwrap(env, value, &wrapped); + if (status == napi_ok) { + *res = wrapped; + return; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto closure = new Closure(env, bridgeState->metadata, signatureOffset, true); + id block = registerBlock(env, closure, value); + *res = (void*)block; + *shouldFree = true; + *shouldFreeAny = true; + return; + } + + default: + napi_throw_error(env, nullptr, "Invalid block pointer type"); + *res = nullptr; + return; + } + } + + void free(napi_env env, void* value) override { + if (value != nullptr) { + [(id)value release]; + } + } + + void encode(std::string* encoding) override { *encoding += "^v"; } +}; + +namespace { +void function_pointer_finalize_now(napi_env env, void* finalize_data, void* finalize_hint) { + Closure* closure = static_cast(finalize_hint); + if (closure != nullptr) { + Closure::destroyOnOwningThread(closure); + } +} +} // namespace + +void function_pointer_finalize(napi_env env, void* finalize_data, void* finalize_hint) { + if (PostFinalizer(env, function_pointer_finalize_now, finalize_data, finalize_hint)) { + return; + } + + function_pointer_finalize_now(env, finalize_data, finalize_hint); +} + +class FunctionPointerTypeConv : public TypeConv { + public: + MDSectionOffset signatureOffset; + + FunctionPointerTypeConv(MDSectionOffset signatureOffset) : signatureOffset(signatureOffset) { + type = &ffi_type_pointer; + kind = mdTypeFunctionPointer; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + void* fn = *((void**)value); + if (fn == nullptr) { + napi_value nullValue; + napi_get_null(env, &nullValue); + return nullValue; + } + return FunctionPointer::wrap(env, fn, signatureOffset, false); + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + void** res = (void**)result; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_null: + case napi_undefined: + *res = nullptr; + return; + + case napi_bigint: { + uint64_t val = 0; + bool lossless = false; + NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + *res = (void*)val; + return; + } + + case napi_external: { + NAPI_GUARD(napi_get_value_external(env, value, res)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + return; + } + + case napi_object: { + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *res = ptr->data; + } else if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *res = ref->data; + } else if (FunctionReference::isInstance(env, value)) { + FunctionReference* ref = FunctionReference::unwrap(env, value); + if (ref == nullptr) { + napi_throw_error(env, nullptr, "Invalid FunctionReference"); + *res = nullptr; + return; + } + *res = ref->getFunctionPointer(signatureOffset, false); + } else { + napi_throw_error(env, nullptr, "Invalid function pointer object"); + *res = nullptr; + } + return; + } + + case napi_function: { + if (FunctionReference::isInstance(env, value)) { + FunctionReference* ref = FunctionReference::unwrap(env, value); + if (ref == nullptr) { + napi_throw_error(env, nullptr, "Invalid FunctionReference"); + *res = nullptr; + return; + } + *res = ref->getFunctionPointer(signatureOffset, false); + return; + } + + void* wrapped; + status = napi_unwrap(env, value, &wrapped); + if (status == napi_ok) { + *res = wrapped; + return; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto closure = new Closure(env, bridgeState->metadata, signatureOffset, false); + closure->func = make_ref(env, value); + napi_remove_wrap(env, value, nullptr); + napi_ref ref; + napi_wrap(env, value, closure->fnptr, function_pointer_finalize, closure, &ref); + *res = (void*)closure->fnptr; + return; + } + + default: + napi_throw_error(env, nullptr, "Invalid block pointer type"); + *res = nullptr; + return; + } + } + + void encode(std::string* encoding) override { *encoding += "^v"; } +}; + +class StringTypeConv : public TypeConv { + public: + StringTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeString; + } + + napi_value toJS(napi_env env, void* cont, uint32_t flags) override { + void* value = *((void**)cont); + if (value == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + if ((flags & kCStringAsReference) != 0) { + return Reference::create(env, scharTypeConv, value, false); + } + + napi_value result; + napi_create_string_utf8(env, (char*)value, NAPI_AUTO_LENGTH, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + napi_valuetype valuetype; + napi_typeof(env, value, &valuetype); + + if (valuetype == napi_null || valuetype == napi_undefined) { + *(char**)result = nullptr; + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + if (valuetype == napi_object) { + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *(char**)result = (char*)ptr->data; + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *(char**)result = (char*)ref->data; + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + bool isTypedArray = false; + napi_is_typedarray(env, value, &isTypedArray); + if (isTypedArray) { + void* data = nullptr; + size_t length = 0; + napi_typedarray_type typedArrayType; + napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); + *(char**)result = static_cast(data); + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + void* data = nullptr; + size_t byteLength = 0; + napi_get_arraybuffer_info(env, value, &data, &byteLength); + *(char**)result = static_cast(data); + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + bool isDataView = false; + napi_is_dataview(env, value, &isDataView); + if (isDataView) { + void* data = nullptr; + size_t byteLength = 0; + napi_value arrayBuffer = nullptr; + size_t byteOffset = 0; + napi_get_dataview_info(env, value, &byteLength, &data, &arrayBuffer, &byteOffset); + *(char**)result = static_cast(data); + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + *(char**)result = nullptr; + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + char** res = (char**)result; + + *res = nullptr; + size_t len = 0; + + NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, len, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + *res = (char*)malloc(len + 1); + + NAPI_GUARD(napi_get_value_string_utf8(env, value, *res, len + 1, &len)) { + NAPI_THROW_LAST_ERROR + ::free(*res); + return; + } + + (*res)[len] = '\0'; + + *shouldFree = true; + *shouldFreeAny = true; + } + + void free(napi_env env, void* value) override { ::free(value); } + + void encode(std::string* encoding) override { *encoding += "*"; } +}; + +static const std::shared_ptr stringTypeConv = std::make_shared(); + +class ObjCObjectTypeConv : public TypeConv { + public: + MDSectionOffset classOffset = 0; + std::vector protocolOffsets; + + ObjCObjectTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeAnyObject; + } + + ObjCObjectTypeConv(MDSectionOffset classOffset, std::vector protocolOffsets) + : classOffset(classOffset), protocolOffsets(protocolOffsets) { + type = &ffi_type_pointer; + if (classOffset != 0) { + kind = mdTypeClassObject; + } else { + kind = protocolOffsets.empty() ? mdTypeAnyObject : mdTypeProtocolObject; + } + } + + ObjCObjectTypeConv(std::vector protocolOffsets) + : protocolOffsets(protocolOffsets) { + type = &ffi_type_pointer; + kind = protocolOffsets.empty() ? mdTypeAnyObject : mdTypeProtocolObject; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + void* rawPtr = *((void**)value); + id obj = (__bridge id)rawPtr; + + if (obj == nil) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + + if (bridgeState != nullptr) { + if (object_isClass(obj)) { + if (napi_value constructor = findRegisteredClassConstructor(env, (Class)obj); + constructor != nullptr) { + return constructor; + } + } + + auto normalizePtr = [](void* ptr) -> uintptr_t { + return normalizeRuntimePointer(reinterpret_cast(ptr)); + }; + + auto protocolIt = bridgeState->mdProtocolsByPointer.find((Protocol*)obj); + if (protocolIt != bridgeState->mdProtocolsByPointer.end()) { + auto proto = bridgeState->getProtocol(env, protocolIt->second); + if (proto != nullptr) { + return get_ref_value(env, proto->constructor); + } + } else { + const uintptr_t objNormalized = normalizePtr((void*)obj); + for (const auto& entry : bridgeState->mdProtocolsByPointer) { + if (normalizePtr((void*)entry.first) != objNormalized) { + continue; + } + + auto proto = bridgeState->getProtocol(env, entry.second); + if (proto != nullptr) { + return get_ref_value(env, proto->constructor); + } + } + } + } + + // Always unbox NSNull and CFBoolean/NSNumber values (except NSDecimalNumber), + // so primitive round-trips match historical runtime behavior. + if (isKindOfClassFast(obj, [NSNull class])) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + if (isKindOfClassFast(obj, [NSNumber class]) && + !isKindOfClassFast(obj, [NSDecimalNumber class])) { + if (CFGetTypeID((CFTypeRef)obj) == CFBooleanGetTypeID()) { + napi_value result; + napi_get_boolean(env, [obj boolValue], &result); + return result; + } + + napi_value result; + napi_create_double(env, [obj doubleValue], &result); + return result; + } + + // Untyped id values that are actually Objective-C blocks should be + // callable from JS. Preserve callback identity when we already have one. + if (isObjCBlockObject(obj)) { + napi_value cached = getCachedBlockCallback(env, (void*)obj); + if (cached != nullptr) { + return cached; + } + + const char* signature = getObjCBlockSignature((void*)obj); + if (signature != nullptr) { + return FunctionPointer::wrapWithEncoding(env, (void*)obj, signature, true); + } + } + + // Auto-unbox plain id string values. + const bool isUntypedObject = classOffset == 0 && protocolOffsets.empty(); + if (isUntypedObject && isKindOfClassFast(obj, [NSString class])) { + NSUInteger length = [obj length]; + std::vector chars(length > 0 ? length : 1); + if (length > 0) { + [((NSString*)obj) getCharacters:(unichar*)chars.data() range:NSMakeRange(0, length)]; + } + napi_value result; + napi_create_string_utf16(env, length > 0 ? chars.data() : nullptr, length, &result); + return result; + } + + if (bridgeState == nullptr) { + return Pointer::create(env, (void*)obj); + } + + if (napi_value existing = bridgeState->findCachedObjectWrapper(env, obj); existing != nullptr) { + return existing; + } + + ObjectOwnership ownership; + if ((flags & kReturnOwned) != 0) { + ownership = kOwnedObject; + } else { + ownership = kUnownedObject; + } + + auto object = bridgeState->getObject(env, obj, ownership, classOffset, &protocolOffsets); + if (object == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + return object; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + id* res = (id*)result; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_null: + case napi_undefined: + *res = nil; + return; + + case napi_string: { + size_t len = 0; + NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, 0, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + std::vector chars(len + 1); + NAPI_GUARD(napi_get_value_string_utf8(env, value, chars.data(), len + 1, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + *res = [[[NSString alloc] initWithBytes:chars.data() + length:len + encoding:NSUTF8StringEncoding] autorelease]; + if (*res == nil) { + *res = [NSString string]; + } + break; + } + + case napi_number: { + double val = 0; + NAPI_GUARD(napi_get_value_double(env, value, &val)) { + NAPI_THROW_LAST_ERROR + return; + } + *res = [NSNumber numberWithDouble:val]; + break; + } + + case napi_boolean: { + bool val = false; + NAPI_GUARD(napi_get_value_bool(env, value, &val)) { + NAPI_THROW_LAST_ERROR + return; + } + *res = [NSNumber numberWithBool:val]; + break; + } + + case napi_bigint: { + int64_t val = 0; + bool lossless = false; + NAPI_GUARD(napi_get_value_bigint_int64(env, value, &val, &lossless)) { + NAPI_THROW_LAST_ERROR + return; + } + *res = [NSNumber numberWithLongLong:val]; + break; + } + + case napi_external: + NAPI_GUARD(napi_get_value_external(env, value, (void**)res)) { + NAPI_THROW_LAST_ERROR + *res = nil; + return; + } + break; + + case napi_object: + case napi_function: { + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto cacheRoundTrip = [&](id nativeObj) { + if (nativeObj == nil || bridgeState == nullptr || + !bridgeState->hasRoundTripCacheFrame()) { + return; + } + + bridgeState->cacheRoundTripObject(env, nativeObj, value); + }; + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + void* pointerData = ptr != nullptr ? ptr->data : nullptr; + if (id cachedObject = resolveCachedHandleObject(env, pointerData); cachedObject != nil) { + *res = cachedObject; + return; + } + *res = (id)pointerData; + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + void* referenceData = ref != nullptr ? ref->data : nullptr; + if (id cachedObject = resolveCachedHandleObject(env, referenceData); + cachedObject != nil) { + *res = cachedObject; + return; + } + *res = (id)referenceData; + return; + } + + if (bridgeState != nullptr) { + id bridgedType = nil; + if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && + bridgedType != nil) { + *res = bridgedType; + return; + } + } + + void* wrapped = nullptr; + bool knownNativeHandle = unwrapKnownNativeHandle(env, value, &wrapped); + if (!knownNativeHandle) { + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + *res = createNSDataWrapper(env, value, bridgeState); + if (*res != nil) { + cacheRoundTrip(*res); + return; + } + } + + bool isTypedArray = false; + napi_is_typedarray(env, value, &isTypedArray); + if (isTypedArray) { + *res = createNSDataWrapper(env, value, bridgeState); + if (*res != nil) { + cacheRoundTrip(*res); + return; + } + } + + bool isDataView = false; + napi_is_dataview(env, value, &isDataView); + if (isDataView) { + *res = createNSDataWrapper(env, value, bridgeState); + if (*res != nil) { + cacheRoundTrip(*res); + return; + } + } + + ScopedObjectConversion conversion(env, value); + if (!conversion.entered()) { + *res = nil; + if (conversion.status() != napi_ok) { + status = conversion.status(); + NAPI_THROW_LAST_ERROR + } + return; + } + + bool isArray = false; + napi_is_array(env, value, &isArray); + if (isArray) { + uint32_t len = 0; + napi_get_array_length(env, value, &len); + *res = [NSMutableArray arrayWithCapacity:len]; + + for (uint32_t i = 0; i < len; i++) { + napi_value elem; + napi_get_element(env, value, i, &elem); + id obj = nil; + toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + [(*res) addObject:obj != nil ? obj : [NSNull null]]; + } + + cacheRoundTrip(*res); + return; + } else { + napi_value global, jsObject, valueConstructor, DateConstructor, MapConstructor; + napi_value StringConstructor, NumberConstructor, BooleanConstructor; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &jsObject); + napi_get_named_property(env, global, "Date", &DateConstructor); + napi_get_named_property(env, global, "Map", &MapConstructor); + napi_get_named_property(env, global, "String", &StringConstructor); + napi_get_named_property(env, global, "Number", &NumberConstructor); + napi_get_named_property(env, global, "Boolean", &BooleanConstructor); + napi_get_named_property(env, value, "constructor", &valueConstructor); + bool isEqual; + napi_strict_equals(env, jsObject, valueConstructor, &isEqual); + bool isDate; + napi_strict_equals(env, DateConstructor, valueConstructor, &isDate); + bool isMap; + napi_strict_equals(env, MapConstructor, valueConstructor, &isMap); + bool isStringObject = false; + bool isNumberObject = false; + bool isBooleanObject = false; + napi_strict_equals(env, StringConstructor, valueConstructor, &isStringObject); + napi_strict_equals(env, NumberConstructor, valueConstructor, &isNumberObject); + napi_strict_equals(env, BooleanConstructor, valueConstructor, &isBooleanObject); + + if (isStringObject || isNumberObject || isBooleanObject) { + napi_value valueOfMethod; + napi_get_named_property(env, value, "valueOf", &valueOfMethod); + napi_value primitiveValue; + napi_call_function(env, value, valueOfMethod, 0, nullptr, &primitiveValue); + if (hasPendingException(env)) { + *res = nil; + return; + } + toNative(env, primitiveValue, result, shouldFree, shouldFreeAny); + return; + } + + if (isDate) { + // Get the timestamp from the JavaScript Date object + napi_value getTimeMethod; + napi_get_named_property(env, value, "getTime", &getTimeMethod); + napi_value timestamp; + napi_call_function(env, value, getTimeMethod, 0, nullptr, ×tamp); + + double timeInMilliseconds; + napi_get_value_double(env, timestamp, &timeInMilliseconds); + + // Convert milliseconds to seconds for NSDate + NSTimeInterval timeInSeconds = timeInMilliseconds / 1000.0; + *res = [NSDate dateWithTimeIntervalSince1970:timeInSeconds]; + cacheRoundTrip(*res); + return; + } + + if (isMap) { + *res = [NSMutableDictionary dictionary]; + + napi_value entriesMethod; + napi_get_named_property(env, value, "entries", &entriesMethod); + napi_value iterator; + napi_call_function(env, value, entriesMethod, 0, nullptr, &iterator); + + napi_value nextMethod; + napi_get_named_property(env, iterator, "next", &nextMethod); + + while (true) { + napi_value step; + napi_call_function(env, iterator, nextMethod, 0, nullptr, &step); + + napi_value doneValue; + napi_get_named_property(env, step, "done", &doneValue); + bool done = false; + napi_get_value_bool(env, doneValue, &done); + if (done) { + break; + } + + napi_value tuple; + napi_get_named_property(env, step, "value", &tuple); + napi_value keyValue; + napi_value elementValue; + napi_get_element(env, tuple, 0, &keyValue); + napi_get_element(env, tuple, 1, &elementValue); + + id keyObject = nil; + id valueObject = nil; + toNative(env, keyValue, (void*)&keyObject, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + toNative(env, elementValue, (void*)&valueObject, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + + if (keyObject != nil && valueObject != nil) { + [(*res) setObject:valueObject forKey:keyObject]; + } + } + + cacheRoundTrip(*res); + return; + } + + if (!isEqual) { + *res = jsObjectToId(env, value); + return; + } + + bool hasLength = false; + napi_has_named_property(env, value, "length", &hasLength); + if (hasLength) { + napi_value lengthValue; + napi_get_named_property(env, value, "length", &lengthValue); + napi_valuetype lengthType = napi_undefined; + napi_typeof(env, lengthValue, &lengthType); + if (lengthType == napi_number) { + uint32_t len = 0; + napi_get_value_uint32(env, lengthValue, &len); + *res = [NSMutableArray arrayWithCapacity:len]; + for (uint32_t i = 0; i < len; i++) { + bool hasElement = false; + napi_has_element(env, value, i, &hasElement); + if (!hasElement) { + [(*res) addObject:[NSNull null]]; + continue; + } + napi_value elem; + napi_get_element(env, value, i, &elem); + id obj = nil; + toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + [(*res) addObject:obj != nil ? obj : [NSNull null]]; + } + cacheRoundTrip(*res); + return; + } + } + + *res = [NSMutableDictionary dictionary]; + napi_value objectKeysMethod = nullptr; + napi_get_named_property(env, jsObject, "keys", &objectKeysMethod); + napi_value keys = nullptr; + napi_call_function(env, jsObject, objectKeysMethod, 1, &value, &keys); + uint32_t len = 0; + napi_get_array_length(env, keys, &len); + + for (uint32_t i = 0; i < len; i++) { + napi_value key = nullptr; + napi_get_element(env, keys, i, &key); + + if (key == nullptr) { + continue; + } + + napi_value keyString = key; + napi_valuetype keyType = napi_undefined; + if (napi_typeof(env, key, &keyType) != napi_ok) { + continue; + } + + if (keyType == napi_symbol) { + continue; + } + + if (keyType != napi_string) { + if (napi_coerce_to_string(env, key, &keyString) != napi_ok || + keyString == nullptr) { + continue; + } + } + + size_t keyLength = 0; + if (napi_get_value_string_utf8(env, keyString, nullptr, 0, &keyLength) != napi_ok) { + continue; + } + + std::vector keyBuffer(keyLength + 1, '\0'); + if (napi_get_value_string_utf8(env, keyString, keyBuffer.data(), keyBuffer.size(), + &keyLength) != napi_ok) { + continue; + } + + NSString* nsKey = [NSString stringWithUTF8String:keyBuffer.data()]; + if (nsKey == nil) { + continue; + } + + id obj = nil; + napi_value elem = nullptr; + if (napi_get_property(env, value, key, &elem) != napi_ok) { + continue; + } + toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + if (obj != nil) { + [(*res) setObject:obj forKey:nsKey]; + } + } + + cacheRoundTrip(*res); + return; + } + } + + if (bridgeState != nullptr && wrapped != nullptr) { + for (const auto& entry : bridgeState->classes) { + auto bridgedClass = entry.second; + if (bridgedClass == wrapped) { + *res = (id)bridgedClass->nativeClass; + return; + } + } + + for (const auto& entry : bridgeState->protocols) { + auto bridgedProtocol = entry.second; + if (bridgedProtocol != wrapped) { + continue; + } + + Protocol* runtimeProtocol = objc_getProtocol(bridgedProtocol->name.c_str()); + if (runtimeProtocol == nil) { + std::string baseName; + if (stripProtocolSuffix(bridgedProtocol->name.c_str(), &baseName)) { + runtimeProtocol = objc_getProtocol(baseName.c_str()); + } + } + + if (runtimeProtocol != nil) { + *res = (id)runtimeProtocol; + return; + } + } + } + + *res = (id)wrapped; + cacheRoundTrip(*res); + return; + + break; + } + + default: + napi_throw_error(env, nullptr, "Invalid object type"); + *res = nil; + break; + } + } + + void free(napi_env env, void* value) override { + id obj = *((id*)value); + auto bridgeState = ObjCBridgeState::InstanceData(env); + bridgeState->unregisterObject(obj); + } + + void encode(std::string* encoding) override { *encoding += "@"; } +}; + +static const std::shared_ptr objcObjectTypeConv = + std::make_shared(); + +class ObjCInstanceObjectTypeConv : public ObjCObjectTypeConv { + public: + ObjCInstanceObjectTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeInstanceObject; + } +}; + +static const auto objcInstanceObjectTypeConv = std::make_shared(); + +class ObjCNSStringObjectTypeConv : public TypeConv { + public: + ObjCNSStringObjectTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeNSStringObject; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + NSString* str = *((NSString**)value); + + if (str == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + NSUInteger length = [str length]; + std::vector chars(length > 0 ? length : 1); + if (length > 0) { + [str getCharacters:(unichar*)chars.data() range:NSMakeRange(0, length)]; + } + napi_value result; + napi_create_string_utf16(env, chars.data(), length, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + ObjCObjectTypeConv typeConv; + typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); + } + + void encode(std::string* encoding) override { *encoding += "@"; } +}; + +static const std::shared_ptr objcNSStringObjectTypeConv = + std::make_shared(); + +class ObjCNSMutableStringObjectTypeConv : public TypeConv { + public: + ObjCNSMutableStringObjectTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeNSMutableStringObject; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + NSMutableString* str = *((NSMutableString**)value); + + if (str == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (napi_value existing = bridgeState->findCachedObjectWrapper(env, str); existing != nullptr) { + return existing; + } + + ObjectOwnership ownership = (flags & kReturnOwned) != 0 ? kOwnedObject : kUnownedObject; + return bridgeState->getObject(env, str, ownership); + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + napi_valuetype type; + napi_typeof(env, value, &type); + if (type == napi_string) { + NSMutableString** res = (NSMutableString**)result; + + size_t len = 0; + NAPI_GUARD(napi_get_value_string_utf16(env, value, nullptr, len, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + std::vector chars(len + 1); + + NAPI_GUARD(napi_get_value_string_utf16(env, value, chars.data(), len + 1, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + *res = [[NSMutableString alloc] initWithCharacters:(unichar*)chars.data() length:len]; + return; + } + + ObjCObjectTypeConv typeConv; + typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); + } + + void encode(std::string* encoding) override { *encoding += "@"; } +}; + +static const std::shared_ptr objcNSMutableStringObjectTypeConv = + std::make_shared(); + +class ObjCClassTypeConv : public TypeConv { + public: + ObjCClassTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeClass; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + Class cls = *((Class*)value); + + if (cls == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + if (napi_value constructor = findRegisteredClassConstructor(env, cls); + constructor != nullptr) { + return constructor; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + return bridgeState != nullptr + ? bridgeState->getObject(env, (id)cls, kUnownedObject, 0, nullptr) + : nullptr; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + ObjCObjectTypeConv typeConv; + typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); + } + + void encode(std::string* encoding) override { *encoding += "#"; } +}; + +static const std::shared_ptr objcClassTypeConv = + std::make_shared(); + +class SelectorTypeConv : public TypeConv { + public: + SelectorTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeSelector; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + SEL val = *((SEL*)value); + napi_create_string_utf8(env, sel_getName(val), NAPI_AUTO_LENGTH, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + SEL* res = (SEL*)result; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_string: { + size_t length = 0; + NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, 0, &length)) { + NAPI_THROW_LAST_ERROR + *res = NULL; + return; + } + + std::vector selectorName(length + 1, '\0'); + NAPI_GUARD(napi_get_value_string_utf8(env, value, selectorName.data(), + selectorName.size(), &length)) { + NAPI_THROW_LAST_ERROR + *res = NULL; + return; + } + *res = sel_registerName(selectorName.data()); + break; + } + + case napi_undefined: + case napi_null: + *res = NULL; + return; + + default: + napi_throw_error(env, nullptr, "Invalid selector type"); + *res = NULL; + return; + } + } + + void encode(std::string* encoding) override { *encoding += ":"; } +}; + +static const std::shared_ptr selectorTypeConv = + std::make_shared(); + +class StructTypeConv : public TypeConv { + public: + MDSectionOffset structOffset; + StructInfo* info = nullptr; + bool structInfoSearched = false; + + StructTypeConv(MDSectionOffset structOffset, ffi_type* type, bool ownsType, + std::vector> retainedElementTypes) + : structOffset(structOffset), + ownsType(ownsType), + retainedElementTypes(std::move(retainedElementTypes)) { + this->type = type; + kind = mdTypeStruct; + } + + ~StructTypeConv() override { + if (ownsType && type != nullptr) { + std::free(type->elements); + delete type; + type = nullptr; + } + } + + inline StructInfo* getInfo(napi_env env) { + if (!structInfoSearched) { + auto bridgeState = ObjCBridgeState::InstanceData(env); + info = bridgeState->getStructInfo(env, structOffset); + structInfoSearched = true; + } + + return info; + } + + inline size_t getStructSize(napi_env env) { + if (this->type != nullptr && this->type->size > 0) { + return this->type->size; + } + + auto info = getInfo(env); + return info != nullptr ? info->size : 0; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + auto info = getInfo(env); + + if (info == nullptr) { + napi_value result; + void* data; + napi_create_arraybuffer(env, type->size, &data, &result); + memcpy(data, value, type->size); + return result; + } else { + return StructObject::fromNative(env, info, value, (flags & kStructZeroCopy) == 0); + } + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + const size_t structSize = getStructSize(env); + if (structSize == 0) { + napi_throw_type_error(env, "TypeError", "Invalid struct size"); + return; + } + + bool isTypedArray = false; + napi_is_typedarray(env, value, &isTypedArray); + + if (isTypedArray) { + void* data; + size_t length = 0; + napi_typedarray_type type; + NAPI_GUARD(napi_get_typedarray_info(env, value, &type, &length, &data, nullptr, nullptr)) { + NAPI_THROW_LAST_ERROR + return; + } + const size_t unitLength = getTypedArrayUnitLength(type); + const size_t byteLength = length * unitLength; + memset(result, 0, structSize); + memcpy(result, data, std::min(byteLength, structSize)); + + return; + } + + napi_valuetype type; + napi_typeof(env, value, &type); + + if (type == napi_null || type == napi_undefined) { + auto info = getInfo(env); + + if (info == nullptr) { + napi_throw_type_error(env, "TypeError", + "Invalid struct type, must be Struct Object, " + "Struct Object Descriptor or TypedArray"); + return; + } + + memset(result, 0, info->size); + return; + } else if (type != napi_object) { + napi_throw_type_error(env, "TypeError", + "Invalid struct type, must be Struct Object, " + "Struct Object Descriptor or TypedArray"); + return; + } + + auto structObject = StructObject::isInstance(env, value) + ? StructObject::unwrap(env, value) + : nullptr; + if (structObject != nullptr) { + const size_t copySize = std::min(static_cast(structObject->info->size), structSize); + memset(result, 0, structSize); + memcpy(result, structObject->data, copySize); + return; + } + + auto info = getInfo(env); + + if (info == nullptr) { + napi_throw_type_error(env, "TypeError", + "Invalid struct type, must be Struct Object or TypedArray"); + return; + } + + if (structSize < info->size) { + std::vector storage(info->size, 0); + StructObject(env, info, value, storage.data()); + memcpy(result, storage.data(), structSize); + return; + } + + // Serialize directly to previously allocated memory. + StructObject(env, info, value, result); + } + + private: + bool ownsType; + std::vector> retainedElementTypes; +}; + +class ArrayTypeConv : public TypeConv { + public: + int arraySize; + std::shared_ptr elementType; + bool decayToPointerForArguments = false; + + ArrayTypeConv(int arraySize, std::shared_ptr elementType) + : arraySize(arraySize), elementType(elementType) { + auto arrayType = new ffi_type(); + arrayType->type = FFI_TYPE_STRUCT; + arrayType->size = 0; + arrayType->alignment = 0; + arrayType->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (arraySize + 1)); + for (int i = 0; i < arraySize; i++) { + arrayType->elements[i] = elementType->type; + } + arrayType->elements[arraySize] = nullptr; + type = arrayType; + kind = mdTypeArray; + } + + ~ArrayTypeConv() override { + if (type != nullptr) { + std::free(type->elements); + delete type; + type = nullptr; + } + } + + ffi_type* ffiTypeForArgument() override { + decayToPointerForArguments = true; + return &ffi_type_pointer; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + if (decayToPointerForArguments) { + void* raw = *((void**)value); + if (raw == nullptr) { + napi_value nullValue; + napi_get_null(env, &nullValue); + return nullValue; + } + return Pointer::create(env, raw); + } + + napi_value result; + napi_create_array_with_length(env, arraySize, &result); + + size_t elementSize = getElementSize(); + + auto base = static_cast(value); + for (int i = 0; i < arraySize; i++) { + void* slot = base + (i * elementSize); + napi_value elementValue = elementType->toJS(env, slot, flags); + napi_valuetype elementValueType = napi_undefined; + napi_typeof(env, elementValue, &elementValueType); + if (elementValueType == napi_boolean) { + bool boolValue = false; + napi_get_value_bool(env, elementValue, &boolValue); + napi_create_uint32(env, boolValue ? 1 : 0, &elementValue); + } + napi_set_element(env, result, i, elementValue); + if (StructObject::isInstance(env, elementValue)) { + napi_set_named_property(env, elementValue, "__ns_parent_struct_array", result); + } + } + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + if (decayToPointerForArguments) { + void** pointerResult = static_cast(result); + *pointerResult = nullptr; + + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType == napi_null || valueType == napi_undefined) { + return; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *pointerResult = ptr != nullptr ? ptr->data : nullptr; + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *pointerResult = ref != nullptr ? ref->data : nullptr; + return; + } + + if (StructObject::isInstance(env, value)) { + StructObject* structObject = StructObject::unwrap(env, value); + *pointerResult = structObject != nullptr ? structObject->data : nullptr; + return; + } + + size_t arrayByteSize = getArrayByteSize(); + if (arrayByteSize == 0) { + return; + } + void* copiedBuffer = malloc(arrayByteSize); + if (copiedBuffer == nullptr) { + napi_throw_error(env, nullptr, "Out of memory while converting C array argument"); + return; + } + + copyToInlineArrayStorage(env, value, copiedBuffer, shouldFree, shouldFreeAny); + + bool hasPendingException = false; + napi_is_exception_pending(env, &hasPendingException); + if (hasPendingException) { + ::free(copiedBuffer); + return; + } + + *pointerResult = copiedBuffer; + if (shouldFree != nullptr) { + *shouldFree = true; + } + if (shouldFreeAny != nullptr) { + *shouldFreeAny = true; + } + return; + } + + copyToInlineArrayStorage(env, value, result, shouldFree, shouldFreeAny); + } + + void free(napi_env env, void* value) override { + if (value != nullptr) { + ::free(value); + } + } + + void encode(std::string* encoding) override { + *encoding += "["; + *encoding += std::to_string(arraySize); + elementType->encode(encoding); + *encoding += "]"; + } + + private: + size_t getElementSize() const { + size_t elementSize = + elementType != nullptr && elementType->type != nullptr ? elementType->type->size : 0; + if (elementSize == 0 && type != nullptr && arraySize > 0 && type->size >= (size_t)arraySize) { + elementSize = type->size / static_cast(arraySize); + } + if (elementSize == 0) { + elementSize = sizeof(void*); + } + return elementSize; + } + + size_t getArrayByteSize() const { return getElementSize() * static_cast(arraySize); } + + void copyToInlineArrayStorage(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) { + size_t elementSize = getElementSize(); + size_t arrayByteSize = getArrayByteSize(); + memset(result, 0, arrayByteSize); + + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType == napi_null || valueType == napi_undefined) { + return; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + if (ptr == nullptr || ptr->data == nullptr) { + return; + } + memcpy(result, ptr->data, arrayByteSize); + return; + } + + bool isArray = false; + napi_is_array(env, value, &isArray); + if (isArray) { + for (int i = 0; i < arraySize; i++) { + bool hasElement = false; + napi_has_element(env, value, i, &hasElement); + if (!hasElement) { + continue; + } + + napi_value elementValue; + napi_get_element(env, value, i, &elementValue); + void* slot = static_cast(result) + (i * elementSize); + elementType->toNative(env, elementValue, slot, shouldFree, shouldFreeAny); + } + return; + } + + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + void* data = nullptr; + size_t byteLength = 0; + napi_get_arraybuffer_info(env, value, &data, &byteLength); + memcpy(result, data, std::min(byteLength, arrayByteSize)); + return; + } + + void* data; + size_t length = 0; + napi_typedarray_type typedArrayType; + napi_status typedArrayStatus = + napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); + if (typedArrayStatus != napi_ok) { + NAPI_THROW_LAST_ERROR + return; + } + + size_t copyLength = length * getTypedArrayUnitLength(typedArrayType); + memcpy(result, data, std::min(copyLength, arrayByteSize)); + } +}; + +class VectorTypeConv : public TypeConv { + public: + uint16_t vectorSize; + std::shared_ptr elementType; + MDTypeKind vectorKind; + + VectorTypeConv(MDTypeKind vectorKind, uint16_t vectorSize, std::shared_ptr elementType) + : vectorSize(vectorSize), elementType(elementType), vectorKind(vectorKind) { + auto vectorType = new ffi_type(); +#if defined(FFI_TYPE_EXT_VECTOR) + vectorType->type = vectorKind == mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_EXT_VECTOR; +#else + vectorType->type = vectorKind == mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_STRUCT; +#endif + size_t lanes = std::max(vectorSize, 1); + // 3-lane vectors are ABI-lowered to 4-lane storage on Apple platforms. + size_t abiLanes = lanes == 3 ? 4 : lanes; + vectorType->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (abiLanes + 1)); + + ffi_type* elementFfiType = elementType != nullptr && elementType->type != nullptr + ? elementType->type + : &ffi_type_float; + const size_t elementSize = std::max(elementFfiType->size, sizeof(float)); + const size_t elementAlignment = + std::max(elementFfiType->alignment, static_cast(1)); + + for (size_t i = 0; i < abiLanes; i++) { + vectorType->elements[i] = elementFfiType; + } + vectorType->elements[abiLanes] = nullptr; + + size_t vectorAlignment = elementAlignment; + if (vectorKind != mdTypeComplex) { + size_t packedSize = abiLanes * elementSize; + size_t preferredAlignment = packedSize >= 16 ? 16 : packedSize; + vectorAlignment = std::max(vectorAlignment, preferredAlignment); + } + vectorAlignment = std::min(vectorAlignment, 16); + vectorType->alignment = static_cast(vectorAlignment); + vectorType->size = alignUp(abiLanes * elementSize, vectorAlignment); + + type = vectorType; + kind = vectorKind; + } + + ~VectorTypeConv() override { + if (type != nullptr) { + std::free(type->elements); + delete type; + type = nullptr; + } + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_array_with_length(env, vectorSize, &result); + + size_t elementSize = getElementSize(); + auto base = static_cast(value); + for (uint16_t i = 0; i < vectorSize; i++) { + void* slot = base + (static_cast(i) * elementSize); + napi_value elementValue = elementType->toJS(env, slot, flags); + napi_valuetype elementValueType = napi_undefined; + napi_typeof(env, elementValue, &elementValueType); + if (elementValueType == napi_boolean) { + bool boolValue = false; + napi_get_value_bool(env, elementValue, &boolValue); + napi_create_uint32(env, boolValue ? 1 : 0, &elementValue); + } + napi_set_element(env, result, i, elementValue); + } + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NS_OBJC_NAPI_PREAMBLE + + memset(result, 0, getVectorByteSize()); + + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType == napi_null || valueType == napi_undefined) { + return; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + if (ptr != nullptr && ptr->data != nullptr) { + copyFromContiguousBuffer(ptr->data, getVectorByteSize(), result); + } + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + if (ref != nullptr && ref->data != nullptr) { + copyFromContiguousBuffer(ref->data, getVectorByteSize(), result); + } + return; + } + + if (StructObject::isInstance(env, value)) { + StructObject* structObject = StructObject::unwrap(env, value); + if (structObject != nullptr && structObject->data != nullptr) { + copyFromContiguousBuffer(structObject->data, structObject->info->size, result); + } + return; + } + + bool isArray = false; + napi_is_array(env, value, &isArray); + if (isArray) { + writeFromArrayElements(env, value, result, shouldFree, shouldFreeAny); + return; + } + + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + void* data = nullptr; + size_t byteLength = 0; + napi_get_arraybuffer_info(env, value, &data, &byteLength); + copyFromContiguousBuffer(data, byteLength, result); + return; + } + + void* data = nullptr; + size_t length = 0; + napi_typedarray_type typedArrayType = napi_int8_array; + napi_status typedArrayStatus = + napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); + if (typedArrayStatus == napi_ok) { + size_t copyLength = length * getTypedArrayUnitLength(typedArrayType); + copyFromContiguousBuffer(data, copyLength, result); + return; + } + + napi_throw_type_error( + env, "TypeError", + "Invalid vector type, expected array, typed array, array buffer, pointer or reference."); + } + + void encode(std::string* encoding) override { + *encoding += "V"; + *encoding += std::to_string(vectorSize); + if (elementType != nullptr) { + elementType->encode(encoding); + } + } + + private: + inline size_t getElementSize() const { + size_t elementSize = + elementType != nullptr && elementType->type != nullptr ? elementType->type->size : 0; + if (elementSize == 0) { + elementSize = sizeof(float); + } + return elementSize; + } + + inline size_t getVectorByteSize() const { + size_t expectedSize = static_cast(vectorSize) * getElementSize(); + if (type != nullptr && type->size > expectedSize) { + expectedSize = type->size; + } + return expectedSize; + } + + inline void copyFromContiguousBuffer(void* source, size_t sourceLength, void* destination) const { + memcpy(destination, source, std::min(sourceLength, getVectorByteSize())); + } + + void writeFromArrayElements(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) { + size_t elementSize = getElementSize(); + auto base = static_cast(result); + + for (uint16_t i = 0; i < vectorSize; i++) { + bool hasElement = false; + napi_has_element(env, value, i, &hasElement); + if (!hasElement) { + continue; + } + + napi_value elementValue; + napi_get_element(env, value, i, &elementValue); + void* slot = base + (static_cast(i) * elementSize); + elementType->toNative(env, elementValue, slot, shouldFree, shouldFreeAny); + } + } +}; + +std::shared_ptr TypeConv::Make(napi_env env, const char** encoding) { + char first = **encoding; + bool readonly = false; + if (first == 'r') { + readonly = true; + first = *(++(*encoding)); + } + + switch (first) { + case 'c': + (*encoding)++; + return scharTypeConv; + case 'i': + (*encoding)++; + return sint32TypeConv; + case 's': + (*encoding)++; + return sint16TypeConv; + case 'l': + case 'q': + (*encoding)++; + return sint64TypeConv; + case 'C': + (*encoding)++; + return uint8TypeConv; + case 'I': + (*encoding)++; + return uint32TypeConv; + case 'S': + (*encoding)++; + return uint16TypeConv; + case 'L': + case 'Q': + (*encoding)++; + return uint64TypeConv; + case 'f': + (*encoding)++; + return float32TypeConv; + case 'd': + (*encoding)++; + return float64TypeConv; + case 'B': + (*encoding)++; + return boolTypeConv; + case 'v': + (*encoding)++; + return voidTypeConv; + case '*': + (*encoding)++; + return stringTypeConv; + case '@': + (*encoding)++; + return objcObjectTypeConv; + case '#': + (*encoding)++; + return objcClassTypeConv; + case ':': + (*encoding)++; + return selectorTypeConv; + case '[': { + char c = **encoding; + std::string num; + while ((c = **encoding) >= '0' && c <= '9') { + num += c; + (*encoding)++; + } + auto arraySize = std::stoi(num); + auto elementType = TypeConv::Make(env, encoding); + while (**encoding != ']') { + (*encoding)++; + } // skip array type + (*encoding)++; // skip ']' + return std::make_shared(arraySize, elementType); + } + case '{': { + auto& encodingStructCache = structTypeCaches(env).encodingStructTypes; + std::string structname; + const char* c = *encoding + 1; + while (*c != '\0' && *c != '=') { + structname += *c; + c++; + } + if (*c != '=') { + while (**encoding != '\0' && **encoding != '}') { + (*encoding)++; + } + if (**encoding == '}') { + (*encoding)++; + } + return pointerTypeConv; + } + + // Check if we already have a cached StructTypeConv for this encoding-based struct + auto cacheIt = encodingStructCache.find(structname); + if (cacheIt != encodingStructCache.end()) { + return cacheIt->second; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + MDSectionOffset structOffset = MD_SECTION_OFFSET_NULL; + if (bridgeState != nullptr) { + auto structOffsetIt = bridgeState->structOffsets.find(structname); + if (structOffsetIt != bridgeState->structOffsets.end()) { + structOffset = structOffsetIt->second; + } + } + bool ownsType = false; + std::vector> retainedElementTypes; + auto type = typeFromStruct(env, encoding, &ownsType, &retainedElementTypes); + auto structTypeConv = std::make_shared( + structOffset, type, ownsType, std::move(retainedElementTypes)); + + // Cache the StructTypeConv + encodingStructCache[structname] = structTypeConv; + + return structTypeConv; + } + case 'b': { + (*encoding)++; + char c = **encoding; + while ((c = **encoding) >= '0' && c <= '9') { + (*encoding)++; + } // skip bits + return uint64TypeConv; + } + case '^': + (*encoding)++; + TypeConv::Make(env, encoding); + return pointerTypeConv; + case '?': + // unknown type + return pointerTypeConv; + default: + std::cout << "getTypeInfo unknown encoding: " << *encoding << std::endl; + return pointerTypeConv; + } +} + +std::shared_ptr TypeConv::Make(napi_env env, MDMetadataReader* reader, + MDSectionOffset* offset, uint8_t opaquePointers) { + auto kind = reader->getTypeKind(*offset); + bool next = (MDTypeFlag)kind & mdTypeFlagNext; + kind = (MDTypeKind)((kind & ~mdTypeFlagNext) & ~mdTypeFlagVariadic); + *offset += sizeof(MDTypeKind); + + switch (kind) { + case mdTypeChar: { + return scharTypeConv; + } + + case mdTypeSInt: { + return sint32TypeConv; + } + + case mdTypeSShort: { + return sint16TypeConv; + } + + case mdTypeSLong: + case mdTypeSInt64: { + return sint64TypeConv; + } + + case mdTypeUInt8: { + return uint8TypeConv; + } + + case mdTypeUChar: { + return ucharTypeConv; + } + + case mdTypeUInt: { + return uint32TypeConv; + } + + case mdTypeUShort: { + return uint16TypeConv; + } + + case mdTypeUnichar: { + return unicharTypeConv; + } + + case mdTypeULong: + case mdTypeUInt64: { + return uint64TypeConv; + } + + case mdTypeFloat: { + return float32TypeConv; + } + + case mdTypeF16: { + return float16TypeConv; + } + + case mdTypeDouble: { + return float64TypeConv; + } + + case mdTypeBool: { + return boolTypeConv; + } + + case mdTypeVoid: { + return voidTypeConv; + } + + case mdTypeString: { + return stringTypeConv; + } + + case mdTypeAnyObject: { + return objcObjectTypeConv; + } + + case mdTypeInstanceObject: { + return objcInstanceObjectTypeConv; + } + + case mdTypeClassObject: { + auto classOffset = reader->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + bool next = (classOffset & mdSectionOffsetNext) != 0; + classOffset &= ~mdSectionOffsetNext; + if (classOffset == MD_SECTION_OFFSET_NULL) { + classOffset = 0; + } else { + classOffset += reader->classesOffset; + } + std::vector protocolOffsets; + while (next) { + auto protocolOffset = reader->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & mdSectionOffsetNext) != 0; + protocolOffset &= ~mdSectionOffsetNext; + if (protocolOffset == MD_SECTION_OFFSET_NULL) { + protocolOffset = 0; + } else { + protocolOffset += reader->protocolsOffset; + protocolOffsets.push_back(protocolOffset); + } + } + return std::make_shared(classOffset, protocolOffsets); + } + + case mdTypeProtocolObject: { + std::vector protocolOffsets; + bool next = true; + while (next) { + auto protocolOffset = reader->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & mdSectionOffsetNext) != 0; + protocolOffset &= ~mdSectionOffsetNext; + if (protocolOffset == MD_SECTION_OFFSET_NULL) { + protocolOffset = 0; + } else { + protocolOffset += reader->protocolsOffset; + protocolOffsets.push_back(protocolOffset); + } + } + return std::make_shared(protocolOffsets); + } + + case mdTypeNSStringObject: { + return objcNSStringObjectTypeConv; + } + + case mdTypeNSMutableStringObject: { + return objcNSMutableStringObjectTypeConv; + } + + case mdTypeClass: { + return objcClassTypeConv; + } + + case mdTypeSelector: { + return selectorTypeConv; + } + + case mdTypeArray: { + auto arraySize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + auto elementType = TypeConv::Make(env, reader, offset); + return std::make_shared(arraySize, elementType); + } + + case mdTypeStruct: { + auto& caches = structTypeCaches(env); + auto& processingStructs = caches.processingStructs; + auto& structTypeCache = caches.structTypes; + auto structOffset = reader->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + auto isUnion = (structOffset & mdSectionOffsetNext) != 0; + structOffset &= ~mdSectionOffsetNext; + if (structOffset == MD_SECTION_OFFSET_NULL) { + return pointerTypeConv; + } + structOffset += isUnion ? reader->unionsOffset : reader->structsOffset; + auto structName = reader->getString(structOffset); + + // Check if we already have a cached StructTypeConv for this struct + auto cacheIt = structTypeCache.find(structOffset); + if (cacheIt != structTypeCache.end()) { + return cacheIt->second; + } + + // Check if we're currently processing this struct (recursion detection) + bool isRecursive = processingStructs.find(structOffset) != processingStructs.end(); + + ffi_type* type = nullptr; + bool ownsType = false; + std::vector> retainedElementTypes; + if (opaquePointers != 2 && !isRecursive) { + type = typeFromStruct(env, reader, structOffset, isUnion, &ownsType, + &retainedElementTypes); + } + + auto structTypeConv = std::make_shared( + structOffset, type, ownsType, std::move(retainedElementTypes)); + + // Cache the StructTypeConv to handle recursion and avoid duplicates + structTypeCache[structOffset] = structTypeConv; + + return structTypeConv; + } + + case mdTypePointer: { + auto pointeeType = TypeConv::Make(env, reader, offset, opaquePointers == 1 ? 2 : 0); + return std::make_shared(pointeeType); + } + + case mdTypeOpaquePointer: { + return pointerTypeConv; + } + + case mdTypeVector: { + auto vectorSize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); + return std::make_shared(kind, vectorSize, elementType); + } + + case mdTypeBlock: { + auto blockSignature = reader->getOffset(*offset) + reader->signaturesOffset; + *offset += sizeof(MDSectionOffset); + return std::make_shared(blockSignature); + } + + case mdTypeFunctionPointer: { + auto blockSignature = reader->getOffset(*offset) + reader->signaturesOffset; + *offset += sizeof(MDSectionOffset); + return std::make_shared(blockSignature); + } + + case mdTypeUInt128: { + return uint128TypeConv; + } + + case mdTypeExtVector: { + auto vectorSize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); + return std::make_shared(kind, vectorSize, elementType); + } + + case mdTypeComplex: { + auto vectorSize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); + return std::make_shared(kind, vectorSize, elementType); + } + + default: + return pointerTypeConv; + } +} + +namespace { + +bool tryFastConvertStringToNSString(napi_env env, napi_value value, id* out, bool mutableString) { + if (out == nullptr) { + return false; + } + + if (mutableString) { + constexpr size_t kStackUtf16Capacity = 128; + char16_t utf16Stack[kStackUtf16Capacity]; + char16_t* utf16Buffer = utf16Stack; + size_t utf16Capacity = kStackUtf16Capacity; + size_t utf16Length = 0; + if (napi_get_value_string_utf16(env, value, utf16Buffer, utf16Capacity, &utf16Length) != + napi_ok) { + return false; + } + + std::vector utf16Heap; + if (utf16Length + 1 >= utf16Capacity) { + if (napi_get_value_string_utf16(env, value, nullptr, 0, &utf16Length) != napi_ok) { + return false; + } + utf16Heap.resize(utf16Length + 1, 0); + utf16Buffer = utf16Heap.data(); + utf16Capacity = utf16Heap.size(); + if (napi_get_value_string_utf16(env, value, utf16Buffer, utf16Capacity, &utf16Length) != + napi_ok) { + return false; + } + } + + *out = [[NSMutableString alloc] initWithCharacters:reinterpret_cast(utf16Buffer) + length:utf16Length]; + return true; + } + + constexpr size_t kStackUtf8Capacity = 256; + char utf8Stack[kStackUtf8Capacity]; + char* utf8Buffer = utf8Stack; + size_t utf8Capacity = kStackUtf8Capacity; + size_t utf8Length = 0; + if (napi_get_value_string_utf8(env, value, utf8Buffer, utf8Capacity, &utf8Length) != napi_ok) { + return false; + } + + std::vector utf8Heap; + if (utf8Length + 1 >= utf8Capacity) { + if (napi_get_value_string_utf8(env, value, nullptr, 0, &utf8Length) != napi_ok) { + return false; + } + utf8Heap.resize(utf8Length + 1, '\0'); + utf8Buffer = utf8Heap.data(); + utf8Capacity = utf8Heap.size(); + if (napi_get_value_string_utf8(env, value, utf8Buffer, utf8Capacity, &utf8Length) != napi_ok) { + return false; + } + } + + id stringValue = [[[NSString alloc] initWithBytes:utf8Buffer + length:utf8Length + encoding:NSUTF8StringEncoding] autorelease]; + *out = stringValue != nil ? stringValue : [NSString string]; + return true; +} + +bool tryFastConvertObjCObjectValue(napi_env env, napi_value value, napi_valuetype valueType, + MDTypeKind kind, id* out) { + if (out == nullptr) { + return false; + } + + switch (valueType) { + case napi_null: + case napi_undefined: + *out = nil; + return true; + + case napi_external: { + void* external = nullptr; + if (napi_get_value_external(env, value, &external) != napi_ok) { + return false; + } + *out = static_cast(external); + return true; + } + + case napi_number: { + double numericValue = 0; + if (napi_get_value_double(env, value, &numericValue) != napi_ok) { + return false; + } + *out = [NSNumber numberWithDouble:numericValue]; + return true; + } + + case napi_boolean: { + bool boolValue = false; + if (napi_get_value_bool(env, value, &boolValue) != napi_ok) { + return false; + } + *out = [NSNumber numberWithBool:boolValue]; + return true; + } + + case napi_bigint: { + int64_t bigintValue = 0; + bool lossless = false; + if (napi_get_value_bigint_int64(env, value, &bigintValue, &lossless) != napi_ok) { + return false; + } + *out = [NSNumber numberWithLongLong:bigintValue]; + return true; + } + + case napi_string: + return tryFastConvertStringToNSString(env, value, out, kind == mdTypeNSMutableStringObject); + + case napi_object: + case napi_function: { + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto cacheRoundTrip = [&](id nativeObj) { + if (nativeObj == nil || bridgeState == nullptr || !bridgeState->hasRoundTripCacheFrame()) { + return; + } + + bridgeState->cacheRoundTripObject(env, nativeObj, value); + }; + + if (valueType == napi_object) { + bool isArray = false; + if (napi_is_array(env, value, &isArray) == napi_ok && isArray) { + return false; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + void* pointerData = ptr != nullptr ? ptr->data : nullptr; + if (id cachedObject = resolveCachedHandleObject(env, pointerData); cachedObject != nil) { + *out = cachedObject; + return true; + } + *out = (id)pointerData; + return true; + } + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + void* referenceData = ref != nullptr ? ref->data : nullptr; + if (id cachedObject = resolveCachedHandleObject(env, referenceData); + cachedObject != nil) { + *out = cachedObject; + return true; + } + *out = (id)referenceData; + return true; + } + } + + void* nativeHandle = nullptr; + if (unwrapKnownNativeHandle(env, value, &nativeHandle)) { + *out = (id)nativeHandle; + cacheRoundTrip(*out); + return true; + } + + bool isTypedArray = false; + if (napi_is_typedarray(env, value, &isTypedArray) == napi_ok && isTypedArray) { + *out = createNSDataWrapper(env, value, bridgeState); + if (*out != nil) { + cacheRoundTrip(*out); + return true; + } + return false; + } + + bool isArrayBuffer = false; + if (napi_is_arraybuffer(env, value, &isArrayBuffer) == napi_ok && isArrayBuffer) { + *out = createNSDataWrapper(env, value, bridgeState); + if (*out != nil) { + cacheRoundTrip(*out); + return true; + } + return false; + } + + bool isDataView = false; + if (napi_is_dataview(env, value, &isDataView) == napi_ok && isDataView) { + *out = createNSDataWrapper(env, value, bridgeState); + if (*out != nil) { + cacheRoundTrip(*out); + return true; + } + return false; + } + + return false; + } + + default: + return false; + } +} + +} // namespace + +bool TryFastConvertNapiArgument(napi_env env, MDTypeKind kind, napi_value value, void* result) { + if (result == nullptr || value == nullptr) { + return false; + } + + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, value, &valueType) != napi_ok) { + return false; + } + + switch (kind) { + case mdTypeAnyObject: + case mdTypeProtocolObject: + case mdTypeClassObject: + case mdTypeInstanceObject: + case mdTypeNSStringObject: + case mdTypeNSMutableStringObject: + return tryFastConvertObjCObjectValue(env, value, valueType, static_cast(kind), + reinterpret_cast(result)); + + case mdTypeSelector: { + SEL* selector = reinterpret_cast(result); + switch (valueType) { + case napi_null: + case napi_undefined: + *selector = nullptr; + return true; + + case napi_string: { + constexpr size_t kStackSelectorCapacity = 128; + char selectorStack[kStackSelectorCapacity]; + size_t selectorLength = 0; + if (napi_get_value_string_utf8(env, value, selectorStack, kStackSelectorCapacity, + &selectorLength) != napi_ok) { + return false; + } + const char* selectorName = selectorStack; + std::vector selectorHeap; + if (selectorLength + 1 >= kStackSelectorCapacity) { + if (napi_get_value_string_utf8(env, value, nullptr, 0, &selectorLength) != napi_ok) { + return false; + } + selectorHeap.resize(selectorLength + 1, '\0'); + if (napi_get_value_string_utf8(env, value, selectorHeap.data(), selectorHeap.size(), + &selectorLength) != napi_ok) { + return false; + } + selectorName = selectorHeap.data(); + } + *selector = sel_registerName(selectorName); + return true; + } + + default: + return false; + } + } + + default: + return false; + } +} + +bool ConsumeNapiArgumentConversionFailure(napi_env env) { + if (failedObjectConversionEnv != env) { + return false; + } + failedObjectConversionEnv = nullptr; + return true; +} + +bool TryFastConvertNapiUInt16Argument(napi_env env, napi_value value, uint16_t* result) { + if (result == nullptr || value == nullptr) { + return false; + } + + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, value, &valueType) != napi_ok) { + return false; + } + + if (valueType == napi_string) { + size_t strLen = 0; + if (napi_get_value_string_utf16(env, value, nullptr, 0, &strLen) != napi_ok) { + return false; + } + if (strLen != 1) { + napi_throw_type_error(env, nullptr, "Expected a single-character string."); + *result = 0; + return false; + } + + char16_t chars[2] = {0, 0}; + if (napi_get_value_string_utf16(env, value, chars, 2, &strLen) != napi_ok) { + return false; + } + + *result = static_cast(chars[0]); + return true; + } + + napi_value coerced = value; + if (napi_coerce_to_number(env, value, &coerced) != napi_ok) { + return false; + } + + uint32_t converted = 0; + if (napi_get_value_uint32(env, coerced, &converted) != napi_ok) { + return false; + } + + *result = static_cast(converted); + return true; +} + +// Cleanup function to clear thread-local caches +void clearStructTypeCaches(napi_env env) { + auto cacheIt = structTypeCachesByEnv.find(env); + if (cacheIt == structTypeCachesByEnv.end()) { + return; + } + + auto& caches = cacheIt->second; + caches.structTypes.clear(); + caches.encodingStructTypes.clear(); + + for (const auto& entry : caches.forwardDeclaredStructs) { + std::free(entry.second->elements); + delete entry.second; + } + for (const auto& entry : caches.forwardDeclaredEncodingStructs) { + std::free(entry.second->elements); + delete entry.second; + } + + structTypeCachesByEnv.erase(cacheIt); +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/Util.h b/NativeScript/ffi/objc/napi/Util.h similarity index 100% rename from NativeScript/ffi/napi/Util.h rename to NativeScript/ffi/objc/napi/Util.h diff --git a/NativeScript/ffi/napi/Util.mm b/NativeScript/ffi/objc/napi/Util.mm similarity index 99% rename from NativeScript/ffi/napi/Util.mm rename to NativeScript/ffi/objc/napi/Util.mm index f5816ed14..5b4ba0e1f 100644 --- a/NativeScript/ffi/napi/Util.mm +++ b/NativeScript/ffi/objc/napi/Util.mm @@ -116,8 +116,6 @@ napi_value jsSymbolFor(napi_env env, const char* string) { return symbol; } -char name_buf[512]; - std::string getEncodedType(napi_env env, napi_value value) { napi_valuetype type; napi_typeof(env, value, &type); diff --git a/NativeScript/ffi/napi/Variable.h b/NativeScript/ffi/objc/napi/Variable.h similarity index 100% rename from NativeScript/ffi/napi/Variable.h rename to NativeScript/ffi/objc/napi/Variable.h diff --git a/NativeScript/ffi/napi/Variable.mm b/NativeScript/ffi/objc/napi/Variable.mm similarity index 100% rename from NativeScript/ffi/napi/Variable.mm rename to NativeScript/ffi/objc/napi/Variable.mm diff --git a/NativeScript/ffi/napi/node_api_util.h b/NativeScript/ffi/objc/napi/node_api_util.h similarity index 96% rename from NativeScript/ffi/napi/node_api_util.h rename to NativeScript/ffi/objc/napi/node_api_util.h index 8a1a0dc00..9ffb7cb17 100644 --- a/NativeScript/ffi/napi/node_api_util.h +++ b/NativeScript/ffi/objc/napi/node_api_util.h @@ -12,10 +12,10 @@ inline bool napiSupportsThreadsafeFunctions(void* dl) { #define NAPI_EXPORT __attribute__((visibility("default"))) -#define NAPI_PREAMBLE napi_status status; +#define NS_OBJC_NAPI_PREAMBLE napi_status status; #define NAPI_CALLBACK_BEGIN(n_args) \ - NAPI_PREAMBLE \ + NS_OBJC_NAPI_PREAMBLE \ napi_value argv[n_args]; \ size_t argc = n_args; \ napi_value jsThis; \ diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.h b/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.h new file mode 100644 index 000000000..d37467617 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.h @@ -0,0 +1,22 @@ +#ifndef NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H +#define NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H + +#include "ffi/objc/shared/NativeApiBackendConfig.h" +#include "quickjs.h" + +namespace nativescript { + +using NativeApiScheduler = NativeApiBackendScheduler; +using NativeApiConfig = NativeApiBackendConfig; + +void InstallNativeApi(JSContext* context, + const NativeApiConfig& config = + NativeApiConfig{}); +void CleanupNativeApi(JSContext* context); + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(JSContext* context, + const char* metadataPath); + +#endif // NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.mm new file mode 100644 index 000000000..60a9f2b05 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.mm @@ -0,0 +1,81 @@ +#include "NativeApiQuickJS.h" + +#ifdef TARGET_ENGINE_QUICKJS + +#include "NativeApiQuickJSRuntime.h" +#include "SignatureDispatch.h" + +namespace nativescript { + +namespace { + +using nativescript::engine::Array; +using nativescript::engine::ArrayBuffer; +using nativescript::engine::BigInt; +using nativescript::engine::Function; +using nativescript::engine::HostObject; +using nativescript::engine::MutableBuffer; +using nativescript::engine::Object; +using nativescript::engine::PropNameID; +using nativescript::engine::Runtime; +using nativescript::engine::String; +using nativescript::engine::StringBuffer; +using nativescript::engine::Value; +using nativescript::engine::JSError; +using metagen::MDMemberFlag; +using metagen::MDMetadataReader; +using metagen::MDSectionOffset; +using metagen::MDTypeKind; + +// clang-format off +#define NATIVESCRIPT_NATIVE_API_HOST_EXPLICIT_OVERRIDE 1 +#define NATIVESCRIPT_NATIVE_API_BACKEND_NAME "quickjs" +#include "../shared/bridge/ObjCBridge.mm" +// clang-format on + +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS 1 +#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION 1 + +#include "NativeApiQuickJSRuntimeSupport.mm" + +// clang-format off +#include "../shared/bridge/HostObjects.mm" +#include "../shared/bridge/Callbacks.mm" +#include "../shared/bridge/TypeConv.mm" +#include "../shared/bridge/Invocation.mm" +#include "../shared/bridge/ClassBuilder.mm" +#include "../shared/bridge/HostObject.mm" +// clang-format on + +#include "NativeApiQuickJSSelectorGroups.mm" + +} // namespace + +#include "../shared/bridge/Install.mm" + +void InstallNativeApi(JSContext* context, const NativeApiConfig& config) { + if (context == nullptr) { + return; + } + auto state = engine::quickjsengine::stateForContext(context); + nativescript::engine::Runtime runtime(state); + engine::quickjsengine::ensureClasses(runtime); + InstallNativeApi(runtime, config); +} + +void CleanupNativeApi(JSContext* context) { + if (context != nullptr) { + engine::quickjsengine::releaseStateForContext(context); + } +} + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(JSContext* context, const char* metadataPath) { + nativescript::NativeApiConfig config; + config.metadataPath = metadataPath; + nativescript::InstallNativeApi(context, config); +} + +#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSGsd.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSGsd.mm new file mode 100644 index 000000000..eed6e786e --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSGsd.mm @@ -0,0 +1,292 @@ +// --- GSD (Generated Signature Dispatch) for QuickJS --- +// GsdObjCContext is the engine-neutral interface the generated invokers use: +// it reads JS arguments and writes the JS return value via the QuickJS API. +// Readers require an actual JS number so coercion edge cases defer to the +// fully correct generic path. +struct GsdObjCContext; +using ObjCGsdInvoker = bool (*)(GsdObjCContext&); +struct ObjCGsdDispatchEntry { + uint64_t dispatchId; + ObjCGsdInvoker invoker; +}; + +struct GsdObjCContext { + Runtime& runtime; + const std::shared_ptr& bridge; + id self; + SEL selector; + JSContext* context; + JSValueConst* arguments; + const NativeApiType& returnType; + JSValue result = JS_UNDEFINED; + const Value* valueArguments = nullptr; + bool materializeValueResult = false; + Value valueResult = Value::undefined(); + + template + void invokeNative(Invocation&& invocation) { + performGeneratedObjCInvocation(runtime, bridge, [&]() { invocation(); }); + } + + bool readNumber(size_t i, double* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (!v.isNumber()) return false; + *out = v.getNumber(); + return true; + } + JSValueConst v = arguments[i]; + if (!JS_IsNumber(v)) return false; + return quickJSNumberValue(context, v, out); + } + bool readBool(size_t i, uint8_t* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (!v.isBool()) return false; + *out = v.getBool() ? 1 : 0; + return true; + } + JSValueConst v = arguments[i]; + if (!JS_IsBool(v)) return false; + *out = JS_ToBool(context, v) != 0 ? 1 : 0; + return true; + } + template + bool readSigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + template + bool readUnsigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readFloat(size_t i, float* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readDouble(size_t i, double* out) { return readNumber(i, out); } + bool readSelector(size_t i, SEL* out) { + if (valueArguments != nullptr) { + return readFastEngineSelectorArgument(runtime, valueArguments[i], out); + } + return readQuickJSEngineSelectorArgument(runtime, arguments[i], out); + } + bool readClass(size_t i, Class* out) { + if (valueArguments != nullptr) { + Class cls = classFromEngineValue(runtime, valueArguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + if (auto* c = quickJSHostObjectRaw( + runtime, arguments[i])) { + *out = c->nativeClass(); + return true; + } + Class cls = quickJSNativeClassArgument(runtime, arguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + bool readObject(size_t i, id* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (v.isNull() || v.isUndefined()) { + *out = nil; + return true; + } + if (!v.isObject()) return false; + Object object = v.asObject(runtime); + if (object.isHostObject(runtime)) { + *out = object.getHostObject(runtime)->object(); + return true; + } + if (object.isHostObject(runtime)) { + *out = static_cast( + object.getHostObject(runtime)->nativeClass()); + return true; + } + Class cls = classFromEngineValue(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + if (object.isHostObject(runtime)) { + *out = static_cast( + object.getHostObject(runtime) + ->nativeProtocol()); + return true; + } + return false; + } + JSValueConst v = arguments[i]; + if (JS_IsNull(v) || JS_IsUndefined(v)) { + *out = nil; + return true; + } + if (auto* h = quickJSHostObjectRaw(runtime, v)) { + *out = h->object(); + return true; + } + if (auto* c = quickJSHostObjectRaw(runtime, v)) { + *out = static_cast(c->nativeClass()); + return true; + } + if (JS_IsObject(v)) { + Class cls = quickJSNativeClassArgument(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + } + if (auto* p = + quickJSHostObjectRaw(runtime, v)) { + *out = static_cast(p->nativeProtocol()); + return true; + } + return false; + } + + void setVoid() { + if (materializeValueResult) { + valueResult = Value::undefined(); + return; + } + result = JS_UNDEFINED; + } + void setBool(bool v) { + if (materializeValueResult) { + valueResult = Value(v); + return; + } + result = JS_NewBool(context, v); + } + void setInt32(int32_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JS_NewInt32(context, v); + } + void setUInt32(uint32_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JS_NewUint32(context, v); + } + void setUInt16(uint16_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JS_NewUint32(context, v); + } + void setInt64(int64_t v) { + if (materializeValueResult) { + valueResult = signedInteger64ToEngineValue(runtime, v); + return; + } + result = quickJSInteger64Value(runtime, v); + } + void setUInt64(uint64_t v) { + if (materializeValueResult) { + valueResult = unsignedInteger64ToEngineValue(runtime, v); + return; + } + result = quickJSUnsignedInteger64Value(runtime, v); + } + void setDouble(double v) { + if (materializeValueResult) { + valueResult = Value(v); + return; + } + result = JS_NewFloat64(context, v); + } + void setSelector(SEL v) { + const char* name = v != nullptr ? sel_getName(v) : nullptr; + if (materializeValueResult) { + valueResult = name != nullptr ? makeString(runtime, name) : Value::null(); + return; + } + result = name == nullptr ? JS_NULL : JS_NewString(context, name); + } + void setClass(Class v) { + if (materializeValueResult) { + if (v == nil) { + valueResult = Value::null(); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + valueResult = makeNativeClassValue(runtime, bridge, std::move(symbol)); + return; + } + if (v == nil) { + result = JS_NULL; + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value classValue = makeNativeClassValue(runtime, bridge, std::move(symbol)); + result = classValue.local(runtime); + } + void setObject(id obj) { + if (materializeValueResult) { + valueResult = convertNativeReturnValue(runtime, bridge, returnType, &obj); + return; + } + result = setQuickJSEngineObjectReturn(runtime, bridge, returnType, obj); + } +}; + +// Close the anonymous namespace so the generated dispatch table lives in +// namespace nativescript; GsdObjCContext/ObjCGsdDispatchEntry stay reachable +// via the unnamed namespace's implicit using-directive. +} // namespace (temporary close for GSD .inc) + +#if defined(__has_include) +#if __has_include("../shared/GeneratedGsdSignatureDispatch.inc") +#include "../shared/GeneratedGsdSignatureDispatch.inc" +#endif +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +inline constexpr ObjCGsdDispatchEntry kGeneratedObjCGsdDispatchEntries[] = { + {0, nullptr}}; +#endif + +ObjCGsdInvoker lookupObjCGsdInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCGsdDispatchEntries, dispatchId); +} + +namespace { // reopen anonymous namespace + +// --- End GSD --- diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm new file mode 100644 index 000000000..2f38e3ffb --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm @@ -0,0 +1,379 @@ +#include "NativeApiQuickJSRuntime.h" +#include "../shared/NativeApiStackValueArray.h" + +#ifdef TARGET_ENGINE_QUICKJS + +namespace nativescript { +class NativeApiObjectHostObject; +} + +namespace nativescript { +namespace engine { + +namespace quickjsengine { + +JSClassID gHostClassId = 0; +JSClassID gFunctionClassId = 0; + +namespace { +std::mutex& runtimeStatesMutex() { + static auto* mutex = new std::mutex(); + return *mutex; +} + +std::unordered_map>& runtimeStates() { + static auto* states = new std::unordered_map>(); + return *states; +} +} // namespace + +std::shared_ptr stateForContext(JSContext* context) { + std::lock_guard lock(runtimeStatesMutex()); + auto& states = runtimeStates(); + auto it = states.find(context); + if (it != states.end()) { + return it->second; + } + auto state = std::make_shared(context); + states[context] = state; + return state; +} + +void releaseStateForContext(JSContext* context) { + std::shared_ptr state; + { + std::lock_guard lock(runtimeStatesMutex()); + auto& states = runtimeStates(); + auto it = states.find(context); + if (it == states.end()) { + return; + } + state = std::move(it->second); + states.erase(it); + } + state->cleanup(); +} + +static bool isNativeInstancePrototypeBypassExcluded(JSContext* ctx, + JSAtom atom) { + const char* name = JS_AtomToCString(ctx, atom); + if (name == nullptr) { + return true; + } + bool excluded = + std::strcmp(name, "kind") == 0 || + std::strcmp(name, "className") == 0 || + std::strcmp(name, "nativeAddress") == 0 || + std::strcmp(name, "class") == 0 || + std::strcmp(name, "constructor") == 0 || + std::strcmp(name, "super") == 0 || + std::strcmp(name, "invoke") == 0 || + std::strcmp(name, "send") == 0 || + std::strcmp(name, "takeRetainedValue") == 0 || + std::strcmp(name, "takeUnretainedValue") == 0 || + std::strcmp(name, "toString") == 0; + JS_FreeCString(ctx, name); + return excluded; +} + +static void freePropertyDescriptor(JSContext* ctx, + JSPropertyDescriptor& desc) { + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + JS_FreeValue(ctx, desc.value); +} + +static JSValue nativePrototypeProperty(JSContext* ctx, JSValueConst obj, + JSAtom atom, JSValueConst receiver, + HostObjectHolder* holder, + bool* handled) { + *handled = false; + if (holder == nullptr || + holder->typeToken != hostObjectTypeToken()) { + return JS_UNDEFINED; + } + + JSValue prototype = JS_GetPrototype(ctx, obj); + if (JS_IsException(prototype)) { + *handled = true; + return prototype; + } + + for (size_t depth = 0; depth < 64 && JS_IsObject(prototype); depth++) { + JSPropertyDescriptor desc = {}; + int found = JS_GetOwnProperty(ctx, &desc, prototype, atom); + if (found < 0) { + JS_FreeValue(ctx, prototype); + *handled = true; + return JS_EXCEPTION; + } + if (found > 0) { + if (isNativeInstancePrototypeBypassExcluded(ctx, atom)) { + freePropertyDescriptor(ctx, desc); + JS_FreeValue(ctx, prototype); + return JS_UNDEFINED; + } + + *handled = true; + JS_FreeValue(ctx, prototype); + if ((desc.flags & JS_PROP_GETSET) != 0) { + JSValue getter = desc.getter; + JS_FreeValue(ctx, desc.setter); + JS_FreeValue(ctx, desc.value); + if (JS_IsUndefined(getter)) { + JS_FreeValue(ctx, getter); + return JS_UNDEFINED; + } + JSValue result = JS_Call(ctx, getter, receiver, 0, nullptr); + JS_FreeValue(ctx, getter); + return result; + } + + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + return desc.value; + } + + JSValue nextPrototype = JS_GetPrototype(ctx, prototype); + JS_FreeValue(ctx, prototype); + if (JS_IsException(nextPrototype)) { + *handled = true; + return nextPrototype; + } + prototype = nextPrototype; + } + + JS_FreeValue(ctx, prototype); + return JS_UNDEFINED; +} + +static JSValue nativeHostGet(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { + Runtime runtime(stateForContext(ctx)); + auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); + if (holder == nullptr || holder->hostObject == nullptr) { + return JS_UNDEFINED; + } + try { + bool handledByPrototype = false; + JSValue prototypeResult = + nativePrototypeProperty(ctx, obj, atom, receiver, holder, + &handledByPrototype); + if (handledByPrototype) { + return prototypeResult; + } + + Value result = holder->hostObject->get(runtime, PropNameID(atomToUtf8(ctx, atom))); + if (!result.isUndefined()) { + return result.local(runtime); + } + return JS_UNDEFINED; + } catch (const std::exception& error) { + return throwError(ctx, error); + } +} + +static int nativeHostSet(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, + JSValueConst, int) { + Runtime runtime(stateForContext(ctx)); + auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); + if (holder == nullptr || holder->hostObject == nullptr) { + return 0; + } + try { + bool handled = holder->hostObject->set( + runtime, PropNameID(atomToUtf8(ctx, atom)), + Value::borrowed(runtime, value)); + return handled ? 1 : 0; + } catch (const std::exception& error) { + throwError(ctx, error); + return -1; + } +} + +static int nativeHostHas(JSContext* ctx, JSValueConst obj, JSAtom atom) { + Runtime runtime(stateForContext(ctx)); + auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); + if (holder == nullptr || holder->hostObject == nullptr) { + return 0; + } + try { + auto names = holder->hostObject->getPropertyNames(runtime); + std::string requested = atomToUtf8(ctx, atom); + for (const auto& name : names) { + if (name.utf8(runtime) == requested) { + return 1; + } + } + } catch (const std::exception&) { + } + return 0; +} + +static int nativeHostOwnNames(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, + JSValueConst obj) { + Runtime runtime(stateForContext(ctx)); + auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); + if (holder == nullptr || holder->hostObject == nullptr) { + *ptab = nullptr; + *plen = 0; + return 0; + } + auto names = holder->hostObject->getPropertyNames(runtime); + *plen = static_cast(names.size()); + *ptab = static_cast(js_mallocz(ctx, sizeof(JSPropertyEnum) * names.size())); + for (uint32_t i = 0; i < *plen; i++) { + (*ptab)[i].is_enumerable = true; + (*ptab)[i].atom = JS_NewAtom(ctx, names[i].utf8(runtime).c_str()); + } + return 0; +} + +static void nativeHostFinalize(JSRuntime*, JSValue value) { + auto* holder = static_cast(JS_GetOpaque(value, gHostClassId)); + if (holder != nullptr && holder->state != nullptr) { + holder->state->untrack(holder); + } + delete holder; +} + +static JSValue invokeFunctionHolder(JSContext* ctx, FunctionHolder* holder, JSValueConst thisValue, + int argc, JSValueConst* argv) { + Runtime runtime(stateForContext(ctx)); + if (holder == nullptr || !holder->callback) { + return JS_UNDEFINED; + } + StackValueArray args(static_cast(argc)); + for (int i = 0; i < argc; i++) { + args.emplace(static_cast(i), Value::borrowed(runtime, argv[i])); + } + try { + Value self = Value::borrowed(runtime, thisValue); + Value result = + holder->callback(runtime, self, args.size() == 0 ? nullptr : args.data(), args.size()); + return result.local(runtime); + } catch (const std::exception& error) { + return throwError(ctx, error); + } +} + +static JSValue nativeFunctionCall(JSContext* ctx, JSValue function, JSValue thisValue, int argc, + JSValue* argv, int) { + auto* holder = static_cast(JS_GetOpaque(function, gFunctionClassId)); + return invokeFunctionHolder(ctx, holder, thisValue, argc, argv); +} + +static JSValue nativeFunctionCallData(JSContext* ctx, JSValue thisValue, int argc, JSValue* argv, + int, JSValue* data) { + auto* holder = static_cast(JS_GetOpaque(data[0], gFunctionClassId)); + return invokeFunctionHolder(ctx, holder, thisValue, argc, argv); +} + +static void nativeFunctionFinalize(JSRuntime*, JSValue value) { + auto* holder = static_cast(JS_GetOpaque(value, gFunctionClassId)); + if (holder != nullptr && holder->state != nullptr) { + holder->state->untrack(holder); + } + delete holder; +} + +static JSClassExoticMethods hostExoticMethods = { + .get_own_property = nullptr, + .get_own_property_names = nativeHostOwnNames, + .delete_property = nullptr, + .define_own_property = nullptr, + .has_property = nativeHostHas, + .get_property = nativeHostGet, + .set_property = nativeHostSet, +}; + +void ensureClasses(Runtime& runtime) { + auto state = runtime.state(); + JSRuntime* rt = JS_GetRuntime(runtime.context()); + if (gHostClassId == 0) { + JS_NewClassID(rt, &gHostClassId); + } + if (!state->hostClassRegistered) { + JSClassDef def = {}; + def.class_name = "NativeScriptEngineHostObject"; + def.exotic = &hostExoticMethods; + def.finalizer = nativeHostFinalize; + JS_NewClass(rt, gHostClassId, &def); + JS_SetClassProto(runtime.context(), gHostClassId, JS_NewObject(runtime.context())); + state->hostClassRegistered = true; + } + if (gFunctionClassId == 0) { + JS_NewClassID(rt, &gFunctionClassId); + } + if (!state->functionClassRegistered) { + JSClassDef def = {}; + def.class_name = "NativeScriptEngineFunction"; + def.call = nativeFunctionCall; + def.finalizer = nativeFunctionFinalize; + JS_NewClass(rt, gFunctionClassId, &def); + JS_SetClassProto(runtime.context(), gFunctionClassId, JS_NewObject(runtime.context())); + state->functionClassRegistered = true; + } +} + +} // namespace quickjsengine + +quickjsengine::HostObjectHolder* Object::hostObjectHolder(Runtime& runtime) const { + quickjsengine::ensureClasses(runtime); + JSValue object = local(runtime); + auto* holder = static_cast( + JS_GetOpaque(object, quickjsengine::gHostClassId)); + JS_FreeValue(runtime.context(), object); + return holder; +} + +Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken) { + quickjsengine::ensureClasses(runtime); + auto* holder = new quickjsengine::HostObjectHolder(runtime.state(), std::move(host), typeToken); + JSValue object = JS_NewObjectClass(runtime.context(), quickjsengine::gHostClassId); + JS_SetOpaque(object, holder); + runtime.state()->track(holder, [](void* pointer) { + auto* tracked = static_cast(pointer); + tracked->state.reset(); + tracked->hostObject.reset(); + }); + Object result = Object::fromValueStorage(Value(runtime, object).storage_); + JS_FreeValue(runtime.context(), object); + return result; +} + +Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, + unsigned int parameterCount, HostFunctionType callback) { + quickjsengine::ensureClasses(runtime); + auto* holder = new quickjsengine::FunctionHolder(runtime.state(), std::move(callback)); + JSValue data = JS_NewObjectClass(runtime.context(), quickjsengine::gFunctionClassId); + if (JS_IsException(data)) { + delete holder; + throw JSError(runtime, "QuickJS host function data allocation failed."); + } + JS_SetOpaque(data, holder); + runtime.state()->track(holder, [](void* pointer) { + auto* tracked = static_cast(pointer); + tracked->state.reset(); + tracked->callback = {}; + }); + + JSValue function = JS_NewCFunctionData(runtime.context(), quickjsengine::nativeFunctionCallData, + static_cast(parameterCount), 0, 1, &data); + JS_FreeValue(runtime.context(), data); + if (JS_IsException(function)) { + throw JSError(runtime, "QuickJS host function allocation failed."); + } + + std::string functionName = name.utf8(runtime); + JSValue nameValue = JS_NewStringLen(runtime.context(), functionName.data(), functionName.size()); + JS_DefinePropertyValueStr(runtime.context(), function, "name", nameValue, JS_PROP_CONFIGURABLE); + Function result = Function(Object::fromValueStorage(Value(runtime, function).storage_)); + JS_FreeValue(runtime.context(), function); + return result; +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSMarshalling.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSMarshalling.mm new file mode 100644 index 000000000..91df50628 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSMarshalling.mm @@ -0,0 +1,478 @@ +// Included by NativeApiQuickJSSelectorGroups.mm inside the NativeScript anonymous namespace. + +std::string quickJSValueToUtf8(JSContext* context, JSValueConst value) { + size_t length = 0; + const char* text = JS_ToCStringLen(context, &length, value); + if (text == nullptr) { + return {}; + } + std::string result(text, length); + JS_FreeCString(context, text); + return result; +} + +bool quickJSNumberValue(JSContext* context, JSValueConst value, + double* result) { + if (result == nullptr) { + return false; + } + double converted = 0; + if (JS_ToFloat64(context, &converted, value) < 0) { + return false; + } + *result = converted; + return true; +} + +template +std::shared_ptr quickJSHostObject(Runtime& runtime, JSValueConst value) { + if (!JS_IsObject(value)) { + return nullptr; + } + engine::quickjsengine::ensureClasses(runtime); + auto* holder = static_cast( + JS_GetOpaque(value, engine::quickjsengine::gHostClassId)); + if (holder == nullptr || + holder->typeToken != engine::quickjsengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); +} + +template +T* quickJSHostObjectRaw(Runtime& runtime, JSValueConst value) { + if (!JS_IsObject(value)) { + return nullptr; + } + engine::quickjsengine::ensureClasses(runtime); + auto* holder = static_cast( + JS_GetOpaque(value, engine::quickjsengine::gHostClassId)); + if (holder == nullptr || + holder->typeToken != engine::quickjsengine::hostObjectTypeToken()) { + return nullptr; + } + return static_cast(holder->hostObject.get()); +} + +id quickJSNativeObjectArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, JSValueConst value, + NativeApiArgumentFrame& frame) { + JSContext* context = runtime.context(); + if (JS_IsNull(value) || JS_IsUndefined(value)) { + return nil; + } + if (JS_IsString(value)) { + std::string utf8 = quickJSValueToUtf8(context, value); + id string = type.kind == metagen::mdTypeNSMutableStringObject + ? [[NSMutableString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding] + : [[NSString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding]; + if (string != nil) { + frame.addObject(string); + } + return string; + } + if (JS_IsBool(value)) { + return [NSNumber numberWithBool:JS_ToBool(context, value) != 0]; + } + if (JS_IsNumber(value) || JS_IsBigInt(context, value)) { + double converted = 0; + if (quickJSNumberValue(context, value, &converted)) { + return [NSNumber numberWithDouble:converted]; + } + } + if (!JS_IsObject(value)) { + return nil; + } + if (auto objectHost = + quickJSHostObject(runtime, value)) { + return objectHost->object(); + } + if (auto classHost = + quickJSHostObject(runtime, value)) { + return static_cast(classHost->nativeClass()); + } + if (auto protocolHost = + quickJSHostObject(runtime, value)) { + return static_cast(protocolHost->nativeProtocol()); + } + if (auto pointerHost = + quickJSHostObject(runtime, value)) { + return static_cast(pointerHost->pointer()); + } + if (auto referenceHost = + quickJSHostObject(runtime, value)) { + return static_cast(referenceHost->data()); + } + if (auto structHost = + quickJSHostObject(runtime, value)) { + return static_cast(structHost->data()); + } + + JSValue wrappedClassValue = + JS_GetPropertyStr(context, value, "__nativeApiClass"); + if (!JS_IsException(wrappedClassValue)) { + if (auto classHost = quickJSHostObject( + runtime, wrappedClassValue)) { + JS_FreeValue(context, wrappedClassValue); + return static_cast(classHost->nativeClass()); + } + } + JS_FreeValue(context, wrappedClassValue); + + Value wrapped = Value::borrowed(runtime, value); + return objectFromEngineValue(runtime, bridge, wrapped, frame, + type.kind == + metagen::mdTypeNSMutableStringObject); +} + +Class quickJSNativeClassArgument(Runtime& runtime, JSValueConst value) { + if (JS_IsNull(value) || JS_IsUndefined(value)) { + return Nil; + } + if (auto classHost = + quickJSHostObject(runtime, value)) { + return classHost->nativeClass(); + } + if (JS_IsObject(value)) { + JSValue wrappedClassValue = + JS_GetPropertyStr(runtime.context(), value, "__nativeApiClass"); + if (!JS_IsException(wrappedClassValue)) { + if (auto classHost = quickJSHostObject( + runtime, wrappedClassValue)) { + JS_FreeValue(runtime.context(), wrappedClassValue); + return classHost->nativeClass(); + } + } + JS_FreeValue(runtime.context(), wrappedClassValue); + } + Value wrapped = Value::borrowed(runtime, value); + return classFromEngineValue(runtime, wrapped); +} + +bool readQuickJSEngineSelectorArgument(Runtime& runtime, JSValueConst value, + SEL* result) { + if (result == nullptr) { + return false; + } + if (JS_IsNull(value) || JS_IsUndefined(value)) { + *result = nullptr; + return true; + } + if (!JS_IsString(value)) { + return false; + } + std::string selectorName = quickJSValueToUtf8(runtime.context(), value); + *result = sel_registerName(selectorName.c_str()); + return true; +} + +template +bool writeQuickJSNumber(JSContext* context, JSValueConst value, void* target) { + double converted = 0; + if (!quickJSNumberValue(context, value, &converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; +} + +bool prepareQuickJSEngineArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, JSValueConst value, + NativeApiArgumentFrame& frame, size_t index) { + ffi_type* ffiType = ffiTypeForEngineArgument(type); + size_t size = + ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); + void* target = frame.storageAt(index, size); + JSContext* context = runtime.context(); + + switch (type.kind) { + case metagen::mdTypeBool: + if (!JS_IsBool(value)) { + return false; + } + *static_cast(target) = JS_ToBool(context, value) != 0 ? 1 : 0; + return true; + case metagen::mdTypeChar: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeSShort: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + if (JS_IsString(value)) { + std::string text = quickJSValueToUtf8(context, value); + if (text.size() != 1) { + return false; + } + *static_cast(target) = + static_cast(static_cast(text[0])); + return true; + } + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeSInt: { + int32_t converted = 0; + if (JS_ToInt32(context, &converted, value) < 0) { + return false; + } + *static_cast(target) = converted; + return true; + } + case metagen::mdTypeUInt: { + uint32_t converted = 0; + if (JS_ToUint32(context, &converted, value) < 0) { + return false; + } + *static_cast(target) = converted; + return true; + } + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: { + int64_t converted = 0; + if (JS_ToInt64Ext(context, &converted, value) < 0) { + return false; + } + *static_cast(target) = converted; + return true; + } + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: { + uint64_t converted = 0; + if (JS_IsBigInt(context, value)) { + if (JS_ToBigUint64(context, &converted, value) < 0) { + return false; + } + } else { + int64_t signedValue = 0; + if (JS_ToInt64Ext(context, &signedValue, value) < 0) { + return false; + } + converted = static_cast(signedValue); + } + *static_cast(target) = converted; + return true; + } + case metagen::mdTypeFloat: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeDouble: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeSelector: + return readQuickJSEngineSelectorArgument(runtime, value, + static_cast(target)); + case metagen::mdTypeClass: { + Class cls = quickJSNativeClassArgument(runtime, value); + if (cls == Nil) { + return false; + } + *static_cast(target) = cls; + return true; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + *static_cast(target) = + quickJSNativeObjectArgument(runtime, bridge, type, value, frame); + return true; + default: + break; + } + + Value wrapped = Value::borrowed(runtime, value); + convertEngineFfiArgument(runtime, bridge, type, wrapped, target, frame); + return true; +} + +JSValue quickJSInteger64Value(Runtime& runtime, int64_t value) { + constexpr int64_t maxSafeInteger = 9007199254740991LL; + constexpr int64_t minSafeInteger = -9007199254740991LL; + if (value >= minSafeInteger && value <= maxSafeInteger) { + return JS_NewFloat64(runtime.context(), static_cast(value)); + } + return JS_NewBigInt64(runtime.context(), value); +} + +JSValue quickJSUnsignedInteger64Value(Runtime& runtime, uint64_t value) { + constexpr uint64_t maxSafeInteger = 9007199254740991ULL; + if (value <= maxSafeInteger) { + return JS_NewFloat64(runtime.context(), static_cast(value)); + } + return JS_NewBigUint64(runtime.context(), value); +} + +JSValue setQuickJSEngineObjectReturn( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, id object) { + JSContext* context = runtime.context(); + if (object == nil) { + return JS_NULL; + } + Value roundTrip = + findCachedNativeObjectReturn(runtime, bridge, type, object); + if (!roundTrip.isUndefined()) { + JSValue result = roundTrip.local(runtime); + if (type.returnOwned) { + [object release]; + } + return result; + } + if (nativeObjectReturnMayCoerceToString(type) && + nativeObjectIsStringLike(object)) { + std::string utf8 = utf8StringFromNSString(static_cast(object)); + if (type.returnOwned) { + [object release]; + } + return JS_NewStringLen(context, utf8.data(), utf8.size()); + } + if ([object isKindOfClass:[NSNull class]]) { + if (type.returnOwned) { + [object release]; + } + return JS_NULL; + } + if ([object isKindOfClass:[NSNumber class]] && + ![object isKindOfClass:[NSDecimalNumber class]]) { + NSNumber* number = static_cast(object); + const char* objCType = [number objCType]; + bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == + CFBooleanGetTypeID() || + (objCType != nullptr && + std::strcmp(objCType, @encode(BOOL)) == 0); + JSValue result = isBool ? JS_NewBool(context, [number boolValue]) + : JS_NewFloat64(context, [number doubleValue]); + if (type.returnOwned) { + [object release]; + } + return result; + } + + if (const NativeApiSymbol* classSymbol = + bridge->findClassForRuntimePointer((void*)object)) { + Value result = makeNativeClassValue(runtime, bridge, *classSymbol); + if (type.returnOwned) { + [object release]; + } + return result.local(runtime); + } + if (const NativeApiSymbol* protocolSymbol = + bridge->findProtocolForRuntimePointer((void*)object)) { + Value result = makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + if (type.returnOwned) { + [object release]; + } + return result.local(runtime); + } + Value result = makeNativeObjectValue(runtime, bridge, object, type.returnOwned); + return result.local(runtime); +} + +JSValue setQuickJSEngineReturnValue( + Runtime& runtime, const std::shared_ptr& bridge, + NativeApiType type, void* value, const std::string& selectorName) { + JSContext* context = runtime.context(); + switch (type.kind) { + case metagen::mdTypeVoid: + return JS_UNDEFINED; + case metagen::mdTypeBool: + return JS_NewBool(context, *static_cast(value) != 0); + case metagen::mdTypeChar: + return JS_NewInt32(context, *static_cast(value)); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return JS_NewUint32(context, *static_cast(value)); + case metagen::mdTypeSShort: + return JS_NewInt32(context, *static_cast(value)); + case metagen::mdTypeUShort: + return JS_NewUint32(context, *static_cast(value)); + case metagen::mdTypeUnichar: { + const char16_t unit = *static_cast(value); + // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates + // fall back to U+FFFD). + char buffer[4] = {0}; + size_t length = 0; + if (unit < 0x80) { + buffer[length++] = static_cast(unit); + } else if (unit < 0x800) { + buffer[length++] = static_cast(0xC0 | (unit >> 6)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } else if (unit >= 0xD800 && unit <= 0xDFFF) { + buffer[length++] = static_cast(0xEF); + buffer[length++] = static_cast(0xBF); + buffer[length++] = static_cast(0xBD); + } else { + buffer[length++] = static_cast(0xE0 | (unit >> 12)); + buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } + return JS_NewStringLen(context, buffer, length); + } + case metagen::mdTypeSInt: + return JS_NewInt32(context, *static_cast(value)); + case metagen::mdTypeUInt: + return JS_NewUint32(context, *static_cast(value)); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return quickJSInteger64Value(runtime, *static_cast(value)); + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return quickJSUnsignedInteger64Value(runtime, + *static_cast(value)); + case metagen::mdTypeFloat: + return JS_NewFloat64(context, *static_cast(value)); + case metagen::mdTypeDouble: + return JS_NewFloat64(context, *static_cast(value)); + case metagen::mdTypeClass: { + Class cls = *static_cast(value); + if (cls == nil) { + return JS_NULL; + } + const char* name = class_getName(cls); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + return result.local(runtime); + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + if ((selectorName == "valueForKey:" || + selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(type)) { + type.kind = metagen::mdTypeAnyObject; + } + return setQuickJSEngineObjectReturn(runtime, bridge, type, + *static_cast(value)); + case metagen::mdTypeSelector: { + SEL selector = *static_cast(value); + const char* selectorNameValue = + selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorNameValue == nullptr) { + return JS_NULL; + } + return JS_NewString(context, selectorNameValue); + } + default: + break; + } + Value result = convertNativeReturnValue(runtime, bridge, type, value); + return result.local(runtime); +} diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h new file mode 100644 index 000000000..04f9620d4 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h @@ -0,0 +1,808 @@ +#ifndef NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H +#define NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H + +#ifdef TARGET_ENGINE_QUICKJS + +#import +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../shared/RuntimeCleanupRegistry.h" +#include "Metadata.h" +#include "MetadataReader.h" +#include "ffi.h" +#include "quickjs.h" + +@protocol NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class BigInt; +class ArrayBuffer; + +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} +}; + +class StringBuffer { + public: + explicit StringBuffer(std::string value) : value_(std::move(value)) {} + const char* data() const { return value_.data(); } + size_t size() const { return value_.size(); } + + private: + std::string value_; +}; + +class MutableBuffer { + public: + virtual ~MutableBuffer() = default; + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) : value_(std::move(value)) {} + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? value : ""); + } + static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } + std::string utf8(Runtime&) const { return value_; } + + private: + std::string value_; +}; + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); +}; + +using HostFunctionType = std::function; + +namespace quickjsengine { + +template +const void* hostObjectTypeToken() { + static int token = 0; + return &token; +} + +struct RuntimeState : RuntimeCleanupRegistry { + explicit RuntimeState(JSContext* context) : context(context) {} + JSContext* context = nullptr; + bool hostClassRegistered = false; + bool functionClassRegistered = false; + bool selectorGroupDataClassRegistered = false; +}; + +extern JSClassID gHostClassId; +extern JSClassID gFunctionClassId; + +std::shared_ptr stateForContext(JSContext* context); +void releaseStateForContext(JSContext* context); + +struct ValueStorage { + enum class Kind { + Undefined, + Null, + Bool, + Number, + QuickJS, + QuickJSBorrowed, + }; + + explicit ValueStorage(Kind kind) : kind(kind) {} + ~ValueStorage() { + if (kind == Kind::QuickJS && context != nullptr && !JS_IsUninitialized(value)) { + JS_FreeValue(context, value); + } + } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + JSContext* context = nullptr; + JSValue value = JS_UNINITIALIZED; +}; + +struct HostObjectHolder { + HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, + const void* typeToken) + : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} + std::shared_ptr state; + std::shared_ptr hostObject; + const void* typeToken = nullptr; +}; + +struct FunctionHolder { + FunctionHolder(std::shared_ptr state, HostFunctionType callback) + : state(std::move(state)), callback(std::move(callback)) {} + std::shared_ptr state; + HostFunctionType callback; +}; + +struct ArrayBufferHolder { + explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + std::shared_ptr buffer; +}; + +inline std::string valueToUtf8(JSContext* context, JSValueConst value) { + size_t length = 0; + const char* cString = JS_ToCStringLen(context, &length, value); + if (cString == nullptr) { + return {}; + } + std::string result(cString, length); + JS_FreeCString(context, cString); + return result; +} + +inline std::string currentExceptionMessage(JSContext* context) { + JSValue exception = JS_GetException(context); + std::string message = valueToUtf8(context, exception); + JS_FreeValue(context, exception); + return message.empty() ? std::string("QuickJS function call failed.") + : message; +} + +inline std::string atomToUtf8(JSContext* context, JSAtom atom) { + const char* cString = JS_AtomToCString(context, atom); + if (cString == nullptr) { + return {}; + } + std::string result(cString); + JS_FreeCString(context, cString); + return result; +} + +inline JSValue throwError(JSContext* context, const std::exception& error) { + return JS_ThrowTypeError(context, "%s", error.what()); +} + +void ensureClasses(Runtime& runtime); + +} // namespace quickjsengine + +class Runtime { + public: + explicit Runtime(JSContext* context) : state_(quickjsengine::stateForContext(context)) {} + explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} + JSContext* context() const { return state_->context; } + std::shared_ptr state() const { return state_; } + void detachState() { state_.reset(); } + Object global(); + Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); + void drainMicrotasks() { + JSContext* ctx = context(); + JSRuntime* rt = JS_GetRuntime(ctx); + JSContext* jobCtx = nullptr; + while (JS_ExecutePendingJob(rt, &jobCtx) > 0) { + } + } + + private: + std::shared_ptr state_; +}; + +class String { + public: + String() = default; + String(Runtime& runtime, JSValue value); + static String createFromUtf8(Runtime& runtime, const char* value) { + return String(runtime, JS_NewString(runtime.context(), value != nullptr ? value : "")); + } + static String createFromUtf8(Runtime& runtime, const std::string& value) { + return String(runtime, JS_NewStringLen(runtime.context(), value.data(), value.size())); + } + static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { + return String(runtime, + JS_NewStringLen(runtime.context(), reinterpret_cast(value), length)); + } + std::string utf8(Runtime& runtime) const; + JSValue local(Runtime& runtime) const; + operator Value() const; + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class Value { + public: + Value() : kind_(quickjsengine::ValueStorage::Kind::Undefined) {} + + Value(bool value) : kind_(quickjsengine::ValueStorage::Kind::Bool), boolValue_(value) {} + + Value(double value) : kind_(quickjsengine::ValueStorage::Kind::Number), numberValue_(value) {} + + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + Value(Runtime& runtime, const Value& value) { + if (value.kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed) { + // Promote borrowed to owned + storage_ = std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS); + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value.borrowedValue_); + kind_ = quickjsengine::ValueStorage::Kind::QuickJS; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + borrowedContext_ = value.borrowedContext_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + borrowedContext_(value.borrowedContext_), + borrowedValue_(value.borrowedValue_), + storage_(std::move(value.storage_)) {} + Value(Runtime& runtime, const String& value) : storage_(value.storage_) { + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; + } + Value(Runtime& runtime, const Object& object); + Value(Runtime& runtime, const Function& function); + Value(Runtime& runtime, const Array& array); + Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); + Value(Runtime& runtime, const BigInt& bigint); + Value(Runtime& runtime, JSValue value) + : kind_(quickjsengine::ValueStorage::Kind::QuickJS), + storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value); + } + + static Value borrowed(Runtime& runtime, JSValueConst value) { + Value result; + result.kind_ = quickjsengine::ValueStorage::Kind::QuickJSBorrowed; + result.borrowedContext_ = runtime.context(); + result.borrowedValue_ = value; + return result; + } + + static Value undefined() { return Value(); } + static Value null() { + Value value; + value.kind_ = quickjsengine::ValueStorage::Kind::Null; + return value; + } + static bool strictEquals(Runtime& runtime, const Value& lhs, + const Value& rhs) { + JSValue lhsValue = lhs.local(runtime); + JSValue rhsValue = rhs.local(runtime); + bool equal = JS_IsStrictEqual(runtime.context(), lhsValue, rhsValue); + JS_FreeValue(runtime.context(), lhsValue); + JS_FreeValue(runtime.context(), rhsValue); + return equal; + } + bool isUndefined() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Undefined) { + return true; + } + return isQuickJS() && JS_IsUndefined(jsValue()); + } + bool isNull() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Null) { + return true; + } + return isQuickJS() && JS_IsNull(jsValue()); + } + bool isBool() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Bool) { + return true; + } + return isQuickJS() && JS_IsBool(jsValue()); + } + bool getBool() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Bool) { + return boolValue_; + } + if (isQuickJS()) { + return JS_ToBool(jsContext(), jsValue()) != 0; + } + return false; + } + bool isNumber() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Number) { + return true; + } + return isQuickJS() && JS_IsNumber(jsValue()); + } + double getNumber() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Number) { + return numberValue_; + } + if (isQuickJS()) { + double value = 0; + JS_ToFloat64(jsContext(), &value, jsValue()); + return value; + } + return 0; + } + bool isObject() const { return isQuickJS() && JS_IsObject(jsValue()); } + bool isString() const { return isQuickJS() && JS_IsString(jsValue()); } + bool isBigInt() const { return isQuickJS() && JS_IsBigInt(jsContext(), jsValue()); } + bool isSymbol() const { return isQuickJS() && JS_IsSymbol(jsValue()); } + + Object asObject(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + JSValue local(Runtime& runtime) const { + switch (kind_) { + case quickjsengine::ValueStorage::Kind::Undefined: + return JS_UNDEFINED; + case quickjsengine::ValueStorage::Kind::Null: + return JS_NULL; + case quickjsengine::ValueStorage::Kind::Bool: + return JS_NewBool(runtime.context(), boolValue_); + case quickjsengine::ValueStorage::Kind::Number: + return JS_NewFloat64(runtime.context(), numberValue_); + case quickjsengine::ValueStorage::Kind::QuickJS: + case quickjsengine::ValueStorage::Kind::QuickJSBorrowed: + return JS_DupValue(runtime.context(), jsValue()); + } + } + + // Access the shared storage (for Object/Function/Array interop) + std::shared_ptr storage() const { return storage_; } + + static Value fromStorage(std::shared_ptr s) { + Value v; + v.kind_ = s->kind; + v.boolValue_ = s->boolValue; + v.numberValue_ = s->numberValue; + v.storage_ = std::move(s); + return v; + } + + private: + friend class Runtime; + friend class Object; + friend class String; + friend class BigInt; + friend class ArrayBuffer; + friend class Function; + friend class Array; + + bool isQuickJS() const { + return kind_ == quickjsengine::ValueStorage::Kind::QuickJS || + kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed; + } + JSContext* jsContext() const { + return kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed ? borrowedContext_ + : storage_->context; + } + JSValue jsValue() const { + return kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed ? borrowedValue_ + : storage_->value; + } + + quickjsengine::ValueStorage::Kind kind_ = quickjsengine::ValueStorage::Kind::Undefined; + bool boolValue_ = false; + double numberValue_ = 0; + JSContext* borrowedContext_ = nullptr; + JSValue borrowedValue_ = JS_UNINITIALIZED; + std::shared_ptr storage_; +}; + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_NewObject(runtime.context()); + } + static Object fromValueStorage(std::shared_ptr storage) { + Object object; + object.storage_ = std::move(storage); + return object; + } + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + quickjsengine::hostObjectTypeToken()); + } + + Value getProperty(Runtime& runtime, const char* name) const { + JSValue object = local(runtime); + JSValue result = JS_GetPropertyStr(runtime.context(), object, name != nullptr ? name : ""); + JS_FreeValue(runtime.context(), object); + if (JS_IsException(result)) { + throw JSError(runtime, "QuickJS property get failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + Value getProperty(Runtime& runtime, const Value& key) const { + JSValue object = local(runtime); + JSValue keyValue = key.local(runtime); + JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); + JS_FreeValue(runtime.context(), keyValue); + JSValue result = + atom == JS_ATOM_NULL ? JS_UNDEFINED : JS_GetProperty(runtime.context(), object, atom); + if (atom != JS_ATOM_NULL) { + JS_FreeAtom(runtime.context(), atom); + } + JS_FreeValue(runtime.context(), object); + if (JS_IsException(result)) { + throw JSError(runtime, "QuickJS property get failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Object getPropertyAsObject(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime); + } + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value) { + JSValue object = local(runtime); + JSValue localValue = value.local(runtime); + int status = + JS_SetPropertyStr(runtime.context(), object, name != nullptr ? name : "", localValue); + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw JSError(runtime, "QuickJS property set failed."); + } + } + void setProperty(Runtime& runtime, const char* name, const String& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Object& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Function& value); + void setProperty(Runtime& runtime, const char* name, const Array& value); + void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const std::string& name, const Value& value) { + setProperty(runtime, name.c_str(), value); + } + void setProperty(Runtime& runtime, const Value& key, const Value& value) { + JSValue object = local(runtime); + JSValue keyValue = key.local(runtime); + JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); + JS_FreeValue(runtime.context(), keyValue); + JSValue localValue = value.local(runtime); + int status = + atom == JS_ATOM_NULL ? -1 : JS_SetProperty(runtime.context(), object, atom, localValue); + if (atom != JS_ATOM_NULL) { + JS_FreeAtom(runtime.context(), atom); + } + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw JSError(runtime, "QuickJS property set failed."); + } + } + bool hasProperty(Runtime& runtime, const char* name) const { + JSValue object = local(runtime); + JSAtom atom = JS_NewAtom(runtime.context(), name != nullptr ? name : ""); + int result = JS_HasProperty(runtime.context(), object, atom); + JS_FreeAtom(runtime.context(), atom); + JS_FreeValue(runtime.context(), object); + return result > 0; + } + bool isFunction(Runtime& runtime) const { + JSValue object = local(runtime); + bool result = JS_IsFunction(runtime.context(), object); + JS_FreeValue(runtime.context(), object); + return result; + } + bool isArray(Runtime& runtime) const { + JSValue object = local(runtime); + int result = JS_IsArray(runtime.context(), object); + JS_FreeValue(runtime.context(), object); + return result > 0; + } + bool isArrayBuffer(Runtime& runtime) const { + JSValue object = local(runtime); + bool result = JS_IsArrayBuffer(object); + JS_FreeValue(runtime.context(), object); + return result; + } + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + return holder != nullptr && holder->typeToken == quickjsengine::hostObjectTypeToken(); + } + template + std::shared_ptr getHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + if (holder == nullptr || holder->typeToken != quickjsengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); + } + JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } + operator Value() const { return Value::fromStorage(storage_); } + + protected: + friend class Value; + friend class Runtime; + friend class Function; + friend class Array; + friend class ArrayBuffer; + explicit Object(std::shared_ptr storage) + : storage_(std::move(storage)) {} + static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + quickjsengine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const; + std::shared_ptr storage_; +}; + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object.storage_)) {} + static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback); + Value call(Runtime& runtime, const Value* args, size_t count) const { + JSValue function = local(runtime); + JSValue global = JS_GetGlobalObject(runtime.context()); + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValue result = JS_Call(runtime.context(), function, global, static_cast(argv.size()), + argv.empty() ? nullptr : argv.data()); + for (auto& arg : argv) { + JS_FreeValue(runtime.context(), arg); + } + JS_FreeValue(runtime.context(), global); + JS_FreeValue(runtime.context(), function); + if (JS_IsException(result)) { + throw JSError(runtime, quickjsengine::currentExceptionMessage(runtime.context())); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value call(Runtime& runtime) const { + return call(runtime, static_cast(nullptr), 0); + } + Value call(Runtime& runtime, std::nullptr_t, size_t) const { + return call(runtime, static_cast(nullptr), 0); + } + template + Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { + return call(runtime, static_cast(args), count); + } + template + Value call(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return call(runtime, static_cast(argv), sizeof...(Args)); + } + Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, + size_t count = 0) const { + JSValue function = local(runtime); + JSValue thisValue = thisObject.local(runtime); + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValue result = JS_Call(runtime.context(), function, thisValue, static_cast(argv.size()), + argv.empty() ? nullptr : argv.data()); + for (auto& arg : argv) { + JS_FreeValue(runtime.context(), arg); + } + JS_FreeValue(runtime.context(), thisValue); + JS_FreeValue(runtime.context(), function); + if (JS_IsException(result)) { + throw JSError(runtime, quickjsengine::currentExceptionMessage(runtime.context())); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { + JSValue function = local(runtime); + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValue result = JS_CallConstructor(runtime.context(), function, static_cast(argv.size()), + argv.empty() ? nullptr : argv.data()); + for (auto& arg : argv) { + JS_FreeValue(runtime.context(), arg); + } + JS_FreeValue(runtime.context(), function); + if (JS_IsException(result)) { + throw JSError(runtime, "QuickJS constructor call failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { + return callAsConstructor(runtime, static_cast(nullptr), 0); + } + template + Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { + return callAsConstructor(runtime, static_cast(args), count); + } + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); + } +}; + +class Array : public Object { + public: + explicit Array(Runtime& runtime, size_t size) + : Object(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_NewArray(runtime.context()); + JS_SetPropertyStr(runtime.context(), storage_->value, "length", + JS_NewUint32(runtime.context(), static_cast(size))); + } + explicit Array(Object object) : Object(std::move(object.storage_)) {} + size_t size(Runtime& runtime) const { + Value length = getProperty(runtime, "length"); + return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; + } + Value getValueAtIndex(Runtime& runtime, size_t index) const { + JSValue object = local(runtime); + JSValue result = JS_GetPropertyUint32(runtime.context(), object, static_cast(index)); + JS_FreeValue(runtime.context(), object); + if (JS_IsException(result)) { + throw JSError(runtime, "QuickJS array get failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { + JSValue object = local(runtime); + JSValue localValue = value.local(runtime); + int status = + JS_SetPropertyUint32(runtime.context(), object, static_cast(index), localValue); + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw JSError(runtime, "QuickJS array set failed."); + } + } + void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { + setValueAtIndex(runtime, index, Value(runtime, value)); + } +}; + +class BigInt { + public: + BigInt() = default; + BigInt(Runtime& runtime, JSValue value) + : storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value); + } + static BigInt fromInt64(Runtime& runtime, int64_t value) { + JSValue result = JS_NewBigInt64(runtime.context(), value); + BigInt bigint(runtime, result); + JS_FreeValue(runtime.context(), result); + return bigint; + } + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + JSValue result = JS_NewBigUint64(runtime.context(), value); + BigInt bigint(runtime, result); + JS_FreeValue(runtime.context(), result); + return bigint; + } + String toString(Runtime& runtime, int) const; + JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } + operator Value() const { return Value::fromStorage(storage_); } + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : Object(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + auto* holder = new quickjsengine::ArrayBufferHolder(std::move(buffer)); + storage_->context = runtime.context(); + storage_->value = JS_NewArrayBuffer( + runtime.context(), holder->buffer->data(), holder->buffer->size(), + [](JSRuntime*, void* opaque, void*) { + delete static_cast(opaque); + }, + holder, false); + } + explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} + size_t size(Runtime& runtime) const { + JSValue object = local(runtime); + size_t size = 0; + JS_GetArrayBuffer(runtime.context(), &size, object); + JS_FreeValue(runtime.context(), object); + return size; + } + uint8_t* data(Runtime& runtime) const { + JSValue object = local(runtime); + size_t size = 0; + uint8_t* data = JS_GetArrayBuffer(runtime.context(), &size, object); + JS_FreeValue(runtime.context(), object); + return data; + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS + +#endif // NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.mm similarity index 92% rename from NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.mm rename to NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.mm index 6a64b8e57..d38eb3ac6 100644 --- a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.mm +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.mm @@ -2,8 +2,8 @@ #ifdef TARGET_ENGINE_QUICKJS -namespace facebook { -namespace jsi { +namespace nativescript { +namespace engine { String BigInt::toString(Runtime& runtime, int) const { JSValue value = local(runtime); @@ -34,7 +34,7 @@ return value; } -} // namespace jsi -} // namespace facebook +} // namespace engine +} // namespace nativescript #endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntimeSupport.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntimeSupport.mm new file mode 100644 index 000000000..91bc9eb12 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntimeSupport.mm @@ -0,0 +1,119 @@ +// Included by NativeApiQuickJS.mm inside the NativeScript anonymous namespace. + +static JSValue NativeApiLazyGlobalGetter(JSContext* context, JSValueConst, int, + JSValueConst*, int, JSValueConst* data) { + JSValue global = JS_GetGlobalObject(context); + JSValue resolver = JS_GetPropertyStr(context, global, "__nativeScriptResolveNativeApiLazyGlobal"); + if (!JS_IsFunction(context, resolver)) { + JS_FreeValue(context, resolver); + JS_FreeValue(context, global); + return JS_UNDEFINED; + } + + JSValueConst args[] = {data[0], data[1]}; + JSValue result = JS_Call(context, resolver, global, 2, args); + JS_FreeValue(context, resolver); + if (JS_IsException(result)) { + JS_FreeValue(context, global); + return result; + } + + JSAtom atom = JS_ValueToAtom(context, data[0]); + if (atom != JS_ATOM_NULL) { + JS_DefinePropertyValue(context, global, atom, JS_DupValue(context, result), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + JS_FreeAtom(context, atom); + } + JS_FreeValue(context, global); + return result; +} + +// Assigning over a lazy global must behave like a plain global assignment +// (@nativescript/core writes shims such as global.System); replace the +// accessor with an ordinary writable property instead of throwing +// "no setter for property". +static JSValue NativeApiLazyGlobalSetter(JSContext* context, JSValueConst, int argc, + JSValueConst* argv, int, JSValueConst* data) { + JSValue global = JS_GetGlobalObject(context); + JSAtom atom = JS_ValueToAtom(context, data[0]); + if (atom != JS_ATOM_NULL) { + JSValue value = argc > 0 ? JS_DupValue(context, argv[0]) : JS_UNDEFINED; + JS_DefinePropertyValue(context, global, atom, value, JS_PROP_C_W_E); + JS_FreeAtom(context, atom); + } + JS_FreeValue(context, global); + return JS_UNDEFINED; +} + +bool InstallNativeApiLazyGlobal(Runtime& runtime, std::shared_ptr, + const std::string& name, const std::string& kind, + bool force) { + if (name.empty() || kind.empty()) { + return false; + } + + JSContext* context = runtime.context(); + JSValue global = JS_GetGlobalObject(context); + JSAtom atom = JS_NewAtomLen(context, name.data(), name.size()); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(context, global); + return false; + } + + int hasProperty = JS_HasProperty(context, global, atom); + if (!force && hasProperty > 0) { + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return false; + } + if (hasProperty < 0) { + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return false; + } + + JSValue data[] = { + JS_NewStringLen(context, name.data(), name.size()), + JS_NewStringLen(context, kind.data(), kind.size()), + }; + if (JS_IsException(data[0]) || JS_IsException(data[1])) { + JS_FreeValue(context, data[0]); + JS_FreeValue(context, data[1]); + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return false; + } + + JSValue getter = JS_NewCFunctionData(context, NativeApiLazyGlobalGetter, 0, 0, 2, data); + JSValue setter = JS_NewCFunctionData(context, NativeApiLazyGlobalSetter, 1, 0, 2, data); + JS_FreeValue(context, data[0]); + JS_FreeValue(context, data[1]); + if (JS_IsException(getter) || JS_IsException(setter)) { + JS_FreeValue(context, getter); + JS_FreeValue(context, setter); + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return false; + } + + int status = JS_DefinePropertyGetSet(context, global, atom, getter, setter, JS_PROP_CONFIGURABLE); + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return status >= 0; +} + +void SetNativeApiObjectPrototype(Runtime& runtime, Object& object, + const Object& prototype) { + JSValue objectValue = object.local(runtime); + JSValue prototypeValue = prototype.local(runtime); + int status = JS_SetPrototype(runtime.context(), objectValue, prototypeValue); + JS_FreeValue(runtime.context(), prototypeValue); + JS_FreeValue(runtime.context(), objectValue); + if (status < 0) { + throw JSError(runtime, "QuickJS prototype assignment failed."); + } +} + +std::shared_ptr retainNativeApiRuntime(Runtime& runtime) { + return std::make_shared(runtime.state()); +} diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSSelectorGroups.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSSelectorGroups.mm new file mode 100644 index 000000000..d6a621966 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSSelectorGroups.mm @@ -0,0 +1,328 @@ +// Included by NativeApiQuickJS.mm inside the NativeScript anonymous namespace. + +#include "../shared/bridge/SelectorGroupData.h" + +#include "NativeApiQuickJSMarshalling.mm" + +#include "NativeApiQuickJSGsd.mm" + +#include "../shared/bridge/SelectorGroupCall.h" + + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId) { + return reinterpret_cast(lookupObjCGsdInvoker(dispatchId)); +} + +bool tryCallGeneratedEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result) { + if (result == nullptr || receiver == nil || + !prepared.gsdEngineCallable || dispatchSuperClass != Nil || + count != prepared.gsdEngineArgumentCount) { + return false; + } + + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, + runtime.context(), nullptr, + prepared.signature.returnType}; + ctx.valueArguments = args; + ctx.materializeValueResult = true; + if (!invoker(ctx)) { + return false; + } + *result = std::move(ctx.valueResult); + return true; +} + +JSValue setQuickJSEnginePreparedObjCResult( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const std::shared_ptr& receiverHostObject, + const std::optional& initializerClassWrapper, + size_t providedCount, JSValueConst arguments[], + Class dispatchSuperClass) { + const NativeApiSignature& signature = prepared.signature; + if (receiver == nil || signature.variadic || + unsupportedEngineType(signature.returnType)) { + throw JSError(runtime, + "Objective-C selector is not supported by QuickJS engine: " + + prepared.selectorName); + } + + const bool isNSErrorOutMethod = prepared.isNSErrorOutMethod; + if (isNSErrorOutMethod) { + size_t expected = signature.argumentTypes.size(); + if (providedCount > expected || providedCount + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } else if (providedCount != signature.argumentTypes.size()) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + + std::to_string(signature.argumentTypes.size()) + "\"."); + } + + // GSD fast path: the generated invoker reads args directly from the QuickJS + // arguments, calls objc_msgSend with a typed cast, and produces the JS + // return value — bypassing all generic marshalling. Excludes appearance + // static selectors — those need the generic path's proxy tagging. + if (prepared.gsdEngineCallable && dispatchSuperClass == Nil && + providedCount == prepared.gsdEngineArgumentCount && + !initializerClassWrapper && !isNSErrorOutMethod && + !isPreparedStaticAppearanceSelector(prepared)) { + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, + runtime.context(), arguments, signature.returnType}; + if (invoker(ctx)) { + if (providedCount > 0) { + Value setterValue = Value::borrowed(runtime, arguments[0]); + cachePreparedAppearanceProxySetterValue(runtime, bridge, receiver, + prepared, &setterValue, 1); + } + return ctx.result; + } + } + + if (dispatchSuperClass == Nil && !initializerClassWrapper && + providedCount <= 2) { + Value fastArgs[2]; + for (size_t i = 0; i < providedCount; i++) { + fastArgs[i] = Value::borrowed(runtime, arguments[i]); + } + Value fastResult; + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, prepared, + fastArgs, providedCount, Nil, + &fastResult)) { + cachePreparedAppearanceProxySetterValue(runtime, bridge, receiver, + prepared, fastArgs, + providedCount); + fastResult = tagPreparedStaticAppearanceSelectorResult( + runtime, bridge, receiver, prepared, std::move(fastResult)); + return fastResult.local(runtime); + } + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + for (size_t i = 0; i < providedCount; i++) { + if (!prepareQuickJSEngineArgument(runtime, bridge, + signature.argumentTypes[i], + arguments[i], frame, i)) { + throw JSError(runtime, + "Objective-C argument is not supported by QuickJS engine: " + + prepared.selectorName); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && providedCount + 1 == signature.argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + size_t outArgIndex = signature.argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } + + NativeApiPointerFrame values(signature.argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, const_cast(&prepared.selector)); + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared.preparedInvoker != nullptr && dispatchSuperClass == Nil) { + prepared.preparedInvoker(reinterpret_cast(objc_msgSend), + values.data(), returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature.returnType.ffiType->size > 16 && + signature.returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(const_cast(&signature.cif), target, + returnStorage.data(), values.data()); +#else + ffi_call(const_cast(&signature.cif), + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature.returnType; + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + if (providedCount > 0) { + Value setterValue = Value::borrowed(runtime, arguments[0]); + cachePreparedAppearanceProxySetterValue(runtime, bridge, receiver, + prepared, &setterValue, 1); + } + if (initializerClassWrapper) { + id resultObject = nil; + if (isObjectiveCObjectType(returnType)) { + resultObject = *static_cast(returnStorage.data()); + } + if (receiverHostObject != nullptr && resultObject != receiver) { + receiverHostObject->disownObject(receiver); + } + if (resultObject != nil) { + bridge->setObjectExpando(runtime, resultObject, + "__nativeApiClassWrapper", + Value(runtime, *initializerClassWrapper)); + } + } + tagPreparedStaticAppearanceNativeReturn( + runtime, bridge, receiver, prepared, returnType, returnStorage.data()); + return setQuickJSEngineReturnValue(runtime, bridge, returnType, + returnStorage.data(), + prepared.selectorName); +} + +static JSClassID gNativeApiSelectorGroupDataClassId = 0; + +void NativeApiSelectorGroupFinalize(JSRuntime*, JSValue value) { + auto* data = static_cast( + JS_GetOpaque(value, gNativeApiSelectorGroupDataClassId)); + if (data != nullptr && data->runtime.state() != nullptr) { + data->runtime.state()->untrack(data); + } + delete data; +} + +void EnsureNativeApiSelectorGroupClass(Runtime& runtime) { + JSRuntime* jsRuntime = JS_GetRuntime(runtime.context()); + if (gNativeApiSelectorGroupDataClassId == 0) { + JS_NewClassID(jsRuntime, &gNativeApiSelectorGroupDataClassId); + } + + auto state = runtime.state(); + if (!state->selectorGroupDataClassRegistered) { + JSClassDef definition = {}; + definition.class_name = "NativeScriptEngineSelectorGroupData"; + definition.finalizer = NativeApiSelectorGroupFinalize; + JS_NewClass(jsRuntime, gNativeApiSelectorGroupDataClassId, + &definition); + state->selectorGroupDataClassRegistered = true; + } +} + +JSValue NativeApiSelectorGroupCall(JSContext* context, JSValue thisValue, + int argc, JSValue* argv, int, + JSValue* dataValues) { + auto* data = static_cast( + JS_GetOpaque(dataValues[0], gNativeApiSelectorGroupDataClassId)); + if (data == nullptr || data->selectors == nullptr || + data->preparedInvocations == nullptr) { + return JS_UNDEFINED; + } + + Runtime& runtime = data->runtime; + try { + NativeApiRoundTripCacheFrameGuard roundTripFrame(data->bridge); + size_t count = argc > 0 ? static_cast(argc) : 0; + auto call = resolveNativeApiSelectorGroupCall( + runtime, *data, count, + [&]() -> id { + auto* host = quickJSHostObjectRaw( + runtime, thisValue); + return host != nullptr ? host->object() : nil; + }, + [&]() { + return data->boundReceiverState == nullptr + ? quickJSHostObject(runtime, + thisValue) + : nullptr; + }, + [](uint64_t dispatchId) { return lookupObjCGsdInvoker(dispatchId); }); + if (call.hasImmediateResult) { + return call.immediateResult.local(runtime); + } + JSValue result = setQuickJSEnginePreparedObjCResult( + runtime, data->bridge, call.receiver, *call.prepared, + call.receiverHostObject, call.initializerClassWrapper, count, argv, + call.dispatchClass); + if (!data->receiverIsClass && call.prepared->isInitMethod) { + if (auto preserved = preservedNativeApiInitializerSelfReturn( + runtime, data->bridge, call.receiver, Value::borrowed(runtime, result), + Value::borrowed(runtime, thisValue))) { + JS_FreeValue(context, result); + return preserved->local(runtime); + } + } + return result; + } catch (const std::exception& error) { + return engine::quickjsengine::throwError(context, error); + } +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState) { + EnsureNativeApiSelectorGroupClass(runtime); + auto* data = new NativeApiSelectorGroupData( + runtime.state(), std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), + std::move(boundReceiver), std::move(boundReceiverState)); + + JSValue dataObject = + JS_NewObjectClass(runtime.context(), + gNativeApiSelectorGroupDataClassId); + if (JS_IsException(dataObject)) { + delete data; + throw JSError(runtime, "QuickJS selector group allocation failed."); + } + JS_SetOpaque(dataObject, data); + runtime.state()->track(data, [](void* pointer) { + auto* tracked = static_cast(pointer); + tracked->runtime.detachState(); + tracked->bridge.reset(); + tracked->selectors.reset(); + tracked->preparedInvocations.reset(); + tracked->boundReceiver.reset(); + tracked->boundReceiverState.reset(); + }); + + JSValue function = + JS_NewCFunctionData(runtime.context(), NativeApiSelectorGroupCall, + 0, 0, 1, &dataObject); + JS_FreeValue(runtime.context(), dataObject); + if (JS_IsException(function)) { + throw JSError(runtime, "QuickJS selector group function allocation failed."); + } + + JSValue nameValue = JS_NewStringLen(runtime.context(), "__nativeSelectorGroup", + std::strlen("__nativeSelectorGroup")); + JS_DefinePropertyValueStr(runtime.context(), function, "name", nameValue, + JS_PROP_CONFIGURABLE); + Value functionValue(runtime, function); + Function result = functionValue.asObject(runtime).asFunction(runtime); + JS_FreeValue(runtime.context(), function); + return result; +} diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSValue.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSValue.mm new file mode 100644 index 000000000..bb6852019 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSValue.mm @@ -0,0 +1,111 @@ +#include "NativeApiQuickJSRuntime.h" + +#ifdef TARGET_ENGINE_QUICKJS + +namespace nativescript { +namespace engine { + +Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } +bool HostObject::set(Runtime&, const PropNameID&, const Value&) { return true; } +std::vector HostObject::getPropertyNames(Runtime&) { return {}; } +String::String(Runtime& runtime, JSValue value) + : storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value); +} +std::string String::utf8(Runtime& runtime) const { + JSValue value = local(runtime); + std::string result = quickjsengine::valueToUtf8(runtime.context(), value); + JS_FreeValue(runtime.context(), value); + return result; +} +JSValue String::local(Runtime& runtime) const { + return JS_DupValue(runtime.context(), storage_->value); +} +String::operator Value() const { return Value::fromStorage(storage_); } +Value::Value(Runtime&, const Object& object) { + storage_ = object.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Function& function) { + storage_ = function.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Array& array) { + storage_ = array.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) { + storage_ = arrayBuffer.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const BigInt& bigint) { + storage_ = bigint.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Object Value::asObject(Runtime& runtime) const { + if (storage_) { + return Object::fromValueStorage(storage_); + } + // Promote to owned storage for Object. + auto s = std::make_shared(kind_); + if (kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed) { + s->kind = quickjsengine::ValueStorage::Kind::QuickJS; + s->context = borrowedContext_; + s->value = JS_DupValue(borrowedContext_, borrowedValue_); + } + return Object::fromValueStorage(std::move(s)); +} +String Value::asString(Runtime& runtime) const { + JSValue value = local(runtime); + String result(runtime, value); + JS_FreeValue(runtime.context(), value); + return result; +} +BigInt Value::getBigInt(Runtime& runtime) const { + JSValue value = local(runtime); + BigInt result(runtime, value); + JS_FreeValue(runtime.context(), value); + return result; +} +Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime).asFunction(runtime); +} +Function Object::asFunction(Runtime&) const { return Function(*this); } +Array Object::getArray(Runtime&) const { return Array(*this); } +ArrayBuffer Object::getArrayBuffer(Runtime&) const { return ArrayBuffer(*this); } +Array Object::getPropertyNames(Runtime& runtime) const { + JSValue object = local(runtime); + JSPropertyEnum* properties = nullptr; + uint32_t count = 0; + int status = JS_GetOwnPropertyNames(runtime.context(), &properties, &count, object, + JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY); + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw JSError(runtime, "QuickJS property names failed."); + } + Array result(runtime, count); + for (uint32_t i = 0; i < count; i++) { + JSValue nameValue = JS_AtomToValue(runtime.context(), properties[i].atom); + result.setValueAtIndex(runtime, i, Value(runtime, nameValue)); + JS_FreeValue(runtime.context(), nameValue); + JS_FreeAtom(runtime.context(), properties[i].atom); + } + js_free(runtime.context(), properties); + return result; +} +void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { + setProperty(runtime, name, Value(runtime, value)); +} +void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { + setProperty(runtime, name, Value(runtime, value)); +} +void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/objc/quickjs/SignatureDispatch.h b/NativeScript/ffi/objc/quickjs/SignatureDispatch.h new file mode 100644 index 000000000..7151eb22e --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/SignatureDispatch.h @@ -0,0 +1,14 @@ +#ifndef NATIVESCRIPT_FFI_QUICKJS_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_FFI_QUICKJS_SIGNATURE_DISPATCH_H + +#include "ffi/objc/shared/SignatureDispatchCore.h" + +#if defined(__has_include) +#if __has_include("../shared/GeneratedSignatureDispatch.inc") +#include "../shared/GeneratedSignatureDispatch.inc" +#endif +#endif + +#include "ffi/objc/shared/PreparedSignatureDispatch.h" + +#endif // NATIVESCRIPT_FFI_QUICKJS_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/shared/direct/EmbeddedMetadata.mm b/NativeScript/ffi/objc/shared/MetadataState.mm similarity index 100% rename from NativeScript/ffi/shared/direct/EmbeddedMetadata.mm rename to NativeScript/ffi/objc/shared/MetadataState.mm diff --git a/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h b/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h new file mode 100644 index 000000000..859526f05 --- /dev/null +++ b/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h @@ -0,0 +1,34 @@ +#ifndef NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H +#define NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H + +#include +#include + +namespace nativescript { + +class NativeApiBackendScheduler { + public: + virtual ~NativeApiBackendScheduler() = default; + virtual void invokeOnJS(std::function task) = 0; + virtual void invokeOnUI(std::function task) = 0; +}; + +struct NativeApiBackendConfig { + const char* metadataPath = nullptr; + const void* metadataPtr = nullptr; + const char* globalName = "__nativeScriptNativeApi"; + std::shared_ptr scheduler = nullptr; + std::function)> nativeInvocationInvoker = nullptr; + std::function)> nativeCallbackInvoker = nullptr; + std::function)> runtimeCallbackInvoker = nullptr; + std::function)> jsThreadCallbackInvoker = nullptr; + std::function)> jsThreadAsyncCallbackInvoker = nullptr; + std::function callbackInvocationAllowed = nullptr; + bool invokeCallbacksOnNativeCallerThread = false; + bool installGlobalSymbols = false; + bool indexRuntimePointers = true; +}; + +} // namespace nativescript + +#endif // NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H diff --git a/NativeScript/ffi/objc/shared/NativeApiStackValueArray.h b/NativeScript/ffi/objc/shared/NativeApiStackValueArray.h new file mode 100644 index 000000000..b91091ddd --- /dev/null +++ b/NativeScript/ffi/objc/shared/NativeApiStackValueArray.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace nativescript::engine { + +template +class StackValueArray { + public: + explicit StackValueArray(size_t count) : count_(count) { + values_ = + count_ > InlineCount + ? static_cast( + ::operator new(sizeof(ValueType) * count_)) + : reinterpret_cast(inlineStorage_); + } + + ~StackValueArray() { + for (size_t i = 0; i < constructed_; i++) { + values_[i].~ValueType(); + } + if (count_ > InlineCount) { + ::operator delete(values_); + } + } + + StackValueArray(const StackValueArray&) = delete; + StackValueArray& operator=(const StackValueArray&) = delete; + + void emplace(size_t index, ValueType&& value) { + new (&values_[index]) ValueType(std::move(value)); + constructed_++; + } + + ValueType* data() { return count_ == 0 ? nullptr : values_; } + size_t size() const { return count_; } + + private: + size_t count_ = 0; + size_t constructed_ = 0; + ValueType* values_ = nullptr; + alignas(ValueType) unsigned char inlineStorage_[sizeof(ValueType) * InlineCount]; +}; + +} // namespace nativescript::engine diff --git a/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h b/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h new file mode 100644 index 000000000..c941006fe --- /dev/null +++ b/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h @@ -0,0 +1,80 @@ +#ifndef NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H + +#include "SignatureDispatchCore.h" + +#ifndef NS_GSD_BACKEND_PREPARED +#define NS_GSD_BACKEND_PREPARED 0 +#endif + +#ifndef NS_GSD_BACKEND_HERMES +#define NS_GSD_BACKEND_HERMES 0 +#endif + +#ifndef NS_GSD_BACKEND_NAPI +#define NS_GSD_BACKEND_NAPI 0 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 +#endif + +#define NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH \ + (NS_GSD_BACKEND_HERMES || NS_GSD_BACKEND_NAPI || NS_GSD_BACKEND_PREPARED) + +#if NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH && \ + !NS_HAS_GENERATED_SIGNATURE_DISPATCH +#error GeneratedSignatureDispatch.inc did not enable this generated signature dispatch backend. +#endif + +#if !NS_HAS_GENERATED_SIGNATURE_DISPATCH +namespace nativescript { +inline constexpr ObjCDispatchEntry kGeneratedObjCDispatchEntries[] = { + {0, nullptr}}; +inline constexpr CFunctionDispatchEntry kGeneratedCFunctionDispatchEntries[] = { + {0, nullptr}}; +inline constexpr BlockDispatchEntry kGeneratedBlockDispatchEntries[] = { + {0, nullptr}}; +} // namespace nativescript +#endif + +namespace nativescript { + +inline ObjCPreparedInvoker lookupObjCPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCDispatchEntries, dispatchId); +} + +inline CFunctionPreparedInvoker lookupCFunctionPreparedInvoker( + uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedCFunctionDispatchEntries, dispatchId); +} + +inline BlockPreparedInvoker lookupBlockPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedBlockDispatchEntries, dispatchId); +} + +inline bool isPreparedGeneratedDispatchRequired() { +#if NS_HAS_GENERATED_SIGNATURE_DISPATCH && \ + (NS_GSD_BACKEND_PREPARED || NS_GSD_BACKEND_HERMES) + return isGeneratedDispatchEnabled(); +#else + return false; +#endif +} + +} // namespace nativescript + +#endif // NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/objc/shared/RuntimeCleanupRegistry.h b/NativeScript/ffi/objc/shared/RuntimeCleanupRegistry.h new file mode 100644 index 000000000..d30928173 --- /dev/null +++ b/NativeScript/ffi/objc/shared/RuntimeCleanupRegistry.h @@ -0,0 +1,39 @@ +#ifndef NATIVESCRIPT_FFI_OBJC_RUNTIME_CLEANUP_REGISTRY_H +#define NATIVESCRIPT_FFI_OBJC_RUNTIME_CLEANUP_REGISTRY_H + +#include +#include + +namespace nativescript::engine { + +class RuntimeCleanupRegistry { + public: + using Cleanup = void (*)(void*); + + void track(void* pointer, Cleanup cleanup) { actions_[pointer] = cleanup; } + + template + void track(T* pointer) { + track(pointer, [](void* value) { delete static_cast(value); }); + } + + void untrack(void* pointer) { actions_.erase(pointer); } + bool empty() const { return actions_.empty(); } + + void cleanup() { + while (!actions_.empty()) { + auto action = actions_.begin(); + void* pointer = action->first; + Cleanup cleanup = action->second; + actions_.erase(action); + cleanup(pointer); + } + } + + private: + std::unordered_map actions_; +}; + +} // namespace nativescript::engine + +#endif // NATIVESCRIPT_FFI_OBJC_RUNTIME_CLEANUP_REGISTRY_H diff --git a/NativeScript/ffi/objc/shared/SignatureDispatchCore.h b/NativeScript/ffi/objc/shared/SignatureDispatchCore.h new file mode 100644 index 000000000..229347a74 --- /dev/null +++ b/NativeScript/ffi/objc/shared/SignatureDispatchCore.h @@ -0,0 +1,298 @@ +#ifndef NS_FFI_SHARED_SIGNATURE_DISPATCH_CORE_H +#define NS_FFI_SHARED_SIGNATURE_DISPATCH_CORE_H + +#include +#include +#include +#include +#include + +#include "Metadata.h" +#include "MetadataReader.h" + +namespace nativescript { + +enum class SignatureCallKind : uint8_t { + ObjCMethod = 1, + CFunction = 2, + BlockInvoke = 3, +}; + +using ObjCPreparedInvoker = void (*)(void* fnptr, void** avalues, + void* rvalue); +using CFunctionPreparedInvoker = void (*)(void* fnptr, void** avalues, + void* rvalue); +using BlockPreparedInvoker = void (*)(void* fnptr, void** avalues, + void* rvalue); + +struct ObjCDispatchEntry { + uint64_t dispatchId; + ObjCPreparedInvoker invoker; +}; + +struct CFunctionDispatchEntry { + uint64_t dispatchId; + CFunctionPreparedInvoker invoker; +}; + +struct BlockDispatchEntry { + uint64_t dispatchId; + BlockPreparedInvoker invoker; +}; + +inline constexpr uint64_t kSignatureHashOffsetBasis = 14695981039346656037ull; +inline constexpr uint64_t kSignatureHashPrime = 1099511628211ull; +inline constexpr metagen::MDSectionOffset kNullMetadataSectionOffset = + static_cast(0xFFFFFFFFu >> 1); + +inline uint64_t hashBytesFnv1a(const void* data, size_t size, + uint64_t seed = kSignatureHashOffsetBasis) { + const auto* bytes = static_cast(data); + uint64_t hash = seed; + for (size_t i = 0; i < size; i++) { + hash ^= static_cast(bytes[i]); + hash *= kSignatureHashPrime; + } + return hash; +} + +inline uint64_t composeSignatureDispatchId(uint64_t signatureHash, + SignatureCallKind kind, + uint8_t flags) { + const uint8_t kindByte = static_cast(kind); + uint64_t hash = hashBytesFnv1a(&kindByte, sizeof(kindByte)); + hash = hashBytesFnv1a(&flags, sizeof(flags), hash); + return hashBytesFnv1a(&signatureHash, sizeof(signatureHash), hash); +} + +template +inline Invoker lookupDispatchInvoker(const Entry (&entries)[N], + uint64_t dispatchId) { + if (dispatchId == 0 || N <= 1) { + return nullptr; + } + + size_t low = 1; + size_t high = N; + while (low < high) { + const size_t mid = low + ((high - low) >> 1); + const uint64_t midId = entries[mid].dispatchId; + if (midId < dispatchId) { + low = mid + 1; + } else { + high = mid; + } + } + + if (low < N && entries[low].dispatchId == dispatchId) { + return entries[low].invoker; + } + return nullptr; +} + +inline bool isGeneratedDispatchEnabled() { + static const bool enabled = []() { + const char* disableFlag = std::getenv("NS_DISABLE_GSD"); + return disableFlag == nullptr || disableFlag[0] == '\0' || + (disableFlag[0] == '0' && disableFlag[1] == '\0'); + }(); + return enabled; +} + +namespace signature_dispatch_detail { + +inline metagen::MDTypeKind canonicalizeSignatureTypeKind( + metagen::MDTypeKind kind) { + switch (kind) { + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return metagen::mdTypeAnyObject; + default: + return kind; + } +} + +template +inline void appendIntegralToHash(uint64_t* hash, T value) { + using Unsigned = typename std::make_unsigned::type; + Unsigned unsignedValue = static_cast(value); + for (size_t i = 0; i < sizeof(Unsigned); i++) { + const uint8_t byte = + static_cast((unsignedValue >> (i * 8)) & 0xFF); + *hash = hashBytesFnv1a(&byte, sizeof(byte), *hash); + } +} + +inline metagen::MDTypeKind stripMetadataTypeFlags(metagen::MDTypeKind kind) { + uint8_t raw = static_cast(kind); + raw &= ~(metagen::mdTypeFlagNext | metagen::mdTypeFlagVariadic); + return static_cast(raw); +} + +inline bool appendMetadataSignatureHash( + metagen::MDMetadataReader* reader, metagen::MDSectionOffset signatureOffset, + std::unordered_set* activeSignatures, + uint64_t* hash); + +inline bool appendMetadataTypeHash( + metagen::MDMetadataReader* reader, metagen::MDSectionOffset* offset, + std::unordered_set* activeSignatures, + uint64_t* hash) { + if (reader == nullptr || offset == nullptr || hash == nullptr || + activeSignatures == nullptr) { + return false; + } + + const metagen::MDTypeKind kindWithFlags = reader->getTypeKind(*offset); + *offset += sizeof(metagen::MDTypeKind); + const metagen::MDTypeKind rawKind = stripMetadataTypeFlags(kindWithFlags); + + appendIntegralToHash(hash, 0xB0); + appendIntegralToHash( + hash, static_cast(canonicalizeSignatureTypeKind(rawKind))); + + switch (rawKind) { + case metagen::mdTypeArray: + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: { + const auto arraySize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + appendIntegralToHash(hash, arraySize); + if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) { + return false; + } + break; + } + + case metagen::mdTypeStruct: { + const auto structOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + appendIntegralToHash(hash, structOffset); + break; + } + + case metagen::mdTypeClassObject: { + auto classOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + bool hasNext = (classOffset & metagen::mdSectionOffsetNext) != 0; + while (hasNext) { + auto protocolOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + hasNext = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + + case metagen::mdTypeProtocolObject: { + bool hasNext = true; + while (hasNext) { + auto protocolOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + hasNext = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + + case metagen::mdTypePointer: + if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) { + return false; + } + break; + + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: { + const auto nestedSignatureOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + if (nestedSignatureOffset != kNullMetadataSectionOffset) { + const auto nestedAbsoluteOffset = + reader->signaturesOffset + nestedSignatureOffset; + if (!appendMetadataSignatureHash(reader, nestedAbsoluteOffset, + activeSignatures, hash)) { + return false; + } + } + break; + } + + default: + break; + } + + appendIntegralToHash(hash, 0xBF); + return true; +} + +inline bool appendMetadataSignatureHash( + metagen::MDMetadataReader* reader, metagen::MDSectionOffset signatureOffset, + std::unordered_set* activeSignatures, + uint64_t* hash) { + if (reader == nullptr || hash == nullptr || activeSignatures == nullptr) { + return false; + } + + if (activeSignatures->find(signatureOffset) != activeSignatures->end()) { + appendIntegralToHash(hash, 0xEE); + return true; + } + activeSignatures->insert(signatureOffset); + + metagen::MDSectionOffset offset = signatureOffset; + const metagen::MDTypeKind returnTypeKind = reader->getTypeKind(offset); + bool next = + (static_cast(returnTypeKind) & metagen::mdTypeFlagNext) != 0; + const bool isVariadic = + (static_cast(returnTypeKind) & metagen::mdTypeFlagVariadic) != 0; + + appendIntegralToHash(hash, 0xA0); + appendIntegralToHash(hash, isVariadic ? 1 : 0); + + if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) { + activeSignatures->erase(signatureOffset); + return false; + } + + uint32_t argCount = 0; + while (next) { + const metagen::MDTypeKind argTypeKind = reader->getTypeKind(offset); + next = + (static_cast(argTypeKind) & metagen::mdTypeFlagNext) != 0; + if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) { + activeSignatures->erase(signatureOffset); + return false; + } + argCount++; + } + + appendIntegralToHash(hash, argCount); + appendIntegralToHash(hash, 0xAF); + + activeSignatures->erase(signatureOffset); + return true; +} + +} // namespace signature_dispatch_detail + +inline uint64_t metadataSignatureHash( + metagen::MDMetadataReader* reader, + metagen::MDSectionOffset signatureOffset) { + if (reader == nullptr || signatureOffset == kNullMetadataSectionOffset) { + return 0; + } + + uint64_t hash = kSignatureHashOffsetBasis; + std::unordered_set activeSignatures; + if (!signature_dispatch_detail::appendMetadataSignatureHash( + reader, signatureOffset, &activeSignatures, &hash)) { + return 0; + } + return hash; +} + +} // namespace nativescript + +#endif // NS_FFI_SHARED_SIGNATURE_DISPATCH_CORE_H diff --git a/NativeScript/ffi/shared/Tasks.cpp b/NativeScript/ffi/objc/shared/Tasks.cpp similarity index 100% rename from NativeScript/ffi/shared/Tasks.cpp rename to NativeScript/ffi/objc/shared/Tasks.cpp diff --git a/NativeScript/ffi/shared/Tasks.h b/NativeScript/ffi/objc/shared/Tasks.h similarity index 100% rename from NativeScript/ffi/shared/Tasks.h rename to NativeScript/ffi/objc/shared/Tasks.h diff --git a/NativeScript/ffi/objc/shared/bridge/Callbacks.mm b/NativeScript/ffi/objc/shared/bridge/Callbacks.mm new file mode 100644 index 000000000..8a42d1ec2 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/Callbacks.mm @@ -0,0 +1,2582 @@ +bool isObjectiveCObjectType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return true; + default: + return false; + } +} + +#ifndef NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME +std::shared_ptr retainNativeApiRuntime(Runtime& runtime) { + return std::shared_ptr(&runtime, [](Runtime*) {}); +} +#endif + +#ifndef NATIVESCRIPT_NATIVE_API_RUNTIME_SCOPE +class NativeApiRuntimeScope final { + public: + explicit NativeApiRuntimeScope(Runtime&) {} +}; +#endif + +struct NativeApiSignature { + ffi_cif cif = {}; + NativeApiType returnType; + std::vector argumentTypes; + std::vector ffiTypes; + std::string selectorName; + uint64_t signatureHash = 0; + uint8_t dispatchFlags = 0; + bool variadic = false; + bool prepared = false; + unsigned int implicitArgumentCount = 0; +}; + +enum class NativeApiCallbackThreadPolicy { + Default, + JS, + Runtime, +}; + +// Per-method callback policy, attached to a JS function via +// NativeScriptRuntime.nativeMethodPolicy(fn, policy) (a `__nativeScriptMethodPolicy` +// expando read back in readEngineMethodCallbackPolicy below). Deliberately +// small: the only live consumers are (a) calling the ObjC super +// implementation before the JS override runs, and (b) suppressing a +// re-entrant callback while a native accessor/construction call is already +// in flight (see nativeAccessorCallbackPolicy and +// shouldSkipConstructingMethodCallback). +struct NativeApiMethodCallbackPolicy { + bool callSuperBeforeCallback = false; + // Associated-object keys checked on the callback's receiver; if any is + // truthy, the callback is skipped entirely (zero-returned). + std::vector skipCallbackIfAssociatedObjectTruthy; +}; + +NativeApiCallbackThreadPolicy readEngineCallbackThreadPolicy( + Runtime& runtime, Object& functionObject) { + constexpr const char* propertyName = "__nativeScriptCallbackThread"; + try { + if (!functionObject.hasProperty(runtime, propertyName)) { + return NativeApiCallbackThreadPolicy::Default; + } + Value policyValue = functionObject.getProperty(runtime, propertyName); + if (!policyValue.isString()) { + return NativeApiCallbackThreadPolicy::Default; + } + std::string policy = policyValue.asString(runtime).utf8(runtime); + if (policy == "js") { + return NativeApiCallbackThreadPolicy::JS; + } + if (policy == "runtime" || policy == "worklet") { + return NativeApiCallbackThreadPolicy::Runtime; + } + } catch (const std::exception&) { + } + return NativeApiCallbackThreadPolicy::Default; +} + +Value optionalObjectProperty(Runtime& runtime, Object& object, + const char* name) { + if (name == nullptr || !object.hasProperty(runtime, name)) { + return Value::undefined(); + } + return object.getProperty(runtime, name); +} + +void appendEngineMethodPolicyAssociatedObjectKeys( + Runtime& runtime, const Value& value, std::vector& keys) { + if (value.isString()) { + keys.push_back(value.asString(runtime).utf8(runtime)); + return; + } + if (!value.isObject()) { + return; + } + Object object = value.asObject(runtime); + if (!object.isArray(runtime)) { + return; + } + Array array = object.getArray(runtime); + size_t size = array.size(runtime); + for (size_t i = 0; i < size; i++) { + Value item = array.getValueAtIndex(runtime, i); + if (item.isString()) { + keys.push_back(item.asString(runtime).utf8(runtime)); + } + } +} + +NativeApiMethodCallbackPolicy readEngineMethodCallbackPolicyValue( + Runtime& runtime, const Value& policyValue) { + NativeApiMethodCallbackPolicy policy; + try { + if (!policyValue.isObject()) { + return policy; + } + + Object policyObject = policyValue.asObject(runtime); + Value callSuperBeforeValue = optionalObjectProperty( + runtime, policyObject, "callSuperBeforeCallback"); + if (callSuperBeforeValue.isBool() && callSuperBeforeValue.getBool()) { + policy.callSuperBeforeCallback = true; + } else { + Value callSuperValue = + optionalObjectProperty(runtime, policyObject, "callSuper"); + if (callSuperValue.isString()) { + policy.callSuperBeforeCallback = + callSuperValue.asString(runtime).utf8(runtime) == "before"; + } + } + + appendEngineMethodPolicyAssociatedObjectKeys( + runtime, + optionalObjectProperty( + runtime, policyObject, "skipCallbackIfAssociatedObjectTruthy"), + policy.skipCallbackIfAssociatedObjectTruthy); + } catch (const std::exception&) { + return NativeApiMethodCallbackPolicy{}; + } + return policy; +} + +// Reads the policy a JS function was tagged with via +// NativeScriptRuntime.nativeMethodPolicy(fn, policy). +NativeApiMethodCallbackPolicy readEngineMethodCallbackPolicy( + Runtime& runtime, Object& functionObject) { + constexpr const char* propertyName = "__nativeScriptMethodPolicy"; + try { + if (!functionObject.hasProperty(runtime, propertyName)) { + return NativeApiMethodCallbackPolicy{}; + } + return readEngineMethodCallbackPolicyValue( + runtime, functionObject.getProperty(runtime, propertyName)); + } catch (const std::exception&) { + return NativeApiMethodCallbackPolicy{}; + } +} + +bool isEmptyMethodCallbackPolicy(const NativeApiMethodCallbackPolicy& policy) { + return !policy.callSuperBeforeCallback && + policy.skipCallbackIfAssociatedObjectTruthy.empty(); +} + +bool selectorEndsWithNSErrorParam(const std::string& selectorName) { + constexpr const char* suffix = "error:"; + size_t suffixLength = std::strlen(suffix); + return selectorName.size() >= suffixLength && + selectorName.compare(selectorName.size() - suffixLength, suffixLength, + suffix) == 0; +} + +bool isNSErrorOutEngineMethodSignature(const NativeApiSignature& signature) { + if (signature.argumentTypes.empty() || signature.variadic || + !selectorEndsWithNSErrorParam(signature.selectorName)) { + return false; + } + + return signature.argumentTypes.back().kind == metagen::mdTypePointer; +} + +bool isNSErrorOutEngineMethodCallback(const NativeApiSignature& signature) { + return signature.returnType.kind == metagen::mdTypeBool && + signature.implicitArgumentCount >= 2 && + isNSErrorOutEngineMethodSignature(signature); +} + +class NativeApiArgumentFrame { + public: + explicit NativeApiArgumentFrame(size_t count) : count_(count) { + if (count_ > kInlineArgumentCount) { + heapStorage_.resize(count_); + heapValues_.resize(count_); + } + } + + ~NativeApiArgumentFrame() { + for (char* string : ownedCStrings_) { + free(string); + } + for (void* buffer : ownedBuffers_) { + free(buffer); + } + for (id object : ownedObjects_) { + [object release]; + } + for (const auto& entry : temporaryRoundTripValues_) { + if (entry.bridge != nullptr && entry.runtime != nullptr) { + entry.bridge->forgetRoundTripValue(*entry.runtime, entry.native); + } + } + ownedLifetimes_.clear(); + } + + void* storageAt(size_t index, size_t size) { + if (index >= count_) { + throw std::out_of_range("Native argument index out of range."); + } + + size = std::max(size, sizeof(void*)); + if (count_ <= kInlineArgumentCount && size <= kInlineStorageSize) { + std::memset(inlineStorage_[index], 0, kInlineStorageSize); + inlineValues_[index] = inlineStorage_[index]; + return inlineValues_[index]; + } + + if (count_ <= kInlineArgumentCount) { + overflowStorage_.emplace_back(size, 0); + inlineValues_[index] = overflowStorage_.back().data(); + return inlineValues_[index]; + } + + heapStorage_[index].assign(size, 0); + heapValues_[index] = heapStorage_[index].data(); + return heapValues_[index]; + } + + void addCString(char* value) { ownedCStrings_.push_back(value); } + void* addBuffer(size_t size) { + void* buffer = calloc(1, std::max(size, 1)); + if (buffer == nullptr) { + throw std::bad_alloc(); + } + ownedBuffers_.push_back(buffer); + return buffer; + } + void addObject(id value) { ownedObjects_.push_back(value); } + void retainObject(id value) { + if (value != nil) { + [value retain]; + ownedObjects_.push_back(value); + } + } + void addLifetime(std::shared_ptr value) { + if (value != nullptr) { + ownedLifetimes_.push_back(std::move(value)); + } + } + void rememberRoundTripValue( + const std::shared_ptr& bridge, Runtime& runtime, + const void* native, const Value& value) { + if (bridge == nullptr || native == nullptr) { + return; + } + bridge->rememberRoundTripValue(runtime, native, value); + temporaryRoundTripValues_.push_back({bridge, &runtime, native}); + } + void** values() { + if (count_ == 0) { + return nullptr; + } + return count_ <= kInlineArgumentCount ? inlineValues_ : heapValues_.data(); + } + + private: + static constexpr size_t kInlineArgumentCount = 8; + static constexpr size_t kInlineStorageSize = 32; + + size_t count_ = 0; + alignas(void*) unsigned char + inlineStorage_[kInlineArgumentCount][kInlineStorageSize] = {}; + void* inlineValues_[kInlineArgumentCount] = {}; + std::vector> heapStorage_; + std::vector heapValues_; + std::vector> overflowStorage_; + std::vector ownedCStrings_; + std::vector ownedBuffers_; + std::vector ownedObjects_; + std::vector> ownedLifetimes_; + struct TemporaryRoundTripValue { + std::shared_ptr bridge; + Runtime* runtime = nullptr; + const void* native = nullptr; + }; + std::vector temporaryRoundTripValues_; +}; + +class NativeApiMutableBuffer final : public MutableBuffer { + public: + explicit NativeApiMutableBuffer(size_t size) : data_(size) {} + NativeApiMutableBuffer(const void* data, size_t size) : data_(size) { + if (data != nullptr && size > 0) { + std::memcpy(data_.data(), data, size); + } + } + + size_t size() const override { return data_.size(); } + uint8_t* data() override { return data_.empty() ? nullptr : data_.data(); } + + private: + std::vector data_; +}; + +void convertEngineArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, + const Value& value, void* target, + NativeApiArgumentFrame& frame); + +Value convertNativeReturnValue(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, void* value); + +Value wrapNativeFunctionPointer(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, void* pointer, + bool block); + +bool isObjectiveCObjectType(const NativeApiType& type); + +struct NativeApiBlockDescriptor { + unsigned long reserved = 0; + unsigned long size = 0; + void (*copyHelper)(void*, void*) = nullptr; + void (*disposeHelper)(void*) = nullptr; + const char* signature = nullptr; +}; + +struct NativeApiBlockLiteral { + void* isa = nullptr; + int flags = 0; + int reserved = 0; + void* invoke = nullptr; + NativeApiBlockDescriptor* descriptor = nullptr; + void* callback = nullptr; +}; + +constexpr int kNativeApiBlockNeedsFree = (1 << 24); +constexpr int kNativeApiBlockHasCopyDispose = (1 << 25); +constexpr int kNativeApiBlockRefCountOne = (1 << 1); +constexpr int kNativeApiBlockHasSignature = (1 << 30); + +void* nativeApiEngineMallocBlockIsa() { + static void* isa = dlsym(RTLD_DEFAULT, "_NSConcreteMallocBlock"); + return isa; +} + +void nativeApiEngineBlockCopy(void* dst, void* src); +void nativeApiEngineBlockDispose(void* src); + +std::string objcEncodingForEngineType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeVoid: + return "v"; + case metagen::mdTypeBool: + return "B"; + case metagen::mdTypeChar: + return "c"; + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return "C"; + case metagen::mdTypeSShort: + return "s"; + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + return "S"; + case metagen::mdTypeSInt: + return "i"; + case metagen::mdTypeUInt: + return "I"; + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return "q"; + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return "Q"; + case metagen::mdTypeFloat: + return "f"; + case metagen::mdTypeDouble: + return "d"; + case metagen::mdTypeString: + return "*"; + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return "@"; + case metagen::mdTypeClass: + return "#"; + case metagen::mdTypeSelector: + return ":"; + case metagen::mdTypeBlock: + return "@?"; + case metagen::mdTypeFunctionPointer: + return "^?"; + case metagen::mdTypePointer: + case metagen::mdTypeOpaquePointer: + if (type.elementType != nullptr && + type.elementType->kind != metagen::mdTypeVoid) { + return "^" + objcEncodingForEngineType(*type.elementType); + } + return "^v"; + case metagen::mdTypeStruct: + return "{" + + (type.aggregateInfo != nullptr ? type.aggregateInfo->name + : std::string("?")) + + "=}"; + case metagen::mdTypeArray: + return "[" + std::to_string(type.arraySize) + + (type.elementType != nullptr ? objcEncodingForEngineType(*type.elementType) + : std::string("?")) + + "]"; + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: + return type.elementType != nullptr ? objcEncodingForEngineType(*type.elementType) + : "?"; + default: + return "?"; + } +} + +std::string objcBlockSignatureForEngineSignature( + const NativeApiSignature& signature) { + std::string encoding = objcEncodingForEngineType(signature.returnType); + encoding += "@?"; + for (const auto& argType : signature.argumentTypes) { + encoding += objcEncodingForEngineType(argType); + } + return encoding; +} + +std::string objcMethodSignatureForEngineSignature( + const NativeApiSignature& signature) { + std::string encoding = objcEncodingForEngineType(signature.returnType); + encoding += "@:"; + for (const auto& argType : signature.argumentTypes) { + encoding += objcEncodingForEngineType(argType); + } + return encoding; +} + +[[noreturn]] void throwNativeApiCallbackException( + const std::string& message) { + NSString* reason = [NSString stringWithUTF8String:message.c_str()]; + @throw [NSException exceptionWithName:@"NativeScriptEngineCallbackException" + reason:reason + userInfo:nil]; +} + +class NativeApiCallback; + +void nativeApiEngineCallbackTrampoline(ffi_cif* cif, void* ret, void* args[], + void* data); + +std::atomic gActiveNativeThreadEngineCallbacks{0}; + +// A callback can outlive the scope in which its function argument was created +// (e.g. a block invoked asynchronously). Round-trip the function through the +// engine value copy constructor so any scope-bound/borrowed handle is promoted +// to a persistent one before it is stored. +Function persistentEngineFunction(Runtime& runtime, const Function& function) { + Value shared(runtime, function); + Value persistent(runtime, shared); + return persistent.asObject(runtime).asFunction(runtime); +} + +class NativeApiCallback final + : public std::enable_shared_from_this { + public: + NativeApiCallback(Runtime& runtime, + std::shared_ptr bridge, + std::shared_ptr signature, + Function function, bool block, + NativeApiCallbackThreadPolicy threadPolicy = + NativeApiCallbackThreadPolicy::Default, + bool bindThis = false, + uintptr_t roundTripValidationKey = 0, + NativeApiMethodCallbackPolicy methodPolicy = {}, + Class methodBaseClass = Nil) + : runtimeOwner_(retainNativeApiRuntime(runtime)), + runtime_(runtimeOwner_.get()), + bridge_(std::move(bridge)), + signature_(std::move(signature)), + function_(std::make_shared( + persistentEngineFunction(runtime, function))), + block_(block), + threadPolicy_(threadPolicy), + bindThis_(bindThis), + roundTripValidationKey_(roundTripValidationKey), + methodPolicy_(std::move(methodPolicy)), + methodBaseClass_(methodBaseClass) { + closure_ = static_cast( + ffi_closure_alloc(sizeof(ffi_closure), &executable_)); + if (closure_ == nullptr || executable_ == nullptr || + signature_ == nullptr || !signature_->prepared) { + throw JSError(runtime, + "Unable to allocate native callback."); + } + + ffi_status status = ffi_prep_closure_loc( + closure_, &signature_->cif, nativeApiEngineCallbackTrampoline, this, + executable_); + if (status != FFI_OK) { + ffi_closure_free(closure_); + closure_ = nullptr; + executable_ = nullptr; + throw JSError(runtime, + "Unable to prepare native callback."); + } + + if (block_) { + blockSignature_ = objcBlockSignatureForEngineSignature(*signature_); + descriptor_ = std::make_unique(); + descriptor_->reserved = 0; + descriptor_->size = sizeof(NativeApiBlockLiteral); + descriptor_->copyHelper = nativeApiEngineBlockCopy; + descriptor_->disposeHelper = nativeApiEngineBlockDispose; + descriptor_->signature = blockSignature_.c_str(); + + blockLiteral_ = static_cast( + calloc(1, sizeof(NativeApiBlockLiteral))); + if (blockLiteral_ == nullptr) { + throw JSError(runtime, "Unable to allocate native block callback."); + } + void* blockIsa = nativeApiEngineMallocBlockIsa(); + if (blockIsa == nullptr) { + free(blockLiteral_); + blockLiteral_ = nullptr; + throw JSError(runtime, + "Objective-C malloc block runtime is unavailable."); + } + blockLiteral_->isa = blockIsa; + blockLiteral_->flags = kNativeApiBlockNeedsFree | + kNativeApiBlockHasCopyDispose | + kNativeApiBlockRefCountOne | + kNativeApiBlockHasSignature; + blockLiteral_->invoke = executable_; + blockLiteral_->descriptor = descriptor_.get(); + blockLiteral_->callback = this; + } + } + + ~NativeApiCallback() { + if (closure_ != nullptr) { + ffi_closure_free(closure_); + closure_ = nullptr; + executable_ = nullptr; + } + } + + void* functionPointer() const { + return block_ && blockLiteral_ != nullptr + ? static_cast(blockLiteral_) + : executable_; + } + + const NativeApiSignature& signature() const { return *signature_; } + + void retainInitialBlockLifetime( + std::shared_ptr lifetime) { + if (block_) { + initialBlockLifetime_ = std::move(lifetime); + } + } + + void retainBlockCopy(const void* blockPointer) { + if (!block_) { + return; + } + auto self = shared_from_this(); + if (bridge_ != nullptr && runtime_ != nullptr && function_ != nullptr && + blockPointer != nullptr) { + bridge_->rememberRoundTripValue(*runtime_, blockPointer, + Value(*runtime_, *function_), false, + roundTripValidationKey_); + } + std::lock_guard lock(retainedBlockCopiesMutex_); + retainedBlockCopies_.push_back({blockPointer, std::move(self)}); + } + + bool releaseBlockCopy(const void* blockPointer) { + if (!block_) { + return false; + } + + bool canRelease = false; + { + std::lock_guard lock(retainedBlockCopiesMutex_); + auto it = retainedBlockCopies_.end(); + if (blockPointer != nullptr) { + it = std::find_if( + retainedBlockCopies_.begin(), retainedBlockCopies_.end(), + [blockPointer](const RetainedBlockCopy& retained) { + return retained.blockPointer == blockPointer; + }); + } + canRelease = + it != retainedBlockCopies_.end() || blockPointer == blockLiteral_; + } + // Forgetting the round-trip value touches the JS engine global/context. + // Block disposal can run during an autorelease-pool drain on an arbitrary + // thread (e.g. an NSOperationQueue worker). Keep the retained block entry + // in place until the JS-thread task runs so the callback and its engine + // function are also destroyed on the JS thread. + if (!canRelease) { + return false; + } + + auto bridge = bridge_; + auto* runtime = runtime_; + auto runtimeOwner = runtimeOwner_; + auto releaseOnJS = [this, bridge, runtime, runtimeOwner, blockPointer]() { + std::shared_ptr keepAlive; + try { + keepAlive = shared_from_this(); + } catch (const std::bad_weak_ptr&) { + return; + } + + const void* pointerToForget = nullptr; + { + std::lock_guard lock(retainedBlockCopiesMutex_); + auto it = retainedBlockCopies_.end(); + if (blockPointer != nullptr) { + it = std::find_if( + retainedBlockCopies_.begin(), retainedBlockCopies_.end(), + [blockPointer](const RetainedBlockCopy& retained) { + return retained.blockPointer == blockPointer; + }); + } + if (it != retainedBlockCopies_.end()) { + pointerToForget = it->blockPointer; + retainedBlockCopies_.erase(it); + } else if (blockPointer == blockLiteral_) { + pointerToForget = blockPointer; + blockLiteral_ = nullptr; + initialBlockLifetime_.reset(); + } + } + + if (bridge != nullptr && runtime != nullptr && + pointerToForget != nullptr) { + NativeApiRuntimeScope runtimeScope(*runtime); + bridge->forgetRoundTripValue(*runtime, pointerToForget); + } + }; + + if (bridge == nullptr) { + releaseOnJS(); + } else if (const auto& asyncInvoker = + bridge->jsThreadAsyncCallbackInvoker()) { + asyncInvoker(std::move(releaseOnJS)); + } else if (auto scheduler = bridge->scheduler()) { + scheduler->invokeOnJS(std::move(releaseOnJS)); + } else if (std::this_thread::get_id() == bridge->jsThreadId()) { + releaseOnJS(); + } else if (const auto& invoker = bridge->jsThreadCallbackInvoker()) { + invoker(std::move(releaseOnJS)); + } else { + releaseOnJS(); + } + return true; + } + + void invoke(void* ret, void* args[]) { + if (runtime_ == nullptr || function_ == nullptr || signature_ == nullptr) { + throwNativeApiCallbackException("Invalid callback."); + } + + // Teardown-safety gate: once the host has signaled that callbacks are no + // longer allowed (runtime shutting down/reloading), bail with a + // zeroed return instead of running JS against a dying runtime. + bool callbackAllowed = false; + if (bridge_ != nullptr) { + @try { + callbackAllowed = bridge_->callbackInvocationAllowed(); + } @catch (...) { + callbackAllowed = false; + } + } + + if (!callbackAllowed) { + zeroReturnValue(ret); + return; + } + + if (methodPolicy_.callSuperBeforeCallback) { + invokeMethodSuper(ret, args); + } + if (shouldSkipMethodCallback(args, ret)) { + return; + } + + std::string error; + auto call = [&]() { invokeOnCurrentThread(ret, args, &error); }; + const auto& nativeCallbackInvoker = bridge_->nativeCallbackInvoker(); + const auto& runtimeCallbackInvoker = bridge_->runtimeCallbackInvoker(); + const auto& jsThreadCallbackInvoker = bridge_->jsThreadCallbackInvoker(); + bool currentThreadIsJs = + std::this_thread::get_id() == bridge_->jsThreadId(); + + auto callOnNativeCallerThread = [&]() { + ScopedNativeCallerThreadEngineCallback callbackScope; + if (nativeCallbackInvoker) { + nativeCallbackInvoker(call); + } else { + call(); + } + }; + auto callOnJSThread = [&]() { + if (currentThreadIsJs) { + call(); + return; + } + if (jsThreadCallbackInvoker) { + jsThreadCallbackInvoker(call); + return; + } + if (auto scheduler = bridge_->scheduler()) { + dispatch_semaphore_t done = dispatch_semaphore_create(0); + scheduler->invokeOnJS([call, done]() mutable { + call(); + dispatch_semaphore_signal(done); + }); + dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER); + return; + } + error = "Native callback was invoked off the JS thread without a JS scheduler."; + }; + auto callOnRuntimeThread = [&]() { + if (currentThreadIsJs) { + call(); + return; + } + if (runtimeCallbackInvoker) { + runtimeCallbackInvoker(call); + return; + } + error = "Native callback was invoked off its owning runtime thread without a runtime scheduler."; + }; + + if (threadPolicy_ == NativeApiCallbackThreadPolicy::JS) { + callOnJSThread(); + if (!error.empty()) { + if (!recordNativeCallbackException(error)) { + throwNativeApiCallbackException(error); + } + } + return; + } + if (threadPolicy_ == NativeApiCallbackThreadPolicy::Runtime) { + callOnRuntimeThread(); + if (!error.empty()) { + if (!recordNativeCallbackException(error)) { + throwNativeApiCallbackException(error); + } + } + return; + } + + bool returnsVoid = signature_->returnType.kind == metagen::mdTypeVoid; + bool nativeCallerThreadCallbacks = + bridge_->invokeCallbacksOnNativeCallerThread(); + bool direct = currentThreadIsJs || + gSynchronousNativeInvocationDepth > 0; + bool waitForNativeThreadCallback = + currentThreadIsJs && nativeCallbackInvoker && + gActiveNativeThreadEngineCallbacks.load(std::memory_order_acquire) > 0; + auto dispatchZeroArgVoidBlockAsync = [&]() -> bool { + if (currentThreadIsJs || !returnsVoid || !block_ || + !signature_->argumentTypes.empty()) { + return false; + } + + std::shared_ptr keepAlive; + try { + keepAlive = shared_from_this(); + } catch (const std::bad_weak_ptr&) { + return false; + } + + auto asyncCall = [keepAlive = std::move(keepAlive)]() mutable { + std::string asyncError; + keepAlive->invokeOnCurrentThread(nullptr, nullptr, &asyncError); + if (!asyncError.empty()) { + recordNativeCallbackException(asyncError); + } + }; + + const auto& asyncInvoker = bridge_->jsThreadAsyncCallbackInvoker(); + if (asyncInvoker) { + asyncInvoker(std::move(asyncCall)); + return true; + } + if (auto scheduler = bridge_->scheduler()) { + scheduler->invokeOnJS(std::move(asyncCall)); + return true; + } + return false; + }; + + if (nativeCallerThreadCallbacks && !currentThreadIsJs) { + callOnNativeCallerThread(); + } else if (dispatchZeroArgVoidBlockAsync()) { + return; + } else if (direct && !waitForNativeThreadCallback) { + call(); + } else if (!currentThreadIsJs) { + callOnJSThread(); + } else if (nativeCallbackInvoker) { + bool nativeThreadCallback = !currentThreadIsJs; + if (nativeThreadCallback) { + gActiveNativeThreadEngineCallbacks.fetch_add(1, + std::memory_order_acq_rel); + } + try { + nativeCallbackInvoker(call); + } catch (...) { + if (nativeThreadCallback) { + gActiveNativeThreadEngineCallbacks.fetch_sub( + 1, std::memory_order_acq_rel); + } + throw; + } + if (nativeThreadCallback) { + gActiveNativeThreadEngineCallbacks.fetch_sub(1, + std::memory_order_acq_rel); + } + } else if (auto scheduler = bridge_->scheduler()) { + dispatch_semaphore_t done = dispatch_semaphore_create(0); + scheduler->invokeOnJS([call, done]() mutable { + call(); + dispatch_semaphore_signal(done); + }); + dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER); + } else { + error = "Native callback was invoked off the JS thread without a JS scheduler."; + } + + if (!error.empty()) { + if (!recordNativeCallbackException(error)) { + throwNativeApiCallbackException(error); + } + } + } + + private: + // The ObjC class whose implementation `callSuperBeforeCallback`/ + // invokeMethodSuper() should dispatch against. A JS-subclass receiver + // (ClassBuilder instance) dispatches to its own runtime superclass; + // anything else falls back to methodBaseClass_ (the class the override was + // registered against). + Class dispatchSuperclassForMethodReceiver(id receiver) const { + if (receiver == nil) { + return Nil; + } + + Class receiverClass = object_getClass(receiver); + if (receiverClass != Nil && + class_conformsToProtocol(receiverClass, + @protocol(NativeApiClassBuilderProtocol))) { + Class superclass = class_getSuperclass(receiverClass); + if (superclass != Nil) { + return superclass; + } + } + + return methodBaseClass_; + } + + // Method callback policies only ever target the callback's receiver (no + // argument-index targeting), so this is just the bound `self`. + id methodCallbackReceiver(void* args[]) const { + if (!bindThis_ || args == nullptr) { + return nil; + } + return *static_cast(args[0]); + } + + id associatedObjectValue(id receiver, const std::string& key) const { + if (receiver == nil || key.empty()) { + return nil; + } + return objc_getAssociatedObject(receiver, sel_registerName(key.c_str())); + } + + bool associatedObjectIsTruthy(id receiver, const std::string& key) const { + id value = associatedObjectValue(receiver, key); + if (value == nil) { + return false; + } + + if ([value respondsToSelector:@selector(boolValue)]) { + return [value boolValue] == YES; + } + if ([value isKindOfClass:[NSString class]]) { + NSString* stringValue = (NSString*)value; + if (stringValue.length == 0) { + return false; + } + NSString* lowercase = [stringValue lowercaseString]; + return ![lowercase isEqualToString:@"0"] && + ![lowercase isEqualToString:@"false"] && + ![lowercase isEqualToString:@"no"]; + } + + return true; + } + + // Skips a re-entrant callback: an alloc/init construction already in + // flight (marked via __nativeApiConstructionState) must not have its + // non-init methods re-entered by a partially-constructed self. + bool shouldSkipConstructingMethodCallback(void* args[], void* ret) { + if (!bindThis_ || args == nullptr || signature_ == nullptr || + signature_->selectorName.rfind("init", 0) == 0) { + return false; + } + + id receiver = *static_cast(args[0]); + if (receiver == nil || + objc_getAssociatedObject( + receiver, sel_registerName("__nativeApiConstructionState")) == nil) { + return false; + } + + zeroReturnValue(ret); + return true; + } + + bool shouldSkipMethodCallback(void* args[], void* ret) { + if (args == nullptr) { + return false; + } + + if (shouldSkipConstructingMethodCallback(args, ret)) { + return true; + } + + id receiver = methodCallbackReceiver(args); + for (const auto& key : + methodPolicy_.skipCallbackIfAssociatedObjectTruthy) { + if (associatedObjectIsTruthy(receiver, key)) { + zeroReturnValue(ret); + return true; + } + } + + return false; + } + + // callSuperBeforeCallback: run the ObjC super implementation before the JS + // override does, via objc_msgSendSuper with the same arguments the JS + // callback is about to receive. + void invokeMethodSuper(void* ret, void* args[]) const { + if (!bindThis_ || args == nullptr || signature_ == nullptr || + methodBaseClass_ == Nil) { + return; + } + + id receiver = *static_cast(args[0]); + Class dispatchClass = dispatchSuperclassForMethodReceiver(receiver); + if (receiver == nil || dispatchClass == Nil) { + return; + } + + struct objc_super superReceiver = {receiver, dispatchClass}; + struct objc_super* superReceiverPtr = &superReceiver; + size_t nativeArgc = + signature_->implicitArgumentCount + signature_->argumentTypes.size(); + std::vector values(nativeArgc); + values[0] = &superReceiverPtr; + values[1] = args[1]; + for (size_t i = 2; i < nativeArgc; i++) { + values[i] = args[i]; + } + + std::vector returnStorage; + void* returnTarget = ret; + if (returnTarget == nullptr) { + returnStorage.resize( + std::max(nativeSizeForType(signature_->returnType), 1)); + returnTarget = returnStorage.data(); + } + + performNativeInvocation(*runtime_, bridge_->nativeInvocationInvoker(), [&]() { +#if defined(__x86_64__) + bool isStret = signature_->returnType.ffiType->size > 16 && + signature_->returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper); + ffi_call(const_cast(&signature_->cif), target, returnTarget, + values.data()); +#else + ffi_call(const_cast(&signature_->cif), + FFI_FN(objc_msgSendSuper), returnTarget, values.data()); +#endif + }); + } + + void invokeOnCurrentThread(void* ret, void* args[], std::string* error) { + try { + NativeApiRuntimeScope runtimeScope(*runtime_); + size_t nativeArgOffset = signature_->implicitArgumentCount; + std::vector jsArgs; + jsArgs.reserve(signature_->argumentTypes.size()); + for (size_t i = 0; i < signature_->argumentTypes.size(); i++) { + jsArgs.emplace_back(convertNativeReturnValue( + *runtime_, bridge_, signature_->argumentTypes[i], + args[i + nativeArgOffset])); + } + + Value result = Value::undefined(); + if (bindThis_ && nativeArgOffset >= 1) { + id self = *static_cast(args[0]); + // `this.super`/`$base` from inside this override should dispatch + // against the class ABOVE the one the override was registered on, + // not the receiver's own (possibly further-subclassed) runtime class. + // `methodBaseClass_` (threaded from ClassBuilder's addEngineOverrideMethod + // as `baseClass`, i.e. `class_getSuperclass(nativeClass)`) IS already + // that class -- it must be used directly, not further superclassed + // (which would skip straight past it to ITS superclass and make any + // member declared exactly on methodBaseClass_, e.g. a method the + // override shadows that isn't itself inherited, unreachable via + // `this.super`). dispatchSuperclassForMethodReceiver() above already + // relies on this same "use methodBaseClass_ as-is" convention. + Class superDispatchClass = methodBaseClass_; + Value thisValue = + makeNativeObjectValue( + *runtime_, bridge_, self, false, superDispatchClass); + Object thisObject = thisValue.isObject() + ? thisValue.asObject(*runtime_) + : Object(*runtime_); + result = + jsArgs.empty() + ? function_->callWithThis(*runtime_, thisObject) + : function_->callWithThis( + *runtime_, thisObject, + static_cast(jsArgs.data()), + static_cast(jsArgs.size())); + } else { + result = + jsArgs.empty() + ? function_->call(*runtime_) + : function_->call(*runtime_, + static_cast(jsArgs.data()), + static_cast(jsArgs.size())); + } + storeReturnValue(result, ret); + if (std::this_thread::get_id() == bridge_->jsThreadId() && + gSynchronousNativeInvocationDepth == 0) { + runtime_->drainMicrotasks(); + } + } catch (const std::exception& exception) { + if (isNSErrorOutEngineMethodCallback(*signature_)) { + zeroReturnValue(ret); + populateNSErrorOutArgument(args, exception.what()); + return; + } + if (error != nullptr) { + *error = exception.what(); + } + zeroReturnValue(ret); + } catch (...) { + if (isNSErrorOutEngineMethodCallback(*signature_)) { + zeroReturnValue(ret); + populateNSErrorOutArgument(args, "Unknown exception in native callback."); + return; + } + if (error != nullptr) { + *error = "Unknown exception in native callback."; + } + zeroReturnValue(ret); + } + } + + void populateNSErrorOutArgument(void* args[], const char* message) { + if (args == nullptr || signature_ == nullptr || + signature_->argumentTypes.empty()) { + return; + } + + size_t outArgIndex = signature_->implicitArgumentCount + + signature_->argumentTypes.size() - 1; + void* outArgValue = args[outArgIndex]; + NSError** outError = + outArgValue != nullptr ? *reinterpret_cast(outArgValue) + : nullptr; + if (outError == nullptr) { + return; + } + + NSString* nsMessage = + message != nullptr ? [NSString stringWithUTF8String:message] : nil; + if (nsMessage == nil) { + nsMessage = @"JS error"; + } + NSDictionary* userInfo = @{NSLocalizedDescriptionKey : nsMessage}; + *outError = [NSError errorWithDomain:@"TNSErrorDomain" + code:1 + userInfo:userInfo]; + } + + void zeroReturnValue(void* ret) { + if (ret == nullptr || signature_ == nullptr || + signature_->returnType.kind == metagen::mdTypeVoid) { + return; + } + size_t size = nativeSizeForType(signature_->returnType); + if (size > 0) { + std::memset(ret, 0, size); + } + } + + void storeReturnValue(const Value& result, void* ret) { + if (ret == nullptr || + signature_->returnType.kind == metagen::mdTypeVoid) { + return; + } + + zeroReturnValue(ret); + if (result.isUndefined() || result.isNull()) { + return; + } + const auto& returnType = signature_->returnType; + if (returnType.kind == metagen::mdTypeString && result.isString()) { + std::string utf8 = result.asString(*runtime_).utf8(*runtime_); + *static_cast(ret) = strdup(utf8.c_str()); + return; + } + if ((returnType.kind == metagen::mdTypePointer || + returnType.kind == metagen::mdTypeOpaquePointer) && + result.isString()) { + std::string utf8 = result.asString(*runtime_).utf8(*runtime_); + *static_cast(ret) = strdup(utf8.c_str()); + return; + } + + NativeApiArgumentFrame frame(1); + convertEngineArgument(*runtime_, bridge_, returnType, result, ret, frame); + if (isObjectiveCObjectType(returnType)) { + id object = *static_cast(ret); + if (object != nil) { + [object retain]; + [object autorelease]; + } + } + } + + std::shared_ptr runtimeOwner_; + Runtime* runtime_ = nullptr; + std::shared_ptr bridge_; + std::shared_ptr signature_; + std::shared_ptr function_; + bool block_ = false; + NativeApiCallbackThreadPolicy threadPolicy_ = + NativeApiCallbackThreadPolicy::Default; + bool bindThis_ = false; + uintptr_t roundTripValidationKey_ = 0; + NativeApiMethodCallbackPolicy methodPolicy_; + Class methodBaseClass_ = Nil; + ffi_closure* closure_ = nullptr; + void* executable_ = nullptr; + std::string blockSignature_; + std::unique_ptr descriptor_; + NativeApiBlockLiteral* blockLiteral_ = nullptr; + std::shared_ptr initialBlockLifetime_; + struct RetainedBlockCopy { + const void* blockPointer = nullptr; + std::shared_ptr lifetime; + }; + std::mutex retainedBlockCopiesMutex_; + std::vector retainedBlockCopies_; +}; + +void nativeApiEngineBlockCopy(void* dst, void* src) { + auto* dstBlock = static_cast(dst); + auto* srcBlock = static_cast(src); + if (dstBlock == nullptr || srcBlock == nullptr || + srcBlock->callback == nullptr) { + return; + } + dstBlock->callback = srcBlock->callback; + static_cast(srcBlock->callback) + ->retainBlockCopy(dstBlock); +} + +void nativeApiEngineBlockDispose(void* src) { + auto* block = static_cast(src); + if (block == nullptr || block->callback == nullptr) { + return; + } + bool released = + static_cast(block->callback)->releaseBlockCopy(block); + if (released) { + block->callback = nullptr; + } +} + +void nativeApiEngineCallbackTrampoline(ffi_cif*, void* ret, void* args[], + void* data) { + auto callback = static_cast(data); + if (callback == nullptr) { + return; + } + @try { + callback->invoke(ret, args); + } @catch (NSException* exception) { + const char* description = + exception.description != nil ? exception.description.UTF8String : nullptr; + std::string message = description != nullptr + ? description + : "Objective-C exception in native callback."; + if (!recordNativeCallbackException(message)) { + @throw; + } + } +} + +size_t nativeSizeForType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeStruct: + if (type.aggregateInfo != nullptr) { + return type.aggregateInfo->size; + } + break; + case metagen::mdTypeArray: + if (type.elementType != nullptr) { + return nativeSizeForType(*type.elementType) * + static_cast(type.arraySize); + } + break; + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: + if (type.elementType != nullptr) { + size_t lanes = std::max(type.arraySize, 1); + size_t abiLanes = lanes == 3 ? 4 : lanes; + return nativeSizeForType(*type.elementType) * abiLanes; + } + break; + default: + break; + } + + if (type.ffiType != nullptr && type.ffiType->size > 0) { + return type.ffiType->size; + } + if (type.ffiType == &ffi_type_void) { + return 0; + } + return sizeof(void*); +} + +Value signedInteger64ToEngineValue(Runtime& runtime, int64_t value) { + constexpr int64_t maxSafeInteger = 9007199254740991LL; + constexpr int64_t minSafeInteger = -9007199254740991LL; + if (value >= minSafeInteger && value <= maxSafeInteger) { + return static_cast(value); + } + return BigInt::fromInt64(runtime, value); +} + +Value unsignedInteger64ToEngineValue(Runtime& runtime, uint64_t value) { + constexpr uint64_t maxSafeInteger = 9007199254740991ULL; + if (value <= maxSafeInteger) { + return static_cast(value); + } + return BigInt::fromUint64(runtime, value); +} + +bool parseIntegerTextToUintptr(const std::string& text, uintptr_t* address) { + if (address == nullptr) { + return false; + } + if (text.empty()) { + return false; + } + + char* end = nullptr; + if (text[0] == '-') { + long long signedValue = std::strtoll(text.c_str(), &end, 10); + if (end == nullptr || *end != '\0') { + return false; + } + *address = static_cast(static_cast(signedValue)); + return true; + } + + int base = 10; + const char* start = text.c_str(); + if (text.size() > 2 && text[0] == '0' && + (text[1] == 'x' || text[1] == 'X')) { + base = 16; + } + unsigned long long unsignedValue = std::strtoull(start, &end, base); + if (end == nullptr || *end != '\0') { + return false; + } + *address = static_cast(unsignedValue); + return true; +} + +bool parseBigIntToUintptr(Runtime& runtime, const BigInt& bigint, + uintptr_t* address) { + return parseIntegerTextToUintptr(bigint.toString(runtime, 10).utf8(runtime), + address); +} + +bool readEngineBuffer(Runtime& runtime, const Object& object, const uint8_t** data, + size_t* byteLength) { + if (data == nullptr || byteLength == nullptr) { + return false; + } + + if (object.isArrayBuffer(runtime)) { + ArrayBuffer buffer = object.getArrayBuffer(runtime); + *data = buffer.data(runtime); + *byteLength = buffer.size(runtime); + return true; + } + + Value bufferValue = object.getProperty(runtime, "buffer"); + if (!bufferValue.isObject()) { + return false; + } + Object bufferObject = bufferValue.asObject(runtime); + if (!bufferObject.isArrayBuffer(runtime)) { + return false; + } + + size_t byteOffset = 0; + size_t viewByteLength = 0; + Value offsetValue = object.getProperty(runtime, "byteOffset"); + if (offsetValue.isNumber()) { + byteOffset = static_cast(std::max(0, offsetValue.getNumber())); + } + Value lengthValue = object.getProperty(runtime, "byteLength"); + if (lengthValue.isNumber()) { + viewByteLength = static_cast(std::max(0, lengthValue.getNumber())); + } + + ArrayBuffer buffer = bufferObject.getArrayBuffer(runtime); + if (byteOffset > buffer.size(runtime)) { + return false; + } + if (viewByteLength == 0 || byteOffset + viewByteLength > buffer.size(runtime)) { + viewByteLength = buffer.size(runtime) - byteOffset; + } + *data = buffer.data(runtime) + byteOffset; + *byteLength = viewByteLength; + return true; +} + +uint32_t rawTypeKind(MDTypeKind kind) { + return static_cast(kind); +} + +MDTypeKind stripTypeFlags(MDTypeKind kind) { + uint32_t raw = rawTypeKind(kind); + raw &= ~static_cast(metagen::mdTypeFlagNext); + raw &= ~static_cast(metagen::mdTypeFlagVariadic); + return static_cast(raw); +} + +size_t alignUp(size_t value, size_t alignment) { + if (alignment == 0) { + return value; + } + return ((value + alignment - 1) / alignment) * alignment; +} + +ffi_type* ffiTypeForEngineKind(MDTypeKind kind) { + switch (kind) { + case metagen::mdTypeChar: + return &ffi_type_sint8; + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + case metagen::mdTypeBool: + return &ffi_type_uint8; + case metagen::mdTypeSShort: + return &ffi_type_sint16; + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + return &ffi_type_uint16; + case metagen::mdTypeSInt: + return &ffi_type_sint32; + case metagen::mdTypeUInt: + return &ffi_type_uint32; + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return &ffi_type_sint64; + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return &ffi_type_uint64; + case metagen::mdTypeFloat: + return &ffi_type_float; + case metagen::mdTypeDouble: + return &ffi_type_double; + case metagen::mdTypeVoid: + return &ffi_type_void; + case metagen::mdTypeString: + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + case metagen::mdTypeClass: + case metagen::mdTypeSelector: + case metagen::mdTypePointer: + case metagen::mdTypeOpaquePointer: + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: + return &ffi_type_pointer; + default: + return nullptr; + } +} + +bool isSupportedEngineKind(MDTypeKind kind) { + switch (kind) { + default: + return ffiTypeForEngineKind(kind) != nullptr; + } +} + +void skipMetadataEngineTypePayload(MDMetadataReader* metadata, MDSectionOffset* offset, + MDTypeKind kind); + +void skipMetadataEngineType(MDMetadataReader* metadata, MDSectionOffset* offset) { + MDTypeKind kind = stripTypeFlags(metadata->getTypeKind(*offset)); + *offset += sizeof(MDTypeKind); + skipMetadataEngineTypePayload(metadata, offset, kind); +} + +void skipMetadataEngineTypePayload(MDMetadataReader* metadata, MDSectionOffset* offset, + MDTypeKind kind) { + switch (kind) { + case metagen::mdTypeClassObject: { + auto classOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + bool next = (classOffset & metagen::mdSectionOffsetNext) != 0; + while (next) { + auto protocolOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + case metagen::mdTypeProtocolObject: { + bool next = true; + while (next) { + auto protocolOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + case metagen::mdTypeArray: + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: + *offset += sizeof(uint16_t); + skipMetadataEngineType(metadata, offset); + break; + case metagen::mdTypeStruct: + *offset += sizeof(MDSectionOffset); + break; + case metagen::mdTypePointer: + skipMetadataEngineType(metadata, offset); + break; + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: + *offset += sizeof(MDSectionOffset); + break; + default: + break; + } +} + +NativeApiType parseMetadataEngineType(MDMetadataReader* metadata, + MDSectionOffset* offset, + NativeApiBridge* bridge) { + MDTypeKind rawKind = metadata->getTypeKind(*offset); + MDTypeKind kind = stripTypeFlags(rawKind); + *offset += sizeof(MDTypeKind); + + NativeApiType type; + type.kind = kind; + + switch (kind) { + case metagen::mdTypeArray: { + type.arraySize = metadata->getArraySize(*offset); + *offset += sizeof(uint16_t); + type.elementType = + std::make_shared( + parseMetadataEngineType(metadata, offset, bridge)); + auto ffiOwner = std::make_shared(); + ffiOwner->elements.reserve(static_cast(type.arraySize) + 1); + ffi_type* elementFfiType = type.elementType->ffiType != nullptr + ? type.elementType->ffiType + : &ffi_type_pointer; + for (uint16_t i = 0; i < type.arraySize; i++) { + ffiOwner->elements.push_back(elementFfiType); + } + ffiOwner->finalize(); + type.ownedFfiType = ffiOwner; + type.ffiType = &ffiOwner->type; + type.supported = type.elementType->supported; + return type; + } + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: { + type.arraySize = metadata->getArraySize(*offset); + *offset += sizeof(uint16_t); + type.elementType = + std::make_shared( + parseMetadataEngineType(metadata, offset, bridge)); + auto ffiOwner = std::make_shared(); +#if defined(FFI_TYPE_EXT_VECTOR) + ffiOwner->type.type = + kind == metagen::mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_EXT_VECTOR; +#else + ffiOwner->type.type = + kind == metagen::mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_STRUCT; +#endif + ffi_type* elementFfiType = type.elementType->ffiType != nullptr + ? type.elementType->ffiType + : &ffi_type_float; + size_t lanes = std::max(type.arraySize, 1); + size_t abiLanes = lanes == 3 ? 4 : lanes; + size_t elementSize = std::max(elementFfiType->size, sizeof(float)); + size_t elementAlignment = + std::max(elementFfiType->alignment, static_cast(1)); + ffiOwner->elements.reserve(abiLanes + 1); + for (size_t i = 0; i < abiLanes; i++) { + ffiOwner->elements.push_back(elementFfiType); + } + ffiOwner->finalize(); + size_t vectorAlignment = elementAlignment; + if (kind != metagen::mdTypeComplex) { + size_t packedSize = abiLanes * elementSize; + size_t preferredAlignment = packedSize >= 16 ? 16 : packedSize; + vectorAlignment = std::max(vectorAlignment, preferredAlignment); + } + vectorAlignment = std::min(vectorAlignment, 16); + ffiOwner->type.alignment = static_cast(vectorAlignment); + ffiOwner->type.size = alignUp(abiLanes * elementSize, vectorAlignment); + type.ownedFfiType = ffiOwner; + type.ffiType = &ffiOwner->type; + type.supported = type.elementType->supported; + return type; + } + case metagen::mdTypeStruct: { + auto structOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + bool isUnion = (structOffset & metagen::mdSectionOffsetNext) != 0; + structOffset &= ~metagen::mdSectionOffsetNext; + if (structOffset == MD_SECTION_OFFSET_NULL || bridge == nullptr) { + type.kind = metagen::mdTypePointer; + type.ffiType = &ffi_type_pointer; + type.supported = true; + return type; + } + + MDSectionOffset absoluteOffset = + structOffset + (isUnion ? metadata->unionsOffset : metadata->structsOffset); + type.aggregateOffset = absoluteOffset; + type.aggregateIsUnion = isUnion; + type.aggregateInfo = bridge->aggregateInfoFor(absoluteOffset, isUnion); + type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr + ? &type.aggregateInfo->ffi->type + : nullptr; + type.supported = type.ffiType != nullptr; + return type; + } + case metagen::mdTypePointer: + type.elementType = + std::make_shared( + parseMetadataEngineType(metadata, offset, bridge)); + type.ffiType = &ffi_type_pointer; + type.supported = true; + return type; + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: + type.signatureOffset = metadata->getOffset(*offset) + metadata->signaturesOffset; + *offset += sizeof(MDSectionOffset); + type.ffiType = &ffi_type_pointer; + type.supported = true; + return type; + case metagen::mdTypeClassObject: { + auto classOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + bool next = (classOffset & metagen::mdSectionOffsetNext) != 0; + while (next) { + auto protocolOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + case metagen::mdTypeProtocolObject: { + bool next = true; + while (next) { + auto protocolOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + default: + break; + } + + type.ffiType = ffiTypeForEngineKind(kind); + type.supported = type.ffiType != nullptr && isSupportedEngineKind(kind); + return type; +} + +std::shared_ptr NativeApiBridge::aggregateInfoFor( + MDSectionOffset aggregateOffset, bool isUnion) { + if (metadata_ == nullptr || aggregateOffset == MD_SECTION_OFFSET_NULL) { + return nullptr; + } + + auto cached = aggregateInfoByOffset_.find(aggregateOffset); + if (cached != aggregateInfoByOffset_.end()) { + return cached->second; + } + + auto info = std::make_shared(); + info->offset = aggregateOffset; + info->isUnion = isUnion; + aggregateInfoByOffset_[aggregateOffset] = info; + + if (aggregateInfoInProgress_.find(aggregateOffset) != + aggregateInfoInProgress_.end()) { + auto ffiOwner = std::make_shared(); + ffiOwner->elements.push_back(&ffi_type_pointer); + ffiOwner->finalize(); + info->ffi = ffiOwner; + return info; + } + + aggregateInfoInProgress_.insert(aggregateOffset); + + MDSectionOffset offset = aggregateOffset; + const char* name = metadata_->getString(offset); + info->name = name != nullptr ? name : ""; + offset += sizeof(MDSectionOffset); + info->size = metadata_->getArraySize(offset); + offset += sizeof(uint16_t); + + bool next = true; + while (next) { + MDSectionOffset nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (nameOffset & metagen::mdSectionOffsetNext) != 0; + nameOffset &= ~metagen::mdSectionOffsetNext; + if (nameOffset == MD_SECTION_OFFSET_NULL) { + break; + } + + NativeApiAggregateField field; + const char* fieldName = metadata_->resolveString(nameOffset); + field.name = fieldName != nullptr ? fieldName : ""; + if (!isUnion) { + field.offset = metadata_->getArraySize(offset); + offset += sizeof(uint16_t); + } + field.type = parseMetadataEngineType(metadata_.get(), &offset, this); + info->fields.push_back(std::move(field)); + } + + auto ffiOwner = std::make_shared(); + if (isUnion) { + ffi_type* largest = &ffi_type_uint8; + size_t largestSize = 0; + for (const auto& field : info->fields) { + size_t fieldSize = nativeSizeForType(field.type); + if (field.type.ffiType != nullptr && fieldSize >= largestSize) { + largest = field.type.ffiType; + largestSize = fieldSize; + } + } + ffiOwner->elements.push_back(largest); + } else { + for (const auto& field : info->fields) { + ffiOwner->elements.push_back(field.type.ffiType != nullptr + ? field.type.ffiType + : &ffi_type_pointer); + } + if (ffiOwner->elements.empty()) { + ffiOwner->elements.push_back(&ffi_type_uint8); + } + } + ffiOwner->finalize(); + info->ffi = ffiOwner; + aggregateInfoInProgress_.erase(aggregateOffset); + return info; +} + +ffi_type* ffiTypeForEngineArgument(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeArray: + return &ffi_type_pointer; + default: + return type.ffiType != nullptr ? type.ffiType : &ffi_type_pointer; + } +} + +std::optional parseMetadataEngineSignature( + MDMetadataReader* metadata, MDSectionOffset signatureOffset, + unsigned int implicitArgumentCount, NativeApiBridge* bridge, + bool returnOwned = false) { + if (metadata == nullptr || signatureOffset == MD_SECTION_OFFSET_NULL) { + return std::nullopt; + } + + NativeApiSignature signature; + signature.implicitArgumentCount = implicitArgumentCount; + signature.signatureHash = isPreparedGeneratedDispatchRequired() + ? metadataSignatureHash(metadata, signatureOffset) + : 0; + signature.dispatchFlags = returnOwned ? 1 : 0; + + MDSectionOffset offset = signatureOffset; + MDTypeKind returnKind = metadata->getTypeKind(offset); + uint32_t returnKindRaw = rawTypeKind(returnKind); + bool next = + (returnKindRaw & static_cast(metagen::mdTypeFlagNext)) != 0; + signature.variadic = + (returnKindRaw & static_cast(metagen::mdTypeFlagVariadic)) != 0; + signature.returnType = parseMetadataEngineType(metadata, &offset, bridge); + signature.returnType.returnOwned = returnOwned; + + while (next) { + MDTypeKind argKind = metadata->getTypeKind(offset); + next = (rawTypeKind(argKind) & + static_cast(metagen::mdTypeFlagNext)) != 0; + signature.argumentTypes.push_back(parseMetadataEngineType(metadata, &offset, bridge)); + } + + signature.ffiTypes.reserve(signature.argumentTypes.size() + + implicitArgumentCount); + for (unsigned int i = 0; i < implicitArgumentCount; i++) { + signature.ffiTypes.push_back(&ffi_type_pointer); + } + for (const auto& argType : signature.argumentTypes) { + signature.ffiTypes.push_back(ffiTypeForEngineArgument(argType)); + } + + ffi_status status = ffi_prep_cif( + &signature.cif, FFI_DEFAULT_ABI, + static_cast(signature.ffiTypes.size()), + signature.returnType.ffiType != nullptr ? signature.returnType.ffiType + : &ffi_type_void, + signature.ffiTypes.empty() ? nullptr : signature.ffiTypes.data()); + signature.prepared = status == FFI_OK; + return signature; +} + +bool prepareEngineCallbackSignature(NativeApiSignature* signature) { + if (signature == nullptr) { + return false; + } + + signature->ffiTypes.clear(); + signature->ffiTypes.reserve(signature->argumentTypes.size() + + signature->implicitArgumentCount); + for (unsigned int i = 0; i < signature->implicitArgumentCount; i++) { + signature->ffiTypes.push_back(&ffi_type_pointer); + } + for (const auto& argType : signature->argumentTypes) { + signature->ffiTypes.push_back(ffiTypeForEngineArgument(argType)); + } + + ffi_status status = ffi_prep_cif( + &signature->cif, FFI_DEFAULT_ABI, + static_cast(signature->ffiTypes.size()), + signature->returnType.ffiType != nullptr ? signature->returnType.ffiType + : &ffi_type_void, + signature->ffiTypes.empty() ? nullptr : signature->ffiTypes.data()); + signature->prepared = status == FFI_OK; + return signature->prepared; +} + +const char* skipObjCTypeQualifiers(const char* encoding) { + while (encoding != nullptr && *encoding != '\0' && + std::strchr("rnNoORV", *encoding) != nullptr) { + encoding++; + } + return encoding; +} + +const char* skipObjCTypeFrameOffset(const char* encoding) { + while (encoding != nullptr && *encoding >= '0' && *encoding <= '9') { + encoding++; + } + return encoding; +} + +const char* skipObjCTypeFieldName(const char* encoding, std::string* name) { + if (encoding == nullptr || *encoding != '"') { + return encoding; + } + + encoding++; + const char* start = encoding; + while (*encoding != '\0' && *encoding != '"') { + encoding++; + } + if (name != nullptr) { + *name = std::string(start, static_cast(encoding - start)); + } + return *encoding == '"' ? encoding + 1 : encoding; +} + +std::string normalizedObjCAggregateName(std::string name) { + if (!name.empty() && name.front() == '_') { + name.erase(name.begin()); + } + return name; +} + +std::vector knownObjCAggregateFieldNames( + const std::string& aggregateName, size_t fieldCount) { + std::string name = normalizedObjCAggregateName(aggregateName); + std::vector fields; + if (name == "CGPoint" || name == "NSPoint") { + fields = {"x", "y"}; + } else if (name == "CGSize" || name == "NSSize") { + fields = {"width", "height"}; + } else if (name == "CGRect" || name == "NSRect") { + fields = {"origin", "size"}; + } else if (name == "CGVector") { + fields = {"dx", "dy"}; + } else if (name == "UIEdgeInsets" || name == "NSEdgeInsets") { + fields = {"top", "left", "bottom", "right"}; + } else if (name == "NSDirectionalEdgeInsets") { + fields = {"top", "leading", "bottom", "trailing"}; + } else if (name == "NSRange" || name == "CFRange") { + fields = {"location", "length"}; + } else if (name == "CGAffineTransform") { + fields = {"a", "b", "c", "d", "tx", "ty"}; + } else if (name == "CATransform3D") { + fields = {"m11", "m12", "m13", "m14", "m21", "m22", "m23", "m24", + "m31", "m32", "m33", "m34", "m41", "m42", "m43", "m44"}; + } + + if (fields.size() != fieldCount) { + fields.clear(); + } + return fields; +} + +const NativeApiSymbol* findObjCAggregateSymbol( + NativeApiBridge* bridge, const std::string& name, bool isUnion) { + if (bridge == nullptr || name.empty()) { + return nullptr; + } + + std::vector candidates; + candidates.push_back(name); + std::string normalized = normalizedObjCAggregateName(name); + if (normalized != name) { + candidates.push_back(normalized); + } else { + candidates.push_back("_" + name); + } + constexpr const char* suffix = "Struct"; + if (normalized.size() > std::strlen(suffix) && + normalized.compare(normalized.size() - std::strlen(suffix), + std::strlen(suffix), suffix) == 0) { + candidates.push_back( + normalized.substr(0, normalized.size() - std::strlen(suffix))); + } else { + candidates.push_back(normalized + suffix); + } + + for (const auto& candidate : candidates) { + const NativeApiSymbol* symbol = + isUnion ? bridge->findUnion(candidate) : bridge->findStruct(candidate); + if (symbol == nullptr) { + symbol = bridge->findAggregate(candidate); + } + if (symbol != nullptr) { + return symbol; + } + } + + return nullptr; +} + +void applyObjCEncodingSizeAndAlignment(const char* encoding, + NativeApiFfiType* ffiType, + uint16_t* sizeOut = nullptr) { + if (encoding == nullptr || ffiType == nullptr) { + return; + } + + NSUInteger size = 0; + NSUInteger alignment = 0; + NSGetSizeAndAlignment(encoding, &size, &alignment); + if (size > 0) { + ffiType->type.size = static_cast(size); + if (sizeOut != nullptr) { + *sizeOut = static_cast(std::min( + size, static_cast(std::numeric_limits::max()))); + } + } + if (alignment > 0) { + ffiType->type.alignment = static_cast(alignment); + } +} + +NativeApiType parseObjCEncodedEngineType( + const char* encoding, NativeApiBridge* bridge = nullptr, + const char** endEncoding = nullptr); + +bool unsupportedEngineType(const NativeApiType& type); + +NativeApiType parseObjCEncodedAggregateEngineType( + const char* encoding, NativeApiBridge* bridge, const char** endEncoding) { + NativeApiType type; + type.kind = metagen::mdTypeStruct; + + const bool isUnion = *encoding == '('; + const char close = isUnion ? ')' : '}'; + const char* cursor = encoding + 1; + const char* nameStart = cursor; + while (*cursor != '\0' && *cursor != '=' && *cursor != close) { + cursor++; + } + std::string aggregateName(nameStart, static_cast(cursor - nameStart)); + + if (const NativeApiSymbol* symbol = + findObjCAggregateSymbol(bridge, aggregateName, isUnion)) { + type.aggregateOffset = symbol->offset; + type.aggregateIsUnion = symbol->kind == NativeApiSymbolKind::Union; + type.aggregateInfo = bridge->aggregateInfoFor(*symbol); + type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr + ? &type.aggregateInfo->ffi->type + : nullptr; + type.supported = type.ffiType != nullptr; + + int depth = 0; + const char* end = encoding; + do { + if (*end == *encoding) { + depth++; + } else if (*end == close) { + depth--; + } + end++; + } while (*end != '\0' && depth > 0); + if (endEncoding != nullptr) { + *endEncoding = end; + } + return type; + } + + auto info = std::make_shared(); + info->name = aggregateName; + info->isUnion = isUnion; + info->offset = MD_SECTION_OFFSET_NULL; + + if (*cursor == '=') { + cursor++; + } + + size_t computedOffset = 0; + size_t maxFieldSize = 0; + size_t fieldIndex = 0; + while (*cursor != '\0' && *cursor != close) { + NativeApiAggregateField field; + std::string encodedFieldName; + cursor = skipObjCTypeFieldName(cursor, &encodedFieldName); + const char* fieldStart = cursor; + const char* fieldEnd = cursor; + field.type = parseObjCEncodedEngineType(cursor, bridge, &fieldEnd); + if (fieldEnd == fieldStart || unsupportedEngineType(field.type)) { + type.supported = false; + type.ffiType = nullptr; + if (endEncoding != nullptr) { + *endEncoding = fieldEnd; + } + return type; + } + + NSUInteger fieldSize = 0; + NSUInteger fieldAlignment = 0; + @try { + NSGetSizeAndAlignment(fieldStart, &fieldSize, &fieldAlignment); + } @catch (NSException*) { + // Some valid method encodings contain standalone bitfields, which + // Foundation refuses to size. Mark the runtime-derived aggregate as + // unsupported so metadata remains authoritative instead of leaking an + // Objective-C exception through the engine host-object boundary. + type.supported = false; + type.ffiType = nullptr; + if (endEncoding != nullptr) { + *endEncoding = fieldEnd; + } + return type; + } + size_t nativeFieldSize = + fieldSize > 0 ? static_cast(fieldSize) + : nativeSizeForType(field.type); + size_t nativeFieldAlignment = + fieldAlignment > 0 ? static_cast(fieldAlignment) + : std::max(1, field.type.ffiType != nullptr + ? field.type.ffiType->alignment + : 1); + if (isUnion) { + field.offset = 0; + maxFieldSize = std::max(maxFieldSize, nativeFieldSize); + } else { + computedOffset = alignUp(computedOffset, nativeFieldAlignment); + field.offset = static_cast(std::min( + computedOffset, std::numeric_limits::max())); + computedOffset += nativeFieldSize; + } + field.name = !encodedFieldName.empty() + ? encodedFieldName + : "field" + std::to_string(fieldIndex); + info->fields.push_back(std::move(field)); + fieldIndex++; + cursor = fieldEnd; + } + + if (*cursor == close) { + cursor++; + } + if (endEncoding != nullptr) { + *endEncoding = cursor; + } + + auto knownNames = knownObjCAggregateFieldNames(aggregateName, info->fields.size()); + for (size_t i = 0; i < knownNames.size(); i++) { + info->fields[i].name = knownNames[i]; + } + + auto ffiOwner = std::make_shared(); + if (isUnion) { + ffi_type* largest = &ffi_type_uint8; + size_t largestSize = 0; + for (const auto& field : info->fields) { + size_t fieldSize = nativeSizeForType(field.type); + if (field.type.ffiType != nullptr && fieldSize >= largestSize) { + largest = field.type.ffiType; + largestSize = fieldSize; + } + } + ffiOwner->elements.push_back(largest); + } else { + for (const auto& field : info->fields) { + ffiOwner->elements.push_back(field.type.ffiType != nullptr + ? field.type.ffiType + : &ffi_type_pointer); + } + } + if (ffiOwner->elements.empty()) { + ffiOwner->elements.push_back(&ffi_type_uint8); + } + ffiOwner->finalize(); + applyObjCEncodingSizeAndAlignment(encoding, ffiOwner.get(), &info->size); + if (info->size == 0) { + info->size = static_cast(std::min( + isUnion ? maxFieldSize : computedOffset, + std::numeric_limits::max())); + } + + info->ffi = ffiOwner; + type.aggregateInfo = info; + type.aggregateOffset = MD_SECTION_OFFSET_NULL; + type.aggregateIsUnion = isUnion; + type.ownedFfiType = ffiOwner; + type.ffiType = &ffiOwner->type; + type.supported = true; + return type; +} + +NativeApiType parseObjCEncodedArrayEngineType( + const char* encoding, NativeApiBridge* bridge, const char** endEncoding) { + NativeApiType type; + type.kind = metagen::mdTypeArray; + + const char* cursor = encoding + 1; + uint16_t count = 0; + while (*cursor >= '0' && *cursor <= '9') { + count = static_cast( + std::min(std::numeric_limits::max(), + (count * 10) + (*cursor - '0'))); + cursor++; + } + type.arraySize = count; + + const char* elementEnd = cursor; + type.elementType = std::make_shared( + parseObjCEncodedEngineType(cursor, bridge, &elementEnd)); + cursor = elementEnd; + if (*cursor == ']') { + cursor++; + } + if (endEncoding != nullptr) { + *endEncoding = cursor; + } + + auto ffiOwner = std::make_shared(); + ffi_type* elementFfiType = + type.elementType != nullptr && type.elementType->ffiType != nullptr + ? type.elementType->ffiType + : &ffi_type_pointer; + for (uint16_t i = 0; i < count; i++) { + ffiOwner->elements.push_back(elementFfiType); + } + if (ffiOwner->elements.empty()) { + ffiOwner->elements.push_back(&ffi_type_uint8); + } + ffiOwner->finalize(); + applyObjCEncodingSizeAndAlignment(encoding, ffiOwner.get()); + + type.ownedFfiType = ffiOwner; + type.ffiType = &ffiOwner->type; + type.supported = type.elementType != nullptr && type.elementType->supported; + return type; +} + +NativeApiType parseObjCEncodedEngineType( + const char* encoding, NativeApiBridge* bridge, const char** endEncoding) { + encoding = skipObjCTypeQualifiers(encoding); + NativeApiType type; + + if (encoding == nullptr || *encoding == '\0') { + type.kind = metagen::mdTypePointer; + type.ffiType = &ffi_type_pointer; + if (endEncoding != nullptr) { + *endEncoding = encoding; + } + return type; + } + + auto finishPrimitive = [&](const char* end) { + type.ffiType = ffiTypeForEngineKind(type.kind); + type.supported = type.ffiType != nullptr; + if (endEncoding != nullptr) { + *endEncoding = end; + } + return type; + }; + + switch (*encoding) { + case 'c': + type.kind = metagen::mdTypeChar; + break; + case 'i': + type.kind = metagen::mdTypeSInt; + break; + case 's': + type.kind = metagen::mdTypeSShort; + break; + case 'l': + case 'q': + type.kind = metagen::mdTypeSInt64; + break; + case 'C': + type.kind = metagen::mdTypeUInt8; + break; + case 'I': + type.kind = metagen::mdTypeUInt; + break; + case 'S': + type.kind = metagen::mdTypeUShort; + break; + case 'L': + case 'Q': + type.kind = metagen::mdTypeUInt64; + break; + case 'f': + type.kind = metagen::mdTypeFloat; + break; + case 'd': + type.kind = metagen::mdTypeDouble; + break; + case 'B': + type.kind = metagen::mdTypeBool; + break; + case 'v': + type.kind = metagen::mdTypeVoid; + break; + case '*': + type.kind = metagen::mdTypeString; + break; + case '@': + if (encoding[1] == '?') { + type.kind = metagen::mdTypeBlock; + return finishPrimitive(encoding + 2); + } + { + const char* objectEnd = encoding + 1; + if (*objectEnd == '"') { + objectEnd++; + while (*objectEnd != '\0' && *objectEnd != '"') { + objectEnd++; + } + if (*objectEnd == '"') { + objectEnd++; + } + } + if (std::strncmp(encoding, "@\"NSString\"", 11) == 0) { + type.kind = metagen::mdTypeNSStringObject; + } else if (std::strncmp(encoding, "@\"NSMutableString\"", 18) == 0) { + type.kind = metagen::mdTypeNSMutableStringObject; + } else { + type.kind = metagen::mdTypeAnyObject; + } + return finishPrimitive(objectEnd); + } + case '#': + type.kind = metagen::mdTypeClass; + break; + case ':': + type.kind = metagen::mdTypeSelector; + break; + case '^': + type.kind = metagen::mdTypePointer; + { + const char* elementEnd = encoding + 1; + type.elementType = std::make_shared( + parseObjCEncodedEngineType(encoding + 1, bridge, &elementEnd)); + type.ffiType = &ffi_type_pointer; + type.supported = true; + if (elementEnd == encoding + 1 && encoding[1] != '\0') { + elementEnd = encoding + 2; + } + if (endEncoding != nullptr) { + *endEncoding = elementEnd; + } + } + return type; + case '{': + case '(': + return parseObjCEncodedAggregateEngineType(encoding, bridge, endEncoding); + case '[': + return parseObjCEncodedArrayEngineType(encoding, bridge, endEncoding); + case 'b': { + type.kind = metagen::mdTypeUInt; + const char* cursor = encoding + 1; + while (*cursor >= '0' && *cursor <= '9') { + cursor++; + } + return finishPrimitive(cursor); + } + case '?': + type.kind = metagen::mdTypeOpaquePointer; + break; + default: + type.kind = metagen::mdTypePointer; + break; + } + + return finishPrimitive(encoding + 1); +} + +std::optional parseObjCCallbackEngineSignature( + const std::string& encodingString, bool block, NativeApiBridge* bridge) { + const char* cursor = skipObjCTypeQualifiers(encodingString.c_str()); + if (cursor == nullptr || *cursor == '\0') { + return std::nullopt; + } + + NativeApiSignature signature; + signature.implicitArgumentCount = block ? 1 : 0; + + const char* returnEnd = cursor; + signature.returnType = parseObjCEncodedEngineType(cursor, bridge, &returnEnd); + if (returnEnd == cursor) { + return std::nullopt; + } + cursor = skipObjCTypeFrameOffset(returnEnd); + + if (block) { + const char* blockSelf = skipObjCTypeQualifiers(cursor); + if (blockSelf != nullptr && blockSelf[0] == '@' && blockSelf[1] == '?') { + cursor = skipObjCTypeFrameOffset(blockSelf + 2); + } + } + + while (cursor != nullptr && *cursor != '\0') { + const char* argStart = skipObjCTypeQualifiers(cursor); + if (argStart == nullptr || *argStart == '\0') { + break; + } + const char* argEnd = argStart; + NativeApiType argType = parseObjCEncodedEngineType(argStart, bridge, &argEnd); + if (argEnd == argStart) { + return std::nullopt; + } + signature.argumentTypes.push_back(std::move(argType)); + cursor = skipObjCTypeFrameOffset(argEnd); + } + + prepareEngineCallbackSignature(&signature); + return signature; +} + +std::optional parseObjCMethodEngineSignature( + Method method, NativeApiBridge* bridge = nullptr) { + if (method == nullptr) { + return std::nullopt; + } + + NativeApiSignature signature; + signature.implicitArgumentCount = 2; + + char* returnEncoding = method_copyReturnType(method); + signature.returnType = parseObjCEncodedEngineType(returnEncoding, bridge); + if (returnEncoding != nullptr) { + free(returnEncoding); + } + + unsigned int totalArgc = method_getNumberOfArguments(method); + for (unsigned int i = 2; i < totalArgc; i++) { + char* argEncoding = method_copyArgumentType(method, i); + signature.argumentTypes.push_back(parseObjCEncodedEngineType(argEncoding, bridge)); + if (argEncoding != nullptr) { + free(argEncoding); + } + } + + signature.ffiTypes.reserve(totalArgc); + signature.ffiTypes.push_back(&ffi_type_pointer); + signature.ffiTypes.push_back(&ffi_type_pointer); + for (const auto& argType : signature.argumentTypes) { + signature.ffiTypes.push_back(ffiTypeForEngineArgument(argType)); + } + + ffi_status status = ffi_prep_cif( + &signature.cif, FFI_DEFAULT_ABI, + static_cast(signature.ffiTypes.size()), + signature.returnType.ffiType != nullptr ? signature.returnType.ffiType + : &ffi_type_void, + signature.ffiTypes.data()); + signature.prepared = status == FFI_OK; + return signature; +} + +bool prepareEngineMethodSignature(NativeApiSignature* signature) { + if (signature == nullptr) { + return false; + } + signature->implicitArgumentCount = 2; + signature->ffiTypes.clear(); + signature->ffiTypes.reserve(signature->argumentTypes.size() + 2); + signature->ffiTypes.push_back(&ffi_type_pointer); + signature->ffiTypes.push_back(&ffi_type_pointer); + for (const auto& argType : signature->argumentTypes) { + ffi_type* ffiType = ffiTypeForEngineArgument(argType); + if (ffiType == nullptr) { + signature->prepared = false; + return false; + } + signature->ffiTypes.push_back(ffiType); + } + ffi_type* returnFfiType = + signature->returnType.ffiType != nullptr ? signature->returnType.ffiType + : &ffi_type_void; + signature->prepared = + ffi_prep_cif(&signature->cif, FFI_DEFAULT_ABI, + static_cast(signature->ffiTypes.size()), + returnFfiType, signature->ffiTypes.data()) == FFI_OK; + return signature->prepared; +} + +bool reconcileObjCMethodRuntimeType(NativeApiType* metadataType, + const NativeApiType& runtimeType, + bool* abiChanged) { + if (metadataType == nullptr || unsupportedEngineType(runtimeType)) { + return false; + } + + if (runtimeType.kind == metagen::mdTypeBlock && + metadataType->kind == metagen::mdTypeFunctionPointer) { + metadataType->kind = metagen::mdTypeBlock; + metadataType->ffiType = runtimeType.ffiType; + metadataType->supported = runtimeType.supported; + return true; + } + + // Do not overwrite aggregate (struct/union) metadata types with the + // anonymous ObjC runtime encoding: the metadata type carries the real + // field names and layout that the runtime encoding (e.g. "{?=qqq}") lacks. + (void)abiChanged; + return false; +} + +bool reconcileObjCMethodRuntimeSignature(NativeApiSignature* signature, + const NativeApiSignature& runtime) { + if (signature == nullptr || + signature->argumentTypes.size() != runtime.argumentTypes.size()) { + return false; + } + + bool changed = false; + bool abiChanged = false; + changed |= reconcileObjCMethodRuntimeType(&signature->returnType, + runtime.returnType, &abiChanged); + for (size_t i = 0; i < signature->argumentTypes.size(); i++) { + changed |= reconcileObjCMethodRuntimeType(&signature->argumentTypes[i], + runtime.argumentTypes[i], + &abiChanged); + } + + if (abiChanged) { + signature->signatureHash = 0; + } + return !changed || prepareEngineMethodSignature(signature); +} + +bool unsupportedEngineType(const NativeApiType& type) { + if (type.kind == metagen::mdTypeStruct && type.aggregateInfo != nullptr && + type.aggregateInfo->ffi != nullptr) { + return false; + } + return !type.supported || type.ffiType == nullptr; +} + +bool signatureSupportedForEngineCallback(const NativeApiSignature& signature) { + if (!signature.prepared || signature.variadic || + unsupportedEngineType(signature.returnType)) { + return false; + } + for (const auto& argType : signature.argumentTypes) { + if (unsupportedEngineType(argType)) { + return false; + } + } + return true; +} + +std::shared_ptr createEngineCallback( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, Function function, bool block, + NativeApiCallbackThreadPolicy threadPolicy = + NativeApiCallbackThreadPolicy::Default) { + if (bridge == nullptr || bridge->metadata() == nullptr || + type.signatureOffset == MD_SECTION_OFFSET_NULL) { + throw JSError( + runtime, "Native callback metadata is unavailable."); + } + + auto parsed = parseMetadataEngineSignature( + bridge->metadata(), type.signatureOffset, block ? 1 : 0, bridge.get()); + if (!parsed || !signatureSupportedForEngineCallback(*parsed)) { + throw JSError( + runtime, "Native callback signature is not supported by backend."); + } + + auto signature = + std::make_shared(std::move(*parsed)); + uintptr_t roundTripValidationKey = + NativeApiBridge::callbackRoundTripValidationKey(type); + auto callback = std::make_shared( + runtime, bridge, std::move(signature), std::move(function), block, + threadPolicy, false, roundTripValidationKey); + if (block) { + callback->retainInitialBlockLifetime(callback); + } else { + bridge->retainEngineLifetime(callback); + } + return callback; +} + +std::shared_ptr createEngineCallback( + Runtime& runtime, const std::shared_ptr& bridge, + const std::string& objcSignatureEncoding, Function function, bool block, + NativeApiCallbackThreadPolicy threadPolicy = + NativeApiCallbackThreadPolicy::Default, + uintptr_t roundTripValidationKey = 0) { + if (bridge == nullptr || objcSignatureEncoding.empty()) { + throw JSError(runtime, "Native callback encoding is unavailable."); + } + + auto parsed = parseObjCCallbackEngineSignature( + objcSignatureEncoding, block, bridge.get()); + if (!parsed || !signatureSupportedForEngineCallback(*parsed)) { + throw JSError( + runtime, "Native callback signature is not supported by backend."); + } + + auto signature = + std::make_shared(std::move(*parsed)); + auto callback = std::make_shared( + runtime, bridge, std::move(signature), std::move(function), block, + threadPolicy, false, roundTripValidationKey); + if (block) { + callback->retainInitialBlockLifetime(callback); + } else { + bridge->retainEngineLifetime(callback); + } + return callback; +} + +std::shared_ptr createEngineMethodCallback( + Runtime& runtime, const std::shared_ptr& bridge, + const std::string& selectorName, MDSectionOffset signatureOffset, + Function function, bool returnOwned, Class methodBaseClass = Nil, + NativeApiMethodCallbackPolicy methodPolicy = {}) { + if (bridge == nullptr || bridge->metadata() == nullptr || + signatureOffset == MD_SECTION_OFFSET_NULL) { + throw JSError( + runtime, "Native method callback metadata is unavailable."); + } + + auto parsed = parseMetadataEngineSignature( + bridge->metadata(), signatureOffset, 2, bridge.get(), returnOwned); + if (!parsed || !signatureSupportedForEngineCallback(*parsed)) { + throw JSError( + runtime, "Native method callback signature is not supported by backend."); + } + parsed->selectorName = selectorName; + + auto signature = + std::make_shared(std::move(*parsed)); + auto threadPolicy = readEngineCallbackThreadPolicy(runtime, function); + // A policy passed explicitly by the caller (e.g. the auto-installed + // accessor/construction re-entry guards) wins; otherwise fall back to + // whatever the JS function itself was tagged with via nativeMethodPolicy(). + if (isEmptyMethodCallbackPolicy(methodPolicy)) { + methodPolicy = readEngineMethodCallbackPolicy(runtime, function); + } + auto callback = std::make_shared( + runtime, bridge, std::move(signature), std::move(function), false, + threadPolicy, true, 0, std::move(methodPolicy), methodBaseClass); + bridge->retainEngineLifetime(callback); + return callback; +} + +std::shared_ptr createEngineMethodCallback( + Runtime& runtime, const std::shared_ptr& bridge, + const std::string& selectorName, NativeApiSignature signature, + Function function, Class methodBaseClass = Nil, + NativeApiMethodCallbackPolicy methodPolicy = {}) { + signature.selectorName = selectorName; + prepareEngineMethodSignature(&signature); + if (!signatureSupportedForEngineCallback(signature)) { + throw JSError( + runtime, "Native method callback signature is not supported by backend."); + } + + auto sharedSignature = + std::make_shared(std::move(signature)); + auto threadPolicy = readEngineCallbackThreadPolicy(runtime, function); + if (isEmptyMethodCallbackPolicy(methodPolicy)) { + methodPolicy = readEngineMethodCallbackPolicy(runtime, function); + } + auto callback = std::make_shared( + runtime, bridge, std::move(sharedSignature), std::move(function), false, + threadPolicy, true, 0, std::move(methodPolicy), methodBaseClass); + bridge->retainEngineLifetime(callback); + return callback; +} diff --git a/NativeScript/ffi/objc/shared/bridge/ClassBuilder.mm b/NativeScript/ffi/objc/shared/bridge/ClassBuilder.mm new file mode 100644 index 000000000..170d025d0 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/ClassBuilder.mm @@ -0,0 +1,896 @@ +std::string readOptionalStringProperty(Runtime& runtime, const Object& object, + const char* name) { + if (name == nullptr || !object.hasProperty(runtime, name)) { + return ""; + } + Value value = object.getProperty(runtime, name); + return value.isString() ? value.asString(runtime).utf8(runtime) : ""; +} + +struct NativeApiClassBuilderRegistration { + std::shared_ptr runtimeOwner; + Runtime* runtime = nullptr; + std::shared_ptr bridge; +}; + +std::mutex gNativeApiClassBuilderMutex; +std::unordered_map + gNativeApiClassBuilders; +struct NativeApiKnownExposedMethod { + std::string selectorName; + NativeApiSignature signature; +}; +std::mutex gNativeApiKnownExposedMethodsMutex; +std::unordered_map + gNativeApiKnownExposedMethods; + +void rememberNativeApiClassBuilder( + Runtime& runtime, const std::shared_ptr& bridge, + Class cls) { + if (cls == Nil) { + return; + } + std::lock_guard lock(gNativeApiClassBuilderMutex); + auto runtimeOwner = retainNativeApiRuntime(runtime); + gNativeApiClassBuilders[cls] = NativeApiClassBuilderRegistration{ + .runtimeOwner = runtimeOwner, + .runtime = runtimeOwner.get(), + .bridge = bridge, + }; +} + +void rememberNativeApiKnownExposedMethod( + const std::string& selectorName, const NativeApiSignature& signature) { + if (selectorName.empty()) { + return; + } + NativeApiKnownExposedMethod method{ + .selectorName = selectorName, + .signature = signature, + }; + std::lock_guard lock(gNativeApiKnownExposedMethodsMutex); + gNativeApiKnownExposedMethods[selectorName] = method; + gNativeApiKnownExposedMethods[jsifySelector(selectorName.c_str())] = + std::move(method); +} + +std::optional knownNativeApiExposedMethod( + const std::string& name) { + std::lock_guard lock(gNativeApiKnownExposedMethodsMutex); + auto it = gNativeApiKnownExposedMethods.find(name); + if (it == gNativeApiKnownExposedMethods.end()) { + return std::nullopt; + } + NativeApiKnownExposedMethod method = it->second; + prepareEngineMethodSignature(&method.signature); + return method; +} + +// An ObjC init convention lets an initializer return an object other than +// `self` (most commonly `self` itself, but a class cluster/singleton +// initializer can return a completely different receiver). When it returns +// the SAME receiver we already wrapped for this call, the JS side should keep +// resolving to that one preserved wrapper rather than creating (and briefly +// GC'ing) a second, divergent one for the identical native object — so the +// stale/duplicate wrapper here is detached (its bridge state, e.g. expandos, +// handed to the preserved wrapper) rather than left to tear down the shared +// native receiver's bridge state on its own destruction. +std::optional preservedNativeApiInitializerSelfReturn( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const Value& result, const Value& receiverValue) { + if (bridge == nullptr || receiver == nil || !receiverValue.isObject()) { + return std::nullopt; + } + + id resultObject = + NativeApiObjectHostObject::nativeObjectFromValue(runtime, result); + if (resultObject != receiver) { + return std::nullopt; + } + + Object receiverObject = receiverValue.asObject(runtime); + if (!receiverObject.isHostObject(runtime)) { + return std::nullopt; + } + + auto receiverHostObject = + receiverObject.getHostObject(runtime); + if (receiverHostObject == nullptr || + receiverHostObject->object() != receiver) { + return std::nullopt; + } + + std::shared_ptr resultHostObject; + if (result.isObject()) { + Object resultObjectValue = result.asObject(runtime); + if (resultObjectValue.isHostObject(runtime)) { + resultHostObject = + resultObjectValue.getHostObject(runtime); + } + } + + if (resultHostObject != nullptr && resultHostObject != receiverHostObject) { + resultHostObject->detachObjectPreservingBridgeState(receiver); + } + + Value preserved(runtime, receiverValue); + bridge->rememberNativeObjectRoundTripValue(runtime, receiver, preserved); + return preserved; +} + +// $base/super dispatch wrapper for ClassBuilder subclasses: calls the ObjC +// super implementation, then — for initializers only — applies the +// preserved-self-return handling above. +Value callNativeApiBaseObjectSelector( + Runtime& runtime, const std::shared_ptr& bridge, + const Object& receiverObject, + const std::shared_ptr& receiverHostObject, + id receiver, const std::string& selectorName, + const NativeApiMember* member, const Value* args, size_t count, + Class dispatchClass) { + Value result = receiverHostObject->callObjectSelector( + runtime, selectorName, member, args, count, dispatchClass); + + if (selectorName.rfind("init", 0) != 0) { + return result; + } + + if (auto preserved = preservedNativeApiInitializerSelfReturn( + runtime, bridge, receiver, result, Value(runtime, receiverObject))) { + return std::move(*preserved); + } + return result; +} + +std::optional +findNativeApiClassBuilder(id object) { + Class cls = object != nil ? object_getClass(object) : Nil; + std::lock_guard lock(gNativeApiClassBuilderMutex); + while (cls != Nil) { + auto it = gNativeApiClassBuilders.find(cls); + if (it != gNativeApiClassBuilders.end()) { + return it->second; + } + cls = class_getSuperclass(cls); + } + return std::nullopt; +} + +const char* nativeApiEngineFastEnumerationEncoding() { + static const char* encoding = nullptr; + if (encoding == nullptr) { + struct objc_method_description desc = protocol_getMethodDescription( + @protocol(NSFastEnumeration), + @selector(countByEnumeratingWithState:objects:count:), YES, YES); + encoding = desc.types; + } + return encoding; +} + +NSUInteger nativeApiEngineSymbolIteratorCountByEnumerating( + id self, SEL, NSFastEnumerationState* state, + id __unsafe_unretained stackbuf[], NSUInteger len) { + if (len == 0 || state == nullptr || stackbuf == nullptr) { + return 0; + } + + auto registration = findNativeApiClassBuilder(self); + if (!registration || registration->runtime == nullptr || + registration->bridge == nullptr) { + return 0; + } + + Runtime& runtime = *registration->runtime; + NativeApiRuntimeScope runtimeScope(runtime); + auto bridge = registration->bridge; + try { + Value receiver = makeNativeObjectValue(runtime, bridge, self, false); + if (!receiver.isObject()) { + return 0; + } + + Value iteratorFactoryValue = + runtime.global().getProperty(runtime, + "__nativeScriptCreateNativeApiIterator"); + if (!iteratorFactoryValue.isObject() || + !iteratorFactoryValue.asObject(runtime).isFunction(runtime)) { + return 0; + } + + Function iteratorFactory = + iteratorFactoryValue.asObject(runtime).asFunction(runtime); + Value prototype = + bridge->findClassPrototype(runtime, object_getClass(self)); + Value iteratorValue = + prototype.isObject() + ? iteratorFactory.call(runtime, Value(runtime, receiver), + Value(runtime, prototype)) + : iteratorFactory.call(runtime, Value(runtime, receiver)); + if (!iteratorValue.isObject()) { + return 0; + } + Object iterator = iteratorValue.asObject(runtime); + Value nextValue = iterator.getProperty(runtime, "next"); + if (!nextValue.isObject() || + !nextValue.asObject(runtime).isFunction(runtime)) { + return 0; + } + Function next = nextValue.asObject(runtime).asFunction(runtime); + + auto callNext = [&]() -> Value { + return next.callWithThis(runtime, iterator); + }; + + for (unsigned long skipped = 0; skipped < state->state; skipped++) { + Value skippedResult = callNext(); + if (!skippedResult.isObject()) { + return 0; + } + Value doneValue = + skippedResult.asObject(runtime).getProperty(runtime, "done"); + if (doneValue.isBool() && doneValue.getBool()) { + return 0; + } + } + + NSUInteger count = 0; + while (count < len) { + Value nextResult = callNext(); + if (!nextResult.isObject()) { + break; + } + Object nextObject = nextResult.asObject(runtime); + Value doneValue = nextObject.getProperty(runtime, "done"); + if (doneValue.isBool() && doneValue.getBool()) { + break; + } + + Value value = nextObject.getProperty(runtime, "value"); + NativeApiArgumentFrame frame(1); + id nativeValue = objectFromEngineValue(runtime, bridge, value, frame, false); + if (nativeValue != nil) { + [nativeValue retain]; + [nativeValue autorelease]; + } + stackbuf[count++] = nativeValue; + } + + state->itemsPtr = stackbuf; + state->mutationsPtr = &state->extra[0]; + state->extra[0] = 0; + state->state += count; + return count; + } catch (const std::exception&) { + return 0; + } +} + +NativeApiSymbol runtimeSymbolForClass( + const std::shared_ptr& bridge, Class cls) { + if (bridge != nullptr) { + if (const NativeApiSymbol* symbol = bridge->findClassForRuntimeClass(cls)) { + return *symbol; + } + } + + const char* name = cls != Nil ? class_getName(cls) : ""; + return NativeApiSymbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; +} + +std::string nextAvailableEngineClassName(const std::string& requestedName) { + if (requestedName.empty()) { + return ""; + } + if (objc_lookUpClass(requestedName.c_str()) == Nil) { + return requestedName; + } + + size_t suffix = 1; + std::string candidate; + do { + candidate = requestedName + std::to_string(suffix++); + } while (objc_lookUpClass(candidate.c_str()) != Nil); + return candidate; +} + +std::vector methodOverridesForName( + const std::vector& members, const std::string& name) { + std::vector result; + std::unordered_set selectors; + for (const auto& member : members) { + if (member.property || member.name != name || + (member.flags & metagen::mdMemberStatic) != 0 || + member.selectorName.empty()) { + continue; + } + if (selectors.insert(member.selectorName).second) { + result.push_back(member); + } + } + return result; +} + +const NativeApiMember* propertyOverrideForName( + const std::vector& members, const std::string& name) { + const NativeApiMember* propertyMember = nullptr; + for (const auto& member : members) { + if (member.property && member.name == name && + (member.flags & metagen::mdMemberStatic) == 0) { + if (propertyMember == nullptr) { + propertyMember = &member; + } + if (!member.readonly && !member.setterSelectorName.empty()) { + return &member; + } + } + } + return propertyMember; +} + +void addEngineOverrideMethod(Runtime& runtime, + const std::shared_ptr& bridge, + Class nativeClass, Class baseClass, + const std::string& selectorName, + MDSectionOffset signatureOffset, + bool returnOwned, Function function, + NativeApiMethodCallbackPolicy methodPolicy = {}) { + if (selectorName.empty() || signatureOffset == MD_SECTION_OFFSET_NULL) { + return; + } + + auto callback = createEngineMethodCallback(runtime, bridge, selectorName, + signatureOffset, std::move(function), + returnOwned, baseClass, + std::move(methodPolicy)); + SEL selector = sel_registerName(selectorName.c_str()); + std::string metadataEncoding = + objcMethodSignatureForEngineSignature(callback->signature()); + class_replaceMethod(nativeClass, selector, + reinterpret_cast(callback->functionPointer()), + metadataEncoding.c_str()); +} + +Value getObjectPropertyOrUndefined(Runtime& runtime, const Object& object, + const std::string& name) { + return object.hasProperty(runtime, name.c_str()) + ? object.getProperty(runtime, name.c_str()) + : Value::undefined(); +} + +// Auto-applied to native accessor (getter/setter) overrides: suppresses +// re-entrancy while the accessor machinery is already dispatching through +// this same receiver (set/cleared around JS-subclass accessor invocation). +NativeApiMethodCallbackPolicy nativeAccessorCallbackPolicy( + NativeApiMethodCallbackPolicy policy = {}) { + policy.skipCallbackIfAssociatedObjectTruthy.push_back( + "__nativeApiAccessorCallbackState"); + return policy; +} + +Class dispatchSuperclassForEngineDerivedReceiver(id receiver, + Class defaultSuperclass) { + if (receiver == nil) { + return Nil; + } + + Class receiverClass = object_getClass(receiver); + if (receiverClass == Nil || + !class_conformsToProtocol(receiverClass, + @protocol(NativeApiClassBuilderProtocol))) { + return Nil; + } + + Class superclass = class_getSuperclass(receiverClass); + return superclass != Nil ? superclass : defaultSuperclass; +} + +std::optional functionForSelector(Runtime& runtime, + const Object& methods, + const std::string& selectorName) { + Value value = getObjectPropertyOrUndefined(runtime, methods, selectorName); + if (!value.isObject() || !value.asObject(runtime).isFunction(runtime)) { + std::string jsName = jsifySelector(selectorName.c_str()); + if (jsName != selectorName) { + value = getObjectPropertyOrUndefined(runtime, methods, jsName); + } + } + if (!value.isObject() || !value.asObject(runtime).isFunction(runtime)) { + return std::nullopt; + } + return value.asObject(runtime).asFunction(runtime); +} + +std::optional readExposedType( + Runtime& runtime, const std::shared_ptr& bridge, + const Object& descriptor, const char* propertyName) { + if (!descriptor.hasProperty(runtime, propertyName)) { + return std::nullopt; + } + return interopTypeFromValue(runtime, bridge, + descriptor.getProperty(runtime, propertyName)); +} + +std::optional exposedMethodSignature( + Runtime& runtime, const std::shared_ptr& bridge, + const std::string& selectorName, const Object& descriptor) { + NativeApiSignature signature; + if (auto returnType = readExposedType(runtime, bridge, descriptor, "returns")) { + signature.returnType = *returnType; + } else { + signature.returnType = primitiveInteropType(metagen::mdTypeVoid); + } + + Value paramsValue = getObjectPropertyOrUndefined(runtime, descriptor, "params"); + if (!paramsValue.isUndefined() && !paramsValue.isNull()) { + if (!paramsValue.isObject() || !paramsValue.asObject(runtime).isArray(runtime)) { + throw JSError( + runtime, "exposedMethods params must be an array."); + } + Array params = paramsValue.asObject(runtime).getArray(runtime); + for (size_t i = 0; i < params.size(runtime); i++) { + Value typeValue = params.getValueAtIndex(runtime, i); + auto type = interopTypeFromValue(runtime, bridge, typeValue); + if (!type) { + throw JSError( + runtime, "exposedMethods contains an unsupported parameter type."); + } + signature.argumentTypes.push_back(*type); + } + } + + // A colon-less selector may still declare params (@nativescript/core + // exposes onReceive with one NSNotification param); the Objective-C runtime + // accepts such methods and callers like NSNotificationCenter invoke them + // with the argument. Only reject a mismatch when the selector explicitly + // declares argument slots. + size_t selectorArguments = selectorArgumentCount(selectorName); + if (selectorArguments != signature.argumentTypes.size() && selectorArguments != 0) { + throw JSError( + runtime, "exposedMethods selector argument count does not match params."); + } + + prepareEngineMethodSignature(&signature); + return signature; +} + +std::optional runtimeProtocolMethodSignature( + const char* types) { + if (types == nullptr) { + return std::nullopt; + } + + NSMethodSignature* methodSignature = + [NSMethodSignature signatureWithObjCTypes:types]; + if (methodSignature == nil || methodSignature.numberOfArguments < 2) { + return std::nullopt; + } + + NativeApiSignature signature; + signature.implicitArgumentCount = 2; + signature.returnType = + parseObjCEncodedEngineType(methodSignature.methodReturnType); + for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) { + signature.argumentTypes.push_back( + parseObjCEncodedEngineType([methodSignature getArgumentTypeAtIndex:i])); + } + if (unsupportedEngineType(signature.returnType)) { + return std::nullopt; + } + for (const auto& argumentType : signature.argumentTypes) { + if (unsupportedEngineType(argumentType)) { + return std::nullopt; + } + } + return signature; +} + +std::optional protocolSymbolFromEngineValue( + Runtime& runtime, const std::shared_ptr& bridge, + const Value& value) { + if (value.isString()) { + std::string name = value.asString(runtime).utf8(runtime); + if (const NativeApiSymbol* symbol = bridge->findProtocol(name)) { + return *symbol; + } + return std::nullopt; + } + if (!value.isObject()) { + return std::nullopt; + } + + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->symbol(); + } + + if (stringPropertyOrEmpty(runtime, object, "kind") != "protocol") { + return std::nullopt; + } + + std::string runtimeName = stringPropertyOrEmpty(runtime, object, "runtimeName"); + if (!runtimeName.empty()) { + if (const NativeApiSymbol* symbol = bridge->findProtocol(runtimeName)) { + return *symbol; + } + } + + std::string name = stringPropertyOrEmpty(runtime, object, "name"); + if (!name.empty()) { + if (const NativeApiSymbol* symbol = bridge->findProtocol(name)) { + return *symbol; + } + } + + return std::nullopt; +} + +void addEngineExposedMethod(Runtime& runtime, + const std::shared_ptr& bridge, + Class nativeClass, const std::string& selectorName, + NativeApiSignature signature, Function function, + Class methodBaseClass = Nil, + NativeApiMethodCallbackPolicy methodPolicy = {}) { + if (selectorName.empty()) { + return; + } + auto callback = createEngineMethodCallback(runtime, bridge, selectorName, + std::move(signature), std::move(function), + methodBaseClass, + std::move(methodPolicy)); + std::string encoding = objcMethodSignatureForEngineSignature(callback->signature()); + class_replaceMethod(nativeClass, sel_registerName(selectorName.c_str()), + reinterpret_cast(callback->functionPointer()), + encoding.c_str()); +} + +bool addRuntimeProtocolOverrideForName( + Runtime& runtime, const std::shared_ptr& bridge, + Class nativeClass, const std::vector& protocols, + const std::string& propertyName, Function function) { + std::unordered_set visited; + std::function visit = [&](Protocol* protocol) -> bool { + if (protocol == nullptr || !visited.insert(protocol).second) { + return false; + } + + Protocol** inherited = protocol_copyProtocolList(protocol, nullptr); + if (inherited != nullptr) { + unsigned int inheritedCount = 0; + free(inherited); + inherited = protocol_copyProtocolList(protocol, &inheritedCount); + for (unsigned int i = 0; i < inheritedCount; i++) { + if (visit(inherited[i])) { + free(inherited); + return true; + } + } + free(inherited); + } + + for (BOOL required : {YES, NO}) { + unsigned int count = 0; + objc_method_description* descriptions = + protocol_copyMethodDescriptionList(protocol, required, YES, &count); + for (unsigned int i = 0; i < count; i++) { + SEL selector = descriptions[i].name; + const char* selectorName = + selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorName == nullptr || + jsifySelector(selectorName) != propertyName) { + continue; + } + auto signature = runtimeProtocolMethodSignature(descriptions[i].types); + if (signature) { + addEngineExposedMethod(runtime, bridge, nativeClass, selectorName, + std::move(*signature), std::move(function)); + free(descriptions); + return true; + } + } + free(descriptions); + } + return false; + }; + + for (Protocol* protocol : protocols) { + if (visit(protocol)) { + return true; + } + } + return false; +} + +Object getOwnPropertyDescriptor(Runtime& runtime, const Object& object, + const std::string& name) { + Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); + Function getOwnPropertyDescriptor = + objectCtor.getPropertyAsFunction(runtime, "getOwnPropertyDescriptor"); + Value args[] = {Value(runtime, object), makeString(runtime, name)}; + Value descriptorValue = + getOwnPropertyDescriptor.call(runtime, static_cast(args), + static_cast(2)); + return descriptorValue.isObject() ? descriptorValue.asObject(runtime) + : Object(runtime); +} + +Value extendNativeApiClass( + Runtime& runtime, const std::shared_ptr& bridge, + const Value* args, size_t count) { + if (count < 2 || !args[0].isObject() || !args[1].isObject()) { + throw JSError( + runtime, "extendClass expects a native class and method object."); + } + + Class baseClass = classFromEngineValue(runtime, args[0]); + if (baseClass == Nil) { + throw JSError( + runtime, "extendClass can only extend native class constructors."); + } + if (class_conformsToProtocol(baseClass, + @protocol(NativeApiClassBuilderProtocol))) { + throw JSError(runtime, + "Cannot extend an already extended class."); + } + + Object methods = args[1].asObject(runtime); + Object options = count >= 3 && args[2].isObject() + ? args[2].asObject(runtime) + : Object(runtime); + std::string requestedName = readOptionalStringProperty(runtime, options, "name"); + if (requestedName.empty()) { + const char* baseName = class_getName(baseClass); + requestedName = std::string(baseName != nullptr ? baseName : "NSObject") + + "_Extended_" + std::to_string(rand()); + } + + std::string className = nextAvailableEngineClassName(requestedName); + Class nativeClass = objc_allocateClassPair(baseClass, className.c_str(), 0); + if (nativeClass == Nil) { + throw JSError(runtime, "Failed to allocate Objective-C class."); + } + + class_addProtocol(nativeClass, @protocol(NativeApiClassBuilderProtocol)); + rememberNativeApiClassBuilder(runtime, bridge, nativeClass); + + NativeApiSymbol baseSymbol = runtimeSymbolForClass(bridge, baseClass); + std::vector extensionMembers = + bridge->membersForClass(baseSymbol); + std::vector optionProtocols; + Value protocolsValue = getObjectPropertyOrUndefined(runtime, options, "protocols"); + if (protocolsValue.isObject() && + protocolsValue.asObject(runtime).isArray(runtime)) { + Array protocols = protocolsValue.asObject(runtime).getArray(runtime); + for (size_t i = 0; i < protocols.size(runtime); i++) { + Value protocolValue = protocols.getValueAtIndex(runtime, i); + Protocol* protocol = protocolFromEngineValue(runtime, protocolValue); + std::optional protocolSymbol = + protocolSymbolFromEngineValue(runtime, bridge, protocolValue); + if (protocol != nullptr) { + optionProtocols.push_back(protocol); + class_addProtocol(nativeClass, protocol); + if (!protocolSymbol) { + if (const NativeApiSymbol* runtimeSymbol = + bridge->findProtocolForRuntimePointer(protocol)) { + protocolSymbol = *runtimeSymbol; + } + } + } + if (protocolSymbol) { + const auto& protocolMembers = bridge->membersForProtocol(*protocolSymbol); + extensionMembers.insert(extensionMembers.begin(), + protocolMembers.begin(), + protocolMembers.end()); + } + } + } + const auto& members = extensionMembers; + Array propertyNames = methods.getPropertyNames(runtime); + for (size_t i = 0; i < propertyNames.size(runtime); i++) { + Value propertyNameValue = propertyNames.getValueAtIndex(runtime, i); + if (!propertyNameValue.isString()) { + continue; + } + + std::string propertyName = propertyNameValue.asString(runtime).utf8(runtime); + Object descriptor = getOwnPropertyDescriptor(runtime, methods, propertyName); + + Value value = descriptor.getProperty(runtime, "value"); + if (value.isObject() && value.asObject(runtime).isFunction(runtime)) { + auto overrides = methodOverridesForName(members, propertyName); + bool addedOverride = false; + for (const auto& member : overrides) { + if (member.selectorName.empty() || + member.signatureOffset == MD_SECTION_OFFSET_NULL || + member.signatureOffset == 0) { + continue; + } + addEngineOverrideMethod( + runtime, bridge, nativeClass, baseClass, member.selectorName, + member.signatureOffset, + (member.flags & metagen::mdMemberReturnOwned) != 0, + value.asObject(runtime).asFunction(runtime)); + addedOverride = true; + } + if (!addedOverride) { + bool addedRuntimeProtocolOverride = addRuntimeProtocolOverrideForName( + runtime, bridge, nativeClass, optionProtocols, propertyName, + value.asObject(runtime).asFunction(runtime)); + if (!addedRuntimeProtocolOverride) { + if (auto known = knownNativeApiExposedMethod(propertyName)) { + addEngineExposedMethod(runtime, bridge, nativeClass, + known->selectorName, + std::move(known->signature), + value.asObject(runtime).asFunction(runtime), + baseClass); + } + } + } + } + + const NativeApiMember* propertyMember = + propertyOverrideForName(members, propertyName); + + Value getter = descriptor.getProperty(runtime, "get"); + if (propertyMember != nullptr && getter.isObject() && + getter.asObject(runtime).isFunction(runtime)) { + addEngineOverrideMethod( + runtime, bridge, nativeClass, baseClass, + propertyMember->selectorName, propertyMember->signatureOffset, + (propertyMember->flags & metagen::mdMemberReturnOwned) != 0, + getter.asObject(runtime).asFunction(runtime), + nativeAccessorCallbackPolicy()); + } else if (propertyMember == nullptr && getter.isObject() && + getter.asObject(runtime).isFunction(runtime)) { + auto overrides = methodOverridesForName(members, propertyName); + for (const auto& member : overrides) { + if (selectorArgumentCount(member.selectorName) != 0) { + continue; + } + addEngineOverrideMethod( + runtime, bridge, nativeClass, baseClass, member.selectorName, + member.signatureOffset, + (member.flags & metagen::mdMemberReturnOwned) != 0, + getter.asObject(runtime).asFunction(runtime), + nativeAccessorCallbackPolicy()); + } + } + + Value setter = descriptor.getProperty(runtime, "set"); + if (propertyMember != nullptr && + setter.isObject() && setter.asObject(runtime).isFunction(runtime) && + !propertyMember->setterSelectorName.empty()) { + addEngineOverrideMethod(runtime, bridge, nativeClass, baseClass, + propertyMember->setterSelectorName, + propertyMember->setterSignatureOffset, false, + setter.asObject(runtime).asFunction(runtime), + nativeAccessorCallbackPolicy()); + } + } + + Value exposedMethodsValue = + getObjectPropertyOrUndefined(runtime, options, "exposedMethods"); + if (!exposedMethodsValue.isObject()) { + exposedMethodsValue = + getObjectPropertyOrUndefined(runtime, methods, "ObjCExposedMethods"); + } + if (exposedMethodsValue.isObject()) { + Object exposedMethods = exposedMethodsValue.asObject(runtime); + Array exposedNames = exposedMethods.getPropertyNames(runtime); + for (size_t i = 0; i < exposedNames.size(runtime); i++) { + Value selectorValue = exposedNames.getValueAtIndex(runtime, i); + if (!selectorValue.isString()) { + continue; + } + std::string selectorName = selectorValue.asString(runtime).utf8(runtime); + Value descriptorValue = + getObjectPropertyOrUndefined(runtime, exposedMethods, selectorName); + if (!descriptorValue.isObject()) { + continue; + } + auto function = functionForSelector(runtime, methods, selectorName); + if (!function) { + continue; + } + auto signature = exposedMethodSignature( + runtime, bridge, selectorName, descriptorValue.asObject(runtime)); + if (signature) { + rememberNativeApiKnownExposedMethod(selectorName, *signature); + addEngineExposedMethod(runtime, bridge, nativeClass, selectorName, + std::move(*signature), std::move(*function), + baseClass); + } + } + } + + Value hasIteratorValue = + getObjectPropertyOrUndefined(runtime, options, "__hasIterator"); + if (hasIteratorValue.isBool() && hasIteratorValue.getBool()) { + class_addProtocol(nativeClass, @protocol(NSFastEnumeration)); + if (const char* encoding = nativeApiEngineFastEnumerationEncoding()) { + class_replaceMethod( + nativeClass, + @selector(countByEnumeratingWithState:objects:count:), + reinterpret_cast(nativeApiEngineSymbolIteratorCountByEnumerating), + encoding); + } + } + + objc_registerClassPair(nativeClass); + + NativeApiSymbol newSymbol = baseSymbol; + newSymbol.name = className; + newSymbol.runtimeName = className; + newSymbol.superclassOffset = baseSymbol.offset; + return makeNativeClassValue(runtime, bridge, std::move(newSymbol)); + } + +Value invokeNativeApiBaseMethod( + Runtime& runtime, const std::shared_ptr& bridge, + const Value* args, size_t count) { + if (count < 3 || !args[0].isObject() || !args[1].isObject() || + !args[2].isString()) { + throw JSError( + runtime, "__invokeBase expects base class, receiver, and member name."); + } + + Class baseClass = classFromEngineValue(runtime, args[0]); + if (baseClass == Nil) { + throw JSError(runtime, "__invokeBase base class is invalid."); + } + + Object receiverObject = args[1].asObject(runtime); + if (!receiverObject.isHostObject(runtime)) { + throw JSError(runtime, "__invokeBase receiver is not native."); + } + + auto receiverHostObject = + receiverObject.getHostObject(runtime); + id receiver = receiverHostObject->object(); + std::string memberName = args[2].asString(runtime).utf8(runtime); + size_t actualArgc = count - 3; + + NativeApiSymbol baseSymbol = runtimeSymbolForClass(bridge, baseClass); + const auto& members = bridge->membersForClass(baseSymbol); + const NativeApiMember* member = + selectMethodMember(members, memberName, false, actualArgc); + if (member == nullptr) { + if (const NativeApiMember* propertyMember = + selectWritablePropertyMember(members, memberName, false)) { + if (actualArgc == 0) { + Class dispatchClass = + dispatchSuperclassForEngineDerivedReceiver(receiver, baseClass); + return callNativeApiBaseObjectSelector( + runtime, bridge, receiverObject, receiverHostObject, receiver, + propertyMember->selectorName, propertyMember, nullptr, 0, + dispatchClass); + } + if (actualArgc == 1 && !propertyMember->setterSelectorName.empty() && + !propertyMember->readonly) { + Class dispatchClass = + dispatchSuperclassForEngineDerivedReceiver(receiver, baseClass); + NativeApiMember setterMember = *propertyMember; + setterMember.selectorName = propertyMember->setterSelectorName; + setterMember.signatureOffset = propertyMember->setterSignatureOffset; + return callNativeApiBaseObjectSelector( + runtime, bridge, receiverObject, receiverHostObject, receiver, + setterMember.selectorName, &setterMember, args + 3, actualArgc, + dispatchClass); + } + } + } + if (member == nullptr) { + throw JSError( + runtime, "Objective-C base selector is not available: " + memberName); + } + + Class dispatchClass = + dispatchSuperclassForEngineDerivedReceiver(receiver, baseClass); + return callNativeApiBaseObjectSelector( + runtime, bridge, receiverObject, receiverHostObject, receiver, + member->selectorName, member, args + 3, actualArgc, dispatchClass); +} diff --git a/NativeScript/ffi/objc/shared/bridge/HostObject.mm b/NativeScript/ffi/objc/shared/bridge/HostObject.mm new file mode 100644 index 000000000..146824f25 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/HostObject.mm @@ -0,0 +1,713 @@ +#ifndef NATIVESCRIPT_NATIVE_API_BACKEND_NAME +#error Engine backends must define NATIVESCRIPT_NATIVE_API_BACKEND_NAME. +#endif + +extern "C" { +void* objc_autoreleasePoolPush(void); +void objc_autoreleasePoolPop(void* pool); +} + +class NativeApiAutoreleasePool final { + public: + NativeApiAutoreleasePool() : pool_(objc_autoreleasePoolPush()) {} + ~NativeApiAutoreleasePool() { objc_autoreleasePoolPop(pool_); } + + private: + void* pool_; +}; + +#ifndef NATIVESCRIPT_NATIVE_API_RUNTIME_NAME +#define NATIVESCRIPT_NATIVE_API_RUNTIME_NAME NATIVESCRIPT_NATIVE_API_BACKEND_NAME +#endif + +#ifndef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS +inline bool InstallNativeApiLazyGlobal( + Runtime&, std::shared_ptr, const std::string&, + const std::string&, bool) { + return false; +} +#endif + +#ifndef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION +#error Engine backends must provide an engine selector group function. +#endif + +class NativeApiHostObject final : public HostObject { + public: + explicit NativeApiHostObject(std::shared_ptr bridge) + : bridge_(std::move(bridge)) {} + + // General accessor (not RN-specific): lets any caller holding the + // per-runtime `__nativeScriptNativeApi` global's HostObject recover the + // underlying bridge, e.g. to wrap/unwrap a native object into an engine + // value via the same mechanism every other crossing already uses (see the + // Hermes-backend wrap/unwrap helpers, ffi/objc/hermes/; this file stays + // engine-neutral and does not itself reference any engine-specific type). + const std::shared_ptr& bridge() const { return bridge_; } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "runtime") { + return makeString(runtime, NATIVESCRIPT_NATIVE_API_RUNTIME_NAME); + } + if (property == "backend") { + return makeString(runtime, NATIVESCRIPT_NATIVE_API_BACKEND_NAME); + } + if (property == "metadata") { + return metadataObject(runtime); + } + if (property == "hasScheduler") { + return bridge_->scheduler() != nullptr; + } + if (property == "interop") { + return createInteropObject(runtime, bridge_); + } + if (property == "autoreleasepool") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "autoreleasepool"), 1, + [](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 1 || !args[0].isObject() || + !args[0].asObject(runtime).isFunction(runtime)) { + throw JSError(runtime, + "autoreleasepool expects a callback function."); + } + NativeApiAutoreleasePool pool; + return args[0].asObject(runtime).asFunction(runtime).call(runtime); + }); + } +#ifdef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS + if (property == "__defineLazyGlobal") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__defineLazyGlobal"), 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string name = readStringArg(runtime, args, count, 0, "name"); + std::string kind = readStringArg(runtime, args, count, 1, "kind"); + bool force = count > 2 && args[2].isBool() && args[2].getBool(); + return InstallNativeApiLazyGlobal(runtime, bridge, name, kind, + force); + }); + } +#endif + if (property == "__fastEnumeration") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__fastEnumeration"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + throw JSError( + runtime, "Fast enumeration expects a native object."); + } + id object = NativeApiObjectHostObject::nativeObjectFromValue(runtime, args[0]); + if (object == nil) { + throw JSError( + runtime, "Fast enumeration expects a native object."); + } + if (![object conformsToProtocol:@protocol(NSFastEnumeration)]) { + throw JSError( + runtime, "Object does not conform to NSFastEnumeration."); + } + return Object::createFromHostObject( + runtime, + std::make_shared( + bridge, static_cast>(object))); + }); + } + if (property == "import") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "import"), 1, + [](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string path = readStringArg(runtime, args, count, 0, "path"); + std::string frameworkPath = path; + if (!frameworkPath.empty() && frameworkPath[0] != '/') { + frameworkPath = "/System/Library/Frameworks/" + frameworkPath + + ".framework"; + } + + NSBundle* bundle = [NSBundle + bundleWithPath:[NSString stringWithUTF8String:frameworkPath.c_str()]]; + if (bundle == nil || ![bundle load]) { + throw JSError( + runtime, "Could not load bundle: " + frameworkPath); + } + return true; + }); + } + if (property == "lookup") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "lookup"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string symbolName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->find(symbolName); + if (symbol == nullptr) { + return Value::null(); + } + return symbolToObject(runtime, *symbol); + }); + } + if (property == "getClass") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getClass"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string className = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findClass(className); + if (symbol == nullptr) { + Class cls = objc_lookUpClass(className.c_str()); + if (cls == nil) { + return Value::null(); + } + NativeApiSymbol runtimeSymbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = className, + .runtimeName = className, + }; + return makeNativeClassValue(runtime, bridge, + std::move(runtimeSymbol)); + } + + return makeNativeClassValue(runtime, bridge, *symbol); + }); + } + if (property == "__extendClass") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__extendClass"), 2, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + return extendNativeApiClass(runtime, bridge, args, count); + }); + } + if (property == "__invokeBase") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__invokeBase"), 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + return invokeNativeApiBaseMethod(runtime, bridge, args, count); + }); + } + if (property == "__makeSelectorGroupFunction") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__makeSelectorGroupFunction"), + 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 3 || !args[1].isBool() || !args[2].isObject() || + !args[2].asObject(runtime).isArray(runtime)) { + throw JSError( + runtime, + "__makeSelectorGroupFunction expects class, receiver kind, " + "and selector table."); + } + + Class lookupClass = classFromEngineValue(runtime, args[0]); + if (lookupClass == Nil) { + throw JSError(runtime, + "__makeSelectorGroupFunction class is invalid."); + } + + bool receiverIsClass = args[1].getBool(); + Array selectorTable = args[2].asObject(runtime).getArray(runtime); + size_t selectorCount = selectorTable.size(runtime); + auto selectors = + std::make_shared< + std::vector>( + selectorCount); + for (size_t i = 0; i < selectorCount; i++) { + Value selectorValue = selectorTable.getValueAtIndex(runtime, i); + if (selectorValue.isString()) { + (*selectors)[i].selectorName = + selectorValue.asString(runtime).utf8(runtime); + } else if (selectorValue.isObject()) { + Object descriptor = selectorValue.asObject(runtime); + Value selectorNameValue = + descriptor.getProperty(runtime, "selectorName"); + if (!selectorNameValue.isString()) { + continue; + } + NativeApiMember member; + member.selectorName = + selectorNameValue.asString(runtime).utf8(runtime); + Value nameValue = descriptor.getProperty(runtime, "name"); + if (nameValue.isString()) { + member.name = nameValue.asString(runtime).utf8(runtime); + } + Value setterSelectorNameValue = + descriptor.getProperty(runtime, "setterSelectorName"); + if (setterSelectorNameValue.isString()) { + member.setterSelectorName = + setterSelectorNameValue.asString(runtime).utf8(runtime); + } + Value signatureOffsetValue = + descriptor.getProperty(runtime, "signatureOffset"); + if (signatureOffsetValue.isNumber()) { + member.signatureOffset = static_cast( + signatureOffsetValue.getNumber()); + } + Value setterSignatureOffsetValue = + descriptor.getProperty(runtime, "setterSignatureOffset"); + if (setterSignatureOffsetValue.isNumber()) { + member.setterSignatureOffset = static_cast( + setterSignatureOffsetValue.getNumber()); + } + Value flagsValue = descriptor.getProperty(runtime, "flags"); + if (flagsValue.isNumber()) { + member.flags = static_cast( + static_cast(flagsValue.getNumber())); + } + Value propertyValue = descriptor.getProperty(runtime, "property"); + if (propertyValue.isBool()) { + member.property = propertyValue.getBool(); + } + Value readonlyValue = descriptor.getProperty(runtime, "readonly"); + if (readonlyValue.isBool()) { + member.readonly = readonlyValue.getBool(); + } + (*selectors)[i].selectorName = member.selectorName; + (*selectors)[i].member = std::move(member); + (*selectors)[i].hasMember = true; + } + } + + auto preparedInvocations = std::make_shared>>( + selectors->size()); + + return CreateNativeApiSelectorGroupFunction( + runtime, bridge, lookupClass, receiverIsClass, selectors, + preparedInvocations); + }); + } + if (property == "__rememberClassWrapper") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__rememberClassWrapper"), 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 2) { + return Value::undefined(); + } + Class cls = classFromEngineValue(runtime, args[0]); + if (cls == Nil) { + return Value::undefined(); + } + bridge->rememberClassValue(runtime, cls, args[1]); + if (count >= 3 && args[2].isObject()) { + bridge->rememberClassPrototype(runtime, cls, args[2]); + } + return Value::undefined(); + }); + } + if (property == "__rememberObjectClassWrapper") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__rememberObjectClassWrapper"), + 2, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 2) { + return Value::undefined(); + } + id object = NativeApiObjectHostObject::nativeObjectFromValue( + runtime, args[0]); + if (object == nil) { + return Value::undefined(); + } + // A factory/class method may return an instance of a different + // class (e.g. +[TNSSwiftLikeFactory create] returns a TNSSwiftLike). + // Only label the object with this wrapper when it actually is an + // instance of the wrapper's class, so `constructor` resolves to the + // object's real class instead of the calling class. + if (args[1].isObject()) { + Class wrapperClass = classFromEngineValue(runtime, args[1]); + if (wrapperClass != Nil && ![object isKindOfClass:wrapperClass]) { + return Value::undefined(); + } + } + bridge->setObjectExpando(runtime, object, + "__nativeApiClassWrapper", args[1]); + if (args[1].isObject()) { + Object classWrapper = args[1].asObject(runtime); + Value prototypeValue = + classWrapper.getProperty(runtime, "prototype"); + if (prototypeValue.isObject()) { + Object instanceObject = args[0].asObject(runtime); + Object prototype = prototypeValue.asObject(runtime); + SetNativeApiObjectPrototype(runtime, instanceObject, + prototype); + } + } + return Value::undefined(); + }); + } + // Re-entry guards for JS-subclass instance construction/accessors, + // backed by an associated object (not a JS expando — see the + // memo/expando-proxy notes: expandos never round-trip on these + // proxies). ClassBuilder wires these around alloc/init and native + // accessor dispatch. + if (property == "__setObjectConstructionState") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__setObjectConstructionState"), + 2, + [](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 1) { + return Value::undefined(); + } + id object = NativeApiObjectHostObject::nativeObjectFromValue( + runtime, args[0]); + if (object == nil) { + return Value::undefined(); + } + bool constructing = + count >= 2 && args[1].isBool() && args[1].getBool(); + objc_setAssociatedObject( + object, sel_registerName("__nativeApiConstructionState"), + constructing ? @YES : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return Value::undefined(); + }); + } + if (property == "__setObjectAccessorCallbackState") { + // Depth-counted (not a bool) so nested/re-entrant accessor calls on the + // same object (e.g. a getter that reads another property) still clear + // correctly on unwind. + return Function::createFromHostFunction( + runtime, PropNameID::forAscii( + runtime, "__setObjectAccessorCallbackState"), + 2, + [](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 1) { + return Value::undefined(); + } + id object = NativeApiObjectHostObject::nativeObjectFromValue( + runtime, args[0]); + if (object == nil) { + return Value::undefined(); + } + bool active = count >= 2 && args[1].isBool() && args[1].getBool(); + SEL key = sel_registerName("__nativeApiAccessorCallbackState"); + NSNumber* current = (NSNumber*)objc_getAssociatedObject(object, key); + NSInteger depth = current != nil ? current.integerValue : 0; + if (active) { + depth += 1; + } else if (depth > 0) { + depth -= 1; + } + objc_setAssociatedObject( + object, key, depth > 0 ? @(depth) : nil, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return Value::undefined(); + }); + } + if (property == "CC_SHA256") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "CC_SHA256"), 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 3 || !args[1].isNumber()) { + throw JSError( + runtime, "CC_SHA256 expects data, length, and output."); + } + void* commonCrypto = + dlopen("/usr/lib/system/libcommonCrypto.dylib", + RTLD_NOW | RTLD_LOCAL); + void* symbol = commonCrypto != nullptr + ? dlsym(commonCrypto, "CC_SHA256") + : nullptr; + if (symbol == nullptr && commonCrypto != nullptr) { + symbol = dlsym(commonCrypto, "_CC_SHA256"); + } + if (symbol == nullptr) { + throw JSError(runtime, + "CC_SHA256 is not available."); + } + NativeApiArgumentFrame frame(3); + void* data = pointerFromEngineValue(runtime, bridge, args[0], frame); + void* output = + pointerFromEngineValue(runtime, bridge, args[2], frame); + using CC_SHA256_Fn = unsigned char* (*)(const void*, unsigned long, + unsigned char*); + auto fn = reinterpret_cast(symbol); + unsigned char* result = + fn(data, static_cast(args[1].getNumber()), + static_cast(output)); + return createPointer(runtime, bridge, result); + }); + } + if (property == "getFunction") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getFunction"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string functionName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findFunction(functionName); + if (symbol == nullptr) { + return Value::null(); + } + auto prepared = + std::make_shared(); + prepared->symbol = *symbol; + auto function = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, symbol->name), 0, + [bridge, prepared](Runtime& runtime, const Value&, + const Value* args, + size_t count) -> Value { + return callCFunction(runtime, bridge, prepared, args, count); + }); + function.setProperty(runtime, "kind", makeString(runtime, "function")); + function.setProperty(runtime, "nativeName", + makeString(runtime, symbol->name)); + function.setProperty(runtime, "metadataOffset", + static_cast(symbol->offset)); + function.setProperty(runtime, "sizeof", + static_cast(sizeof(void*))); + return function; + }); + } + if (property == "getConstant") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getConstant"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string constantName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findConstant(constantName); + if (symbol == nullptr) { + return Value::undefined(); + } + return constantToValue(runtime, bridge, *symbol); + }); + } + if (property == "getEnum") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getEnum"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string enumName = readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findEnum(enumName); + if (symbol == nullptr) { + return Value::undefined(); + } + return enumToObject(runtime, bridge->metadata(), *symbol); + }); + } + if (property == "getProtocol") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getProtocol"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string protocolName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findProtocol(protocolName); + if (symbol == nullptr) { + Protocol* protocol = lookupProtocolByNativeName(protocolName); + if (protocol == nullptr) { + return Value::null(); + } + const char* runtimeName = protocol_getName(protocol); + NativeApiSymbol runtimeSymbol{ + .kind = NativeApiSymbolKind::Protocol, + .offset = MD_SECTION_OFFSET_NULL, + .name = protocolName, + .runtimeName = runtimeName != nullptr ? runtimeName : protocolName, + }; + return makeNativeProtocolValue(runtime, bridge, + std::move(runtimeSymbol)); + } + return makeNativeProtocolValue(runtime, bridge, *symbol); + }); + } + if (property == "getStruct" || property == "getUnion") { + auto bridge = bridge_; + bool isUnion = property == "getUnion"; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 1, + [bridge, isUnion](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string aggregateName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = + isUnion ? bridge->findUnion(aggregateName) + : bridge->findStruct(aggregateName); + if (symbol == nullptr) { + return Value::undefined(); + } + return makeAggregateConstructor(runtime, bridge, *symbol); + }); + } + + if (const NativeApiSymbol* classSymbol = bridge_->findClass(property)) { + return makeNativeClassValue(runtime, bridge_, *classSymbol); + } + + if (const NativeApiSymbol* functionSymbol = bridge_->findFunction(property)) { + auto prepared = + std::make_shared(); + prepared->symbol = *functionSymbol; + auto bridge = bridge_; + Function function = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [bridge, prepared](Runtime& runtime, const Value&, + const Value* args, + size_t count) -> Value { + return callCFunction(runtime, bridge, prepared, args, count); + }); + function.setProperty(runtime, "kind", makeString(runtime, "function")); + function.setProperty(runtime, "nativeName", + makeString(runtime, functionSymbol->name)); + function.setProperty(runtime, "metadataOffset", + static_cast(functionSymbol->offset)); + function.setProperty(runtime, "sizeof", + static_cast(sizeof(void*))); + return function; + } + + if (const NativeApiSymbol* constantSymbol = bridge_->findConstant(property)) { + return constantToValue(runtime, bridge_, *constantSymbol); + } + + if (const NativeApiSymbol* enumSymbol = bridge_->findEnum(property)) { + return enumToObject(runtime, bridge_->metadata(), *enumSymbol); + } + + if (const NativeApiSymbol* protocolSymbol = + bridge_->findProtocol(property)) { + return makeNativeProtocolValue(runtime, bridge_, *protocolSymbol); + } + + if (const NativeApiSymbol* aggregateSymbol = + bridge_->findAggregate(property)) { + return makeAggregateConstructor(runtime, bridge_, *aggregateSymbol); + } + + return Value::undefined(); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + names.reserve(11); + addPropertyName(runtime, names, "runtime"); + addPropertyName(runtime, names, "backend"); + addPropertyName(runtime, names, "metadata"); + addPropertyName(runtime, names, "hasScheduler"); + addPropertyName(runtime, names, "interop"); + addPropertyName(runtime, names, "autoreleasepool"); +#ifdef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS + addPropertyName(runtime, names, "__defineLazyGlobal"); +#endif + addPropertyName(runtime, names, "import"); + addPropertyName(runtime, names, "lookup"); + addPropertyName(runtime, names, "getClass"); + addPropertyName(runtime, names, "__extendClass"); + addPropertyName(runtime, names, "__invokeBase"); + addPropertyName(runtime, names, "__makeSelectorGroupFunction"); + addPropertyName(runtime, names, "__rememberClassWrapper"); + addPropertyName(runtime, names, "__rememberObjectClassWrapper"); + addPropertyName(runtime, names, "__setObjectConstructionState"); + addPropertyName(runtime, names, "getFunction"); + addPropertyName(runtime, names, "getConstant"); + addPropertyName(runtime, names, "getEnum"); + addPropertyName(runtime, names, "getProtocol"); + addPropertyName(runtime, names, "getStruct"); + addPropertyName(runtime, names, "getUnion"); + return names; + } + + private: + Object metadataObject(Runtime& runtime) const { + Object metadata(runtime); + metadata.setProperty(runtime, "classes", + static_cast(bridge_->classCount())); + metadata.setProperty(runtime, "functions", + static_cast(bridge_->functionCount())); + metadata.setProperty(runtime, "constants", + static_cast(bridge_->constantCount())); + metadata.setProperty(runtime, "protocols", + static_cast(bridge_->protocolCount())); + metadata.setProperty(runtime, "enums", + static_cast(bridge_->enumCount())); + metadata.setProperty(runtime, "structs", + static_cast(bridge_->structCount())); + metadata.setProperty(runtime, "unions", + static_cast(bridge_->unionCount())); + + metadata.setProperty( + runtime, "classNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "classNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->classNames()); + })); + metadata.setProperty( + runtime, "functionNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "functionNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->functionNames()); + })); + metadata.setProperty( + runtime, "constantNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "constantNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->constantNames()); + })); + metadata.setProperty( + runtime, "protocolNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "protocolNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->protocolNames()); + })); + metadata.setProperty( + runtime, "enumNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "enumNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->enumNames()); + })); + metadata.setProperty( + runtime, "structNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "structNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->structNames()); + })); + metadata.setProperty( + runtime, "unionNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "unionNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->unionNames()); + })); + return metadata; + } + + std::shared_ptr bridge_; +}; diff --git a/NativeScript/ffi/objc/shared/bridge/HostObjects.mm b/NativeScript/ffi/objc/shared/bridge/HostObjects.mm new file mode 100644 index 000000000..c54053916 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/HostObjects.mm @@ -0,0 +1,56 @@ +// HostObject::set returns bool on engines whose interceptors can defer an +// unhandled set to the JS prototype chain. JSI's HostObject::set is void, so +// the Hermes backend defines NATIVESCRIPT_NATIVE_API_HOST_SET_VOID and the +// set overrides below collapse their return type/values accordingly. +#ifdef NATIVESCRIPT_NATIVE_API_HOST_SET_VOID +using NativeApiHostSetResult = void; +#define NATIVE_API_SET_RETURN(handled) return +#else +using NativeApiHostSetResult = bool; +#define NATIVE_API_SET_RETURN(handled) return (handled) +#endif + +// Engine-neutral factory for native object instance wrappers. V8 uses its +// kNonMasking native instance template (fast prototype-based property access); +// every other engine uses its standard host-object creation. Selected at +// compile time so the shared bridge code stays engine-agnostic. +template +Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { +#ifdef TARGET_ENGINE_V8 + return Object::createNativeInstanceHostObject(runtime, std::move(host)); +#else + return Object::createFromHostObject(runtime, std::move(host)); +#endif +} + +class NativeApiObjectLifetimeState final { + public: + explicit NativeApiObjectLifetimeState(id object) + : object_(reinterpret_cast(object)) {} + + id object() const { + return reinterpret_cast(object_.load(std::memory_order_relaxed)); + } + + void setObject(id object) { + object_.store(reinterpret_cast(object), std::memory_order_relaxed); + } + + void clear() { object_.store(nullptr, std::memory_order_relaxed); } + + private: + std::atomic object_{nullptr}; +}; + + +#include "host_objects/Interop.mm" + +#include "host_objects/Struct.mm" + +#include "host_objects/Appearance.mm" + +#include "host_objects/Object.mm" + +#include "host_objects/Class.mm" + +#include "host_objects/Protocol.mm" diff --git a/NativeScript/ffi/objc/shared/bridge/Install.mm b/NativeScript/ffi/objc/shared/bridge/Install.mm new file mode 100644 index 000000000..22121ef64 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/Install.mm @@ -0,0 +1,2166 @@ +Object CreateNativeApi(Runtime& runtime, const NativeApiConfig& config) { + auto bridge = std::make_shared(config); + return Object::createFromHostObject(runtime, + std::make_shared(std::move(bridge))); +} + +void NativeApiWriteSmokeStage(const char* stage) { + const char* enabled = getenv("NATIVESCRIPT_RN_TURBO_SMOKE_MARKER"); + if (enabled == nullptr || enabled[0] == '\0') { + return; + } + + NSString* path = + [NSTemporaryDirectory() stringByAppendingPathComponent:@"NativeScriptNativeApiSmoke.marker"]; + NSString* content = [NSString stringWithFormat:@"stage=%s\n", stage != nullptr ? stage : ""]; + [content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; +} + +void InstallAggregateGlobals(Runtime& runtime, Object& api, const char* namesFunction) { + Value metadataValue = api.getProperty(runtime, "metadata"); + if (!metadataValue.isObject()) { + return; + } + Object metadata = metadataValue.asObject(runtime); + Value namesValue = metadata.getProperty(runtime, namesFunction); + if (!namesValue.isObject()) { + return; + } + Object namesObject = namesValue.asObject(runtime); + if (!namesObject.isFunction(runtime)) { + return; + } + Value namesResult = namesObject.asFunction(runtime).call(runtime); + if (!namesResult.isObject() || !namesResult.asObject(runtime).isArray(runtime)) { + return; + } + Array names = namesResult.asObject(runtime).getArray(runtime); + Object global = runtime.global(); + for (size_t i = 0; i < names.size(runtime); i++) { + Value nameValue = names.getValueAtIndex(runtime, i); + if (!nameValue.isString()) { + continue; + } + std::string name = nameValue.asString(runtime).utf8(runtime); + if (name.empty() || global.hasProperty(runtime, name.c_str())) { + continue; + } + try { + Value aggregate = api.getProperty(runtime, name.c_str()); + if (!aggregate.isUndefined()) { + global.setProperty(runtime, name.c_str(), aggregate); + } + } catch (const std::exception&) { + // Some React Native globals are read-only even when hasProperty misses + // them. Keep NativeScript initialization resilient and skip collisions. + } + } +} + +std::string jsStringLiteral(const char* value) { + std::string result = "'"; + if (value != nullptr) { + for (const char* current = value; *current != '\0'; current++) { + switch (*current) { + case '\\': + result += "\\\\"; + break; + case '\'': + result += "\\'"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + default: + result += *current; + break; + } + } + } + result += "'"; + return result; +} + +void InstallNativeApiGlobalSymbols(Runtime& runtime, const char* globalName) { + NativeApiWriteSmokeStage("engine:globals:before-eval"); + static const char* GlobalInstaller = R"Engine_GLOBALS( +(function(nativeApiGlobalName) { + 'use strict'; + var api = globalThis[nativeApiGlobalName]; + var installedFlagName = '__nativeScriptNativeApiGlobalsInstalled'; + if (!api || globalThis[installedFlagName]) { + return; + } + + var cacheName = '__nativeScriptNativeApiGlobalCache'; + var typeCodeKey = '__nativeApiTypeCode'; + var classWrappers = typeof WeakMap === 'function' ? new WeakMap() : null; + var classWrappersByName = Object.create(null); + var resolvingGlobal = Object.create(null); + + function globalCache() { + var existing = globalThis[cacheName]; + if (existing && typeof existing === 'object') { + return existing; + } + var cache = Object.create(null); + Object.defineProperty(globalThis, cacheName, { + configurable: false, + enumerable: false, + writable: false, + value: cache + }); + return cache; + } + + function cacheGlobal(name, value) { + if (name && value !== undefined) { + globalCache()[name] = value; + } + } + + function resolveCachedGlobal(name, expectedKind) { + if (!name) { + return undefined; + } + var cached = globalCache()[name]; + if (cached && (typeof cached === 'object' || typeof cached === 'function') && cached.kind === expectedKind) { + return cached; + } + if (resolvingGlobal[name] || !Object.prototype.hasOwnProperty.call(globalThis, name)) { + return undefined; + } + resolvingGlobal[name] = true; + try { + var value = globalThis[name]; + if (value && (typeof value === 'object' || typeof value === 'function') && value.kind === expectedKind) { + cacheGlobal(name, value); + return value; + } + } finally { + delete resolvingGlobal[name]; + } + return undefined; + } + + function defineLazyGlobal(name, resolve, force, nativeKind) { + if (!name) { + return; + } + if (!force && Object.prototype.hasOwnProperty.call(globalThis, name)) { + try { + var existingDescriptor = Object.getOwnPropertyDescriptor(globalThis, name); + if (existingDescriptor && Object.prototype.hasOwnProperty.call(existingDescriptor, 'value')) { + cacheGlobal(name, existingDescriptor.value); + } + } catch (_) { + } + return; + } + var nativeDefineLazyGlobal = api.__defineLazyGlobal; + if (nativeKind && typeof nativeDefineLazyGlobal === 'function' && + typeof globalThis.__nativeScriptResolveNativeApiLazyGlobal === 'function') { + try { + if (nativeDefineLazyGlobal(name, nativeKind, !!force)) { + return; + } + } catch (_) { + } + } + try { + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: false, + get: function() { + var value = resolve(name); + cacheGlobal(name, value); + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: false, + writable: true, + value: value + }); + return value; + }, + set: function(value) { + // Assignment over a lazy global must behave like a plain global + // assignment (@nativescript/core writes shims such as + // global.System), not throw "no setter for property". + cacheGlobal(name, value); + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: true, + writable: true, + value: value + }); + } + }); + } catch (_) { + var value = resolve(name); + if (value !== undefined) { + cacheGlobal(name, value); + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: false, + writable: true, + value: value + }); + } + } + } + + Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiGlobal', { + configurable: false, + enumerable: false, + writable: false, + value: resolveCachedGlobal + }); + + Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiClassWrapper', { + configurable: false, + enumerable: false, + writable: false, + value: function(name) { + return name ? classWrappersByName[name] : undefined; + } + }); + + function findPrototypeDescriptor(className, property) { + var prototype; + if (className && (typeof className === 'object' || typeof className === 'function')) { + prototype = className; + } else { + var wrapper = className ? classWrappersByName[className] : undefined; + prototype = wrapper && wrapper.prototype; + } + while (prototype != null) { + var descriptor = Object.getOwnPropertyDescriptor(prototype, property); + if (descriptor) { + return descriptor; + } + prototype = Object.getPrototypeOf(prototype); + } + return undefined; + } + + function setObjectAccessorCallbackState(instance, active) { + try { + if (typeof api.__setObjectAccessorCallbackState === 'function') { + api.__setObjectAccessorCallbackState(instance, !!active); + } + } catch (_) { + } + } + + function nativeExtensionAccessorWithCallbackState(fn) { + if (typeof fn !== 'function') { + return fn; + } + return function() { + setObjectAccessorCallbackState(this, true); + try { + var args = Array.prototype.slice.call(arguments); + return fn.apply(this, args); + } finally { + setObjectAccessorCallbackState(this, false); + } + }; + } + + // Wraps extend()'s methods object with: (a) the re-entry guard above around + // any accessor (get/set) so a native accessor invocation calling back into + // itself through the ObjC runtime is suppressed rather than recursing, and + // (b) NSFastEnumeration-flavored indexed-collection aliases + // (objectAtIndexedSubscript / setObjectAtIndexedSubscript / Symbol.iterator) + // when the methods object looks like an Obj-C indexed collection + // (objectAtIndex + count), matching what a hand-written ObjC subclass gets + // for free from the runtime. + function nativeExtensionMethodsWithIndexedCollectionAliases(methods) { + if (methods == null || typeof methods !== 'object') { + return methods; + } + + var descriptors = Object.getOwnPropertyDescriptors(methods); + var descriptorKeys = + typeof Reflect === 'object' && typeof Reflect.ownKeys === 'function' + ? Reflect.ownKeys(descriptors) + : Object.keys(descriptors); + var needsAccessorCallbackState = false; + for (var descriptorIndex = 0; descriptorIndex < descriptorKeys.length; descriptorIndex++) { + var descriptor = descriptors[descriptorKeys[descriptorIndex]]; + if (descriptor && + (typeof descriptor.get === 'function' || + typeof descriptor.set === 'function')) { + needsAccessorCallbackState = true; + break; + } + } + + var hasObjectAtIndex = + Object.prototype.hasOwnProperty.call(methods, 'objectAtIndex'); + var hasCount = + Object.prototype.hasOwnProperty.call(methods, 'count'); + var hasSymbolIterator = + typeof Symbol === 'function' && Symbol.iterator && + Object.prototype.hasOwnProperty.call(methods, Symbol.iterator); + var needsObjectAtIndexedSubscript = + hasObjectAtIndex && + !Object.prototype.hasOwnProperty.call(methods, 'objectAtIndexedSubscript'); + var needsSetObjectAtIndexedSubscript = + Object.prototype.hasOwnProperty.call(methods, 'replaceObjectAtIndexWithObject') && + !Object.prototype.hasOwnProperty.call(methods, 'setObjectAtIndexedSubscript'); + var needsIndexedCollectionIterator = + typeof Symbol === 'function' && Symbol.iterator && + hasObjectAtIndex && hasCount && !hasSymbolIterator; + + if (!needsObjectAtIndexedSubscript && + !needsSetObjectAtIndexedSubscript && + !needsIndexedCollectionIterator && + !needsAccessorCallbackState) { + return methods; + } + + if (needsAccessorCallbackState) { + for (var accessorIndex = 0; accessorIndex < descriptorKeys.length; accessorIndex++) { + var accessorKey = descriptorKeys[accessorIndex]; + var accessorDescriptor = descriptors[accessorKey]; + if (!accessorDescriptor) { + continue; + } + if (typeof accessorDescriptor.get === 'function') { + accessorDescriptor.get = + nativeExtensionAccessorWithCallbackState(accessorDescriptor.get); + } + if (typeof accessorDescriptor.set === 'function') { + accessorDescriptor.set = + nativeExtensionAccessorWithCallbackState(accessorDescriptor.set); + } + } + } + + var prepared = Object.create(Object.getPrototypeOf(methods)); + Object.defineProperties(prepared, descriptors); + + if (needsObjectAtIndexedSubscript) { + Object.defineProperty(prepared, 'objectAtIndexedSubscript', { + configurable: true, + enumerable: false, + writable: true, + value: function(index) { + return this.objectAtIndex(index); + } + }); + } + + if (needsSetObjectAtIndexedSubscript) { + Object.defineProperty(prepared, 'setObjectAtIndexedSubscript', { + configurable: true, + enumerable: false, + writable: true, + value: function(anObject, index) { + return this.replaceObjectAtIndexWithObject(index, anObject); + } + }); + } + + if (needsIndexedCollectionIterator) { + Object.defineProperty(prepared, Symbol.iterator, { + configurable: true, + enumerable: false, + writable: true, + value: function() { + var receiver = this; + var index = 0; + return { + next: function() { + var countValue = receiver.count; + var count = typeof countValue === 'function' + ? countValue.call(receiver) + : countValue; + if (!(index < count)) { + return { done: true }; + } + return { + value: receiver.objectAtIndex(index++), + done: false + }; + } + }; + } + }); + } + + return prepared; + } + + function nativeExtensionMethodsHaveIterator(methods) { + return typeof Symbol === 'function' && Symbol.iterator && + methods != null && typeof methods === 'object' && + Object.prototype.hasOwnProperty.call(methods, Symbol.iterator); + } + + function nativeExtensionOptionsWithIterator(options, methods) { + var extendOptions = options || {}; + if (!nativeExtensionMethodsHaveIterator(methods)) { + return extendOptions; + } + try { + return Object.assign({}, extendOptions, { + __hasIterator: true + }); + } catch (_) { + extendOptions.__hasIterator = true; + return extendOptions; + } + } + + Object.defineProperty(globalThis, '__nativeScriptCreateNativeApiIterator', { + configurable: false, + enumerable: false, + writable: false, + value: function(receiver, prototype) { + if (!receiver || typeof Symbol !== 'function') { + return undefined; + } + var descriptor = findPrototypeDescriptor(prototype || receiver.className, Symbol.iterator); + if (descriptor && typeof descriptor.value === 'function') { + return descriptor.value.call(receiver); + } + if (descriptor && typeof descriptor.get === 'function') { + var getterValue = descriptor.get.call(receiver); + if (typeof getterValue === 'function') { + return getterValue.call(receiver); + } + } + var iteratorMethod = receiver[Symbol.iterator]; + return typeof iteratorMethod === 'function' + ? iteratorMethod.call(receiver) + : undefined; + } + }); + + function wrapAggregateConstructor(nativeConstructor) { + if (typeof nativeConstructor !== 'function') { + return nativeConstructor; + } + var aggregate = function NativeScriptAggregate(initialValue) { + return nativeConstructor(initialValue); + }; + try { + Object.defineProperty(aggregate, Symbol.hasInstance, { + configurable: true, + enumerable: false, + value: function(value) { + return !!value && + typeof value === 'object' && + value.kind === nativeConstructor.kind && + value.name === nativeConstructor.runtimeName; + } + }); + } catch (_) { + } + ['kind', 'runtimeName', 'metadataOffset', 'sizeof', 'fields', 'equals'].forEach(function(key) { + try { + Object.defineProperty(aggregate, key, { + configurable: true, + enumerable: false, + writable: false, + value: nativeConstructor[key] + }); + } catch (_) { + } + }); + return aggregate; + } + + function setDescriptorValue(target, property, receiver, value) { + for (var current = target; current; current = Object.getPrototypeOf(current)) { + var descriptor = Object.getOwnPropertyDescriptor(current, property); + if (!descriptor) { + continue; + } + if (typeof descriptor.set === 'function') { + descriptor.set.call(receiver, value); + return true; + } + if (descriptor.writable) { + if (receiver && receiver !== current) { + Object.defineProperty(receiver, property, { + configurable: true, + enumerable: true, + writable: true, + value: value + }); + } else { + current[property] = value; + } + return true; + } + return false; + } + return false; + } + + function setInheritedNativeClassValue(target, property, value) { + for (var current = Object.getPrototypeOf(target); + current && current !== Function.prototype; + current = Object.getPrototypeOf(current)) { + var nativeClassValue; + try { + nativeClassValue = current.__nativeApiClass; + } catch (_) { + nativeClassValue = null; + } + if (!nativeClassValue) { + continue; + } + try { + nativeClassValue[property] = value; + return true; + } catch (_) { + } + } + return false; + } + + function isConstructorOptions(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return false; + } + if (value.kind || value.nativeAddress || value instanceof Date) { + return false; + } + return Object.getPrototypeOf(value) === Object.prototype || + Object.getPrototypeOf(value) === null; + } + + function capitalizeToken(value) { + value = String(value || ''); + return value ? value.charAt(0).toUpperCase() + value.slice(1) : value; + } + + function selectorCandidatesFromOptions(options) { + var keys = Object.keys(options || {}); + if (!keys.length) { + return []; + } + var first = capitalizeToken(keys[0]); + var tail = ''; + for (var i = 1; i < keys.length; i++) { + tail += keys[i] + ':'; + } + return [ + 'initWith' + first + ':' + tail, + 'init' + first + ':' + tail + ]; + } + + function valuesFromOptions(options) { + return Object.keys(options || {}).map(function(key) { + return options[key]; + }); + } + + function selectorScoreForArguments(selectorName, args) { + if (!selectorName || selectorName.indexOf('init') !== 0) { + return -1; + } + if (selectorName === 'init') { + return args.length === 0 ? 100 : -1; + } + if (args.length === 0) { + return -1; + } + var lower = selectorName.toLowerCase(); + var first = args[0]; + var score = 1; + if (Array.isArray(first)) { + if (lower.indexOf('array') !== -1) { + score += 40; + } + } else if (typeof first === 'string') { + if (lower.indexOf('string') !== -1) { + score += 40; + } + if (lower.indexOf('url') !== -1) { + score += 10; + } + } else if (typeof first === 'number') { + if (lower.indexOf('primitive') !== -1) { + score += 50; + } + if (lower.indexOf('int') !== -1 || + lower.indexOf('integer') !== -1 || + lower.indexOf('number') !== -1 || + lower.indexOf('float') !== -1 || + lower.indexOf('double') !== -1 || + lower.indexOf('long') !== -1 || + lower.indexOf('short') !== -1) { + score += 30; + } + } else if (isConstructorOptions(first)) { + if (lower.indexOf('struct') !== -1 || + lower.indexOf('structure') !== -1) { + score += 40; + } + if (lower.indexOf('dictionary') !== -1) { + score += 20; + } + } else if (first === null || typeof first === 'undefined') { + score += 5; + } else if (lower.indexOf('object') !== -1 || + lower.indexOf('url') !== -1 || + lower.indexOf('data') !== -1) { + score += 20; + } + + var allStrings = args.length > 1 && args.every(function(value) { + return typeof value === 'string'; + }); + var allNumbers = args.length > 1 && args.every(function(value) { + return typeof value === 'number'; + }); + if (allStrings && lower.indexOf('string') !== -1) { + score += 25; + } + if (allNumbers && + (lower.indexOf('int') !== -1 || lower.indexOf('number') !== -1)) { + score += 25; + } + return score; + } + + function initializerMembers(nativeClass, argumentCount) { + var metadataMembers = nativeClass.__instanceMembers || []; + var runtimeMembers = nativeClass.__runtimeInstanceMembers || []; + var members = metadataMembers.concat(runtimeMembers); + var result = []; + for (var i = 0; i < members.length; i++) { + var member = members[i]; + if (!member || member.property || !member.selectorName) { + continue; + } + if (member.selectorName.indexOf('init') !== 0) { + continue; + } + if (typeof argumentCount === 'number' && + member.argumentCount !== argumentCount) { + continue; + } + result.push(member); + } + return result; + } + + function chooseInitializer(nativeClass, args, optionSelectors) { + var members = initializerMembers(nativeClass, args.length); + if (!members.length) { + return null; + } + if (optionSelectors && optionSelectors.length) { + for (var i = 0; i < optionSelectors.length; i++) { + for (var j = 0; j < members.length; j++) { + if (members[j].selectorName === optionSelectors[i]) { + return members[j]; + } + } + } + } + + var best = null; + var bestScore = -1; + for (var k = 0; k < members.length; k++) { + var score = selectorScoreForArguments(members[k].selectorName, args); + if (score > bestScore) { + bestScore = score; + best = members[k]; + } + } + return bestScore >= 0 ? best : null; + } + + function chooseInitializerBySelectors(nativeClass, args, selectors) { + if (!selectors || !selectors.length) { + return null; + } + var members = initializerMembers(nativeClass, args.length); + for (var i = 0; i < selectors.length; i++) { + for (var j = 0; j < members.length; j++) { + if (members[j].selectorName === selectors[i]) { + return members[j]; + } + } + } + return null; + } + + function unavailableInitializerError(error) { + return error && + /Objective-C selector is not available/.test(String(error.message || error)); + } + + // markConstructing is true for JS-subclass (ClassBuilder) instances: their + // alloc/init sequence marks the receiver as "under construction" so a + // non-init method callback landing on a partially-constructed self (e.g. + // from within an ObjC framework's own init machinery) is suppressed + // instead of re-entering JS with an object that isn't fully set up yet + // (see shouldSkipConstructingMethodCallback). + function shouldUseAllocInitConstructor(constructable, wrapper) { + var target = wrapper || constructable; + try { + return !!(target && target.__nativeApiUseAllocInitConstructor); + } catch (_) { + return false; + } + } + + function setObjectConstructionState(instance, constructing) { + try { + if (api && typeof api.__setObjectConstructionState === 'function') { + api.__setObjectConstructionState(instance, !!constructing); + } + } catch (_) { + } + } + + function constructNativeInstance(nativeClass, args, rememberInstance, markConstructing) { + if (args.length === 1 && + args[0] && + typeof args[0] === 'object' && + (args[0].kind === 'pointer' || args[0].kind === 'reference') && + typeof nativeClass.construct === 'function') { + return nativeClass.construct(args[0]); + } + + var actualArgs = args; + var initializer = null; + if (args.length === 1 && isConstructorOptions(args[0])) { + var optionSelectors = selectorCandidatesFromOptions(args[0]); + if (!optionSelectors.length) { + throw new Error('No initializer found that matches constructor invocation.'); + } + var optionArgs = valuesFromOptions(args[0]); + initializer = chooseInitializerBySelectors( + nativeClass, + optionArgs, + optionSelectors + ); + if (initializer) { + actualArgs = optionArgs; + } + } + if (!initializer) { + initializer = chooseInitializer(nativeClass, actualArgs, null); + } + if (!initializer) { + throw new Error('No initializer found that matches constructor invocation.'); + } + if (typeof nativeClass.alloc !== 'function') { + throw new Error('Native class cannot be allocated'); + } + var instance = nativeClass.alloc(); + if (typeof rememberInstance === 'function') { + instance = rememberInstance(instance); + } + if (markConstructing) { + setObjectConstructionState(instance, true); + } + try { + if (initializer.selectorName === 'init') { + if (typeof instance.init !== 'function') { + throw new Error('No initializer found that matches constructor invocation.'); + } + return instance.init(); + } + if (initializer.name && typeof instance[initializer.name] === 'function') { + return instance[initializer.name](...actualArgs); + } + var invokeArgs = [initializer.selectorName]; + for (var invokeArgIndex = 0; invokeArgIndex < actualArgs.length; invokeArgIndex++) { + invokeArgs.push(actualArgs[invokeArgIndex]); + } + return instance.invoke(...invokeArgs); + } catch (error) { + if (unavailableInitializerError(error)) { + throw new Error('No initializer found that matches constructor invocation.'); + } + throw error; + } finally { + if (markConstructing) { + setObjectConstructionState(instance, false); + } + } + } + + function nativeClassForInstance(instance, classFallback, baseConstructor) { + var constructor = instance && instance.constructor; + if (constructor && constructor !== baseConstructor && + constructor !== classFallback) { + return constructor; + } + return classFallback || baseConstructor; + } + + // Gives extend()ed/TypeScript-native-subclass instances a `class`/ + // `superclass` identity that resolves to the ACTUAL (possibly further + // JS-subclassed) constructor rather than always reporting the class the + // extension was originally built against. + function installInstanceClassIdentity(target, classFallback, baseConstructor) { + if (!target || typeof Object.create !== 'function' || + typeof Object.setPrototypeOf !== 'function') { + return; + } + var parent = null; + try { + parent = Object.getPrototypeOf(target); + } catch (_) { + } + var identityPrototype = Object.create(parent || null); + try { + Object.defineProperty(identityPrototype, 'class', { + configurable: true, + enumerable: false, + writable: true, + value: function() { + return nativeClassForInstance(this, classFallback, baseConstructor); + } + }); + } catch (_) { + } + try { + Object.defineProperty(identityPrototype, 'superclass', { + configurable: true, + enumerable: false, + get: function() { + var constructor = nativeClassForInstance( + this, + classFallback, + baseConstructor + ); + if (!constructor) { + return undefined; + } + var superclass = constructor.superclass; + if (typeof superclass === 'function' && superclass.kind !== 'class') { + return superclass.call(constructor); + } + return superclass; + } + }); + } catch (_) { + } + try { + Object.setPrototypeOf(target, identityPrototype); + } catch (_) { + } + } + + function wrapNativeClass(nativeClass) { + if (!nativeClass || (typeof nativeClass !== 'object' && typeof nativeClass !== 'function')) { + return nativeClass; + } + var nativeClassName = nativeClass.runtimeName || nativeClass.name || ''; + if (nativeClassName && classWrappersByName[nativeClassName]) { + if (classWrappers) { + try { + classWrappers.set(nativeClass, classWrappersByName[nativeClassName]); + } catch (_) { + } + } + return classWrappersByName[nativeClassName]; + } + if (classWrappers) { + var cached = classWrappers.get(nativeClass); + if (cached) { + return cached; + } + } + var constructable = function NativeScriptNativeClass() { + var args = Array.prototype.slice.call(arguments); + var redirectConstructor = this && this.constructor; + if (redirectConstructor && + redirectConstructor !== constructable && + redirectConstructor !== wrapper && + typeof redirectConstructor.__nativeApiEnsureClass === 'function') { + var redirectedWrapper = redirectConstructor.__nativeApiEnsureClass(); + if (redirectedWrapper && + redirectedWrapper !== constructable && + redirectedWrapper !== wrapper && + typeof redirectedWrapper === 'function') { + return rememberClassOnInstance( + redirectedWrapper.call(this, ...args), + redirectConstructor + ); + } + } + if (args.length > 0 || + shouldUseAllocInitConstructor(constructable, wrapper)) { + return rememberInstanceClass(constructNativeInstance( + nativeClass, + args, + rememberInstanceClass, + shouldUseAllocInitConstructor(constructable, wrapper) + )); + } + if (typeof nativeClass.new !== 'function') { + throw new Error('Native class cannot be initialized'); + } + return rememberInstanceClass(nativeClass.new()); + }; + function rememberInstanceClass(instance) { + return rememberClassOnInstance(instance, wrapper || constructable); + } + // Static allocators may be invoked with a TypeScript-derived class as + // the receiver (core does `_super.new.call(this)`); those must + // materialize and allocate the derived Objective-C class, not the base. + function derivedClassWrapper(target) { + if (target && target !== constructable && target !== wrapper && + typeof target.__nativeApiEnsureClass === 'function') { + var derived = target.__nativeApiEnsureClass(); + if (derived && derived !== constructable && derived !== wrapper) { + return derived; + } + } + return undefined; + } + try { + Object.defineProperty(constructable, 'name', { + configurable: true, + enumerable: false, + value: nativeClassName || nativeClass.name || 'NativeScriptNativeClass' + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'extend', { + configurable: true, + enumerable: false, + writable: false, + value: function(methods, options) { + if (methods == null || typeof methods !== 'object') { + throw new Error('extend() first parameter must be an object'); + } + var extensionMethods = nativeExtensionMethodsWithIndexedCollectionAliases(methods); + var extendOptions = + nativeExtensionOptionsWithIterator(options, extensionMethods); + var extendedNativeClass = api.__extendClass(nativeClass, extensionMethods, extendOptions); + var extended = wrapNativeClass(extendedNativeClass); + try { + Object.defineProperty(extended, '__nativeApiUseAllocInitConstructor', { + configurable: false, + enumerable: false, + writable: false, + value: true + }); + } catch (_) { + } + try { + Object.setPrototypeOf(extended, wrapper || constructable); + } catch (_) { + } + var extendedPrototype = Object.create(constructable.prototype || null); + try { + Object.defineProperties(extendedPrototype, Object.getOwnPropertyDescriptors(extensionMethods)); + } catch (_) { + Object.keys(extensionMethods).forEach(function(key) { + extendedPrototype[key] = extensionMethods[key]; + }); + } + try { + Object.defineProperty(extendedPrototype, 'constructor', { + configurable: true, + enumerable: false, + writable: true, + value: extended + }); + } catch (_) { + } + installInstanceClassIdentity(extendedPrototype, extended, constructable); + extended.prototype = extendedPrototype; + try { + api.__rememberClassWrapper(extendedNativeClass, extended, extendedPrototype); + } catch (_) { + } + return extended; + } + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'alloc', { + configurable: true, + enumerable: false, + writable: true, + value: function() { + if (arguments.length !== 0) { + throw new Error('alloc does not take arguments; use invoke for an explicit Objective-C selector.'); + } + var derived = derivedClassWrapper(this); + if (derived && typeof derived.alloc === 'function') { + return rememberClassOnInstance(derived.alloc.apply(derived, arguments), this); + } + return rememberInstanceClass(nativeClass.alloc.apply(nativeClass, arguments)); + } + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'new', { + configurable: true, + enumerable: false, + writable: false, + value: function() { + if (arguments.length !== 0) { + throw new Error('new does not take arguments; use invoke for an explicit Objective-C selector.'); + } + var derived = derivedClassWrapper(this); + if (derived && typeof derived.new === 'function') { + return rememberClassOnInstance(derived.new(), this); + } + if (typeof nativeClass.new !== 'function') { + throw new Error('Native class cannot be initialized'); + } + return rememberInstanceClass(nativeClass.new()); + } + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'caller', { + configurable: true, + enumerable: false, + writable: false, + value: null + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'arguments', { + configurable: true, + enumerable: false, + writable: false, + value: null + }); + } catch (_) { + } + var basePrototypeTarget = {}; + var classMembersInstalled = false; + function selectorArgumentCount(selectorName) { + var count = 0; + if (typeof selectorName !== 'string') { + return count; + } + for (var i = 0; i < selectorName.length; i++) { + if (selectorName.charCodeAt(i) === 58) { + count++; + } + } + return count; + } + function selectorDescriptor(member, selectorName, signatureOffset, argumentCount, runtimeOnly) { + return { + name: member.name || '', + selectorName: selectorName || '', + setterSelectorName: member.setterSelectorName || '', + signatureOffset: typeof signatureOffset === 'number' ? signatureOffset : 0, + setterSignatureOffset: typeof member.setterSignatureOffset === 'number' + ? member.setterSignatureOffset + : 0, + flags: typeof member.flags === 'number' ? member.flags : 0, + property: !!member.property, + readonly: !!member.readonly, + argumentCount: typeof argumentCount === 'number' + ? argumentCount + : selectorArgumentCount(selectorName), + runtimeOnly: !!runtimeOnly + }; + } + function addSelectorGroups(groups, members, runtimeOnly) { + if (!groups || !members || typeof members.length !== 'number') { + return; + } + for (var i = 0; i < members.length; i++) { + var member = members[i]; + if (!member || member.property || !member.name || !member.selectorName) { + continue; + } + // Skip methods that need special interceptor handling with kNonMasking. + if (member.name === 'superclass' || member.name === 'class' || + member.name === 'constructor' || member.name === 'className') { + continue; + } + var argumentCount = typeof member.argumentCount === 'number' + ? member.argumentCount + : 0; + var group = groups[member.name]; + if (!group) { + group = []; + groups[member.name] = group; + } + if (group[argumentCount] === undefined) { + group[argumentCount] = selectorDescriptor( + member, + member.selectorName, + member.signatureOffset, + argumentCount, + runtimeOnly + ); + } + // Methods with a trailing NSError** out-parameter (selector ending in + // "error:") may be called with the error argument omitted, so register + // the error-omitted arity too. + if (argumentCount > 0 && + /error:$/.test(member.selectorName) && + group[argumentCount - 1] === undefined) { + group[argumentCount - 1] = selectorDescriptor( + member, + member.selectorName, + member.signatureOffset, + argumentCount - 1, + runtimeOnly + ); + } + } + } + function installSelectorGroups(target, groups, receiverIsClass) { + if (!target || !groups) { + return; + } + for (var name in groups) { + if (!Object.prototype.hasOwnProperty.call(groups, name) || + Object.prototype.hasOwnProperty.call(target, name)) { + continue; + } + var selectors = groups[name]; + if (!selectors || !selectors.length) { + continue; + } + var hasMetadataSelector = false; + for (var selectorIndex = 0; selectorIndex < selectors.length; selectorIndex++) { + if (selectors[selectorIndex] && !selectors[selectorIndex].runtimeOnly) { + hasMetadataSelector = true; + break; + } + } + if (!hasMetadataSelector && receiverIsClass && name in target) { + continue; + } + var selectorFunction = + api.__makeSelectorGroupFunction(nativeClass, !!receiverIsClass, selectors); + Object.defineProperty(target, name, { + configurable: true, + enumerable: false, + writable: true, + value: receiverIsClass + ? (function(fn, memberName) { + return function() { + if (this && typeof this === 'object' && this.kind === 'object') { + var baseArgs = [nativeClass, this, memberName]; + for (var baseArgIndex = 0; baseArgIndex < arguments.length; baseArgIndex++) { + baseArgs.push(arguments[baseArgIndex]); + } + return api.__invokeBase(...baseArgs); + } + var args = []; + for (var argIndex = 0; argIndex < arguments.length; argIndex++) { + args.push(arguments[argIndex]); + } + return rememberInstanceClass(fn(...args)); + }; + })(selectorFunction, name) + : selectorFunction + }); + } + } + function installClassMembers(target, members, receiverIsClass, runtimeMembers) { + var hasMetadataMembers = members && typeof members.length === 'number'; + var hasRuntimeMembers = runtimeMembers && typeof runtimeMembers.length === 'number'; + if (!target || (!hasMetadataMembers && !hasRuntimeMembers)) { + return; + } + var selectorGroups = Object.create(null); + addSelectorGroups(selectorGroups, members, false); + for (var i = 0; hasMetadataMembers && i < members.length; i++) { + var member = members[i]; + if (!member || !member.name) { + continue; + } + if (member.property) { + // Skip properties that need special interceptor handling (they + // return wrapped class constructors, not raw native values). + if (member.name === 'superclass' || member.name === 'class' || + member.name === 'constructor' || member.name === 'debugDescription' || + member.name === 'className') { + continue; + } + var existingDescriptor = Object.getOwnPropertyDescriptor(target, member.name); + if (existingDescriptor && + (typeof existingDescriptor.get === 'function' || + typeof existingDescriptor.set === 'function')) { + continue; + } + var getterFunction = member.selectorName + ? api.__makeSelectorGroupFunction( + nativeClass, + !!receiverIsClass, + [selectorDescriptor(member, member.selectorName, member.signatureOffset, 0)] + ) + : undefined; + var setterFunction = !member.readonly && member.setterSelectorName + ? api.__makeSelectorGroupFunction( + nativeClass, + !!receiverIsClass, + [ + null, + selectorDescriptor( + member, + member.setterSelectorName, + member.setterSignatureOffset, + 1 + ) + ] + ) + : undefined; + var descriptor = { + configurable: true, + enumerable: false, + get: receiverIsClass + ? (function(name, selectorName) { + return function() { + return selectorName + ? nativeClass.invoke(selectorName) + : nativeClass[name]; + }; + })(member.name, member.selectorName) + : (getterFunction || (function(name) { + return function() { + return api.__invokeBase(nativeClass, this, name); + }; + })(member.name)) + }; + if (!member.readonly) { + descriptor.set = receiverIsClass + ? (function(name, setterSelectorName) { + return function(value) { + if (setterSelectorName) { + return nativeClass.invoke(setterSelectorName, value); + } + nativeClass[name] = value; + }; + })(member.name, member.setterSelectorName) + : (setterFunction || (function(name) { + return function(value) { + return api.__invokeBase(nativeClass, this, name, value); + }; + })(member.name)); + } + Object.defineProperty(target, member.name, descriptor); + } else { + continue; + } + } + installSelectorGroups(target, selectorGroups, receiverIsClass); + } + function installNativeClassMembersIfNeeded() { + if (classMembersInstalled) { + return; + } + classMembersInstalled = true; + installClassMembers( + constructable, + nativeClass.__staticMembers, + true, + nativeClass.__runtimeStaticMembers + ); + installClassMembers( + basePrototypeTarget, + nativeClass.__instanceMembers, + false, + nativeClass.__runtimeInstanceMembers + ); + try { + delete constructable.__nativeApiInstallMembers; + } catch (_) { + } + } + try { + Object.defineProperty(constructable, '__nativeApiInstallMembers', { + configurable: true, + enumerable: false, + writable: false, + value: installNativeClassMembersIfNeeded + }); + } catch (_) { + } + try { + Object.defineProperty(basePrototypeTarget, 'constructor', { + configurable: true, + enumerable: false, + writable: true, + value: constructable + }); + } catch (_) { + } + try { + Object.defineProperty(basePrototypeTarget, 'toString', { + configurable: true, + enumerable: false, + writable: true, + value: function() { + return '[object NativeScriptObject]'; + } + }); + } catch (_) { + } + try { + if (typeof Symbol === 'function' && Symbol.iterator && + typeof api.__fastEnumeration === 'function') { + Object.defineProperty(basePrototypeTarget, Symbol.iterator, { + configurable: true, + enumerable: false, + writable: true, + value: function() { + return api.__fastEnumeration(this); + } + }); + } + } catch (_) { + } + constructable.prototype = basePrototypeTarget; + try { + Object.defineProperty(constructable, Symbol.hasInstance, { + configurable: true, + enumerable: false, + value: function(value) { + if (!value || typeof value !== 'object') { + return false; + } + // `this` is the constructor instanceof was invoked on. A + // TypeScript-derived native class inherits this method through the + // wrapper prototype chain until it materializes, so membership must + // be answered for the DERIVED Objective-C class — and before + // materialization no instance of it can exist. + try { + if (this && this !== constructable && this !== wrapper && + this.__nativeApiTypeScriptState) { + var derivedWrapper = this.__nativeApiTypeScriptState.wrapper; + if (!derivedWrapper) { + return false; + } + if (derivedWrapper !== constructable && derivedWrapper !== wrapper) { + return derivedWrapper[Symbol.hasInstance](value); + } + } + } catch (_) { + } + var expectedName = nativeClass.runtimeName || nativeClass.name; + try { + // Pass the proxied wrapper: the raw constructable carries no + // __nativeApiClass, so it does not marshal to the Objective-C + // Class and isKindOfClass() misreports. + if (typeof value.isKindOfClass === 'function' && + value.isKindOfClass(wrapper || constructable) === true) { + return true; + } + } catch (_) { + } + try { + var current = typeof value.class === 'function' ? value.class() : null; + while (current) { + if (current === wrapper || current === constructable) { + return true; + } + var currentName = current.runtimeName || current.name; + if (typeof expectedName === 'string' && currentName === expectedName) { + return true; + } + var next = current.superclass || null; + if (typeof next === 'function' && next.kind !== 'class') { + next = next.call(current); + } + current = next || null; + } + } catch (_) { + } + return typeof expectedName === 'string' && value.className === expectedName; + } + }); + } catch (_) { + } + var cachedNativeFunctions = typeof Map === 'function' ? new Map() : null; + var wrapper = typeof Proxy === 'function' + ? new Proxy(constructable, { + get: function(target, property, receiver) { + if (property === '__nativeApiClass') { + return nativeClass; + } + if (property === 'toString') { + return function() { + return String(nativeClass); + }; + } + if (property === 'prototype') { + installNativeClassMembersIfNeeded(); + return Reflect.get(target, property, receiver); + } + if (property === 'hasOwnProperty') { + return function(key) { + installNativeClassMembersIfNeeded(); + return Object.prototype.hasOwnProperty.call(target, key); + }; + } + if (Object.prototype.hasOwnProperty.call(target, property) || + property === 'prototype' || + property === 'length' || + property === 'name') { + return Reflect.get(target, property, receiver); + } + installNativeClassMembersIfNeeded(); + if (Object.prototype.hasOwnProperty.call(target, property)) { + return Reflect.get(target, property, receiver); + } + if (cachedNativeFunctions && cachedNativeFunctions.has(property)) { + return cachedNativeFunctions.get(property); + } + var nativeValue = nativeClass[property]; + if (nativeValue !== undefined) { + if (typeof nativeValue === 'function') { + if (cachedNativeFunctions) { + cachedNativeFunctions.set(property, nativeValue); + } + try { + Object.defineProperty(target, property, { + configurable: true, + enumerable: false, + writable: false, + value: nativeValue + }); + } catch (_) { + } + } + return nativeValue; + } + var reflected = Reflect.get(target, property, receiver); + if (reflected !== undefined || property in target) { + return reflected; + } + installNativeClassMembersIfNeeded(); + reflected = Reflect.get(target, property, receiver); + if (reflected !== undefined || property in target) { + return reflected; + } + return reflected; + }, + set: function(target, property, value, receiver) { + if (property === 'prototype') { + target[property] = value; + return true; + } + installNativeClassMembersIfNeeded(); + if (setDescriptorValue(target, property, receiver, value)) { + return true; + } + if (setInheritedNativeClassValue(target, property, value)) { + return true; + } + try { + nativeClass[property] = value; + return true; + } catch (_) { + } + if (receiver && receiver !== target) { + Object.defineProperty(receiver, property, { + configurable: true, + enumerable: true, + writable: true, + value: value + }); + return true; + } + return Reflect.set(target, property, value, receiver); + }, + has: function(target, property) { + installNativeClassMembersIfNeeded(); + return property in target || property in nativeClass; + }, + ownKeys: function(target) { + installNativeClassMembersIfNeeded(); + return Reflect.ownKeys(target).filter(function(key) { + return key !== 'new' && + key !== 'hasOwnProperty' && + key !== '__nativeApiInstallMembers'; + }); + }, + getOwnPropertyDescriptor: function(target, property) { + installNativeClassMembersIfNeeded(); + return Reflect.getOwnPropertyDescriptor(target, property); + } + }) + : constructable; + if (classWrappers) { + classWrappers.set(nativeClass, wrapper); + } + try { + var nativeSuperclass = nativeClass.__superclass; + if (nativeSuperclass && nativeSuperclass !== nativeClass) { + var superclassWrapper = wrapNativeClass(nativeSuperclass); + if (superclassWrapper && superclassWrapper !== wrapper && + typeof Object.setPrototypeOf === 'function') { + Object.setPrototypeOf(wrapper, superclassWrapper); + if (superclassWrapper.prototype) { + Object.setPrototypeOf(constructable.prototype, superclassWrapper.prototype); + } + } + } + } catch (_) { + } + try { + api.__rememberClassWrapper(nativeClass, wrapper, constructable.prototype); + } catch (_) { + } + if (nativeClassName) { + classWrappersByName[nativeClassName] = wrapper; + cacheGlobal(nativeClassName, wrapper); + if (!Object.prototype.hasOwnProperty.call(globalThis, nativeClassName)) { + try { + Object.defineProperty(globalThis, nativeClassName, { + configurable: true, + enumerable: false, + writable: false, + value: wrapper + }); + } catch (_) { + } + } + } + if (nativeClass.name && nativeClass.name !== nativeClassName) { + classWrappersByName[nativeClass.name] = wrapper; + cacheGlobal(nativeClass.name, wrapper); + } + return wrapper; + } + + function rememberClassOnInstance(instance, classWrapper) { + if (instance && typeof instance === 'object' && classWrapper) { + try { + if (typeof classWrapper.__nativeApiInstallMembers === 'function') { + classWrapper.__nativeApiInstallMembers(); + } + if (typeof api.__rememberObjectClassWrapper === 'function') { + api.__rememberObjectClassWrapper(instance, classWrapper); + } else { + instance.__nativeApiClassWrapper = classWrapper; + } + } catch (_) { + } + } + return instance; + } + + function isNativeClassLike(value) { + if (!value || (typeof value !== 'object' && typeof value !== 'function')) { + return false; + } + if (value.kind === 'class') { + return true; + } + try { + return !!value.__nativeApiClass; + } catch (_) { + return false; + } + } + + function nativeClassLikeHandle(value) { + if (!value || (typeof value !== 'object' && typeof value !== 'function')) { + return value; + } + try { + if (typeof value.__nativeApiEnsureClass === 'function') { + value = value.__nativeApiEnsureClass(); + } + } catch (_) { + } + try { + return value.__nativeApiClass || value; + } catch (_) { + return value; + } + } + + function materializeTypeScriptNativeClass(constructor) { + if (!constructor || typeof constructor !== 'function') { + return undefined; + } + var state = constructor.__nativeApiTypeScriptState; + if (!state) { + return undefined; + } + if (state.wrapper) { + return state.wrapper; + } + if (state.materializing) { + return state.base; + } + + state.materializing = true; + try { + var baseWrapper = state.base; + if (baseWrapper && typeof baseWrapper.__nativeApiEnsureClass === 'function') { + baseWrapper = baseWrapper.__nativeApiEnsureClass(); + } + + var options = {}; + var className = constructor.ObjCClassName || constructor.name; + if (className) { + options.name = className; + } + if (constructor.ObjCProtocols) { + options.protocols = constructor.ObjCProtocols; + } + if (constructor.ObjCExposedMethods) { + options.exposedMethods = constructor.ObjCExposedMethods; + } + + var nativeBase = nativeClassLikeHandle(baseWrapper); + var extensionMethods = + nativeExtensionMethodsWithIndexedCollectionAliases(constructor.prototype || {}); + options = nativeExtensionOptionsWithIterator(options, extensionMethods); + var nativeClass = api.__extendClass(nativeBase, extensionMethods, options); + var wrapper = wrapNativeClass(nativeClass); + state.wrapper = wrapper; + try { + Object.defineProperty(wrapper, '__nativeApiUseAllocInitConstructor', { + configurable: false, + enumerable: false, + writable: false, + value: true + }); + } catch (_) { + } + + try { + Object.setPrototypeOf(constructor, wrapper); + } catch (_) { + } + try { + api.__rememberClassWrapper(nativeClass, constructor, extensionMethods); + } catch (_) { + } + return wrapper; + } finally { + state.materializing = false; + } + } + + function defineTypeScriptStaticForwarder(constructor, name, isProperty, readonly) { + if (!name || name === 'length' || name === 'name' || name === 'prototype' || + Object.prototype.hasOwnProperty.call(constructor, name)) { + return; + } + + var descriptor = { + configurable: true, + enumerable: false + }; + + if (isProperty) { + descriptor.get = function() { + var wrapper = materializeTypeScriptNativeClass(constructor); + return wrapper ? wrapper[name] : undefined; + }; + if (!readonly) { + descriptor.set = function(value) { + var wrapper = materializeTypeScriptNativeClass(constructor); + if (wrapper) { + wrapper[name] = value; + } + }; + } + } else { + descriptor.writable = true; + descriptor.value = function() { + if (name === 'class') { + materializeTypeScriptNativeClass(constructor); + return constructor; + } + if (name === 'superclass') { + var state = constructor.__nativeApiTypeScriptState; + return state && state.base; + } + var wrapper = materializeTypeScriptNativeClass(constructor); + var member = wrapper && wrapper[name]; + if (typeof member !== 'function') { + throw new TypeError(String(name) + ' is not a function'); + } + var memberArgs = []; + for (var memberArgIndex = 0; memberArgIndex < arguments.length; memberArgIndex++) { + memberArgs.push(arguments[memberArgIndex]); + } + var result = wrapper[name](...memberArgs); + if (name === 'alloc' || name === 'new' || name === 'construct') { + return rememberClassOnInstance(result, constructor); + } + return result; + }; + } + + try { + Object.defineProperty(constructor, name, descriptor); + } catch (_) { + } + } + + function installTypeScriptNativeClassSupport(constructor, base) { + if (!constructor || typeof constructor !== 'function' || !isNativeClassLike(base)) { + return false; + } + if (constructor.__nativeApiTypeScriptState) { + return true; + } + + try { + Object.defineProperty(constructor, '__nativeApiTypeScriptState', { + configurable: false, + enumerable: false, + writable: false, + value: { + base: base, + wrapper: null, + materializing: false + } + }); + } catch (_) { + constructor.__nativeApiTypeScriptState = { + base: base, + wrapper: null, + materializing: false + }; + } + + try { + Object.defineProperty(constructor, '__nativeApiEnsureClass', { + configurable: false, + enumerable: false, + writable: false, + value: function() { + return materializeTypeScriptNativeClass(constructor); + } + }); + } catch (_) { + } + + try { + Object.defineProperty(constructor, '__nativeApiClass', { + configurable: true, + enumerable: false, + get: function() { + var wrapper = materializeTypeScriptNativeClass(constructor); + return wrapper && wrapper.__nativeApiClass; + } + }); + } catch (_) { + } + + installInstanceClassIdentity(constructor.prototype || {}, constructor, null); + + ['alloc', 'new', 'class', 'superclass', 'extend'].forEach(function(name) { + defineTypeScriptStaticForwarder(constructor, name, false, false); + }); + + try { + var members = base.__staticMembers || []; + for (var i = 0; i < members.length; i++) { + var member = members[i]; + if (member && member.name) { + defineTypeScriptStaticForwarder( + constructor, + member.name, + !!member.property, + !!member.readonly + ); + } + } + } catch (_) { + } + + return true; + } + + function installTypeScriptNativeHelpers() { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function(d, b) { d.__proto__ = b; }) || + function(d, b) { + for (var p in b) { + if (Object.prototype.hasOwnProperty.call(b, p)) { + d[p] = b[p]; + } + } + }; + + globalThis.__extends = function(d, b) { + if (typeof b !== 'function' && b !== null) { + throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null'); + } + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + if (b !== null) { + installTypeScriptNativeClassSupport(d, b); + } + }; + + globalThis.NativeClass = function NativeClass(constructor) { + if (constructor && typeof constructor.__nativeApiEnsureClass === 'function') { + constructor.__nativeApiEnsureClass(); + } + return constructor; + }; + + globalThis.ObjCClass = function ObjCClass() { + var protocols = Array.prototype.slice.call(arguments); + return function(constructor) { + if (constructor.ObjCProtocols) { + for (var protocolIndex = 0; protocolIndex < protocols.length; protocolIndex++) { + constructor.ObjCProtocols.push(protocols[protocolIndex]); + } + } else { + constructor.ObjCProtocols = protocols; + } + if (typeof constructor.__nativeApiEnsureClass === 'function') { + constructor.__nativeApiEnsureClass(); + } + return constructor; + }; + }; + } + + function wrapInteropFactory(nativeFactory, properties) { + if (typeof nativeFactory !== 'function' || nativeFactory.__nativeScriptConstructable) { + return nativeFactory; + } + var constructable = function NativeScriptInteropValue() { + var factoryArgs = []; + for (var factoryArgIndex = 0; factoryArgIndex < arguments.length; factoryArgIndex++) { + factoryArgs.push(arguments[factoryArgIndex]); + } + return nativeFactory(...factoryArgs); + }; + try { + if (nativeFactory.prototype) { + constructable.prototype = nativeFactory.prototype; + } + } catch (_) { + } + try { + Object.defineProperty(constructable, Symbol.hasInstance, { + configurable: true, + enumerable: false, + value: function(value) { + return !!value && typeof value === 'object' && value.kind === properties.kind; + } + }); + } catch (_) { + } + Object.keys(properties).forEach(function(key) { + try { + Object.defineProperty(constructable, key, { + configurable: true, + enumerable: false, + writable: false, + value: properties[key] + }); + } catch (_) { + } + }); + Object.defineProperty(constructable, '__nativeScriptConstructable', { + configurable: false, + enumerable: false, + writable: false, + value: true + }); + return constructable; + } + + function installInteropConstructors() { + var interop = globalThis.interop; + if (!interop || typeof interop !== 'object') { + return; + } + var pointerSize; + try { + if (typeof interop.sizeof === 'function' && interop.types && interop.types.pointer !== undefined) { + pointerSize = interop.sizeof(interop.types.pointer); + } + } catch (_) { + pointerSize = undefined; + } + interop.Pointer = wrapInteropFactory(interop.Pointer, { kind: 'pointer', sizeof: pointerSize }); + interop.Reference = wrapInteropFactory(interop.Reference, { kind: 'reference', sizeof: pointerSize }); + interop.Block = wrapInteropFactory(interop.Block, { kind: 'block', sizeof: pointerSize }); + interop.FunctionReference = wrapInteropFactory( + interop.FunctionReference, + { kind: 'functionReference', sizeof: pointerSize } + ); + if (interop.types && typeof interop.types === 'object') { + Object.keys(interop.types).forEach(function(name) { + var value = interop.types[name]; + if (typeof value !== 'number') { + return; + } + var boxed = { + valueOf: function() { return value; }, + toString: function() { return String(value); } + }; + Object.defineProperty(boxed, typeCodeKey, { + configurable: false, + enumerable: false, + writable: false, + value: value + }); + interop.types[name] = boxed; + }); + } + } + + function installObjcHelpers() { + var objc = globalThis.objc; + if (!objc || (typeof objc !== 'object' && typeof objc !== 'function')) { + objc = {}; + Object.defineProperty(globalThis, 'objc', { + configurable: true, + enumerable: true, + writable: true, + value: objc + }); + } + if (typeof objc.autoreleasepool !== 'function') { + objc.autoreleasepool = api.autoreleasepool; + } + } + + function defineInlineFunction(name, value) { + if (Object.prototype.hasOwnProperty.call(globalThis, name)) { + return; + } + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: false, + writable: true, + value: value + }); + } + + function installInlineFunctions() { + var makePoint = function(x, y) { return { x: x, y: y }; }; + var makeSize = function(width, height) { return { width: width, height: height }; }; + var makeRect = function(x, y, width, height) { + return { origin: { x: x, y: y }, size: { width: width, height: height } }; + }; + defineInlineFunction('CGPointMake', makePoint); + defineInlineFunction('NSMakePoint', makePoint); + defineInlineFunction('CGSizeMake', makeSize); + defineInlineFunction('NSMakeSize', makeSize); + defineInlineFunction('CGRectMake', makeRect); + defineInlineFunction('NSMakeRect', makeRect); + defineInlineFunction('NSMakeRange', function(location, length) { + return { location: location, length: length }; + }); + defineInlineFunction('UIEdgeInsetsMake', function(top, left, bottom, right) { + return { top: top, left: left, bottom: bottom, right: right }; + }); + } + + function names(kind) { + var metadata = api.metadata; + var fn = metadata && metadata[kind]; + return typeof fn === 'function' ? fn() : []; + } + + function nameSet(values) { + var result = Object.create(null); + (values || []).forEach(function(value) { + result[value] = true; + }); + return result; + } + + var classNameList = names('classNames'); + var functionNameList = names('functionNames'); + var constantNameList = names('constantNames'); + var protocolNameList = names('protocolNames'); + var enumNameList = names('enumNames'); + var functionNameSet = nameSet(functionNameList); + var constantNameSet = nameSet(constantNameList); + var classNameSet = nameSet(classNameList); + var protocolNameSet = nameSet(protocolNameList); + var enumNameSet = nameSet(enumNameList); + + function resolveNativeApiEnum(enumName) { + return (api.getEnum && api.getEnum(enumName)) || api[enumName]; + } + + Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiLazyGlobal', { + configurable: false, + enumerable: false, + writable: false, + value: function(name, kind) { + var value; + if (kind === 'class') { + value = wrapNativeClass(api[name]); + } else if (kind === 'function' || kind === 'constant') { + value = api[name]; + } else if (kind === 'protocol') { + value = (api.getProtocol && api.getProtocol(name)) || api[name]; + } else if (kind === 'enum') { + value = resolveNativeApiEnum(name); + } else if (kind === 'struct') { + value = wrapAggregateConstructor((api.getStruct && api.getStruct(name)) || api[name]); + } else if (kind === 'union') { + value = wrapAggregateConstructor((api.getUnion && api.getUnion(name)) || api[name]); + } else if (kind && kind.indexOf('enumMember:') === 0) { + var enumValue = resolveNativeApiEnum(kind.slice('enumMember:'.length)); + value = enumValue && enumValue[name]; + } else { + value = api[name]; + } + cacheGlobal(name, value); + return value; + } + }); + + classNameList.forEach(function(name) { + defineLazyGlobal(name, function(className) { + return wrapNativeClass(api[className]); + }, false, 'class'); + }); + functionNameList.forEach(function(name) { + defineLazyGlobal(name, function(functionName) { + return api[functionName]; + }, false, 'function'); + }); + constantNameList.forEach(function(name) { + defineLazyGlobal(name, function(constantName) { + return api[constantName]; + }, false, 'constant'); + }); + protocolNameList.forEach(function(name) { + defineLazyGlobal(name, function(protocolName) { + return (api.getProtocol && api.getProtocol(protocolName)) || api[protocolName]; + }, false, 'protocol'); + }); + enumNameList.forEach(function(name) { + defineLazyGlobal(name, resolveNativeApiEnum, false, 'enum'); + var enumValue = resolveNativeApiEnum(name); + if (!enumValue || typeof enumValue !== 'object') { + return; + } + Object.keys(enumValue).forEach(function(memberName) { + if (/^-?\d+$/.test(memberName)) { + return; + } + defineLazyGlobal(memberName, function() { + return enumValue[memberName]; + }, false, 'enumMember:' + name); + }); + }); + names('structNames').forEach(function(name) { + var conflictsWithValue = + !!functionNameSet[name] || !!constantNameSet[name] || !!classNameSet[name] || + !!protocolNameSet[name] || !!enumNameSet[name]; + defineLazyGlobal(name, function(structName) { + return wrapAggregateConstructor((api.getStruct && api.getStruct(structName)) || api[structName]); + }, !conflictsWithValue, 'struct'); + }); + names('unionNames').forEach(function(name) { + var conflictsWithValue = + !!functionNameSet[name] || !!constantNameSet[name] || !!classNameSet[name] || + !!protocolNameSet[name] || !!enumNameSet[name]; + defineLazyGlobal(name, function(unionName) { + return wrapAggregateConstructor((api.getUnion && api.getUnion(unionName)) || api[unionName]); + }, !conflictsWithValue, 'union'); + }); + + if (typeof globalThis.UIColor === 'undefined' && + typeof globalThis.NSColor !== 'undefined') { + globalThis.UIColor = globalThis.NSColor; + cacheGlobal('UIColor', globalThis.UIColor); + } + var colorCtor = globalThis.UIColor || globalThis.NSColor; + if (colorCtor && colorCtor.prototype && + typeof colorCtor.prototype.initWithRedGreenBlueAlpha !== 'function') { + colorCtor.prototype.initWithRedGreenBlueAlpha = function(red, green, blue, alpha) { + if (typeof this.initWithSRGBRedGreenBlueAlpha === 'function') { + return this.initWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof this.initWithCalibratedRedGreenBlueAlpha === 'function') { + return this.initWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof colorCtor.colorWithSRGBRedGreenBlueAlpha === 'function') { + return colorCtor.colorWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof colorCtor.colorWithCalibratedRedGreenBlueAlpha === 'function') { + return colorCtor.colorWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); + } + return this; + }; + } + defineLazyGlobal('CC_SHA256', function() { return api.CC_SHA256; }); + + installInteropConstructors(); + installObjcHelpers(); + installTypeScriptNativeHelpers(); + installInlineFunctions(); + + try { + Object.defineProperty(globalThis, installedFlagName, { + configurable: false, + enumerable: false, + writable: false, + value: true + }); + } catch (_) { + } +}) +)Engine_GLOBALS"; + + std::string script(GlobalInstaller); + script += "("; + script += jsStringLiteral(globalName); + script += ");"; + runtime.evaluateJavaScript(std::make_shared(std::move(script)), + "NativeApiGlobals.js"); + NativeApiWriteSmokeStage("engine:globals:after-eval"); +} + +void InstallNativeApi(Runtime& runtime, const NativeApiConfig& config) { + const char* globalName = config.globalName != nullptr && config.globalName[0] != '\0' + ? config.globalName + : "__nativeScriptNativeApi"; + NativeApiWriteSmokeStage("engine:create-api"); + Object api = CreateNativeApi(runtime, config); + Object global = runtime.global(); + NativeApiWriteSmokeStage("engine:set-global"); + global.setProperty(runtime, globalName, api); + + NativeApiWriteSmokeStage("engine:set-interop"); + Value existingInterop = global.getProperty(runtime, "interop"); + if (existingInterop.isUndefined() || existingInterop.isNull()) { + global.setProperty(runtime, "interop", api.getProperty(runtime, "interop")); + } + if (config.installGlobalSymbols) { + NativeApiWriteSmokeStage("engine:install-globals"); + InstallNativeApiGlobalSymbols(runtime, globalName); + } else { + // RN doesn't install the aggregate global surface: unused, and building + // it eagerly costs launch time. + NativeApiWriteSmokeStage("engine:skip-globals"); + } + NativeApiWriteSmokeStage("engine:installed"); +} diff --git a/NativeScript/ffi/objc/shared/bridge/Invocation.mm b/NativeScript/ffi/objc/shared/bridge/Invocation.mm new file mode 100644 index 000000000..b0230c892 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/Invocation.mm @@ -0,0 +1,1846 @@ +bool isValidMetadataStringOffset(MDMetadataReader* metadata, + MDSectionOffset offset) { + if (metadata == nullptr || metadata->constantsOffset < metadata->stringsOffset) { + return false; + } + return offset < metadata->constantsOffset - metadata->stringsOffset; +} + +bool startsWith(const std::string& value, const std::string& prefix) { + return value.size() >= prefix.size() && + value.compare(0, prefix.size(), prefix) == 0; +} + +bool endsWith(const std::string& value, const std::string& suffix) { + return value.size() >= suffix.size() && + value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +std::string stripEnumSuffix(const std::string& enumName) { + static const std::vector suffixes = { + "Options", "Option", "Enums", "Enum", "Result", "Enumeration", + "Orientation", "Style", "Mask", "Type", "Status", "Modes", "Mode", "s"}; + + for (const auto& suffix : suffixes) { + if (enumName.size() > suffix.size() && endsWith(enumName, suffix)) { + return enumName.substr(0, enumName.size() - suffix.size()); + } + } + + return enumName; +} + +bool isNSComparisonResultOrderingName(const std::string& enumName, + const std::string& member) { + if (enumName != "NSComparisonResult") { + return false; + } + return member == "Ascending" || member == "Same" || member == "Descending"; +} + +class NativeApiReturnStorage { + public: + explicit NativeApiReturnStorage(size_t size) + : size_(std::max(size, sizeof(void*))) { + if (size_ > kInlineSize) { + heap_.assign(size_, 0); + } else { + std::memset(inline_, 0, kInlineSize); + } + } + + void* data() { return heap_.empty() ? inline_ : heap_.data(); } + unsigned char* bytes() { return static_cast(data()); } + + private: + static constexpr size_t kInlineSize = 64; + + size_t size_ = 0; + alignas(std::max_align_t) unsigned char inline_[kInlineSize] = {}; + std::vector heap_; +}; + +class NativeApiPointerFrame { + public: + explicit NativeApiPointerFrame(size_t count) : count_(count) { + if (count_ > kInlineCount) { + heap_.resize(count_); + } + } + + void set(size_t index, void* value) { + if (index >= count_) { + throw std::out_of_range("Native invocation argument index out of range."); + } + if (count_ <= kInlineCount) { + inline_[index] = value; + } else { + heap_[index] = value; + } + } + + void** data() { + if (count_ == 0) { + return nullptr; + } + return count_ <= kInlineCount ? inline_ : heap_.data(); + } + + private: + static constexpr size_t kInlineCount = 10; + + size_t count_ = 0; + void* inline_[kInlineCount] = {}; + std::vector heap_; +}; + +Value enumToObject(Runtime& runtime, MDMetadataReader* metadata, + const NativeApiSymbol& symbol) { + Object result(runtime); + if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { + return result; + } + + std::string enumName = symbol.name; + std::string strippedPrefix = stripEnumSuffix(enumName); + MDSectionOffset offset = symbol.offset + sizeof(MDSectionOffset); + bool next = true; + while (next) { + auto nameOffset = metadata->getOffset(offset); + next = (nameOffset & metagen::mdSectionOffsetNext) != 0; + nameOffset &= ~metagen::mdSectionOffsetNext; + offset += sizeof(MDSectionOffset); + + const char* memberName = metadata->resolveString(nameOffset); + int64_t value = metadata->getEnumValue(offset); + offset += sizeof(int64_t); + + std::string canonicalName = memberName != nullptr ? memberName : ""; + std::vector aliases; + aliases.push_back(canonicalName); + + if (!strippedPrefix.empty() && startsWith(canonicalName, strippedPrefix) && + canonicalName.size() > strippedPrefix.size()) { + aliases.push_back(canonicalName.substr(strippedPrefix.size())); + } else if (!strippedPrefix.empty() && + !startsWith(canonicalName, strippedPrefix)) { + aliases.push_back(strippedPrefix + canonicalName); + } + + if (startsWith(enumName, "NS") && !startsWith(canonicalName, "NS")) { + aliases.push_back(std::string("NS") + canonicalName); + } + + if (enumName == "NSStringCompareOptions" && + !endsWith(canonicalName, "Search")) { + aliases.push_back(canonicalName + "Search"); + aliases.push_back(std::string("NS") + canonicalName + "Search"); + } + + if (!startsWith(canonicalName, "k")) { + aliases.push_back(std::string("k") + enumName + canonicalName); + } + + if (isNSComparisonResultOrderingName(enumName, canonicalName)) { + aliases.push_back(std::string("Ordered") + canonicalName); + aliases.push_back(std::string("NSOrdered") + canonicalName); + } + + std::vector uniqueAliases; + std::unordered_set seenAliases; + for (const auto& alias : aliases) { + if (!alias.empty() && seenAliases.insert(alias).second) { + uniqueAliases.push_back(alias); + } + } + + for (const auto& alias : uniqueAliases) { + result.setProperty(runtime, alias.c_str(), static_cast(value)); + } + + char valueKey[32] = {}; + snprintf(valueKey, sizeof(valueKey), "%lld", static_cast(value)); + if (!result.hasProperty(runtime, valueKey)) { + std::string reverseName = + uniqueAliases.size() > 1 ? uniqueAliases[1] : canonicalName; + result.setProperty(runtime, valueKey, makeString(runtime, reverseName)); + } + } + return result; +} + +Value constantToValue(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiSymbol& symbol) { + MDMetadataReader* metadata = bridge->metadata(); + if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { + return Value::undefined(); + } + + MDSectionOffset offset = symbol.offset + sizeof(MDSectionOffset); + auto evalKind = metadata->getVariableEvalKind(offset); + offset += sizeof(metagen::MDVariableEvalKind); + + switch (evalKind) { + case metagen::mdEvalInt64: + return static_cast(metadata->getInt64(offset)); + case metagen::mdEvalDouble: + return metadata->getDouble(offset); + case metagen::mdEvalString: { + if (isValidMetadataStringOffset(metadata, offset)) { + auto stringOffset = metadata->getOffset(offset); + return makeString(runtime, metadata->resolveString(stringOffset)); + } + + void* symbolPtr = dlsym(bridge->selfDl(), symbol.name.c_str()); + if (symbolPtr == nullptr) { + return Value::undefined(); + } + + NativeApiType stringObjectType; + stringObjectType.kind = metagen::mdTypeNSStringObject; + stringObjectType.ffiType = &ffi_type_pointer; + stringObjectType.supported = true; + return convertNativeReturnValue(runtime, bridge, stringObjectType, + symbolPtr); + } + case metagen::mdEvalNone: + break; + } + + MDSectionOffset typeOffset = offset; + NativeApiType type = parseMetadataEngineType(metadata, &typeOffset, bridge.get()); + if (unsupportedEngineType(type)) { + throw JSError( + runtime, "Native constant type is not supported by backend: " + + symbol.name); + } + + void* symbolPtr = dlsym(bridge->selfDl(), symbol.name.c_str()); + if (symbolPtr == nullptr) { + return Value::undefined(); + } + return convertNativeReturnValue(runtime, bridge, type, symbolPtr); +} + +void prepareEngineArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, const Value& arg, + size_t index, NativeApiArgumentFrame& frame) { + ffi_type* ffiType = ffiTypeForEngineArgument(type); + size_t size = + ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); + void* target = frame.storageAt(index, size); + convertEngineFfiArgument(runtime, bridge, type, arg, target, frame); +} + +void prepareEngineArguments(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiSignature& signature, + const Value* args, size_t count, + NativeApiArgumentFrame& frame) { + if (count != signature.argumentTypes.size()) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(count) + + "\". Expected: \"" + + std::to_string(signature.argumentTypes.size()) + "\"."); + } + + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + prepareEngineArgument(runtime, bridge, signature.argumentTypes[i], args[i], i, + frame); + } +} + +inline uint64_t dispatchIdForEngineSignature( + const NativeApiSignature& signature, SignatureCallKind kind) { + if (signature.signatureHash == 0) { + return 0; + } + return composeSignatureDispatchId(signature.signatureHash, kind, + signature.dispatchFlags); +} + +struct NativeApiPreparedCFunctionInvocation { + NativeApiSymbol symbol; + bool initialized = false; + void* function = nullptr; + NativeApiSignature signature; + CFunctionPreparedInvoker preparedInvoker = nullptr; +}; + +bool tryCallFastEngineCFunction( + Runtime& runtime, const std::shared_ptr& bridge, + void* function, const NativeApiSignature& signature, const Value* args, + size_t count, Value* result); + +Value callNativeFunctionPointer( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, void* pointer, bool block, const Value* args, + size_t count) { + if (pointer == nullptr) { + throw JSError(runtime, "Native function pointer is null."); + } + if (bridge == nullptr || bridge->metadata() == nullptr || + type.signatureOffset == MD_SECTION_OFFSET_NULL) { + throw JSError( + runtime, "Native function pointer metadata is unavailable."); + } + + auto signature = parseMetadataEngineSignature( + bridge->metadata(), type.signatureOffset, block ? 1 : 0, bridge.get()); + if (!signature || !signature->prepared || signature->variadic || + unsupportedEngineType(signature->returnType)) { + throw JSError( + runtime, + "Native function pointer signature is not supported by backend."); + } + + NativeApiArgumentFrame frame(signature->argumentTypes.size()); + prepareEngineArguments(runtime, bridge, *signature, args, count, frame); + + NativeApiPointerFrame values(signature->argumentTypes.size() + 1); + if (block) { + values.set(0, &pointer); + for (size_t i = 0; i < signature->argumentTypes.size(); i++) { + values.set(i + 1, frame.values()[i]); + } + } + + void* callable = pointer; + if (block) { + auto literal = static_cast(pointer); + if (literal == nullptr || literal->invoke == nullptr) { + throw JSError(runtime, "Native block invoke pointer is null."); + } + callable = literal->invoke; + } + + if (!block) { + Value fastResult; + if (tryCallFastEngineCFunction(runtime, bridge, callable, *signature, args, + count, &fastResult)) { + return fastResult; + } + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature->returnType)); + BlockPreparedInvoker blockPreparedInvoker = nullptr; + CFunctionPreparedInvoker functionPreparedInvoker = nullptr; + if (block) { + blockPreparedInvoker = lookupBlockPreparedInvoker(dispatchIdForEngineSignature( + *signature, SignatureCallKind::BlockInvoke)); + } else { + functionPreparedInvoker = lookupCFunctionPreparedInvoker( + dispatchIdForEngineSignature(*signature, SignatureCallKind::CFunction)); + } + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (block) { + if (blockPreparedInvoker != nullptr) { + blockPreparedInvoker(callable, values.data(), returnStorage.data()); + return; + } + } else { + if (functionPreparedInvoker != nullptr) { + functionPreparedInvoker(callable, frame.values(), returnStorage.data()); + return; + } + } + ffi_call(&signature->cif, FFI_FN(callable), returnStorage.data(), + block ? values.data() : frame.values()); + }); + + return convertNativeReturnValue(runtime, bridge, signature->returnType, + returnStorage.data()); +} + +Value wrapNativeFunctionPointer(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, void* pointer, + bool block) { + const char* functionName = block ? "NativeApiBlock" : "NativeApiFunctionPointer"; + auto function = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, functionName), 0, + [bridge, type, pointer, block](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + return callNativeFunctionPointer(runtime, bridge, type, pointer, block, + args, count); + }); + function.setProperty(runtime, "kind", + makeString(runtime, block ? "block" : "functionPointer")); + function.setProperty( + runtime, "__nativeApiPointerObject", + createPointer(runtime, bridge, pointer)); + function.setProperty( + runtime, "__nativeApiPointer", + static_cast(reinterpret_cast(pointer))); + function.setProperty( + runtime, "nativeAddress", + static_cast(reinterpret_cast(pointer))); + function.setProperty(runtime, "sizeof", + static_cast(sizeof(void*))); + function.setProperty( + runtime, "toString", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [pointer, block](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", pointer); + return makeString(runtime, + std::string("[NativeApi ") + + (block ? "Block " : "FunctionPointer ") + + address + "]"); + })); + return function; +} + +Value callCFunction(Runtime& runtime, + const std::shared_ptr& bridge, + const std::shared_ptr& prepared, + const Value* args, + size_t count) { + if (prepared == nullptr) { + throw JSError(runtime, "Native function state is unavailable."); + } + NativeApiRoundTripCacheFrameGuard roundTripFrame(bridge); + + MDMetadataReader* metadata = bridge->metadata(); + if (metadata == nullptr) { + throw JSError(runtime, "Native metadata is not loaded."); + } + + if (!prepared->initialized) { + void* fnptr = dlsym(bridge->selfDl(), prepared->symbol.name.c_str()); + if (fnptr == nullptr) { + throw JSError(runtime, + "Native function is not available: " + + prepared->symbol.name); + } + + MDSectionOffset signatureOffset = + metadata->signaturesOffset + + metadata->getOffset(prepared->symbol.offset + sizeof(MDSectionOffset)); + auto signature = parseMetadataEngineSignature( + metadata, signatureOffset, 0, bridge.get(), + (metadata->getFunctionFlag( + prepared->symbol.offset + sizeof(MDSectionOffset) * 2) & + metagen::mdFunctionReturnOwned) != 0); + if (!signature || !signature->prepared || signature->variadic || + unsupportedEngineType(signature->returnType)) { + throw JSError( + runtime, "Native function signature is not supported by backend: " + + prepared->symbol.name); + } + + prepared->function = fnptr; + prepared->signature = std::move(*signature); + prepared->preparedInvoker = lookupCFunctionPreparedInvoker( + dispatchIdForEngineSignature(prepared->signature, + SignatureCallKind::CFunction)); + prepared->initialized = true; + } + + NativeApiSignature& signature = prepared->signature; + Value fastResult; + if (tryCallFastEngineCFunction(runtime, bridge, prepared->function, signature, + args, count, &fastResult)) { + return fastResult; + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + prepareEngineArguments(runtime, bridge, signature, args, count, frame); + + if (prepared->symbol.name == "NSApplicationMain" || + prepared->symbol.name == "UIApplicationMain") { + runtime.drainMicrotasks(); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared->preparedInvoker != nullptr) { + prepared->preparedInvoker(prepared->function, frame.values(), + returnStorage.data()); + } else { + ffi_call(&signature.cif, FFI_FN(prepared->function), returnStorage.data(), + frame.values()); + } + }); + + NativeApiType returnType = signature.returnType; + if (prepared->symbol.name == "CFBagContainsValue" && + (returnType.kind == metagen::mdTypeChar || + returnType.kind == metagen::mdTypeUChar || + returnType.kind == metagen::mdTypeUInt8)) { + return *returnStorage.bytes() != 0; + } + return convertNativeReturnValue(runtime, bridge, returnType, + returnStorage.data()); +} + +Value callCFunction(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiSymbol& symbol, const Value* args, + size_t count) { + auto prepared = std::make_shared(); + prepared->symbol = symbol; + return callCFunction(runtime, bridge, prepared, args, count); +} + +bool signatureSupportedForEngineInvocation( + const std::optional& signature) { + if (!signature || !signature->prepared || signature->variadic || + unsupportedEngineType(signature->returnType)) { + return false; + } + for (const auto& argType : signature->argumentTypes) { + if (unsupportedEngineType(argType)) { + return false; + } + } + return true; +} + +bool signatureSupportedForEngineInvocation( + const NativeApiSignature& signature) { + if (!signature.prepared || signature.variadic || + unsupportedEngineType(signature.returnType)) { + return false; + } + for (const auto& argType : signature.argumentTypes) { + if (unsupportedEngineType(argType)) { + return false; + } + } + return true; +} + +struct NativeApiPreparedObjCInvocation { + SEL selector = nullptr; + Class receiverClass = Nil; + std::string selectorName; + // Set when this prepared invocation IS a metadata property's setter + // selector (one arg, matches member->setterSelectorName): lets a + // successful call cache the value into the UIAppearance proxy cache + // without re-deriving the property name from the selector. + std::string propertySetterName; + NativeApiSignature signature; + ObjCPreparedInvoker preparedInvoker = nullptr; + void* engineInvoker = nullptr; // Engine-neutral GSD invoker (ObjCGsdInvoker) + bool isNSErrorOutMethod = false; // Cached: avoids per-call selector scan. + bool isInitMethod = false; // Cached: avoids per-call "init" rfind. + bool gsdEngineCallable = false; + uint8_t gsdEngineArgumentCount = 0; + bool fastEngineCallable = false; + uint8_t fastEngineArgumentCount = 0; + uint8_t fastEngineFirstArgKind = 0; + uint8_t fastEngineSecondArgKind = 0; +}; + +bool preparedObjCInvocationIsInit( + const NativeApiPreparedObjCInvocation& prepared) { + return prepared.isInitMethod; +} + +void cachePreparedAppearanceProxySetterValue( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count) { + if (prepared.propertySetterName.empty() || args == nullptr || count == 0) { + return; + } + cacheAppearanceProxyPropertyValue(runtime, bridge, receiver, + prepared.propertySetterName, args[0]); +} + +bool isFastEngineObjectType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return true; + default: + return false; + } +} + +bool isFastEngineSignedIntegerType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeChar: + case metagen::mdTypeSShort: + case metagen::mdTypeSInt: + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return true; + default: + return false; + } +} + +bool isFastEngineUnsignedIntegerType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + case metagen::mdTypeUInt: + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return true; + default: + return false; + } +} + +enum class NativeApiFastEngineArgKind : uint8_t { + Bool, + SignedInteger, + UnsignedInteger, + Float, + Double, + Object, + Class, + Selector, +}; + +std::optional fastEngineArgKind( + const NativeApiType& type) { + if (isFastEngineObjectType(type)) { + return NativeApiFastEngineArgKind::Object; + } + if (isFastEngineSignedIntegerType(type)) { + return NativeApiFastEngineArgKind::SignedInteger; + } + if (isFastEngineUnsignedIntegerType(type)) { + return NativeApiFastEngineArgKind::UnsignedInteger; + } + switch (type.kind) { + case metagen::mdTypeBool: + return NativeApiFastEngineArgKind::Bool; + case metagen::mdTypeFloat: + return NativeApiFastEngineArgKind::Float; + case metagen::mdTypeDouble: + return NativeApiFastEngineArgKind::Double; + case metagen::mdTypeClass: + return NativeApiFastEngineArgKind::Class; + case metagen::mdTypeSelector: + return NativeApiFastEngineArgKind::Selector; + default: + return std::nullopt; + } +} + +bool readFastEngineBoolArgument(Runtime& runtime, const Value& value, + BOOL* result) { + if (result == nullptr || !value.isBool()) { + return false; + } + *result = value.getBool() ? YES : NO; + return true; +} + +bool readFastEngineSignedIntegerArgument(Runtime& runtime, const Value& value, + NSInteger* result) { + if (result == nullptr) { + return false; + } + if (value.isNumber()) { + *result = static_cast(value.getNumber()); + return true; + } + return false; +} + +bool readFastEngineUnsignedIntegerArgument(Runtime& runtime, const Value& value, + NSUInteger* result) { + if (result == nullptr) { + return false; + } + if (value.isNumber()) { + *result = static_cast(value.getNumber()); + return true; + } + return false; +} + +bool readFastEngineFloatArgument(Runtime&, const Value& value, float* result) { + if (result == nullptr || !value.isNumber()) { + return false; + } + *result = static_cast(value.getNumber()); + return true; +} + +bool readFastEngineDoubleArgument(Runtime&, const Value& value, double* result) { + if (result == nullptr || !value.isNumber()) { + return false; + } + *result = value.getNumber(); + return true; +} + +bool readFastEngineObjectArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, + NativeApiArgumentFrame& frame, id* result) { + if (result == nullptr) { + return false; + } + *result = objectFromEngineValue( + runtime, bridge, value, frame, + type.kind == metagen::mdTypeNSMutableStringObject); + if (valueIsNativeObjectHostObject(runtime, value)) { + frame.retainObject(*result); + } + return true; +} + +class NativeApiScopedObjCObjectRetain { + public: + explicit NativeApiScopedObjCObjectRetain(id object) : object_(object) { + if (object_ != nil) { + [object_ retain]; + } + } + + ~NativeApiScopedObjCObjectRetain() { + if (object_ != nil) { + [object_ release]; + } + } + + private: + id object_ = nil; +}; + +bool readFastEngineClassArgument(Runtime& runtime, const Value& value, + Class* result) { + if (result == nullptr) { + return false; + } + *result = classFromEngineValue(runtime, value); + return *result != Nil; +} + +bool readFastEngineSelectorArgument(Runtime& runtime, const Value& value, + SEL* result) { + if (result == nullptr) { + return false; + } + if (value.isNull() || value.isUndefined()) { + *result = nullptr; + return true; + } + if (!value.isString()) { + return false; + } + std::string selectorName = value.asString(runtime).utf8(runtime); + *result = sel_registerName(selectorName.c_str()); + return true; +} + +template +Value callFastEngineCFunctionWithReturn( + Runtime& runtime, const std::shared_ptr& bridge, + void* function, NativeApiType returnType, Args... nativeArgs) { + auto finalizeObjectReturn = [&](id object) -> Value { + return convertNativeReturnValue(runtime, bridge, returnType, &object); + }; + + switch (returnType.kind) { + case metagen::mdTypeVoid: { + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = void (*)(Args...); + reinterpret_cast(function)(nativeArgs...); + }); + return Value::undefined(); + } + case metagen::mdTypeBool: { + BOOL nativeResult = NO; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = BOOL (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + uint8_t storage = nativeResult ? 1 : 0; + return convertNativeReturnValue(runtime, bridge, returnType, &storage); + } + case metagen::mdTypeFloat: { + float nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = float (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + case metagen::mdTypeDouble: { + double nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = double (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + default: + break; + } + + if (isFastEngineObjectType(returnType) || + returnType.kind == metagen::mdTypeClass) { + id nativeResult = nil; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = id (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return finalizeObjectReturn(nativeResult); + } + + if (returnType.kind == metagen::mdTypeSelector) { + SEL nativeResult = nullptr; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = SEL (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + if (isFastEngineSignedIntegerType(returnType)) { + int64_t nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = int64_t (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + if (isFastEngineUnsignedIntegerType(returnType)) { + uint64_t nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = uint64_t (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + throw JSError(runtime, "C function return type is not engine fast-callable."); +} + +bool isFastEngineCallableReturnType(const NativeApiType& returnType) { + return isFastEngineObjectType(returnType) || + returnType.kind == metagen::mdTypeVoid || + returnType.kind == metagen::mdTypeBool || + returnType.kind == metagen::mdTypeFloat || + returnType.kind == metagen::mdTypeDouble || + returnType.kind == metagen::mdTypeClass || + returnType.kind == metagen::mdTypeSelector || + isFastEngineSignedIntegerType(returnType) || + isFastEngineUnsignedIntegerType(returnType); +} + +bool tryCallFastEngineCFunction( + Runtime& runtime, const std::shared_ptr& bridge, + void* function, const NativeApiSignature& signature, const Value* args, + size_t count, Value* result) { + if (result == nullptr || function == nullptr || signature.variadic || + count != signature.argumentTypes.size() || count > 2 || + unsupportedEngineType(signature.returnType) || + !isFastEngineCallableReturnType(signature.returnType)) { + return false; + } + + std::optional firstArgKind; + std::optional secondArgKind; + if (count > 0) { + firstArgKind = fastEngineArgKind(signature.argumentTypes[0]); + if (!firstArgKind) { + return false; + } + } + if (count > 1) { + secondArgKind = fastEngineArgKind(signature.argumentTypes[1]); + if (!secondArgKind) { + return false; + } + } + + if (count == 0) { + *result = callFastEngineCFunctionWithReturn( + runtime, bridge, function, signature.returnType); + return true; + } + + NativeApiArgumentFrame frame(count); + auto callOne = [&](auto nativeArg0) -> Value { + return callFastEngineCFunctionWithReturn(runtime, bridge, function, + signature.returnType, nativeArg0); + }; + auto callTwo = [&](auto nativeArg0, auto nativeArg1) -> Value { + return callFastEngineCFunctionWithReturn(runtime, bridge, function, + signature.returnType, nativeArg0, + nativeArg1); + }; + + auto callWithSecondArg = [&](auto nativeArg0) -> bool { + switch (*secondArgKind) { + case NativeApiFastEngineArgKind::Bool: { + BOOL arg1 = NO; + if (!readFastEngineBoolArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::SignedInteger: { + NSInteger arg1 = 0; + if (!readFastEngineSignedIntegerArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::UnsignedInteger: { + NSUInteger arg1 = 0; + if (!readFastEngineUnsignedIntegerArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Float: { + float arg1 = 0; + if (!readFastEngineFloatArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Double: { + double arg1 = 0; + if (!readFastEngineDoubleArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Object: { + id arg1 = nil; + if (!readFastEngineObjectArgument( + runtime, bridge, signature.argumentTypes[1], args[1], frame, + &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Class: { + Class arg1 = Nil; + if (!readFastEngineClassArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Selector: { + SEL arg1 = nullptr; + if (!readFastEngineSelectorArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + } + return false; + }; + + switch (*firstArgKind) { + case NativeApiFastEngineArgKind::Bool: { + BOOL arg0 = NO; + if (!readFastEngineBoolArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::SignedInteger: { + NSInteger arg0 = 0; + if (!readFastEngineSignedIntegerArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::UnsignedInteger: { + NSUInteger arg0 = 0; + if (!readFastEngineUnsignedIntegerArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Float: { + float arg0 = 0; + if (!readFastEngineFloatArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Double: { + double arg0 = 0; + if (!readFastEngineDoubleArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Object: { + id arg0 = nil; + if (!readFastEngineObjectArgument(runtime, bridge, + signature.argumentTypes[0], args[0], + frame, &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Class: { + Class arg0 = Nil; + if (!readFastEngineClassArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Selector: { + SEL arg0 = nullptr; + if (!readFastEngineSelectorArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + } + + return false; +} + +template +Value callFastEngineObjCWithReturn( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, SEL selector, NativeApiType returnType, + const std::string& selectorName, Args... nativeArgs) { + auto finalizeObjectReturn = [&](id object) -> Value { + NativeApiType effectiveReturnType = returnType; + if ((selectorName == "valueForKey:" || selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(effectiveReturnType)) { + effectiveReturnType.kind = metagen::mdTypeAnyObject; + } + if (startsWith(selectorName, "init") && + isObjectiveCObjectType(effectiveReturnType)) { + effectiveReturnType.kind = metagen::mdTypeInstanceObject; + } + return convertNativeReturnValue(runtime, bridge, effectiveReturnType, + &object); + }; + + switch (returnType.kind) { + case metagen::mdTypeVoid: { + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = void (*)(id, SEL, Args...); + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return Value::undefined(); + } + case metagen::mdTypeBool: { + BOOL nativeResult = NO; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = BOOL (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + uint8_t storage = nativeResult ? 1 : 0; + return convertNativeReturnValue(runtime, bridge, returnType, &storage); + } + case metagen::mdTypeFloat: { + float nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = float (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + case metagen::mdTypeDouble: { + double nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = double (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + default: + break; + } + + if (isFastEngineObjectType(returnType) || returnType.kind == metagen::mdTypeClass) { + id nativeResult = nil; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = id (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return finalizeObjectReturn(nativeResult); + } + + if (isFastEngineSignedIntegerType(returnType)) { + int64_t nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = int64_t (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + if (isFastEngineUnsignedIntegerType(returnType)) { + uint64_t nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = uint64_t (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + throw JSError(runtime, "Objective-C return type is not engine fast-callable."); +} + +template +Value callFastEngineObjC1( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, SEL selector, const NativeApiType& returnType, + const std::string& selectorName, A0 arg0) { + return callFastEngineObjCWithReturn(runtime, bridge, receiver, selector, + returnType, selectorName, arg0); +} + +template +Value callFastEngineObjC2( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, SEL selector, const NativeApiType& returnType, + const std::string& selectorName, A0 arg0, A1 arg1) { + return callFastEngineObjCWithReturn(runtime, bridge, receiver, selector, + returnType, selectorName, arg0, arg1); +} + +bool tryCallFastEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result) { + if (result == nullptr || receiver == nil || dispatchSuperClass != Nil) { + return false; + } + + const NativeApiSignature& signature = prepared.signature; + if (!prepared.fastEngineCallable || + count != prepared.fastEngineArgumentCount) { + return false; + } + + NativeApiFastEngineArgKind firstArgKind = + static_cast(prepared.fastEngineFirstArgKind); + NativeApiFastEngineArgKind secondArgKind = + static_cast(prepared.fastEngineSecondArgKind); + + SEL selector = prepared.selector; + if (count == 0) { + *result = callFastEngineObjCWithReturn( + runtime, bridge, receiver, selector, signature.returnType, + prepared.selectorName); + return true; + } + + NativeApiArgumentFrame frame(count); + auto callOne = [&](auto nativeArg0) -> Value { + return callFastEngineObjC1(runtime, bridge, receiver, selector, + signature.returnType, prepared.selectorName, + nativeArg0); + }; + auto callTwo = [&](auto nativeArg0, auto nativeArg1) -> Value { + return callFastEngineObjC2(runtime, bridge, receiver, selector, + signature.returnType, prepared.selectorName, + nativeArg0, nativeArg1); + }; + auto callWithSecondArg = [&](auto nativeArg0) -> bool { + switch (secondArgKind) { + case NativeApiFastEngineArgKind::Bool: { + BOOL arg1 = NO; + if (!readFastEngineBoolArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::SignedInteger: { + NSInteger arg1 = 0; + if (!readFastEngineSignedIntegerArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::UnsignedInteger: { + NSUInteger arg1 = 0; + if (!readFastEngineUnsignedIntegerArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Float: { + float arg1 = 0; + if (!readFastEngineFloatArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Double: { + double arg1 = 0; + if (!readFastEngineDoubleArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Object: { + id arg1 = nil; + if (!readFastEngineObjectArgument( + runtime, bridge, signature.argumentTypes[1], args[1], frame, + &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Class: { + Class arg1 = Nil; + if (!readFastEngineClassArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Selector: { + SEL arg1 = nullptr; + if (!readFastEngineSelectorArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + } + return false; + }; + + switch (firstArgKind) { + case NativeApiFastEngineArgKind::Bool: { + BOOL arg0 = NO; + if (!readFastEngineBoolArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::SignedInteger: { + NSInteger arg0 = 0; + if (!readFastEngineSignedIntegerArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::UnsignedInteger: { + NSUInteger arg0 = 0; + if (!readFastEngineUnsignedIntegerArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Float: { + float arg0 = 0; + if (!readFastEngineFloatArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Double: { + double arg0 = 0; + if (!readFastEngineDoubleArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Object: { + id arg0 = nil; + if (!readFastEngineObjectArgument(runtime, bridge, signature.argumentTypes[0], + args[0], frame, &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Class: { + Class arg0 = Nil; + if (!readFastEngineClassArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Selector: { + SEL arg0 = nullptr; + if (!readFastEngineSelectorArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + } + + return false; +} + +bool isFastEngineObjCReturnType(const NativeApiType& returnType) { + return !unsupportedEngineType(returnType) && + (isFastEngineObjectType(returnType) || + returnType.kind == metagen::mdTypeVoid || + returnType.kind == metagen::mdTypeBool || + returnType.kind == metagen::mdTypeFloat || + returnType.kind == metagen::mdTypeDouble || + returnType.kind == metagen::mdTypeClass || + isFastEngineSignedIntegerType(returnType) || + isFastEngineUnsignedIntegerType(returnType)); +} + +void configureFastEngineObjCInvocation( + NativeApiPreparedObjCInvocation& prepared) { + prepared.fastEngineCallable = false; + prepared.fastEngineArgumentCount = 0; + prepared.fastEngineFirstArgKind = 0; + prepared.fastEngineSecondArgKind = 0; + + const NativeApiSignature& signature = prepared.signature; + if (signature.variadic || prepared.isNSErrorOutMethod || + signature.argumentTypes.size() > 2 || + !isFastEngineObjCReturnType(signature.returnType)) { + return; + } + + if (!signature.argumentTypes.empty()) { + std::optional firstArgKind = + fastEngineArgKind(signature.argumentTypes[0]); + if (!firstArgKind) { + return; + } + prepared.fastEngineFirstArgKind = static_cast(*firstArgKind); + } + if (signature.argumentTypes.size() > 1) { + std::optional secondArgKind = + fastEngineArgKind(signature.argumentTypes[1]); + if (!secondArgKind) { + return; + } + prepared.fastEngineSecondArgKind = static_cast(*secondArgKind); + } + + prepared.fastEngineArgumentCount = + static_cast(signature.argumentTypes.size()); + prepared.fastEngineCallable = true; +} + +void configureGeneratedEngineObjCInvocation( + NativeApiPreparedObjCInvocation& prepared) { + prepared.gsdEngineCallable = false; + prepared.gsdEngineArgumentCount = 0; + + const NativeApiSignature& signature = prepared.signature; + if (prepared.engineInvoker == nullptr || signature.variadic || + prepared.isNSErrorOutMethod || signature.argumentTypes.size() > 255) { + return; + } + + prepared.gsdEngineArgumentCount = + static_cast(signature.argumentTypes.size()); + prepared.gsdEngineCallable = true; +} + +std::shared_ptr +prepareNativeApiObjCInvocation( + Runtime& runtime, const std::shared_ptr& bridge, + Class lookupClass, bool receiverIsClass, const std::string& selectorName, + const NativeApiMember* member) { + if (lookupClass == Nil) { + throw JSError(runtime, + "Objective-C class is not available for selector: " + + selectorName); + } + + SEL selector = sel_registerName(selectorName.c_str()); + Method method = receiverIsClass ? class_getClassMethod(lookupClass, selector) + : class_getInstanceMethod(lookupClass, selector); + if (method == nullptr) { + throw JSError(runtime, + "Objective-C selector is not available: " + selectorName); + } + + std::optional signature; + std::optional runtimeSignature; + if (member != nullptr && + member->signatureOffset != MD_SECTION_OFFSET_NULL && + member->signatureOffset != 0) { + signature = parseMetadataEngineSignature( + bridge->metadata(), member->signatureOffset, 2, bridge.get(), + (member->flags & metagen::mdMemberReturnOwned) != 0); + } + if (method != nullptr) { + runtimeSignature = parseObjCMethodEngineSignature(method, bridge.get()); + } + if (signatureSupportedForEngineInvocation(signature) && + signatureSupportedForEngineInvocation(runtimeSignature)) { + reconcileObjCMethodRuntimeSignature(&*signature, *runtimeSignature); + } + if (!signatureSupportedForEngineInvocation(signature) && runtimeSignature) { + signature = std::move(runtimeSignature); + } + + if (!signatureSupportedForEngineInvocation(signature)) { + throw JSError( + runtime, "Objective-C signature is not supported by backend: " + + selectorName); + } + signature->selectorName = selectorName; + + auto prepared = std::make_shared(); + prepared->selector = selector; + prepared->receiverClass = receiverIsClass ? lookupClass : Nil; + prepared->selectorName = selectorName; + if (member != nullptr && member->property && !member->name.empty() && + !member->setterSelectorName.empty() && + selectorName == member->setterSelectorName && + selectorArgumentCount(selectorName) == 1) { + prepared->propertySetterName = member->name; + } + prepared->signature = std::move(*signature); + prepared->preparedInvoker = lookupObjCPreparedInvoker( + dispatchIdForEngineSignature(prepared->signature, + SignatureCallKind::ObjCMethod)); + prepared->engineInvoker = lookupGeneratedEngineObjCGsdInvoker( + dispatchIdForEngineSignature(prepared->signature, + SignatureCallKind::ObjCMethod)); + prepared->isNSErrorOutMethod = + isNSErrorOutEngineMethodSignature(prepared->signature); + prepared->isInitMethod = prepared->selectorName.rfind("init", 0) == 0; + configureGeneratedEngineObjCInvocation(*prepared); + configureFastEngineObjCInvocation(*prepared); + return prepared; +} + +bool isPreparedStaticAppearanceSelector( + const NativeApiPreparedObjCInvocation& prepared) { + return prepared.receiverClass != Nil && + prepared.selectorName.rfind("appearance", 0) == 0; +} + +Value tagPreparedStaticAppearanceSelectorResult( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + Value result) { + return tagStaticAppearanceSelectorResult( + runtime, bridge, receiver, isPreparedStaticAppearanceSelector(prepared), + prepared.selectorName, std::move(result)); +} + +void tagPreparedStaticAppearanceNativeReturn( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const NativeApiType& returnType, void* returnData) { + if (!isPreparedStaticAppearanceSelector(prepared) || + !isObjectiveCObjectType(returnType) || returnData == nullptr) { + return; + } + tagStaticAppearanceNativeResult( + runtime, bridge, static_cast(receiver), + *static_cast(returnData)); +} + +Value callPreparedObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, bool receiverIsClass, + const NativeApiPreparedObjCInvocation& prepared, const Value* args, + size_t count, Class dispatchSuperClass) { + if (receiver == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + NativeApiRoundTripCacheFrameGuard roundTripFrame(bridge); + + const NativeApiSignature& signature = prepared.signature; + Value fastResult; + if (tryCallGeneratedEngineObjCSelector(runtime, bridge, receiver, prepared, + args, count, dispatchSuperClass, + &fastResult)) { + cachePreparedAppearanceProxySetterValue(runtime, bridge, receiver, + prepared, args, count); + return tagPreparedStaticAppearanceSelectorResult( + runtime, bridge, receiver, prepared, std::move(fastResult)); + } + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, prepared, args, + count, dispatchSuperClass, &fastResult)) { + cachePreparedAppearanceProxySetterValue(runtime, bridge, receiver, + prepared, args, count); + return tagPreparedStaticAppearanceSelectorResult( + runtime, bridge, receiver, prepared, std::move(fastResult)); + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + frame.retainObject(receiver); + const bool isNSErrorOutMethod = prepared.isNSErrorOutMethod; + if (isNSErrorOutMethod) { + size_t expected = signature.argumentTypes.size(); + if (count > expected || count + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(count) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && count + 1 == signature.argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + for (size_t i = 0; i < count; i++) { + prepareEngineArgument(runtime, bridge, signature.argumentTypes[i], args[i], + i, frame); + } + + size_t outArgIndex = signature.argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } else { + prepareEngineArguments(runtime, bridge, signature, args, count, frame); + } + + NativeApiPointerFrame values(signature.argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, const_cast(&prepared.selector)); + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared.preparedInvoker != nullptr && dispatchSuperClass == Nil) { + prepared.preparedInvoker(reinterpret_cast(objc_msgSend), + values.data(), returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature.returnType.ffiType->size > 16 && + signature.returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(const_cast(&signature.cif), target, + returnStorage.data(), values.data()); +#else + ffi_call(const_cast(&signature.cif), + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature.returnType; + if ((prepared.selectorName == "valueForKey:" || + prepared.selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(returnType)) { + returnType.kind = metagen::mdTypeAnyObject; + } + if (prepared.isInitMethod && + isObjectiveCObjectType(returnType)) { + returnType.kind = metagen::mdTypeInstanceObject; + } + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + cachePreparedAppearanceProxySetterValue(runtime, bridge, receiver, prepared, + args, count); + Value result = convertNativeReturnValue(runtime, bridge, returnType, + returnStorage.data()); + return tagPreparedStaticAppearanceSelectorResult( + runtime, bridge, receiver, prepared, std::move(result)); +} + +Value callObjCSelector(Runtime& runtime, + const std::shared_ptr& bridge, + id receiver, bool receiverIsClass, + const std::string& selectorName, + const NativeApiMember* member, + const Value* args, size_t count, + Class dispatchSuperClass) { + if (receiver == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + NativeApiRoundTripCacheFrameGuard roundTripFrame(bridge); + + SEL selector = sel_registerName(selectorName.c_str()); + Class receiverClass = + receiverIsClass ? static_cast(receiver) : object_getClass(receiver); + Class lookupClass = dispatchSuperClass != Nil ? dispatchSuperClass : receiverClass; + Method method = receiverIsClass ? class_getClassMethod(lookupClass, selector) + : class_getInstanceMethod(lookupClass, selector); + // A UIAppearance proxy is opaque to class_getInstanceMethod (it forwards + // selectors dynamically, so no Method exists for them) — allow through the + // exact property getter/setter selectors respondsToSelector: already + // vetted elsewhere, since -respondsToSelector: on the proxy itself can + // still return NO for a selector it will happily forward. + bool allowForwardedAppearancePropertySelector = false; + if (method == nullptr && !receiverIsClass && member != nullptr && + member->property && bridge != nullptr && + taggedAppearanceProxyClass(runtime, bridge, receiver) != Nil) { + allowForwardedAppearancePropertySelector = + (count == 0 && selectorName == member->selectorName) || + (count == 1 && !member->setterSelectorName.empty() && + selectorName == member->setterSelectorName); + } + if (method == nullptr && + !allowForwardedAppearancePropertySelector && + (dispatchSuperClass != Nil || ![receiver respondsToSelector:selector])) { + throw JSError(runtime, + "Objective-C selector is not available: " + + selectorName); + } + + std::optional signature; + std::optional runtimeSignature; + if (member != nullptr && + member->signatureOffset != MD_SECTION_OFFSET_NULL && + member->signatureOffset != 0) { + signature = parseMetadataEngineSignature( + bridge->metadata(), member->signatureOffset, 2, bridge.get(), + (member->flags & metagen::mdMemberReturnOwned) != 0); + } + if (method != nullptr) { + runtimeSignature = parseObjCMethodEngineSignature(method, bridge.get()); + } + if (signatureSupportedForEngineInvocation(signature) && + signatureSupportedForEngineInvocation(runtimeSignature)) { + reconcileObjCMethodRuntimeSignature(&*signature, *runtimeSignature); + } + if (!signatureSupportedForEngineInvocation(signature) && runtimeSignature) { + signature = std::move(runtimeSignature); + } + + if (!signatureSupportedForEngineInvocation(signature)) { + throw JSError( + runtime, "Objective-C signature is not supported by backend: " + + selectorName); + } + signature->selectorName = selectorName; + + NativeApiPreparedObjCInvocation engineInvocation; + engineInvocation.selector = selector; + engineInvocation.selectorName = selectorName; + engineInvocation.signature = *signature; + engineInvocation.engineInvoker = lookupGeneratedEngineObjCGsdInvoker( + dispatchIdForEngineSignature(*signature, SignatureCallKind::ObjCMethod)); + engineInvocation.isNSErrorOutMethod = + isNSErrorOutEngineMethodSignature(*signature); + engineInvocation.isInitMethod = selectorName.rfind("init", 0) == 0; + configureGeneratedEngineObjCInvocation(engineInvocation); + configureFastEngineObjCInvocation(engineInvocation); + Value fastResult; + if (tryCallGeneratedEngineObjCSelector(runtime, bridge, receiver, + engineInvocation, args, count, + dispatchSuperClass, &fastResult)) { + return tagStaticAppearanceSelectorResult( + runtime, bridge, receiver, receiverIsClass, selectorName, + std::move(fastResult)); + } + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, + engineInvocation, args, count, + dispatchSuperClass, &fastResult)) { + return tagStaticAppearanceSelectorResult( + runtime, bridge, receiver, receiverIsClass, selectorName, + std::move(fastResult)); + } + + NativeApiArgumentFrame frame(signature->argumentTypes.size()); + const bool isNSErrorOutMethod = engineInvocation.isNSErrorOutMethod; + if (isNSErrorOutMethod) { + size_t expected = signature->argumentTypes.size(); + if (count > expected || count + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(count) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && count + 1 == signature->argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + for (size_t i = 0; i < count; i++) { + prepareEngineArgument(runtime, bridge, signature->argumentTypes[i], args[i], i, + frame); + } + + size_t outArgIndex = signature->argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } else { + prepareEngineArguments(runtime, bridge, *signature, args, count, frame); + } + + NativeApiPointerFrame values(signature->argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, &selector); + for (size_t i = 0; i < signature->argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature->returnType)); + auto preparedInvoker = + dispatchSuperClass == Nil + ? lookupObjCPreparedInvoker(dispatchIdForEngineSignature( + *signature, SignatureCallKind::ObjCMethod)) + : nullptr; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (preparedInvoker != nullptr) { + preparedInvoker(reinterpret_cast(objc_msgSend), values.data(), + returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature->returnType.ffiType->size > 16 && + signature->returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(&signature->cif, target, returnStorage.data(), values.data()); +#else + ffi_call(&signature->cif, + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature->returnType; + if ((selectorName == "valueForKey:" || selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(returnType)) { + returnType.kind = metagen::mdTypeAnyObject; + } + if (engineInvocation.isInitMethod && isObjectiveCObjectType(returnType)) { + returnType.kind = metagen::mdTypeInstanceObject; + } + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + Value result = convertNativeReturnValue(runtime, bridge, returnType, + returnStorage.data()); + return tagStaticAppearanceSelectorResult( + runtime, bridge, receiver, receiverIsClass, selectorName, + std::move(result)); +} diff --git a/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm b/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm new file mode 100644 index 000000000..0a8a99841 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm @@ -0,0 +1,2699 @@ +thread_local int gSynchronousNativeInvocationDepth = 0; +thread_local int gNativeCallerThreadEngineCallbackDepth = 0; +thread_local std::vector gNativeCallbackExceptionCaptureStack; +std::atomic gActiveSynchronousNativeInvocationDepth{0}; + +class ScopedNativeApiSynchronousInvocation final { + public: + ScopedNativeApiSynchronousInvocation() { + gSynchronousNativeInvocationDepth += 1; + gActiveSynchronousNativeInvocationDepth.fetch_add(1, + std::memory_order_acq_rel); + } + + ~ScopedNativeApiSynchronousInvocation() { + gSynchronousNativeInvocationDepth -= 1; + gActiveSynchronousNativeInvocationDepth.fetch_sub(1, + std::memory_order_acq_rel); + } +}; + +class ScopedNativeCallerThreadEngineCallback final { + public: + ScopedNativeCallerThreadEngineCallback() { + gNativeCallerThreadEngineCallbackDepth += 1; + } + + ~ScopedNativeCallerThreadEngineCallback() { + gNativeCallerThreadEngineCallbackDepth -= 1; + } + + ScopedNativeCallerThreadEngineCallback( + const ScopedNativeCallerThreadEngineCallback&) = delete; + ScopedNativeCallerThreadEngineCallback& operator=( + const ScopedNativeCallerThreadEngineCallback&) = delete; +}; + +class ScopedNativeCallbackExceptionCapture final { + public: + explicit ScopedNativeCallbackExceptionCapture(std::string* message) + : message_(message) { + gNativeCallbackExceptionCaptureStack.push_back(message_); + } + + ~ScopedNativeCallbackExceptionCapture() { + if (!gNativeCallbackExceptionCaptureStack.empty() && + gNativeCallbackExceptionCaptureStack.back() == message_) { + gNativeCallbackExceptionCaptureStack.pop_back(); + } + } + + ScopedNativeCallbackExceptionCapture( + const ScopedNativeCallbackExceptionCapture&) = delete; + ScopedNativeCallbackExceptionCapture& operator=( + const ScopedNativeCallbackExceptionCapture&) = delete; + + private: + std::string* message_ = nullptr; +}; + +bool recordNativeCallbackException(const std::string& message) { + if (gNativeCallbackExceptionCaptureStack.empty()) { + return false; + } + + std::string* captured = gNativeCallbackExceptionCaptureStack.back(); + if (captured == nullptr) { + return false; + } + + if (captured->empty()) { + *captured = message; + } + return true; +} + +template +void performNativeInvocation(Runtime& runtime, + const std::function)>& + invoker, + Invocation&& invocation) { + NSString* exceptionDescription = nil; + std::string callbackException; + auto run = [&]() { + ScopedNativeApiSynchronousInvocation synchronousInvocation; + ScopedNativeCallbackExceptionCapture callbackExceptionCapture( + &callbackException); + @try { + invocation(); + } @catch (NSException* exception) { + exceptionDescription = [exception.description copy]; + } + }; + + bool skipInvoker = gNativeCallerThreadEngineCallbackDepth > 0; + if (invoker && !skipInvoker) { + invoker(run); + } else { + run(); + } + + if (exceptionDescription != nil) { + std::string message = exceptionDescription.UTF8String ?: ""; + [exceptionDescription release]; + throw JSError(runtime, message); + } + if (!callbackException.empty()) { + throw JSError(runtime, callbackException); + } +} + +template +void performDirectObjCInvocation(Runtime& runtime, Invocation&& invocation) { + NSString* exceptionDescription = nil; + auto run = [&]() { + @try { + invocation(); + } @catch (NSException* exception) { + exceptionDescription = [exception.description copy]; + } + }; + + run(); + + if (exceptionDescription != nil) { + std::string message = exceptionDescription.UTF8String ?: ""; + [exceptionDescription release]; + throw JSError(runtime, message); + } +} + +enum class NativeApiSymbolKind { + Class, + Function, + Constant, + Protocol, + Enum, + Struct, + Union, +}; + +struct NativeApiSymbol { + NativeApiSymbolKind kind; + MDSectionOffset offset = 0; + MDSectionOffset superclassOffset = MD_SECTION_OFFSET_NULL; + std::string name; + std::string runtimeName; +}; + +struct NativeApiMember { + std::string name; + std::string selectorName; + std::string setterSelectorName; + MDSectionOffset signatureOffset = MD_SECTION_OFFSET_NULL; + MDSectionOffset setterSignatureOffset = MD_SECTION_OFFSET_NULL; + MDMemberFlag flags = metagen::mdMemberFlagNull; + bool property = false; + bool readonly = false; +}; + +struct NativeApiSelectorGroupEntry { + std::string selectorName; + NativeApiMember member; + bool hasMember = false; + bool propertyGetterResolved = false; + bool propertyGetterCanPrepare = true; + bool propertyGetterHasAdjustedMember = false; + std::string propertyGetterSelectorName; + NativeApiMember propertyGetterMember; +}; + +struct NativeApiSelectorGroupCallTarget { + const std::string* selectorName = nullptr; + const NativeApiMember* member = nullptr; + bool canPrepare = true; +}; + +struct NativeApiAggregateInfo; + +struct NativeApiFfiType { + ffi_type type = {}; + std::vector elements; + + NativeApiFfiType() { + type.type = FFI_TYPE_STRUCT; + type.size = 0; + type.alignment = 0; + type.elements = nullptr; + } + + void finalize() { + elements.push_back(nullptr); + type.elements = elements.data(); + } +}; + +struct NativeApiType { + MDTypeKind kind = metagen::mdTypeVoid; + ffi_type* ffiType = &ffi_type_void; + bool supported = true; + bool returnOwned = false; + MDSectionOffset signatureOffset = MD_SECTION_OFFSET_NULL; + MDSectionOffset aggregateOffset = MD_SECTION_OFFSET_NULL; + bool aggregateIsUnion = false; + uint16_t arraySize = 0; + std::shared_ptr elementType; + std::shared_ptr aggregateInfo; + std::shared_ptr ownedFfiType; +}; + +struct NativeApiAggregateField { + std::string name; + uint16_t offset = 0; + NativeApiType type; +}; + +struct NativeApiAggregateInfo { + std::string name; + uint16_t size = 0; + bool isUnion = false; + MDSectionOffset offset = MD_SECTION_OFFSET_NULL; + std::vector fields; + std::shared_ptr ffi; +}; + +std::string jsifySelector(const char* selector) { + std::string jsifiedSelector; + bool nextUpper = false; + for (const char* c = selector; c != nullptr && *c != '\0'; c++) { + if (*c == ':') { + nextUpper = true; + } else if (nextUpper) { + jsifiedSelector += static_cast(toupper(*c)); + nextUpper = false; + } else { + jsifiedSelector += *c; + } + } + return jsifiedSelector; +} + +std::string booleanGetterSelectorForProperty(const std::string& property) { + if (property.empty()) { + return property; + } + + std::string selector = "is"; + selector += static_cast(toupper(property[0])); + selector += property.substr(1); + return selector; +} + +std::optional respondingPropertyGetterSelector( + id receiver, const std::string& property, + const std::string& preferredSelector) { + if (receiver == nil) { + return std::nullopt; + } + + auto respondsToSelectorName = [receiver](const std::string& selectorName) { + return !selectorName.empty() && + [receiver respondsToSelector:sel_getUid(selectorName.c_str())]; + }; + + if (respondsToSelectorName(preferredSelector)) { + return preferredSelector; + } + if (preferredSelector != property && respondsToSelectorName(property)) { + return property; + } + + std::string booleanSelector = booleanGetterSelectorForProperty(property); + if (booleanSelector != preferredSelector && booleanSelector != property && + respondsToSelectorName(booleanSelector)) { + return booleanSelector; + } + + return std::nullopt; +} + +std::string setterSelectorForProperty(const std::string& property) { + if (property.empty()) { + return property; + } + + std::string selector = "set"; + selector += static_cast(toupper(property[0])); + selector += property.substr(1); + selector += ":"; + return selector; +} + +size_t selectorArgumentCount(const std::string& selector) { + return static_cast( + std::count(selector.begin(), selector.end(), ':')); +} + +const NativeApiMember* selectMethodMember( + const std::vector& members, const std::string& property, + bool staticMethod, size_t argumentCount) { + for (const auto& member : members) { + if (member.property || member.name != property) { + continue; + } + + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic != staticMethod) { + continue; + } + + if (selectorArgumentCount(member.selectorName) == argumentCount) { + return &member; + } + } + return nullptr; +} + +bool hasMethodMember(const std::vector& members, + const std::string& property, bool staticMethod) { + for (const auto& member : members) { + if (member.property || member.name != property) { + continue; + } + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic == staticMethod) { + return true; + } + } + return false; +} + +std::shared_ptr> +selectorGroupEntriesForMethod(const std::vector& members, + const std::string& property, bool staticMethod) { + auto selectors = std::make_shared>(); + for (const auto& member : members) { + if (member.property || member.name != property || member.selectorName.empty()) { + continue; + } + + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic != staticMethod) { + continue; + } + + size_t argumentCount = selectorArgumentCount(member.selectorName); + if (selectors->size() <= argumentCount) { + selectors->resize(argumentCount + 1); + } + if ((*selectors)[argumentCount].selectorName.empty()) { + (*selectors)[argumentCount].selectorName = member.selectorName; + (*selectors)[argumentCount].member = member; + (*selectors)[argumentCount].hasMember = true; + } + + if (argumentCount > 0 && member.selectorName.size() >= 6 && + member.selectorName.compare(member.selectorName.size() - 6, 6, + "error:") == 0) { + size_t omittedErrorCount = argumentCount - 1; + if (selectors->size() <= omittedErrorCount) { + selectors->resize(omittedErrorCount + 1); + } + if ((*selectors)[omittedErrorCount].selectorName.empty()) { + (*selectors)[omittedErrorCount].selectorName = member.selectorName; + (*selectors)[omittedErrorCount].member = member; + (*selectors)[omittedErrorCount].hasMember = true; + } + } + } + return selectors->empty() ? nullptr : selectors; +} + +bool selectorGroupCanPrepareSelector(id receiver, Class lookupClass, + bool receiverIsClass, + const std::string& selectorName) { + if (selectorName.empty()) { + return false; + } + SEL selector = sel_registerName(selectorName.c_str()); + if (receiverIsClass) { + return lookupClass != Nil && + class_getClassMethod(lookupClass, selector) != nullptr; + } + if (lookupClass != Nil && + class_getInstanceMethod(lookupClass, selector) != nullptr) { + return true; + } + return receiver != nil && + class_getInstanceMethod(object_getClass(receiver), selector) != nullptr; +} + +std::string selectorGroupPropertyGetterSelector( + id receiver, Class lookupClass, bool receiverIsClass, + const NativeApiMember& member) { + if (selectorGroupCanPrepareSelector(receiver, lookupClass, receiverIsClass, + member.selectorName)) { + return member.selectorName; + } + if (member.selectorName != member.name && + selectorGroupCanPrepareSelector(receiver, lookupClass, receiverIsClass, + member.name)) { + return member.name; + } + + std::string booleanSelector = booleanGetterSelectorForProperty(member.name); + if (booleanSelector != member.selectorName && booleanSelector != member.name && + selectorGroupCanPrepareSelector(receiver, lookupClass, receiverIsClass, + booleanSelector)) { + return booleanSelector; + } + + if (auto responding = respondingPropertyGetterSelector( + receiver, member.name, member.selectorName)) { + return *responding; + } + + return member.selectorName != member.name ? member.name : member.selectorName; +} + +NativeApiSelectorGroupCallTarget selectorGroupMemberForCall( + id receiver, Class lookupClass, bool receiverIsClass, + NativeApiSelectorGroupEntry& entry, size_t count) { + if (!entry.hasMember) { + return {&entry.selectorName, nullptr, true}; + } + if (count == 0 && entry.member.property) { + if (!entry.propertyGetterResolved) { + entry.propertyGetterSelectorName = selectorGroupPropertyGetterSelector( + receiver, lookupClass, receiverIsClass, entry.member); + entry.propertyGetterCanPrepare = selectorGroupCanPrepareSelector( + receiver, lookupClass, receiverIsClass, + entry.propertyGetterSelectorName); + if (entry.propertyGetterSelectorName != entry.member.selectorName) { + entry.propertyGetterMember = entry.member; + entry.propertyGetterMember.selectorName = + entry.propertyGetterSelectorName; + entry.propertyGetterHasAdjustedMember = true; + } + entry.propertyGetterResolved = true; + } + return {&entry.propertyGetterSelectorName, + entry.propertyGetterHasAdjustedMember ? &entry.propertyGetterMember + : &entry.member, + entry.propertyGetterCanPrepare}; + } + return {&entry.selectorName, &entry.member, true}; +} + +inline NativeApiSelectorGroupCallTarget selectorGroupCallTargetForEntry( + id receiver, Class lookupClass, bool receiverIsClass, + NativeApiSelectorGroupEntry& entry, size_t count) { + if (entry.hasMember && (!entry.member.property || count != 0)) { + return {&entry.selectorName, &entry.member, true}; + } + return selectorGroupMemberForCall(receiver, lookupClass, receiverIsClass, + entry, count); +} + +const NativeApiMember* selectPropertyMember( + const std::vector& members, const std::string& property, + bool staticMethod) { + for (const auto& member : members) { + if (!member.property || member.name != property) { + continue; + } + + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic == staticMethod) { + return &member; + } + } + return nullptr; +} + +const NativeApiMember* selectWritablePropertyMember( + const std::vector& members, const std::string& property, + bool staticMethod) { + const NativeApiMember* propertyMember = nullptr; + for (const auto& member : members) { + if (!member.property || member.name != property) { + continue; + } + + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic != staticMethod) { + continue; + } + + if (propertyMember == nullptr) { + propertyMember = &member; + } + if (!member.readonly && !member.setterSelectorName.empty()) { + return &member; + } + } + return propertyMember; +} + +void skipMetadataEngineType(MDMetadataReader* metadata, MDSectionOffset* offset); +Protocol* lookupProtocolByNativeName(const std::string& name); +struct NativeApiPreparedObjCInvocation; +bool preparedObjCInvocationIsInit( + const NativeApiPreparedObjCInvocation& prepared); + +inline uintptr_t normalizeRuntimePointer(uintptr_t pointer) { +#if INTPTR_MAX == INT64_MAX + return pointer & 0x0000FFFFFFFFFFFFULL; +#else + return pointer; +#endif +} + +// One bridge is shared by every Runtime that touches the same process (a +// worklet spins up an additional Runtime on its own thread). Expandos are +// per-runtime: a Value created in one Runtime must never be handed back out +// of a different one, so every expando read/write is keyed on the owning +// runtime's identity, not just the native pointer. +uintptr_t runtimeObjectExpandoKey(Runtime& runtime) { +#if defined(TARGET_ENGINE_V8) || defined(TARGET_ENGINE_JSC) || \ + defined(TARGET_ENGINE_QUICKJS) + return normalizeRuntimePointer( + reinterpret_cast(runtime.state().get())); +#else + return normalizeRuntimePointer(reinterpret_cast(&runtime)); +#endif +} + +class NativeApiBridge { + struct NativeApiRoundTripValue { + std::shared_ptr value; + bool stringLikeNative = false; + bool persistBeyondFrame = true; + uintptr_t validationKey = 0; + }; + using NativeApiRoundTripReleaseList = + std::vector; + using NativeApiRoundTripFrame = + std::unordered_map; + using NativeApiRoundTripFrameStack = std::vector; + + static constexpr size_t kRecentRoundTripValueLimit = 2; + + public: + explicit NativeApiBridge(const NativeApiConfig& config) + : metadata_(loadMetadata(config)), + scheduler_(config.scheduler), + nativeInvocationInvoker_(config.nativeInvocationInvoker), + nativeCallbackInvoker_(config.nativeCallbackInvoker), + runtimeCallbackInvoker_(config.runtimeCallbackInvoker), + jsThreadCallbackInvoker_(config.jsThreadCallbackInvoker), + jsThreadAsyncCallbackInvoker_(config.jsThreadAsyncCallbackInvoker), + callbackInvocationAllowed_(config.callbackInvocationAllowed), + invokeCallbacksOnNativeCallerThread_( + config.invokeCallbacksOnNativeCallerThread), + indexRuntimePointers_(config.indexRuntimePointers) { + selfDl_ = dlopen(nullptr, RTLD_NOW); + buildSymbolIndexes(); + } + + ~NativeApiBridge() { + if (selfDl_ != nullptr) { + dlclose(selfDl_); + } + } + + MDMetadataReader* metadata() const { return metadata_.get(); } + + void* selfDl() const { return selfDl_; } + + const NativeApiSymbol* find(const std::string& name) const { + auto it = symbolsByName_.find(name); + return it != symbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findClass(const std::string& name) const { + const NativeApiSymbol* symbol = find(name); + if (symbol != nullptr && symbol->kind == NativeApiSymbolKind::Class) { + return symbol; + } + auto it = classSymbolsByRuntimeName_.find(name); + return it != classSymbolsByRuntimeName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findClassByOffset(MDSectionOffset offset) const { + auto it = classSymbolsByOffset_.find(offset); + return it != classSymbolsByOffset_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findClassForRuntimeClass(Class cls) const { + Class current = cls; + while (current != Nil) { + const char* name = class_getName(current); + if (name != nullptr) { + if (const NativeApiSymbol* symbol = findClass(name)) { + return symbol; + } + } + current = class_getSuperclass(current); + } + + // The class-name walk above found nothing at all: not even a distant + // ancestor (e.g. NSObject) is known to metadata. As a last resort, + // identify the object by the most specific conformed protocol metadata + // does know about (see forEachConformedProtocol for the deterministic + // traversal/tie-break order), so callers that just need *some* symbol to + // key off (building a class-identity/prototype value, for instance) + // still get one instead of nullptr. + // + // This is the rarer half of the protocol-method-metadata fallback. The + // common real-world case -- a class walk that *does* resolve to a known + // symbol (the object's own class, or an ancestor), but whose + // metadata-baked member table doesn't carry a method declared solely on + // a protocol the object conforms to only at the ObjC runtime level (a + // category/class-extension conformance the metadata generator never + // parsed, or a fully private concrete class such as the one behind + // UIViewControllerTransitionCoordinator) -- is handled by + // protocolMembersForRuntimeClass below, which callers consult once a + // normal member lookup on this symbol comes up empty. + const NativeApiSymbol* protocolSymbol = nullptr; + forEachConformedProtocol(cls, [&](Protocol* protocol) { + auto it = protocolSymbolsByRuntimePointer_.find( + normalizeRuntimePointer(reinterpret_cast(protocol))); + if (it != protocolSymbolsByRuntimePointer_.end()) { + protocolSymbol = &it->second; + return true; + } + return false; + }); + return protocolSymbol; + } + + // Invokes `visit` for every protocol that `cls` (or an ancestor in its + // *runtime* class hierarchy) conforms to, until `visit` returns true. + // class_copyProtocolList only reports protocols adopted directly at the + // queried class level -- it does not walk superclasses -- so this walks + // class_getSuperclass itself (mirroring findClassForRuntimeClass's own + // walk) to collect the full picture. + // + // Traversal order (this is the deterministic tie-break rule for a class + // that conforms to more than one protocol declaring the same selector): + // the most-derived class in cls's hierarchy is visited first, then each + // ancestor in turn; within one class's own adopted-protocol list, in the + // order class_copyProtocolList returns them (stable per compiled binary -- + // it reflects the order protocols were listed in that class's + // @interface/category/extension); each protocol's own inherited protocols + // are expanded depth-first immediately after it, ahead of its next + // sibling. The first protocol in this order that satisfies the caller's + // `visit` wins. This mirrors the "closest/most-specific declaration wins" + // rule the class-hierarchy member composition elsewhere in this file + // already follows (see appendSurfaceMember). + template + void forEachConformedProtocol(Class cls, Visitor&& visit) const { + std::unordered_set visited; + for (Class current = cls; current != Nil; + current = class_getSuperclass(current)) { + unsigned int count = 0; + Protocol* __unsafe_unretained* protocols = + class_copyProtocolList(current, &count); + if (protocols == nullptr) { + continue; + } + bool stop = false; + for (unsigned int i = 0; i < count && !stop; i++) { + stop = visitProtocolDepthFirst(protocols[i], visited, visit); + } + free(protocols); + if (stop) { + return; + } + } + } + + template + bool visitProtocolDepthFirst(Protocol* protocol, + std::unordered_set& visited, + Visitor&& visit) const { + if (protocol == nullptr || !visited.insert(protocol).second) { + return false; + } + if (visit(protocol)) { + return true; + } + + unsigned int count = 0; + Protocol* __unsafe_unretained* inherited = + protocol_copyProtocolList(protocol, &count); + if (inherited == nullptr) { + return false; + } + bool stop = false; + for (unsigned int i = 0; i < count && !stop; i++) { + stop = visitProtocolDepthFirst(inherited[i], visited, visit); + } + free(inherited); + return stop; + } + + // Members contributed by protocols `cls` conforms to at the Objective-C + // runtime level, restricted to protocols metadata knows about. This is + // the fallback for a selector declared solely on a protocol -- e.g. every + // method of UIViewControllerTransitionCoordinator -- where + // findClassForRuntimeClass's plain class_getSuperclass walk resolves to a + // real, known class symbol (the object's own class, or some ancestor), + // but that symbol's own member table doesn't carry the method, because + // metadata bakes conformance from what each class's *own* processed + // header declares, not from what the object conforms to at runtime. + // + // Cached per runtime Class (not per metadata symbol offset, the way + // membersForClass/surfaceMembersForClass are): two different runtime + // classes can both resolve to the same metadata symbol while conforming + // to different runtime-only protocols, so the metadata-symbol cache would + // be the wrong granularity here and could leak members across unrelated + // classes. + // + // Callers are expected to consult this only once an ordinary + // membersForClass/surfaceMembersForClass lookup has already missed, so + // classes with complete metadata (the common case) pay nothing extra. + const std::vector& protocolMembersForRuntimeClass( + Class cls) const { + static const std::vector kEmptyMembers; + if (cls == Nil) { + return kEmptyMembers; + } + + uintptr_t key = normalizeRuntimePointer(reinterpret_cast(cls)); + auto cached = protocolMembersByRuntimeClass_.find(key); + if (cached != protocolMembersByRuntimeClass_.end()) { + return cached->second; + } + + std::vector members; + forEachConformedProtocol(cls, [&](Protocol* protocol) { + auto it = protocolSymbolsByRuntimePointer_.find( + normalizeRuntimePointer(reinterpret_cast(protocol))); + if (it != protocolSymbolsByRuntimePointer_.end()) { + for (const auto& member : membersForProtocol(it->second)) { + bool exists = false; + for (const auto& existing : members) { + if (sameMemberSlot(existing, member)) { + exists = true; + break; + } + } + if (!exists) { + members.push_back(member); + } + } + } + return false; // Keep visiting: collect members from every conformed + // protocol, not just the first. + }); + + auto inserted = protocolMembersByRuntimeClass_.emplace( + key, std::move(members)); + return inserted.first->second; + } + + const NativeApiSymbol* findClassForRuntimePointer(void* pointer) const { + if (pointer == nullptr) { + return nullptr; + } + + auto it = classSymbolsByRuntimePointer_.find( + normalizeRuntimePointer(reinterpret_cast(pointer))); + return it != classSymbolsByRuntimePointer_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findProtocolForRuntimePointer(void* pointer) const { + if (pointer == nullptr) { + return nullptr; + } + + auto it = protocolSymbolsByRuntimePointer_.find( + normalizeRuntimePointer(reinterpret_cast(pointer))); + return it != protocolSymbolsByRuntimePointer_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findFunction(const std::string& name) const { + auto it = functionSymbolsByName_.find(name); + return it != functionSymbolsByName_.end() ? &it->second : nullptr; + } + + static uintptr_t callbackRoundTripValidationKey( + const NativeApiType& type) { + if (type.signatureOffset == 0 || + type.signatureOffset == MD_SECTION_OFFSET_NULL) { + return 0; + } + return (static_cast(type.signatureOffset) << 8) | + (static_cast(type.kind) & 0xff); + } + + void rememberRoundTripValue(Runtime& runtime, const void* native, + const Value& value, + bool stringLikeNative = false, + uintptr_t validationKey = 0) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + storeRoundTripEntry( + roundTripValues_, key, + NativeApiRoundTripValue{ + std::make_shared(runtime, value), stringLikeNative, true, + validationKey}, + releaseAfterUnlock); + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } +#ifdef TARGET_ENGINE_HERMES + rootRoundTripValue(runtime, key, value); +#endif + } + + // Convenience for preservedNativeApiInitializerSelfReturn: remembers an id + // (rather than an arbitrary native pointer) with the class-derived + // validation key, the same key an ordinary object wrapping would use. + void rememberNativeObjectRoundTripValue(Runtime& runtime, id object, + const Value& value, + bool stringLikeNative = false) { + rememberRoundTripValue(runtime, object, value, stringLikeNative, + nativeObjectClassKey(object)); + } + + void rememberScopedRoundTripValue(Runtime& runtime, const void* native, + const Value& value, + bool stringLikeNative = false, + bool persistBeyondFrame = true) { + rememberScopedRoundTripValueWithValidationKey( + runtime, native, value, stringLikeNative, persistBeyondFrame, + nativeObjectClassKey(native)); + } + + void rememberScopedRawRoundTripValue(Runtime& runtime, const void* native, + const Value& value, + bool stringLikeNative = false, + bool persistBeyondFrame = true) { + rememberScopedRoundTripValueWithValidationKey(runtime, native, value, + stringLikeNative, + persistBeyondFrame, 0); + } + + void rememberScopedRoundTripValueWithValidationKey(Runtime& runtime, + const void* native, + const Value& value, + bool stringLikeNative, + bool persistBeyondFrame, + uintptr_t validationKey) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + NativeApiRoundTripValue entry{ + std::make_shared(runtime, value), stringLikeNative, + persistBeyondFrame, validationKey}; + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + auto framesIt = + roundTripCacheFramesByThread_.find(std::this_thread::get_id()); + if (framesIt != roundTripCacheFramesByThread_.end() && + !framesIt->second.empty()) { + storeRoundTripEntry(framesIt->second.back(), key, std::move(entry), + releaseAfterUnlock); + } else if (persistBeyondFrame) { + rememberRecentRoundTripValue(key, std::move(entry), + releaseAfterUnlock); + } + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + } + + Value findRoundTripValue(Runtime& runtime, const void* native, + bool* stringLikeNative = nullptr, + bool nativeIsObject = false, + uintptr_t validationKey = 0) { + if (stringLikeNative != nullptr) { + *stringLikeNative = false; + } + if (native == nullptr) { + return Value::undefined(); + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + const uintptr_t expectedValidationKey = + validationKey != 0 + ? validationKey + : (nativeIsObject ? nativeObjectClassKey(native) : 0); + struct RoundTripCacheEntry { + const NativeApiBridge* bridge = nullptr; + uintptr_t key = 0; + uint64_t generation = 0; + std::weak_ptr value; + bool miss = false; + bool stringLikeNative = false; + uintptr_t validationKey = 0; + }; + static thread_local RoundTripCacheEntry cache[4]; + const uint64_t generation = + roundTripValuesGeneration_.load(std::memory_order_acquire); + const size_t firstSlot = (key >> 4) & 3; + for (size_t i = 0; i < 4; i++) { + RoundTripCacheEntry& entry = cache[(firstSlot + i) & 3]; + if (entry.bridge == this && entry.key == key && + entry.generation == generation) { + if (entry.validationKey != expectedValidationKey) { + break; + } + if (entry.miss) { + return Value::undefined(); + } + if (auto cached = entry.value.lock()) { + if (roundTripValuesGeneration_.load(std::memory_order_acquire) == + generation) { + if (stringLikeNative != nullptr) { + *stringLikeNative = entry.stringLikeNative; + } + return Value(runtime, *cached); + } + } + break; + } + } + + std::shared_ptr storedValue; + bool cachedStringLike = false; + { + std::lock_guard lock(roundTripValuesMutex_); + auto findEntry = [&](const auto& map) -> const NativeApiRoundTripValue* { + auto it = map.find(key); + if (it == map.end() || it->second.value == nullptr) { + return nullptr; + } + if (it->second.validationKey != expectedValidationKey) { + return nullptr; + } + return &it->second; + }; + + const NativeApiRoundTripValue* entry = findEntry(roundTripValues_); + if (entry == nullptr) { + auto framesIt = + roundTripCacheFramesByThread_.find(std::this_thread::get_id()); + if (framesIt != roundTripCacheFramesByThread_.end()) { + for (auto frame = framesIt->second.rbegin(); + frame != framesIt->second.rend(); ++frame) { + entry = findEntry(*frame); + if (entry != nullptr) { + break; + } + } + } + } + if (entry == nullptr) { + entry = findEntry(recentRoundTripValues_); + } + if (entry == nullptr) { + cache[firstSlot] = RoundTripCacheEntry{ + this, key, generation, {}, true, false, expectedValidationKey}; + return Value::undefined(); + } + storedValue = entry->value; + cachedStringLike = entry->stringLikeNative; + cache[firstSlot] = RoundTripCacheEntry{ + this, key, generation, storedValue, false, cachedStringLike, + entry->validationKey}; + } + if (stringLikeNative != nullptr) { + *stringLikeNative = cachedStringLike; + } + return Value(runtime, *storedValue); + } + + void forgetRoundTripValue(Runtime& runtime, const void* native) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); +#ifdef TARGET_ENGINE_HERMES + bool rooted = false; + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + eraseRoundTripMapKey(roundTripValues_, key, releaseAfterUnlock); + eraseRoundTripKeyFromScopedCaches(key, releaseAfterUnlock); + rooted = rootedRoundTripValues_.erase(key) > 0; + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + if (rooted) { + unrootRoundTripValue(runtime, key); + } +#else + forgetRoundTripKey(key); +#endif + } + + void forgetRoundTripKey(uintptr_t key) { + if (key == 0) { + return; + } + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + eraseRoundTripMapKey(roundTripValues_, key, releaseAfterUnlock); + eraseRoundTripKeyFromScopedCaches(key, releaseAfterUnlock); + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + } + + void forgetRoundTripValue(const void* native) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + eraseRoundTripMapKey(roundTripValues_, key, releaseAfterUnlock); + eraseRoundTripKeyFromScopedCaches(key, releaseAfterUnlock); + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + } + + uint64_t roundTripValuesGeneration() const { + return roundTripValuesGeneration_.load(std::memory_order_acquire); + } + + void beginRoundTripCacheFrame() { + std::lock_guard lock(roundTripValuesMutex_); + roundTripCacheFramesByThread_[std::this_thread::get_id()].emplace_back(); + } + + void endRoundTripCacheFrame() { + NativeApiRoundTripReleaseList releaseAfterUnlock; + NativeApiRoundTripFrame frame; + { + std::lock_guard lock(roundTripValuesMutex_); + auto framesIt = + roundTripCacheFramesByThread_.find(std::this_thread::get_id()); + if (framesIt == roundTripCacheFramesByThread_.end() || + framesIt->second.empty()) { + return; + } + + auto& frames = framesIt->second; + frame = std::move(frames.back()); + frames.pop_back(); + if (!frames.empty()) { + auto& parent = frames.back(); + for (auto& entry : frame) { + storeRoundTripEntry(parent, entry.first, std::move(entry.second), + releaseAfterUnlock); + } + } else { + roundTripCacheFramesByThread_.erase(framesIt); + for (auto& entry : frame) { + if (entry.second.persistBeyondFrame) { + rememberRecentRoundTripValue(entry.first, std::move(entry.second), + releaseAfterUnlock); + } else { + releaseAfterUnlock.push_back(std::move(entry.second)); + } + } + } + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + } + + void rememberClassValue(Runtime& runtime, Class cls, const Value& value) { + if (cls == Nil) { + return; + } + classValues_[normalizeRuntimePointer(reinterpret_cast(cls))] = + std::make_shared(runtime, value); + } + + Value findClassValue(Runtime& runtime, Class cls) const { + if (cls == Nil) { + return Value::undefined(); + } + auto it = classValues_.find( + normalizeRuntimePointer(reinterpret_cast(cls))); + if (it == classValues_.end() || it->second == nullptr) { + return Value::undefined(); + } + return Value(runtime, *it->second); + } + + void rememberClassPrototype(Runtime& runtime, Class cls, const Value& value) { + if (cls == Nil) { + return; + } + classPrototypes_[normalizeRuntimePointer(reinterpret_cast(cls))] = + std::make_shared(runtime, value); + } + + Value findClassPrototype(Runtime& runtime, Class cls) const { + if (cls == Nil) { + return Value::undefined(); + } + auto it = classPrototypes_.find( + normalizeRuntimePointer(reinterpret_cast(cls))); + if (it == classPrototypes_.end() || it->second == nullptr) { + return Value::undefined(); + } + return Value(runtime, *it->second); + } + + // Expandos are keyed native-pointer -> property -> owning-runtime, all + // guarded by objectExpandosMutex_: worklet runtimes run on their own + // thread but share this bridge, and a host-object dtor releasing its + // expando owner count can run on either thread relative to a get/set. + void setObjectExpando(Runtime& runtime, const void* native, + const std::string& property, const Value& value) { + if (native == nullptr || property.empty()) { + return; + } + const uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + const uintptr_t runtimeKey = runtimeObjectExpandoKey(runtime); + { + std::lock_guard lock(objectExpandosMutex_); + objectExpandos_[key][property][runtimeKey] = + std::make_shared(runtime, value); + objectExpandosGeneration_.fetch_add(1, std::memory_order_release); + } + } + + void retainObjectExpandoOwner(const void* native) { + if (native == nullptr) { + return; + } + std::lock_guard lock(objectExpandosMutex_); + objectExpandoOwnerCounts_[ + normalizeRuntimePointer(reinterpret_cast(native))] += 1; + } + + // preserveExpandos keeps the stored values around after the last owner + // releases (used when a wrapper is being replaced/detached but the + // underlying native receiver's expando state must survive the swap). + void releaseObjectExpandoOwner(const void* native, + bool preserveExpandos = false) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + bool shouldForget = false; + { + std::lock_guard lock(objectExpandosMutex_); + auto ownerIt = objectExpandoOwnerCounts_.find(key); + if (ownerIt != objectExpandoOwnerCounts_.end()) { + if (ownerIt->second > 1) { + ownerIt->second -= 1; + return; + } + objectExpandoOwnerCounts_.erase(ownerIt); + } + shouldForget = !preserveExpandos; + } + if (shouldForget) { + forgetObjectExpandos(native); + } + } + + Value findObjectExpando(Runtime& runtime, const void* native, + const std::string& property) const { + if (native == nullptr || property.empty()) { + return Value::undefined(); + } + struct ObjectExpandoCacheEntry { + const NativeApiBridge* bridge = nullptr; + uintptr_t key = 0; + uintptr_t runtimeKey = 0; + uint64_t generation = 0; + std::string property; + std::weak_ptr value; + bool miss = false; + }; + static thread_local ObjectExpandoCacheEntry cache[8]; + static thread_local size_t nextSlot = 0; + + const uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + const uintptr_t runtimeKey = runtimeObjectExpandoKey(runtime); + const uint64_t generation = + objectExpandosGeneration_.load(std::memory_order_acquire); + for (auto& entry : cache) { + if (entry.bridge == this && entry.key == key && + entry.runtimeKey == runtimeKey && entry.generation == generation && + entry.property == property) { + if (entry.miss) { + return Value::undefined(); + } + if (auto cached = entry.value.lock()) { + return Value(runtime, *cached); + } + break; + } + } + + std::shared_ptr storedValue; + const size_t slot = nextSlot++ & 7; + { + std::lock_guard lock(objectExpandosMutex_); + auto objectIt = objectExpandos_.find(key); + if (objectIt != objectExpandos_.end()) { + auto propertyIt = objectIt->second.find(property); + if (propertyIt != objectIt->second.end()) { + auto runtimeIt = propertyIt->second.find(runtimeKey); + if (runtimeIt != propertyIt->second.end() && + runtimeIt->second != nullptr) { + storedValue = runtimeIt->second; + } + } + } + if (storedValue == nullptr) { + cache[slot] = ObjectExpandoCacheEntry{ + this, key, runtimeKey, generation, property, {}, true}; + return Value::undefined(); + } + cache[slot] = ObjectExpandoCacheEntry{ + this, key, runtimeKey, generation, property, storedValue, false}; + } + return Value(runtime, *storedValue); + } + + // Erases every runtime's stored values for this native (codex semantics): + // the native object itself is gone, so no runtime should keep seeing it. + void forgetObjectExpandos(const void* native) { + if (native == nullptr) { + return; + } + auto key = normalizeRuntimePointer(reinterpret_cast(native)); + { + std::lock_guard lock(objectExpandosMutex_); + objectExpandos_.erase(key); + objectExpandosGeneration_.fetch_add(1, std::memory_order_release); + } + } + + // Per-class cache of resolved metadata property-getter members. Lets the + // instance property interceptor skip the special-name chain + metadata + // discovery on every `object.prop` access (the engines without V8's + // kNonMasking prototype fast path otherwise re-resolve on each access). + // membersByClassOffset_ vectors are permanent, so the member pointer is + // stable for the bridge's lifetime. + struct CachedPropertyGetter { + const NativeApiMember* member; + std::string selectorName; + std::shared_ptr preparedInvocation; + }; + const CachedPropertyGetter* findCachedPropertyGetter( + Class cls, const std::string& property) const { + if (cls == Nil || property.empty()) { + return nullptr; + } + struct PropertyGetterCacheEntry { + const NativeApiBridge* bridge = nullptr; + Class cls = Nil; + uint64_t generation = 0; + std::string property; + const CachedPropertyGetter* getter = nullptr; + bool miss = false; + }; + static thread_local PropertyGetterCacheEntry cache[8]; + static thread_local size_t nextSlot = 0; + + const uint64_t generation = + propertyGetterCacheGeneration_.load(std::memory_order_acquire); + for (auto& entry : cache) { + if (entry.bridge == this && entry.cls == cls && + entry.generation == generation && entry.property == property) { + return entry.miss ? nullptr : entry.getter; + } + } + + auto classIt = propertyGetterCache_.find(cls); + const size_t slot = nextSlot++ & 7; + if (classIt == propertyGetterCache_.end()) { + cache[slot] = PropertyGetterCacheEntry{ + this, cls, generation, property, nullptr, true}; + return nullptr; + } + auto propIt = classIt->second.find(property); + if (propIt == classIt->second.end()) { + cache[slot] = PropertyGetterCacheEntry{ + this, cls, generation, property, nullptr, true}; + return nullptr; + } + cache[slot] = + PropertyGetterCacheEntry{this, cls, generation, property, + &propIt->second, false}; + return &propIt->second; + } + void cachePropertyGetter(Class cls, const std::string& property, + const NativeApiMember* member, + const std::string& selectorName, + std::shared_ptr + preparedInvocation = nullptr) { + propertyGetterCache_[cls][property] = + CachedPropertyGetter{member, selectorName, + std::move(preparedInvocation)}; + propertyGetterCacheGeneration_.fetch_add(1, std::memory_order_release); + } + + void rememberPointerValue(Runtime& runtime, const void* native, + const Value& value) { + pointerValues_[reinterpret_cast(native)] = + std::make_shared(runtime, value); + } + + Value findPointerValue(Runtime& runtime, const void* native) const { + auto it = pointerValues_.find(reinterpret_cast(native)); + if (it == pointerValues_.end() || it->second == nullptr) { + return Value::undefined(); + } + return Value(runtime, *it->second); + } + + void forgetPointerValue(const void* native) { + if (native == nullptr) { + return; + } + pointerValues_.erase(reinterpret_cast(native)); + } + + const NativeApiSymbol* findConstant(const std::string& name) const { + auto it = constantSymbolsByName_.find(name); + return it != constantSymbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findProtocol(const std::string& name) const { + const NativeApiSymbol* symbol = find(name); + if (symbol != nullptr && symbol->kind == NativeApiSymbolKind::Protocol) { + return symbol; + } + auto it = protocolSymbolsByRuntimeName_.find(name); + return it != protocolSymbolsByRuntimeName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findEnum(const std::string& name) const { + auto it = enumSymbolsByName_.find(name); + return it != enumSymbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findStruct(const std::string& name) const { + auto it = structSymbolsByName_.find(name); + return it != structSymbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findUnion(const std::string& name) const { + auto it = unionSymbolsByName_.find(name); + return it != unionSymbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findAggregate(const std::string& name) const { + const NativeApiSymbol* symbol = findStruct(name); + if (symbol != nullptr) { + return symbol; + } + return findUnion(name); + } + + size_t classCount() const { return classNames_.size(); } + size_t functionCount() const { return functionNames_.size(); } + size_t constantCount() const { return constantNames_.size(); } + size_t protocolCount() const { return protocolNames_.size(); } + size_t enumCount() const { return enumNames_.size(); } + size_t structCount() const { return structNames_.size(); } + size_t unionCount() const { return unionNames_.size(); } + + const std::vector& classNames() const { return classNames_; } + const std::vector& functionNames() const { return functionNames_; } + const std::vector& constantNames() const { return constantNames_; } + const std::vector& protocolNames() const { return protocolNames_; } + const std::vector& enumNames() const { return enumNames_; } + const std::vector& structNames() const { return structNames_; } + const std::vector& unionNames() const { return unionNames_; } + std::shared_ptr scheduler() const { return scheduler_; } + const std::function)>& nativeInvocationInvoker() + const { + return nativeInvocationInvoker_; + } + const std::function)>& nativeCallbackInvoker() + const { + return nativeCallbackInvoker_; + } + const std::function)>& runtimeCallbackInvoker() + const { + return runtimeCallbackInvoker_; + } + const std::function)>& jsThreadCallbackInvoker() + const { + return jsThreadCallbackInvoker_; + } + const std::function)>& + jsThreadAsyncCallbackInvoker() const { + return jsThreadAsyncCallbackInvoker_; + } + // Teardown-safety gate: RN sets this so callbacks can be refused once the + // host is tearing down/reloading, without every call site needing to know + // why. Both a C++ try and an @try wrap the call — they catch different + // exception families (std::exception-derived vs. NSException), and either + // one escaping here would otherwise cross into caller frames that aren't + // set up to catch it. + bool callbackInvocationAllowed() const noexcept { + if (!callbackInvocationAllowed_) { + return true; + } + @try { + try { + return callbackInvocationAllowed_(); + } catch (...) { + return false; + } + } @catch (...) { + return false; + } + } + bool invokeCallbacksOnNativeCallerThread() const { + return invokeCallbacksOnNativeCallerThread_; + } + + std::thread::id jsThreadId() const { return jsThreadId_; } + + void retainEngineLifetime(std::shared_ptr lifetime) { + if (lifetime == nullptr) { + return; + } + std::lock_guard lock(retainedLifetimesMutex_); + retainedLifetimes_.push_back(std::move(lifetime)); + } + +#ifdef TARGET_ENGINE_HERMES + std::string roundTripRootKey(uintptr_t key) const { + char buffer[32] = {}; + snprintf(buffer, sizeof(buffer), "p%llx", + static_cast(key)); + return buffer; + } + + Object roundTripRootObject(Runtime& runtime) { + if (roundTripRootCache_) { + return roundTripRootCache_->asObject(runtime); + } + static constexpr const char* kRootName = + "__nativeScriptNativeApiRoundTripValues"; + Object global = runtime.global(); + if (global.hasProperty(runtime, kRootName)) { + Value existing = global.getProperty(runtime, kRootName); + if (existing.isObject()) { + Object root = existing.asObject(runtime); + roundTripRootCache_ = std::make_shared(runtime, root); + return root; + } + } + + Object root(runtime); + global.setProperty(runtime, kRootName, root); + roundTripRootCache_ = std::make_shared(runtime, root); + return root; + } + + void rootRoundTripValue(Runtime& runtime, uintptr_t key, + const Value& value) { + roundTripRootObject(runtime) + .setProperty(runtime, roundTripRootKey(key).c_str(), value); + std::lock_guard lock(roundTripValuesMutex_); + rootedRoundTripValues_.insert(key); + } + + void unrootRoundTripValue(Runtime& runtime, uintptr_t key) { + roundTripRootObject(runtime) + .setProperty(runtime, roundTripRootKey(key).c_str(), + Value::undefined()); + } +#endif + + uintptr_t nativeObjectClassKey(const void* native) const { + if (native == nullptr) { + return 0; + } + return normalizeRuntimePointer( + reinterpret_cast(object_getClass(static_cast(native)))); + } + + void storeRoundTripEntry( + std::unordered_map& map, + uintptr_t key, NativeApiRoundTripValue&& entry, + NativeApiRoundTripReleaseList& releaseAfterUnlock) { + auto it = map.find(key); + if (it == map.end()) { + map.emplace(key, std::move(entry)); + return; + } + + releaseAfterUnlock.push_back(std::move(it->second)); + it->second = std::move(entry); + } + + void eraseRoundTripMapKey( + std::unordered_map& map, + uintptr_t key, NativeApiRoundTripReleaseList& releaseAfterUnlock) { + auto it = map.find(key); + if (it == map.end()) { + return; + } + + releaseAfterUnlock.push_back(std::move(it->second)); + map.erase(it); + } + + void eraseRoundTripKeyFromScopedCaches( + uintptr_t key, NativeApiRoundTripReleaseList& releaseAfterUnlock) { + eraseRoundTripMapKey(recentRoundTripValues_, key, releaseAfterUnlock); + recentRoundTripValueOrder_.erase( + std::remove(recentRoundTripValueOrder_.begin(), + recentRoundTripValueOrder_.end(), key), + recentRoundTripValueOrder_.end()); + for (auto& stackEntry : roundTripCacheFramesByThread_) { + for (auto& frame : stackEntry.second) { + eraseRoundTripMapKey(frame, key, releaseAfterUnlock); + } + } + } + + void rememberRecentRoundTripValue(uintptr_t key, + NativeApiRoundTripValue&& entry, + NativeApiRoundTripReleaseList& releaseAfterUnlock) { + if (recentRoundTripValues_.find(key) == recentRoundTripValues_.end()) { + recentRoundTripValueOrder_.push_back(key); + } + storeRoundTripEntry(recentRoundTripValues_, key, std::move(entry), + releaseAfterUnlock); + while (recentRoundTripValueOrder_.size() > kRecentRoundTripValueLimit) { + uintptr_t evicted = recentRoundTripValueOrder_.front(); + recentRoundTripValueOrder_.erase(recentRoundTripValueOrder_.begin()); + eraseRoundTripMapKey(recentRoundTripValues_, evicted, + releaseAfterUnlock); + } + } + + const std::vector& membersForClass( + const NativeApiSymbol& symbol) const { + auto cached = membersByClassOffset_.find(symbol.offset); + if (cached != membersByClassOffset_.end()) { + return cached->second; + } + + auto inserted = membersByClassOffset_.emplace( + symbol.offset, readMembersForClassHierarchy(symbol)); + return inserted.first->second; + } + + const std::vector& surfaceMembersForClass( + const NativeApiSymbol& symbol) const { + auto cached = surfaceMembersByClassOffset_.find(symbol.offset); + if (cached != surfaceMembersByClassOffset_.end()) { + return cached->second; + } + + auto inserted = surfaceMembersByClassOffset_.emplace( + symbol.offset, readSurfaceMembersForClass(symbol)); + return inserted.first->second; + } + + const std::vector& membersForProtocol( + const NativeApiSymbol& symbol) const { + auto cached = membersByProtocolOffset_.find(symbol.offset); + if (cached != membersByProtocolOffset_.end()) { + return cached->second; + } + + auto inserted = membersByProtocolOffset_.emplace( + symbol.offset, readMembersForProtocolHierarchy(symbol.offset)); + return inserted.first->second; + } + + std::shared_ptr aggregateInfoFor( + MDSectionOffset aggregateOffset, bool isUnion); + + std::shared_ptr aggregateInfoFor( + const NativeApiSymbol& symbol) { + return aggregateInfoFor(symbol.offset, + symbol.kind == NativeApiSymbolKind::Union); + } + + private: + static std::unique_ptr loadMetadataFromFile( + const char* metadataPath) { + const char* path = metadataPath != nullptr ? metadataPath : "metadata.nsmd"; + FILE* file = fopen(path, "rb"); + if (file == nullptr) { + throw std::runtime_error(std::string("metadata.nsmd not found: ") + path); + } + + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + if (size <= 0) { + fclose(file); + throw std::runtime_error(std::string("metadata.nsmd is empty: ") + path); + } + + void* buffer = malloc(static_cast(size)); + if (buffer == nullptr) { + fclose(file); + throw std::bad_alloc(); + } + + size_t read = fread(buffer, 1, static_cast(size), file); + fclose(file); + if (read != static_cast(size)) { + free(buffer); + throw std::runtime_error(std::string("failed to read metadata: ") + path); + } + + return std::make_unique(buffer, true); + } + + static std::unique_ptr loadMetadata( + const NativeApiConfig& config) { + if (config.metadataPtr != nullptr && + *static_cast(config.metadataPtr) != '\0') { +#ifdef EMBED_METADATA_SIZE + return std::make_unique((void*)embedded_metadata); +#else + return std::make_unique( + const_cast(config.metadataPtr)); +#endif + } + +#ifdef EMBED_METADATA_SIZE + if (config.metadataPath == nullptr) { + return std::make_unique((void*)embedded_metadata); + } +#endif + + unsigned long segmentSize = 0; + auto segmentData = getsegmentdata( + reinterpret_cast(_dyld_get_image_header(0)), + "__objc_metadata", &segmentSize); + if (segmentData != nullptr && segmentSize > 0) { + return std::make_unique(segmentData); + } + + return loadMetadataFromFile(config.metadataPath); + } + + void addSymbol(NativeApiSymbolKind kind, MDSectionOffset offset, + const char* name, const char* runtimeName = nullptr, + MDSectionOffset superclassOffset = MD_SECTION_OFFSET_NULL) { + if (name == nullptr || name[0] == '\0') { + return; + } + + NativeApiSymbol symbol{ + .kind = kind, + .offset = offset, + .superclassOffset = superclassOffset, + .name = name, + .runtimeName = runtimeName != nullptr ? runtimeName : name, + }; + + switch (kind) { + case NativeApiSymbolKind::Class: + classNames_.push_back(symbol.name); + break; + case NativeApiSymbolKind::Function: + functionNames_.push_back(symbol.name); + functionSymbolsByName_[symbol.name] = symbol; + break; + case NativeApiSymbolKind::Constant: + constantNames_.push_back(symbol.name); + constantSymbolsByName_[symbol.name] = symbol; + break; + case NativeApiSymbolKind::Protocol: + protocolNames_.push_back(symbol.name); + break; + case NativeApiSymbolKind::Enum: + enumNames_.push_back(symbol.name); + enumSymbolsByName_[symbol.name] = symbol; + break; + case NativeApiSymbolKind::Struct: + structNames_.push_back(symbol.name); + structSymbolsByName_[symbol.name] = symbol; + break; + case NativeApiSymbolKind::Union: + unionNames_.push_back(symbol.name); + unionSymbolsByName_[symbol.name] = symbol; + break; + } + + symbolsByName_[symbol.name] = symbol; + if (kind == NativeApiSymbolKind::Class) { + classSymbolsByOffset_[symbol.offset] = symbol; + classSymbolsByRuntimeName_[symbol.runtimeName] = symbol; + if (indexRuntimePointers_) { + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + if (cls != Nil) { + classSymbolsByRuntimePointer_[normalizeRuntimePointer( + reinterpret_cast(cls))] = symbol; + } + } + } else if (kind == NativeApiSymbolKind::Protocol) { + protocolSymbolsByOffset_[symbol.offset] = symbol; + protocolSymbolsByRuntimeName_[symbol.runtimeName] = symbol; + auto rememberProtocolRuntimeName = [&](const std::string& runtimeName) { + if (runtimeName.empty()) { + return; + } + protocolSymbolsByRuntimeName_[runtimeName] = symbol; + if (indexRuntimePointers_) { + Protocol* runtimeProtocol = lookupProtocolByNativeName(runtimeName); + if (runtimeProtocol != nullptr) { + protocolSymbolsByRuntimePointer_[normalizeRuntimePointer( + reinterpret_cast(runtimeProtocol))] = symbol; + } + } + }; + if (symbol.name.size() > 9 && + std::isdigit(static_cast(symbol.name.back()))) { + size_t digitsStart = symbol.name.size(); + while (digitsStart > 0 && + std::isdigit(static_cast(symbol.name[digitsStart - 1]))) { + digitsStart--; + } + constexpr const char* protocolSuffix = "Protocol"; + size_t protocolSuffixLength = std::strlen(protocolSuffix); + if (digitsStart > protocolSuffixLength && + symbol.name.compare(digitsStart - protocolSuffixLength, + protocolSuffixLength, protocolSuffix) == 0) { + rememberProtocolRuntimeName( + symbol.name.substr(0, digitsStart - protocolSuffixLength)); + } + } + if (indexRuntimePointers_) { + Protocol* protocol = lookupProtocolByNativeName(symbol.runtimeName); + if (protocol == nullptr && symbol.runtimeName != symbol.name) { + protocol = lookupProtocolByNativeName(symbol.name); + } + if (protocol != nullptr) { + protocolSymbolsByRuntimePointer_[normalizeRuntimePointer( + reinterpret_cast(protocol))] = symbol; + } + } + } else if (kind == NativeApiSymbolKind::Struct) { + structSymbolsByOffset_[symbol.offset] = symbol; + } else if (kind == NativeApiSymbolKind::Union) { + unionSymbolsByOffset_[symbol.offset] = symbol; + } + } + + void addAggregateAliases(NativeApiSymbolKind kind, MDSectionOffset offset, + const std::string& name) { + if (name.empty()) { + return; + } + + if (!name.empty() && name[0] == '_') { + std::string alias = name.substr(1); + if (!alias.empty() && symbolsByName_.find(alias) == symbolsByName_.end()) { + addSymbol(kind, offset, alias.c_str(), name.c_str()); + } + } + + constexpr const char* suffix = "Struct"; + if (name.size() < std::strlen(suffix) || + name.compare(name.size() - std::strlen(suffix), std::strlen(suffix), + suffix) != 0) { + std::string alias = name + suffix; + if (symbolsByName_.find(alias) == symbolsByName_.end()) { + addSymbol(kind, offset, alias.c_str(), name.c_str()); + } + } + } + + void buildSymbolIndexes() { + if (metadata_ == nullptr) { + return; + } + + indexConstants(); + indexEnums(); + indexFunctions(); + indexProtocols(); + indexClasses(); + indexStructs(); + indexUnions(); + } + + static void skipConstantValue(MDMetadataReader* metadata, + MDSectionOffset& offset, + metagen::MDVariableEvalKind evalKind) { + switch (evalKind) { + case metagen::mdEvalNone: + skipMetadataEngineType(metadata, &offset); + break; + case metagen::mdEvalInt64: + offset += sizeof(int64_t); + break; + case metagen::mdEvalDouble: + offset += sizeof(double); + break; + case metagen::mdEvalString: + offset += sizeof(MDSectionOffset); + break; + } + } + + void indexConstants() { + MDSectionOffset offset = metadata_->constantsOffset; + while (offset < metadata_->enumsOffset) { + MDSectionOffset originalOffset = offset; + addSymbol(NativeApiSymbolKind::Constant, originalOffset, + metadata_->getString(offset)); + offset += sizeof(MDSectionOffset); + auto evalKind = metadata_->getVariableEvalKind(offset); + offset += sizeof(metagen::MDVariableEvalKind); + skipConstantValue(metadata_.get(), offset, evalKind); + } + } + + void indexEnums() { + MDSectionOffset offset = metadata_->enumsOffset; + while (offset < metadata_->signaturesOffset) { + MDSectionOffset originalOffset = offset; + addSymbol(NativeApiSymbolKind::Enum, originalOffset, + metadata_->getString(offset)); + offset += sizeof(MDSectionOffset); + + bool next = true; + while (next) { + auto nameOffset = metadata_->getOffset(offset); + next = (nameOffset & metagen::mdSectionOffsetNext) != 0; + offset += sizeof(MDSectionOffset); + offset += sizeof(int64_t); + } + } + } + + void indexFunctions() { + MDSectionOffset offset = metadata_->functionsOffset; + while (offset < metadata_->protocolsOffset) { + MDSectionOffset originalOffset = offset; + addSymbol(NativeApiSymbolKind::Function, originalOffset, + metadata_->getString(offset)); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + offset += sizeof(metagen::MDFunctionFlag); + } + } + + void indexProtocols() { + MDSectionOffset offset = metadata_->protocolsOffset; + while (offset < metadata_->classesOffset) { + MDSectionOffset originalOffset = offset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool next = (nameOffset & metagen::mdSectionOffsetNext) != 0; + nameOffset &= ~metagen::mdSectionOffsetNext; + addSymbol(NativeApiSymbolKind::Protocol, originalOffset, + metadata_->resolveString(nameOffset)); + + while (next) { + auto protocolOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + + next = true; + while (next) { + auto flags = metadata_->getMemberFlag(offset); + next = (flags & metagen::mdMemberNext) != 0; + offset += sizeof(flags); + if (flags == metagen::mdMemberFlagNull) { + break; + } + + skipMember(flags, offset); + } + } + } + + void indexClasses() { + MDSectionOffset offset = metadata_->classesOffset; + while (offset < metadata_->structsOffset) { + MDSectionOffset originalOffset = offset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + auto runtimeNameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; + nameOffset &= ~metagen::mdSectionOffsetNext; + + auto name = metadata_->resolveString(nameOffset); + const char* runtimeName = name; + if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { + runtimeName = metadata_->resolveString(runtimeNameOffset); + } + + while (hasProtocols) { + auto protocolOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + + auto superclass = metadata_->getOffset(offset); + offset += sizeof(superclass); + MDSectionOffset superclassOffset = + superclass & ~metagen::mdSectionOffsetNext; + if (superclassOffset != MD_SECTION_OFFSET_NULL) { + superclassOffset += metadata_->classesOffset; + } + + addSymbol(NativeApiSymbolKind::Class, originalOffset, name, runtimeName, + superclassOffset); + + bool next = (superclass & metagen::mdSectionOffsetNext) != 0; + while (next) { + auto flags = metadata_->getMemberFlag(offset); + next = (flags & metagen::mdMemberNext) != 0; + offset += sizeof(flags); + skipMember(flags, offset); + } + } + } + + void skipAggregateFields(MDSectionOffset& offset, bool isUnion) const { + bool next = true; + while (next) { + MDSectionOffset nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (nameOffset & metagen::mdSectionOffsetNext) != 0; + nameOffset &= ~metagen::mdSectionOffsetNext; + if (nameOffset == MD_SECTION_OFFSET_NULL) { + break; + } + if (!isUnion) { + offset += sizeof(uint16_t); + } + skipMetadataEngineType(metadata_.get(), &offset); + } + } + + void indexStructs() { + MDSectionOffset offset = metadata_->structsOffset; + while (offset < metadata_->unionsOffset) { + if (metadata_->getOffset(offset) == 0) { + break; + } + MDSectionOffset originalOffset = offset; + const char* name = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + offset += sizeof(uint16_t); + addSymbol(NativeApiSymbolKind::Struct, originalOffset, name); + addAggregateAliases(NativeApiSymbolKind::Struct, originalOffset, + name != nullptr ? name : ""); + skipAggregateFields(offset, false); + } + } + + void indexUnions() { + MDSectionOffset offset = metadata_->unionsOffset; + while (metadata_->getOffset(offset) != 0) { + MDSectionOffset originalOffset = offset; + const char* name = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + offset += sizeof(uint16_t); + addSymbol(NativeApiSymbolKind::Union, originalOffset, name); + addAggregateAliases(NativeApiSymbolKind::Union, originalOffset, + name != nullptr ? name : ""); + skipAggregateFields(offset, true); + } + } + + void skipMember(MDMemberFlag flags, MDSectionOffset& offset) const { + if ((flags & metagen::mdMemberProperty) != 0) { + bool readonly = (flags & metagen::mdMemberReadonly) != 0; + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + if (!readonly) { + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + } + return; + } + + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + } + + std::vector readProtocolOffsetsForClass( + MDSectionOffset classOffset, MDSectionOffset* memberOffset = nullptr, + MDSectionOffset* superclassOffsetOut = nullptr) const { + std::vector protocols; + if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { + return protocols; + } + + MDSectionOffset offset = classOffset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; + + while (hasProtocols) { + auto protocolOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + protocolOffset &= ~metagen::mdSectionOffsetNext; + if (protocolOffset != MD_SECTION_OFFSET_NULL) { + protocols.push_back(protocolOffset + metadata_->protocolsOffset); + } + } + + auto superclass = metadata_->getOffset(offset); + offset += sizeof(superclass); + const bool hasMembers = (superclass & metagen::mdSectionOffsetNext) != 0; + if (superclassOffsetOut != nullptr) { + MDSectionOffset superclassOffset = + superclass & ~metagen::mdSectionOffsetNext; + *superclassOffsetOut = + superclassOffset != MD_SECTION_OFFSET_NULL + ? superclassOffset + metadata_->classesOffset + : MD_SECTION_OFFSET_NULL; + } + if (memberOffset != nullptr) { + *memberOffset = hasMembers ? offset : MD_SECTION_OFFSET_NULL; + } + return protocols; + } + + std::vector readOwnMembersForClass( + MDSectionOffset classOffset) const { + std::vector members; + if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { + return members; + } + + MDSectionOffset memberOffset = MD_SECTION_OFFSET_NULL; + for (MDSectionOffset protocolOffset : + readProtocolOffsetsForClass(classOffset, &memberOffset)) { + auto protocol = protocolSymbolsByOffset_.find(protocolOffset); + if (protocol == protocolSymbolsByOffset_.end()) { + continue; + } + const auto& protocolMembers = membersForProtocol(protocol->second); + members.insert(members.end(), protocolMembers.begin(), + protocolMembers.end()); + } + + if (memberOffset != MD_SECTION_OFFSET_NULL) { + std::vector ownMembers = + readMembersAtOffset(memberOffset); + members.insert(members.end(), ownMembers.begin(), ownMembers.end()); + } + return members; + } + + std::vector readMembersForClass( + MDSectionOffset classOffset) const { + std::vector members; + if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { + return members; + } + + MDSectionOffset offset = classOffset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; + + while (hasProtocols) { + auto protocolOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + + auto superclass = metadata_->getOffset(offset); + offset += sizeof(superclass); + + bool next = (superclass & metagen::mdSectionOffsetNext) != 0; + while (next) { + auto flags = metadata_->getMemberFlag(offset); + next = (flags & metagen::mdMemberNext) != 0; + offset += sizeof(flags); + if (flags == metagen::mdMemberFlagNull) { + break; + } + + NativeApiMember member; + member.flags = flags; + if ((flags & metagen::mdMemberProperty) != 0) { + member.property = true; + member.readonly = (flags & metagen::mdMemberReadonly) != 0; + member.name = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.selectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.signatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + + if (!member.readonly) { + member.setterSelectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.setterSignatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + } + } else { + member.selectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.signatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + member.name = jsifySelector(member.selectorName.c_str()); + } + members.push_back(std::move(member)); + } + + return members; + } + + static bool memberIsStatic(const NativeApiMember& member) { + return (member.flags & metagen::mdMemberStatic) != 0; + } + + static bool sameMemberSlot(const NativeApiMember& lhs, + const NativeApiMember& rhs) { + return lhs.name == rhs.name && lhs.property == rhs.property && + memberIsStatic(lhs) == memberIsStatic(rhs); + } + + static bool sameMethodSelector(const NativeApiMember& lhs, + const NativeApiMember& rhs) { + return !lhs.property && !rhs.property && sameMemberSlot(lhs, rhs) && + lhs.selectorName == rhs.selectorName; + } + + static const NativeApiMember* findPropertyMember( + const std::vector& members, + const NativeApiMember& candidate) { + for (const auto& member : members) { + if (member.property && sameMemberSlot(member, candidate)) { + return &member; + } + } + return nullptr; + } + + static bool selectorExistsInMembers( + const std::vector& members, + const NativeApiMember& candidate) { + for (const auto& member : members) { + if (sameMethodSelector(member, candidate)) { + return true; + } + } + return false; + } + + static bool shouldSkipPropertyOverride( + const NativeApiMember* inherited, const NativeApiMember& member) { + if (inherited == nullptr || !inherited->property) { + return false; + } + + bool sameGetter = inherited->selectorName == member.selectorName; + bool sameSetter = + inherited->setterSelectorName == member.setterSelectorName; + if ((!inherited->readonly && member.readonly) || + (inherited->readonly == member.readonly && sameGetter && + (member.readonly || sameSetter))) { + return true; + } + return false; + } + + static void appendSurfaceMember( + std::vector& surface, + const std::vector& inheritedMembers, + const NativeApiMember& member) { + if (member.name.empty()) { + return; + } + + if (member.property) { + const NativeApiMember* inherited = + findPropertyMember(inheritedMembers, member); + if (shouldSkipPropertyOverride(inherited, member)) { + return; + } + + for (auto& existing : surface) { + if (!existing.property || !sameMemberSlot(existing, member)) { + continue; + } + if (existing.readonly && !member.readonly) { + existing = member; + } + return; + } + surface.push_back(member); + return; + } + + const bool keepInheritedMethod = + member.name == "alloc" || member.name == "toString" || + member.name == "superclass"; + if (!keepInheritedMethod && + selectorExistsInMembers(inheritedMembers, member)) { + return; + } + if (selectorExistsInMembers(surface, member)) { + return; + } + surface.push_back(member); + } + + std::vector readSurfaceMembersForClass( + const NativeApiSymbol& symbol) const { + std::vector inheritedMembers; + if (symbol.superclassOffset != MD_SECTION_OFFSET_NULL) { + auto superclass = classSymbolsByOffset_.find(symbol.superclassOffset); + if (superclass != classSymbolsByOffset_.end()) { + const auto& inherited = surfaceMembersForClass(superclass->second); + inheritedMembers.insert(inheritedMembers.end(), inherited.begin(), + inherited.end()); + } + } + + std::vector surface; + for (const auto& member : readOwnMembersForClass(symbol.offset)) { + appendSurfaceMember(surface, inheritedMembers, member); + } + return surface; + } + + std::vector readMembersAtOffset( + MDSectionOffset& offset) const { + std::vector members; + bool next = true; + while (next) { + auto flags = metadata_->getMemberFlag(offset); + next = (flags & metagen::mdMemberNext) != 0; + offset += sizeof(flags); + if (flags == metagen::mdMemberFlagNull) { + break; + } + + NativeApiMember member; + member.flags = flags; + if ((flags & metagen::mdMemberProperty) != 0) { + member.property = true; + member.readonly = (flags & metagen::mdMemberReadonly) != 0; + member.name = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.selectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.signatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + + if (!member.readonly) { + member.setterSelectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.setterSignatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + } + } else { + member.selectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.signatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + member.name = jsifySelector(member.selectorName.c_str()); + } + members.push_back(std::move(member)); + } + return members; + } + + std::vector readMembersForProtocolHierarchy( + MDSectionOffset protocolOffset) const { + std::vector members; + if (metadata_ == nullptr || protocolOffset == MD_SECTION_OFFSET_NULL) { + return members; + } + + MDSectionOffset offset = protocolOffset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; + + while (hasProtocols) { + auto inheritedOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (inheritedOffset & metagen::mdSectionOffsetNext) != 0; + inheritedOffset &= ~metagen::mdSectionOffsetNext; + if (inheritedOffset == MD_SECTION_OFFSET_NULL) { + continue; + } + + MDSectionOffset absoluteOffset = + inheritedOffset + metadata_->protocolsOffset; + auto inheritedSymbol = protocolSymbolsByOffset_.find(absoluteOffset); + if (inheritedSymbol != protocolSymbolsByOffset_.end()) { + const auto& inheritedMembers = + membersForProtocol(inheritedSymbol->second); + members.insert(members.end(), inheritedMembers.begin(), + inheritedMembers.end()); + } + } + + std::vector ownMembers = readMembersAtOffset(offset); + members.insert(members.end(), ownMembers.begin(), ownMembers.end()); + return members; + } + + std::vector readMembersForClassHierarchy( + const NativeApiSymbol& symbol) const { + std::vector members = readOwnMembersForClass(symbol.offset); + if (symbol.superclassOffset == MD_SECTION_OFFSET_NULL) { + return members; + } + + auto superclass = classSymbolsByOffset_.find(symbol.superclassOffset); + if (superclass != classSymbolsByOffset_.end()) { + const auto& inheritedMembers = membersForClass(superclass->second); + members.insert(members.end(), inheritedMembers.begin(), + inheritedMembers.end()); + } + return members; + } + + std::unique_ptr metadata_; + void* selfDl_ = nullptr; + std::unordered_map symbolsByName_; + std::unordered_map functionSymbolsByName_; + std::unordered_map constantSymbolsByName_; + std::unordered_map enumSymbolsByName_; + std::unordered_map structSymbolsByName_; + std::unordered_map unionSymbolsByName_; + std::unordered_map classSymbolsByRuntimeName_; + std::unordered_map protocolSymbolsByRuntimeName_; + std::unordered_map classSymbolsByRuntimePointer_; + std::unordered_map protocolSymbolsByRuntimePointer_; + mutable std::mutex roundTripValuesMutex_; + std::unordered_map roundTripValues_; + std::unordered_map + roundTripCacheFramesByThread_; + std::unordered_map + recentRoundTripValues_; + std::vector recentRoundTripValueOrder_; +#ifdef TARGET_ENGINE_HERMES + std::unordered_set rootedRoundTripValues_; + std::shared_ptr roundTripRootCache_; +#endif + std::atomic roundTripValuesGeneration_{1}; + std::unordered_map> classValues_; + std::unordered_map> classPrototypes_; + std::unordered_map> pointerValues_; + mutable std::mutex objectExpandosMutex_; + std::unordered_map< + uintptr_t, + std::unordered_map< + std::string, + std::unordered_map>>> + objectExpandos_; + std::unordered_map objectExpandoOwnerCounts_; + std::atomic objectExpandosGeneration_{1}; + std::unordered_map> + propertyGetterCache_; + std::atomic propertyGetterCacheGeneration_{1}; + std::unordered_map classSymbolsByOffset_; + std::unordered_map protocolSymbolsByOffset_; + std::vector classNames_; + std::vector functionNames_; + std::vector constantNames_; + std::vector protocolNames_; + std::vector enumNames_; + std::vector structNames_; + std::vector unionNames_; + std::shared_ptr scheduler_; + std::function)> nativeInvocationInvoker_; + std::function)> nativeCallbackInvoker_; + std::function)> runtimeCallbackInvoker_; + std::function)> jsThreadCallbackInvoker_; + std::function)> jsThreadAsyncCallbackInvoker_; + std::function callbackInvocationAllowed_; + bool invokeCallbacksOnNativeCallerThread_ = false; + bool indexRuntimePointers_ = true; + mutable std::unordered_map> + membersByClassOffset_; + mutable std::unordered_map> + surfaceMembersByClassOffset_; + mutable std::unordered_map> + membersByProtocolOffset_; + // Keyed by runtime Class pointer (not metadata offset -- see + // protocolMembersForRuntimeClass for why). Caches the protocol-conformance + // method-metadata fallback. + mutable std::unordered_map> + protocolMembersByRuntimeClass_; + std::unordered_map structSymbolsByOffset_; + std::unordered_map unionSymbolsByOffset_; + std::unordered_map> + aggregateInfoByOffset_; + std::unordered_set aggregateInfoInProgress_; + std::thread::id jsThreadId_ = std::this_thread::get_id(); + std::mutex retainedLifetimesMutex_; + std::vector> retainedLifetimes_; +}; + +class NativeApiRoundTripCacheFrameGuard final { + public: + explicit NativeApiRoundTripCacheFrameGuard( + const std::shared_ptr& bridge) + : bridge_(bridge) { + if (bridge_ != nullptr) { + bridge_->beginRoundTripCacheFrame(); + } + } + + ~NativeApiRoundTripCacheFrameGuard() { + if (bridge_ != nullptr) { + bridge_->endRoundTripCacheFrame(); + } + } + + NativeApiRoundTripCacheFrameGuard( + const NativeApiRoundTripCacheFrameGuard&) = delete; + NativeApiRoundTripCacheFrameGuard& operator=( + const NativeApiRoundTripCacheFrameGuard&) = delete; + + private: + std::shared_ptr bridge_; +}; + +template +void performGeneratedObjCInvocation( + Runtime& runtime, const std::shared_ptr& bridge, + Invocation&& invocation) { + const auto& invoker = bridge->nativeInvocationInvoker(); + if (invoker || bridge->invokeCallbacksOnNativeCallerThread()) { + performNativeInvocation(runtime, invoker, [&]() { invocation(); }); + } else { + invocation(); + } +} + +bool nativeObjectReturnMayCoerceToString(const NativeApiType& type) { + return type.kind == metagen::mdTypeAnyObject || + type.kind == metagen::mdTypeNSStringObject; +} + +// Guards against treating a misread register value as an object pointer: +// low addresses can't be valid ObjC objects, but a register holding e.g. an +// unboxed integer or a non-object primitive read as `id` can land there. +// Without this guard, dereferencing it (object_getClass, isKindOfClass:, +// etc. below) can crash on garbage. Do not remove — this is what stops +// crashes on AnyObject-typed returns from selectors whose actual return +// isn't an object. +bool nativeObjectPointerMayBeObject(id object) { + if (object == nil) { + return false; + } + + const uintptr_t raw = reinterpret_cast(object); + return raw > 0x1000; +} + +bool nativeObjectIsStringLike(id object) { + if (!nativeObjectPointerMayBeObject(object)) { + return false; + } + Class cls = object_getClass(object); + struct StringLikeClassCacheEntry { + Class cls = Nil; + bool stringLike = false; + }; + static thread_local StringLikeClassCacheEntry cache[4]; + const size_t firstSlot = (reinterpret_cast(cls) >> 4) & 3; + for (size_t i = 0; i < 4; i++) { + const auto& entry = cache[(firstSlot + i) & 3]; + if (entry.cls == cls) { + return entry.stringLike; + } + } + bool stringLike = [object isKindOfClass:[NSString class]]; + cache[firstSlot] = StringLikeClassCacheEntry{cls, stringLike}; + return stringLike; +} + +Value findCachedNativeObjectReturn(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, id object) { + if (!nativeObjectPointerMayBeObject(object)) { + return Value::undefined(); + } + + bool roundTripStringLike = false; + const bool stringReturnCandidate = nativeObjectReturnMayCoerceToString(type); + // AnyObject/NSString returns intentionally coerce string-like native objects + // to JS strings, so cached identity is only valid for non-string wrappers. + if (stringReturnCandidate && nativeObjectIsStringLike(object)) { + return Value::undefined(); + } + Value roundTrip = bridge->findRoundTripValue( + runtime, object, stringReturnCandidate ? &roundTripStringLike : nullptr, + true); + if (!roundTrip.isUndefined() && !roundTripStringLike) { + return roundTrip; + } + return Value::undefined(); +} + +Value makeString(Runtime& runtime, const std::string& value) { + return String::createFromUtf8(runtime, value); +} + +std::string readStringArg(Runtime& runtime, const Value* args, size_t count, + size_t index, const char* argumentName) { + if (index >= count || !args[index].isString()) { + throw JSError( + runtime, std::string(argumentName) + " must be a string."); + } + return args[index].asString(runtime).utf8(runtime); +} + +const char* kindName(NativeApiSymbolKind kind) { + switch (kind) { + case NativeApiSymbolKind::Class: + return "class"; + case NativeApiSymbolKind::Function: + return "function"; + case NativeApiSymbolKind::Constant: + return "constant"; + case NativeApiSymbolKind::Protocol: + return "protocol"; + case NativeApiSymbolKind::Enum: + return "enum"; + case NativeApiSymbolKind::Struct: + return "struct"; + case NativeApiSymbolKind::Union: + return "union"; + } + return "unknown"; +} + +Array namesToArray(Runtime& runtime, const std::vector& names) { + Array result(runtime, names.size()); + for (size_t i = 0; i < names.size(); i++) { + result.setValueAtIndex(runtime, i, makeString(runtime, names[i])); + } + return result; +} + +void addPropertyName(Runtime& runtime, std::vector& names, + const char* name) { + names.push_back(PropNameID::forAscii(runtime, name)); +} + +class NativeApiPointerHostObject; +class NativeApiObjectHostObject; +class NativeApiClassHostObject; +class NativeApiProtocolHostObject; +class NativeApiArgumentFrame; +struct NativeApiPreparedCFunctionInvocation; +struct NativeApiPreparedObjCInvocation; + +Value callCFunction(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiSymbol& symbol, const Value* args, + size_t count); +Value callCFunction( + Runtime& runtime, const std::shared_ptr& bridge, + const std::shared_ptr& prepared, + const Value* args, size_t count); + +Value callObjCSelector(Runtime& runtime, + const std::shared_ptr& bridge, + id receiver, bool receiverIsClass, + const std::string& selectorName, + const NativeApiMember* member, + const Value* args, size_t count, + Class dispatchSuperClass = Nil); + +std::shared_ptr +prepareNativeApiObjCInvocation( + Runtime& runtime, const std::shared_ptr& bridge, + Class lookupClass, bool receiverIsClass, const std::string& selectorName, + const NativeApiMember* member); + +Value callPreparedObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, bool receiverIsClass, + const NativeApiPreparedObjCInvocation& prepared, const Value* args, + size_t count, Class dispatchSuperClass = Nil); + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId); +bool tryCallGeneratedEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result); + +Function CreateNativeApiSelectorGroupFunction( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations); + +Function CreateNativeApiBoundSelectorGroupFunction( + Runtime& runtime, std::shared_ptr bridge, Class lookupClass, + std::shared_ptr receiverHostObject, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations); + +Value makeNativeObjectValue(Runtime& runtime, + const std::shared_ptr& bridge, + id object, bool ownsObject, + Class superDispatchClass = Nil); + +Value makeNativeClassValue(Runtime& runtime, + const std::shared_ptr& bridge, + NativeApiSymbol symbol); + +Object symbolToObject(Runtime& runtime, const NativeApiSymbol& symbol) { + Object result(runtime); + result.setProperty(runtime, "kind", makeString(runtime, kindName(symbol.kind))); + result.setProperty(runtime, "name", makeString(runtime, symbol.name)); + result.setProperty(runtime, "runtimeName", + makeString(runtime, symbol.runtimeName)); + result.setProperty(runtime, "metadataOffset", + static_cast(symbol.offset)); + + if (symbol.kind == NativeApiSymbolKind::Class) { + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + result.setProperty(runtime, "available", cls != nil); + if (cls != nil) { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", cls); + result.setProperty(runtime, "nativeAddress", makeString(runtime, address)); + } + } else if (symbol.kind == NativeApiSymbolKind::Struct || + symbol.kind == NativeApiSymbolKind::Union) { + result.setProperty(runtime, "available", true); + } + + return result; +} + +size_t nativeSizeForType(const NativeApiType& type); +std::optional parseArrayIndexProperty(const std::string& property); + +NativeApiType nativeObjectReturnType( + MDTypeKind kind = metagen::mdTypeAnyObject) { + NativeApiType type; + type.kind = kind; + type.ffiType = &ffi_type_pointer; + type.supported = true; + return type; +} + +NativeApiType nativeObjectReturnTypeForClass(Class cls) { + if (cls != Nil) { + const char* name = class_getName(cls); + if (name != nullptr && std::strcmp(name, "NSString") == 0) { + return nativeObjectReturnType(metagen::mdTypeNSStringObject); + } + if (name != nullptr && std::strcmp(name, "NSMutableString") == 0) { + return nativeObjectReturnType(metagen::mdTypeNSMutableStringObject); + } + } + return nativeObjectReturnType(metagen::mdTypeInstanceObject); +} + +Value convertNativeReturnValue(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, void* value); +Object createPointer(Runtime& runtime, + const std::shared_ptr& bridge, + void* pointer, bool adopted = false, + std::shared_ptr backingValue = nullptr); + +NativeApiType primitiveInteropType(MDTypeKind kind); diff --git a/NativeScript/ffi/objc/shared/bridge/SelectorGroupCall.h b/NativeScript/ffi/objc/shared/bridge/SelectorGroupCall.h new file mode 100644 index 000000000..907aef1cb --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/SelectorGroupCall.h @@ -0,0 +1,213 @@ +#pragma once + +#include "SelectorGroupState.h" + +struct NativeApiResolvedSelectorGroupCall { + id receiver = nil; + NativeApiPreparedObjCInvocation* prepared = nullptr; + std::shared_ptr receiverHostObject; + std::optional initializerClassWrapper; + Class dispatchClass = Nil; + bool hasImmediateResult = false; + Value immediateResult; + // False when the prepared invocation is a `[SomeClass appearance...]` + // static selector: the GSD fast path bypasses tagStaticAppearance*, so it + // must be excluded from GSD eligibility to keep proxy tagging/caching + // working. + bool gsdAllowed = true; +}; + +template +inline NativeApiResolvedSelectorGroupCall resolveNativeApiSelectorGroupCall( + Runtime& runtime, NativeApiSelectorGroupState& data, size_t argumentCount, + ResolveReceiver&& resolveReceiver, + ResolveReceiverHost&& resolveReceiverHost, + LookupGsdInvoker&& lookupGsdInvoker) { + if (argumentCount >= data.selectors->size() || + (*data.selectors)[argumentCount].selectorName.empty()) { + throw JSError( + runtime, + "Objective-C selector is not available for the provided arguments count."); + } + + NativeApiResolvedSelectorGroupCall result; + NativeApiSelectorGroupEntry& entry = (*data.selectors)[argumentCount]; + auto& prepared = (*data.preparedInvocations)[argumentCount]; + Class selectorLookupClass = data.lookupClass; + result.receiver = + data.receiverIsClass ? static_cast(data.lookupClass) : nil; + if (!data.receiverIsClass) { + result.receiver = data.boundReceiverState != nullptr + ? data.boundReceiverState->object() + : nil; + if (result.receiver == nil) { + // Either this call is unbound (the common case -- `resolveReceiver()` + // resolves the live `thisValue`), OR it IS bound but the bound + // receiver's `NativeApiObjectHostObject` wrapper has already been torn + // down: the cached selector-group function itself survives as a + // native-object expando (keyed by the native pointer, see Object.mm's + // `bridge_->setObjectExpando(..., methodFunction)`), which outlives the + // specific wrapper instance it was bound to when this runtime later + // mints a FRESH wrapper for the same native pointer on another + // crossing. Re-resolve from the actual call-site receiver in both + // cases -- for a bound call this is exactly the live object the method + // is being invoked on right now, so it is always correct. + result.receiver = resolveReceiver(); + } + } + if (result.receiver == nil) { + throw JSError(runtime, + "Objective-C selector requires a native receiver."); + } + + const bool propertyGetterCall = + entry.hasMember && entry.member.property && argumentCount == 0; + if (propertyGetterCall) { + Value appearanceExpando = cachedAppearanceProxyPropertyValue( + runtime, data.bridge, result.receiver, entry.member.name); + if (!appearanceExpando.isUndefined()) { + result.immediateResult = std::move(appearanceExpando); + result.hasImmediateResult = true; + return result; + } + } + const std::string* selectorNamePtr = &entry.selectorName; + const NativeApiMember* selectedMember = + entry.hasMember ? &entry.member : nullptr; + bool callTargetCanPrepare = true; + if (prepared == nullptr || propertyGetterCall) { + NativeApiSelectorGroupCallTarget callTarget = + selectorGroupCallTargetForEntry( + result.receiver, selectorLookupClass, data.receiverIsClass, entry, + argumentCount); + selectorNamePtr = callTarget.selectorName; + selectedMember = callTarget.member; + callTargetCanPrepare = callTarget.canPrepare; + if (prepared != nullptr && prepared->selectorName != *selectorNamePtr) { + prepared = nullptr; + } + } + const std::string& selectorName = + prepared != nullptr && !propertyGetterCall ? prepared->selectorName + : *selectorNamePtr; + + if (data.receiverIsClass) { + Class methodClass = prepared != nullptr ? prepared->receiverClass : Nil; + if (methodClass == Nil) { + SEL selector = sel_registerName(selectorName.c_str()); + methodClass = NativeApiClassHostObject::classRespondingToClassSelector( + data.lookupClass, selector); + } + if (methodClass == Nil) { + throw JSError(runtime, + "Objective-C selector is not available: " + + entry.selectorName); + } + selectorLookupClass = methodClass; + result.receiver = static_cast(methodClass); + } + if (propertyGetterCall && !callTargetCanPrepare) { + result.immediateResult = + callObjCSelector(runtime, data.bridge, result.receiver, + data.receiverIsClass, selectorName, selectedMember, + nullptr, 0); + result.hasImmediateResult = true; + return result; + } + + if (prepared == nullptr) { + if (!data.receiverIsClass) { + SEL selector = sel_registerName(selectorName.c_str()); + if (class_getInstanceMethod(selectorLookupClass, selector) == nullptr) { + Class receiverClass = object_getClass(result.receiver); + if (class_getInstanceMethod(receiverClass, selector) != nullptr) { + selectorLookupClass = receiverClass; + } + } + } + prepared = prepareNativeApiObjCInvocation( + runtime, data.bridge, selectorLookupClass, data.receiverIsClass, + selectorName, selectedMember); + if (prepared->engineInvoker == nullptr) { + uint64_t dispatchId = dispatchIdForEngineSignature( + prepared->signature, SignatureCallKind::ObjCMethod); + if (auto gsdInvoker = lookupGsdInvoker(dispatchId)) { + prepared->engineInvoker = reinterpret_cast(gsdInvoker); + configureGeneratedEngineObjCInvocation(*prepared); + } + } + } + result.prepared = prepared.get(); + result.gsdAllowed = !isPreparedStaticAppearanceSelector(*prepared); + + if constexpr (PrepareInitializer) { + if (!data.receiverIsClass && prepared->isInitMethod) { + if (data.boundReceiverState != nullptr) { + result.receiverHostObject = data.boundReceiver.lock(); + } + if (!result.receiverHostObject) { + result.receiverHostObject = resolveReceiverHost(); + } + Value classWrapperValue = data.bridge->findObjectExpando( + runtime, result.receiver, "__nativeApiClassWrapper"); + if (classWrapperValue.isObject()) { + result.initializerClassWrapper.emplace( + classWrapperValue.asObject(runtime)); + } + data.bridge->forgetRoundTripValue(result.receiver); + data.bridge->forgetObjectExpandos(result.receiver); + } + } + + if (!data.receiverIsClass) { + Class receiverClass = object_getClass(result.receiver); + if (receiverClass == data.cachedReceiverClass) { + result.dispatchClass = data.cachedDispatchClass; + } else { + result.dispatchClass = dispatchSuperclassForEngineDerivedReceiver( + result.receiver, data.lookupClass); + data.cachedReceiverClass = receiverClass; + data.cachedDispatchClass = result.dispatchClass; + } + } + return result; +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState); + +inline Function CreateNativeApiSelectorGroupFunction( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations) { + return CreateNativeApiSelectorGroupFunctionImpl( + runtime, std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), {}, nullptr); +} + +inline Function CreateNativeApiBoundSelectorGroupFunction( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, + std::shared_ptr receiverHostObject, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations) { + return CreateNativeApiSelectorGroupFunctionImpl( + runtime, std::move(bridge), lookupClass, false, std::move(selectors), + std::move(preparedInvocations), receiverHostObject, + receiverHostObject != nullptr ? receiverHostObject->lifetimeState() + : nullptr); +} diff --git a/NativeScript/ffi/objc/shared/bridge/SelectorGroupData.h b/NativeScript/ffi/objc/shared/bridge/SelectorGroupData.h new file mode 100644 index 000000000..d82878aa8 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/SelectorGroupData.h @@ -0,0 +1,24 @@ +#pragma once + +#include "SelectorGroupState.h" + +struct NativeApiSelectorGroupData : NativeApiSelectorGroupState { + template + NativeApiSelectorGroupData( + std::shared_ptr state, + std::shared_ptr bridge, Class lookupClass, + bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver = {}, + std::shared_ptr boundReceiverState = nullptr) + : NativeApiSelectorGroupState( + std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), + std::move(boundReceiver), std::move(boundReceiverState)), + runtime(std::move(state)) {} + + Runtime runtime; +}; diff --git a/NativeScript/ffi/objc/shared/bridge/SelectorGroupState.h b/NativeScript/ffi/objc/shared/bridge/SelectorGroupState.h new file mode 100644 index 000000000..175988210 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/SelectorGroupState.h @@ -0,0 +1,32 @@ +#pragma once + +struct NativeApiSelectorGroupState { + NativeApiSelectorGroupState( + std::shared_ptr bridge, Class lookupClass, + bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver = {}, + std::shared_ptr boundReceiverState = nullptr) + : bridge(std::move(bridge)), + lookupClass(lookupClass), + receiverIsClass(receiverIsClass), + selectors(std::move(selectors)), + preparedInvocations(std::move(preparedInvocations)), + boundReceiver(std::move(boundReceiver)), + boundReceiverState(std::move(boundReceiverState)) {} + + std::shared_ptr bridge; + Class lookupClass = Nil; + bool receiverIsClass = false; + std::shared_ptr> selectors; + std::shared_ptr< + std::vector>> + preparedInvocations; + std::weak_ptr boundReceiver; + std::shared_ptr boundReceiverState; + Class cachedReceiverClass = Nil; + Class cachedDispatchClass = Nil; +}; diff --git a/NativeScript/ffi/objc/shared/bridge/TypeConv.mm b/NativeScript/ffi/objc/shared/bridge/TypeConv.mm new file mode 100644 index 000000000..37b2c35e1 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/TypeConv.mm @@ -0,0 +1,2402 @@ +std::string stringPropertyOrEmpty(Runtime& runtime, const Object& object, const char* name); +void* pointerFromSymbolLikeObject(Runtime& runtime, const Object& object); + +class NativeApiObjectConversionStack final { + public: + bool contains(Runtime& runtime, const Value& value) const { + for (const auto& active : activeValues_) { + if (Value::strictEquals(runtime, active, value)) { + return true; + } + } + return false; + } + + void push(Runtime& runtime, const Value& value) { + activeValues_.emplace_back(runtime, value); + } + + void pop() { activeValues_.pop_back(); } + + private: + std::vector activeValues_; +}; + +class NativeApiObjectConversionGuard final { + public: + NativeApiObjectConversionGuard(Runtime& runtime, const Value& value, + NativeApiObjectConversionStack& stack) + : stack_(stack) { + if (stack_.contains(runtime, value)) { + throw JSError( + runtime, + "Circular JavaScript object graphs cannot be converted to Objective-C collections."); + } + stack_.push(runtime, value); + } + + ~NativeApiObjectConversionGuard() { stack_.pop(); } + + private: + NativeApiObjectConversionStack& stack_; +}; + +id objectFromEngineValueImpl( + Runtime& runtime, const std::shared_ptr& bridge, + const Value& value, NativeApiArgumentFrame& frame, bool mutableString, + NativeApiObjectConversionStack& conversionStack) { + if (value.isNull() || value.isUndefined()) { + return nil; + } + if (value.isString()) { + std::string utf8 = value.asString(runtime).utf8(runtime); + id string = mutableString ? [[NSMutableString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding] + : [[NSString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding]; + frame.addObject(string); + return string; + } + if (value.isBool()) { + return [NSNumber numberWithBool:value.getBool()]; + } + if (value.isNumber()) { + return [NSNumber numberWithDouble:value.getNumber()]; + } + if (value.isObject()) { + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->object(); + } + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + return static_cast(cls); + } + if (object.isHostObject(runtime)) { + return static_cast( + object.getHostObject(runtime)->nativeProtocol()); + } + if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { + return static_cast(symbolPointer); + } + if (object.isHostObject(runtime)) { + return static_cast(object.getHostObject(runtime)->pointer()); + } + if (object.isHostObject(runtime)) { + return static_cast(object.getHostObject(runtime)->data()); + } + if (auto structObject = getNativeStructHostObject(runtime, object)) { + return static_cast(structObject->data()); + } + + Value getTimeValue = object.getProperty(runtime, "getTime"); + Value toISOStringValue = object.getProperty(runtime, "toISOString"); + if (getTimeValue.isObject() && getTimeValue.asObject(runtime).isFunction(runtime) && + toISOStringValue.isObject() && toISOStringValue.asObject(runtime).isFunction(runtime)) { + Value millisValue = getTimeValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (millisValue.isNumber()) { + NSDate* date = [NSDate dateWithTimeIntervalSince1970:millisValue.getNumber() / 1000.0]; + bridge->rememberScopedRoundTripValue(runtime, date, value, false, true); + return date; + } + } + + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + Value primitiveValue = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (primitiveValue.isString() || primitiveValue.isBool() || primitiveValue.isNumber()) { + return objectFromEngineValueImpl(runtime, bridge, primitiveValue, frame, + mutableString, conversionStack); + } + } + + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + NSData* data = [NSData dataWithBytes:bytes length:byteLength]; + bridge->rememberScopedRoundTripValue(runtime, data, value, false, false); + return data; + } + + NativeApiObjectConversionGuard conversionGuard(runtime, value, + conversionStack); + + if (object.isArray(runtime)) { + Array array = object.getArray(runtime); + NSMutableArray* nativeArray = [NSMutableArray arrayWithCapacity:array.size(runtime)]; + for (size_t i = 0; i < array.size(runtime); i++) { + id element = + objectFromEngineValueImpl(runtime, bridge, + array.getValueAtIndex(runtime, i), frame, + false, conversionStack); + [nativeArray addObject:element != nil ? element : [NSNull null]]; + } + bridge->rememberScopedRoundTripValue(runtime, nativeArray, value, false, false); + return nativeArray; + } + + Value lengthValue = object.getProperty(runtime, "length"); + if (lengthValue.isNumber() && std::isfinite(lengthValue.getNumber()) && + lengthValue.getNumber() >= 0) { + size_t length = static_cast(std::floor(lengthValue.getNumber())); + NSMutableArray* nativeArray = [NSMutableArray arrayWithCapacity:length]; + for (size_t i = 0; i < length; i++) { + std::string key = std::to_string(i); + id element = objectFromEngineValueImpl( + runtime, bridge, object.getProperty(runtime, key.c_str()), frame, + false, conversionStack); + [nativeArray addObject:element != nil ? element : [NSNull null]]; + } + bridge->rememberScopedRoundTripValue(runtime, nativeArray, value, false, false); + return nativeArray; + } + + Value entriesValue = object.getProperty(runtime, "entries"); + Value sizeValue = object.getProperty(runtime, "size"); + Value getValue = object.getProperty(runtime, "get"); + if (entriesValue.isObject() && entriesValue.asObject(runtime).isFunction(runtime) && + sizeValue.isNumber() && getValue.isObject() && + getValue.asObject(runtime).isFunction(runtime)) { + Object arrayCtor = runtime.global().getPropertyAsObject(runtime, "Array"); + Function arrayFrom = arrayCtor.getPropertyAsFunction(runtime, "from"); + Value iterator = entriesValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + Value pairsValue = arrayFrom.call(runtime, iterator); + if (pairsValue.isObject() && pairsValue.asObject(runtime).isArray(runtime)) { + Array pairs = pairsValue.asObject(runtime).getArray(runtime); + NSMutableDictionary* nativeMap = + [NSMutableDictionary dictionaryWithCapacity:pairs.size(runtime)]; + for (size_t i = 0; i < pairs.size(runtime); i++) { + Value pairValue = pairs.getValueAtIndex(runtime, i); + if (!pairValue.isObject() || !pairValue.asObject(runtime).isArray(runtime)) { + continue; + } + Array pair = pairValue.asObject(runtime).getArray(runtime); + if (pair.size(runtime) < 2) { + continue; + } + id key = objectFromEngineValueImpl( + runtime, bridge, pair.getValueAtIndex(runtime, 0), frame, false, + conversionStack); + id nativeValue = objectFromEngineValueImpl( + runtime, bridge, pair.getValueAtIndex(runtime, 1), frame, false, + conversionStack); + if (key != nil) { + [nativeMap setObject:nativeValue != nil ? nativeValue : [NSNull null] forKey:key]; + } + } + bridge->rememberScopedRoundTripValue(runtime, nativeMap, value, false, false); + return nativeMap; + } + } + + NSMutableDictionary* dictionary = [NSMutableDictionary dictionary]; + Array propertyNames = object.getPropertyNames(runtime); + for (size_t i = 0; i < propertyNames.size(runtime); i++) { + Value propertyNameValue = propertyNames.getValueAtIndex(runtime, i); + if (!propertyNameValue.isString()) { + continue; + } + std::string key = propertyNameValue.asString(runtime).utf8(runtime); + Value propertyValue = object.getProperty(runtime, key.c_str()); + if (propertyValue.isUndefined()) { + continue; + } + id nativeValue = objectFromEngineValueImpl( + runtime, bridge, propertyValue, frame, false, conversionStack); + NSString* nativeKey = [NSString stringWithUTF8String:key.c_str()]; + if (nativeKey != nil) { + [dictionary setObject:nativeValue != nil ? nativeValue : [NSNull null] forKey:nativeKey]; + } + } + bridge->rememberScopedRoundTripValue(runtime, dictionary, value, false, false); + return dictionary; + } + throw JSError(runtime, "Value cannot be converted to Objective-C object."); +} + +id objectFromEngineValue(Runtime& runtime, + const std::shared_ptr& bridge, + const Value& value, NativeApiArgumentFrame& frame, + bool mutableString) { + NativeApiObjectConversionStack conversionStack; + return objectFromEngineValueImpl(runtime, bridge, value, frame, + mutableString, conversionStack); +} + +std::string utf8StringFromNSString(NSString* string) { + if (string == nil) { + return ""; + } + NSUInteger length = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + std::string result(length, '\0'); + NSUInteger usedLength = 0; + NSRange remainingRange = NSMakeRange(0, 0); + BOOL ok = [string getBytes:result.data() + maxLength:length + usedLength:&usedLength + encoding:NSUTF8StringEncoding + options:0 + range:NSMakeRange(0, string.length) + remainingRange:&remainingRange]; + if (!ok) { + return string.UTF8String ?: ""; + } + result.resize(usedLength); + return result; +} + +char* copyCStringForReference(const char* string, size_t* byteLength = nullptr) { + size_t length = string != nullptr ? std::strlen(string) + 1 : 1; + char* copy = static_cast(malloc(length)); + if (copy == nullptr) { + throw std::bad_alloc(); + } + if (string != nullptr) { + std::memcpy(copy, string, length); + } else { + copy[0] = '\0'; + } + if (byteLength != nullptr) { + *byteLength = length; + } + return copy; +} + +bool readNativePointerProperty(Runtime& runtime, const Object& object, void** pointer) { + if (pointer == nullptr) { + return false; + } + + Value nativePointerObjectValue = object.getProperty(runtime, "__nativeApiPointerObject"); + if (nativePointerObjectValue.isObject()) { + Object nativePointerObject = nativePointerObjectValue.asObject(runtime); + if (nativePointerObject.isHostObject(runtime)) { + *pointer = nativePointerObject.getHostObject(runtime)->pointer(); + return true; + } + } + + Value nativePointerValue = object.getProperty(runtime, "__nativeApiPointer"); + if (nativePointerValue.isNumber()) { + *pointer = reinterpret_cast(static_cast(nativePointerValue.getNumber())); + return true; + } + + Value nativeAddressValue = object.getProperty(runtime, "nativeAddress"); + if (nativeAddressValue.isNumber()) { + *pointer = reinterpret_cast(static_cast(nativeAddressValue.getNumber())); + return true; + } + + return false; +} + +std::string stringPropertyOrEmpty(Runtime& runtime, const Object& object, const char* name) { + if (name == nullptr || !object.hasProperty(runtime, name)) { + return ""; + } + Value value = object.getProperty(runtime, name); + return value.isString() ? value.asString(runtime).utf8(runtime) : ""; +} + +constexpr const char* kNativeApiCallbackEncodingProperty = "__nativeApiCallbackEncoding"; + +Function interopCallbackFromArguments(Runtime& runtime, const char* constructorName, + const char* kind, const Value* args, size_t count) { + if (count < 1) { + throw JSError(runtime, std::string(constructorName) + " expects a function."); + } + + std::optional callbackObject; + bool hasCallback = false; + std::string encoding; + + for (size_t i = 0; i < count; i++) { + const Value& arg = args[i]; + if (arg.isUndefined() || arg.isNull()) { + continue; + } + if (arg.isString()) { + if (!encoding.empty()) { + throw JSError(runtime, std::string(constructorName) + + " expects only one Objective-C encoding string."); + } + encoding = arg.asString(runtime).utf8(runtime); + continue; + } + if (arg.isObject()) { + Object object = arg.asObject(runtime); + if (object.isFunction(runtime)) { + if (hasCallback) { + throw JSError(runtime, std::string(constructorName) + " expects only one function."); + } + callbackObject.emplace(std::move(object)); + hasCallback = true; + continue; + } + } + + throw JSError(runtime, std::string(constructorName) + + " expects a function and an optional Objective-C encoding string."); + } + + if (!hasCallback) { + throw JSError(runtime, std::string(constructorName) + " expects a function."); + } + + Function function = callbackObject->asFunction(runtime); + function.setProperty(runtime, "kind", makeString(runtime, kind)); + function.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + if (!encoding.empty()) { + function.setProperty(runtime, kNativeApiCallbackEncodingProperty, + makeString(runtime, encoding)); + } + + return function; +} + +void* pointerFromSymbolLikeObject(Runtime& runtime, const Object& object) { + std::string kind = stringPropertyOrEmpty(runtime, object, "kind"); + if (kind != "class" && kind != "protocol") { + return nullptr; + } + + std::string runtimeName = stringPropertyOrEmpty(runtime, object, "runtimeName"); + if (runtimeName.empty()) { + runtimeName = stringPropertyOrEmpty(runtime, object, "name"); + } + if (runtimeName.empty()) { + return nullptr; + } + + if (kind == "class") { + return objc_lookUpClass(runtimeName.c_str()); + } + return lookupProtocolByNativeName(runtimeName); +} + +void* pointerFromEngineValue(Runtime& runtime, const std::shared_ptr& bridge, + const Value& value, NativeApiArgumentFrame& frame) { + if (value.isNull() || value.isUndefined()) { + return nullptr; + } + if (value.isNumber()) { + return reinterpret_cast(static_cast(value.getNumber())); + } + if (value.isObject()) { + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->pointer(); + } + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->object(); + } + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + return cls; + } + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->nativeProtocol(); + } + if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { + return symbolPointer; + } + if (object.isHostObject(runtime)) { + auto reference = object.getHostObject(runtime); + if (reference->data() == nullptr) { + reference->ensureStorage(runtime, reference->type(), frame); + } + return reference->data(); + } + if (auto structObject = getNativeStructHostObject(runtime, object)) { + return structObject->data(); + } + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + return nativePointer; + } + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + return const_cast(bytes); + } + } + if (value.isString()) { + std::string utf8 = value.asString(runtime).utf8(runtime); + char* string = strdup(utf8.c_str()); + if (string == nullptr) { + throw std::bad_alloc(); + } + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, string, value, true, false); + } + frame.addCString(string); + return string; + } + throw JSError(runtime, "Value cannot be converted to pointer."); +} + +bool readPointerLikeValue(Runtime& runtime, const Value& value, void** pointer) { + if (pointer == nullptr || !value.isObject()) { + return false; + } + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + *pointer = object.getHostObject(runtime)->pointer(); + return true; + } + if (object.isHostObject(runtime)) { + *pointer = object.getHostObject(runtime)->data(); + return true; + } + if (auto structObject = getNativeStructHostObject(runtime, object)) { + *pointer = structObject->data(); + return true; + } + if (object.isHostObject(runtime)) { + *pointer = object.getHostObject(runtime)->object(); + return true; + } + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + *pointer = cls; + return true; + } + if (object.isHostObject(runtime)) { + *pointer = object.getHostObject(runtime)->nativeProtocol(); + return true; + } + if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { + *pointer = symbolPointer; + return true; + } + return readNativePointerProperty(runtime, object, pointer); +} + +// interop.set/getAssociatedObject's target parameter accepts either a live +// wrapped native object/pointer, or (for cases the engine can't hold a +// reference to, e.g. round-tripping a raw address from logs/debugging) the +// decimal text of a pointer value. +id nativeAssociatedObjectTargetFromValue(Runtime& runtime, const Value& value) { + if (value.isNull() || value.isUndefined()) { + return nil; + } + + if (value.isString()) { + uintptr_t address = 0; + if (!parseIntegerTextToUintptr(value.asString(runtime).utf8(runtime), &address)) { + throw JSError(runtime, "Associated object target expects a native object or object pointer."); + } + return static_cast(reinterpret_cast(address)); + } + + if (!value.isObject()) { + throw JSError(runtime, "Associated object target expects a native object or object pointer."); + } + + void* pointer = nullptr; + if (readPointerLikeValue(runtime, value, &pointer)) { + return static_cast(pointer); + } + + throw JSError(runtime, "Associated object target expects a native object or object pointer."); +} + +objc_AssociationPolicy associatedObjectPolicyFromValue(Runtime& runtime, const Value& value) { + if (value.isUndefined() || value.isNull()) { + return OBJC_ASSOCIATION_RETAIN_NONATOMIC; + } + + if (value.isNumber()) { + return static_cast(static_cast(value.getNumber())); + } + + if (!value.isString()) { + throw JSError(runtime, "Associated object policy expects a string or numeric objc_AssociationPolicy."); + } + + std::string policy = value.asString(runtime).utf8(runtime); + if (policy == "assign") { + return OBJC_ASSOCIATION_ASSIGN; + } + if (policy == "retain") { + return OBJC_ASSOCIATION_RETAIN; + } + if (policy == "retainNonatomic" || policy == "strong" || policy == "strongNonatomic") { + return OBJC_ASSOCIATION_RETAIN_NONATOMIC; + } + if (policy == "copy") { + return OBJC_ASSOCIATION_COPY; + } + if (policy == "copyNonatomic") { + return OBJC_ASSOCIATION_COPY_NONATOMIC; + } + + throw JSError(runtime, "Unknown associated object policy."); +} + +template +void writeNumericArgument(Runtime& runtime, const Value& value, void* target, + const char* typeName) { + const Value* numericValue = &value; + Value primitiveValue = Value::undefined(); + if (value.isObject()) { + Object object = value.asObject(runtime); + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + primitiveValue = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + numericValue = &primitiveValue; + } + } + + if (!numericValue->isNumber() && !numericValue->isBool()) { + throw JSError(runtime, std::string("Expected numeric ") + typeName + " argument."); + } + double number = + numericValue->isBool() ? (numericValue->getBool() ? 1.0 : 0.0) : numericValue->getNumber(); + *static_cast(target) = static_cast(number); +} + +void convertEngineArgument(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame); + +Value convertNativeReturnValue(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, void* value); + +Class classFromEngineValue(Runtime& runtime, const Value& value); +Protocol* protocolFromEngineValue(Runtime& runtime, const Value& value); + +bool valueIsNativeObjectHostObject(Runtime& runtime, const Value& value) { + if (!value.isObject()) { + return false; + } + return value.asObject(runtime).isHostObject(runtime); +} + +bool nativeTypeStoresObjectiveCObject(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return true; + default: + return false; + } +} + +void NativeApiReferenceHostObject::retainObjectSlot(size_t index, id object) { + if (retainedObjects_.size() <= index) { + retainedObjects_.resize(index + 1, nil); + } + id previous = retainedObjects_[index]; + if (previous == object) { + return; + } + [object retain]; + retainedObjects_[index] = object; + [previous release]; +} + +std::optional parseArrayIndexProperty(const std::string& property) { + if (property.empty()) { + return std::nullopt; + } + size_t index = 0; + for (char c : property) { + if (!std::isdigit(static_cast(c))) { + return std::nullopt; + } + size_t digit = static_cast(c - '0'); + if (index > (std::numeric_limits::max() - digit) / 10) { + return std::nullopt; + } + index = (index * 10) + digit; + } + return index; +} + +size_t referenceElementStride(const NativeApiType& type) { + return std::max(nativeSizeForType(type), 1); +} + +void convertAggregateArgument(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame) { + size_t size = nativeSizeForType(type); + if (size == 0) { + return; + } + + std::memset(target, 0, size); + if (value.isNull() || value.isUndefined()) { + return; + } + + if (value.isObject()) { + Object object = value.asObject(runtime); + if (auto structObject = getNativeStructHostObject(runtime, object)) { + if (structObject->data() != nullptr) { + std::memcpy(target, structObject->data(), + std::min(size, static_cast(structObject->info()->size))); + } + return; + } + if (object.isHostObject(runtime)) { + void* data = object.getHostObject(runtime)->data(); + if (data != nullptr) { + std::memcpy(target, data, size); + } + return; + } + if (object.isHostObject(runtime)) { + void* data = object.getHostObject(runtime)->pointer(); + if (data != nullptr) { + std::memcpy(target, data, size); + } + return; + } + + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + if (bytes != nullptr) { + std::memcpy(target, bytes, std::min(byteLength, size)); + } + return; + } + } + + if (type.aggregateInfo == nullptr) { + throw JSError(runtime, "Missing native struct metadata."); + } + if (!value.isObject()) { + throw JSError(runtime, "Expected struct descriptor object."); + } + + Object object = value.asObject(runtime); + for (const auto& field : type.aggregateInfo->fields) { + bool hasField = object.hasProperty(runtime, field.name.c_str()); + if (!hasField) { + continue; + } + Value fieldValue = object.getProperty(runtime, field.name.c_str()); + void* fieldTarget = static_cast(target) + field.offset; + convertEngineArgument(runtime, bridge, field.type, fieldValue, fieldTarget, frame); + } +} + +void convertIndexedAggregateArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame) { + size_t size = nativeSizeForType(type); + std::memset(target, 0, size); + if (value.isNull() || value.isUndefined()) { + return; + } + if (value.isObject()) { + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, value.asObject(runtime), &bytes, &byteLength)) { + if (bytes != nullptr) { + std::memcpy(target, bytes, std::min(byteLength, size)); + } + return; + } + } + if (!value.isObject() || !value.asObject(runtime).isArray(runtime)) { + throw JSError(runtime, "Expected array, ArrayBuffer, or typed array."); + } + + Array array = value.asObject(runtime).getArray(runtime); + size_t elementSize = type.elementType != nullptr ? nativeSizeForType(*type.elementType) : 0; + if (elementSize == 0 || type.elementType == nullptr) { + throw JSError(runtime, "Invalid native array element type."); + } + size_t count = std::min(type.arraySize, array.size(runtime)); + for (size_t i = 0; i < count; i++) { + void* slot = static_cast(target) + (i * elementSize); + convertEngineArgument(runtime, bridge, *type.elementType, array.getValueAtIndex(runtime, i), + slot, frame); + } +} + +void convertEngineFfiArgument(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame) { + if (type.kind != metagen::mdTypeArray) { + convertEngineArgument(runtime, bridge, type, value, target, frame); + return; + } + + void* pointer = nullptr; + if (!value.isNull() && !value.isUndefined()) { + if (value.isObject()) { + Object object = value.asObject(runtime); + if (!readPointerLikeValue(runtime, value, &pointer)) { + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + pointer = const_cast(bytes); + } + } + } + + if (pointer == nullptr) { + size_t byteLength = nativeSizeForType(type); + void* buffer = frame.addBuffer(byteLength); + convertIndexedAggregateArgument(runtime, bridge, type, value, buffer, frame); + pointer = buffer; + } + } + + *static_cast(target) = pointer; +} + +void convertEngineArgument(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame) { + if (unsupportedEngineType(type)) { + throw JSError(runtime, "This native signature is not supported by " + "the engine bridge yet."); + } + + switch (type.kind) { + case metagen::mdTypeBool: + if (!value.isNumber() && !value.isBool()) { + throw JSError(runtime, "Expected boolean or numeric argument."); + } + *static_cast(target) = value.isBool() + ? static_cast(value.getBool()) + : static_cast(value.getNumber() != 0); + break; + case metagen::mdTypeChar: + writeNumericArgument(runtime, value, target, "int8"); + break; + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + writeNumericArgument(runtime, value, target, "uint8"); + break; + case metagen::mdTypeSShort: + writeNumericArgument(runtime, value, target, "int16"); + break; + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + if (value.isString()) { + std::string text = value.asString(runtime).utf8(runtime); + if (text.size() != 1) { + throw JSError(runtime, "Expected a single-character string."); + } + *static_cast(target) = + static_cast(static_cast(text[0])); + } else { + writeNumericArgument(runtime, value, target, "uint16"); + } + break; + case metagen::mdTypeSInt: + writeNumericArgument(runtime, value, target, "int32"); + break; + case metagen::mdTypeUInt: + writeNumericArgument(runtime, value, target, "uint32"); + break; + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + writeNumericArgument(runtime, value, target, "int64"); + break; + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + writeNumericArgument(runtime, value, target, "uint64"); + break; + case metagen::mdTypeFloat: + writeNumericArgument(runtime, value, target, "float"); + break; + case metagen::mdTypeDouble: + writeNumericArgument(runtime, value, target, "double"); + break; + case metagen::mdTypeString: { + if (value.isNull() || value.isUndefined()) { + *static_cast(target) = nullptr; + break; + } + if (value.isObject()) { + Object object = value.asObject(runtime); + void* pointer = nullptr; + if (readPointerLikeValue(runtime, value, &pointer)) { + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, pointer, value, false, true); + } + *static_cast(target) = static_cast(pointer); + break; + } + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, bytes, value, false, true); + } + *static_cast(target) = reinterpret_cast(const_cast(bytes)); + break; + } + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + Value primitive = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (primitive.isString()) { + std::string utf8 = primitive.asString(runtime).utf8(runtime); + char* string = strdup(utf8.c_str()); + if (string == nullptr) { + throw std::bad_alloc(); + } + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, string, value, true, false); + } + frame.addCString(string); + *static_cast(target) = string; + break; + } + } + } + if (!value.isString()) { + throw JSError(runtime, "Expected string argument."); + } + std::string utf8 = value.asString(runtime).utf8(runtime); + char* string = strdup(utf8.c_str()); + if (string == nullptr) { + throw std::bad_alloc(); + } + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, string, value, true, false); + } + frame.addCString(string); + *static_cast(target) = string; + break; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: { + id object = objectFromEngineValue(runtime, bridge, value, frame, + type.kind == metagen::mdTypeNSMutableStringObject); + if (valueIsNativeObjectHostObject(runtime, value)) { + frame.retainObject(object); + } + *static_cast(target) = object; + break; + } + case metagen::mdTypeClass: { + *static_cast(target) = classFromEngineValue(runtime, value); + break; + } + case metagen::mdTypeSelector: { + if (value.isNull() || value.isUndefined()) { + *static_cast(target) = nullptr; + break; + } + if (!value.isString()) { + throw JSError(runtime, "Expected selector string."); + } + std::string selectorName = value.asString(runtime).utf8(runtime); + *static_cast(target) = sel_registerName(selectorName.c_str()); + break; + } + case metagen::mdTypePointer: + if (value.isObject()) { + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + auto reference = object.getHostObject(runtime); + if (reference->data() == nullptr && type.elementType != nullptr) { + reference->ensureStorage(runtime, *type.elementType, frame); + } else if (reference->data() == nullptr) { + reference->ensureStorage(runtime, reference->type(), frame); + } + void* pointer = reference->data(); + frame.rememberRoundTripValue(bridge, runtime, pointer, value); + *static_cast(target) = pointer; + break; + } + if (auto structObject = getNativeStructHostObject(runtime, object)) { + void* pointer = structObject->data(); + frame.rememberRoundTripValue(bridge, runtime, pointer, value); + *static_cast(target) = pointer; + break; + } + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + void* pointer = const_cast(bytes); + frame.rememberRoundTripValue(bridge, runtime, pointer, value); + *static_cast(target) = pointer; + break; + } + } + *static_cast(target) = pointerFromEngineValue(runtime, bridge, value, frame); + break; + case metagen::mdTypeOpaquePointer: + *static_cast(target) = pointerFromEngineValue(runtime, bridge, value, frame); + break; + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: { + if (value.isNull() || value.isUndefined()) { + *static_cast(target) = nullptr; + break; + } + if (value.isObject()) { + Object object = value.asObject(runtime); + void* nativePointer = nullptr; + if (object.isFunction(runtime)) { + std::string functionKind = stringPropertyOrEmpty(runtime, object, "kind"); + if (functionKind == "block" || functionKind == "functionPointer" || + functionKind == "functionReference") { + if (readNativePointerProperty(runtime, object, &nativePointer)) { + *static_cast(target) = nativePointer; + break; + } + } + + uintptr_t roundTripValidationKey = NativeApiBridge::callbackRoundTripValidationKey(type); + auto threadPolicy = readEngineCallbackThreadPolicy(runtime, object); + std::string callbackEncoding = + stringPropertyOrEmpty(runtime, object, kNativeApiCallbackEncodingProperty); + auto callback = + callbackEncoding.empty() + ? createEngineCallback(runtime, bridge, type, object.asFunction(runtime), + type.kind == metagen::mdTypeBlock, threadPolicy) + : createEngineCallback( + runtime, bridge, callbackEncoding, object.asFunction(runtime), + type.kind == metagen::mdTypeBlock, threadPolicy, roundTripValidationKey); + void* pointer = callback->functionPointer(); + if (type.kind == metagen::mdTypeBlock) { + frame.addObject(static_cast(pointer)); + frame.addLifetime(callback); + bridge->rememberRoundTripValue(runtime, pointer, value, false, roundTripValidationKey); + } else { + bridge->rememberRoundTripValue(runtime, pointer, value, false, roundTripValidationKey); + } + try { + object.setProperty(runtime, "__nativeApiPointerObject", + createPointer(runtime, bridge, pointer)); + object.setProperty(runtime, "__nativeApiPointer", + static_cast(reinterpret_cast(pointer))); + } catch (const std::exception&) { + } + *static_cast(target) = pointer; + break; + } + } + *static_cast(target) = pointerFromEngineValue(runtime, bridge, value, frame); + break; + } + case metagen::mdTypeStruct: + convertAggregateArgument(runtime, bridge, type, value, target, frame); + break; + case metagen::mdTypeArray: + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: + convertIndexedAggregateArgument(runtime, bridge, type, value, target, frame); + break; + default: + throw JSError(runtime, "Unsupported Engine argument type."); + } +} + +Value convertNativeReturnValue(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, void* value) { + if (unsupportedEngineType(type)) { + throw JSError(runtime, "This native return type is not supported by " + "the engine bridge yet."); + } + + switch (type.kind) { + case metagen::mdTypeVoid: + return Value::undefined(); + case metagen::mdTypeBool: + return *static_cast(value) != 0; + case metagen::mdTypeChar: + return static_cast(*static_cast(value)); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return static_cast(*static_cast(value)); + case metagen::mdTypeSShort: + return static_cast(*static_cast(value)); + case metagen::mdTypeUShort: + return static_cast(*static_cast(value)); + case metagen::mdTypeUnichar: { + const char16_t unit = *static_cast(value); + // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates + // fall back to U+FFFD). + char buffer[4] = {0}; + size_t length = 0; + if (unit < 0x80) { + buffer[length++] = static_cast(unit); + } else if (unit < 0x800) { + buffer[length++] = static_cast(0xC0 | (unit >> 6)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } else if (unit >= 0xD800 && unit <= 0xDFFF) { + buffer[length++] = static_cast(0xEF); + buffer[length++] = static_cast(0xBF); + buffer[length++] = static_cast(0xBD); + } else { + buffer[length++] = static_cast(0xE0 | (unit >> 12)); + buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } + return String::createFromUtf8( + runtime, reinterpret_cast(buffer), length); + } + case metagen::mdTypeSInt: + return static_cast(*static_cast(value)); + case metagen::mdTypeUInt: + return static_cast(*static_cast(value)); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return signedInteger64ToEngineValue(runtime, *static_cast(value)); + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return unsignedInteger64ToEngineValue(runtime, *static_cast(value)); + case metagen::mdTypeFloat: + return static_cast(*static_cast(value)); + case metagen::mdTypeDouble: + return *static_cast(value); + case metagen::mdTypeString: { + const char* string = *static_cast(value); + if (string == nullptr) { + return Value::null(); + } + NativeApiType cStringType = primitiveInteropType(metagen::mdTypeChar); + std::shared_ptr backingValue; + bool stringLikeNative = false; + if (bridge != nullptr) { + Value roundTrip = bridge->findRoundTripValue(runtime, string, &stringLikeNative); + if (!roundTrip.isUndefined()) { + backingValue = std::make_shared(runtime, roundTrip); + } + } + if (stringLikeNative) { + size_t byteLength = 0; + char* copy = copyCStringForReference(string, &byteLength); + return Object::createFromHostObject( + runtime, std::make_shared(bridge, cStringType, copy, true, + byteLength)); + } + return Object::createFromHostObject( + runtime, std::make_shared( + bridge, cStringType, const_cast(string), false, 0, nullptr, + std::move(backingValue))); + } + case metagen::mdTypeClass: { + Class cls = *static_cast(value); + if (cls == nil) { + return Value::null(); + } + const char* name = class_getName(cls); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + return makeNativeClassValue(runtime, bridge, std::move(symbol)); + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: { + id object = *static_cast(value); + if (object == nil) { + return Value::null(); + } + if (!nativeObjectPointerMayBeObject(object)) { + return Value::undefined(); + } + Value roundTrip = findCachedNativeObjectReturn(runtime, bridge, type, object); + if (!roundTrip.isUndefined()) { + if (type.returnOwned) { + [object release]; + } + return roundTrip; + } + if (nativeObjectReturnMayCoerceToString(type) && nativeObjectIsStringLike(object)) { + std::string utf8 = utf8StringFromNSString(static_cast(object)); + if (type.returnOwned) { + [object release]; + } + return makeString(runtime, utf8); + } + if ([object isKindOfClass:[NSNull class]]) { + if (type.returnOwned) { + [object release]; + } + return Value::null(); + } + if ([object isKindOfClass:[NSNumber class]] && + ![object isKindOfClass:[NSDecimalNumber class]]) { + NSNumber* number = static_cast(object); + const char* objCType = [number objCType]; + bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == CFBooleanGetTypeID() || + (objCType != nullptr && std::strcmp(objCType, @encode(BOOL)) == 0); + Value result = + isBool ? Value(static_cast([number boolValue])) : Value([number doubleValue]); + if (type.returnOwned) { + [object release]; + } + return result; + } + if (object_isClass(object)) { + Class cls = static_cast(object); + Value cachedClass = bridge->findClassValue(runtime, cls); + if (!cachedClass.isUndefined()) { + if (type.returnOwned) { + [object release]; + } + return cachedClass; + } + + const char* className = class_getName(cls); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = className != nullptr ? className : "", + .runtimeName = className != nullptr ? className : "", + }; + if (const NativeApiSymbol* found = + bridge->findClassForRuntimePointer(cls)) { + symbol = *found; + } else if (const NativeApiSymbol* found = + bridge->findClassForRuntimeClass(cls)) { + symbol = *found; + } + Value classValue = + makeNativeClassValue(runtime, bridge, std::move(symbol)); + if (type.returnOwned) { + [object release]; + } + return classValue; + } + if (const NativeApiSymbol* classSymbol = bridge->findClassForRuntimePointer((void*)object)) { + return makeNativeClassValue(runtime, bridge, *classSymbol); + } + if (const NativeApiSymbol* protocolSymbol = + bridge->findProtocolForRuntimePointer((void*)object)) { + return makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + } + return makeNativeObjectValue(runtime, bridge, object, type.returnOwned); + } + case metagen::mdTypeSelector: { + SEL selector = *static_cast(value); + const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; + return selectorName != nullptr ? makeString(runtime, selectorName) : Value::null(); + } + case metagen::mdTypePointer: + case metagen::mdTypeOpaquePointer: { + void* pointer = *static_cast(value); + if (pointer == nullptr) { + return Value::null(); + } + if (const NativeApiSymbol* classSymbol = bridge->findClassForRuntimePointer(pointer)) { + return makeNativeClassValue(runtime, bridge, *classSymbol); + } + if (const NativeApiSymbol* protocolSymbol = bridge->findProtocolForRuntimePointer(pointer)) { + return makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + } + if (type.kind == metagen::mdTypePointer && type.elementType != nullptr) { + std::shared_ptr backingValue; + bool stringLikeNative = false; + Value roundTrip = bridge->findRoundTripValue(runtime, pointer, &stringLikeNative); + if (stringLikeNative) { + size_t byteLength = 0; + char* copy = copyCStringForReference(static_cast(pointer), &byteLength); + return Object::createFromHostObject( + runtime, std::make_shared(bridge, *type.elementType, + copy, true, byteLength)); + } + if (!roundTrip.isUndefined()) { + backingValue = std::make_shared(runtime, roundTrip); + } + return Object::createFromHostObject(runtime, std::make_shared( + bridge, *type.elementType, pointer, false, + 0, nullptr, std::move(backingValue))); + } + return createPointer(runtime, bridge, pointer); + } + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: { + void* pointer = *static_cast(value); + if (pointer == nullptr) { + return Value::null(); + } + Value roundTrip = bridge->findRoundTripValue( + runtime, pointer, nullptr, false, NativeApiBridge::callbackRoundTripValidationKey(type)); + if (!roundTrip.isUndefined()) { + return roundTrip; + } + return wrapNativeFunctionPointer(runtime, bridge, type, pointer, + type.kind == metagen::mdTypeBlock); + } + case metagen::mdTypeStruct: + if (type.aggregateInfo == nullptr) { + return ArrayBuffer( + runtime, std::make_shared(value, nativeSizeForType(type))); + } + return createNativeStructHostObject( + runtime, std::make_shared(bridge, type.aggregateInfo, + value, true)); + case metagen::mdTypeArray: + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: { + Array result(runtime, type.arraySize); + if (type.elementType == nullptr) { + return result; + } + size_t elementSize = nativeSizeForType(*type.elementType); + auto base = static_cast(value); + for (uint16_t i = 0; i < type.arraySize; i++) { + result.setValueAtIndex( + runtime, i, + convertNativeReturnValue(runtime, bridge, *type.elementType, + base + (static_cast(i) * elementSize))); + } + return result; + } + default: + throw JSError(runtime, "Unsupported Engine return type."); + } +} + +void NativeApiReferenceHostObject::ensureStorage(Runtime& runtime, NativeApiType type, + NativeApiArgumentFrame& frame, size_t elements) { + size_t elementCount = std::max(elements, 1); + NativeApiType storageType = std::move(type); + size_t stride = std::max(nativeSizeForType(storageType), 1); + size_t required = std::max(stride * elementCount, sizeof(void*)); + type_ = std::move(storageType); + + if (data_ == nullptr) { + data_ = calloc(1, required); + ownsData_ = true; + byteLength_ = required; + } else if (ownsData_ && byteLength_ < required) { + void* expanded = realloc(data_, required); + if (expanded == nullptr) { + throw std::bad_alloc(); + } + std::memset(static_cast(expanded) + byteLength_, 0, required - byteLength_); + data_ = expanded; + byteLength_ = required; + } + + if (data_ != nullptr && pendingValue_ != nullptr) { + Value pending(runtime, *pendingValue_); + convertEngineArgument(runtime, bridge_, type_, pending, data_, frame); + if (nativeTypeStoresObjectiveCObject(type_)) { + retainObjectSlot(0, *static_cast(data_)); + } + pendingValue_.reset(); + } +} + +Value NativeApiReferenceHostObject::get(Runtime& runtime, const PropNameID& name) { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, "reference"); + } + if (property == "address") { + return static_cast(reinterpret_cast(data_)); + } + if (property == "value") { + if (data_ == nullptr) { + if (pendingValue_ != nullptr) { + return Value(runtime, *pendingValue_); + } + return Value::undefined(); + } + if (backingValue_ != nullptr && nativeTypeStoresObjectiveCObject(type_)) { + return Value(runtime, *backingValue_); + } + return convertNativeReturnValue(runtime, bridge_, type_, data_); + } + if (auto index = parseArrayIndexProperty(property)) { + if (data_ == nullptr) { + return Value::undefined(); + } + void* slot = static_cast(data_) + (*index * referenceElementStride(type_)); + return convertNativeReturnValue(runtime, bridge_, type_, slot); + } + if (property == "toString") { + void* data = data_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [data](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", data); + return makeString(runtime, ""); + }); + } + return Value::undefined(); +} + +NativeApiHostSetResult NativeApiReferenceHostObject::set(Runtime& runtime, const PropNameID& name, + const Value& value) { + std::string property = name.utf8(runtime); + auto index = parseArrayIndexProperty(property); + if (property != "value" && !index) { + NATIVE_API_SET_RETURN(true); + } + size_t slotIndex = index.value_or(0); + NativeApiArgumentFrame frame(1); + if (data_ == nullptr) { + if (slotIndex == 0) { + pendingValue_ = std::make_shared(runtime, value); + NATIVE_API_SET_RETURN(true); + } + ensureStorage(runtime, type_, frame, slotIndex + 1); + } + pendingValue_.reset(); + backingValue_.reset(); + void* slot = static_cast(data_) + (slotIndex * referenceElementStride(type_)); + convertEngineArgument(runtime, bridge_, type_, value, slot, frame); + if (nativeTypeStoresObjectiveCObject(type_)) { + retainObjectSlot(slotIndex, *static_cast(slot)); + } + NATIVE_API_SET_RETURN(true); +} + +Value NativeApiStructObjectHostObject::get(Runtime& runtime, const PropNameID& name) { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, info_ != nullptr && info_->isUnion ? "union" : "struct"); + } + if (property == "name") { + return makeString(runtime, info_ != nullptr ? info_->name : ""); + } + if (property == "sizeof") { + return static_cast(info_ != nullptr ? info_->size : 0); + } + if (property == "address") { + return static_cast(reinterpret_cast(data_)); + } + if (property == "toString") { + auto info = info_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [info](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return makeString(runtime, std::string("[NativeApi ") + + (info != nullptr && info->isUnion ? "Union " : "Struct ") + + (info != nullptr ? info->name : "") + "]"); + }); + } + + if (info_ != nullptr && data_ != nullptr) { + for (const auto& field : info_->fields) { + if (field.name != property) { + continue; + } + void* fieldData = static_cast(data_) + field.offset; + if (field.type.kind == metagen::mdTypeStruct && field.type.aggregateInfo != nullptr) { + return createNativeStructHostObject( + runtime, + std::make_shared( + bridge_, field.type.aggregateInfo, fieldData, false, ownedData_, backingValue_)); + } + return convertNativeReturnValue(runtime, bridge_, field.type, fieldData); + } + } + return Value::undefined(); +} + +NativeApiHostSetResult NativeApiStructObjectHostObject::set(Runtime& runtime, + const PropNameID& name, + const Value& value) { + std::string property = name.utf8(runtime); + if (info_ == nullptr || data_ == nullptr) { + throw JSError(runtime, "Struct is not initialized."); + } + for (const auto& field : info_->fields) { + if (field.name != property) { + continue; + } + NativeApiArgumentFrame frame(1); + convertEngineArgument(runtime, bridge_, field.type, value, + static_cast(data_) + field.offset, frame); + NATIVE_API_SET_RETURN(true); + } + throw JSError(runtime, "No native struct field: " + property); +} + +std::vector NativeApiStructObjectHostObject::getPropertyNames(Runtime& runtime) { + std::vector names; + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "name"); + addPropertyName(runtime, names, "sizeof"); + addPropertyName(runtime, names, "address"); + addPropertyName(runtime, names, "toString"); + if (info_ != nullptr) { + for (const auto& field : info_->fields) { + addPropertyName(runtime, names, field.name.c_str()); + } + } + return names; +} + +NativeApiType primitiveInteropType(MDTypeKind kind) { + NativeApiType type; + type.kind = kind; + type.ffiType = ffiTypeForEngineKind(kind); + type.supported = type.ffiType != nullptr; + return type; +} + +std::optional primitiveInteropTypeFromCode(int32_t code) { + MDTypeKind kind = static_cast(code); + switch (kind) { + case metagen::mdTypeVoid: + case metagen::mdTypeBool: + case metagen::mdTypeChar: + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + case metagen::mdTypeSShort: + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + case metagen::mdTypeSInt: + case metagen::mdTypeUInt: + case metagen::mdTypeSLong: + case metagen::mdTypeULong: + case metagen::mdTypeSInt64: + case metagen::mdTypeUInt64: + case metagen::mdTypeFloat: + case metagen::mdTypeDouble: + case metagen::mdTypeString: + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClass: + case metagen::mdTypeSelector: + case metagen::mdTypePointer: + case metagen::mdTypeOpaquePointer: + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: + return primitiveInteropType(kind); + default: + return std::nullopt; + } +} + +std::optional interopTypeFromValue(Runtime& runtime, + const std::shared_ptr& bridge, + const Value& value) { + if (value.isNumber()) { + return primitiveInteropTypeFromCode(static_cast(value.getNumber())); + } + + if (!value.isObject()) { + return std::nullopt; + } + + Object object = value.asObject(runtime); + Value typeCodeValue = object.getProperty(runtime, "__nativeApiTypeCode"); + if (typeCodeValue.isNumber()) { + return primitiveInteropTypeFromCode(static_cast(typeCodeValue.getNumber())); + } + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + Value primitive = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (primitive.isNumber()) { + return primitiveInteropTypeFromCode(static_cast(primitive.getNumber())); + } + } + + Class descriptorClass = nativeClassFromEngineObject(runtime, object); + if (descriptorClass == Nil && stringPropertyOrEmpty(runtime, object, "kind") == "class") { + descriptorClass = static_cast(pointerFromSymbolLikeObject(runtime, object)); + } + if (descriptorClass != Nil) { + return nativeObjectReturnTypeForClass(descriptorClass); + } + + if (auto structObject = getNativeStructHostObject(runtime, object)) { + NativeApiType type; + type.kind = metagen::mdTypeStruct; + type.aggregateInfo = structObject->info(); + type.aggregateOffset = + type.aggregateInfo != nullptr ? type.aggregateInfo->offset : MD_SECTION_OFFSET_NULL; + type.aggregateIsUnion = type.aggregateInfo != nullptr && type.aggregateInfo->isUnion; + type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr + ? &type.aggregateInfo->ffi->type + : nullptr; + type.supported = type.ffiType != nullptr; + return type; + } + + Value kindValue = object.getProperty(runtime, "kind"); + if (kindValue.isString()) { + std::string kindName = kindValue.asString(runtime).utf8(runtime); + if (kindName == "pointer") { + return primitiveInteropType(metagen::mdTypePointer); + } + if (kindName == "reference") { + return primitiveInteropType(metagen::mdTypePointer); + } + if (kindName == "class") { + return nativeObjectReturnType(metagen::mdTypeInstanceObject); + } + if (kindName == "selector") { + return primitiveInteropType(metagen::mdTypeSelector); + } + if (kindName == "protocol") { + return primitiveInteropType(metagen::mdTypeProtocolObject); + } + if (kindName == "block") { + return primitiveInteropType(metagen::mdTypeBlock); + } + if (kindName == "functionPointer") { + return primitiveInteropType(metagen::mdTypeFunctionPointer); + } + if (kindName == "functionReference") { + return primitiveInteropType(metagen::mdTypeFunctionPointer); + } + } + Value offsetValue = object.getProperty(runtime, "metadataOffset"); + if (kindValue.isString() && offsetValue.isNumber()) { + std::string kindName = kindValue.asString(runtime).utf8(runtime); + if (kindName == "struct" || kindName == "union") { + bool isUnion = kindName == "union"; + auto info = + bridge->aggregateInfoFor(static_cast(offsetValue.getNumber()), isUnion); + NativeApiType type; + type.kind = metagen::mdTypeStruct; + type.aggregateInfo = info; + type.aggregateOffset = info != nullptr ? info->offset : MD_SECTION_OFFSET_NULL; + type.aggregateIsUnion = isUnion; + type.ffiType = info != nullptr && info->ffi != nullptr ? &info->ffi->type : nullptr; + type.supported = type.ffiType != nullptr; + return type; + } + } + + return std::nullopt; +} + +Value makeAggregateConstructor(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiSymbol& symbol) { + auto info = bridge->aggregateInfoFor(symbol); + auto constructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, symbol.name.c_str()), 1, + [bridge, symbol, info](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (info == nullptr) { + throw JSError(runtime, "Native aggregate metadata is unavailable: " + symbol.name); + } + + NativeApiType type; + type.kind = metagen::mdTypeStruct; + type.aggregateInfo = info; + type.aggregateOffset = info->offset; + type.aggregateIsUnion = info->isUnion; + type.ffiType = info->ffi != nullptr ? &info->ffi->type : nullptr; + type.supported = type.ffiType != nullptr; + + if (count > 0 && args[0].isObject()) { + void* pointer = nullptr; + if (readPointerLikeValue(runtime, args[0], &pointer) && pointer != nullptr) { + return createNativeStructHostObject( + runtime, std::make_shared( + bridge, info, pointer, false, nullptr, + std::make_shared(runtime, args[0]))); + } + } + + std::vector storage(info->size, 0); + if (count > 0) { + NativeApiArgumentFrame frame(1); + convertAggregateArgument(runtime, bridge, type, args[0], storage.data(), frame); + } + return createNativeStructHostObject( + runtime, + std::make_shared(bridge, info, storage.data(), true)); + }); + + constructor.setProperty( + runtime, "kind", + makeString(runtime, symbol.kind == NativeApiSymbolKind::Union ? "union" : "struct")); + constructor.setProperty(runtime, "runtimeName", makeString(runtime, symbol.runtimeName)); + constructor.setProperty(runtime, "metadataOffset", static_cast(symbol.offset)); + constructor.setProperty(runtime, "sizeof", static_cast(info != nullptr ? info->size : 0)); + constructor.setProperty( + runtime, "equals", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "equals"), 2, + [bridge, info](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (info == nullptr || count < 2) { + return false; + } + + NativeApiType type; + type.kind = metagen::mdTypeStruct; + type.aggregateInfo = info; + type.aggregateOffset = info->offset; + type.aggregateIsUnion = info->isUnion; + type.ffiType = info->ffi != nullptr ? &info->ffi->type : nullptr; + type.supported = type.ffiType != nullptr; + + std::vector left(info->size, 0); + std::vector right(info->size, 0); + try { + NativeApiArgumentFrame leftFrame(1); + convertAggregateArgument(runtime, bridge, type, args[0], left.data(), leftFrame); + NativeApiArgumentFrame rightFrame(1); + convertAggregateArgument(runtime, bridge, type, args[1], right.data(), rightFrame); + } catch (const std::exception&) { + return false; + } + + return std::memcmp(left.data(), right.data(), info->size) == 0; + })); + Array fields(runtime, info != nullptr ? info->fields.size() : 0); + if (info != nullptr) { + for (size_t i = 0; i < info->fields.size(); i++) { + fields.setValueAtIndex(runtime, i, makeString(runtime, info->fields[i].name)); + } + } + constructor.setProperty(runtime, "fields", fields); + return constructor; +} + +size_t sizeofInteropType(Runtime& runtime, const std::shared_ptr& bridge, + const Value& value) { + if (auto type = interopTypeFromValue(runtime, bridge, value)) { + return nativeSizeForType(*type); + } + + if (value.isObject()) { + Object object = value.asObject(runtime); + if (object.isHostObject(runtime) || + object.isHostObject(runtime) || + object.isHostObject(runtime) || + nativeClassFromEngineObject(runtime, object) != Nil) { + return sizeof(void*); + } + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + return sizeof(void*); + } + Value sizeValue = object.getProperty(runtime, "sizeof"); + if (sizeValue.isNumber()) { + return static_cast(sizeValue.getNumber()); + } + } + + throw JSError(runtime, "Invalid type for interop.sizeof."); +} + +Object createPointer(Runtime& runtime, const std::shared_ptr& bridge, + void* pointer, bool adopted, std::shared_ptr backingValue) { + if (!adopted && bridge != nullptr) { + Value cached = bridge->findPointerValue(runtime, pointer); + if (cached.isObject()) { + Object cachedObject = cached.asObject(runtime); + if (backingValue != nullptr && + cachedObject.isHostObject(runtime)) { + cachedObject.getHostObject(runtime)->setBackingValue( + runtime, *backingValue); + } + return cachedObject; + } + } + + Object result = Object::createFromHostObject( + runtime, std::make_shared(bridge, pointer, "pointer", adopted, + std::move(backingValue))); + if (!adopted && bridge != nullptr) { + bridge->rememberPointerValue(runtime, pointer, Value(runtime, result)); + } + return result; +} + +void installInteropHasInstance(Runtime& runtime, Function& constructor, const char* kind) { + Value symbolCtorValue = runtime.global().getProperty(runtime, "Symbol"); + if (!symbolCtorValue.isObject()) { + return; + } + + Object symbolCtor = symbolCtorValue.asObject(runtime); + Value hasInstanceValue = symbolCtor.getProperty(runtime, "hasInstance"); + if (!hasInstanceValue.isSymbol()) { + return; + } + + try { + Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); + Function defineProperty = objectCtor.getPropertyAsFunction(runtime, "defineProperty"); + Object descriptor(runtime); + descriptor.setProperty(runtime, "configurable", true); + descriptor.setProperty( + runtime, "value", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Symbol.hasInstance"), 1, + [kind = std::string(kind)](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + return false; + } + + Object object = args[0].asObject(runtime); + Value kindValue = object.getProperty(runtime, "kind"); + return kindValue.isString() && kindValue.asString(runtime).utf8(runtime) == kind; + })); + defineProperty.call(runtime, constructor, hasInstanceValue, descriptor); + } catch (const std::exception&) { + } +} + +Class classFromEngineValue(Runtime& runtime, const Value& value) { + if (value.isString()) { + std::string name = value.asString(runtime).utf8(runtime); + return objc_lookUpClass(name.c_str()); + } + if (!value.isObject()) { + return Nil; + } + Object object = value.asObject(runtime); + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + return cls; + } + if (stringPropertyOrEmpty(runtime, object, "kind") == "class") { + if (void* pointer = pointerFromSymbolLikeObject(runtime, object)) { + return static_cast(pointer); + } + } + if (object.isHostObject(runtime)) { + id nativeObject = object.getHostObject(runtime)->object(); + return nativeObject != nil ? object_getClass(nativeObject) : Nil; + } + return Nil; +} + +Protocol* protocolFromEngineValue(Runtime& runtime, const Value& value) { + if (value.isString()) { + std::string name = value.asString(runtime).utf8(runtime); + Protocol* protocol = objc_getProtocol(name.c_str()); + if (protocol == nullptr) { + constexpr const char* suffix = "Protocol"; + if (name.size() > std::strlen(suffix) && + name.compare(name.size() - std::strlen(suffix), std::strlen(suffix), suffix) == 0) { + protocol = objc_getProtocol(name.substr(0, name.size() - std::strlen(suffix)).c_str()); + } + } + return protocol; + } + if (!value.isObject()) { + return nullptr; + } + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->nativeProtocol(); + } + if (stringPropertyOrEmpty(runtime, object, "kind") == "protocol") { + return static_cast(pointerFromSymbolLikeObject(runtime, object)); + } + if (object.isHostObject(runtime)) { + return static_cast( + object.getHostObject(runtime)->pointer()); + } + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + return static_cast(nativePointer); + } + Value nameValue = object.getProperty(runtime, "name"); + if (nameValue.isString()) { + return protocolFromEngineValue(runtime, nameValue); + } + return nullptr; +} + +Object createInteropObject(Runtime& runtime, const std::shared_ptr& bridge) { + Object interop(runtime); + Object types(runtime); + auto setType = [&](const char* name, MDTypeKind kind) { + Object type(runtime); + double code = static_cast(kind); + type.setProperty(runtime, "__nativeApiTypeCode", code); + type.setProperty( + runtime, "valueOf", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "valueOf"), 0, + [code](Runtime&, const Value&, const Value*, size_t) -> Value { return code; })); + type.setProperty(runtime, "toString", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [code](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + char text[32] = {}; + snprintf(text, sizeof(text), "%d", static_cast(code)); + return makeString(runtime, text); + })); + types.setProperty(runtime, name, type); + }; + setType("void", metagen::mdTypeVoid); + setType("bool", metagen::mdTypeBool); + setType("int8", metagen::mdTypeChar); + setType("uint8", metagen::mdTypeUInt8); + setType("int16", metagen::mdTypeSShort); + setType("uint16", metagen::mdTypeUShort); + setType("int32", metagen::mdTypeSInt); + setType("uint32", metagen::mdTypeUInt); + setType("int64", metagen::mdTypeSInt64); + setType("uint64", metagen::mdTypeUInt64); + setType("long", metagen::mdTypeSLong); + setType("ulong", metagen::mdTypeULong); + setType("NSInteger", metagen::mdTypeSLong); + setType("NSUInteger", metagen::mdTypeULong); + setType("float", metagen::mdTypeFloat); + setType("double", metagen::mdTypeDouble); + setType("BOOL", metagen::mdTypeBool); +#if defined(CGFLOAT_IS_DOUBLE) && CGFLOAT_IS_DOUBLE + setType("CGFloat", metagen::mdTypeDouble); +#else + setType("CGFloat", metagen::mdTypeFloat); +#endif + setType("NSTimeInterval", metagen::mdTypeDouble); + setType("CFTimeInterval", metagen::mdTypeDouble); + setType("UTF8CString", metagen::mdTypeString); + setType("unichar", metagen::mdTypeUnichar); + setType("id", metagen::mdTypeAnyObject); + setType("class", metagen::mdTypeClass); + setType("protocol", metagen::mdTypeProtocolObject); + setType("SEL", metagen::mdTypeSelector); + setType("selector", metagen::mdTypeSelector); + setType("pointer", metagen::mdTypePointer); + setType("block", metagen::mdTypeBlock); + setType("functionPointer", metagen::mdTypeFunctionPointer); + interop.setProperty(runtime, "types", types); + + Function pointerConstructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Pointer"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count > 0 && args[0].isObject()) { + Object object = args[0].asObject(runtime); + if (object.isHostObject(runtime)) { + return Value(runtime, object); + } + } + void* pointer = nullptr; + if (count > 0 && !args[0].isNull() && !args[0].isUndefined()) { + auto readAddress = [&](const Value& value, uintptr_t* address) -> bool { + auto readAddressFromString = [&](const Value& source) -> bool { + try { + Value stringCtorValue = runtime.global().getProperty(runtime, "String"); + if (!stringCtorValue.isObject() || + !stringCtorValue.asObject(runtime).isFunction(runtime)) { + return false; + } + Value stringValue = + stringCtorValue.asObject(runtime).asFunction(runtime).call(runtime, source); + if (!stringValue.isString()) { + return false; + } + return parseIntegerTextToUintptr(stringValue.asString(runtime).utf8(runtime), + address); + } catch (const std::exception&) { + return false; + } + }; + + if (value.isNumber()) { + double number = value.getNumber(); + if (!std::isfinite(number)) { + return false; + } + *address = static_cast(static_cast(number)); + return true; + } + if (value.isBigInt()) { + if (readAddressFromString(value)) { + return true; + } + BigInt bigint = value.getBigInt(runtime); + return parseBigIntToUintptr(runtime, bigint, address); + } + if (value.isObject()) { + Object object = value.asObject(runtime); + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + Value primitive = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (primitive.isNumber()) { + double number = primitive.getNumber(); + if (!std::isfinite(number)) { + return false; + } + *address = static_cast(static_cast(number)); + return true; + } + if (primitive.isBigInt()) { + if (readAddressFromString(primitive)) { + return true; + } + BigInt bigint = primitive.getBigInt(runtime); + return parseBigIntToUintptr(runtime, bigint, address); + } + } + return readAddressFromString(value); + } + return false; + }; + + uintptr_t address = 0; + if (!readAddress(args[0], &address)) { + throw JSError(runtime, "Pointer expects a numeric address."); + } + pointer = reinterpret_cast(address); + } + return createPointer(runtime, bridge, pointer); + }); + Object pointerPrototype(runtime); + pointerPrototype.setProperty(runtime, "constructor", pointerConstructor); + pointerConstructor.setProperty(runtime, "prototype", pointerPrototype); + installInteropHasInstance(runtime, pointerConstructor, "pointer"); + pointerConstructor.setProperty(runtime, "kind", makeString(runtime, "pointer")); + pointerConstructor.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + interop.setProperty(runtime, "Pointer", pointerConstructor); + + Function blockConstructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Block"), 2, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + return interopCallbackFromArguments(runtime, "Block", "block", args, count); + }); + Object blockPrototype(runtime); + blockPrototype.setProperty(runtime, "constructor", blockConstructor); + blockConstructor.setProperty(runtime, "prototype", blockPrototype); + installInteropHasInstance(runtime, blockConstructor, "block"); + blockConstructor.setProperty(runtime, "kind", makeString(runtime, "block")); + blockConstructor.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + interop.setProperty(runtime, "Block", blockConstructor); + + Function functionReferenceConstructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "FunctionReference"), 2, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + return interopCallbackFromArguments(runtime, "FunctionReference", "functionReference", args, + count); + }); + Object functionReferencePrototype(runtime); + functionReferencePrototype.setProperty(runtime, "constructor", functionReferenceConstructor); + functionReferenceConstructor.setProperty(runtime, "prototype", functionReferencePrototype); + installInteropHasInstance(runtime, functionReferenceConstructor, "functionReference"); + functionReferenceConstructor.setProperty(runtime, "kind", + makeString(runtime, "functionReference")); + functionReferenceConstructor.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + interop.setProperty(runtime, "FunctionReference", functionReferenceConstructor); + + Function referenceConstructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Reference"), 2, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + NativeApiType type = primitiveInteropType(metagen::mdTypePointer); + bool firstArgumentIsType = false; + if (count > 1) { + firstArgumentIsType = true; + } else if (count == 1 && args[0].isObject()) { + Object object = args[0].asObject(runtime); + Value typeCodeValue = object.getProperty(runtime, "__nativeApiTypeCode"); + Value kindValue = object.getProperty(runtime, "kind"); + firstArgumentIsType = + typeCodeValue.isNumber() || object.isFunction(runtime) || + nativeClassFromEngineObject(runtime, object) != Nil || + (kindValue.isString() && (kindValue.asString(runtime).utf8(runtime) == "class" || + kindValue.asString(runtime).utf8(runtime) == "protocol")); + } + std::optional requestedType = + firstArgumentIsType ? interopTypeFromValue(runtime, bridge, args[0]) : std::nullopt; + bool hasType = firstArgumentIsType && requestedType.has_value(); + if (hasType) { + type = *requestedType; + } + + void* data = nullptr; + bool ownsData = false; + size_t byteLength = 0; + std::shared_ptr pendingValue; + std::shared_ptr backingValue; + if (hasType) { + bool usesExternalStorage = false; + Value valueToStore = Value::undefined(); + if (count > 1) { + valueToStore = Value(runtime, args[1]); + if (args[1].isObject()) { + Object object = args[1].asObject(runtime); + auto structObject = type.kind == metagen::mdTypeStruct + ? getNativeStructHostObject(runtime, object) + : nullptr; + if (object.isHostObject(runtime)) { + data = object.getHostObject(runtime)->pointer(); + usesExternalStorage = true; + } else if (object.isHostObject(runtime)) { + auto reference = object.getHostObject(runtime); + data = reference->data(); + if (data != nullptr) { + usesExternalStorage = true; + } else { + valueToStore = object.getProperty(runtime, "value"); + } + } else if (structObject != nullptr) { + data = structObject->data(); + usesExternalStorage = true; + } else if (type.kind == metagen::mdTypePointer || + type.kind == metagen::mdTypeOpaquePointer || + type.kind == metagen::mdTypeBlock || + type.kind == metagen::mdTypeFunctionPointer) { + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + data = nativePointer; + usesExternalStorage = true; + } + } + } + } + if (!usesExternalStorage) { + byteLength = std::max(nativeSizeForType(type), sizeof(void*)); + data = calloc(1, byteLength); + if (data == nullptr) { + throw std::bad_alloc(); + } + ownsData = true; + if (count > 1) { + NativeApiArgumentFrame frame(1); + convertEngineArgument(runtime, bridge, type, valueToStore, data, frame); + if (nativeTypeStoresObjectiveCObject(type) && valueToStore.isObject()) { + backingValue = std::make_shared(runtime, valueToStore); + } + } + } + } else if (count > 0) { + pendingValue = std::make_shared(runtime, args[0]); + } + + if (ownsData && data == nullptr) { + throw std::bad_alloc(); + } + return Object::createFromHostObject( + runtime, std::make_shared( + bridge, type, data, ownsData, byteLength, std::move(pendingValue), + std::move(backingValue))); + }); + Object referencePrototype(runtime); + referencePrototype.setProperty(runtime, "constructor", referenceConstructor); + referenceConstructor.setProperty(runtime, "prototype", referencePrototype); + installInteropHasInstance(runtime, referenceConstructor, "reference"); + referenceConstructor.setProperty(runtime, "kind", makeString(runtime, "reference")); + referenceConstructor.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + interop.setProperty(runtime, "Reference", referenceConstructor); + + interop.setProperty( + runtime, "sizeof", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "sizeof"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1) { + throw JSError(runtime, "sizeof expects a type."); + } + return static_cast(sizeofInteropType(runtime, bridge, args[0])); + })); + + interop.setProperty( + runtime, "alloc", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "alloc"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isNumber()) { + throw JSError(runtime, "alloc expects a byte size."); + } + size_t size = static_cast(std::max(0, args[0].getNumber())); + return createPointer(runtime, bridge, calloc(1, size), false); + })); + + interop.setProperty( + runtime, "free", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "free"), 1, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + return Value::undefined(); + } + Object object = args[0].asObject(runtime); + if (!object.isHostObject(runtime)) { + return Value::undefined(); + } + auto pointer = object.getHostObject(runtime); + void* raw = pointer->pointer(); + if (raw != nullptr) { + free(raw); + pointer->clearWithoutFree(); + } + return Value::undefined(); + })); + + interop.setProperty( + runtime, "adopt", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "adopt"), 1, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + throw JSError(runtime, "adopt expects a Pointer."); + } + Object object = args[0].asObject(runtime); + if (!object.isHostObject(runtime)) { + throw JSError(runtime, "adopt expects a Pointer."); + } + object.getHostObject(runtime)->adopt(); + return Value(runtime, object); + })); + + interop.setProperty( + runtime, "handleof", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "handleof"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || args[0].isNull() || args[0].isUndefined()) { + return Value::null(); + } + if (args[0].isString()) { + std::string utf8 = args[0].asString(runtime).utf8(runtime); + char* data = strdup(utf8.c_str()); + return createPointer(runtime, bridge, data); + } + if (!args[0].isObject()) { + return Value::null(); + } + Object object = args[0].asObject(runtime); + if (object.isHostObject(runtime)) { + return Value(runtime, object); + } + if (object.isHostObject(runtime)) { + auto reference = object.getHostObject(runtime); + void* data = reference->data(); + if (data == nullptr) { + throw JSError(runtime, "Cannot get handle of empty Reference."); + } + std::shared_ptr backingValue; + if (reference->backingValue() != nullptr && + nativeTypeStoresObjectiveCObject(reference->type())) { + backingValue = reference->backingValue(); + } + return createPointer(runtime, bridge, data, false, std::move(backingValue)); + } + if (auto structObject = getNativeStructHostObject(runtime, object)) { + if (structObject->backingValue() != nullptr) { + return Value(runtime, *structObject->backingValue()); + } + return createPointer(runtime, bridge, structObject->data()); + } + if (object.isHostObject(runtime)) { + id nativeObject = object.getHostObject(runtime)->object(); + return createPointer(runtime, bridge, nativeObject, false, + std::make_shared(runtime, args[0])); + } + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + return createPointer(runtime, bridge, cls); + } + if (object.isHostObject(runtime)) { + return createPointer( + runtime, bridge, + object.getHostObject(runtime)->nativeProtocol()); + } + if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { + return createPointer(runtime, bridge, symbolPointer); + } + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + return createPointer(runtime, bridge, nativePointer); + } + Value kindValue = object.getProperty(runtime, "kind"); + if (kindValue.isString()) { + std::string kind = kindValue.asString(runtime).utf8(runtime); + if (kind == "block" || kind == "functionPointer" || kind == "functionReference") { + throw JSError(runtime, "Cannot get handle of uninitialized native callback."); + } + } + Value nativeName = object.getProperty(runtime, "nativeName"); + if (nativeName.isString()) { + std::string name = nativeName.asString(runtime).utf8(runtime); + void* symbol = dlsym(bridge->selfDl(), name.c_str()); + if (symbol != nullptr) { + return createPointer(runtime, bridge, symbol); + } + } + return Value::null(); + })); + + interop.setProperty( + runtime, "object", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "object"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || args[0].isNull() || args[0].isUndefined()) { + return Value::null(); + } + + void* pointer = nullptr; + if (args[0].isString()) { + uintptr_t address = 0; + if (!parseIntegerTextToUintptr(args[0].asString(runtime).utf8(runtime), + &address)) { + throw JSError(runtime, + "interop.object expects an Objective-C object pointer."); + } + pointer = reinterpret_cast(address); + } else { + NativeApiArgumentFrame frame(1); + pointer = pointerFromEngineValue(runtime, bridge, args[0], frame); + } + + if (pointer == nullptr) { + return Value::null(); + } + + id object = static_cast(pointer); + NativeApiType type = nativeObjectReturnTypeForClass(object_getClass(object)); + return convertNativeReturnValue(runtime, bridge, type, &object); + })); + + // interop.set/getAssociatedObject: the sanctioned way to persist state on + // a native UIKit-backed object across engine calls. JS expandos set on a + // host-object wrapper do NOT round-trip (each call can hand back a fresh + // wrapper for the same native receiver); a real objc_setAssociatedObject + // does, because it lives on the native object itself. + interop.setProperty( + runtime, "setAssociatedObject", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "setAssociatedObject"), 4, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 3) { + throw JSError(runtime, + "interop.setAssociatedObject expects target, key, and value."); + } + id target = nativeAssociatedObjectTargetFromValue(runtime, args[0]); + if (target == nil || !args[1].isString()) { + throw JSError(runtime, + "interop.setAssociatedObject expects target, key, and value."); + } + + std::string key = args[1].asString(runtime).utf8(runtime); + NativeApiArgumentFrame frame(1); + id value = nil; + if (!args[2].isNull() && !args[2].isUndefined()) { + value = objectFromEngineValue(runtime, bridge, args[2], frame, false); + } + objc_AssociationPolicy policy = + count > 3 ? associatedObjectPolicyFromValue(runtime, args[3]) + : OBJC_ASSOCIATION_RETAIN_NONATOMIC; + objc_setAssociatedObject(target, sel_registerName(key.c_str()), value, policy); + return Value::undefined(); + })); + + interop.setProperty( + runtime, "getAssociatedObject", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getAssociatedObject"), 2, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 2 || !args[1].isString()) { + throw JSError(runtime, + "interop.getAssociatedObject expects target and key."); + } + id target = nativeAssociatedObjectTargetFromValue(runtime, args[0]); + if (target == nil) { + return Value::null(); + } + + std::string key = args[1].asString(runtime).utf8(runtime); + id associated = objc_getAssociatedObject(target, sel_registerName(key.c_str())); + if (associated == nil) { + return Value::null(); + } + + NativeApiType type = nativeObjectReturnTypeForClass(object_getClass(associated)); + return convertNativeReturnValue(runtime, bridge, type, &associated); + })); + + interop.setProperty( + runtime, "stringFromCString", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "stringFromCString"), 2, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || args[0].isNull() || args[0].isUndefined()) { + return Value::null(); + } + NativeApiArgumentFrame frame(1); + const char* data = + static_cast(pointerFromEngineValue(runtime, bridge, args[0], frame)); + if (data == nullptr) { + return Value::null(); + } + if (count > 1 && args[1].isNumber()) { + size_t length = static_cast(std::max(0, args[1].getNumber())); + return String::createFromUtf8(runtime, reinterpret_cast(data), + length); + } + return makeString(runtime, data); + })); + + interop.setProperty( + runtime, "bufferFromData", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "bufferFromData"), 1, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + throw JSError(runtime, "Invalid data."); + } + Object object = args[0].asObject(runtime); + if (object.isArrayBuffer(runtime)) { + return Value(runtime, object); + } + id native = nil; + if (object.isHostObject(runtime)) { + native = object.getHostObject(runtime)->object(); + } else if (object.isHostObject(runtime)) { + native = static_cast( + object.getHostObject(runtime)->pointer()); + } + if (native == nil || ![native isKindOfClass:[NSData class]]) { + throw JSError(runtime, "Invalid data."); + } + NSData* data = static_cast(native); + return ArrayBuffer(runtime, std::make_shared( + data.bytes, static_cast(data.length))); + })); + + interop.setProperty( + runtime, "addMethod", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "addMethod"), 2, + [](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + throw JSError(runtime, "interop.addMethod requires the Engine class builder layer."); + })); + interop.setProperty( + runtime, "addProtocol", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "addProtocol"), 2, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 2) { + throw JSError(runtime, "interop.addProtocol expects class and protocol."); + } + Class cls = classFromEngineValue(runtime, args[0]); + Protocol* protocol = protocolFromEngineValue(runtime, args[1]); + if (cls == Nil || protocol == nullptr) { + return false; + } + return class_addProtocol(cls, protocol); + })); + + return interop; +} diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Appearance.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Appearance.mm new file mode 100644 index 000000000..961c1b0f0 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Appearance.mm @@ -0,0 +1,321 @@ +// UIAppearance proxy primitives. +// +// `[SomeView appearance]` (and the whenContainedIn:/appearanceWhenContainedIn: +// variants) hands back an opaque `_UIAppearance` proxy, not a real instance of +// the class — UIKit forwards whatever selectors it recognizes to an internal +// invocation-recording store instead of actually executing them. There is no +// public, introspectable way to ask one of these proxies "what class are you +// a proxy for" other than parsing its `-description`, which UIKit formats as +// ``. Everything below exists to (a) recover +// that class from the description once, tag it onto the wrapped object as an +// expando so we don't have to re-parse on every access, and (b) cache +// get/set values in a class-keyed expando store (keyed on the customizable +// class, not the proxy instance — UIAppearance state is effectively global +// per class/containment-chain, not per proxy object) so repeated reads see +// the value a set() just wrote instead of round-tripping back into UIKit's +// opaque recording machinery. Setters ALSO cache: an appearance proxy setter +// doesn't reliably support read-your-write, so we do it ourselves. + +// Forward declaration: runtimeWritablePropertySetter is defined later in +// Object.mm (this file is included before it), but is needed here by +// makeAppearanceProxyPropertySetter's non-metadata-setter fallback. +std::optional runtimeWritablePropertySetter( + id object, const std::string& property); + +constexpr const char* kNativeApiAppearanceClassNameExpando = + "__nativeApiAppearanceClassName"; + +// Parses UIKit's `` description format. +Class appearanceProxyCustomizableClassFromExactDescription(id object) { +#if TARGET_OS_IPHONE + if (object == nil) { + return Nil; + } + + NSString* description = [object description]; + NSString* prefix = @""]) { + return Nil; + } + + NSRange classNameRange = + NSMakeRange(prefix.length, description.length - prefix.length - 1); + NSString* className = [description substringWithRange:classNameRange]; + return NSClassFromString(className); +#else + return Nil; +#endif +} + +// The customizable class an appearance proxy wraps, preferring the tagged +// expando (set once by tagStaticAppearanceNativeResult) over re-parsing the +// description on every access. +// +// The description-parsing fallback below sends a real `-description` message +// to `object` -- an arbitrary, potentially-overridden Objective-C method, not +// a safe runtime introspection call. It must NEVER run while `object` is a +// callback argument native code just handed to JS reentrantly (tracked by +// gNativeCallerThreadEngineCallbackDepth, incremented for the duration of +// exactly that kind of invocation -- see callOnNativeCallerThread in +// Callbacks.mm and its use in performNativeInvocation's skipInvoker check +// above): a real UIAppearance proxy is only ever obtained by JS calling an +// `+appearance`-family method itself (an OUTBOUND call this engine makes, +// never something delivered inbound as a callback argument), so skipping the +// fallback in that situation never regresses genuine appearance-proxy +// detection. It matters for callback arguments that are NOT appearance +// proxies but are transient/live objects UIKit hands to a block while its own +// internal machinery is still on the stack -- e.g. the +// UISheetPresentationControllerDetentResolutionContext passed to a custom +// detent's resolver block. Calling `-description` on that object from inside +// the resolver deadlocks (confirmed: an otherwise-identical resolver that +// never touches the context argument returns cleanly; the hang is inside +// this function, specifically in the `-description` send, per an os_log +// breadcrumb trace -- see item 2 of the parity-tail investigation). This +// guard trades a rare caching miss (a brand-new, untagged appearance proxy +// obtained for the first time from inside some other callback) for +// eliminating that deadlock class entirely. +Class taggedAppearanceProxyClass( + Runtime& runtime, const std::shared_ptr& bridge, + id object) { + if (object == nil || bridge == nullptr) { + return Nil; + } + + Value classNameValue = bridge->findObjectExpando( + runtime, object, kNativeApiAppearanceClassNameExpando); + if (!classNameValue.isString()) { + if (gNativeCallerThreadEngineCallbackDepth > 0) { + return Nil; + } + return appearanceProxyCustomizableClassFromExactDescription(object); + } + + std::string className = + classNameValue.asString(runtime).utf8(runtime); + return objc_lookUpClass(className.c_str()); +} + +std::string appearanceProxyExpandoPropertyKey( + const std::string& property) { + return "__nativeApiAppearance:" + property; +} + +// Cached class-keyed (not proxy-instance-keyed — see file header) UIAppearance +// property value. +Value cachedAppearanceProxyPropertyValue( + Runtime& runtime, const std::shared_ptr& bridge, + id object, const std::string& property) { + if (Class appearanceClass = + taggedAppearanceProxyClass(runtime, bridge, object)) { + return bridge->findObjectExpando( + runtime, appearanceClass, appearanceProxyExpandoPropertyKey(property)); + } + return Value::undefined(); +} + +void cacheAppearanceProxyPropertyValue( + Runtime& runtime, const std::shared_ptr& bridge, + id object, const std::string& property, const Value& value) { + if (Class appearanceClass = + taggedAppearanceProxyClass(runtime, bridge, object)) { + bridge->setObjectExpando(runtime, appearanceClass, + appearanceProxyExpandoPropertyKey(property), + value); + } +} + +// Picks the more capable of two candidate members exposing the same property +// name (prefers writable over readonly, a member with a known setter +// selector, a member with resolved signature metadata). +const NativeApiMember* betterAppearanceProxyAccessorMember( + const NativeApiMember* current, const NativeApiMember& candidate) { + if (current == nullptr) { + return &candidate; + } + if (current->readonly != candidate.readonly) { + return candidate.readonly ? current : &candidate; + } + if (current->setterSelectorName.empty() && + !candidate.setterSelectorName.empty()) { + return &candidate; + } + if (current->signatureOffset == MD_SECTION_OFFSET_NULL && + candidate.signatureOffset != MD_SECTION_OFFSET_NULL) { + return &candidate; + } + return current; +} + +const NativeApiMember* selectAppearanceProxyPropertyMember( + const std::vector& members, const std::string& property) { + const NativeApiMember* selected = nullptr; + for (const auto& member : members) { + if (!member.property || member.name != property) { + continue; + } + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic) { + continue; + } + selected = betterAppearanceProxyAccessorMember(selected, member); + } + return selected; +} + +// True for a `[SomeClass appearance...]` static-selector call — the class +// receiver + selector-name convention UIKit uses for all of the appearance +// proxy factory methods. +bool isStaticAppearanceSelector(bool receiverIsClass, + const std::string& selectorName) { + return receiverIsClass && selectorName.rfind("appearance", 0) == 0; +} + +// Recovers the customizable class from an appearance proxy's description and +// tags it onto the proxy as an expando (so future accesses don't need to +// re-parse the description). +Class tagStaticAppearanceNativeResult( + Runtime& runtime, const std::shared_ptr& bridge, + Class appearanceClass, id native) { + if (bridge == nullptr || appearanceClass == Nil || native == nil) { + return Nil; + } + Class customizableClass = + appearanceProxyCustomizableClassFromExactDescription(native); + if (customizableClass == Nil) { + return Nil; + } + const char* className = class_getName(customizableClass); + if (className == nullptr || className[0] == '\0') { + return Nil; + } + bridge->setObjectExpando(runtime, native, kNativeApiAppearanceClassNameExpando, + makeString(runtime, className)); + return customizableClass; +} + +std::shared_ptr retainAppearanceProxyForAccessor(id native) { + id retained = [native retain]; + return std::shared_ptr(static_cast(retained), [](void* value) { + [(id)value release]; + }); +} + +bool shouldInstallAppearanceProxyAccessor(const NativeApiMember& member) { + if (!member.property || member.name.empty()) { + return false; + } + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic) { + return false; + } + return member.name != "superclass" && member.name != "class" && + member.name != "constructor" && member.name != "debugDescription" && + member.name != "className" && member.name != "description"; +} + +Function makeAppearanceProxyPropertyGetter( + Runtime& runtime, std::shared_ptr bridge, id native, + std::shared_ptr retainedNative, std::string property) { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [bridge = std::move(bridge), native, retainedNative = std::move(retainedNative), + property = std::move(property)](Runtime& runtime, const Value&, + const Value*, size_t) -> Value { + return cachedAppearanceProxyPropertyValue(runtime, bridge, native, + property); + }); +} + +Function makeAppearanceProxyPropertySetter( + Runtime& runtime, std::shared_ptr bridge, id native, + std::shared_ptr retainedNative, NativeApiMember member) { + std::string functionName = member.setterSelectorName.empty() + ? member.name + : member.setterSelectorName; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, functionName.c_str()), 1, + [bridge = std::move(bridge), native, retainedNative = std::move(retainedNative), + member = std::move(member)](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 1) { + throw JSError(runtime, + "UIAppearance property setter expects a value."); + } + Value setterArgs[] = {Value(runtime, args[0])}; + if (!member.setterSelectorName.empty()) { + NativeApiMember setterMember = member; + setterMember.selectorName = member.setterSelectorName; + setterMember.signatureOffset = member.setterSignatureOffset; + callObjCSelector(runtime, bridge, native, false, + setterMember.selectorName, &setterMember, + setterArgs, 1); + } else if (auto setterSelectorName = + runtimeWritablePropertySetter(native, member.name)) { + callObjCSelector(runtime, bridge, native, false, *setterSelectorName, + nullptr, setterArgs, 1); + } else { + throw JSError(runtime, + "UIAppearance property setter is unavailable."); + } + cacheAppearanceProxyPropertyValue(runtime, bridge, native, member.name, + args[0]); + return Value::undefined(); + }); +} + +// Installs get/set accessor descriptors for every writable metadata property +// of `customizableClass` onto `resultObject` (the JS wrapper for the +// appearance proxy) — this is what makes `View.appearance().tintColor = ...` +// resolve as a real property assignment instead of requiring `.invoke(...)`. +void installAppearanceProxyPropertyAccessors( + Runtime& runtime, const std::shared_ptr& bridge, + Class customizableClass, id native, Object& resultObject) { + if (bridge == nullptr || customizableClass == Nil) { + return; + } + const NativeApiSymbol* symbol = + bridge->findClassForRuntimeClass(customizableClass); + if (symbol == nullptr) { + return; + } + + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function defineProperty = + objectConstructor.getPropertyAsFunction(runtime, "defineProperty"); + std::shared_ptr retainedNative = + retainAppearanceProxyForAccessor(native); + const auto& members = bridge->membersForClass(*symbol); + std::unordered_map accessors; + for (const auto& member : members) { + if (!shouldInstallAppearanceProxyAccessor(member)) { + continue; + } + accessors[member.name] = + betterAppearanceProxyAccessorMember(accessors[member.name], member); + } + + for (const auto& accessor : accessors) { + const NativeApiMember& member = *accessor.second; + + try { + Object descriptor(runtime); + descriptor.setProperty(runtime, "configurable", true); + descriptor.setProperty(runtime, "enumerable", false); + descriptor.setProperty( + runtime, "get", + makeAppearanceProxyPropertyGetter(runtime, bridge, native, + retainedNative, member.name)); + if (!member.readonly) { + descriptor.setProperty( + runtime, "set", + makeAppearanceProxyPropertySetter(runtime, bridge, native, + retainedNative, member)); + } + defineProperty.call(runtime, resultObject, makeString(runtime, member.name), + descriptor); + } catch (const std::exception&) { + } + } +} diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Class.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Class.mm new file mode 100644 index 000000000..500bd901c --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Class.mm @@ -0,0 +1,557 @@ +class NativeApiClassHostObject final : public HostObject { + public: + NativeApiClassHostObject(std::shared_ptr bridge, + NativeApiSymbol symbol) + : bridge_(std::move(bridge)), symbol_(std::move(symbol)) {} + + Class nativeClass() const { + return objc_lookUpClass(symbol_.runtimeName.c_str()); + } + + static Class classRespondingToClassSelector(Class cls, SEL selector) { + for (Class current = cls; current != Nil; + current = class_getSuperclass(current)) { + if (class_getClassMethod(current, selector) != nullptr) { + return current; + } + } + return Nil; + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, "class"); + } + if (property == "name") { + return makeString(runtime, symbol_.name); + } + if (property == "runtimeName") { + return makeString(runtime, symbol_.runtimeName); + } + if (property == "available") { + return objc_lookUpClass(symbol_.runtimeName.c_str()) != nil; + } + if (property == "metadataOffset") { + return static_cast(symbol_.offset); + } + if (property == "__superclass") { + if (symbol_.superclassOffset == MD_SECTION_OFFSET_NULL) { + return Value::undefined(); + } + const NativeApiSymbol* superclass = + bridge_->findClassByOffset(symbol_.superclassOffset); + if (superclass == nullptr) { + return Value::undefined(); + } + return makeNativeClassValue(runtime, bridge_, *superclass); + } + if (property == "__runtimeStaticMembers" || + property == "__runtimeInstanceMembers") { + return runtimeMembersArray(runtime, nativeClass(), + property == "__runtimeStaticMembers"); + } + if (property == "__staticMembers" || property == "__instanceMembers") { + bool staticMembers = property == "__staticMembers"; + const auto& members = bridge_->surfaceMembersForClass(symbol_); + Array result(runtime, members.size()); + size_t index = 0; + for (const auto& member : members) { + bool memberIsStatic = + (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic != staticMembers) { + continue; + } + Object descriptor(runtime); + descriptor.setProperty(runtime, "name", makeString(runtime, member.name)); + descriptor.setProperty(runtime, "selectorName", + makeString(runtime, member.selectorName)); + descriptor.setProperty( + runtime, "argumentCount", + static_cast(selectorArgumentCount(member.selectorName))); + descriptor.setProperty(runtime, "property", member.property); + descriptor.setProperty(runtime, "readonly", member.readonly); + descriptor.setProperty(runtime, "signatureOffset", + static_cast(member.signatureOffset)); + descriptor.setProperty( + runtime, "setterSignatureOffset", + static_cast(member.setterSignatureOffset)); + descriptor.setProperty(runtime, "flags", + static_cast(member.flags)); + descriptor.setProperty(runtime, "setterSelectorName", + makeString(runtime, member.setterSelectorName)); + result.setValueAtIndex(runtime, index++, descriptor); + } + Array compact(runtime, index); + for (size_t i = 0; i < index; i++) { + compact.setValueAtIndex(runtime, i, result.getValueAtIndex(runtime, i)); + } + return compact; + } + if (property == "toString") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [symbol = symbol_](Runtime& runtime, const Value&, + const Value*, size_t) -> Value { + return makeString(runtime, + "[NativeApiClass " + symbol.name + "]"); + }); + } + if (property == "construct" || property == "alloc" || property == "new") { + auto bridge = bridge_; + auto symbol = symbol_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property), 0, + [bridge, symbol, property](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + if (cls == nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol.name); + } + + id result = nil; + if (property == "construct" && count == 1) { + void* pointer = nullptr; + if (args[0].isNumber()) { + pointer = reinterpret_cast( + static_cast(args[0].getNumber())); + } else if (args[0].isObject()) { + Object object = args[0].asObject(runtime); + if (object.isHostObject(runtime)) { + auto pointerHost = + object.getHostObject( + runtime); + pointer = pointerHost->pointer(); + if (pointerHost->backingValue() != nullptr) { + Value backingValue(runtime, *pointerHost->backingValue()); + id backingObject = + NativeApiObjectHostObject::nativeObjectFromValue( + runtime, backingValue); + if (backingObject == static_cast(pointer) && + backingObject != nil && + [backingObject isKindOfClass:cls]) { + return backingValue; + } + } + } else if (object.isHostObject( + runtime)) { + auto referenceHost = + object.getHostObject( + runtime); + pointer = referenceHost->data(); + if (referenceHost->backingValue() != nullptr) { + Value backingValue(runtime, *referenceHost->backingValue()); + id backingObject = + NativeApiObjectHostObject::nativeObjectFromValue( + runtime, backingValue); + if (backingObject == static_cast(pointer) && + backingObject != nil && + [backingObject isKindOfClass:cls]) { + return backingValue; + } + } + } else if (object.isHostObject( + runtime)) { + pointer = object + .getHostObject( + runtime) + ->object(); + } + } + return makeNativeObjectValue(runtime, bridge, + static_cast(pointer), false); + } + + if (property == "new") { + if (count != 0) { + throw JSError( + runtime, "new does not take arguments; use invoke for an " + "explicit Objective-C selector."); + } + performDirectObjCInvocation(runtime, + [&]() { result = [[cls alloc] init]; }); + } else { + if (count != 0) { + throw JSError( + runtime, "alloc does not take arguments; call invoke on the " + "allocated object for an explicit init selector."); + } + performDirectObjCInvocation(runtime, + [&]() { result = [cls alloc]; }); + } + + return makeNativeObjectValue(runtime, bridge, result, true); + }); + } + if (property == "invoke" || property == "send") { + auto bridge = bridge_; + auto symbol = symbol_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 1, + [bridge, symbol](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string selectorName = + readStringArg(runtime, args, count, 0, "selector"); + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + if (cls == nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol.name); + } + return callObjCSelector(runtime, bridge, static_cast(cls), true, + selectorName, nullptr, args + 1, + count - 1); + }); + } + + Class cls = nativeClass(); + if (cls != Nil) { + Value expando = bridge_->findObjectExpando(runtime, cls, property); + if (!expando.isUndefined()) { + return expando; + } + } + + const auto& members = bridge_->membersForClass(symbol_); + // `[SomeClass appearance]` (and the whenContainedIn:/ + // appearanceWhenContainedIn: overloads) intercepted here so the returned + // opaque UIAppearance proxy gets tagged with the class it represents and + // has its property accessors installed — otherwise it would just be a + // callable selector-group function, not the property-settable object + // callers expect (see host_objects/Appearance.mm). + if (property == "appearance" && + selectorGroupEntriesForMethod(members, property, true) != nullptr) { + auto bridge = bridge_; + auto symbol = symbol_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [bridge, symbol](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + if (cls == Nil) { + throw JSError( + runtime, "Objective-C class is not available: " + + symbol.name); + } + + const auto& members = bridge->membersForClass(symbol); + const NativeApiMember* selected = + selectMethodMember(members, "appearance", true, count); + if (selected == nullptr) { + throw JSError(runtime, + "Objective-C selector is not available: appearance"); + } + + Value result = callObjCSelector( + runtime, bridge, static_cast(cls), true, + selected->selectorName, selected, args, count); + if (result.isObject()) { + Object resultObject = result.asObject(runtime); + if (resultObject.isHostObject( + runtime)) { + id native = resultObject + .getHostObject( + runtime) + ->object(); + Class customizableClass = + tagStaticAppearanceNativeResult(runtime, bridge, cls, + native); + installAppearanceProxyPropertyAccessors( + runtime, bridge, customizableClass, native, resultObject); + } + } + return result; + }); + } + + if (const NativeApiMember* propertyMember = + selectWritablePropertyMember(members, property, true)) { + auto bridge = bridge_; + auto symbol = symbol_; + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + if (cls == nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol.name); + } + SEL selector = sel_getUid(propertyMember->selectorName.c_str()); + Class dispatchClass = classRespondingToClassSelector(cls, selector); + if (dispatchClass != Nil) { + return callObjCSelector(runtime, bridge, static_cast(dispatchClass), true, + propertyMember->selectorName, propertyMember, + nullptr, 0); + } + } + + auto selectors = selectorGroupEntriesForMethod(members, property, true); + if (selectors != nullptr) { + if (cls == Nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol_.name); + } + auto preparedInvocations = std::make_shared>>(selectors->size()); + Value methodFunction = CreateNativeApiSelectorGroupFunction( + runtime, bridge_, cls, true, selectors, preparedInvocations); + bridge_->setObjectExpando(runtime, cls, property, methodFunction); + return methodFunction; + } + + return Value::undefined(); + } + + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override { + std::string property = name.utf8(runtime); + Class cls = objc_lookUpClass(symbol_.runtimeName.c_str()); + if (cls == nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol_.name); + } + + const auto& members = bridge_->membersForClass(symbol_); + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, true)) { + if (propertyMember->readonly) { + throw JSError( + runtime, "Attempted to assign to readonly property."); + } + NativeApiMember setterMember = *propertyMember; + setterMember.selectorName = propertyMember->setterSelectorName; + setterMember.signatureOffset = propertyMember->setterSignatureOffset; + SEL selector = sel_getUid(setterMember.selectorName.c_str()); + Class dispatchClass = classRespondingToClassSelector(cls, selector); + if (dispatchClass == Nil) { + throw JSError(runtime, + "Objective-C selector is not available: " + + setterMember.selectorName); + } + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, static_cast(dispatchClass), true, + setterMember.selectorName, &setterMember, args, 1); + NATIVE_API_SET_RETURN(true); + } + + throw JSError(runtime, + "No writable native property: " + property); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + names.reserve(8); + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "name"); + addPropertyName(runtime, names, "runtimeName"); + addPropertyName(runtime, names, "available"); + addPropertyName(runtime, names, "metadataOffset"); + addPropertyName(runtime, names, "toString"); + addPropertyName(runtime, names, "construct"); + addPropertyName(runtime, names, "alloc"); + addPropertyName(runtime, names, "new"); + addPropertyName(runtime, names, "invoke"); + addPropertyName(runtime, names, "send"); + return names; + } + + private: + std::shared_ptr bridge_; + NativeApiSymbol symbol_; +}; + +Value makeNativeObjectValue(Runtime& runtime, + const std::shared_ptr& bridge, + id object, bool ownsObject, + Class superDispatchClass) { + if (object == nil) { + return Value::null(); + } + + Value cached = bridge->findRoundTripValue(runtime, object, nullptr, true); + if (!cached.isUndefined()) { + // A consumed wrapper (e.g. an alloc'd placeholder singleton already passed + // to an initializer) must not be reused: drop the stale entry and re-wrap. + auto cachedHost = + cached.isObject() + ? cached.asObject(runtime).getHostObject(runtime) + : nullptr; + if (cachedHost != nullptr && cachedHost->object() != nil) { + if (superDispatchClass != Nil) { + cachedHost->setSuperDispatchClass(superDispatchClass); + } + if (ownsObject) { + [object release]; + } + return cached; + } + bridge->forgetRoundTripValue(runtime, object); + } + + Object result = createNativeInstanceHostObject( + runtime, + std::make_shared( + bridge, object, ownsObject, superDispatchClass)); + Value prototypeValue = Value::undefined(); + Value classWrapperValue = + bridge->findObjectExpando(runtime, object, "__nativeApiClassWrapper"); + if (classWrapperValue.isObject()) { + Object classWrapper = classWrapperValue.asObject(runtime); + prototypeValue = classWrapper.getProperty(runtime, "prototype"); + } + if (!prototypeValue.isObject()) { + prototypeValue = bridge->findClassPrototype(runtime, object_getClass(object)); + } + if (!prototypeValue.isObject()) { + Value classWrapper = makeNativeClassValue( + runtime, bridge, + nativeApiSymbolForRuntimeClass(bridge, object_getClass(object))); + if (classWrapper.isObject()) { + prototypeValue = + classWrapper.asObject(runtime).getProperty(runtime, "prototype"); + } + } + if (prototypeValue.isObject()) { + Object prototype = prototypeValue.asObject(runtime); + SetNativeApiObjectPrototype(runtime, result, prototype); + } + bridge->rememberScopedRoundTripValue( + runtime, object, Value(runtime, result), + nativeObjectIsStringLike(object)); + return result; +} + +Value globalNativeSymbolValue(Runtime& runtime, const NativeApiSymbol& symbol, + const char* expectedKind) { + Object global = runtime.global(); + Value cacheValue = global.getProperty( + runtime, "__nativeScriptNativeApiGlobalCache"); + if (!cacheValue.isObject()) { + return Value::undefined(); + } + + Object cache = cacheValue.asObject(runtime); + auto readCache = [&](const std::string& name) -> Value { + if (name.empty()) { + return Value::undefined(); + } + + Value value = cache.getProperty(runtime, name.c_str()); + if (!value.isObject()) { + return Value::undefined(); + } + + try { + Object object = value.asObject(runtime); + Value kindValue = object.getProperty(runtime, "kind"); + if (kindValue.isString() && + kindValue.asString(runtime).utf8(runtime) == expectedKind) { + return value; + } + } catch (const std::exception&) { + } + + return Value::undefined(); + }; + + Value value = readCache(symbol.name); + if (!value.isUndefined()) { + return value; + } + if (symbol.runtimeName != symbol.name) { + value = readCache(symbol.runtimeName); + if (!value.isUndefined()) { + return value; + } + } + + try { + if (std::strcmp(expectedKind, "class") == 0) { + Value classResolverValue = global.getProperty( + runtime, "__nativeScriptResolveNativeApiClassWrapper"); + if (classResolverValue.isObject() && + classResolverValue.asObject(runtime).isFunction(runtime)) { + Function classResolver = + classResolverValue.asObject(runtime).asFunction(runtime); + auto resolveClassWrapper = [&](const std::string& name) -> Value { + if (name.empty()) { + return Value::undefined(); + } + Value resolved = classResolver.call(runtime, makeString(runtime, name)); + return resolved.isObject() ? std::move(resolved) : Value::undefined(); + }; + + value = resolveClassWrapper(symbol.name); + if (!value.isUndefined()) { + return value; + } + if (symbol.runtimeName != symbol.name) { + value = resolveClassWrapper(symbol.runtimeName); + if (!value.isUndefined()) { + return value; + } + } + } + } + + Value resolverValue = + global.getProperty(runtime, "__nativeScriptResolveNativeApiGlobal"); + if (resolverValue.isObject() && + resolverValue.asObject(runtime).isFunction(runtime)) { + Function resolver = resolverValue.asObject(runtime).asFunction(runtime); + auto resolveGlobal = [&](const std::string& name) -> Value { + if (name.empty()) { + return Value::undefined(); + } + Value resolved = resolver.call(runtime, makeString(runtime, name), + makeString(runtime, expectedKind)); + if (resolved.isObject()) { + return resolved; + } + return Value::undefined(); + }; + + value = resolveGlobal(symbol.name); + if (!value.isUndefined()) { + return value; + } + if (symbol.runtimeName != symbol.name) { + value = resolveGlobal(symbol.runtimeName); + if (!value.isUndefined()) { + return value; + } + } + } + } catch (const std::exception&) { + } + + return Value::undefined(); +} + +Value makeNativeClassValue(Runtime& runtime, + const std::shared_ptr& bridge, + NativeApiSymbol symbol) { + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + Value cachedClass = bridge->findClassValue(runtime, cls); + if (!cachedClass.isUndefined()) { + return cachedClass; + } + Value globalValue = globalNativeSymbolValue(runtime, symbol, "class"); + if (!globalValue.isUndefined()) { + return globalValue; + } + return Object::createFromHostObject( + runtime, + std::make_shared(bridge, std::move(symbol))); +} + +Protocol* lookupProtocolByNativeName(const std::string& name) { + Protocol* protocol = objc_getProtocol(name.c_str()); + if (protocol != nullptr) { + return protocol; + } + constexpr const char* suffix = "Protocol"; + size_t suffixLength = std::strlen(suffix); + if (name.size() > suffixLength && + name.compare(name.size() - suffixLength, suffixLength, suffix) == 0) { + protocol = objc_getProtocol( + name.substr(0, name.size() - suffixLength).c_str()); + } + return protocol; +} diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Interop.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Interop.mm new file mode 100644 index 000000000..5913da68d --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Interop.mm @@ -0,0 +1,225 @@ +class NativeApiPointerHostObject final + : public HostObject, + public std::enable_shared_from_this { + public: + NativeApiPointerHostObject(std::shared_ptr bridge, + void* pointer, std::string kind = "pointer", + bool adopted = false, + std::shared_ptr backingValue = nullptr) + : bridge_(std::move(bridge)), + pointer_(pointer), + kind_(std::move(kind)), + adopted_(adopted), + backingValue_(std::move(backingValue)) {} + + ~NativeApiPointerHostObject() override { + if (adopted_ && pointer_ != nullptr) { + if (bridge_ != nullptr) { + bridge_->forgetPointerValue(pointer_); + } + free(pointer_); + pointer_ = nullptr; + } + } + + void* pointer() const { return pointer_; } + std::shared_ptr backingValue() const { return backingValue_; } + void setBackingValue(Runtime& runtime, const Value& value) { + backingValue_ = std::make_shared(runtime, value); + } + bool adopted() const { return adopted_; } + void adopt() { adopted_ = true; } + void clearWithoutFree() { + if (bridge_ != nullptr) { + bridge_->forgetPointerValue(pointer_); + } + pointer_ = nullptr; + adopted_ = false; + backingValue_.reset(); + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, kind_); + } + if (property == "address") { + return static_cast(reinterpret_cast(pointer_)); + } + if (property == "adopted") { + return adopted_; + } + if (property == "takeRetainedValue" || property == "takeUnretainedValue") { + bool retained = property == "takeRetainedValue"; + std::weak_ptr weakSelf = shared_from_this(); + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [weakSelf, retained](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + auto self = weakSelf.lock(); + if (!self || self->pointer_ == nullptr || self->consumed_) { + throw JSError(runtime, "Unmanaged value has already been consumed."); + } + id object = static_cast(self->pointer_); + self->consumed_ = true; + self->pointer_ = nullptr; + self->adopted_ = false; + self->backingValue_.reset(); + return makeNativeObjectValue(runtime, self->bridge_, object, retained); + }); + } + if (property == "add" || property == "subtract") { + void* pointer = pointer_; + bool add = property == "add"; + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 1, + [bridge, pointer, add](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isNumber()) { + throw JSError(runtime, "Pointer offset must be a number."); + } + intptr_t offset = static_cast(args[0].getNumber()); + intptr_t base = reinterpret_cast(pointer); + void* result = reinterpret_cast(add ? base + offset : base - offset); + return createPointer(runtime, bridge, result); + }); + } + if (property == "toNumber") { + void* pointer = pointer_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toNumber"), 0, + [pointer](Runtime&, const Value&, const Value*, size_t) -> Value { + return static_cast(reinterpret_cast(pointer)); + }); + } + if (property == "toBigInt") { + void* pointer = pointer_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toBigInt"), 0, + [pointer](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return BigInt::fromUint64( + runtime, + static_cast(reinterpret_cast(pointer))); + }); + } + if (property == "toHexString" || property == "toDecimalString") { + void* pointer = pointer_; + bool hex = property == "toHexString"; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [pointer, hex](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + if (hex) { + char text[2 + sizeof(uintptr_t) * 2 + 1] = {}; + snprintf(text, sizeof(text), "0x%llx", + static_cast( + reinterpret_cast(pointer))); + return makeString(runtime, text); + } else { + char text[32] = {}; + snprintf(text, sizeof(text), "%lld", + static_cast(reinterpret_cast(pointer))); + return makeString(runtime, text); + } + }); + } + if (property == "toString") { + void* pointer = pointer_; + std::string kind = kind_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [pointer, kind](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", pointer); + if (kind == "pointer") { + return makeString(runtime, + ""); + } + return makeString(runtime, "[NativeApi " + kind + " " + + std::string(address) + "]"); + }); + } + return Value::undefined(); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + names.reserve(3); + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "address"); + addPropertyName(runtime, names, "adopted"); + addPropertyName(runtime, names, "takeRetainedValue"); + addPropertyName(runtime, names, "takeUnretainedValue"); + addPropertyName(runtime, names, "add"); + addPropertyName(runtime, names, "subtract"); + addPropertyName(runtime, names, "toNumber"); + addPropertyName(runtime, names, "toBigInt"); + addPropertyName(runtime, names, "toHexString"); + addPropertyName(runtime, names, "toDecimalString"); + addPropertyName(runtime, names, "toString"); + return names; + } + + private: + std::shared_ptr bridge_; + void* pointer_ = nullptr; + std::string kind_; + bool adopted_ = false; + bool consumed_ = false; + std::shared_ptr backingValue_; +}; + +class NativeApiReferenceHostObject final : public HostObject { + public: + NativeApiReferenceHostObject(std::shared_ptr bridge, + NativeApiType type, void* data, bool ownsData, + size_t byteLength = 0, + std::shared_ptr pendingValue = nullptr, + std::shared_ptr backingValue = nullptr) + : bridge_(std::move(bridge)), + type_(std::move(type)), + data_(data), + ownsData_(ownsData), + byteLength_(byteLength), + pendingValue_(std::move(pendingValue)), + backingValue_(std::move(backingValue)) {} + + ~NativeApiReferenceHostObject() override { + for (id object : retainedObjects_) { + [object release]; + } + if (ownsData_ && data_ != nullptr) { + free(data_); + data_ = nullptr; + } + } + + void* data() const { return data_; } + const NativeApiType& type() const { return type_; } + std::shared_ptr backingValue() const { return backingValue_; } + void ensureStorage(Runtime& runtime, NativeApiType type, + NativeApiArgumentFrame& frame, size_t elements = 1); + void retainObjectSlot(size_t index, id object); + + Value get(Runtime& runtime, const PropNameID& name) override; + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override; + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "value"); + addPropertyName(runtime, names, "address"); + addPropertyName(runtime, names, "toString"); + return names; + } + + private: + std::shared_ptr bridge_; + NativeApiType type_; + void* data_ = nullptr; + bool ownsData_ = false; + size_t byteLength_ = 0; + std::shared_ptr pendingValue_; + std::shared_ptr backingValue_; + std::vector retainedObjects_; +}; diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm new file mode 100644 index 000000000..18eb2130f --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm @@ -0,0 +1,1668 @@ +class NativeApiFastEnumerationIteratorHostObject final : public HostObject { + public: + NativeApiFastEnumerationIteratorHostObject( + std::shared_ptr bridge, id collection) + : bridge_(std::move(bridge)), collection_(collection) { + [(id)collection_ retain]; + } + + ~NativeApiFastEnumerationIteratorHostObject() override { + [(id)collection_ release]; + collection_ = nil; + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "next") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "next"), 0, + [this](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return next(runtime); + }); + } + return Value::undefined(); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + addPropertyName(runtime, names, "next"); + return names; + } + + private: + Value next(Runtime& runtime) { + Object result(runtime); + if (done_ || collection_ == nil) { + result.setProperty(runtime, "done", true); + return result; + } + + if (stackIndex_ >= stackLength_) { + stackLength_ = [collection_ countByEnumeratingWithState:&state_ + objects:stack_ + count:16]; + stackIndex_ = 0; + if (stackLength_ == 0) { + done_ = true; + result.setProperty(runtime, "done", true); + return result; + } + } + + id value = state_.itemsPtr[stackIndex_++]; + NativeApiType valueType = nativeObjectReturnTypeForClass(object_getClass(value)); + result.setProperty(runtime, "value", + convertNativeReturnValue(runtime, bridge_, valueType, &value)); + result.setProperty(runtime, "done", false); + return result; + } + + std::shared_ptr bridge_; + id collection_ = nil; + NSFastEnumerationState state_ = {}; + id __unsafe_unretained stack_[16] = {}; + NSUInteger stackLength_ = 0; + NSUInteger stackIndex_ = 0; + bool done_ = false; +}; + +NativeApiSymbol nativeApiSymbolForRuntimeClass( + const std::shared_ptr& bridge, Class cls) { + const char* name = cls != Nil ? class_getName(cls) : ""; + if (bridge != nullptr) { + if (const NativeApiSymbol* symbol = bridge->findClassForRuntimePointer(cls)) { + return *symbol; + } + if (const NativeApiSymbol* symbol = bridge->findClassForRuntimeClass(cls)) { + return *symbol; + } + if (name != nullptr) { + if (const NativeApiSymbol* symbol = bridge->findClass(name)) { + return *symbol; + } + } + } + + return NativeApiSymbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; +} + +std::optional runtimeWritablePropertySetter(id object, + const std::string& property) { + if (object == nil || property.empty()) { + return std::nullopt; + } + + Class current = object_getClass(object); + while (current != Nil) { + objc_property_t prop = class_getProperty(current, property.c_str()); + if (prop != nullptr) { + if (char* readonly = property_copyAttributeValue(prop, "R")) { + free(readonly); + return std::nullopt; + } + + std::string setter = setterSelectorForProperty(property); + if (char* customSetter = property_copyAttributeValue(prop, "S")) { + setter = customSetter; + free(customSetter); + } + + SEL selector = sel_getUid(setter.c_str()); + if ([object respondsToSelector:selector]) { + return setter; + } + } + + current = class_getSuperclass(current); + } + + std::string setter = setterSelectorForProperty(property); + SEL selector = sel_getUid(setter.c_str()); + if ([object respondsToSelector:selector]) { + return setter; + } + + return std::nullopt; +} + +std::optional resolveRuntimeReadablePropertyGetter( + id object, const std::string& property) { + Class current = object_getClass(object); + while (current != Nil) { + objc_property_t prop = class_getProperty(current, property.c_str()); + if (prop != nullptr) { + std::string getter = property; + if (char* customGetter = property_copyAttributeValue(prop, "G")) { + getter = customGetter; + free(customGetter); + } + + if (auto selector = + respondingPropertyGetterSelector(object, property, getter)) { + return selector; + } + } + + current = class_getSuperclass(current); + } + + return respondingPropertyGetterSelector(object, property, property); +} + +// Caches the resolved getter selector per (class, property): this path only +// serves JS-subclass instances (the hot metadata-getter path already has +// findCachedPropertyGetter in front, see below), but the objc-runtime class +// walk above is still worth amortizing across repeated accesses. +std::optional runtimeReadablePropertyGetter(id object, + const std::string& property) { + if (object == nil || property.empty()) { + return std::nullopt; + } + + Class cls = object_getClass(object); + static std::mutex cacheMutex; + static std::unordered_map>> + cache; + + { + std::lock_guard lock(cacheMutex); + auto classIt = cache.find(cls); + if (classIt != cache.end()) { + auto propertyIt = classIt->second.find(property); + if (propertyIt != classIt->second.end()) { + return propertyIt->second; + } + } + } + + std::optional resolved = + resolveRuntimeReadablePropertyGetter(object, property); + + std::lock_guard lock(cacheMutex); + cache[cls][property] = resolved; + return resolved; +} + +// True if `property` resolves to a real runtime getter (metadata property or +// a JS-subclass instance's own class-builder-registered getter) — used to +// decide whether a successful runtime SET also needs an expando write so a +// subsequent GET (which may not consult the same runtime path) sees it. +bool objectGetPathCanReadRuntimeProperty(id object, + const std::string& property) { + if (object == nil || property.empty()) { + return false; + } + + if (class_conformsToProtocol(object_getClass(object), + @protocol(NativeApiClassBuilderProtocol))) { + return runtimeReadablePropertyGetter(object, property).has_value(); + } + + if (objc_property_t prop = + class_getProperty(object_getClass(object), property.c_str())) { + std::string getter = property; + if (char* customGetter = property_copyAttributeValue(prop, "G")) { + getter = customGetter; + free(customGetter); + } + return respondingPropertyGetterSelector(object, property, getter) + .has_value(); + } + + return false; +} + +class NativeApiSuperHostObject final : public HostObject { + public: + NativeApiSuperHostObject(std::shared_ptr bridge, + id receiver, Class dispatchClass) + : bridge_(std::move(bridge)), + receiver_(receiver), + dispatchClass_(dispatchClass) { + if (receiver_ != nil) { + [receiver_ retain]; + } + } + + ~NativeApiSuperHostObject() override { + if (receiver_ != nil) { + [receiver_ release]; + receiver_ = nil; + } + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, "super"); + } + if (property == "toString") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return makeString(runtime, "[NativeApiSuper]"); + }); + } + if (receiver_ == nil || dispatchClass_ == Nil) { + return Value::undefined(); + } + + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(dispatchClass_)) { + const auto& members = bridge_->membersForClass(*symbol); + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, false)) { + SEL selector = sel_getUid(propertyMember->selectorName.c_str()); + if (class_getInstanceMethod(dispatchClass_, selector) != nullptr) { + return callObjCSelector(runtime, bridge_, receiver_, false, + propertyMember->selectorName, propertyMember, + nullptr, 0, dispatchClass_); + } + } + + if (hasMethodMember(members, property, false)) { + auto bridge = bridge_; + id receiver = receiver_; + Class dispatchClass = dispatchClass_; + std::string memberName = property; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [bridge, receiver, dispatchClass, memberName]( + Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + const NativeApiSymbol* symbol = + bridge->findClassForRuntimeClass(dispatchClass); + if (symbol == nullptr) { + throw JSError( + runtime, "Objective-C metadata is not available for super."); + } + const NativeApiMember* selected = selectMethodMember( + bridge->membersForClass(*symbol), memberName, false, count); + if (selected == nullptr) { + throw JSError( + runtime, "Objective-C super selector is not available: " + + memberName); + } + return callObjCSelector(runtime, bridge, receiver, false, + selected->selectorName, selected, args, + count, dispatchClass); + }); + } + } + + return Value::undefined(); + } + + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override { + std::string property = name.utf8(runtime); + if (receiver_ == nil || dispatchClass_ == Nil) { + throw JSError(runtime, "Cannot set property on nil super."); + } + + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(dispatchClass_)) { + const auto& members = bridge_->membersForClass(*symbol); + if (const NativeApiMember* propertyMember = + selectWritablePropertyMember(members, property, false)) { + if (propertyMember->readonly || + propertyMember->setterSelectorName.empty()) { + throw JSError( + runtime, "Attempted to assign to readonly property."); + } + NativeApiMember setterMember = *propertyMember; + setterMember.selectorName = propertyMember->setterSelectorName; + setterMember.signatureOffset = propertyMember->setterSignatureOffset; + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, receiver_, false, + setterMember.selectorName, &setterMember, args, 1, + dispatchClass_); + NATIVE_API_SET_RETURN(true); + } + } + + std::string setterSelectorName = setterSelectorForProperty(property); + SEL selector = sel_getUid(setterSelectorName.c_str()); + if (class_getInstanceMethod(dispatchClass_, selector) != nullptr) { + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, receiver_, false, setterSelectorName, + nullptr, args, 1, dispatchClass_); + NATIVE_API_SET_RETURN(true); + } + + throw JSError(runtime, + "No writable native super property: " + + property); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "toString"); + return names; + } + + private: + std::shared_ptr bridge_; + id receiver_ = nil; + Class dispatchClass_ = Nil; +}; + +struct NativeApiRuntimeMember { + std::string name; + std::string selectorName; + size_t argumentCount = 0; +}; + +using NativeApiRuntimeMembers = std::vector; + +struct NativeApiRuntimeMemberIndex { + NativeApiRuntimeMembers members; + std::unordered_set memberNames; + std::unordered_map> + selectorsByNameAndCount; +}; + +struct NativeApiRuntimeMembersCacheKey { + Class cls = Nil; + bool staticMembers = false; + + bool operator==(const NativeApiRuntimeMembersCacheKey& other) const { + return cls == other.cls && staticMembers == other.staticMembers; + } +}; + +struct NativeApiRuntimeMembersCacheKeyHash { + size_t operator()(const NativeApiRuntimeMembersCacheKey& key) const { + size_t classHash = std::hash{}(reinterpret_cast(key.cls)); + return classHash ^ (key.staticMembers ? 0x9e3779b97f4a7c15ULL : 0); + } +}; + +std::mutex& runtimeMembersCacheMutex() { + static std::mutex mutex; + return mutex; +} + +std::unordered_map, + NativeApiRuntimeMembersCacheKeyHash>& +runtimeMembersCache() { + static std::unordered_map, + NativeApiRuntimeMembersCacheKeyHash> + cache; + return cache; +} + +std::shared_ptr emptyRuntimeMembers() { + static auto empty = std::make_shared(); + return empty; +} + +NativeApiRuntimeMemberIndex buildRuntimeMembersForClass(Class cls, + bool staticMembers) { + NativeApiRuntimeMemberIndex index; + if (cls == Nil) { + return index; + } + + std::unordered_set seen; + Class current = staticMembers ? object_getClass(cls) : cls; + while (current != Nil) { + unsigned int methodCount = 0; + Method* methods = class_copyMethodList(current, &methodCount); + for (unsigned int i = 0; i < methodCount; i++) { + SEL selector = method_getName(methods[i]); + const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorName == nullptr || selectorName[0] == '\0') { + continue; + } + + std::string selectorString(selectorName); + std::string name = jsifySelector(selectorString.c_str()); + if (name.empty()) { + continue; + } + + size_t argumentCount = selectorArgumentCount(selectorString); + std::string key = name + "\x1f" + std::to_string(argumentCount); + if (!seen.insert(key).second) { + continue; + } + + index.memberNames.insert(name); + index.selectorsByNameAndCount[name].emplace(argumentCount, selectorString); + index.members.push_back(NativeApiRuntimeMember{ + .name = std::move(name), + .selectorName = std::move(selectorString), + .argumentCount = argumentCount, + }); + } + if (methods != nullptr) { + free(methods); + } + current = class_getSuperclass(current); + } + + return index; +} + +std::shared_ptr runtimeMembersForClass( + Class cls, bool staticMembers) { + if (cls == Nil) { + return emptyRuntimeMembers(); + } + + NativeApiRuntimeMembersCacheKey key{.cls = cls, + .staticMembers = staticMembers}; + + { + std::lock_guard lock(runtimeMembersCacheMutex()); + auto& cache = runtimeMembersCache(); + auto cached = cache.find(key); + if (cached != cache.end()) { + return cached->second; + } + } + + auto members = + std::make_shared( + buildRuntimeMembersForClass(cls, staticMembers)); + + { + std::lock_guard lock(runtimeMembersCacheMutex()); + auto& cache = runtimeMembersCache(); + auto [cached, inserted] = cache.emplace(key, members); + return inserted ? members : cached->second; + } +} + +bool hasRuntimeMemberForName(Class cls, bool staticMembers, + const std::string& name) { + auto index = runtimeMembersForClass(cls, staticMembers); + return index->memberNames.find(name) != index->memberNames.end(); +} + +std::optional selectRuntimeSelectorForName( + Class cls, bool staticMembers, const std::string& name, size_t count) { + auto index = runtimeMembersForClass(cls, staticMembers); + auto selectorsForName = index->selectorsByNameAndCount.find(name); + if (selectorsForName == index->selectorsByNameAndCount.end()) { + return std::nullopt; + } + auto selector = selectorsForName->second.find(count); + if (selector == selectorsForName->second.end()) { + return std::nullopt; + } + return selector->second; +} + +Array runtimeMembersArray(Runtime& runtime, Class cls, bool staticMembers) { + auto index = runtimeMembersForClass(cls, staticMembers); + Array result(runtime, index->members.size()); + for (size_t i = 0; i < index->members.size(); i++) { + const auto& member = index->members[i]; + Object descriptor(runtime); + descriptor.setProperty(runtime, "name", makeString(runtime, member.name)); + descriptor.setProperty(runtime, "selectorName", + makeString(runtime, member.selectorName)); + descriptor.setProperty(runtime, "argumentCount", + static_cast(member.argumentCount)); + descriptor.setProperty(runtime, "property", false); + descriptor.setProperty(runtime, "readonly", false); + descriptor.setProperty(runtime, "setterSelectorName", makeString(runtime, "")); + result.setValueAtIndex(runtime, i, descriptor); + } + return result; +} + +class NativeApiObjectHostObject final + : public HostObject, + public std::enable_shared_from_this { + public: + NativeApiObjectHostObject(std::shared_ptr bridge, + id object, bool ownsObject, + Class superDispatchClass = Nil) + : bridge_(std::move(bridge)), + object_(object), + ownsObject_(ownsObject), + superDispatchClass_(superDispatchClass), + lifetimeState_(std::make_shared(object)) { + if (bridge_ != nullptr && object_ != nil) { + bridge_->retainObjectExpandoOwner(object_); + } + if (object_ != nil && !ownsObject_) { + [object_ retain]; + ownsObject_ = true; + wrapperRetainedObject_ = true; + } + } + + ~NativeApiObjectHostObject() override { + if (bridge_ != nullptr && object_ != nil) { + bridge_->forgetRoundTripValue(object_); + bridge_->releaseObjectExpandoOwner( + object_, class_conformsToProtocol(object_getClass(object_), + @protocol(NativeApiClassBuilderProtocol))); + } + if (lifetimeState_ != nullptr) { + lifetimeState_->clear(); + } + if (ownsObject_ && object_ != nil) { + [object_ release]; + object_ = nil; + } + } + + id object() const { return object_; } + void setSuperDispatchClass(Class superDispatchClass) { + superDispatchClass_ = superDispatchClass; + } + std::shared_ptr lifetimeState() const { + return lifetimeState_; + } + + // Store a JS-owned property as a bridge expando (read back by get()). Used by + // engine adapters whose exotic property storage doesn't fall back to own + // properties when the host set handler defers. + void storeOwnExpando(Runtime& runtime, const std::string& property, + const Value& value) { + if (object_ != nil) { + bridge_->setObjectExpando(runtime, object_, property, value); + } + } + + void disownObject(id expected, bool preserveExpandos = false) { + if (object_ == expected) { + if (bridge_ != nullptr && expected != nil) { + bridge_->forgetRoundTripValue(expected); + bridge_->releaseObjectExpandoOwner(expected, preserveExpandos); + } + ownsObject_ = false; + wrapperRetainedObject_ = false; + object_ = nil; + if (lifetimeState_ != nullptr) { + lifetimeState_->clear(); + } + } + } + + // Disown without forgetting the round-trip value: used when a wrapper is + // being replaced (e.g. an initializer returned a different/self receiver) + // but the native receiver itself is staying alive and must keep resolving + // to the SAME preserved engine value on the next lookup. Unlike + // disownObject(), this does not call forgetRoundTripValue — losing that + // would let a second, divergent wrapper get created for the same native + // receiver. releaseObjectExpandoOwner still runs (to keep the refcount + // balanced against the matching retain in the constructor), but with + // preserveExpandos=true so the receiver's expando state also survives. + void detachObjectPreservingBridgeState(id expected) { + if (object_ != expected) { + return; + } + + id object = object_; + bool releaseObject = ownsObject_; + if (bridge_ != nullptr && expected != nil) { + bridge_->releaseObjectExpandoOwner(expected, /*preserveExpandos=*/true); + } + ownsObject_ = false; + wrapperRetainedObject_ = false; + object_ = nil; + if (lifetimeState_ != nullptr) { + lifetimeState_->clear(); + } + if (releaseObject && object != nil) { + [object release]; + } + } + + static bool isInitializerSelector(const std::string& selectorName) { + return selectorName.rfind("init", 0) == 0; + } + + static id nativeObjectFromValue(Runtime& runtime, const Value& value) { + if (!value.isObject()) { + return nil; + } + Object object = value.asObject(runtime); + if (!object.isHostObject(runtime)) { + return nil; + } + return object.getHostObject(runtime)->object(); + } + + static Value descriptionString(Runtime& runtime, id object) { + NSString* description = nil; + performDirectObjCInvocation(runtime, [&]() { + description = [(object != nil ? [object description] : @"") copy]; + }); + std::string text = description.UTF8String ?: ""; + [description release]; + return makeString(runtime, text); + } + + void finishInitializer(Runtime& runtime, id receiver, Value& result, + const std::optional& classWrapper) { + id resultObject = nativeObjectFromValue(runtime, result); + std::shared_ptr resultHost; + if (result.isObject()) { + Object resultValue = result.asObject(runtime); + if (resultValue.isHostObject(runtime)) { + resultHost = resultValue.getHostObject(runtime); + } + } + + const bool returnedThisWrapper = resultHost.get() == this; + disownObject(receiver, resultObject == receiver); + if (resultObject == nil) { + return; + } + + if (returnedThisWrapper) { + // disownObject transfers the receiver's existing ownership. Restoring it + // here must not add another retain. + object_ = resultObject; + ownsObject_ = true; + wrapperRetainedObject_ = false; + if (bridge_ != nullptr) { + bridge_->retainObjectExpandoOwner(object_); + } + if (lifetimeState_ != nullptr) { + lifetimeState_->setObject(object_); + } + } + + if (classWrapper) { + bridge_->setObjectExpando(runtime, resultObject, + "__nativeApiClassWrapper", + Value(runtime, *classWrapper)); + Value prototypeValue = classWrapper->getProperty(runtime, "prototype"); + if (prototypeValue.isObject()) { + Object resultValue = result.asObject(runtime); + Object prototype = prototypeValue.asObject(runtime); + SetNativeApiObjectPrototype(runtime, resultValue, prototype); + } + } + } + + Value callObjectSelector(Runtime& runtime, const std::string& selectorName, + const NativeApiMember* member, const Value* args, + size_t count, Class dispatchSuperClass = Nil) { + id receiver = object_; + if (receiver == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + + const bool initializer = isInitializerSelector(selectorName); + std::optional classWrapper; + if (initializer) { + Value classWrapperValue = bridge_->findObjectExpando( + runtime, receiver, "__nativeApiClassWrapper"); + if (classWrapperValue.isObject()) { + classWrapper.emplace(classWrapperValue.asObject(runtime)); + } + bridge_->forgetRoundTripValue(runtime, receiver); + } + + Value result = + callObjCSelector(runtime, bridge_, receiver, false, selectorName, member, + args, count, dispatchSuperClass); + if (initializer) { + finishInitializer(runtime, receiver, result, classWrapper); + } + return result; + } + + Value callPreparedObjectSelector( + Runtime& runtime, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass = Nil) { + id receiver = object_; + if (receiver == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + + const bool initializer = preparedObjCInvocationIsInit(prepared); + std::optional classWrapper; + if (initializer) { + Value classWrapperValue = bridge_->findObjectExpando( + runtime, receiver, "__nativeApiClassWrapper"); + if (classWrapperValue.isObject()) { + classWrapper.emplace(classWrapperValue.asObject(runtime)); + } + bridge_->forgetRoundTripValue(runtime, receiver); + } + + Value result = callPreparedObjCSelector( + runtime, bridge_, receiver, false, prepared, args, count, + dispatchSuperClass); + if (initializer) { + finishInitializer(runtime, receiver, result, classWrapper); + } + return result; + } + + Value classPrototypeForObject(Runtime& runtime) { + if (object_ == nil) { + return Value::undefined(); + } + + Value classWrapperValue = bridge_->findObjectExpando( + runtime, object_, "__nativeApiClassWrapper"); + if (!classWrapperValue.isObject()) { + classWrapperValue = bridge_->findClassValue(runtime, object_getClass(object_)); + } + if (!classWrapperValue.isObject()) { + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(object_getClass(object_))) { + classWrapperValue = bridge_->findClassValue( + runtime, objc_lookUpClass(symbol->runtimeName.c_str())); + } + } + if (classWrapperValue.isObject()) { + Object classWrapper = classWrapperValue.asObject(runtime); + Value prototypeValue = classWrapper.getProperty(runtime, "prototype"); + if (prototypeValue.isObject()) { + return prototypeValue; + } + } + Value prototypeValue = + bridge_->findClassPrototype(runtime, object_getClass(object_)); + if (prototypeValue.isObject()) { + return prototypeValue; + } + // Fallback for classes only known by symbol name (not yet indexed by + // runtime pointer — RN disables eager runtime-pointer indexing). + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(object_getClass(object_))) { + prototypeValue = bridge_->findClassPrototype( + runtime, objc_lookUpClass(symbol->runtimeName.c_str())); + if (prototypeValue.isObject()) { + return prototypeValue; + } + } + return Value::undefined(); + } + + Value engineThisValueForObject(Runtime& runtime) { + Value thisValue = bridge_->findRoundTripValue(runtime, object_, + nullptr, true); + if (thisValue.isObject()) { + return thisValue; + } + return makeNativeObjectValue(runtime, bridge_, object_, false); + } + + Value prototypeFunctionForProperty(Runtime& runtime, + const std::string& property) { + if (property.empty()) { + return Value::undefined(); + } + + Value prototypeValue = classPrototypeForObject(runtime); + if (!prototypeValue.isObject()) { + return Value::undefined(); + } + + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function getOwnPropertyDescriptor = + objectConstructor.getPropertyAsFunction(runtime, + "getOwnPropertyDescriptor"); + Function getPrototypeOf = + objectConstructor.getPropertyAsFunction(runtime, "getPrototypeOf"); + Value propertyName = makeString(runtime, property); + Value currentValue(runtime, prototypeValue); + + for (size_t depth = 0; depth < 64 && currentValue.isObject(); depth++) { + Object current = currentValue.asObject(runtime); + Value descriptorValue = + getOwnPropertyDescriptor.call(runtime, Value(runtime, current), + propertyName); + if (descriptorValue.isObject()) { + Value functionValue = + descriptorValue.asObject(runtime).getProperty(runtime, "value"); + if (functionValue.isObject() && + functionValue.asObject(runtime).isFunction(runtime)) { + bridge_->setObjectExpando(runtime, object_, property, functionValue); + return functionValue; + } + return Value::undefined(); + } + currentValue = + getPrototypeOf.call(runtime, Value(runtime, current)); + } + + return Value::undefined(); + } + + // Invoke a JS-prototype getter accessor with this instance as the receiver. + // Sets *found and returns the resolved value. + Value resolveEnginePrototypeGetter(Runtime& runtime, + const std::string& property, bool* found) { + *found = false; + if (object_ == nil || property.empty()) { + return Value::undefined(); + } + Value prototypeValue = classPrototypeForObject(runtime); + if (!prototypeValue.isObject()) { + return Value::undefined(); + } + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function getOwnPropertyDescriptor = + objectConstructor.getPropertyAsFunction(runtime, "getOwnPropertyDescriptor"); + Function getPrototypeOf = + objectConstructor.getPropertyAsFunction(runtime, "getPrototypeOf"); + Value propertyName = makeString(runtime, property); + Value currentValue(runtime, prototypeValue); + for (size_t depth = 0; depth < 64 && currentValue.isObject(); depth++) { + Object current = currentValue.asObject(runtime); + Value descriptorValue = getOwnPropertyDescriptor.call( + runtime, Value(runtime, current), propertyName); + if (descriptorValue.isObject()) { + Object descriptor = descriptorValue.asObject(runtime); + Value getterValue = descriptor.getProperty(runtime, "get"); + if (getterValue.isObject() && + getterValue.asObject(runtime).isFunction(runtime)) { + Value thisValue = engineThisValueForObject(runtime); + if (thisValue.isObject()) { + *found = true; + return getterValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, thisValue.asObject(runtime), + static_cast(nullptr), static_cast(0)); + } + } + Value dataValue = descriptor.getProperty(runtime, "value"); + if (!dataValue.isUndefined()) { + *found = true; + return dataValue; + } + return Value::undefined(); + } + currentValue = getPrototypeOf.call(runtime, Value(runtime, current)); + } + return Value::undefined(); + } + + // Invoke a JS-prototype setter accessor with this instance as the receiver. + // Returns true when a setter was found and invoked. + bool invokeEnginePrototypeSetter(Runtime& runtime, const std::string& property, + const Value& value) { + if (object_ == nil || property.empty()) { + return false; + } + Value prototypeValue = classPrototypeForObject(runtime); + if (!prototypeValue.isObject()) { + return false; + } + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function getOwnPropertyDescriptor = + objectConstructor.getPropertyAsFunction(runtime, "getOwnPropertyDescriptor"); + Function getPrototypeOf = + objectConstructor.getPropertyAsFunction(runtime, "getPrototypeOf"); + Value propertyName = makeString(runtime, property); + Value currentValue(runtime, prototypeValue); + for (size_t depth = 0; depth < 64 && currentValue.isObject(); depth++) { + Object current = currentValue.asObject(runtime); + Value descriptorValue = getOwnPropertyDescriptor.call( + runtime, Value(runtime, current), propertyName); + if (descriptorValue.isObject()) { + Value setterValue = + descriptorValue.asObject(runtime).getProperty(runtime, "set"); + if (setterValue.isObject() && + setterValue.asObject(runtime).isFunction(runtime)) { + Value thisValue = engineThisValueForObject(runtime); + if (thisValue.isObject()) { + Value args[] = {Value(runtime, value)}; + setterValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, thisValue.asObject(runtime), + static_cast(args), static_cast(1)); + return true; + } + } + return false; + } + currentValue = getPrototypeOf.call(runtime, Value(runtime, current)); + } + return false; + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + + // Fast path: check expando cache first (hot path for method calls). + Value expando = bridge_->findObjectExpando(runtime, object_, property); + if (!expando.isUndefined()) { + return expando; + } + // If this receiver is a UIAppearance proxy, its properties live in the + // class-keyed appearance cache, not on the object itself. + Value appearanceExpando = + cachedAppearanceProxyPropertyValue(runtime, bridge_, object_, property); + if (!appearanceExpando.isUndefined()) { + return appearanceExpando; + } + + // Fast path: cached metadata property-getter resolution. Skips the + // special-name chain + per-access metadata discovery for hot getters + // (hash/length/count/...). Only populated for genuine non-extended + // metadata property members below, so a hit is always safe to serve. + if (object_ != nil) { + if (const auto* cached = bridge_->findCachedPropertyGetter( + object_getClass(object_), property)) { + if (cached->preparedInvocation != nullptr) { + return callPreparedObjectSelector(runtime, + *cached->preparedInvocation, + nullptr, 0); + } + return callObjectSelector(runtime, cached->selectorName, cached->member, + nullptr, 0); + } + } + + if (property == "kind") { + return makeString(runtime, "object"); + } + if (property == "className") { + return makeString(runtime, object_ != nil ? object_getClassName(object_) : ""); + } + if (property == "nativeAddress") { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", object_); + return makeString(runtime, address); + } + if (property == "class") { + auto bridge = bridge_; + id object = object_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "class"), 0, + [bridge, object](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + if (object == nil) { + return Value::undefined(); + } + Value classWrapper = bridge->findObjectExpando( + runtime, object, "__nativeApiClassWrapper"); + if (classWrapper.isObject()) { + return classWrapper; + } + NativeApiSymbol symbol = + nativeApiSymbolForRuntimeClass(bridge, object_getClass(object)); + return makeNativeClassValue(runtime, bridge, std::move(symbol)); + }); + } + if (property == "constructor") { + if (object_ == nil) { + return Value::undefined(); + } + // Check class wrapper expando first (set during class setup). + Value classWrapper = bridge_->findObjectExpando( + runtime, object_, "__nativeApiClassWrapper"); + if (classWrapper.isObject()) { + return classWrapper; + } + // Try cached class value. + Class objClass = object_getClass(object_); + Value cached = bridge_->findClassValue(runtime, objClass); + if (!cached.isUndefined()) { + return cached; + } + // Resolve through metadata and global. + NativeApiSymbol symbol = + nativeApiSymbolForRuntimeClass(bridge_, objClass); + // Try the global by the symbol's name (which may be the JS-friendly name + // from metadata, different from the ObjC runtime name for Swift classes). + if (!symbol.name.empty()) { + Object global = runtime.global(); + if (global.hasProperty(runtime, symbol.name.c_str())) { + Value globalClass = global.getProperty(runtime, symbol.name.c_str()); + if (!globalClass.isUndefined() && !globalClass.isNull()) { + return globalClass; + } + } + // Also try the runtime name if different. + if (symbol.runtimeName != symbol.name && + global.hasProperty(runtime, symbol.runtimeName.c_str())) { + Value globalClass = global.getProperty(runtime, symbol.runtimeName.c_str()); + if (!globalClass.isUndefined() && !globalClass.isNull()) { + return globalClass; + } + } + } + // For Swift classes: try findClass by runtime name which checks + // classSymbolsByRuntimeName_ and may return a different JS-friendly name. + if (bridge_ != nullptr) { + const char* runtimeName = class_getName(objClass); + if (runtimeName != nullptr) { + if (const NativeApiSymbol* found = bridge_->findClass(runtimeName)) { + if (found->name != symbol.name) { + Object global = runtime.global(); + if (global.hasProperty(runtime, found->name.c_str())) { + Value globalClass = global.getProperty(runtime, found->name.c_str()); + if (!globalClass.isUndefined() && !globalClass.isNull()) { + return globalClass; + } + } + } + } + } + } + return makeNativeClassValue(runtime, bridge_, std::move(symbol)); + } + if (property == "superclass") { + if (object_ == nil) { + return Value::undefined(); + } + Class superclass = class_getSuperclass(object_getClass(object_)); + if (superclass == Nil) { + return Value::null(); + } + // Try cached class value. + Value cached = bridge_->findClassValue(runtime, superclass); + if (!cached.isUndefined()) { + return cached; + } + // Try global lookup by class name. + const char* name = class_getName(superclass); + if (name != nullptr && name[0] != '\0') { + Object global = runtime.global(); + if (global.hasProperty(runtime, name)) { + Value globalClass = global.getProperty(runtime, name); + if (!globalClass.isUndefined()) { + return globalClass; + } + } + } + NativeApiSymbol symbol = nativeApiSymbolForRuntimeClass(bridge_, superclass); + return makeNativeClassValue(runtime, bridge_, std::move(symbol)); + } + if (property == "super") { + // A JS-subclass instance dispatches `$base`/super against the ObjC + // class it was constructed to extend, not necessarily the receiver's + // immediate runtime superclass (which can differ, e.g. after a + // preserved initializer self-return re-seated the wrapper). + Class dispatchClass = + superDispatchClass_ != Nil + ? superDispatchClass_ + : (object_ != nil ? class_getSuperclass(object_getClass(object_)) + : Nil); + return Object::createFromHostObject( + runtime, + std::make_shared(bridge_, object_, + dispatchClass)); + } + if (property == "invoke" || property == "send") { + auto bridge = bridge_; + id object = object_; + std::weak_ptr weakSelf = shared_from_this(); + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 1, + [bridge, object, weakSelf](Runtime& runtime, const Value&, + const Value* args, + size_t count) -> Value { + std::string selectorName = + readStringArg(runtime, args, count, 0, "selector"); + if (auto self = weakSelf.lock()) { + return self->callObjectSelector(runtime, selectorName, nullptr, + args + 1, count - 1); + } + return callObjCSelector(runtime, bridge, object, false, selectorName, + nullptr, args + 1, count - 1); + }); + } + if (property == "takeRetainedValue" || property == "takeUnretainedValue") { + bool retained = property == "takeRetainedValue"; + std::weak_ptr weakSelf = shared_from_this(); + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [weakSelf, retained](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + auto self = weakSelf.lock(); + if (!self || self->object_ == nil || self->consumed_) { + throw JSError(runtime, "Unmanaged value has already been consumed."); + } + + id object = self->object_; + bool ownsObject = self->ownsObject_; + bool wrapperRetainedObject = self->wrapperRetainedObject_; + if (self->bridge_ != nullptr) { + self->bridge_->forgetRoundTripValue(runtime, object); + self->bridge_->releaseObjectExpandoOwner(object); + } + self->object_ = nil; + self->ownsObject_ = false; + self->wrapperRetainedObject_ = false; + if (self->lifetimeState_ != nullptr) { + self->lifetimeState_->clear(); + } + self->consumed_ = true; + const bool releasePreviousOwnership = + ownsObject && (!retained || wrapperRetainedObject); + try { + Value result = + makeNativeObjectValue(runtime, self->bridge_, object, retained); + if (releasePreviousOwnership) { + [object release]; + } + return result; + } catch (...) { + if (releasePreviousOwnership) { + [object release]; + } + throw; + } + }); + } + if (property == "toString") { + id object = object_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [object](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return NativeApiObjectHostObject::descriptionString(runtime, object); + }); + } + if (property == "description") { + return descriptionString(runtime, object_); + } + if (property == "URL" && object_ != nil && + [object_ respondsToSelector:@selector(URL)]) { + return callObjectSelector(runtime, "URL", nullptr, nullptr, 0); + } + if (property == "Symbol.iterator" || + property == "Symbol(Symbol.iterator)" || + property == "@@iterator") { + auto bridge = bridge_; + id object = object_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Symbol.iterator"), 0, + [bridge, object](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + if (object == nil || + ![object conformsToProtocol:@protocol(NSFastEnumeration)]) { + throw JSError( + runtime, "Object does not conform to NSFastEnumeration."); + } + return Object::createFromHostObject( + runtime, + std::make_shared( + bridge, static_cast>(object))); + }); + } + +#if TARGET_OS_OSX + if (property == "initWithRedGreenBlueAlpha") { + Class nsColorClass = NSClassFromString(@"NSColor"); + if (object_ != nil && nsColorClass != Nil && + [object_ isKindOfClass:nsColorClass]) { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 4, + [bridge, nsColorClass](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + const char* selectors[] = { + "colorWithSRGBRed:green:blue:alpha:", + "colorWithCalibratedRed:green:blue:alpha:", + "colorWithDeviceRed:green:blue:alpha:", + }; + for (const char* selectorName : selectors) { + if (class_getClassMethod(nsColorClass, + sel_getUid(selectorName)) != nullptr) { + return callObjCSelector(runtime, bridge, + static_cast(nsColorClass), true, + selectorName, nullptr, args, count); + } + } + throw JSError( + runtime, "NSColor RGB initializer is not available."); + }); + } + } +#endif + + if (property == "initWithFireDateIntervalTargetSelectorUserInfoRepeats") { + Class timerClass = NSClassFromString(@"NSTimer"); + if (object_ != nil && timerClass != Nil && + [object_ isKindOfClass:timerClass]) { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 6, + [bridge, timerClass](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + if (count < 6) { + throw JSError( + runtime, "NSTimer initializer expects six arguments."); + } + return callObjCSelector( + runtime, bridge, static_cast(timerClass), true, + "timerWithTimeInterval:target:selector:userInfo:repeats:", + nullptr, args + 1, count - 1); + }); + } + } + + if (object_ != nil && [object_ isKindOfClass:[NSArray class]]) { + NSArray* array = static_cast(object_); + if (property == "length") { + return static_cast(array.count); + } + if (auto index = parseArrayIndexProperty(property)) { + if (*index >= array.count) { + return Value::undefined(); + } + id element = [array objectAtIndex:*index]; + NativeApiType elementType = nativeObjectReturnType(); + return convertNativeReturnValue(runtime, bridge_, elementType, &element); + } + } + + if (object_ != nil && property == "length" && + ![object_ respondsToSelector:@selector(length)]) { + return Value::undefined(); + } + if (object_ != nil && property == "count" && + ![object_ respondsToSelector:@selector(count)]) { + return Value::undefined(); + } + + // For JS-extended instances, metadata property accessors live on the + // prototype chain (native accessors plus any JS overrides), so defer to the + // engine instead of reading the native property here and shadowing a JS + // override. + bool isEngineExtendedInstance = + object_ != nil && + class_conformsToProtocol(object_getClass(object_), + @protocol(NativeApiClassBuilderProtocol)); + + if (object_ != nil && !isEngineExtendedInstance) { + // Tries to resolve `property` as a metadata getter or method against + // one member table, returning the resolved JS value if it does. + // Shared by the ordinary class-hierarchy lookup below and the + // protocol-conformance fallback, so both paths behave identically. + auto tryResolveFromMembers = + [&](const std::vector& members) + -> std::optional { + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, false)) { + if (auto getter = respondingPropertyGetterSelector( + object_, property, propertyMember->selectorName)) { + NativeApiMember getterMember = *propertyMember; + getterMember.selectorName = *getter; + std::shared_ptr preparedGetter; + try { + preparedGetter = prepareNativeApiObjCInvocation( + runtime, bridge_, object_getClass(object_), false, + getterMember.selectorName, &getterMember); + } catch (const std::exception&) { + } + bridge_->cachePropertyGetter(object_getClass(object_), property, + propertyMember, + getterMember.selectorName, + preparedGetter); + if (preparedGetter != nullptr) { + return callPreparedObjectSelector(runtime, *preparedGetter, + nullptr, 0); + } + return callObjectSelector(runtime, getterMember.selectorName, + &getterMember, nullptr, 0); + } + } + + // Resolve metadata methods to a bound selector-group function. The + // bound receiver keeps method-call semantics correct even on engines + // whose host-object interceptor does not preserve `this`, while the + // engine backend can still use its direct selector-group/GSD path. + if (hasMethodMember(members, property, false)) { + auto selectors = + selectorGroupEntriesForMethod(members, property, false); + if (selectors != nullptr) { + auto preparedInvocations = std::make_shared>>( + selectors->size()); + Value methodFunction = CreateNativeApiBoundSelectorGroupFunction( + runtime, bridge_, object_getClass(object_), shared_from_this(), + selectors, preparedInvocations); + // Cache the resolved host function so repeated method access does + // not reallocate it on every call (hot path). + bridge_->setObjectExpando(runtime, object_, property, + methodFunction); + return methodFunction; + } + } + return std::nullopt; + }; + + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(object_getClass(object_))) { + if (auto result = + tryResolveFromMembers(bridge_->membersForClass(*symbol))) { + return std::move(*result); + } + } + + // Fallback for a selector declared solely on a protocol the object + // conforms to only at the ObjC runtime level (a category/class- + // extension conformance the metadata generator never parsed, or a + // fully private concrete class such as the one behind + // UIViewControllerTransitionCoordinator). Only reached once the + // ordinary class-hierarchy lookup above has already missed, so + // classes with complete metadata pay nothing extra. + const auto& protocolMembers = bridge_->protocolMembersForRuntimeClass( + object_getClass(object_)); + if (!protocolMembers.empty()) { + if (auto result = tryResolveFromMembers(protocolMembers)) { + return std::move(*result); + } + } + } + + Value prototypeFunction = prototypeFunctionForProperty(runtime, property); + if (!prototypeFunction.isUndefined()) { + return prototypeFunction; + } + + // JS-subclassed instances own their members in JS (prototype accessors and + // methods); defer so the engine resolves them instead of the bridge + // returning a registered getter IMP as a raw callable. + if (isEngineExtendedInstance) { + // Prefer JS prototype accessors before falling back to runtime ObjC + // getters; otherwise an ObjC getter implemented by the JS subclass can + // re-enter the same JS accessor recursively. + bool found = false; + Value resolved = resolveEnginePrototypeGetter(runtime, property, &found); + if (found) { + return resolved; + } + if (auto selector = + runtimeReadablePropertyGetter(object_, property)) { + return callObjectSelector(runtime, *selector, nullptr, nullptr, 0); + } + // Inherited ObjC selectors on a ClassBuilder subclass have no JS + // prototype entry (the engine may register an empty prototype for the + // subclass) and no runtime ObjC property, so the getter probes above + // miss. Fall through to metadata METHOD resolution using the nearest + // metadata ancestor (findClassForRuntimeClass walks up from the concrete + // subclass, which itself carries no metadata) so first-access inherited + // selectors resolve as bound selector-group functions instead of hard- + // returning undefined. This mirrors the non-extended method path above. + // Property accessors stay deferred here on purpose (accessor shadowing / + // reentry suppression), so resolve METHODS ONLY — JS-overridden methods + // are already handled earlier via the JS prototype chain. + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(object_getClass(object_))) { + const auto& members = bridge_->membersForClass(*symbol); + if (hasMethodMember(members, property, false)) { + auto selectors = + selectorGroupEntriesForMethod(members, property, false); + if (selectors != nullptr) { + auto preparedInvocations = std::make_shared>>( + selectors->size()); + Value methodFunction = CreateNativeApiBoundSelectorGroupFunction( + runtime, bridge_, object_getClass(object_), shared_from_this(), + selectors, preparedInvocations); + // Cache the resolved host function so repeated method access does + // not reallocate it on every call (hot path). + bridge_->setObjectExpando(runtime, object_, property, + methodFunction); + return methodFunction; + } + } + } + return Value::undefined(); + } + + if (object_ != nil) { + // A runtime ObjC property (e.g. from a protocol the concrete, non-metadata + // class adopts) must be invoked as a getter, not returned as a callable. + if (objc_property_t prop = + class_getProperty(object_getClass(object_), property.c_str())) { + std::string getter = property; + if (char* customGetter = property_copyAttributeValue(prop, "G")) { + getter = customGetter; + free(customGetter); + } + if (auto selector = + respondingPropertyGetterSelector(object_, property, getter)) { + return callObjectSelector(runtime, *selector, nullptr, nullptr, 0); + } + } + } + + if (object_ != nil && + hasRuntimeMemberForName(object_getClass(object_), false, property)) { + std::weak_ptr weakSelf = shared_from_this(); + std::string memberName = property; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [weakSelf, memberName](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + auto self = weakSelf.lock(); + if (!self || self->object_ == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + auto selectorName = selectRuntimeSelectorForName( + object_getClass(self->object_), false, memberName, count); + if (!selectorName) { + throw JSError(runtime, + "Objective-C selector is not available: " + + memberName); + } + return self->callObjectSelector(runtime, *selectorName, nullptr, + args, count); + }); + } + + return Value::undefined(); + } + + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override { + std::string property = name.utf8(runtime); + if (object_ == nil) { + throw JSError(runtime, "Cannot set property on nil object."); + } + + bool isEngineExtendedInstance = + class_conformsToProtocol(object_getClass(object_), + @protocol(NativeApiClassBuilderProtocol)); + // A JS accessor override must win over a native ObjC setter: try it + // first, before any of the metadata/runtime setter paths below. + if (isEngineExtendedInstance) { + if (invokeEnginePrototypeSetter(runtime, property, value)) { + NATIVE_API_SET_RETURN(true); + } + } + + // If this receiver is a UIAppearance proxy, its properties are recorded + // in the class-keyed appearance cache, not set through the metadata/ + // runtime setter paths below (an appearance proxy setter doesn't + // reliably support read-your-write, so we cache it ourselves too). + if (Class appearanceClass = + taggedAppearanceProxyClass(runtime, bridge_, object_)) { + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(appearanceClass)) { + const auto& members = bridge_->membersForClass(*symbol); + if (const NativeApiMember* propertyMember = + selectAppearanceProxyPropertyMember(members, property)) { + if (propertyMember->readonly) { + throw JSError( + runtime, "Attempted to assign to readonly property."); + } + Value args[] = {Value(runtime, value)}; + if (!propertyMember->setterSelectorName.empty()) { + NativeApiMember setterMember = *propertyMember; + setterMember.selectorName = propertyMember->setterSelectorName; + setterMember.signatureOffset = propertyMember->setterSignatureOffset; + callObjCSelector(runtime, bridge_, object_, false, + setterMember.selectorName, &setterMember, args, 1); + } else if (auto setterSelectorName = + runtimeWritablePropertySetter(object_, property)) { + callObjCSelector(runtime, bridge_, object_, false, + *setterSelectorName, nullptr, args, 1); + } else { + throw JSError( + runtime, "UIAppearance property setter is unavailable."); + } + cacheAppearanceProxyPropertyValue(runtime, bridge_, object_, + property, value); + NATIVE_API_SET_RETURN(true); + } + } + } + + // Tries to resolve `property` as a writable metadata property against + // one member table. Returns true (and performs the assignment) if it + // does. Shared by the ordinary class-hierarchy lookup and the + // protocol-conformance fallback below. + auto tryAssignFromMembers = + [&](const std::vector& members) -> bool { + const NativeApiMember* propertyMember = + selectWritablePropertyMember(members, property, false); + if (propertyMember == nullptr) { + return false; + } + if (propertyMember->readonly) { + throw JSError(runtime, "Attempted to assign to readonly property."); + } + NativeApiMember setterMember = *propertyMember; + setterMember.selectorName = propertyMember->setterSelectorName; + setterMember.signatureOffset = propertyMember->setterSignatureOffset; + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, object_, false, + setterMember.selectorName, &setterMember, args, 1); + cacheAppearanceProxyPropertyValue(runtime, bridge_, object_, property, + value); + return true; + }; + + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(object_getClass(object_))) { + if (tryAssignFromMembers(bridge_->membersForClass(*symbol))) { + NATIVE_API_SET_RETURN(true); + } + } + + // Fallback for a writable property declared solely on a protocol the + // object conforms to only at the ObjC runtime level. See + // NativeApiBridge::protocolMembersForRuntimeClass. Only reached once the + // ordinary class-hierarchy lookup above has already missed. + const auto& protocolMembers = + bridge_->protocolMembersForRuntimeClass(object_getClass(object_)); + if (!protocolMembers.empty() && tryAssignFromMembers(protocolMembers)) { + NATIVE_API_SET_RETURN(true); + } + + if (auto setterSelectorName = + runtimeWritablePropertySetter(object_, property)) { + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, object_, false, + *setterSelectorName, nullptr, args, 1); + cacheAppearanceProxyPropertyValue(runtime, bridge_, object_, property, + value); + // The property was set through a runtime-discovered setter, but the + // GET path may not find a matching readable getter (e.g. a + // write-only or asymmetrically-named property) — write an expando + // too so a subsequent get() still sees this value. + if (!objectGetPathCanReadRuntimeProperty(object_, property)) { + bridge_->setObjectExpando(runtime, object_, property, value); + } + NATIVE_API_SET_RETURN(true); + } + + // For JS-subclassed instances, an unknown property is owned by the JS + // prototype (e.g. a JS-defined accessor); defer so the engine runs it instead of + // shadowing it with a bridge expando. + if (isEngineExtendedInstance) { +#ifdef NATIVESCRIPT_NATIVE_API_HOST_EXPLICIT_OVERRIDE + // Engines whose exotic property storage doesn't fall back to own + // properties need the JS-owned set resolved here: invoke a JS-prototype + // setter if present, otherwise store the value as a bridge expando. + bool invokedPrototypeSetter = + invokeEnginePrototypeSetter(runtime, property, value); + if (!invokedPrototypeSetter) { + storeOwnExpando(runtime, property, value); + } + NATIVE_API_SET_RETURN(true); +#else + // The prototype-setter attempt at the top of set() already ran and + // didn't return — reaching here means no JS setter fired, so store the + // expando unconditionally instead of re-probing for one. + storeOwnExpando(runtime, property, value); + NATIVE_API_SET_RETURN(false); +#endif + } + + bridge_->setObjectExpando(runtime, object_, property, value); + NATIVE_API_SET_RETURN(true); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + names.reserve(6); + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "className"); + addPropertyName(runtime, names, "nativeAddress"); + addPropertyName(runtime, names, "constructor"); + addPropertyName(runtime, names, "superclass"); + addPropertyName(runtime, names, "super"); + addPropertyName(runtime, names, "invoke"); + addPropertyName(runtime, names, "send"); + addPropertyName(runtime, names, "takeRetainedValue"); + addPropertyName(runtime, names, "takeUnretainedValue"); + addPropertyName(runtime, names, "toString"); + return names; + } + + private: + std::shared_ptr bridge_; + id object_ = nil; + bool ownsObject_ = false; + bool wrapperRetainedObject_ = false; + bool consumed_ = false; + // Set when this wrapper represents a JS-subclass instance whose `$base`/ + // super dispatch must resolve against a specific ObjC superclass (rather + // than the receiver's own class) — see the "super" property handling in + // get() below. + Class superDispatchClass_ = Nil; + std::shared_ptr lifetimeState_; +}; + +// Tags a `[SomeClass appearance]`-family call's result (a wrapped +// UIAppearance proxy) with the class it proxies for, and installs the +// property accessors that make it behave like a real object rather than +// requiring `.invoke(...)`. Lives here (not host_objects/Appearance.mm) +// because it needs the complete NativeApiObjectHostObject type; Class.mm, +// Protocol.mm, Invocation.mm, and the per-engine selector-group code all +// consume it and are included later in the bridge. +Value tagStaticAppearanceSelectorResult( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, bool receiverIsClass, const std::string& selectorName, + Value result) { + if (!isStaticAppearanceSelector(receiverIsClass, selectorName) || + !result.isObject()) { + return result; + } + Object resultObject = result.asObject(runtime); + if (!resultObject.isHostObject(runtime)) { + return result; + } + Class customizableClass = tagStaticAppearanceNativeResult( + runtime, bridge, static_cast(receiver), + resultObject.getHostObject(runtime)->object()); + installAppearanceProxyPropertyAccessors( + runtime, bridge, customizableClass, + resultObject.getHostObject(runtime)->object(), + resultObject); + return result; +} diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Protocol.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Protocol.mm new file mode 100644 index 000000000..cf480883b --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Protocol.mm @@ -0,0 +1,326 @@ +class NativeApiProtocolHostObject final : public HostObject { + public: + NativeApiProtocolHostObject(std::shared_ptr bridge, + NativeApiSymbol symbol) + : bridge_(std::move(bridge)), symbol_(std::move(symbol)) {} + + Protocol* nativeProtocol() const { + Protocol* protocol = lookupProtocolByNativeName(symbol_.runtimeName); + if (protocol == nullptr && symbol_.runtimeName != symbol_.name) { + protocol = lookupProtocolByNativeName(symbol_.name); + } + return protocol; + } + + const NativeApiSymbol& symbol() const { return symbol_; } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, "protocol"); + } + if (property == "name") { + return makeString(runtime, symbol_.name); + } + if (property == "runtimeName") { + return makeString(runtime, symbol_.runtimeName); + } + if (property == "available") { + return nativeProtocol() != nullptr; + } + if (property == "metadataOffset") { + return static_cast(symbol_.offset); + } + if (property == "nativeAddress") { + return static_cast( + reinterpret_cast(nativeProtocol())); + } + if (property == "prototype") { + Object prototype(runtime); + for (const auto& member : bridge_->membersForProtocol(symbol_)) { + if (prototype.hasProperty(runtime, member.name.c_str())) { + continue; + } + if (member.property) { + defineProtocolProperty(runtime, prototype, member, false); + } else { + prototype.setProperty(runtime, member.name.c_str(), + makeProtocolMemberFunction(runtime, member, + false)); + } + } + return prototype; + } + if (property == "toString") { + auto symbol = symbol_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [symbol](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return makeString(runtime, + "[NativeApiProtocol " + symbol.name + "]"); + }); + } + const auto& members = bridge_->membersForProtocol(symbol_); + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, true)) { + return makeProtocolPropertyGetter(runtime, *propertyMember, true); + } + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, false)) { + return makeProtocolPropertyGetter(runtime, *propertyMember, true); + } + for (const auto& member : members) { + if (member.property || member.name != property) { + continue; + } + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic) { + return makeProtocolMemberFunction(runtime, member, true); + } + } + for (const auto& member : members) { + if (member.property || member.name != property) { + continue; + } + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (!memberIsStatic) { + return makeProtocolMemberFunction(runtime, member, true); + } + } + return Value::undefined(); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "name"); + addPropertyName(runtime, names, "runtimeName"); + addPropertyName(runtime, names, "available"); + addPropertyName(runtime, names, "metadataOffset"); + addPropertyName(runtime, names, "nativeAddress"); + addPropertyName(runtime, names, "prototype"); + addPropertyName(runtime, names, "toString"); + for (const auto& member : bridge_->membersForProtocol(symbol_)) { + addPropertyName(runtime, names, member.name.c_str()); + } + return names; + } + + private: + static Class classReceiverFromThis(Runtime& runtime, const Value& thisValue) { + if (!thisValue.isObject()) { + return Nil; + } + + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->nativeClass(); + } + + Value wrappedClass = object.getProperty(runtime, "__nativeApiClass"); + if (wrappedClass.isObject()) { + Object wrappedObject = wrappedClass.asObject(runtime); + if (wrappedObject.isHostObject(runtime)) { + return wrappedObject.getHostObject(runtime) + ->nativeClass(); + } + } + + Value kindValue = object.getProperty(runtime, "kind"); + if (kindValue.isString() && + kindValue.asString(runtime).utf8(runtime) == "class") { + Value runtimeNameValue = object.getProperty(runtime, "runtimeName"); + if (!runtimeNameValue.isString()) { + runtimeNameValue = object.getProperty(runtime, "name"); + } + if (runtimeNameValue.isString()) { + std::string runtimeName = + runtimeNameValue.asString(runtime).utf8(runtime); + return objc_lookUpClass(runtimeName.c_str()); + } + } + + return Nil; + } + + id objectReceiverFromThis(Runtime& runtime, const Value& thisValue) const { + if (!thisValue.isObject()) { + return nil; + } + + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->object(); + } + + return nil; + } + + Value makeProtocolMemberFunction(Runtime& runtime, NativeApiMember member, + bool receiverIsClass) const { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, member.name.c_str()), 0, + [bridge, member, receiverIsClass](Runtime& runtime, + const Value& thisValue, + const Value* args, + size_t count) -> Value { + id receiver = nil; + if (receiverIsClass) { + receiver = static_cast( + classReceiverFromThis(runtime, thisValue)); + } else if (thisValue.isObject()) { + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + receiver = object.getHostObject(runtime) + ->object(); + } + } + + if (receiver == nil) { + throw JSError( + runtime, "Protocol member requires a native receiver."); + } + return callObjCSelector(runtime, bridge, receiver, receiverIsClass, + member.selectorName, &member, args, count); + }); + } + + Value makeProtocolPropertyGetter(Runtime& runtime, NativeApiMember member, + bool receiverIsClass) const { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, member.name.c_str()), 0, + [bridge, member, receiverIsClass](Runtime& runtime, + const Value& thisValue, + const Value*, size_t) -> Value { + id receiver = nil; + if (receiverIsClass) { + receiver = static_cast( + classReceiverFromThis(runtime, thisValue)); + } else if (thisValue.isObject()) { + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + receiver = object.getHostObject(runtime) + ->object(); + } + } + + if (receiver == nil) { + throw JSError( + runtime, "Protocol property requires a native receiver."); + } + Value appearanceExpando = cachedAppearanceProxyPropertyValue( + runtime, bridge, receiver, member.name); + if (!appearanceExpando.isUndefined()) { + return appearanceExpando; + } + NativeApiMember getterMember = member; + if (auto selector = respondingPropertyGetterSelector( + receiver, member.name, member.selectorName)) { + getterMember.selectorName = *selector; + } + return callObjCSelector(runtime, bridge, receiver, receiverIsClass, + getterMember.selectorName, &getterMember, + nullptr, 0); + }); + } + + Value makeProtocolPropertySetter(Runtime& runtime, NativeApiMember member, + bool receiverIsClass) const { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, member.setterSelectorName.c_str()), + 1, + [bridge, member, receiverIsClass](Runtime& runtime, + const Value& thisValue, + const Value* args, + size_t count) -> Value { + id receiver = nil; + if (receiverIsClass) { + receiver = static_cast( + classReceiverFromThis(runtime, thisValue)); + } else if (thisValue.isObject()) { + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + receiver = object.getHostObject(runtime) + ->object(); + } + } + + if (receiver == nil) { + throw JSError( + runtime, "Protocol property requires a native receiver."); + } + if (count < 1) { + throw JSError( + runtime, "Protocol property setter expects a value."); + } + + NativeApiMember setterMember = member; + setterMember.selectorName = member.setterSelectorName; + setterMember.signatureOffset = member.setterSignatureOffset; + Value result = callObjCSelector( + runtime, bridge, receiver, receiverIsClass, + setterMember.selectorName, &setterMember, args, 1); + cacheAppearanceProxyPropertyValue(runtime, bridge, receiver, + member.name, args[0]); + return result; + }); + } + + void defineProtocolProperty(Runtime& runtime, Object& target, + const NativeApiMember& member, + bool receiverIsClass) const { + try { + Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); + Function defineProperty = + objectCtor.getPropertyAsFunction(runtime, "defineProperty"); + Object descriptor(runtime); + descriptor.setProperty(runtime, "configurable", true); + descriptor.setProperty(runtime, "enumerable", true); + descriptor.setProperty(runtime, "get", + makeProtocolPropertyGetter(runtime, member, + receiverIsClass)); + if (!member.readonly && !member.setterSelectorName.empty()) { + descriptor.setProperty(runtime, "set", + makeProtocolPropertySetter(runtime, member, + receiverIsClass)); + } + defineProperty.call(runtime, target, makeString(runtime, member.name), + descriptor); + } catch (const std::exception&) { + } + } + + std::shared_ptr bridge_; + NativeApiSymbol symbol_; +}; + +Value makeNativeProtocolValue(Runtime& runtime, + const std::shared_ptr& bridge, + NativeApiSymbol symbol) { + Value globalValue = globalNativeSymbolValue(runtime, symbol, "protocol"); + if (!globalValue.isUndefined()) { + return globalValue; + } + return Object::createFromHostObject( + runtime, + std::make_shared(bridge, std::move(symbol))); +} + +Class nativeClassFromEngineObject(Runtime& runtime, const Object& object) { + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->nativeClass(); + } + + Value wrappedClass = object.getProperty(runtime, "__nativeApiClass"); + if (wrappedClass.isObject()) { + Object wrappedObject = wrappedClass.asObject(runtime); + if (wrappedObject.isHostObject(runtime)) { + return wrappedObject.getHostObject(runtime) + ->nativeClass(); + } + } + return Nil; +} diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Struct.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Struct.mm new file mode 100644 index 000000000..3a4475369 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Struct.mm @@ -0,0 +1,154 @@ +class NativeApiStructObjectHostObject final : public HostObject { + public: + NativeApiStructObjectHostObject( + std::shared_ptr bridge, + std::shared_ptr info, + const void* data = nullptr, bool ownsData = true, + std::shared_ptr> storageOwner = nullptr, + std::shared_ptr backingValue = nullptr) + : bridge_(std::move(bridge)), + info_(std::move(info)), + ownedData_(std::move(storageOwner)), + backingValue_(std::move(backingValue)), + ownsData_(ownsData) { + size_t size = info_ != nullptr ? info_->size : 0; + if (ownedData_ != nullptr) { + data_ = const_cast(data); + ownsData_ = false; + } else if (ownsData_) { + ownedData_ = std::make_shared>(size, 0); + if (data != nullptr && size > 0) { + std::memcpy(ownedData_->data(), data, size); + } + data_ = ownedData_->empty() ? nullptr : ownedData_->data(); + } else { + data_ = const_cast(data); + } + } + + void* data() const { return data_; } + std::shared_ptr info() const { return info_; } + std::shared_ptr> storageOwner() const { + return ownedData_; + } + std::shared_ptr backingValue() const { return backingValue_; } + + Value get(Runtime& runtime, const PropNameID& name) override; + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override; + std::vector getPropertyNames(Runtime& runtime) override; + + private: + std::shared_ptr bridge_; + std::shared_ptr info_; + std::shared_ptr> ownedData_; + std::shared_ptr backingValue_; + void* data_ = nullptr; + bool ownsData_ = true; +}; + +#ifdef TARGET_ENGINE_HERMES +class NativeApiStructObjectState final : public NativeApiNativeState { + public: + explicit NativeApiStructObjectState( + std::shared_ptr host) + : host_(std::move(host)) {} + + std::shared_ptr host() const { + return host_; + } + + private: + std::shared_ptr host_; +}; +#endif + +std::shared_ptr getNativeStructHostObject( + Runtime& runtime, const Object& object) { + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime); + } +#ifdef TARGET_ENGINE_HERMES + if (!object.hasNativeState(runtime)) { + return nullptr; + } + return object.getNativeState(runtime)->host(); +#else + return nullptr; +#endif +} + +Object createNativeStructHostObject( + Runtime& runtime, std::shared_ptr host) { +#ifdef TARGET_ENGINE_HERMES + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function defineProperty = + objectConstructor.getPropertyAsFunction(runtime, "defineProperty"); + Object object(runtime); + object.setNativeState(runtime, + std::make_shared(host)); + std::weak_ptr weakHost = host; + + for (const auto& propertyName : host->getPropertyNames(runtime)) { + std::string property = propertyName.utf8(runtime); + const auto info = host->info(); + const bool isField = info != nullptr && std::any_of( + info->fields.begin(), info->fields.end(), + [&](const NativeApiAggregateField& field) { + return field.name == property; + }); + if (!isField) { + object.setProperty(runtime, property.c_str(), + host->get(runtime, propertyName)); + continue; + } + + Object descriptor(runtime); + descriptor.setProperty(runtime, "configurable", false); + descriptor.setProperty(runtime, "enumerable", true); + descriptor.setProperty( + runtime, "get", + Function::createFromHostFunction( + runtime, PropNameID::forUtf8(runtime, property), 0, + [weakHost, property](Runtime& runtime, const Value& receiver, + const Value*, size_t) -> Value { + auto host = weakHost.lock(); + if (host == nullptr || !receiver.isObject()) { + throw JSError(runtime, "Invalid struct receiver"); + } + Object receiverObject = receiver.asObject(runtime); + if (getNativeStructHostObject(runtime, receiverObject).get() != + host.get()) { + throw JSError(runtime, "Invalid struct receiver"); + } + return host->get(runtime, + PropNameID::forUtf8(runtime, property)); + })); + + descriptor.setProperty( + runtime, "set", + Function::createFromHostFunction( + runtime, PropNameID::forUtf8(runtime, property), 1, + [weakHost, property](Runtime& runtime, const Value& receiver, + const Value* args, size_t count) -> Value { + auto host = weakHost.lock(); + if (host == nullptr || !receiver.isObject() || count < 1) { + throw JSError(runtime, "Invalid struct receiver"); + } + Object receiverObject = receiver.asObject(runtime); + if (getNativeStructHostObject(runtime, receiverObject).get() != + host.get()) { + throw JSError(runtime, "Invalid struct receiver"); + } + host->set(runtime, PropNameID::forUtf8(runtime, property), + args[0]); + return Value::undefined(); + })); + defineProperty.call(runtime, object, makeString(runtime, property), + descriptor); + } + return object; +#else + return Object::createFromHostObject(runtime, host); +#endif +} diff --git a/NativeScript/ffi/objc/v8/NativeApiV8.h b/NativeScript/ffi/objc/v8/NativeApiV8.h new file mode 100644 index 000000000..e9ed9c63f --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8.h @@ -0,0 +1,23 @@ +#ifndef NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H +#define NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H + +#include "ffi/objc/shared/NativeApiBackendConfig.h" +#include "v8.h" + +namespace nativescript { + +using NativeApiScheduler = NativeApiBackendScheduler; +using NativeApiConfig = NativeApiBackendConfig; + +void InstallNativeApi(v8::Isolate* isolate, + v8::Local context, + const NativeApiConfig& config = NativeApiConfig{}); +void CleanupNativeApi(v8::Isolate* isolate); + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(v8::Isolate* isolate, + v8::Local context, + const char* metadataPath); + +#endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H diff --git a/NativeScript/ffi/objc/v8/NativeApiV8.mm b/NativeScript/ffi/objc/v8/NativeApiV8.mm new file mode 100644 index 000000000..8b162b79e --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8.mm @@ -0,0 +1,86 @@ +#include "NativeApiV8.h" + +#ifdef TARGET_ENGINE_V8 + +#include "NativeApiV8Runtime.h" +#include "SignatureDispatch.h" + +namespace nativescript { + +namespace { + +using nativescript::engine::Array; +using nativescript::engine::ArrayBuffer; +using nativescript::engine::BigInt; +using nativescript::engine::Function; +using nativescript::engine::HostObject; +using nativescript::engine::MutableBuffer; +using nativescript::engine::Object; +using nativescript::engine::PropNameID; +using nativescript::engine::Runtime; +using nativescript::engine::String; +using nativescript::engine::StringBuffer; +using nativescript::engine::Value; +using nativescript::engine::JSError; +using metagen::MDMemberFlag; +using metagen::MDMetadataReader; +using metagen::MDSectionOffset; +using metagen::MDTypeKind; + +// clang-format off +#define NATIVESCRIPT_NATIVE_API_BACKEND_NAME "v8" +#include "../shared/bridge/ObjCBridge.mm" +// clang-format on + +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS 1 +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION 1 +#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 +#define NATIVESCRIPT_NATIVE_API_RUNTIME_SCOPE 1 + +#include "NativeApiV8RuntimeSupport.mm" + +// clang-format off +#include "../shared/bridge/HostObjects.mm" +#include "../shared/bridge/Callbacks.mm" +#include "../shared/bridge/TypeConv.mm" +#include "../shared/bridge/Invocation.mm" +#include "../shared/bridge/ClassBuilder.mm" +#include "../shared/bridge/HostObject.mm" +// clang-format on + + +#include "NativeApiV8SelectorGroups.mm" + +} // namespace + +#include "../shared/bridge/Install.mm" + +void InstallNativeApi(v8::Isolate* isolate, v8::Local context, + const NativeApiConfig& config) { + if (isolate == nullptr || context.IsEmpty()) { + return; + } + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(context); + Runtime runtime(isolate, context); + InstallNativeApi(runtime, config); +} + +void CleanupNativeApi(v8::Isolate* isolate) { + if (isolate != nullptr) { + engine::v8engine::cleanupRuntimeAllocations(isolate); + } +} + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(v8::Isolate* isolate, v8::Local context, + const char* metadataPath) { + nativescript::NativeApiConfig config; + config.metadataPath = metadataPath; + nativescript::InstallNativeApi(isolate, context, config); +} + +#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Gsd.mm b/NativeScript/ffi/objc/v8/NativeApiV8Gsd.mm new file mode 100644 index 000000000..65bc6e243 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Gsd.mm @@ -0,0 +1,216 @@ +// --- GSD (Generated Signature Dispatch) for V8 --- +// GsdObjCContext is the engine-neutral interface the generated invokers use: +// it reads JS arguments and writes the JS return value via the V8 API. The +// readers mirror V8's generic argument/return conversions exactly; any value +// that is not in the fast representation makes a reader return false so the +// invoker falls back to the fully correct generic path. +struct GsdObjCContext; +using ObjCGsdInvoker = bool (*)(GsdObjCContext&); +struct ObjCGsdDispatchEntry { + uint64_t dispatchId; + ObjCGsdInvoker invoker; +}; + +struct GsdObjCContext { + Runtime& runtime; + const std::shared_ptr& bridge; + id self; + SEL selector; + const v8::FunctionCallbackInfo& info; + v8::Isolate* isolate; + v8::Local jsContext; + const NativeApiType& returnType; + + template + void invokeNative(Invocation&& invocation) { + performGeneratedObjCInvocation(runtime, bridge, [&]() { invocation(); }); + } + + v8::Local arg(size_t i) const { + return info[static_cast(i)]; + } + + bool readBool(size_t i, uint8_t* out) { + *out = arg(i)->BooleanValue(isolate) ? 1 : 0; + return true; + } + template + bool readSigned(size_t i, T* out) { + v8::Local v = arg(i); + if (v->IsInt32()) { + *out = static_cast(v.As()->Value()); + return true; + } + if constexpr (sizeof(T) <= 4) { + int32_t tmp = 0; + if (!v->Int32Value(jsContext).To(&tmp)) return false; + *out = static_cast(tmp); + } else { + if (v->IsBigInt()) { + bool lossless = false; + *out = static_cast(v.As()->Int64Value(&lossless)); + } else { + int64_t tmp = 0; + if (!v->IntegerValue(jsContext).To(&tmp)) return false; + *out = static_cast(tmp); + } + } + return true; + } + template + bool readUnsigned(size_t i, T* out) { + v8::Local v = arg(i); + if (v->IsUint32()) { + *out = static_cast(v.As()->Value()); + return true; + } + if (v->IsInt32()) { + *out = static_cast(v.As()->Value()); + return true; + } + if constexpr (sizeof(T) <= 4) { + uint32_t tmp = 0; + if (!v->Uint32Value(jsContext).To(&tmp)) return false; + *out = static_cast(tmp); + } else { + if (v->IsBigInt()) { + bool lossless = false; + *out = static_cast(v.As()->Uint64Value(&lossless)); + } else { + int64_t tmp = 0; + if (!v->IntegerValue(jsContext).To(&tmp)) return false; + *out = static_cast(static_cast(tmp)); + } + } + return true; + } + bool readFloat(size_t i, float* out) { + double tmp = 0.0; + if (!readDouble(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readDouble(size_t i, double* out) { + v8::Local v = arg(i); + if (v->IsNumber()) { + *out = v.As()->Value(); + return true; + } + return v->NumberValue(jsContext).To(out); + } + bool readSelector(size_t i, SEL* out) { + return readV8EngineSelectorArgument(runtime, arg(i), out); + } + bool readClass(size_t i, Class* out) { + Class cls = v8NativeClassArgument(runtime, arg(i)); + if (cls == Nil) return false; + *out = cls; + return true; + } + bool readObject(size_t i, id* out) { + v8::Local v = arg(i); + if (v.IsEmpty() || v->IsNullOrUndefined()) { + *out = nil; + return true; + } + if (!v->IsObject()) return false; + if (auto* h = v8HostObjectRaw(v)) { + *out = h->object(); + return true; + } + if (auto* c = v8HostObjectRaw(v)) { + *out = static_cast(c->nativeClass()); + return true; + } + Class cls = v8NativeClassArgument(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + if (auto* p = v8HostObjectRaw(v)) { + *out = static_cast(p->nativeProtocol()); + return true; + } + return false; + } + + void setVoid() {} + void setBool(bool v) { + info.GetReturnValue().Set(v8::Boolean::New(isolate, v)); + } + void setInt32(int32_t v) { + info.GetReturnValue().Set(v8::Integer::New(isolate, v)); + } + void setUInt32(uint32_t v) { + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, v)); + } + void setUInt16(uint16_t v) { + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, v)); + } + void setInt64(int64_t v) { + info.GetReturnValue().Set(v8Integer64Value(isolate, v)); + } + void setUInt64(uint64_t v) { + info.GetReturnValue().Set(v8UnsignedInteger64Value(isolate, v)); + } + void setDouble(double v) { + info.GetReturnValue().Set(v8::Number::New(isolate, v)); + } + void setSelector(SEL v) { + const char* name = v != nullptr ? sel_getName(v) : nullptr; + if (name == nullptr) { + info.GetReturnValue().Set(v8::Null(isolate)); + } else { + info.GetReturnValue().Set(engine::v8engine::makeV8String(isolate, name)); + } + } + void setClass(Class v) { + if (v == nil) { + info.GetReturnValue().Set(v8::Null(isolate)); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + info.GetReturnValue().Set(result.local(runtime)); + } + void setObject(id obj) { + setV8EngineObjectReturn(runtime, bridge, returnType, obj, info); + } +}; + +// Close the anonymous namespace so the generated dispatch table lives in +// namespace nativescript (visible to lookupObjCGsdInvoker). GsdObjCContext is +// reachable from there via the unnamed namespace's implicit using-directive. +} // namespace (temporary close for GSD .inc) + +#if defined(__has_include) +#if __has_include("../shared/GeneratedGsdSignatureDispatch.inc") +#include "../shared/GeneratedGsdSignatureDispatch.inc" +#endif +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +inline constexpr ObjCGsdDispatchEntry kGeneratedObjCGsdDispatchEntries[] = { + {0, nullptr}}; +#endif + +ObjCGsdInvoker lookupObjCGsdInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCGsdDispatchEntries, dispatchId); +} + +namespace { // reopen anonymous namespace + +// --- End GSD --- diff --git a/NativeScript/ffi/objc/v8/NativeApiV8HostObjects.mm b/NativeScript/ffi/objc/v8/NativeApiV8HostObjects.mm new file mode 100644 index 000000000..bb34dd398 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8HostObjects.mm @@ -0,0 +1,495 @@ +#include "NativeApiV8Runtime.h" +#include "../shared/NativeApiStackValueArray.h" + +#ifdef TARGET_ENGINE_V8 + +namespace nativescript { +namespace engine { + +namespace v8engine { + +Value valueFromLocal(Runtime& runtime, v8::Local value) { return Value(runtime, value); } + +// V8's named-property interceptors run BEFORE prototype-chain lookup, so a +// JS-subclass accessor defined on the prototype (not the host object itself) +// would otherwise be shadowed by the interceptor. These let the get/set +// interceptors below check the prototype chain first and defer to it when a +// descriptor is found there. +bool findPrototypeDescriptor(Runtime& runtime, v8::Local object, + v8::Local property, + v8::Local* descriptorOut) { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local currentValue = object->GetPrototypeV2(); + for (size_t depth = 0; depth < 64 && currentValue->IsObject(); depth++) { + v8::Local current = currentValue.As(); + v8::Local descriptorValue; + if (!current->GetOwnPropertyDescriptor(runtime.context(), property) + .ToLocal(&descriptorValue)) { + throw JSError(runtime, + currentExceptionMessage(runtime.isolate(), tryCatch)); + } + if (descriptorValue->IsObject()) { + *descriptorOut = descriptorValue.As(); + return true; + } + currentValue = current->GetPrototypeV2(); + } + return false; +} + +bool tryResolvePrototypeGet(Runtime& runtime, v8::Local object, + v8::Local receiver, + v8::Local property, + v8::Local* resultOut) { + v8::Local descriptor; + if (!findPrototypeDescriptor(runtime, object, property, &descriptor)) { + return false; + } + + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local getKey = makeV8String(runtime.isolate(), "get"); + v8::Local getterValue; + if (!descriptor->Get(runtime.context(), getKey).ToLocal(&getterValue)) { + throw JSError(runtime, currentExceptionMessage(runtime.isolate(), tryCatch)); + } + if (getterValue->IsFunction()) { + v8::Local result; + if (!getterValue.As() + ->Call(runtime.context(), receiver, 0, nullptr) + .ToLocal(&result)) { + throw JSError(runtime, + currentExceptionMessage(runtime.isolate(), tryCatch)); + } + *resultOut = result; + return true; + } + + v8::Local valueKey = makeV8String(runtime.isolate(), "value"); + bool hasValue = + descriptor->HasOwnProperty(runtime.context(), valueKey).FromMaybe(false); + if (hasValue) { + v8::Local value; + if (!descriptor->Get(runtime.context(), valueKey).ToLocal(&value)) { + throw JSError(runtime, + currentExceptionMessage(runtime.isolate(), tryCatch)); + } + *resultOut = value; + return true; + } + + *resultOut = v8::Undefined(runtime.isolate()); + return true; +} + +bool tryInvokePrototypeSetter(Runtime& runtime, v8::Local object, + v8::Local receiver, + v8::Local property, + v8::Local value) { + v8::Local descriptor; + if (!findPrototypeDescriptor(runtime, object, property, &descriptor)) { + return false; + } + + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local setKey = makeV8String(runtime.isolate(), "set"); + v8::Local setterValue; + if (!descriptor->Get(runtime.context(), setKey).ToLocal(&setterValue)) { + throw JSError(runtime, currentExceptionMessage(runtime.isolate(), tryCatch)); + } + if (!setterValue->IsFunction()) { + return false; + } + + v8::Local args[] = {value}; + v8::Local ignored; + if (!setterValue.As() + ->Call(runtime.context(), receiver, 1, args) + .ToLocal(&ignored)) { + throw JSError(runtime, currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return true; +} + +v8::Local hostObjectTemplate(Runtime& runtime) { + auto state = runtime.state(); + if (state->hostObjectTemplate.IsEmpty()) { + v8::Local objectTemplate = v8::ObjectTemplate::New(runtime.isolate()); + objectTemplate->SetInternalFieldCount(1); + // toString must be own property to override Object.prototype.toString + // when using kNonMasking interceptor. + objectTemplate->Set( + makeV8String(runtime.isolate(), "toString"), + v8::FunctionTemplate::New(runtime.isolate(), + [](const v8::FunctionCallbackInfo& info) { + v8::Local self = info.This(); + if (self.IsEmpty() || self->InternalFieldCount() < 1) return; + auto* holder = static_cast( + self->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) return; + Runtime rt(holder->state); + try { + Value toStr = holder->hostObject->get(rt, PropNameID("toString")); + if (!toStr.isUndefined()) { + v8::Local v8Val = toStr.local(rt); + if (v8Val->IsFunction()) { + v8::Local result; + if (v8Val.As()->Call(rt.context(), self, 0, nullptr) + .ToLocal(&result)) { + info.GetReturnValue().Set(result); + return; + } + } + } + } catch (...) {} + }), + v8::DontEnum); + objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration( + [](v8::Local property, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + // Fast path: skip symbols entirely (they never match our properties). + if (!property->IsString()) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + v8::Isolate* isolate = info.GetIsolate(); + v8::String::Utf8Value utf8(isolate, property); + if (*utf8 == nullptr) { + return v8::Intercepted::kNo; + } + Value result = holder->hostObject->get( + runtime, PropNameID(std::string(*utf8, utf8.length()))); + if (!result.isUndefined()) { + info.GetReturnValue().Set(result.local(runtime)); + return v8::Intercepted::kYes; + } + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + return v8::Intercepted::kNo; + }, + [](v8::Local property, v8::Local value, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + bool handled = holder->hostObject->set( + runtime, PropNameID(propertyNameToUtf8(info.GetIsolate(), property)), + Value(runtime, value)); + return handled ? v8::Intercepted::kYes : v8::Intercepted::kNo; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + }, + nullptr, nullptr, + [](const v8::PropertyCallbackInfo& info) { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) { + return; + } + Runtime runtime(holder->state); + try { + auto propertyNames = holder->hostObject->getPropertyNames(runtime); + v8::Local result = + v8::Array::New(info.GetIsolate(), static_cast(propertyNames.size())); + for (size_t i = 0; i < propertyNames.size(); i++) { + std::string name = propertyNames[i].utf8(runtime); + result + ->Set(runtime.context(), static_cast(i), + makeV8String(info.GetIsolate(), name)) + .FromMaybe(false); + } + info.GetReturnValue().Set(result); + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + } + }, + v8::Local(), v8::PropertyHandlerFlags::kNone)); + objectTemplate->SetHandler(v8::IndexedPropertyHandlerConfiguration( + [](uint32_t index, const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + Value result = holder->hostObject->get(runtime, PropNameID(std::to_string(index))); + if (!result.isUndefined()) { + info.GetReturnValue().Set(result.local(runtime)); + return v8::Intercepted::kYes; + } + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + return v8::Intercepted::kNo; + }, + [](uint32_t index, v8::Local value, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + holder->hostObject->set(runtime, PropNameID(std::to_string(index)), + Value(runtime, value)); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + }, + nullptr, nullptr, nullptr, v8::Local(), + v8::PropertyHandlerFlags::kNone)); + state->hostObjectTemplate.Reset(runtime.isolate(), objectTemplate); + } + return state->hostObjectTemplate.Get(runtime.isolate()); +} + +// Template for native object instances — uses kNonMasking so V8 checks +// prototype chain first (methods/properties installed there are found +// without calling the interceptor). +v8::Local nativeObjectTemplate(Runtime& runtime) { + auto state = runtime.state(); + if (state->nativeObjectTemplate.IsEmpty()) { + v8::Local objectTemplate = v8::ObjectTemplate::New(runtime.isolate()); + objectTemplate->SetInternalFieldCount(1); + // toString must be own property to override Object.prototype.toString + objectTemplate->Set( + makeV8String(runtime.isolate(), "toString"), + v8::FunctionTemplate::New(runtime.isolate(), + [](const v8::FunctionCallbackInfo& info) { + v8::Local self = info.This(); + if (self.IsEmpty() || self->InternalFieldCount() < 1) return; + auto* holder = static_cast( + self->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) return; + Runtime rt(holder->state); + try { + Value toStr = holder->hostObject->get(rt, PropNameID("toString")); + if (!toStr.isUndefined()) { + v8::Local v8Val = toStr.local(rt); + if (v8Val->IsFunction()) { + v8::Local result; + if (v8Val.As()->Call(rt.context(), self, 0, nullptr) + .ToLocal(&result)) { + info.GetReturnValue().Set(result); + return; + } + } + } + } catch (...) {} + }), + v8::DontEnum); + objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration( + [](v8::Local property, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + if (!property->IsString()) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + v8::Isolate* isolate = info.GetIsolate(); + v8::String::Utf8Value utf8(isolate, property); + if (*utf8 == nullptr) { + return v8::Intercepted::kNo; + } + v8::Local holderObject = info.Holder(); + v8::Local receiver = + info.This()->IsObject() ? info.This().As() : holderObject; + v8::Local prototypeResult; + if (tryResolvePrototypeGet(runtime, holderObject, receiver, + property, &prototypeResult)) { + info.GetReturnValue().Set(prototypeResult); + return v8::Intercepted::kYes; + } + Value result = holder->hostObject->get( + runtime, PropNameID(std::string(*utf8, utf8.length()))); + if (!result.isUndefined()) { + info.GetReturnValue().Set(result.local(runtime)); + return v8::Intercepted::kYes; + } + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + return v8::Intercepted::kNo; + }, + [](v8::Local property, v8::Local value, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + if (!property->IsString()) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + v8::Isolate* isolate = info.GetIsolate(); + v8::String::Utf8Value utf8(isolate, property); + if (*utf8 == nullptr) { + return v8::Intercepted::kNo; + } + v8::Local holderObject = info.Holder(); + v8::Local receiver = + info.This()->IsObject() ? info.This().As() : holderObject; + if (tryInvokePrototypeSetter(runtime, holderObject, receiver, + property, value)) { + return v8::Intercepted::kYes; + } + bool handled = holder->hostObject->set( + runtime, PropNameID(std::string(*utf8, utf8.length())), + Value(runtime, value)); + return handled ? v8::Intercepted::kYes : v8::Intercepted::kNo; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + }, + nullptr, nullptr, nullptr, v8::Local(), + v8::PropertyHandlerFlags::kNonMasking)); + objectTemplate->SetHandler(v8::IndexedPropertyHandlerConfiguration( + [](uint32_t index, const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + Value result = holder->hostObject->get(runtime, PropNameID(std::to_string(index))); + if (!result.isUndefined()) { + info.GetReturnValue().Set(result.local(runtime)); + return v8::Intercepted::kYes; + } + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + return v8::Intercepted::kNo; + }, + [](uint32_t index, v8::Local value, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + holder->hostObject->set(runtime, PropNameID(std::to_string(index)), + Value(runtime, value)); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + }, + nullptr, nullptr, nullptr, v8::Local(), + v8::PropertyHandlerFlags::kNonMasking)); + state->nativeObjectTemplate.Reset(runtime.isolate(), objectTemplate); + } + return state->nativeObjectTemplate.Get(runtime.isolate()); +} + +void hostObjectWeakCallback(const v8::WeakCallbackInfo& info) { + untrackRuntimeAllocation(info.GetIsolate(), info.GetParameter()); + delete info.GetParameter(); +} + +void functionWeakCallback(const v8::WeakCallbackInfo& info) { + untrackRuntimeAllocation(info.GetIsolate(), info.GetParameter()); + delete info.GetParameter(); +} + +} // namespace v8engine + +Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken) { + v8::Local object = + v8engine::hostObjectTemplate(runtime)->NewInstance(runtime.context()).ToLocalChecked(); + auto* holder = new v8engine::HostObjectHolder(runtime.state(), std::move(host), typeToken); + v8engine::trackRuntimeAllocation(runtime.isolate(), holder); + object->SetAlignedPointerInInternalField(0, holder); + holder->object.Reset(runtime.isolate(), object); + holder->object.SetWeak(holder, v8engine::hostObjectWeakCallback, + v8::WeakCallbackType::kParameter); + return Object::fromValueStorage(Value(runtime, object).storage_); +} + +Object Object::createNativeInstanceWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken) { + v8::Local object = + v8engine::nativeObjectTemplate(runtime)->NewInstance(runtime.context()).ToLocalChecked(); + auto* holder = new v8engine::HostObjectHolder(runtime.state(), std::move(host), typeToken); + v8engine::trackRuntimeAllocation(runtime.isolate(), holder); + object->SetAlignedPointerInInternalField(0, holder); + holder->object.Reset(runtime.isolate(), object); + holder->object.SetWeak(holder, v8engine::hostObjectWeakCallback, + v8::WeakCallbackType::kParameter); + return Object::fromValueStorage(Value(runtime, object).storage_); +} + +Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback) { + auto* holder = new v8engine::FunctionHolder(runtime.state(), std::move(callback)); + v8engine::trackRuntimeAllocation(runtime.isolate(), holder); + v8::Local data = v8::External::New(runtime.isolate(), holder); + v8::Local functionTemplate = v8::FunctionTemplate::New( + runtime.isolate(), + [](const v8::FunctionCallbackInfo& info) { + auto* holder = + static_cast(info.Data().As()->Value()); + Runtime runtime(holder->state); + StackValueArray args(static_cast(info.Length())); + for (int i = 0; i < info.Length(); i++) { + args.emplace(static_cast(i), Value::borrowed(runtime, info[i])); + } + try { + Value thisValue = Value::borrowed(runtime, info.This()); + Value result = holder->callback(runtime, thisValue, args.size() == 0 ? nullptr : args.data(), + args.size()); + info.GetReturnValue().Set(result.local(runtime)); + } catch (const std::exception& exception) { + v8engine::throwV8Exception(info.GetIsolate(), exception); + } + }, + data); + v8::Local function = + functionTemplate->GetFunction(runtime.context()).ToLocalChecked(); + std::string functionName = name.utf8(runtime); + if (!functionName.empty()) { + function->SetName(v8engine::makeV8String(runtime.isolate(), functionName)); + } + holder->function.Reset(runtime.isolate(), function); + holder->function.SetWeak(holder, v8engine::functionWeakCallback, + v8::WeakCallbackType::kParameter); + return Function(Object::fromValueStorage(Value(runtime, function).storage_)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm b/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm new file mode 100644 index 000000000..262be2e77 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm @@ -0,0 +1,593 @@ +// Included by NativeApiV8SelectorGroups.mm inside the NativeScript anonymous namespace. + +std::string v8StringToUtf8(v8::Isolate* isolate, + v8::Local value) { + v8::String::Utf8Value utf8(isolate, value); + return *utf8 != nullptr ? std::string(*utf8, utf8.length()) : std::string(); +} + +template +std::shared_ptr v8HostObject(Runtime& runtime, v8::Local value) { + if (value.IsEmpty() || !value->IsObject()) { + return nullptr; + } + v8::Local object = value.As(); + if (object->InternalFieldCount() < 1) { + return nullptr; + } + auto* holder = static_cast( + object->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || + holder->typeToken != engine::v8engine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); +} + +// Fast version that returns raw pointer (no atomic ref count). +// Only safe when the caller guarantees the object stays alive. +template +T* v8HostObjectRaw(v8::Local value) { + if (value.IsEmpty() || !value->IsObject()) { + return nullptr; + } + v8::Local object = value.As(); + if (object->InternalFieldCount() < 1) { + return nullptr; + } + auto* holder = static_cast( + object->GetAlignedPointerFromInternalField(0)); + if (holder == nullptr || + holder->typeToken != engine::v8engine::hostObjectTypeToken()) { + return nullptr; + } + return static_cast(holder->hostObject.get()); +} + +id v8NativeObjectArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, + v8::Local value, + NativeApiArgumentFrame& frame) { + v8::Isolate* isolate = runtime.isolate(); + if (value.IsEmpty() || value->IsNullOrUndefined()) { + return nil; + } + if (value->IsString()) { + std::string utf8 = v8StringToUtf8(isolate, value); + id string = type.kind == metagen::mdTypeNSMutableStringObject + ? [[NSMutableString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding] + : [[NSString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding]; + if (string != nil) { + frame.addObject(string); + } + return string; + } + if (value->IsBoolean()) { + return [NSNumber numberWithBool:value->BooleanValue(isolate)]; + } + if (value->IsNumber()) { + return [NSNumber numberWithDouble:value->NumberValue(runtime.context()) + .FromMaybe(0)]; + } + if (!value->IsObject()) { + return nil; + } + if (auto objectHost = + v8HostObject(runtime, value)) { + return objectHost->object(); + } + if (auto classHost = v8HostObject(runtime, value)) { + return static_cast(classHost->nativeClass()); + } + if (auto protocolHost = + v8HostObject(runtime, value)) { + return static_cast(protocolHost->nativeProtocol()); + } + if (auto pointerHost = + v8HostObject(runtime, value)) { + return static_cast(pointerHost->pointer()); + } + if (auto referenceHost = + v8HostObject(runtime, value)) { + return static_cast(referenceHost->data()); + } + if (auto structHost = + v8HostObject(runtime, value)) { + return static_cast(structHost->data()); + } + + v8::Local wrappedClassValue; + if (value.As() + ->Get(runtime.context(), + engine::v8engine::makeV8String(isolate, "__nativeApiClass")) + .ToLocal(&wrappedClassValue)) { + if (auto classHost = + v8HostObject(runtime, wrappedClassValue)) { + return static_cast(classHost->nativeClass()); + } + } + + Value wrapped = Value::borrowed(runtime, value); + return objectFromEngineValue(runtime, bridge, wrapped, frame, + type.kind == + metagen::mdTypeNSMutableStringObject); +} + +Class v8NativeClassArgument(Runtime& runtime, v8::Local value) { + if (value.IsEmpty() || value->IsNullOrUndefined()) { + return Nil; + } + auto* state = runtime.rawState(); + if (state != nullptr && value->IsObject()) { + if (state->nativeClassArgumentLast.nativeClass != Nil && + !state->nativeClassArgumentLast.value.IsEmpty() && + state->nativeClassArgumentLast.value.Get(runtime.isolate()) == value) { + return state->nativeClassArgumentLast.nativeClass; + } + for (auto& entry : state->nativeClassArgumentCache) { + if (entry.nativeClass != Nil && !entry.value.IsEmpty() && + entry.value.Get(runtime.isolate()) == value) { + state->nativeClassArgumentLast.value.Reset(runtime.isolate(), value); + state->nativeClassArgumentLast.nativeClass = entry.nativeClass; + return entry.nativeClass; + } + } + } + + Class result = Nil; + if (auto classHost = v8HostObject(runtime, value)) { + result = classHost->nativeClass(); + } else if (value->IsObject()) { + v8::Local wrappedClassValue; + if (value.As() + ->Get(runtime.context(), + engine::v8engine::makeV8String(runtime.isolate(), + "__nativeApiClass")) + .ToLocal(&wrappedClassValue)) { + if (auto classHost = + v8HostObject(runtime, + wrappedClassValue)) { + result = classHost->nativeClass(); + } + } + } + + if (result == Nil) { + Value wrapped = Value::borrowed(runtime, value); + result = classFromEngineValue(runtime, wrapped); + } + + if (result != Nil && state != nullptr && value->IsObject()) { + constexpr size_t cacheSize = + sizeof(state->nativeClassArgumentCache) / + sizeof(state->nativeClassArgumentCache[0]); + auto& entry = state->nativeClassArgumentCache[ + state->nativeClassArgumentCacheNext++ % cacheSize]; + entry.value.Reset(runtime.isolate(), value); + entry.nativeClass = result; + state->nativeClassArgumentLast.value.Reset(runtime.isolate(), value); + state->nativeClassArgumentLast.nativeClass = result; + } + return result; +} + +bool readV8EngineSelectorArgument(Runtime& runtime, v8::Local value, + SEL* result) { + if (result == nullptr) { + return false; + } + if (value.IsEmpty() || value->IsNullOrUndefined()) { + *result = nullptr; + return true; + } + if (!value->IsString()) { + return false; + } + auto* state = runtime.rawState(); + if (state != nullptr) { + if (state->nativeSelectorArgumentLast.selector != nullptr && + !state->nativeSelectorArgumentLast.value.IsEmpty() && + state->nativeSelectorArgumentLast.value.Get(runtime.isolate()) == + value) { + *result = state->nativeSelectorArgumentLast.selector; + return true; + } + for (auto& entry : state->nativeSelectorArgumentCache) { + if (entry.selector != nullptr && !entry.value.IsEmpty() && + entry.value.Get(runtime.isolate()) == value) { + *result = entry.selector; + state->nativeSelectorArgumentLast.value.Reset(runtime.isolate(), value); + state->nativeSelectorArgumentLast.selector = entry.selector; + return true; + } + } + } + + v8::Isolate* isolate = runtime.isolate(); + v8::Local string = value.As(); + char stackBuffer[128]; + if (string->Utf8LengthV2(isolate) + 1 <= sizeof(stackBuffer)) { + string->WriteUtf8V2(isolate, stackBuffer, sizeof(stackBuffer), + v8::String::WriteFlags::kNullTerminate); + *result = sel_registerName(stackBuffer); + } else { + std::string selectorName = v8StringToUtf8(isolate, value); + *result = sel_registerName(selectorName.c_str()); + } + if (*result != nullptr && state != nullptr) { + constexpr size_t cacheSize = + sizeof(state->nativeSelectorArgumentCache) / + sizeof(state->nativeSelectorArgumentCache[0]); + auto& entry = state->nativeSelectorArgumentCache[ + state->nativeSelectorArgumentCacheNext++ % cacheSize]; + entry.value.Reset(isolate, value); + entry.selector = *result; + state->nativeSelectorArgumentLast.value.Reset(isolate, value); + state->nativeSelectorArgumentLast.selector = *result; + } + return true; +} + +bool prepareV8EngineArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, v8::Local value, + NativeApiArgumentFrame& frame, size_t index) { + ffi_type* ffiType = ffiTypeForEngineArgument(type); + size_t size = + ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); + void* target = frame.storageAt(index, size); + + switch (type.kind) { + case metagen::mdTypeBool: + if (!value->IsBoolean()) { + return false; + } + *static_cast(target) = + value->BooleanValue(runtime.isolate()) ? 1 : 0; + return true; + case metagen::mdTypeChar: { + int32_t converted = 0; + if (!value->Int32Value(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: { + uint32_t converted = 0; + if (!value->Uint32Value(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeSShort: { + int32_t converted = 0; + if (!value->Int32Value(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: { + if (value->IsString()) { + std::string text = v8StringToUtf8(runtime.isolate(), value); + if (text.size() != 1) { + return false; + } + *static_cast(target) = + static_cast(static_cast(text[0])); + return true; + } + uint32_t converted = 0; + if (!value->Uint32Value(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeSInt: + return value->Int32Value(runtime.context()).To( + static_cast(target)); + case metagen::mdTypeUInt: + return value->Uint32Value(runtime.context()).To( + static_cast(target)); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: { + if (value->IsBigInt()) { + bool lossless = false; + *static_cast(target) = + value.As()->Int64Value(&lossless); + return true; + } + return value->IntegerValue(runtime.context()).To( + static_cast(target)); + } + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: { + if (value->IsBigInt()) { + bool lossless = false; + *static_cast(target) = + value.As()->Uint64Value(&lossless); + return true; + } + int64_t converted = 0; + if (!value->IntegerValue(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeFloat: { + double converted = 0; + if (!value->NumberValue(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeDouble: + return value->NumberValue(runtime.context()).To( + static_cast(target)); + case metagen::mdTypeSelector: + return readV8EngineSelectorArgument(runtime, value, + static_cast(target)); + case metagen::mdTypeClass: { + Class cls = v8NativeClassArgument(runtime, value); + if (cls == Nil) { + return false; + } + *static_cast(target) = cls; + return true; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + *static_cast(target) = + v8NativeObjectArgument(runtime, bridge, type, value, frame); + return true; + default: + break; + } + + Value wrapped = Value::borrowed(runtime, value); + convertEngineFfiArgument(runtime, bridge, type, wrapped, target, frame); + return true; +} + +v8::Local v8Integer64Value(v8::Isolate* isolate, int64_t value) { + constexpr int64_t maxSafeInteger = 9007199254740991LL; + constexpr int64_t minSafeInteger = -9007199254740991LL; + if (value >= minSafeInteger && value <= maxSafeInteger) { + return v8::Number::New(isolate, static_cast(value)); + } + return v8::BigInt::New(isolate, value); +} + +v8::Local v8UnsignedInteger64Value(v8::Isolate* isolate, + uint64_t value) { + constexpr uint64_t maxSafeInteger = 9007199254740991ULL; + if (value <= maxSafeInteger) { + return v8::Number::New(isolate, static_cast(value)); + } + return v8::BigInt::NewFromUnsigned(isolate, value); +} + +bool setV8EngineObjectReturn( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, id object, + const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = runtime.isolate(); + if (object == nil) { + info.GetReturnValue().Set(v8::Null(isolate)); + return true; + } + Value roundTrip = + findCachedNativeObjectReturn(runtime, bridge, type, object); + if (!roundTrip.isUndefined()) { + info.GetReturnValue().Set(roundTrip.local(runtime)); + if (type.returnOwned) { + [object release]; + } + return true; + } + if (nativeObjectReturnMayCoerceToString(type) && + nativeObjectIsStringLike(object)) { + std::string utf8 = utf8StringFromNSString(static_cast(object)); + if (type.returnOwned) { + [object release]; + } + info.GetReturnValue().Set(engine::v8engine::makeV8String(isolate, utf8)); + return true; + } + if ([object isKindOfClass:[NSNull class]]) { + if (type.returnOwned) { + [object release]; + } + info.GetReturnValue().Set(v8::Null(isolate)); + return true; + } + if ([object isKindOfClass:[NSNumber class]] && + ![object isKindOfClass:[NSDecimalNumber class]]) { + NSNumber* number = static_cast(object); + const char* objCType = [number objCType]; + bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == + CFBooleanGetTypeID() || + (objCType != nullptr && + std::strcmp(objCType, @encode(BOOL)) == 0); + if (isBool) { + info.GetReturnValue().Set(v8::Boolean::New(isolate, [number boolValue])); + } else { + info.GetReturnValue().Set(v8::Number::New(isolate, [number doubleValue])); + } + if (type.returnOwned) { + [object release]; + } + return true; + } + + if (const NativeApiSymbol* classSymbol = + bridge->findClassForRuntimePointer((void*)object)) { + Value result = makeNativeClassValue(runtime, bridge, *classSymbol); + info.GetReturnValue().Set(result.local(runtime)); + if (type.returnOwned) { + [object release]; + } + return true; + } + if (const NativeApiSymbol* protocolSymbol = + bridge->findProtocolForRuntimePointer((void*)object)) { + Value result = makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + info.GetReturnValue().Set(result.local(runtime)); + if (type.returnOwned) { + [object release]; + } + return true; + } + Value result = makeNativeObjectValue(runtime, bridge, object, type.returnOwned); + info.GetReturnValue().Set(result.local(runtime)); + return true; +} + +bool setV8EngineReturnValue( + Runtime& runtime, const std::shared_ptr& bridge, + NativeApiType type, void* value, const std::string& selectorName, + const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = runtime.isolate(); + switch (type.kind) { + case metagen::mdTypeVoid: + info.GetReturnValue().Set(v8::Undefined(isolate)); + return true; + case metagen::mdTypeBool: + info.GetReturnValue().Set( + v8::Boolean::New(isolate, *static_cast(value) != 0)); + return true; + case metagen::mdTypeChar: + info.GetReturnValue().Set( + v8::Integer::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned( + isolate, *static_cast(value))); + return true; + case metagen::mdTypeSShort: + info.GetReturnValue().Set( + v8::Integer::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeUShort: + info.GetReturnValue().Set( + v8::Integer::NewFromUnsigned(isolate, *static_cast(value))); + return true; + case metagen::mdTypeUnichar: { + const char16_t unit = *static_cast(value); + // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates + // fall back to U+FFFD). + char buffer[4] = {0}; + size_t length = 0; + if (unit < 0x80) { + buffer[length++] = static_cast(unit); + } else if (unit < 0x800) { + buffer[length++] = static_cast(0xC0 | (unit >> 6)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } else if (unit >= 0xD800 && unit <= 0xDFFF) { + buffer[length++] = static_cast(0xEF); + buffer[length++] = static_cast(0xBF); + buffer[length++] = static_cast(0xBD); + } else { + buffer[length++] = static_cast(0xE0 | (unit >> 12)); + buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } + info.GetReturnValue().Set( + engine::v8engine::makeV8String(isolate, std::string(buffer, length))); + return true; + } + case metagen::mdTypeSInt: + info.GetReturnValue().Set( + v8::Integer::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeUInt: + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned( + isolate, *static_cast(value))); + return true; + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + info.GetReturnValue().Set( + v8Integer64Value(isolate, *static_cast(value))); + return true; + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + info.GetReturnValue().Set( + v8UnsignedInteger64Value(isolate, *static_cast(value))); + return true; + case metagen::mdTypeFloat: + info.GetReturnValue().Set( + v8::Number::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeDouble: + info.GetReturnValue().Set( + v8::Number::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeClass: { + Class cls = *static_cast(value); + if (cls == nil) { + info.GetReturnValue().Set(v8::Null(isolate)); + return true; + } + const char* name = class_getName(cls); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + info.GetReturnValue().Set(result.local(runtime)); + return true; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + if ((selectorName == "valueForKey:" || + selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(type)) { + type.kind = metagen::mdTypeAnyObject; + } + return setV8EngineObjectReturn(runtime, bridge, type, + *static_cast(value), info); + case metagen::mdTypeSelector: { + SEL selector = *static_cast(value); + const char* selectorNameValue = + selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorNameValue == nullptr) { + info.GetReturnValue().Set(v8::Null(isolate)); + } else { + info.GetReturnValue().Set( + engine::v8engine::makeV8String(isolate, selectorNameValue)); + } + return true; + } + default: + break; + } + Value result = convertNativeReturnValue(runtime, bridge, type, value); + info.GetReturnValue().Set(result.local(runtime)); + return true; +} diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h new file mode 100644 index 000000000..633ae6099 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h @@ -0,0 +1,847 @@ +#ifndef NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H +#define NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H + +#ifdef TARGET_ENGINE_V8 + +#import +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../shared/RuntimeCleanupRegistry.h" +#include "Metadata.h" +#include "MetadataReader.h" +#include "ffi.h" +#include "v8.h" + +@protocol NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class BigInt; +class ArrayBuffer; + +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} +}; + +class StringBuffer { + public: + explicit StringBuffer(std::string value) : value_(std::move(value)) {} + const char* data() const { return value_.data(); } + size_t size() const { return value_.size(); } + + private: + std::string value_; +}; + +class MutableBuffer { + public: + virtual ~MutableBuffer() = default; + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) : value_(std::move(value)) {} + + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? value : ""); + } + + static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } + + std::string utf8(Runtime&) const { return value_; } + + private: + std::string value_; +}; + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); +}; + +using HostFunctionType = std::function; + +namespace v8engine { + +struct RuntimeState { + RuntimeState(v8::Isolate* isolate, v8::Local context) + : isolate(isolate), context(isolate, context) {} + + ~RuntimeState() { + nativeClassArgumentLast.value.Reset(); + nativeClassArgumentLast.nativeClass = Nil; + for (auto& entry : nativeClassArgumentCache) { + entry.value.Reset(); + entry.nativeClass = Nil; + } + nativeSelectorArgumentLast.value.Reset(); + nativeSelectorArgumentLast.selector = nullptr; + for (auto& entry : nativeSelectorArgumentCache) { + entry.value.Reset(); + entry.selector = nullptr; + } + context.Reset(); + } + + v8::Local localContext() const { + return context.Get(isolate); + } + + v8::Isolate* isolate = nullptr; + v8::Global context; + v8::Global hostObjectTemplate; + v8::Global nativeObjectTemplate; // kNonMasking for instances + struct NativeClassArgumentCacheEntry { + v8::Global value; + Class nativeClass = Nil; + }; + NativeClassArgumentCacheEntry nativeClassArgumentLast; + NativeClassArgumentCacheEntry nativeClassArgumentCache[4]; + size_t nativeClassArgumentCacheNext = 0; + struct NativeSelectorArgumentCacheEntry { + v8::Global value; + SEL selector = nullptr; + }; + NativeSelectorArgumentCacheEntry nativeSelectorArgumentLast; + NativeSelectorArgumentCacheEntry nativeSelectorArgumentCache[4]; + size_t nativeSelectorArgumentCacheNext = 0; +}; + +struct ValueStorage { + enum class Kind : uint8_t { + Undefined, + Null, + Bool, + Number, + V8, + V8Borrowed, + }; + + explicit ValueStorage(Kind kind) : kind(kind) {} + + ~ValueStorage() { value.Reset(); } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + v8::Global value; + v8::Local borrowedValue; +}; + +template +const void* hostObjectTypeToken() { + static int token = 0; + return &token; +} + +struct HostObjectHolder { + HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, + const void* typeToken) + : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} + + ~HostObjectHolder() { object.Reset(); } + + std::shared_ptr state; + std::shared_ptr hostObject; + const void* typeToken = nullptr; + v8::Global object; +}; + +struct FunctionHolder { + FunctionHolder(std::shared_ptr state, HostFunctionType callback) + : state(std::move(state)), callback(std::move(callback)) {} + + ~FunctionHolder() { function.Reset(); } + + std::shared_ptr state; + HostFunctionType callback; + v8::Global function; +}; + +inline thread_local std::unordered_map + runtimeAllocations; + +template +void trackRuntimeAllocation(v8::Isolate* isolate, T* allocation) { + runtimeAllocations[isolate].track(allocation); +} + +inline void untrackRuntimeAllocation(v8::Isolate* isolate, + void* allocation) { + auto runtime = runtimeAllocations.find(isolate); + if (runtime == runtimeAllocations.end()) { + return; + } + runtime->second.untrack(allocation); + if (runtime->second.empty()) { + runtimeAllocations.erase(runtime); + } +} + +inline void cleanupRuntimeAllocations(v8::Isolate* isolate) { + auto runtime = runtimeAllocations.find(isolate); + if (runtime == runtimeAllocations.end()) { + return; + } + + runtime->second.cleanup(); + runtimeAllocations.erase(runtime); +} + +struct ArrayBufferHolder { + explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + + std::shared_ptr buffer; + v8::Global object; +}; + +inline v8::Local makeV8String(v8::Isolate* isolate, const std::string& value) { + return v8::String::NewFromUtf8(isolate, value.c_str(), v8::NewStringType::kNormal, + static_cast(value.size())) + .ToLocalChecked(); +} + +inline std::string toUtf8(v8::Isolate* isolate, v8::Local value) { + if (value.IsEmpty()) { + return {}; + } + v8::String::Utf8Value utf8(isolate, value); + return *utf8 != nullptr ? std::string(*utf8, utf8.length()) : std::string(); +} + +inline std::string propertyNameToUtf8(v8::Isolate* isolate, v8::Local property) { + if (property->IsSymbol() && + property.As()->StrictEquals(v8::Symbol::GetIterator(isolate))) { + return "Symbol.iterator"; + } + return toUtf8(isolate, property); +} + +inline std::string currentExceptionMessage(v8::Isolate* isolate, v8::TryCatch& tryCatch) { + if (tryCatch.HasCaught()) { + return toUtf8(isolate, tryCatch.Exception()); + } + return "NativeScript V8 engine operation failed."; +} + +inline void throwV8Exception(v8::Isolate* isolate, const std::exception& exception) { + isolate->ThrowException(v8::Exception::Error(makeV8String(isolate, exception.what()))); +} + +} // namespace v8engine + +class Runtime { + public: + Runtime(v8::Isolate* isolate, v8::Local context) + : state_(std::make_shared(isolate, context)) {} + + explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} + + v8::Isolate* isolate() const { return state_->isolate; } + v8::Local context() const { return state_->localContext(); } + v8engine::RuntimeState* rawState() const { return state_.get(); } + std::shared_ptr state() const { return state_; } + + Object global(); + + Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); + + void drainMicrotasks() { isolate()->PerformMicrotaskCheckpoint(); } + + private: + std::shared_ptr state_; +}; + +class String { + public: + String() = default; + String(Runtime& runtime, v8::Local value); + + static String createFromUtf8(Runtime& runtime, const char* value) { + return String(runtime, + v8engine::makeV8String(runtime.isolate(), value != nullptr ? value : "")); + } + + static String createFromUtf8(Runtime& runtime, const std::string& value) { + return String(runtime, v8engine::makeV8String(runtime.isolate(), value)); + } + + static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { + return String(runtime, v8::String::NewFromUtf8( + runtime.isolate(), + reinterpret_cast( + value != nullptr ? value : reinterpret_cast("")), + v8::NewStringType::kNormal, static_cast(length)) + .ToLocalChecked()); + } + + std::string utf8(Runtime& runtime) const { + return v8engine::toUtf8(runtime.isolate(), local(runtime)); + } + + v8::Local local(Runtime& runtime) const { + return storage_->value.Get(runtime.isolate()).As(); + } + + operator Value() const; + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class Value { + public: + Value() : kind_(v8engine::ValueStorage::Kind::Undefined) {} + + Value(bool value) : kind_(v8engine::ValueStorage::Kind::Bool), boolValue_(value) {} + + Value(double value) : kind_(v8engine::ValueStorage::Kind::Number), numberValue_(value) {} + + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + Value(Runtime& runtime, const Value& value) { + if (value.kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + // Promote borrowed to owned + storage_ = + std::make_shared(v8engine::ValueStorage::Kind::V8); + storage_->value.Reset(runtime.isolate(), value.borrowedValue_); + kind_ = v8engine::ValueStorage::Kind::V8; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + borrowedValue_(value.borrowedValue_), + storage_(std::move(value.storage_)) {} + Value(Runtime& runtime, const String& value); + Value(Runtime& runtime, const Object& object); + Value(Runtime& runtime, const Function& function); + Value(Runtime& runtime, const Array& array); + Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); + Value(Runtime& runtime, const BigInt& bigint); + + static Value undefined() { return Value(); } + + static Value null() { + Value value; + value.kind_ = v8engine::ValueStorage::Kind::Null; + return value; + } + + static bool strictEquals(Runtime& runtime, const Value& lhs, + const Value& rhs) { + return lhs.local(runtime)->StrictEquals(rhs.local(runtime)); + } + + bool isUndefined() const; + bool isNull() const; + bool isBool() const; + bool getBool() const; + bool isNumber() const; + double getNumber() const; + + bool isObject() const; + bool isString() const; + bool isBigInt() const; + bool isSymbol() const; + + Object asObject(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + v8::Local local(Runtime& runtime) const { + v8::Isolate* isolate = runtime.isolate(); + switch (kind_) { + case v8engine::ValueStorage::Kind::Undefined: + return v8::Undefined(isolate); + case v8engine::ValueStorage::Kind::Null: + return v8::Null(isolate); + case v8engine::ValueStorage::Kind::Bool: + return v8::Boolean::New(isolate, boolValue_); + case v8engine::ValueStorage::Kind::Number: + return v8::Number::New(isolate, numberValue_); + case v8engine::ValueStorage::Kind::V8: + return storage_->value.Get(isolate); + case v8engine::ValueStorage::Kind::V8Borrowed: + return borrowedValue_; + } + } + + Value(Runtime& runtime, v8::Local value) + : kind_(v8engine::ValueStorage::Kind::V8), + storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), value); + } + + static Value borrowed(Runtime&, v8::Local value) { + Value result; + result.kind_ = v8engine::ValueStorage::Kind::V8Borrowed; + result.borrowedValue_ = value; + return result; + } + + // Access the shared storage (for Object/Function/Array interop) + std::shared_ptr storage() const { return storage_; } + + static Value fromStorage(std::shared_ptr s) { + Value v; + v.kind_ = s->kind; + v.boolValue_ = s->boolValue; + v.numberValue_ = s->numberValue; + v.borrowedValue_ = s->borrowedValue; + v.storage_ = std::move(s); + return v; + } + + private: + friend class Runtime; + friend class Object; + friend class String; + friend class BigInt; + friend class ArrayBuffer; + friend class Function; + friend class Array; + + v8engine::ValueStorage::Kind kind_ = v8engine::ValueStorage::Kind::Undefined; + bool boolValue_ = false; + double numberValue_ = 0; + v8::Local borrowedValue_; + std::shared_ptr storage_; +}; + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), v8::Object::New(runtime.isolate())); + } + + static Object fromValueStorage(std::shared_ptr storage) { + Object object; + object.storage_ = std::move(storage); + return object; + } + + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + v8engine::hostObjectTypeToken()); + } + + // Create a native object instance using kNonMasking template for fast + // prototype-based property access. + template + static Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createNativeInstanceWithToken(runtime, std::move(baseHost), + v8engine::hostObjectTypeToken()); + } + + static Object createNativeInstanceWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + + Value getProperty(Runtime& runtime, const char* name) const { + return getProperty(runtime, + v8engine::makeV8String(runtime.isolate(), name != nullptr ? name : "")); + } + + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + + Value getProperty(Runtime& runtime, const Value& key) const { + return getProperty(runtime, key.local(runtime)); + } + + Value getProperty(Runtime& runtime, v8::Local key) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->Get(runtime.context(), key).ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + Object getPropertyAsObject(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime); + } + + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value) { + setProperty(runtime, v8engine::makeV8String(runtime.isolate(), name != nullptr ? name : ""), + value); + } + + void setProperty(Runtime& runtime, const char* name, const String& value) { + setProperty(runtime, name, Value(runtime, value)); + } + + void setProperty(Runtime& runtime, const char* name, const Object& value) { + setProperty(runtime, name, Value(runtime, value)); + } + + void setProperty(Runtime& runtime, const char* name, const Function& value); + void setProperty(Runtime& runtime, const char* name, const Array& value); + void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + + void setProperty(Runtime& runtime, const std::string& name, const Value& value) { + setProperty(runtime, name.c_str(), value); + } + + void setProperty(Runtime& runtime, const Value& key, const Value& value) { + setProperty(runtime, key.local(runtime), value); + } + + void setProperty(Runtime& runtime, v8::Local key, const Value& value) { + v8::TryCatch tryCatch(runtime.isolate()); + if (!local(runtime)->Set(runtime.context(), key, value.local(runtime)).FromMaybe(false)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + } + + bool hasProperty(Runtime& runtime, const char* name) const { + v8::TryCatch tryCatch(runtime.isolate()); + return local(runtime) + ->Has(runtime.context(), + v8engine::makeV8String(runtime.isolate(), name != nullptr ? name : "")) + .FromMaybe(false); + } + + bool isFunction(Runtime& runtime) const { return local(runtime)->IsFunction(); } + bool isArray(Runtime& runtime) const { return local(runtime)->IsArray(); } + bool isArrayBuffer(Runtime& runtime) const { return local(runtime)->IsArrayBuffer(); } + + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + return holder != nullptr && holder->typeToken == v8engine::hostObjectTypeToken(); + } + + template + std::shared_ptr getHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + if (holder == nullptr || holder->typeToken != v8engine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); + } + + v8::Local local(Runtime& runtime) const { + if (storage_->kind == v8engine::ValueStorage::Kind::V8Borrowed) { + return storage_->borrowedValue.As(); + } + return storage_->value.Get(runtime.isolate()).As(); + } + + operator Value() const { + return Value::fromStorage(storage_); + } + + protected: + friend class Value; + friend class Runtime; + friend class Function; + friend class Array; + friend class ArrayBuffer; + + explicit Object(std::shared_ptr storage) : storage_(std::move(storage)) {} + + static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + + v8engine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { + v8::Local object = local(runtime); + if (object->InternalFieldCount() < 1) { + return nullptr; + } + return static_cast(object->GetAlignedPointerFromInternalField(0)); + } + + std::shared_ptr storage_; +}; + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object.storage_)) {} + + static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback); + + Value call(Runtime& runtime, const Value* args, size_t count) const { + v8::TryCatch tryCatch(runtime.isolate()); + std::vector> argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + v8::Local result; + if (!local(runtime) + .As() + ->Call(runtime.context(), runtime.context()->Global(), static_cast(argv.size()), + argv.data()) + .ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + Value call(Runtime& runtime) const { + return call(runtime, static_cast(nullptr), 0); + } + + Value call(Runtime& runtime, std::nullptr_t, size_t) const { + return call(runtime, static_cast(nullptr), 0); + } + + template + Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { + return call(runtime, static_cast(args), count); + } + + template + Value call(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return call(runtime, static_cast(argv), sizeof...(Args)); + } + + Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, + size_t count = 0) const { + v8::TryCatch tryCatch(runtime.isolate()); + std::vector> argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + v8::Local result; + if (!local(runtime) + .As() + ->Call(runtime.context(), thisObject.local(runtime), static_cast(argv.size()), + argv.data()) + .ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { + v8::TryCatch tryCatch(runtime.isolate()); + std::vector> argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + v8::Local result; + if (!local(runtime) + .As() + ->NewInstance(runtime.context(), static_cast(argv.size()), argv.data()) + .ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { + return callAsConstructor(runtime, static_cast(nullptr), 0); + } + + template + Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { + return callAsConstructor(runtime, static_cast(args), count); + } + + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); + } + + operator Value() const { + return Value::fromStorage(storage_); + } +}; + +class Array : public Object { + public: + explicit Array(Runtime& runtime, size_t size) + : Object(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), + v8::Array::New(runtime.isolate(), static_cast(size))); + } + + explicit Array(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { return local(runtime).As()->Length(); } + + Value getValueAtIndex(Runtime& runtime, size_t index) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->Get(runtime.context(), static_cast(index)).ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Value(runtime, result); + } + + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { + v8::TryCatch tryCatch(runtime.isolate()); + if (!local(runtime) + ->Set(runtime.context(), static_cast(index), value.local(runtime)) + .FromMaybe(false)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + } + + void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { + setValueAtIndex(runtime, index, Value(runtime, value)); + } + + operator Value() const { + return Value::fromStorage(storage_); + } +}; + +class BigInt { + public: + BigInt() = default; + BigInt(Runtime& runtime, v8::Local value) + : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), value); + } + + static BigInt fromInt64(Runtime& runtime, int64_t value) { + return BigInt(runtime, v8::BigInt::New(runtime.isolate(), value)); + } + + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + return BigInt(runtime, v8::BigInt::NewFromUnsigned(runtime.isolate(), value)); + } + + String toString(Runtime& runtime, int radix) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + (void)radix; + if (!local(runtime)->ToString(runtime.context()).ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return String(runtime, result); + } + + v8::Local local(Runtime& runtime) const { + return storage_->value.Get(runtime.isolate()).As(); + } + + operator Value() const { + return Value::fromStorage(storage_); + } + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : Object(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + auto holder = new v8engine::ArrayBufferHolder(std::move(buffer)); + auto backingStore = v8::ArrayBuffer::NewBackingStore( + holder->buffer->data(), holder->buffer->size(), + [](void*, size_t, void* deleterData) { + auto* holder = static_cast(deleterData); + holder->object.Reset(); + delete holder; + }, + holder); + v8::Local arrayBuffer = + v8::ArrayBuffer::New(runtime.isolate(), std::move(backingStore)); + storage_->value.Reset(runtime.isolate(), arrayBuffer); + holder->object.Reset(runtime.isolate(), arrayBuffer); + } + + explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { return local(runtime).As()->ByteLength(); } + + uint8_t* data(Runtime& runtime) const { + auto backingStore = local(runtime).As()->GetBackingStore(); + return static_cast(backingStore->Data()); + } + + operator Value() const { + return Value::fromStorage(storage_); + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 + +#endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H diff --git a/NativeScript/ffi/v8/NativeApiV8Runtime.mm b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.mm similarity index 77% rename from NativeScript/ffi/v8/NativeApiV8Runtime.mm rename to NativeScript/ffi/objc/v8/NativeApiV8Runtime.mm index 681a211b0..10f8e3d86 100644 --- a/NativeScript/ffi/v8/NativeApiV8Runtime.mm +++ b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.mm @@ -2,8 +2,8 @@ #ifdef TARGET_ENGINE_V8 -namespace facebook { -namespace jsi { +namespace nativescript { +namespace engine { Object Runtime::global() { return Object::fromValueStorage(Value(*this, context()->Global()).storage_); @@ -17,20 +17,20 @@ v8::NewStringType::kNormal, buffer != nullptr ? static_cast(buffer->size()) : 0) .ToLocalChecked(); - v8::Local resourceName = v8direct::makeV8String(isolate(), sourceURL); + v8::Local resourceName = v8engine::makeV8String(isolate(), sourceURL); v8::ScriptOrigin origin(resourceName); v8::Local script; if (!v8::Script::Compile(context(), source, &origin).ToLocal(&script)) { - throw JSError(*this, v8direct::currentExceptionMessage(isolate(), tryCatch)); + throw JSError(*this, v8engine::currentExceptionMessage(isolate(), tryCatch)); } v8::Local result; if (!script->Run(context()).ToLocal(&result)) { - throw JSError(*this, v8direct::currentExceptionMessage(isolate(), tryCatch)); + throw JSError(*this, v8engine::currentExceptionMessage(isolate(), tryCatch)); } return Value(*this, result); } -} // namespace jsi -} // namespace facebook +} // namespace engine +} // namespace nativescript #endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/objc/v8/NativeApiV8RuntimeSupport.mm b/NativeScript/ffi/objc/v8/NativeApiV8RuntimeSupport.mm new file mode 100644 index 000000000..b23a0e02e --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8RuntimeSupport.mm @@ -0,0 +1,135 @@ +// Included by NativeApiV8.mm inside the NativeScript anonymous namespace. + +struct NativeApiLazyGlobalData { + NativeApiLazyGlobalData(v8::Isolate* isolate, const std::string& name, + const std::string& kind) { + nameValue.Reset(isolate, engine::v8engine::makeV8String(isolate, name)); + kindValue.Reset(isolate, engine::v8engine::makeV8String(isolate, kind)); + } + + ~NativeApiLazyGlobalData() { + external.Reset(); + nameValue.Reset(); + kindValue.Reset(); + } + + v8::Global external; + v8::Global nameValue; + v8::Global kindValue; +}; + +void NativeApiLazyGlobalWeakCallback( + const v8::WeakCallbackInfo& info) { + engine::v8engine::untrackRuntimeAllocation(info.GetIsolate(), + info.GetParameter()); + delete info.GetParameter(); +} + +std::shared_ptr retainNativeApiRuntime(Runtime& runtime) { + return std::make_shared(runtime.state()); +} + +class NativeApiRuntimeScope final { + public: + explicit NativeApiRuntimeScope(Runtime& runtime) + : locker_(runtime.isolate()), + isolateScope_(runtime.isolate()), + handleScope_(runtime.isolate()), + context_(runtime.context()), + contextScope_(context_) {} + + private: + v8::Locker locker_; + v8::Isolate::Scope isolateScope_; + v8::HandleScope handleScope_; + v8::Local context_; + v8::Context::Scope contextScope_; +}; + +void NativeApiLazyGlobalGetter(v8::Local, + const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + v8::HandleScope handleScope(isolate); + v8::Local context = isolate->GetCurrentContext(); + if (!info.Data()->IsExternal()) { + return; + } + + auto* data = static_cast(info.Data().As()->Value()); + if (data == nullptr) { + return; + } + v8::Local nameValue = data->nameValue.Get(isolate); + v8::Local kindValue = data->kindValue.Get(isolate); + + v8::Local global = context->Global(); + v8::Local resolverValue; + if (!global + ->Get(context, engine::v8engine::makeV8String( + isolate, "__nativeScriptResolveNativeApiLazyGlobal")) + .ToLocal(&resolverValue) || + !resolverValue->IsFunction()) { + return; + } + + v8::TryCatch tryCatch(isolate); + v8::Local args[] = {nameValue, kindValue}; + v8::Local result; + if (!resolverValue.As()->Call(context, global, 2, args).ToLocal(&result)) { + if (tryCatch.HasCaught()) { + isolate->ThrowException(tryCatch.Exception()); + } + return; + } + if (global->Delete(context, nameValue).FromMaybe(false)) { + global->DefineOwnProperty(context, nameValue, result, v8::DontEnum).FromMaybe(false); + } + info.GetReturnValue().Set(result); +} + +bool InstallNativeApiLazyGlobal(Runtime& runtime, std::shared_ptr, + const std::string& name, const std::string& kind, + bool force) { + if (name.empty() || kind.empty()) { + return false; + } + + v8::Isolate* isolate = runtime.isolate(); + v8::EscapableHandleScope handleScope(isolate); + v8::Local context = runtime.context(); + v8::Local global = context->Global(); + v8::Local property = engine::v8engine::makeV8String(isolate, name); + if (!force && global->HasOwnProperty(context, property).FromMaybe(false)) { + return false; + } + + auto* data = new NativeApiLazyGlobalData(isolate, name, kind); + engine::v8engine::trackRuntimeAllocation(isolate, data); + v8::Local external = v8::External::New(isolate, data); + + bool installed = global + ->SetNativeDataProperty(context, property, NativeApiLazyGlobalGetter, + nullptr, external, v8::DontEnum) + .FromMaybe(false); + if (installed) { + data->external.Reset(isolate, external); + data->external.SetWeak(data, NativeApiLazyGlobalWeakCallback, + v8::WeakCallbackType::kParameter); + } else { + engine::v8engine::untrackRuntimeAllocation(isolate, data); + delete data; + } + return installed; +} + +void SetNativeApiObjectPrototype(Runtime& runtime, Object& object, + const Object& prototype) { + v8::TryCatch tryCatch(runtime.isolate()); + if (!object.local(runtime) + ->SetPrototypeV2(runtime.context(), prototype.local(runtime)) + .FromMaybe(false)) { + throw JSError(runtime, + engine::v8engine::currentExceptionMessage(runtime.isolate(), + tryCatch)); + } +} diff --git a/NativeScript/ffi/objc/v8/NativeApiV8SelectorGroups.mm b/NativeScript/ffi/objc/v8/NativeApiV8SelectorGroups.mm new file mode 100644 index 000000000..259df2425 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8SelectorGroups.mm @@ -0,0 +1,299 @@ +// Included by NativeApiV8.mm inside the NativeScript anonymous namespace. + +#include "../shared/bridge/SelectorGroupData.h" + +#include "NativeApiV8Marshalling.mm" + +#include "NativeApiV8Gsd.mm" + +#include "../shared/bridge/SelectorGroupCall.h" + + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId) { + return reinterpret_cast(lookupObjCGsdInvoker(dispatchId)); +} + +bool tryCallGeneratedEngineObjCSelector( + Runtime&, const std::shared_ptr&, id, + const NativeApiPreparedObjCInvocation&, const Value*, size_t, Class, + Value*) { + return false; +} + +void setV8EnginePreparedObjCResult( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const std::shared_ptr& receiverHostObject, + const std::optional& initializerClassWrapper, + const v8::FunctionCallbackInfo& info, + Class dispatchSuperClass) { + const NativeApiSignature& signature = prepared.signature; + if (receiver == nil || signature.variadic || + unsupportedEngineType(signature.returnType)) { + throw JSError(runtime, + "Objective-C selector is not supported by V8 engine: " + + prepared.selectorName); + } + + const bool isNSErrorOutMethod = prepared.isNSErrorOutMethod; + const size_t providedCount = static_cast(info.Length()); + if (isNSErrorOutMethod) { + size_t expected = signature.argumentTypes.size(); + if (providedCount > expected || providedCount + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } else if (providedCount != signature.argumentTypes.size()) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + + std::to_string(signature.argumentTypes.size()) + "\"."); + } + + // GSD fast path: the generated invoker reads args directly from + // FunctionCallbackInfo, calls objc_msgSend with a typed cast, and sets the + // return via the V8 API — all in one generated function. Bypasses all + // generic marshalling. + if (prepared.gsdEngineCallable && dispatchSuperClass == Nil && + providedCount == prepared.gsdEngineArgumentCount && + !initializerClassWrapper && !isNSErrorOutMethod) { + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, + bridge, + receiver, + prepared.selector, + info, + runtime.isolate(), + runtime.context(), + signature.returnType}; + if (invoker(ctx)) { + return; + } + } + + if (dispatchSuperClass == Nil && !initializerClassWrapper && + providedCount <= 2) { + Value fastArgs[2]; + for (size_t i = 0; i < providedCount; i++) { + fastArgs[i] = Value::borrowed(runtime, info[static_cast(i)]); + } + Value fastResult; + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, prepared, + fastArgs, providedCount, Nil, + &fastResult)) { + info.GetReturnValue().Set(fastResult.local(runtime)); + return; + } + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + for (size_t i = 0; i < providedCount; i++) { + if (!prepareV8EngineArgument(runtime, bridge, signature.argumentTypes[i], + info[static_cast(i)], frame, i)) { + throw JSError(runtime, + "Objective-C argument is not supported by V8 engine: " + + prepared.selectorName); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && providedCount + 1 == signature.argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + size_t outArgIndex = signature.argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } + + NativeApiPointerFrame values(signature.argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, const_cast(&prepared.selector)); + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared.preparedInvoker != nullptr && dispatchSuperClass == Nil) { + prepared.preparedInvoker(reinterpret_cast(objc_msgSend), + values.data(), returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature.returnType.ffiType->size > 16 && + signature.returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(const_cast(&signature.cif), target, + returnStorage.data(), values.data()); +#else + ffi_call(const_cast(&signature.cif), + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature.returnType; + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + if (initializerClassWrapper) { + id resultObject = nil; + if (isObjectiveCObjectType(returnType)) { + resultObject = *static_cast(returnStorage.data()); + } + if (receiverHostObject != nullptr && resultObject != receiver) { + receiverHostObject->disownObject(receiver); + } + if (resultObject != nil) { + bridge->setObjectExpando(runtime, resultObject, + "__nativeApiClassWrapper", + Value(runtime, *initializerClassWrapper)); + } + } + setV8EngineReturnValue(runtime, bridge, returnType, returnStorage.data(), + prepared.selectorName, info); +} + +struct NativeApiSelectorGroupDataHolder { + explicit NativeApiSelectorGroupDataHolder( + std::shared_ptr data) + : data(std::move(data)) {} + + ~NativeApiSelectorGroupDataHolder() { function.Reset(); } + + std::shared_ptr data; + v8::Global function; +}; + +void NativeApiSelectorGroupWeakCallback( + const v8::WeakCallbackInfo& info) { + engine::v8engine::untrackRuntimeAllocation(info.GetIsolate(), + info.GetParameter()); + delete info.GetParameter(); +} + +void NativeApiSelectorGroupCallback( + const v8::FunctionCallbackInfo& info) { + auto* holder = static_cast( + info.Data().As()->Value()); + auto* data = holder != nullptr ? holder->data.get() : nullptr; + if (data == nullptr || data->selectors == nullptr || + data->preparedInvocations == nullptr) { + return; + } + + Runtime& runtime = data->runtime; + v8::HandleScope handleScope(runtime.isolate()); + try { + NativeApiRoundTripCacheFrameGuard roundTripFrame(data->bridge); + size_t count = static_cast(info.Length()); + auto call = resolveNativeApiSelectorGroupCall( + runtime, *data, count, + [&]() -> id { + // The V8 handle keeps this raw host object alive for the call. + auto* host = + v8HostObjectRaw(info.This()); + return host != nullptr ? host->object() : nil; + }, + [&]() { + return v8HostObject(runtime, info.This()); + }, + [](uint64_t dispatchId) { return lookupObjCGsdInvoker(dispatchId); }); + if (call.hasImmediateResult) { + info.GetReturnValue().Set(call.immediateResult.local(runtime)); + return; + } + // Inline GSD fast path: skip the setV8EnginePreparedObjCResult call and its + // argument-count/NSError preamble entirely for the common case. The + // generated invoker reads args, calls objc_msgSend, and sets the return. + // Excludes appearance static selectors (gsdAllowed) — those need the + // generic path's proxy tagging. + if (call.prepared->gsdEngineCallable && call.dispatchClass == Nil && + !call.prepared->isInitMethod && + count == call.prepared->gsdEngineArgumentCount && + call.gsdAllowed) { + auto invoker = + reinterpret_cast(call.prepared->engineInvoker); + GsdObjCContext ctx{runtime, + data->bridge, + call.receiver, + call.prepared->selector, + info, + runtime.isolate(), + runtime.context(), + call.prepared->signature.returnType}; + if (invoker(ctx)) { + if (count > 0) { + Value setterValue = Value::borrowed(runtime, info[0]); + cachePreparedAppearanceProxySetterValue(runtime, data->bridge, + call.receiver, *call.prepared, + &setterValue, 1); + } + return; + } + } + setV8EnginePreparedObjCResult( + runtime, data->bridge, call.receiver, *call.prepared, + call.receiverHostObject, call.initializerClassWrapper, info, + call.dispatchClass); + if (!data->receiverIsClass && call.prepared->isInitMethod) { + if (auto preserved = preservedNativeApiInitializerSelfReturn( + runtime, data->bridge, call.receiver, + Value(runtime, info.GetReturnValue().Get()), + Value(runtime, info.This()))) { + info.GetReturnValue().Set(preserved->local(runtime)); + } + } + } catch (const std::exception& exception) { + engine::v8engine::throwV8Exception(info.GetIsolate(), exception); + } +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState) { + auto data = std::make_shared( + runtime.state(), std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), + std::move(boundReceiver), std::move(boundReceiverState)); + auto* holder = new NativeApiSelectorGroupDataHolder(std::move(data)); + engine::v8engine::trackRuntimeAllocation(runtime.isolate(), holder); + + v8::Local external = + v8::External::New(runtime.isolate(), holder); + v8::Local functionTemplate = + v8::FunctionTemplate::New(runtime.isolate(), + NativeApiSelectorGroupCallback, external); + v8::Local function = + functionTemplate->GetFunction(runtime.context()).ToLocalChecked(); + function->SetName( + engine::v8engine::makeV8String(runtime.isolate(), "__nativeSelectorGroup")); + holder->function.Reset(runtime.isolate(), function); + holder->function.SetWeak(holder, NativeApiSelectorGroupWeakCallback, + v8::WeakCallbackType::kParameter); + Value functionValue(runtime, function); + return functionValue.asObject(runtime).asFunction(runtime); +} diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Value.mm b/NativeScript/ffi/objc/v8/NativeApiV8Value.mm new file mode 100644 index 000000000..dab1e0271 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Value.mm @@ -0,0 +1,242 @@ +#include "NativeApiV8Runtime.h" + +#ifdef TARGET_ENGINE_V8 + +namespace nativescript { +namespace engine { + +Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } + +bool HostObject::set(Runtime&, const PropNameID&, const Value&) { return true; } + +std::vector HostObject::getPropertyNames(Runtime&) { return {}; } + +String::String(Runtime& runtime, v8::Local value) + : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), value); +} + +String::operator Value() const { + return Value::fromStorage(storage_); +} + +Value::Value(Runtime&, const String& value) { + storage_ = value.storage_; + kind_ = storage_->kind; +} +Value::Value(Runtime&, const Object& object) { + storage_ = object.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Function& function) { + storage_ = function.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Array& array) { + storage_ = array.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) { + storage_ = arrayBuffer.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const BigInt& bigint) { + storage_ = bigint.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} + +bool Value::isObject() const { + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsObject(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsObject(); +} + +bool Value::isUndefined() const { + if (kind_ == v8engine::ValueStorage::Kind::Undefined) { + return true; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return borrowedValue_.IsEmpty() || borrowedValue_->IsUndefined(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsUndefined(); +} + +bool Value::isNull() const { + if (kind_ == v8engine::ValueStorage::Kind::Null) { + return true; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsNull(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsNull(); +} + +bool Value::isBool() const { + if (kind_ == v8engine::ValueStorage::Kind::Bool) { + return true; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsBoolean(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsBoolean(); +} + +bool Value::getBool() const { + if (kind_ == v8engine::ValueStorage::Kind::Bool) { + return boolValue_; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return isolate != nullptr && !borrowedValue_.IsEmpty() + ? borrowedValue_->BooleanValue(isolate) + : false; + } + if (kind_ == v8engine::ValueStorage::Kind::V8 && storage_ && !storage_->value.IsEmpty()) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (isolate != nullptr) { + return storage_->value.Get(isolate)->BooleanValue(isolate); + } + } + return false; +} + +bool Value::isNumber() const { + if (kind_ == v8engine::ValueStorage::Kind::Number) { + return true; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsNumber(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsNumber(); +} + +double Value::getNumber() const { + if (kind_ == v8engine::ValueStorage::Kind::Number) { + return numberValue_; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (isolate != nullptr && !borrowedValue_.IsEmpty()) { + return borrowedValue_->NumberValue(isolate->GetCurrentContext()).FromMaybe(0); + } + return 0; + } + if (kind_ == v8engine::ValueStorage::Kind::V8 && storage_ && !storage_->value.IsEmpty()) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (isolate != nullptr) { + return storage_->value.Get(isolate)->NumberValue(isolate->GetCurrentContext()).FromMaybe(0); + } + } + return 0; +} + +bool Value::isString() const { + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsString(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return storage_->value.Get(isolate)->IsString(); +} + +bool Value::isBigInt() const { + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsBigInt(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return storage_->value.Get(isolate)->IsBigInt(); +} + +bool Value::isSymbol() const { + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsSymbol(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return storage_->value.Get(isolate)->IsSymbol(); +} + +Object Value::asObject(Runtime& runtime) const { + if (storage_) { + return Object::fromValueStorage(storage_); + } + // Need to promote to storage for Object + auto s = std::make_shared(kind_); + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + s->kind = v8engine::ValueStorage::Kind::V8; + s->value.Reset(runtime.isolate(), borrowedValue_); + } + return Object::fromValueStorage(std::move(s)); +} + +String Value::asString(Runtime& runtime) const { + return String(runtime, local(runtime).As()); +} + +BigInt Value::getBigInt(Runtime& runtime) const { + return BigInt(runtime, local(runtime).As()); +} + +Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime).asFunction(runtime); +} + +Function Object::asFunction(Runtime& runtime) const { return Function(*this); } + +Array Object::getArray(Runtime& runtime) const { return Array(*this); } + +ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const { return ArrayBuffer(*this); } + +Array Object::getPropertyNames(Runtime& runtime) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->GetPropertyNames(runtime.context()).ToLocal(&result)) { + throw JSError(runtime, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return Array(Object::fromValueStorage(Value(runtime, result).storage_)); +} + +void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/objc/v8/SignatureDispatch.h b/NativeScript/ffi/objc/v8/SignatureDispatch.h new file mode 100644 index 000000000..d92d60f8d --- /dev/null +++ b/NativeScript/ffi/objc/v8/SignatureDispatch.h @@ -0,0 +1,48 @@ +#ifndef NATIVESCRIPT_FFI_V8_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_FFI_V8_SIGNATURE_DISPATCH_H + +#include + +#include "ffi/objc/shared/SignatureDispatchCore.h" + +// Engine-neutral GSD (Generated Signature Dispatch). The GsdObjCContext struct, +// the ObjCGsdInvoker/ObjCGsdDispatchEntry types, the generated dispatch table, +// and lookupObjCGsdInvoker are all defined in NativeApiV8SelectorGroups.mm, +// which NativeApiV8.mm includes after the host object helpers the context +// relies on. Nothing GSD-related is declared here to avoid creating an +// ambiguous second GsdObjCContext. + +#ifndef NS_GSD_BACKEND_PREPARED +#define NS_GSD_BACKEND_PREPARED 1 +#endif + +#ifndef NS_GSD_BACKEND_NAPI +#define NS_GSD_BACKEND_NAPI 0 +#endif + +#ifndef NS_GSD_BACKEND_HERMES +#define NS_GSD_BACKEND_HERMES 0 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH 0 +#endif + +// NOTE: GeneratedGsdSignatureDispatch.inc is included from +// NativeApiV8SelectorGroups.mm after GsdObjCContext is defined (avoids +// namespace ordering issues). + +// The main .inc (prepared invokers + tables) is included here. +#if defined(__has_include) +#if __has_include("../shared/GeneratedSignatureDispatch.inc") +#include "../shared/GeneratedSignatureDispatch.inc" +#endif +#endif + +#include "ffi/objc/shared/PreparedSignatureDispatch.h" + +#endif // NATIVESCRIPT_FFI_V8_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJS.h b/NativeScript/ffi/quickjs/NativeApiQuickJS.h deleted file mode 100644 index 73c31984d..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJS.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H -#define NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H - -#include "ffi/shared/direct/NativeApiDirect.h" -#include "quickjs.h" - -namespace nativescript { - -using NativeApiQuickJSConfig = NativeApiDirectConfig; - -void InstallNativeApiQuickJS(JSContext* context, - const NativeApiQuickJSConfig& config = - NativeApiQuickJSConfig{}); - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiQuickJS(JSContext* context, - const char* metadataPath); - -#endif // NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJS.mm b/NativeScript/ffi/quickjs/NativeApiQuickJS.mm deleted file mode 100644 index b72dc695a..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJS.mm +++ /dev/null @@ -1,178 +0,0 @@ -#include "NativeApiQuickJS.h" - -#ifdef TARGET_ENGINE_QUICKJS - -#include "NativeApiQuickJSRuntime.h" - -namespace nativescript { - -using NativeApiJsiConfig = NativeApiDirectConfig; -using NativeApiJsiScheduler = NativeApiDirectScheduler; - -namespace { - -using facebook::jsi::Array; -using facebook::jsi::ArrayBuffer; -using facebook::jsi::BigInt; -using facebook::jsi::Function; -using facebook::jsi::HostObject; -using facebook::jsi::MutableBuffer; -using facebook::jsi::Object; -using facebook::jsi::PropNameID; -using facebook::jsi::Runtime; -using facebook::jsi::String; -using facebook::jsi::StringBuffer; -using facebook::jsi::Value; -using metagen::MDMemberFlag; -using metagen::MDMetadataReader; -using metagen::MDSectionOffset; -using metagen::MDTypeKind; - -// clang-format off -#include "jsi/NativeApiJsiBridge.h" -// clang-format on - -#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS 1 -#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 - -static JSValue NativeApiQuickJSLazyGlobalGetter(JSContext* context, JSValueConst, int, - JSValueConst*, int, JSValueConst* data) { - JSValue global = JS_GetGlobalObject(context); - JSValue resolver = JS_GetPropertyStr(context, global, "__nativeScriptResolveNativeApiLazyGlobal"); - if (!JS_IsFunction(context, resolver)) { - JS_FreeValue(context, resolver); - JS_FreeValue(context, global); - return JS_UNDEFINED; - } - - JSValueConst args[] = {data[0], data[1]}; - JSValue result = JS_Call(context, resolver, global, 2, args); - JS_FreeValue(context, resolver); - if (JS_IsException(result)) { - JS_FreeValue(context, global); - return result; - } - - JSAtom atom = JS_ValueToAtom(context, data[0]); - if (atom != JS_ATOM_NULL) { - JS_DefinePropertyValue(context, global, atom, JS_DupValue(context, result), - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); - JS_FreeAtom(context, atom); - } - JS_FreeValue(context, global); - return result; -} - -// Assigning over a lazy global must behave like a plain global assignment -// (@nativescript/core writes shims such as global.System); replace the -// accessor with an ordinary writable property instead of throwing -// "no setter for property". -static JSValue NativeApiQuickJSLazyGlobalSetter(JSContext* context, JSValueConst, int argc, - JSValueConst* argv, int, JSValueConst* data) { - JSValue global = JS_GetGlobalObject(context); - JSAtom atom = JS_ValueToAtom(context, data[0]); - if (atom != JS_ATOM_NULL) { - JSValue value = argc > 0 ? JS_DupValue(context, argv[0]) : JS_UNDEFINED; - JS_DefinePropertyValue(context, global, atom, value, JS_PROP_C_W_E); - JS_FreeAtom(context, atom); - } - JS_FreeValue(context, global); - return JS_UNDEFINED; -} - -bool InstallNativeApiEngineLazyGlobal(Runtime& runtime, std::shared_ptr, - const std::string& name, const std::string& kind, - bool force) { - if (name.empty() || kind.empty()) { - return false; - } - - JSContext* context = runtime.context(); - JSValue global = JS_GetGlobalObject(context); - JSAtom atom = JS_NewAtomLen(context, name.data(), name.size()); - if (atom == JS_ATOM_NULL) { - JS_FreeValue(context, global); - return false; - } - - int hasProperty = JS_HasProperty(context, global, atom); - if (!force && hasProperty > 0) { - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return false; - } - if (hasProperty < 0) { - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return false; - } - - JSValue data[] = { - JS_NewStringLen(context, name.data(), name.size()), - JS_NewStringLen(context, kind.data(), kind.size()), - }; - if (JS_IsException(data[0]) || JS_IsException(data[1])) { - JS_FreeValue(context, data[0]); - JS_FreeValue(context, data[1]); - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return false; - } - - JSValue getter = JS_NewCFunctionData(context, NativeApiQuickJSLazyGlobalGetter, 0, 0, 2, data); - JSValue setter = JS_NewCFunctionData(context, NativeApiQuickJSLazyGlobalSetter, 1, 0, 2, data); - JS_FreeValue(context, data[0]); - JS_FreeValue(context, data[1]); - if (JS_IsException(getter) || JS_IsException(setter)) { - JS_FreeValue(context, getter); - JS_FreeValue(context, setter); - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return false; - } - - int status = JS_DefinePropertyGetSet(context, global, atom, getter, setter, JS_PROP_CONFIGURABLE); - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return status >= 0; -} - -// clang-format off -#include "jsi/NativeApiJsiHostObjects.h" -// clang-format on - -std::shared_ptr retainNativeApiJsiRuntime(Runtime& runtime) { - return std::make_shared(runtime.state()); -} - -// clang-format off -#include "jsi/NativeApiJsiCallbacks.h" -#include "jsi/NativeApiJsiConversion.h" -#include "jsi/NativeApiJsiInvocation.h" -#include "jsi/NativeApiJsiClassBuilder.h" -#include "jsi/NativeApiJsiHostObject.h" -// clang-format on - -} // namespace - -#include "jsi/NativeApiJsiInstall.h" - -void InstallNativeApiQuickJS(JSContext* context, const NativeApiQuickJSConfig& config) { - if (context == nullptr) { - return; - } - auto state = facebook::jsi::quickjsdirect::stateForContext(context); - facebook::jsi::Runtime runtime(state); - facebook::jsi::quickjsdirect::ensureClasses(runtime); - InstallNativeApiJSI(runtime, config); -} - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiQuickJS(JSContext* context, const char* metadataPath) { - nativescript::NativeApiQuickJSConfig config; - config.metadataPath = metadataPath; - nativescript::InstallNativeApiQuickJS(context, config); -} - -#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJSHostObjects.mm b/NativeScript/ffi/quickjs/NativeApiQuickJSHostObjects.mm deleted file mode 100644 index 1cd706a30..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJSHostObjects.mm +++ /dev/null @@ -1,239 +0,0 @@ -#include "NativeApiQuickJSRuntime.h" - -#ifdef TARGET_ENGINE_QUICKJS - -namespace facebook { -namespace jsi { - -namespace quickjsdirect { - -JSClassID gHostClassId = 0; -JSClassID gFunctionClassId = 0; - -namespace { -std::mutex& runtimeStatesMutex() { - static auto* mutex = new std::mutex(); - return *mutex; -} - -std::unordered_map>& runtimeStates() { - static auto* states = new std::unordered_map>(); - return *states; -} -} // namespace - -std::shared_ptr stateForContext(JSContext* context) { - std::lock_guard lock(runtimeStatesMutex()); - auto& states = runtimeStates(); - auto it = states.find(context); - if (it != states.end()) { - return it->second; - } - auto state = std::make_shared(context); - states[context] = state; - return state; -} - -static JSValue nativeHostGet(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { - (void)receiver; - Runtime runtime(stateForContext(ctx)); - auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); - if (holder == nullptr || holder->hostObject == nullptr) { - return JS_UNDEFINED; - } - try { - Value result = holder->hostObject->get(runtime, PropNameID(atomToUtf8(ctx, atom))); - return result.local(runtime); - } catch (const std::exception& error) { - return throwError(ctx, error); - } -} - -static int nativeHostSet(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, - JSValueConst, int) { - Runtime runtime(stateForContext(ctx)); - auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); - if (holder == nullptr || holder->hostObject == nullptr) { - return 0; - } - try { - holder->hostObject->set(runtime, PropNameID(atomToUtf8(ctx, atom)), Value(runtime, value)); - return 1; - } catch (const std::exception& error) { - throwError(ctx, error); - return -1; - } -} - -static int nativeHostHas(JSContext* ctx, JSValueConst obj, JSAtom atom) { - Runtime runtime(stateForContext(ctx)); - auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); - if (holder == nullptr || holder->hostObject == nullptr) { - return 0; - } - try { - auto names = holder->hostObject->getPropertyNames(runtime); - std::string requested = atomToUtf8(ctx, atom); - for (const auto& name : names) { - if (name.utf8(runtime) == requested) { - return 1; - } - } - } catch (const std::exception&) { - } - return 0; -} - -static int nativeHostOwnNames(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, - JSValueConst obj) { - Runtime runtime(stateForContext(ctx)); - auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); - if (holder == nullptr || holder->hostObject == nullptr) { - *ptab = nullptr; - *plen = 0; - return 0; - } - auto names = holder->hostObject->getPropertyNames(runtime); - *plen = static_cast(names.size()); - *ptab = static_cast(js_mallocz(ctx, sizeof(JSPropertyEnum) * names.size())); - for (uint32_t i = 0; i < *plen; i++) { - (*ptab)[i].is_enumerable = true; - (*ptab)[i].atom = JS_NewAtom(ctx, names[i].utf8(runtime).c_str()); - } - return 0; -} - -static void nativeHostFinalize(JSRuntime*, JSValue value) { - auto* holder = static_cast(JS_GetOpaque(value, gHostClassId)); - delete holder; -} - -static JSValue invokeFunctionHolder(JSContext* ctx, FunctionHolder* holder, JSValueConst thisValue, - int argc, JSValueConst* argv) { - Runtime runtime(stateForContext(ctx)); - if (holder == nullptr || !holder->callback) { - return JS_UNDEFINED; - } - std::vector args; - args.reserve(argc); - for (int i = 0; i < argc; i++) { - args.emplace_back(runtime, argv[i]); - } - try { - Value self(runtime, thisValue); - Value result = - holder->callback(runtime, self, args.empty() ? nullptr : args.data(), args.size()); - return result.local(runtime); - } catch (const std::exception& error) { - return throwError(ctx, error); - } -} - -static JSValue nativeFunctionCall(JSContext* ctx, JSValue function, JSValue thisValue, int argc, - JSValue* argv, int) { - auto* holder = static_cast(JS_GetOpaque(function, gFunctionClassId)); - return invokeFunctionHolder(ctx, holder, thisValue, argc, argv); -} - -static JSValue nativeFunctionCallData(JSContext* ctx, JSValue thisValue, int argc, JSValue* argv, - int, JSValue* data) { - auto* holder = static_cast(JS_GetOpaque(data[0], gFunctionClassId)); - return invokeFunctionHolder(ctx, holder, thisValue, argc, argv); -} - -static void nativeFunctionFinalize(JSRuntime*, JSValue value) { - auto* holder = static_cast(JS_GetOpaque(value, gFunctionClassId)); - delete holder; -} - -static JSClassExoticMethods hostExoticMethods = { - .get_own_property = nullptr, - .get_own_property_names = nativeHostOwnNames, - .delete_property = nullptr, - .define_own_property = nullptr, - .has_property = nativeHostHas, - .get_property = nativeHostGet, - .set_property = nativeHostSet, -}; - -void ensureClasses(Runtime& runtime) { - auto state = runtime.state(); - JSRuntime* rt = JS_GetRuntime(runtime.context()); - if (gHostClassId == 0) { - JS_NewClassID(rt, &gHostClassId); - } - if (!state->hostClassRegistered) { - JSClassDef def = {}; - def.class_name = "NativeScriptDirectHostObject"; - def.exotic = &hostExoticMethods; - def.finalizer = nativeHostFinalize; - JS_NewClass(rt, gHostClassId, &def); - JS_SetClassProto(runtime.context(), gHostClassId, JS_NewObject(runtime.context())); - state->hostClassRegistered = true; - } - if (gFunctionClassId == 0) { - JS_NewClassID(rt, &gFunctionClassId); - } - if (!state->functionClassRegistered) { - JSClassDef def = {}; - def.class_name = "NativeScriptDirectFunction"; - def.call = nativeFunctionCall; - def.finalizer = nativeFunctionFinalize; - JS_NewClass(rt, gFunctionClassId, &def); - JS_SetClassProto(runtime.context(), gFunctionClassId, JS_NewObject(runtime.context())); - state->functionClassRegistered = true; - } -} - -} // namespace quickjsdirect - -quickjsdirect::HostObjectHolder* Object::hostObjectHolder(Runtime& runtime) const { - quickjsdirect::ensureClasses(runtime); - JSValue object = local(runtime); - auto* holder = static_cast( - JS_GetOpaque(object, quickjsdirect::gHostClassId)); - JS_FreeValue(runtime.context(), object); - return holder; -} - -Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken) { - quickjsdirect::ensureClasses(runtime); - auto* holder = new quickjsdirect::HostObjectHolder(runtime.state(), std::move(host), typeToken); - JSValue object = JS_NewObjectClass(runtime.context(), quickjsdirect::gHostClassId); - JS_SetOpaque(object, holder); - Object result = Object::fromValueStorage(Value(runtime, object).storage_); - JS_FreeValue(runtime.context(), object); - return result; -} - -Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, - unsigned int parameterCount, HostFunctionType callback) { - quickjsdirect::ensureClasses(runtime); - auto* holder = new quickjsdirect::FunctionHolder(runtime.state(), std::move(callback)); - JSValue data = JS_NewObjectClass(runtime.context(), quickjsdirect::gFunctionClassId); - if (JS_IsException(data)) { - delete holder; - throw JSError(runtime, "QuickJS host function data allocation failed."); - } - JS_SetOpaque(data, holder); - - JSValue function = JS_NewCFunctionData(runtime.context(), quickjsdirect::nativeFunctionCallData, - static_cast(parameterCount), 0, 1, &data); - JS_FreeValue(runtime.context(), data); - if (JS_IsException(function)) { - throw JSError(runtime, "QuickJS host function allocation failed."); - } - - std::string functionName = name.utf8(runtime); - JSValue nameValue = JS_NewStringLen(runtime.context(), functionName.data(), functionName.size()); - JS_DefinePropertyValueStr(runtime.context(), function, "name", nameValue, JS_PROP_CONFIGURABLE); - Function result = Function(Object::fromValueStorage(Value(runtime, function).storage_)); - JS_FreeValue(runtime.context(), function); - return result; -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.h b/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.h deleted file mode 100644 index 438a78166..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.h +++ /dev/null @@ -1,745 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H -#define NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H - -#ifdef TARGET_ENGINE_QUICKJS - -#import -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Metadata.h" -#include "MetadataReader.h" -#include "ffi.h" -#include "quickjs.h" - -@protocol NativeApiJsiClassBuilderProtocol -@end - -#ifdef EMBED_METADATA_SIZE -extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; -#endif - -namespace facebook { -namespace jsi { - -class Runtime; -class Value; -class Object; -class Function; -class Array; -class String; -class BigInt; -class ArrayBuffer; - -class JSError : public std::runtime_error { - public: - JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} - explicit JSError(const std::string& message) : std::runtime_error(message) {} -}; - -class StringBuffer { - public: - explicit StringBuffer(std::string value) : value_(std::move(value)) {} - const char* data() const { return value_.data(); } - size_t size() const { return value_.size(); } - - private: - std::string value_; -}; - -class MutableBuffer { - public: - virtual ~MutableBuffer() = default; - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -class PropNameID { - public: - PropNameID() = default; - explicit PropNameID(std::string value) : value_(std::move(value)) {} - static PropNameID forAscii(Runtime&, const char* value) { - return PropNameID(value != nullptr ? value : ""); - } - static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } - std::string utf8(Runtime&) const { return value_; } - - private: - std::string value_; -}; - -class HostObject { - public: - virtual ~HostObject() = default; - virtual Value get(Runtime& runtime, const PropNameID& name); - virtual void set(Runtime& runtime, const PropNameID& name, const Value& value); - virtual std::vector getPropertyNames(Runtime& runtime); -}; - -using HostFunctionType = std::function; - -namespace quickjsdirect { - -template -const void* hostObjectTypeToken() { - static int token = 0; - return &token; -} - -struct RuntimeState { - explicit RuntimeState(JSContext* context) : context(context) {} - JSContext* context = nullptr; - bool hostClassRegistered = false; - bool functionClassRegistered = false; -}; - -extern JSClassID gHostClassId; -extern JSClassID gFunctionClassId; - -std::shared_ptr stateForContext(JSContext* context); - -struct ValueStorage { - enum class Kind { - Undefined, - Null, - Bool, - Number, - QuickJS, - }; - - explicit ValueStorage(Kind kind) : kind(kind) {} - ~ValueStorage() { - if (context != nullptr && !JS_IsUninitialized(value)) { - JS_FreeValue(context, value); - } - } - - Kind kind = Kind::Undefined; - bool boolValue = false; - double numberValue = 0; - JSContext* context = nullptr; - JSValue value = JS_UNINITIALIZED; -}; - -struct HostObjectHolder { - HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, - const void* typeToken) - : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} - std::shared_ptr state; - std::shared_ptr hostObject; - const void* typeToken = nullptr; -}; - -struct FunctionHolder { - FunctionHolder(std::shared_ptr state, HostFunctionType callback) - : state(std::move(state)), callback(std::move(callback)) {} - std::shared_ptr state; - HostFunctionType callback; -}; - -struct ArrayBufferHolder { - explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} - std::shared_ptr buffer; -}; - -inline std::string valueToUtf8(JSContext* context, JSValueConst value) { - size_t length = 0; - const char* cString = JS_ToCStringLen(context, &length, value); - if (cString == nullptr) { - return {}; - } - std::string result(cString, length); - JS_FreeCString(context, cString); - return result; -} - -inline std::string atomToUtf8(JSContext* context, JSAtom atom) { - const char* cString = JS_AtomToCString(context, atom); - if (cString == nullptr) { - return {}; - } - std::string result(cString); - JS_FreeCString(context, cString); - return result; -} - -inline JSValue throwError(JSContext* context, const std::exception& error) { - return JS_ThrowTypeError(context, "%s", error.what()); -} - -void ensureClasses(Runtime& runtime); - -} // namespace quickjsdirect - -class Runtime { - public: - explicit Runtime(JSContext* context) : state_(quickjsdirect::stateForContext(context)) {} - explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} - JSContext* context() const { return state_->context; } - std::shared_ptr state() const { return state_; } - Object global(); - Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); - void drainMicrotasks() { - JSContext* ctx = context(); - JSRuntime* rt = JS_GetRuntime(ctx); - JSContext* jobCtx = nullptr; - while (JS_ExecutePendingJob(rt, &jobCtx) > 0) { - } - } - - private: - std::shared_ptr state_; -}; - -class String { - public: - String() = default; - String(Runtime& runtime, JSValue value); - static String createFromUtf8(Runtime& runtime, const char* value) { - return String(runtime, JS_NewString(runtime.context(), value != nullptr ? value : "")); - } - static String createFromUtf8(Runtime& runtime, const std::string& value) { - return String(runtime, JS_NewStringLen(runtime.context(), value.data(), value.size())); - } - static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { - return String(runtime, - JS_NewStringLen(runtime.context(), reinterpret_cast(value), length)); - } - std::string utf8(Runtime& runtime) const; - JSValue local(Runtime& runtime) const; - operator Value() const; - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class Value { - public: - Value() - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::Undefined)) {} - Value(bool value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::Bool)) { - storage_->boolValue = value; - } - Value(double value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::Number)) { - storage_->numberValue = value; - } - Value(int value) : Value(static_cast(value)) {} - Value(uint32_t value) : Value(static_cast(value)) {} - - Value(Runtime& runtime, const Value& value) : storage_(value.storage_) {} - Value(Runtime& runtime, Value&& value) : storage_(std::move(value.storage_)) {} - Value(Runtime& runtime, const String& value) : storage_(value.storage_) {} - Value(Runtime& runtime, const Object& object); - Value(Runtime& runtime, const Function& function); - Value(Runtime& runtime, const Array& array); - Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); - Value(Runtime& runtime, const BigInt& bigint); - Value(Runtime& runtime, JSValue value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_DupValue(runtime.context(), value); - } - - static Value undefined() { return Value(); } - static Value null() { - Value value; - value.storage_ = - std::make_shared(quickjsdirect::ValueStorage::Kind::Null); - return value; - } - bool isUndefined() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::Undefined || - (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsUndefined(storage_->value)); - } - bool isNull() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::Null || - (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsNull(storage_->value)); - } - bool isBool() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::Bool || - (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsBool(storage_->value)); - } - bool getBool() const { - if (storage_->kind == quickjsdirect::ValueStorage::Kind::Bool) { - return storage_->boolValue; - } - if (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS) { - return JS_ToBool(storage_->context, storage_->value) != 0; - } - return false; - } - bool isNumber() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::Number || - (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsNumber(storage_->value)); - } - double getNumber() const { - if (storage_->kind == quickjsdirect::ValueStorage::Kind::Number) { - return storage_->numberValue; - } - if (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS) { - double value = 0; - JS_ToFloat64(storage_->context, &value, storage_->value); - return value; - } - return 0; - } - bool isObject() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsObject(storage_->value); - } - bool isString() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsString(storage_->value); - } - bool isBigInt() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsBigInt(storage_->context, storage_->value); - } - bool isSymbol() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsSymbol(storage_->value); - } - - Object asObject(Runtime& runtime) const; - String asString(Runtime& runtime) const; - BigInt getBigInt(Runtime& runtime) const; - - JSValue local(Runtime& runtime) const { - switch (storage_->kind) { - case quickjsdirect::ValueStorage::Kind::Undefined: - return JS_UNDEFINED; - case quickjsdirect::ValueStorage::Kind::Null: - return JS_NULL; - case quickjsdirect::ValueStorage::Kind::Bool: - return JS_NewBool(runtime.context(), storage_->boolValue); - case quickjsdirect::ValueStorage::Kind::Number: - return JS_NewFloat64(runtime.context(), storage_->numberValue); - case quickjsdirect::ValueStorage::Kind::QuickJS: - return JS_DupValue(runtime.context(), storage_->value); - } - } - - private: - friend class Runtime; - friend class Object; - friend class String; - friend class BigInt; - friend class ArrayBuffer; - friend class Function; - friend class Array; - std::shared_ptr storage_; -}; - -class Object { - public: - Object() = default; - explicit Object(Runtime& runtime) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_NewObject(runtime.context()); - } - static Object fromValueStorage(std::shared_ptr storage) { - Object object; - object.storage_ = std::move(storage); - return object; - } - template - static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - quickjsdirect::hostObjectTypeToken()); - } - - Value getProperty(Runtime& runtime, const char* name) const { - JSValue object = local(runtime); - JSValue result = JS_GetPropertyStr(runtime.context(), object, name != nullptr ? name : ""); - JS_FreeValue(runtime.context(), object); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS property get failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value getProperty(Runtime& runtime, const std::string& name) const { - return getProperty(runtime, name.c_str()); - } - Value getProperty(Runtime& runtime, const Value& key) const { - JSValue object = local(runtime); - JSValue keyValue = key.local(runtime); - JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); - JS_FreeValue(runtime.context(), keyValue); - JSValue result = - atom == JS_ATOM_NULL ? JS_UNDEFINED : JS_GetProperty(runtime.context(), object, atom); - if (atom != JS_ATOM_NULL) { - JS_FreeAtom(runtime.context(), atom); - } - JS_FreeValue(runtime.context(), object); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS property get failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Object getPropertyAsObject(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime); - } - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - void setProperty(Runtime& runtime, const char* name, const Value& value) { - JSValue object = local(runtime); - JSValue localValue = value.local(runtime); - int status = - JS_SetPropertyStr(runtime.context(), object, name != nullptr ? name : "", localValue); - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS property set failed."); - } - } - void setProperty(Runtime& runtime, const char* name, const String& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Object& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Function& value); - void setProperty(Runtime& runtime, const char* name, const Array& value); - void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); - void setProperty(Runtime& runtime, const char* name, bool value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const char* name, double value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const std::string& name, const Value& value) { - setProperty(runtime, name.c_str(), value); - } - void setProperty(Runtime& runtime, const Value& key, const Value& value) { - JSValue object = local(runtime); - JSValue keyValue = key.local(runtime); - JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); - JS_FreeValue(runtime.context(), keyValue); - JSValue localValue = value.local(runtime); - int status = - atom == JS_ATOM_NULL ? -1 : JS_SetProperty(runtime.context(), object, atom, localValue); - if (atom != JS_ATOM_NULL) { - JS_FreeAtom(runtime.context(), atom); - } - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS property set failed."); - } - } - bool hasProperty(Runtime& runtime, const char* name) const { - JSValue object = local(runtime); - JSAtom atom = JS_NewAtom(runtime.context(), name != nullptr ? name : ""); - int result = JS_HasProperty(runtime.context(), object, atom); - JS_FreeAtom(runtime.context(), atom); - JS_FreeValue(runtime.context(), object); - return result > 0; - } - bool isFunction(Runtime& runtime) const { - JSValue object = local(runtime); - bool result = JS_IsFunction(runtime.context(), object); - JS_FreeValue(runtime.context(), object); - return result; - } - bool isArray(Runtime& runtime) const { - JSValue object = local(runtime); - int result = JS_IsArray(runtime.context(), object); - JS_FreeValue(runtime.context(), object); - return result > 0; - } - bool isArrayBuffer(Runtime& runtime) const { - JSValue object = local(runtime); - bool result = JS_IsArrayBuffer(object); - JS_FreeValue(runtime.context(), object); - return result; - } - Function asFunction(Runtime& runtime) const; - Array getArray(Runtime& runtime) const; - ArrayBuffer getArrayBuffer(Runtime& runtime) const; - Array getPropertyNames(Runtime& runtime) const; - - template - bool isHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - return holder != nullptr && holder->typeToken == quickjsdirect::hostObjectTypeToken(); - } - template - std::shared_ptr getHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - if (holder == nullptr || holder->typeToken != quickjsdirect::hostObjectTypeToken()) { - return nullptr; - } - return std::static_pointer_cast(holder->hostObject); - } - JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - protected: - friend class Value; - friend class Runtime; - friend class Function; - friend class Array; - friend class ArrayBuffer; - explicit Object(std::shared_ptr storage) - : storage_(std::move(storage)) {} - static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken); - quickjsdirect::HostObjectHolder* hostObjectHolder(Runtime& runtime) const; - std::shared_ptr storage_; -}; - -class Function : public Object { - public: - Function() = default; - explicit Function(Object object) : Object(std::move(object.storage_)) {} - static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback); - Value call(Runtime& runtime, const Value* args, size_t count) const { - JSValue function = local(runtime); - JSValue global = JS_GetGlobalObject(runtime.context()); - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValue result = JS_Call(runtime.context(), function, global, static_cast(argv.size()), - argv.empty() ? nullptr : argv.data()); - for (auto& arg : argv) { - JS_FreeValue(runtime.context(), arg); - } - JS_FreeValue(runtime.context(), global); - JS_FreeValue(runtime.context(), function); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS function call failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value call(Runtime& runtime) const { - return call(runtime, static_cast(nullptr), 0); - } - Value call(Runtime& runtime, std::nullptr_t, size_t) const { - return call(runtime, static_cast(nullptr), 0); - } - template - Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { - return call(runtime, static_cast(args), count); - } - template - Value call(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return call(runtime, static_cast(argv), sizeof...(Args)); - } - Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, - size_t count = 0) const { - JSValue function = local(runtime); - JSValue thisValue = thisObject.local(runtime); - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValue result = JS_Call(runtime.context(), function, thisValue, static_cast(argv.size()), - argv.empty() ? nullptr : argv.data()); - for (auto& arg : argv) { - JS_FreeValue(runtime.context(), arg); - } - JS_FreeValue(runtime.context(), thisValue); - JS_FreeValue(runtime.context(), function); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS function call failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { - JSValue function = local(runtime); - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValue result = JS_CallConstructor(runtime.context(), function, static_cast(argv.size()), - argv.empty() ? nullptr : argv.data()); - for (auto& arg : argv) { - JS_FreeValue(runtime.context(), arg); - } - JS_FreeValue(runtime.context(), function); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS constructor call failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { - return callAsConstructor(runtime, static_cast(nullptr), 0); - } - template - Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { - return callAsConstructor(runtime, static_cast(args), count); - } - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); - } -}; - -class Array : public Object { - public: - explicit Array(Runtime& runtime, size_t size) - : Object(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_NewArray(runtime.context()); - JS_SetPropertyStr(runtime.context(), storage_->value, "length", - JS_NewUint32(runtime.context(), static_cast(size))); - } - explicit Array(Object object) : Object(std::move(object.storage_)) {} - size_t size(Runtime& runtime) const { - Value length = getProperty(runtime, "length"); - return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; - } - Value getValueAtIndex(Runtime& runtime, size_t index) const { - JSValue object = local(runtime); - JSValue result = JS_GetPropertyUint32(runtime.context(), object, static_cast(index)); - JS_FreeValue(runtime.context(), object); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS array get failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { - JSValue object = local(runtime); - JSValue localValue = value.local(runtime); - int status = - JS_SetPropertyUint32(runtime.context(), object, static_cast(index), localValue); - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS array set failed."); - } - } - void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { - setValueAtIndex(runtime, index, Value(runtime, value)); - } -}; - -class BigInt { - public: - BigInt() = default; - BigInt(Runtime& runtime, JSValue value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_DupValue(runtime.context(), value); - } - static BigInt fromInt64(Runtime& runtime, int64_t value) { - JSValue result = JS_NewBigInt64(runtime.context(), value); - BigInt bigint(runtime, result); - JS_FreeValue(runtime.context(), result); - return bigint; - } - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - JSValue result = JS_NewBigUint64(runtime.context(), value); - BigInt bigint(runtime, result); - JS_FreeValue(runtime.context(), result); - return bigint; - } - String toString(Runtime& runtime, int) const; - JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class ArrayBuffer : public Object { - public: - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : Object(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - auto* holder = new quickjsdirect::ArrayBufferHolder(std::move(buffer)); - storage_->context = runtime.context(); - storage_->value = JS_NewArrayBuffer( - runtime.context(), holder->buffer->data(), holder->buffer->size(), - [](JSRuntime*, void* opaque, void*) { - delete static_cast(opaque); - }, - holder, false); - } - explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} - size_t size(Runtime& runtime) const { - JSValue object = local(runtime); - size_t size = 0; - JS_GetArrayBuffer(runtime.context(), &size, object); - JS_FreeValue(runtime.context(), object); - return size; - } - uint8_t* data(Runtime& runtime) const { - JSValue object = local(runtime); - size_t size = 0; - uint8_t* data = JS_GetArrayBuffer(runtime.context(), &size, object); - JS_FreeValue(runtime.context(), object); - return data; - } -}; -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_QUICKJS - -#endif // NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJSValue.mm b/NativeScript/ffi/quickjs/NativeApiQuickJSValue.mm deleted file mode 100644 index 94ae05e0e..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJSValue.mm +++ /dev/null @@ -1,88 +0,0 @@ -#include "NativeApiQuickJSRuntime.h" - -#ifdef TARGET_ENGINE_QUICKJS - -namespace facebook { -namespace jsi { - -Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } -void HostObject::set(Runtime&, const PropNameID&, const Value&) {} -std::vector HostObject::getPropertyNames(Runtime&) { return {}; } -String::String(Runtime& runtime, JSValue value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_DupValue(runtime.context(), value); -} -std::string String::utf8(Runtime& runtime) const { - JSValue value = local(runtime); - std::string result = quickjsdirect::valueToUtf8(runtime.context(), value); - JS_FreeValue(runtime.context(), value); - return result; -} -JSValue String::local(Runtime& runtime) const { - return JS_DupValue(runtime.context(), storage_->value); -} -String::operator Value() const { - Value value; - value.storage_ = storage_; - return value; -} -Value::Value(Runtime&, const Object& object) : storage_(object.storage_) {} -Value::Value(Runtime&, const Function& function) : storage_(function.storage_) {} -Value::Value(Runtime&, const Array& array) : storage_(array.storage_) {} -Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) : storage_(arrayBuffer.storage_) {} -Value::Value(Runtime&, const BigInt& bigint) : storage_(bigint.storage_) {} -Object Value::asObject(Runtime&) const { return Object::fromValueStorage(storage_); } -String Value::asString(Runtime& runtime) const { - JSValue value = local(runtime); - String result(runtime, value); - JS_FreeValue(runtime.context(), value); - return result; -} -BigInt Value::getBigInt(Runtime& runtime) const { - JSValue value = local(runtime); - BigInt result(runtime, value); - JS_FreeValue(runtime.context(), value); - return result; -} -Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime).asFunction(runtime); -} -Function Object::asFunction(Runtime&) const { return Function(*this); } -Array Object::getArray(Runtime&) const { return Array(*this); } -ArrayBuffer Object::getArrayBuffer(Runtime&) const { return ArrayBuffer(*this); } -Array Object::getPropertyNames(Runtime& runtime) const { - JSValue object = local(runtime); - JSPropertyEnum* properties = nullptr; - uint32_t count = 0; - int status = JS_GetOwnPropertyNames(runtime.context(), &properties, &count, object, - JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY); - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS property names failed."); - } - Array result(runtime, count); - for (uint32_t i = 0; i < count; i++) { - JSValue nameValue = JS_AtomToValue(runtime.context(), properties[i].atom); - result.setValueAtIndex(runtime, i, Value(runtime, nameValue)); - JS_FreeValue(runtime.context(), nameValue); - JS_FreeAtom(runtime.context(), properties[i].atom); - } - js_free(runtime.context(), properties); - return result; -} -void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { - setProperty(runtime, name, Value(runtime, value)); -} -void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { - setProperty(runtime, name, Value(runtime, value)); -} -void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/shared/direct/NativeApiDirect.h b/NativeScript/ffi/shared/direct/NativeApiDirect.h deleted file mode 100644 index ef55cda4f..000000000 --- a/NativeScript/ffi/shared/direct/NativeApiDirect.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_SHARED_DIRECT_NATIVE_API_DIRECT_H -#define NATIVESCRIPT_FFI_SHARED_DIRECT_NATIVE_API_DIRECT_H - -#include -#include - -namespace nativescript { - -class NativeApiDirectScheduler { - public: - virtual ~NativeApiDirectScheduler() = default; - virtual void invokeOnJS(std::function task) = 0; - virtual void invokeOnUI(std::function task) = 0; -}; - -struct NativeApiDirectConfig { - const char* metadataPath = nullptr; - const void* metadataPtr = nullptr; - const char* globalName = "__nativeScriptNativeApi"; - std::shared_ptr scheduler = nullptr; - std::function)> nativeInvocationInvoker = nullptr; - std::function)> nativeCallbackInvoker = nullptr; - std::function)> jsThreadCallbackInvoker = nullptr; - bool invokeCallbacksOnNativeCallerThread = false; - bool installGlobalSymbols = false; -}; - -} // namespace nativescript - -#endif // NATIVESCRIPT_FFI_SHARED_DIRECT_NATIVE_API_DIRECT_H diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiBridge.h b/NativeScript/ffi/shared/jsi/NativeApiJsiBridge.h deleted file mode 100644 index d543b1ec3..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiBridge.h +++ /dev/null @@ -1,1705 +0,0 @@ -thread_local bool gDispatchNativeCallsToUI = false; -thread_local bool gExecutingDispatchedUINativeCall = false; -thread_local int gSynchronousNativeInvocationDepth = 0; -thread_local int gNativeCallerThreadJsiCallbackDepth = 0; -thread_local std::vector gNativeCallbackExceptionCaptureStack; -std::atomic gActiveSynchronousNativeInvocationDepth{0}; -static char gNativeApiJsiExtendedClassKey; - -void markNativeApiJsiExtendedClass(Class cls) { - if (cls == Nil) { - return; - } - objc_setAssociatedObject(cls, &gNativeApiJsiExtendedClassKey, @YES, - OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -bool isNativeApiJsiExtendedClass(Class cls) { - Class current = cls; - while (current != Nil) { - if (objc_getAssociatedObject(current, &gNativeApiJsiExtendedClassKey) != nil) { - return true; - } - current = class_getSuperclass(current); - } - return false; -} - -class ScopedNativeApiUINativeCallDispatch final { - public: - ScopedNativeApiUINativeCallDispatch() - : previous_(gDispatchNativeCallsToUI) { - gDispatchNativeCallsToUI = true; - } - - ~ScopedNativeApiUINativeCallDispatch() { - gDispatchNativeCallsToUI = previous_; - } - - private: - bool previous_ = false; -}; - -bool shouldDispatchNativeCallToUI() { - return gDispatchNativeCallsToUI && ![NSThread isMainThread]; -} - -class ScopedNativeApiSynchronousInvocation final { - public: - ScopedNativeApiSynchronousInvocation() { - gSynchronousNativeInvocationDepth += 1; - gActiveSynchronousNativeInvocationDepth.fetch_add(1, - std::memory_order_acq_rel); - } - - ~ScopedNativeApiSynchronousInvocation() { - gSynchronousNativeInvocationDepth -= 1; - gActiveSynchronousNativeInvocationDepth.fetch_sub(1, - std::memory_order_acq_rel); - } -}; - -class ScopedNativeCallerThreadJsiCallback final { - public: - ScopedNativeCallerThreadJsiCallback() { - gNativeCallerThreadJsiCallbackDepth += 1; - } - - ~ScopedNativeCallerThreadJsiCallback() { - gNativeCallerThreadJsiCallbackDepth -= 1; - } - - ScopedNativeCallerThreadJsiCallback( - const ScopedNativeCallerThreadJsiCallback&) = delete; - ScopedNativeCallerThreadJsiCallback& operator=( - const ScopedNativeCallerThreadJsiCallback&) = delete; -}; - -class ScopedNativeCallbackExceptionCapture final { - public: - explicit ScopedNativeCallbackExceptionCapture(std::string* message) - : message_(message) { - gNativeCallbackExceptionCaptureStack.push_back(message_); - } - - ~ScopedNativeCallbackExceptionCapture() { - if (!gNativeCallbackExceptionCaptureStack.empty() && - gNativeCallbackExceptionCaptureStack.back() == message_) { - gNativeCallbackExceptionCaptureStack.pop_back(); - } - } - - ScopedNativeCallbackExceptionCapture( - const ScopedNativeCallbackExceptionCapture&) = delete; - ScopedNativeCallbackExceptionCapture& operator=( - const ScopedNativeCallbackExceptionCapture&) = delete; - - private: - std::string* message_ = nullptr; -}; - -bool recordNativeCallbackException(const std::string& message) { - if (gNativeCallbackExceptionCaptureStack.empty()) { - return false; - } - - std::string* captured = gNativeCallbackExceptionCaptureStack.back(); - if (captured == nullptr) { - return false; - } - - if (captured->empty()) { - *captured = message; - } - return true; -} - -template -void performNativeInvocation(Runtime& runtime, - const std::function)>& - invoker, - Invocation&& invocation) { - NSString* exceptionDescription = nil; - std::string callbackException; - auto run = [&]() { - ScopedNativeApiSynchronousInvocation synchronousInvocation; - ScopedNativeCallbackExceptionCapture callbackExceptionCapture( - &callbackException); - @try { - invocation(); - } @catch (NSException* exception) { - exceptionDescription = [exception.description copy]; - } - }; - - bool skipInvoker = gNativeCallerThreadJsiCallbackDepth > 0; - if (shouldDispatchNativeCallToUI()) { - dispatch_sync(dispatch_get_main_queue(), ^{ - bool previous = gExecutingDispatchedUINativeCall; - gExecutingDispatchedUINativeCall = true; - if (invoker && !skipInvoker) { - invoker(run); - } else { - run(); - } - gExecutingDispatchedUINativeCall = previous; - }); - } else if (invoker && !skipInvoker) { - invoker(run); - } else { - run(); - } - - if (exceptionDescription != nil) { - std::string message = exceptionDescription.UTF8String ?: ""; - [exceptionDescription release]; - throw facebook::jsi::JSError(runtime, message); - } - if (!callbackException.empty()) { - throw facebook::jsi::JSError(runtime, callbackException); - } -} - -enum class NativeApiSymbolKind { - Class, - Function, - Constant, - Protocol, - Enum, - Struct, - Union, -}; - -struct NativeApiSymbol { - NativeApiSymbolKind kind; - MDSectionOffset offset = 0; - MDSectionOffset superclassOffset = MD_SECTION_OFFSET_NULL; - std::string name; - std::string runtimeName; -}; - -struct NativeApiMember { - std::string name; - std::string selectorName; - std::string setterSelectorName; - MDSectionOffset signatureOffset = MD_SECTION_OFFSET_NULL; - MDSectionOffset setterSignatureOffset = MD_SECTION_OFFSET_NULL; - MDMemberFlag flags = metagen::mdMemberFlagNull; - bool property = false; - bool readonly = false; -}; - -struct NativeApiJsiAggregateInfo; - -struct NativeApiJsiFfiType { - ffi_type type = {}; - std::vector elements; - - NativeApiJsiFfiType() { - type.type = FFI_TYPE_STRUCT; - type.size = 0; - type.alignment = 0; - type.elements = nullptr; - } - - void finalize() { - elements.push_back(nullptr); - type.elements = elements.data(); - } -}; - -struct NativeApiJsiType { - MDTypeKind kind = metagen::mdTypeVoid; - ffi_type* ffiType = &ffi_type_void; - bool supported = true; - bool returnOwned = false; - MDSectionOffset signatureOffset = MD_SECTION_OFFSET_NULL; - MDSectionOffset aggregateOffset = MD_SECTION_OFFSET_NULL; - bool aggregateIsUnion = false; - uint16_t arraySize = 0; - std::shared_ptr elementType; - std::shared_ptr aggregateInfo; - std::shared_ptr ownedFfiType; -}; - -struct NativeApiJsiAggregateField { - std::string name; - uint16_t offset = 0; - NativeApiJsiType type; -}; - -struct NativeApiJsiAggregateInfo { - std::string name; - uint16_t size = 0; - bool isUnion = false; - MDSectionOffset offset = MD_SECTION_OFFSET_NULL; - std::vector fields; - std::shared_ptr ffi; -}; - -std::string jsifySelector(const char* selector) { - std::string jsifiedSelector; - bool nextUpper = false; - for (const char* c = selector; c != nullptr && *c != '\0'; c++) { - if (*c == ':') { - nextUpper = true; - } else if (nextUpper) { - jsifiedSelector += static_cast(toupper(*c)); - nextUpper = false; - } else { - jsifiedSelector += *c; - } - } - return jsifiedSelector; -} - -std::string booleanGetterSelectorForProperty(const std::string& property) { - if (property.empty()) { - return property; - } - - std::string selector = "is"; - selector += static_cast(toupper(property[0])); - selector += property.substr(1); - return selector; -} - -std::optional runtimeBooleanGetterSelectorForProperty( - Class cls, bool staticMethod, const std::string& property) { - if (cls == nil || property.empty()) { - return std::nullopt; - } - - std::string selectorName = booleanGetterSelectorForProperty(property); - SEL selector = sel_getUid(selectorName.c_str()); - if ((!staticMethod && class_getInstanceMethod(cls, selector) != nullptr) || - (staticMethod && class_getClassMethod(cls, selector) != nullptr)) { - return selectorName; - } - return std::nullopt; -} - -std::optional runtimeSelectorNameForProperty( - Class cls, bool staticMethod, const std::string& property) { - if (cls == nil || property.empty()) { - return std::nullopt; - } - -#if TARGET_OS_OSX - if (property == "initWithRedGreenBlueAlpha") { - const char* candidates[] = { - "initWithSRGBRed:green:blue:alpha:", - "initWithCalibratedRed:green:blue:alpha:", - }; - for (const char* candidate : candidates) { - SEL selector = sel_getUid(candidate); - if ((!staticMethod && class_getInstanceMethod(cls, selector) != nullptr) || - (staticMethod && class_getClassMethod(cls, selector) != nullptr)) { - return std::string(candidate); - } - } - } else if (property == "colorWithRedGreenBlueAlpha") { - const char* candidates[] = { - "colorWithSRGBRed:green:blue:alpha:", - "colorWithCalibratedRed:green:blue:alpha:", - }; - for (const char* candidate : candidates) { - SEL selector = sel_getUid(candidate); - if ((!staticMethod && class_getInstanceMethod(cls, selector) != nullptr) || - (staticMethod && class_getClassMethod(cls, selector) != nullptr)) { - return std::string(candidate); - } - } - } -#endif - - if (auto selectorName = - runtimeBooleanGetterSelectorForProperty(cls, staticMethod, property)) { - return selectorName; - } - - Class scan = staticMethod ? object_getClass(cls) : cls; - while (scan != Nil) { - unsigned int methodCount = 0; - Method* methods = class_copyMethodList(scan, &methodCount); - for (unsigned int i = 0; i < methodCount; i++) { - SEL selector = method_getName(methods[i]); - const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; - if (selectorName != nullptr && - (property == selectorName || jsifySelector(selectorName) == property)) { - std::string result(selectorName); - free(methods); - return result; - } - } - free(methods); - scan = class_getSuperclass(scan); - } - - return std::nullopt; -} - -std::string setterSelectorForProperty(const std::string& property) { - if (property.empty()) { - return property; - } - - std::string selector = "set"; - selector += static_cast(toupper(property[0])); - selector += property.substr(1); - selector += ":"; - return selector; -} - -bool hasRuntimeSetterForProperty(Class cls, bool staticMethod, - const std::string& property) { - if (cls == nil || property.empty()) { - return false; - } - - std::string setterSelectorName = setterSelectorForProperty(property); - SEL selector = sel_getUid(setterSelectorName.c_str()); - return staticMethod ? class_getClassMethod(cls, selector) != nullptr - : class_getInstanceMethod(cls, selector) != nullptr; -} - -size_t selectorArgumentCount(const std::string& selector) { - return static_cast( - std::count(selector.begin(), selector.end(), ':')); -} - -const NativeApiMember* selectMethodMember( - const std::vector& members, const std::string& property, - bool staticMethod, size_t argumentCount) { - const NativeApiMember* fallback = nullptr; - for (const auto& member : members) { - if (member.property || member.name != property) { - continue; - } - - bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; - if (memberIsStatic != staticMethod) { - continue; - } - - if (fallback == nullptr) { - fallback = &member; - } - if (selectorArgumentCount(member.selectorName) == argumentCount) { - return &member; - } - } - return fallback; -} - -const NativeApiMember* selectPropertyMember( - const std::vector& members, const std::string& property, - bool staticMethod) { - for (const auto& member : members) { - if (!member.property || member.name != property) { - continue; - } - - bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; - if (memberIsStatic == staticMethod) { - return &member; - } - } - return nullptr; -} - -const NativeApiMember* selectWritablePropertyMember( - const std::vector& members, const std::string& property, - bool staticMethod) { - const NativeApiMember* fallback = nullptr; - for (const auto& member : members) { - if (!member.property || member.name != property) { - continue; - } - - bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; - if (memberIsStatic != staticMethod) { - continue; - } - - if (fallback == nullptr) { - fallback = &member; - } - if (!member.readonly && !member.setterSelectorName.empty()) { - return &member; - } - } - return fallback; -} - -void skipMetadataJsiType(MDMetadataReader* metadata, MDSectionOffset* offset); -Protocol* lookupProtocolByNativeName(const std::string& name); - -inline uintptr_t normalizeRuntimePointer(uintptr_t pointer) { -#if INTPTR_MAX == INT64_MAX - return pointer & 0x0000FFFFFFFFFFFFULL; -#else - return pointer; -#endif -} - -class NativeApiJsiBridge { - public: - explicit NativeApiJsiBridge(const NativeApiJsiConfig& config) - : metadata_(loadMetadata(config)), - scheduler_(config.scheduler), - nativeInvocationInvoker_(config.nativeInvocationInvoker), - nativeCallbackInvoker_(config.nativeCallbackInvoker), - jsThreadCallbackInvoker_(config.jsThreadCallbackInvoker), - invokeCallbacksOnNativeCallerThread_( - config.invokeCallbacksOnNativeCallerThread) { - selfDl_ = dlopen(nullptr, RTLD_NOW); - buildSymbolIndexes(); - } - - ~NativeApiJsiBridge() { - if (selfDl_ != nullptr) { - dlclose(selfDl_); - } - } - - MDMetadataReader* metadata() const { return metadata_.get(); } - - void* selfDl() const { return selfDl_; } - - const NativeApiSymbol* find(const std::string& name) const { - auto it = symbolsByName_.find(name); - return it != symbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findClass(const std::string& name) const { - const NativeApiSymbol* symbol = find(name); - if (symbol != nullptr && symbol->kind == NativeApiSymbolKind::Class) { - return symbol; - } - auto it = classSymbolsByRuntimeName_.find(name); - return it != classSymbolsByRuntimeName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findClassByOffset(MDSectionOffset offset) const { - auto it = classSymbolsByOffset_.find(offset); - return it != classSymbolsByOffset_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findClassForRuntimeClass(Class cls) const { - Class current = cls; - while (current != Nil) { - const char* name = class_getName(current); - if (name != nullptr) { - if (const NativeApiSymbol* symbol = findClass(name)) { - return symbol; - } - } - current = class_getSuperclass(current); - } - return nullptr; - } - - const NativeApiSymbol* findClassForRuntimePointer(void* pointer) const { - if (pointer == nullptr) { - return nullptr; - } - - auto it = classSymbolsByRuntimePointer_.find( - normalizeRuntimePointer(reinterpret_cast(pointer))); - return it != classSymbolsByRuntimePointer_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findProtocolForRuntimePointer(void* pointer) const { - if (pointer == nullptr) { - return nullptr; - } - - auto it = protocolSymbolsByRuntimePointer_.find( - normalizeRuntimePointer(reinterpret_cast(pointer))); - return it != protocolSymbolsByRuntimePointer_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findFunction(const std::string& name) const { - auto it = functionSymbolsByName_.find(name); - return it != functionSymbolsByName_.end() ? &it->second : nullptr; - } - - void rememberRoundTripValue(Runtime& runtime, const void* native, - const Value& value) { - if (native == nullptr) { - return; - } - std::lock_guard lock(roundTripValuesMutex_); - roundTripValues_[normalizeRuntimePointer( - reinterpret_cast(native))] = - std::make_shared(runtime, value); - } - - Value findRoundTripValue(Runtime& runtime, const void* native) const { - if (native == nullptr) { - return Value::undefined(); - } - std::lock_guard lock(roundTripValuesMutex_); - auto it = roundTripValues_.find( - normalizeRuntimePointer(reinterpret_cast(native))); - if (it == roundTripValues_.end() || it->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *it->second); - } - - void forgetRoundTripValue(const void* native) { - if (native == nullptr) { - return; - } - std::lock_guard lock(roundTripValuesMutex_); - roundTripValues_.erase( - normalizeRuntimePointer(reinterpret_cast(native))); - } - - void rememberClassValue(Runtime& runtime, Class cls, const Value& value) { - if (cls == Nil) { - return; - } - classValues_[normalizeRuntimePointer(reinterpret_cast(cls))] = - std::make_shared(runtime, value); - } - - Value findClassValue(Runtime& runtime, Class cls) const { - if (cls == Nil) { - return Value::undefined(); - } - auto it = classValues_.find( - normalizeRuntimePointer(reinterpret_cast(cls))); - if (it == classValues_.end() || it->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *it->second); - } - - void rememberClassPrototype(Runtime& runtime, Class cls, const Value& value) { - if (cls == Nil) { - return; - } - classPrototypes_[normalizeRuntimePointer(reinterpret_cast(cls))] = - std::make_shared(runtime, value); - } - - Value findClassPrototype(Runtime& runtime, Class cls) const { - if (cls == Nil) { - return Value::undefined(); - } - auto it = classPrototypes_.find( - normalizeRuntimePointer(reinterpret_cast(cls))); - if (it == classPrototypes_.end() || it->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *it->second); - } - - void setObjectExpando(Runtime& runtime, const void* native, - const std::string& property, const Value& value) { - if (native == nullptr || property.empty()) { - return; - } - objectExpandos_[normalizeRuntimePointer(reinterpret_cast(native))] - [property] = std::make_shared(runtime, value); - } - - Value findObjectExpando(Runtime& runtime, const void* native, - const std::string& property) const { - if (native == nullptr || property.empty()) { - return Value::undefined(); - } - auto objectIt = objectExpandos_.find( - normalizeRuntimePointer(reinterpret_cast(native))); - if (objectIt == objectExpandos_.end()) { - return Value::undefined(); - } - auto propertyIt = objectIt->second.find(property); - if (propertyIt == objectIt->second.end() || propertyIt->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *propertyIt->second); - } - - void forgetObjectExpandos(const void* native) { - if (native == nullptr) { - return; - } - objectExpandos_.erase( - normalizeRuntimePointer(reinterpret_cast(native))); - } - - void rememberPointerValue(Runtime& runtime, const void* native, - const Value& value) { - pointerValues_[reinterpret_cast(native)] = - std::make_shared(runtime, value); - } - - Value findPointerValue(Runtime& runtime, const void* native) const { - auto it = pointerValues_.find(reinterpret_cast(native)); - if (it == pointerValues_.end() || it->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *it->second); - } - - void forgetPointerValue(const void* native) { - if (native == nullptr) { - return; - } - pointerValues_.erase(reinterpret_cast(native)); - } - - const NativeApiSymbol* findConstant(const std::string& name) const { - auto it = constantSymbolsByName_.find(name); - return it != constantSymbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findProtocol(const std::string& name) const { - const NativeApiSymbol* symbol = find(name); - if (symbol != nullptr && symbol->kind == NativeApiSymbolKind::Protocol) { - return symbol; - } - auto it = protocolSymbolsByRuntimeName_.find(name); - return it != protocolSymbolsByRuntimeName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findEnum(const std::string& name) const { - auto it = enumSymbolsByName_.find(name); - return it != enumSymbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findStruct(const std::string& name) const { - auto it = structSymbolsByName_.find(name); - return it != structSymbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findUnion(const std::string& name) const { - auto it = unionSymbolsByName_.find(name); - return it != unionSymbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findAggregate(const std::string& name) const { - const NativeApiSymbol* symbol = findStruct(name); - if (symbol != nullptr) { - return symbol; - } - return findUnion(name); - } - - size_t classCount() const { return classNames_.size(); } - size_t functionCount() const { return functionNames_.size(); } - size_t constantCount() const { return constantNames_.size(); } - size_t protocolCount() const { return protocolNames_.size(); } - size_t enumCount() const { return enumNames_.size(); } - size_t structCount() const { return structNames_.size(); } - size_t unionCount() const { return unionNames_.size(); } - - const std::vector& classNames() const { return classNames_; } - const std::vector& functionNames() const { return functionNames_; } - const std::vector& constantNames() const { return constantNames_; } - const std::vector& protocolNames() const { return protocolNames_; } - const std::vector& enumNames() const { return enumNames_; } - const std::vector& structNames() const { return structNames_; } - const std::vector& unionNames() const { return unionNames_; } - std::shared_ptr scheduler() const { return scheduler_; } - const std::function)>& nativeInvocationInvoker() - const { - return nativeInvocationInvoker_; - } - const std::function)>& nativeCallbackInvoker() - const { - return nativeCallbackInvoker_; - } - const std::function)>& jsThreadCallbackInvoker() - const { - return jsThreadCallbackInvoker_; - } - bool invokeCallbacksOnNativeCallerThread() const { - return invokeCallbacksOnNativeCallerThread_; - } - std::thread::id jsThreadId() const { return jsThreadId_; } - - void retainJsiLifetime(std::shared_ptr lifetime) { - if (lifetime == nullptr) { - return; - } - std::lock_guard lock(retainedLifetimesMutex_); - retainedLifetimes_.push_back(std::move(lifetime)); - } - - const std::vector& membersForClass( - const NativeApiSymbol& symbol) const { - auto cached = membersByClassOffset_.find(symbol.offset); - if (cached != membersByClassOffset_.end()) { - return cached->second; - } - - auto inserted = membersByClassOffset_.emplace( - symbol.offset, readMembersForClassHierarchy(symbol)); - return inserted.first->second; - } - - const std::vector& surfaceMembersForClass( - const NativeApiSymbol& symbol) const { - auto cached = surfaceMembersByClassOffset_.find(symbol.offset); - if (cached != surfaceMembersByClassOffset_.end()) { - return cached->second; - } - - auto inserted = surfaceMembersByClassOffset_.emplace( - symbol.offset, readSurfaceMembersForClass(symbol)); - return inserted.first->second; - } - - const std::vector& membersForProtocol( - const NativeApiSymbol& symbol) const { - auto cached = membersByProtocolOffset_.find(symbol.offset); - if (cached != membersByProtocolOffset_.end()) { - return cached->second; - } - - auto inserted = membersByProtocolOffset_.emplace( - symbol.offset, readMembersForProtocolHierarchy(symbol.offset)); - return inserted.first->second; - } - - std::shared_ptr aggregateInfoFor( - MDSectionOffset aggregateOffset, bool isUnion); - - std::shared_ptr aggregateInfoFor( - const NativeApiSymbol& symbol) { - return aggregateInfoFor(symbol.offset, - symbol.kind == NativeApiSymbolKind::Union); - } - - private: - static std::unique_ptr loadMetadataFromFile( - const char* metadataPath) { - const char* path = metadataPath != nullptr ? metadataPath : "metadata.nsmd"; - FILE* file = fopen(path, "rb"); - if (file == nullptr) { - throw std::runtime_error(std::string("metadata.nsmd not found: ") + path); - } - - fseek(file, 0, SEEK_END); - long size = ftell(file); - fseek(file, 0, SEEK_SET); - if (size <= 0) { - fclose(file); - throw std::runtime_error(std::string("metadata.nsmd is empty: ") + path); - } - - void* buffer = malloc(static_cast(size)); - if (buffer == nullptr) { - fclose(file); - throw std::bad_alloc(); - } - - size_t read = fread(buffer, 1, static_cast(size), file); - fclose(file); - if (read != static_cast(size)) { - free(buffer); - throw std::runtime_error(std::string("failed to read metadata: ") + path); - } - - return std::make_unique(buffer, true); - } - - static std::unique_ptr loadMetadata( - const NativeApiJsiConfig& config) { - if (config.metadataPtr != nullptr && - *static_cast(config.metadataPtr) != '\0') { -#ifdef EMBED_METADATA_SIZE - return std::make_unique((void*)embedded_metadata); -#else - return std::make_unique( - const_cast(config.metadataPtr)); -#endif - } - -#ifdef EMBED_METADATA_SIZE - if (config.metadataPath == nullptr) { - return std::make_unique((void*)embedded_metadata); - } -#endif - - unsigned long segmentSize = 0; - auto segmentData = getsegmentdata( - reinterpret_cast(_dyld_get_image_header(0)), - "__objc_metadata", &segmentSize); - if (segmentData != nullptr && segmentSize > 0) { - return std::make_unique(segmentData); - } - - return loadMetadataFromFile(config.metadataPath); - } - - void addSymbol(NativeApiSymbolKind kind, MDSectionOffset offset, - const char* name, const char* runtimeName = nullptr, - MDSectionOffset superclassOffset = MD_SECTION_OFFSET_NULL) { - if (name == nullptr || name[0] == '\0') { - return; - } - - NativeApiSymbol symbol{ - .kind = kind, - .offset = offset, - .superclassOffset = superclassOffset, - .name = name, - .runtimeName = runtimeName != nullptr ? runtimeName : name, - }; - - switch (kind) { - case NativeApiSymbolKind::Class: - classNames_.push_back(symbol.name); - break; - case NativeApiSymbolKind::Function: - functionNames_.push_back(symbol.name); - functionSymbolsByName_[symbol.name] = symbol; - break; - case NativeApiSymbolKind::Constant: - constantNames_.push_back(symbol.name); - constantSymbolsByName_[symbol.name] = symbol; - break; - case NativeApiSymbolKind::Protocol: - protocolNames_.push_back(symbol.name); - break; - case NativeApiSymbolKind::Enum: - enumNames_.push_back(symbol.name); - enumSymbolsByName_[symbol.name] = symbol; - break; - case NativeApiSymbolKind::Struct: - structNames_.push_back(symbol.name); - structSymbolsByName_[symbol.name] = symbol; - break; - case NativeApiSymbolKind::Union: - unionNames_.push_back(symbol.name); - unionSymbolsByName_[symbol.name] = symbol; - break; - } - - symbolsByName_[symbol.name] = symbol; - if (kind == NativeApiSymbolKind::Class) { - classSymbolsByOffset_[symbol.offset] = symbol; - classSymbolsByRuntimeName_[symbol.runtimeName] = symbol; - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls != Nil) { - classSymbolsByRuntimePointer_[normalizeRuntimePointer( - reinterpret_cast(cls))] = symbol; - } - } else if (kind == NativeApiSymbolKind::Protocol) { - protocolSymbolsByOffset_[symbol.offset] = symbol; - protocolSymbolsByRuntimeName_[symbol.runtimeName] = symbol; - auto rememberProtocolRuntimeName = [&](const std::string& runtimeName) { - if (runtimeName.empty()) { - return; - } - protocolSymbolsByRuntimeName_[runtimeName] = symbol; - Protocol* runtimeProtocol = lookupProtocolByNativeName(runtimeName); - if (runtimeProtocol != nullptr) { - protocolSymbolsByRuntimePointer_[normalizeRuntimePointer( - reinterpret_cast(runtimeProtocol))] = symbol; - } - }; - if (symbol.name.size() > 9 && - std::isdigit(static_cast(symbol.name.back()))) { - size_t digitsStart = symbol.name.size(); - while (digitsStart > 0 && - std::isdigit(static_cast(symbol.name[digitsStart - 1]))) { - digitsStart--; - } - constexpr const char* protocolSuffix = "Protocol"; - size_t protocolSuffixLength = std::strlen(protocolSuffix); - if (digitsStart > protocolSuffixLength && - symbol.name.compare(digitsStart - protocolSuffixLength, - protocolSuffixLength, protocolSuffix) == 0) { - rememberProtocolRuntimeName( - symbol.name.substr(0, digitsStart - protocolSuffixLength)); - } - } - Protocol* protocol = lookupProtocolByNativeName(symbol.runtimeName); - if (protocol == nullptr && symbol.runtimeName != symbol.name) { - protocol = lookupProtocolByNativeName(symbol.name); - } - if (protocol != nullptr) { - protocolSymbolsByRuntimePointer_[normalizeRuntimePointer( - reinterpret_cast(protocol))] = symbol; - } - } else if (kind == NativeApiSymbolKind::Struct) { - structSymbolsByOffset_[symbol.offset] = symbol; - } else if (kind == NativeApiSymbolKind::Union) { - unionSymbolsByOffset_[symbol.offset] = symbol; - } - } - - void addAggregateAliases(NativeApiSymbolKind kind, MDSectionOffset offset, - const std::string& name) { - if (name.empty()) { - return; - } - - if (!name.empty() && name[0] == '_') { - std::string alias = name.substr(1); - if (!alias.empty() && symbolsByName_.find(alias) == symbolsByName_.end()) { - addSymbol(kind, offset, alias.c_str(), name.c_str()); - } - } - - constexpr const char* suffix = "Struct"; - if (name.size() < std::strlen(suffix) || - name.compare(name.size() - std::strlen(suffix), std::strlen(suffix), - suffix) != 0) { - std::string alias = name + suffix; - if (symbolsByName_.find(alias) == symbolsByName_.end()) { - addSymbol(kind, offset, alias.c_str(), name.c_str()); - } - } - } - - void buildSymbolIndexes() { - if (metadata_ == nullptr) { - return; - } - - indexConstants(); - indexEnums(); - indexFunctions(); - indexProtocols(); - indexClasses(); - indexStructs(); - indexUnions(); - } - - static void skipConstantValue(MDMetadataReader* metadata, - MDSectionOffset& offset, - metagen::MDVariableEvalKind evalKind) { - switch (evalKind) { - case metagen::mdEvalNone: - skipMetadataJsiType(metadata, &offset); - break; - case metagen::mdEvalInt64: - offset += sizeof(int64_t); - break; - case metagen::mdEvalDouble: - offset += sizeof(double); - break; - case metagen::mdEvalString: - offset += sizeof(MDSectionOffset); - break; - } - } - - void indexConstants() { - MDSectionOffset offset = metadata_->constantsOffset; - while (offset < metadata_->enumsOffset) { - MDSectionOffset originalOffset = offset; - addSymbol(NativeApiSymbolKind::Constant, originalOffset, - metadata_->getString(offset)); - offset += sizeof(MDSectionOffset); - auto evalKind = metadata_->getVariableEvalKind(offset); - offset += sizeof(metagen::MDVariableEvalKind); - skipConstantValue(metadata_.get(), offset, evalKind); - } - } - - void indexEnums() { - MDSectionOffset offset = metadata_->enumsOffset; - while (offset < metadata_->signaturesOffset) { - MDSectionOffset originalOffset = offset; - addSymbol(NativeApiSymbolKind::Enum, originalOffset, - metadata_->getString(offset)); - offset += sizeof(MDSectionOffset); - - bool next = true; - while (next) { - auto nameOffset = metadata_->getOffset(offset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - offset += sizeof(MDSectionOffset); - offset += sizeof(int64_t); - } - } - } - - void indexFunctions() { - MDSectionOffset offset = metadata_->functionsOffset; - while (offset < metadata_->protocolsOffset) { - MDSectionOffset originalOffset = offset; - addSymbol(NativeApiSymbolKind::Function, originalOffset, - metadata_->getString(offset)); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - offset += sizeof(metagen::MDFunctionFlag); - } - } - - void indexProtocols() { - MDSectionOffset offset = metadata_->protocolsOffset; - while (offset < metadata_->classesOffset) { - MDSectionOffset originalOffset = offset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - addSymbol(NativeApiSymbolKind::Protocol, originalOffset, - metadata_->resolveString(nameOffset)); - - while (next) { - auto protocolOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - - next = true; - while (next) { - auto flags = metadata_->getMemberFlag(offset); - next = (flags & metagen::mdMemberNext) != 0; - offset += sizeof(flags); - if (flags == metagen::mdMemberFlagNull) { - break; - } - - skipMember(flags, offset); - } - } - } - - void indexClasses() { - MDSectionOffset offset = metadata_->classesOffset; - while (offset < metadata_->structsOffset) { - MDSectionOffset originalOffset = offset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - auto runtimeNameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - - auto name = metadata_->resolveString(nameOffset); - const char* runtimeName = name; - if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { - runtimeName = metadata_->resolveString(runtimeNameOffset); - } - - while (hasProtocols) { - auto protocolOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - - auto superclass = metadata_->getOffset(offset); - offset += sizeof(superclass); - MDSectionOffset superclassOffset = - superclass & ~metagen::mdSectionOffsetNext; - if (superclassOffset != MD_SECTION_OFFSET_NULL) { - superclassOffset += metadata_->classesOffset; - } - - addSymbol(NativeApiSymbolKind::Class, originalOffset, name, runtimeName, - superclassOffset); - - bool next = (superclass & metagen::mdSectionOffsetNext) != 0; - while (next) { - auto flags = metadata_->getMemberFlag(offset); - next = (flags & metagen::mdMemberNext) != 0; - offset += sizeof(flags); - skipMember(flags, offset); - } - } - } - - void skipAggregateFields(MDSectionOffset& offset, bool isUnion) const { - bool next = true; - while (next) { - MDSectionOffset nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - if (nameOffset == MD_SECTION_OFFSET_NULL) { - break; - } - if (!isUnion) { - offset += sizeof(uint16_t); - } - skipMetadataJsiType(metadata_.get(), &offset); - } - } - - void indexStructs() { - MDSectionOffset offset = metadata_->structsOffset; - while (offset < metadata_->unionsOffset) { - if (metadata_->getOffset(offset) == 0) { - break; - } - MDSectionOffset originalOffset = offset; - const char* name = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - offset += sizeof(uint16_t); - addSymbol(NativeApiSymbolKind::Struct, originalOffset, name); - addAggregateAliases(NativeApiSymbolKind::Struct, originalOffset, - name != nullptr ? name : ""); - skipAggregateFields(offset, false); - } - } - - void indexUnions() { - MDSectionOffset offset = metadata_->unionsOffset; - while (metadata_->getOffset(offset) != 0) { - MDSectionOffset originalOffset = offset; - const char* name = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - offset += sizeof(uint16_t); - addSymbol(NativeApiSymbolKind::Union, originalOffset, name); - addAggregateAliases(NativeApiSymbolKind::Union, originalOffset, - name != nullptr ? name : ""); - skipAggregateFields(offset, true); - } - } - - void skipMember(MDMemberFlag flags, MDSectionOffset& offset) const { - if ((flags & metagen::mdMemberProperty) != 0) { - bool readonly = (flags & metagen::mdMemberReadonly) != 0; - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - if (!readonly) { - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - } - return; - } - - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - } - - std::vector readProtocolOffsetsForClass( - MDSectionOffset classOffset, MDSectionOffset* memberOffset = nullptr, - MDSectionOffset* superclassOffsetOut = nullptr) const { - std::vector protocols; - if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { - return protocols; - } - - MDSectionOffset offset = classOffset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; - - while (hasProtocols) { - auto protocolOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - protocolOffset &= ~metagen::mdSectionOffsetNext; - if (protocolOffset != MD_SECTION_OFFSET_NULL) { - protocols.push_back(protocolOffset + metadata_->protocolsOffset); - } - } - - auto superclass = metadata_->getOffset(offset); - offset += sizeof(superclass); - const bool hasMembers = (superclass & metagen::mdSectionOffsetNext) != 0; - if (superclassOffsetOut != nullptr) { - MDSectionOffset superclassOffset = - superclass & ~metagen::mdSectionOffsetNext; - *superclassOffsetOut = - superclassOffset != MD_SECTION_OFFSET_NULL - ? superclassOffset + metadata_->classesOffset - : MD_SECTION_OFFSET_NULL; - } - if (memberOffset != nullptr) { - *memberOffset = hasMembers ? offset : MD_SECTION_OFFSET_NULL; - } - return protocols; - } - - std::vector readOwnMembersForClass( - MDSectionOffset classOffset) const { - std::vector members; - if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { - return members; - } - - MDSectionOffset memberOffset = MD_SECTION_OFFSET_NULL; - for (MDSectionOffset protocolOffset : - readProtocolOffsetsForClass(classOffset, &memberOffset)) { - auto protocol = protocolSymbolsByOffset_.find(protocolOffset); - if (protocol == protocolSymbolsByOffset_.end()) { - continue; - } - const auto& protocolMembers = membersForProtocol(protocol->second); - members.insert(members.end(), protocolMembers.begin(), - protocolMembers.end()); - } - - if (memberOffset != MD_SECTION_OFFSET_NULL) { - std::vector ownMembers = - readMembersAtOffset(memberOffset); - members.insert(members.end(), ownMembers.begin(), ownMembers.end()); - } - return members; - } - - std::vector readMembersForClass( - MDSectionOffset classOffset) const { - std::vector members; - if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { - return members; - } - - MDSectionOffset offset = classOffset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; - - while (hasProtocols) { - auto protocolOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - - auto superclass = metadata_->getOffset(offset); - offset += sizeof(superclass); - - bool next = (superclass & metagen::mdSectionOffsetNext) != 0; - while (next) { - auto flags = metadata_->getMemberFlag(offset); - next = (flags & metagen::mdMemberNext) != 0; - offset += sizeof(flags); - if (flags == metagen::mdMemberFlagNull) { - break; - } - - NativeApiMember member; - member.flags = flags; - if ((flags & metagen::mdMemberProperty) != 0) { - member.property = true; - member.readonly = (flags & metagen::mdMemberReadonly) != 0; - member.name = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.selectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.signatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - - if (!member.readonly) { - member.setterSelectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.setterSignatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - } - } else { - member.selectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.signatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - member.name = jsifySelector(member.selectorName.c_str()); - } - members.push_back(std::move(member)); - } - - return members; - } - - static bool memberIsStatic(const NativeApiMember& member) { - return (member.flags & metagen::mdMemberStatic) != 0; - } - - static bool sameMemberSlot(const NativeApiMember& lhs, - const NativeApiMember& rhs) { - return lhs.name == rhs.name && lhs.property == rhs.property && - memberIsStatic(lhs) == memberIsStatic(rhs); - } - - static bool sameMethodSelector(const NativeApiMember& lhs, - const NativeApiMember& rhs) { - return !lhs.property && !rhs.property && sameMemberSlot(lhs, rhs) && - lhs.selectorName == rhs.selectorName; - } - - static const NativeApiMember* findPropertyMember( - const std::vector& members, - const NativeApiMember& candidate) { - for (const auto& member : members) { - if (member.property && sameMemberSlot(member, candidate)) { - return &member; - } - } - return nullptr; - } - - static bool selectorExistsInMembers( - const std::vector& members, - const NativeApiMember& candidate) { - for (const auto& member : members) { - if (sameMethodSelector(member, candidate)) { - return true; - } - } - return false; - } - - static bool shouldSkipPropertyOverride( - const NativeApiMember* inherited, const NativeApiMember& member) { - if (inherited == nullptr || !inherited->property) { - return false; - } - - bool sameGetter = inherited->selectorName == member.selectorName; - bool sameSetter = - inherited->setterSelectorName == member.setterSelectorName; - if ((!inherited->readonly && member.readonly) || - (inherited->readonly == member.readonly && sameGetter && - (member.readonly || sameSetter))) { - return true; - } - return false; - } - - static void appendSurfaceMember( - std::vector& surface, - const std::vector& inheritedMembers, - const NativeApiMember& member) { - if (member.name.empty()) { - return; - } - - if (member.property) { - const NativeApiMember* inherited = - findPropertyMember(inheritedMembers, member); - if (shouldSkipPropertyOverride(inherited, member)) { - return; - } - - for (auto& existing : surface) { - if (!existing.property || !sameMemberSlot(existing, member)) { - continue; - } - if (existing.readonly && !member.readonly) { - existing = member; - } - return; - } - surface.push_back(member); - return; - } - - const bool keepInheritedMethod = - member.name == "alloc" || member.name == "toString" || - member.name == "superclass"; - if (!keepInheritedMethod && - selectorExistsInMembers(inheritedMembers, member)) { - return; - } - if (selectorExistsInMembers(surface, member)) { - return; - } - surface.push_back(member); - } - - std::vector readSurfaceMembersForClass( - const NativeApiSymbol& symbol) const { - std::vector inheritedMembers; - if (symbol.superclassOffset != MD_SECTION_OFFSET_NULL) { - auto superclass = classSymbolsByOffset_.find(symbol.superclassOffset); - if (superclass != classSymbolsByOffset_.end()) { - const auto& inherited = surfaceMembersForClass(superclass->second); - inheritedMembers.insert(inheritedMembers.end(), inherited.begin(), - inherited.end()); - } - } - - std::vector surface; - for (const auto& member : readOwnMembersForClass(symbol.offset)) { - appendSurfaceMember(surface, inheritedMembers, member); - } - return surface; - } - - std::vector readMembersAtOffset( - MDSectionOffset& offset) const { - std::vector members; - bool next = true; - while (next) { - auto flags = metadata_->getMemberFlag(offset); - next = (flags & metagen::mdMemberNext) != 0; - offset += sizeof(flags); - if (flags == metagen::mdMemberFlagNull) { - break; - } - - NativeApiMember member; - member.flags = flags; - if ((flags & metagen::mdMemberProperty) != 0) { - member.property = true; - member.readonly = (flags & metagen::mdMemberReadonly) != 0; - member.name = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.selectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.signatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - - if (!member.readonly) { - member.setterSelectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.setterSignatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - } - } else { - member.selectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.signatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - member.name = jsifySelector(member.selectorName.c_str()); - } - members.push_back(std::move(member)); - } - return members; - } - - std::vector readMembersForProtocolHierarchy( - MDSectionOffset protocolOffset) const { - std::vector members; - if (metadata_ == nullptr || protocolOffset == MD_SECTION_OFFSET_NULL) { - return members; - } - - MDSectionOffset offset = protocolOffset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; - - while (hasProtocols) { - auto inheritedOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (inheritedOffset & metagen::mdSectionOffsetNext) != 0; - inheritedOffset &= ~metagen::mdSectionOffsetNext; - if (inheritedOffset == MD_SECTION_OFFSET_NULL) { - continue; - } - - MDSectionOffset absoluteOffset = - inheritedOffset + metadata_->protocolsOffset; - auto inheritedSymbol = protocolSymbolsByOffset_.find(absoluteOffset); - if (inheritedSymbol != protocolSymbolsByOffset_.end()) { - const auto& inheritedMembers = - membersForProtocol(inheritedSymbol->second); - members.insert(members.end(), inheritedMembers.begin(), - inheritedMembers.end()); - } - } - - std::vector ownMembers = readMembersAtOffset(offset); - members.insert(members.end(), ownMembers.begin(), ownMembers.end()); - return members; - } - - std::vector readMembersForClassHierarchy( - const NativeApiSymbol& symbol) const { - std::vector members = readOwnMembersForClass(symbol.offset); - if (symbol.superclassOffset == MD_SECTION_OFFSET_NULL) { - return members; - } - - auto superclass = classSymbolsByOffset_.find(symbol.superclassOffset); - if (superclass != classSymbolsByOffset_.end()) { - const auto& inheritedMembers = membersForClass(superclass->second); - members.insert(members.end(), inheritedMembers.begin(), - inheritedMembers.end()); - } - return members; - } - - std::unique_ptr metadata_; - void* selfDl_ = nullptr; - std::unordered_map symbolsByName_; - std::unordered_map functionSymbolsByName_; - std::unordered_map constantSymbolsByName_; - std::unordered_map enumSymbolsByName_; - std::unordered_map structSymbolsByName_; - std::unordered_map unionSymbolsByName_; - std::unordered_map classSymbolsByRuntimeName_; - std::unordered_map protocolSymbolsByRuntimeName_; - std::unordered_map classSymbolsByRuntimePointer_; - std::unordered_map protocolSymbolsByRuntimePointer_; - mutable std::mutex roundTripValuesMutex_; - std::unordered_map> roundTripValues_; - std::unordered_map> classValues_; - std::unordered_map> classPrototypes_; - std::unordered_map> pointerValues_; - std::unordered_map>> - objectExpandos_; - std::unordered_map classSymbolsByOffset_; - std::unordered_map protocolSymbolsByOffset_; - std::vector classNames_; - std::vector functionNames_; - std::vector constantNames_; - std::vector protocolNames_; - std::vector enumNames_; - std::vector structNames_; - std::vector unionNames_; - std::shared_ptr scheduler_; - std::function)> nativeInvocationInvoker_; - std::function)> nativeCallbackInvoker_; - std::function)> jsThreadCallbackInvoker_; - bool invokeCallbacksOnNativeCallerThread_ = false; - mutable std::unordered_map> - membersByClassOffset_; - mutable std::unordered_map> - surfaceMembersByClassOffset_; - mutable std::unordered_map> - membersByProtocolOffset_; - std::unordered_map structSymbolsByOffset_; - std::unordered_map unionSymbolsByOffset_; - std::unordered_map> - aggregateInfoByOffset_; - std::unordered_set aggregateInfoInProgress_; - std::thread::id jsThreadId_ = std::this_thread::get_id(); - std::mutex retainedLifetimesMutex_; - std::vector> retainedLifetimes_; -}; - -Value makeString(Runtime& runtime, const std::string& value) { - return String::createFromUtf8(runtime, value); -} - -std::string readStringArg(Runtime& runtime, const Value* args, size_t count, - size_t index, const char* argumentName) { - if (index >= count || !args[index].isString()) { - throw facebook::jsi::JSError( - runtime, std::string(argumentName) + " must be a string."); - } - return args[index].asString(runtime).utf8(runtime); -} - -const char* kindName(NativeApiSymbolKind kind) { - switch (kind) { - case NativeApiSymbolKind::Class: - return "class"; - case NativeApiSymbolKind::Function: - return "function"; - case NativeApiSymbolKind::Constant: - return "constant"; - case NativeApiSymbolKind::Protocol: - return "protocol"; - case NativeApiSymbolKind::Enum: - return "enum"; - case NativeApiSymbolKind::Struct: - return "struct"; - case NativeApiSymbolKind::Union: - return "union"; - } - return "unknown"; -} - -Array namesToArray(Runtime& runtime, const std::vector& names) { - Array result(runtime, names.size()); - for (size_t i = 0; i < names.size(); i++) { - result.setValueAtIndex(runtime, i, makeString(runtime, names[i])); - } - return result; -} - -void addPropertyName(Runtime& runtime, std::vector& names, - const char* name) { - names.push_back(PropNameID::forAscii(runtime, name)); -} - -class NativeApiPointerHostObject; -class NativeApiObjectHostObject; -class NativeApiClassHostObject; -class NativeApiProtocolHostObject; -class NativeApiJsiArgumentFrame; - -Value callCFunction(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiSymbol& symbol, const Value* args, - size_t count); - -Value callObjCSelector(Runtime& runtime, - const std::shared_ptr& bridge, - id receiver, bool receiverIsClass, - const std::string& selectorName, - const NativeApiMember* member, - const Value* args, size_t count, - Class dispatchSuperClass = Nil); - -Value makeNativeObjectValue(Runtime& runtime, - const std::shared_ptr& bridge, - id object, bool ownsObject); - -Value makeNativeClassValue(Runtime& runtime, - const std::shared_ptr& bridge, - NativeApiSymbol symbol); - -Object symbolToObject(Runtime& runtime, const NativeApiSymbol& symbol) { - Object result(runtime); - result.setProperty(runtime, "kind", makeString(runtime, kindName(symbol.kind))); - result.setProperty(runtime, "name", makeString(runtime, symbol.name)); - result.setProperty(runtime, "runtimeName", - makeString(runtime, symbol.runtimeName)); - result.setProperty(runtime, "metadataOffset", - static_cast(symbol.offset)); - - if (symbol.kind == NativeApiSymbolKind::Class) { - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - result.setProperty(runtime, "available", cls != nil); - if (cls != nil) { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", cls); - result.setProperty(runtime, "nativeAddress", makeString(runtime, address)); - } - } else if (symbol.kind == NativeApiSymbolKind::Struct || - symbol.kind == NativeApiSymbolKind::Union) { - result.setProperty(runtime, "available", true); - } - - return result; -} - -size_t nativeSizeForType(const NativeApiJsiType& type); -std::optional parseArrayIndexProperty(const std::string& property); - -NativeApiJsiType nativeObjectReturnType( - MDTypeKind kind = metagen::mdTypeAnyObject) { - NativeApiJsiType type; - type.kind = kind; - type.ffiType = &ffi_type_pointer; - type.supported = true; - return type; -} - -NativeApiJsiType nativeObjectReturnTypeForClass(Class cls) { - if (cls != Nil) { - const char* name = class_getName(cls); - if (name != nullptr && std::strcmp(name, "NSString") == 0) { - return nativeObjectReturnType(metagen::mdTypeNSStringObject); - } - if (name != nullptr && std::strcmp(name, "NSMutableString") == 0) { - return nativeObjectReturnType(metagen::mdTypeNSMutableStringObject); - } - } - return nativeObjectReturnType(metagen::mdTypeInstanceObject); -} - -Value convertNativeReturnValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* value); -Object createPointer(Runtime& runtime, - const std::shared_ptr& bridge, - void* pointer, bool adopted = false); - -NativeApiJsiType primitiveInteropType(MDTypeKind kind); diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiCallbacks.h b/NativeScript/ffi/shared/jsi/NativeApiJsiCallbacks.h deleted file mode 100644 index cad9a54d1..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiCallbacks.h +++ /dev/null @@ -1,1974 +0,0 @@ -bool isObjectiveCObjectType(const NativeApiJsiType& type) { - switch (type.kind) { - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: - return true; - default: - return false; - } -} - -#ifndef NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME -std::shared_ptr retainNativeApiJsiRuntime(Runtime& runtime) { - return std::shared_ptr(&runtime, [](Runtime*) {}); -} -#endif - -#ifndef NATIVESCRIPT_NATIVE_API_RUNTIME_SCOPE -class NativeApiJsiRuntimeScope final { - public: - explicit NativeApiJsiRuntimeScope(Runtime&) {} -}; -#endif - -struct NativeApiJsiSignature { - ffi_cif cif = {}; - NativeApiJsiType returnType; - std::vector argumentTypes; - std::vector ffiTypes; - std::string selectorName; - bool variadic = false; - bool prepared = false; - unsigned int implicitArgumentCount = 0; -}; - -enum class NativeApiJsiCallbackThreadPolicy { - Default, - UI, - JS, -}; - -NativeApiJsiCallbackThreadPolicy readJsiCallbackThreadPolicy( - Runtime& runtime, Object& functionObject) { - constexpr const char* propertyName = "__nativeScriptCallbackThread"; - try { - if (!functionObject.hasProperty(runtime, propertyName)) { - return NativeApiJsiCallbackThreadPolicy::Default; - } - Value policyValue = functionObject.getProperty(runtime, propertyName); - if (!policyValue.isString()) { - return NativeApiJsiCallbackThreadPolicy::Default; - } - std::string policy = policyValue.asString(runtime).utf8(runtime); - if (policy == "ui") { - return NativeApiJsiCallbackThreadPolicy::UI; - } - if (policy == "js") { - return NativeApiJsiCallbackThreadPolicy::JS; - } - } catch (const std::exception&) { - } - return NativeApiJsiCallbackThreadPolicy::Default; -} - -bool selectorEndsWithNSErrorParam(const std::string& selectorName) { - constexpr const char* suffix = "error:"; - size_t suffixLength = std::strlen(suffix); - return selectorName.size() >= suffixLength && - selectorName.compare(selectorName.size() - suffixLength, suffixLength, - suffix) == 0; -} - -bool isNSErrorOutJsiMethodSignature(const NativeApiJsiSignature& signature) { - if (signature.argumentTypes.empty() || signature.variadic || - !selectorEndsWithNSErrorParam(signature.selectorName)) { - return false; - } - - return signature.argumentTypes.back().kind == metagen::mdTypePointer; -} - -bool isNSErrorOutJsiMethodCallback(const NativeApiJsiSignature& signature) { - return signature.returnType.kind == metagen::mdTypeBool && - signature.implicitArgumentCount >= 2 && - isNSErrorOutJsiMethodSignature(signature); -} - -class NativeApiJsiArgumentFrame { - public: - explicit NativeApiJsiArgumentFrame(size_t count) : storage_(count), values_(count) {} - - ~NativeApiJsiArgumentFrame() { - for (char* string : ownedCStrings_) { - free(string); - } - for (void* buffer : ownedBuffers_) { - free(buffer); - } - for (id object : ownedObjects_) { - [object release]; - } - for (const auto& entry : temporaryRoundTripValues_) { - if (entry.first != nullptr) { - entry.first->forgetRoundTripValue(entry.second); - } - } - ownedLifetimes_.clear(); - } - - void* storageAt(size_t index, size_t size) { - storage_[index].assign(std::max(size, sizeof(void*)), 0); - values_[index] = storage_[index].data(); - return values_[index]; - } - - void addCString(char* value) { ownedCStrings_.push_back(value); } - void* addBuffer(size_t size) { - void* buffer = calloc(1, std::max(size, 1)); - if (buffer == nullptr) { - throw std::bad_alloc(); - } - ownedBuffers_.push_back(buffer); - return buffer; - } - void addObject(id value) { ownedObjects_.push_back(value); } - void addLifetime(std::shared_ptr value) { - if (value != nullptr) { - ownedLifetimes_.push_back(std::move(value)); - } - } - void rememberRoundTripValue( - const std::shared_ptr& bridge, Runtime& runtime, - const void* native, const Value& value) { - if (bridge == nullptr || native == nullptr) { - return; - } - bridge->rememberRoundTripValue(runtime, native, value); - temporaryRoundTripValues_.push_back({bridge, native}); - } - void** values() { return values_.empty() ? nullptr : values_.data(); } - - private: - std::vector> storage_; - std::vector values_; - std::vector ownedCStrings_; - std::vector ownedBuffers_; - std::vector ownedObjects_; - std::vector> ownedLifetimes_; - std::vector, const void*>> - temporaryRoundTripValues_; -}; - -class NativeApiMutableBuffer final : public MutableBuffer { - public: - explicit NativeApiMutableBuffer(size_t size) : data_(size) {} - NativeApiMutableBuffer(const void* data, size_t size) : data_(size) { - if (data != nullptr && size > 0) { - std::memcpy(data_.data(), data, size); - } - } - - size_t size() const override { return data_.size(); } - uint8_t* data() override { return data_.empty() ? nullptr : data_.data(); } - - private: - std::vector data_; -}; - -void convertJsiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame); - -Value convertNativeReturnValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* value); - -Value wrapNativeFunctionPointer(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* pointer, - bool block); - -bool isObjectiveCObjectType(const NativeApiJsiType& type); - -struct NativeApiJsiBlockDescriptor { - unsigned long reserved = 0; - unsigned long size = 0; - void (*copyHelper)(void*, void*) = nullptr; - void (*disposeHelper)(void*) = nullptr; - const char* signature = nullptr; -}; - -struct NativeApiJsiBlockLiteral { - void* isa = nullptr; - int flags = 0; - int reserved = 0; - void* invoke = nullptr; - NativeApiJsiBlockDescriptor* descriptor = nullptr; - void* callback = nullptr; -}; - -constexpr int kNativeApiJsiBlockNeedsFree = (1 << 24); -constexpr int kNativeApiJsiBlockHasCopyDispose = (1 << 25); -constexpr int kNativeApiJsiBlockRefCountOne = (1 << 1); -constexpr int kNativeApiJsiBlockHasSignature = (1 << 30); - -void* nativeApiJsiStackBlockIsa() { - static void* isa = dlsym(RTLD_DEFAULT, "_NSConcreteStackBlock"); - return isa; -} - -void nativeApiJsiBlockCopy(void* dst, void* src); -void nativeApiJsiBlockDispose(void* src); - -std::string objcEncodingForJsiType(const NativeApiJsiType& type) { - switch (type.kind) { - case metagen::mdTypeVoid: - return "v"; - case metagen::mdTypeBool: - return "B"; - case metagen::mdTypeChar: - return "c"; - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - return "C"; - case metagen::mdTypeSShort: - return "s"; - case metagen::mdTypeUShort: - case metagen::mdTypeUnichar: - return "S"; - case metagen::mdTypeSInt: - return "i"; - case metagen::mdTypeUInt: - return "I"; - case metagen::mdTypeSLong: - case metagen::mdTypeSInt64: - return "q"; - case metagen::mdTypeULong: - case metagen::mdTypeUInt64: - return "Q"; - case metagen::mdTypeFloat: - return "f"; - case metagen::mdTypeDouble: - return "d"; - case metagen::mdTypeString: - return "*"; - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: - return "@"; - case metagen::mdTypeClass: - return "#"; - case metagen::mdTypeSelector: - return ":"; - case metagen::mdTypeBlock: - return "@?"; - case metagen::mdTypeFunctionPointer: - return "^?"; - case metagen::mdTypePointer: - case metagen::mdTypeOpaquePointer: - if (type.elementType != nullptr && - type.elementType->kind != metagen::mdTypeVoid) { - return "^" + objcEncodingForJsiType(*type.elementType); - } - return "^v"; - case metagen::mdTypeStruct: - return "{" + - (type.aggregateInfo != nullptr ? type.aggregateInfo->name - : std::string("?")) + - "=}"; - case metagen::mdTypeArray: - return "[" + std::to_string(type.arraySize) + - (type.elementType != nullptr ? objcEncodingForJsiType(*type.elementType) - : std::string("?")) + - "]"; - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: - return type.elementType != nullptr ? objcEncodingForJsiType(*type.elementType) - : "?"; - default: - return "?"; - } -} - -std::string objcBlockSignatureForJsiSignature( - const NativeApiJsiSignature& signature) { - std::string encoding = objcEncodingForJsiType(signature.returnType); - encoding += "@?"; - for (const auto& argType : signature.argumentTypes) { - encoding += objcEncodingForJsiType(argType); - } - return encoding; -} - -std::string objcMethodSignatureForJsiSignature( - const NativeApiJsiSignature& signature) { - std::string encoding = objcEncodingForJsiType(signature.returnType); - encoding += "@:"; - for (const auto& argType : signature.argumentTypes) { - encoding += objcEncodingForJsiType(argType); - } - return encoding; -} - -[[noreturn]] void throwNativeApiJsiCallbackException( - const std::string& message) { - NSString* reason = [NSString stringWithUTF8String:message.c_str()]; - @throw [NSException exceptionWithName:@"NativeScriptJSICallbackException" - reason:reason - userInfo:nil]; -} - -class NativeApiJsiCallback; - -void nativeApiJsiCallbackTrampoline(ffi_cif* cif, void* ret, void* args[], - void* data); - -std::atomic gActiveNativeThreadJsiCallbacks{0}; - -class NativeApiJsiCallback final - : public std::enable_shared_from_this { - public: - NativeApiJsiCallback(Runtime& runtime, - std::shared_ptr bridge, - std::shared_ptr signature, - Function function, bool block, - NativeApiJsiCallbackThreadPolicy threadPolicy = - NativeApiJsiCallbackThreadPolicy::Default, - bool bindThis = false) - : runtimeOwner_(retainNativeApiJsiRuntime(runtime)), - runtime_(runtimeOwner_.get()), - bridge_(std::move(bridge)), - signature_(std::move(signature)), - function_(std::make_shared(std::move(function))), - block_(block), - threadPolicy_(threadPolicy), - bindThis_(bindThis) { - closure_ = static_cast( - ffi_closure_alloc(sizeof(ffi_closure), &executable_)); - if (closure_ == nullptr || executable_ == nullptr || - signature_ == nullptr || !signature_->prepared) { - throw facebook::jsi::JSError(runtime, - "Unable to allocate native JSI callback."); - } - - ffi_status status = ffi_prep_closure_loc( - closure_, &signature_->cif, nativeApiJsiCallbackTrampoline, this, - executable_); - if (status != FFI_OK) { - ffi_closure_free(closure_); - closure_ = nullptr; - executable_ = nullptr; - throw facebook::jsi::JSError(runtime, - "Unable to prepare native JSI callback."); - } - - if (block_) { - blockSignature_ = objcBlockSignatureForJsiSignature(*signature_); - descriptor_ = std::make_unique(); - descriptor_->reserved = 0; - descriptor_->size = sizeof(NativeApiJsiBlockLiteral); - descriptor_->copyHelper = nativeApiJsiBlockCopy; - descriptor_->disposeHelper = nativeApiJsiBlockDispose; - descriptor_->signature = blockSignature_.c_str(); - - blockLiteral_ = std::make_unique(); - blockLiteral_->isa = nativeApiJsiStackBlockIsa(); - blockLiteral_->flags = kNativeApiJsiBlockHasCopyDispose | - kNativeApiJsiBlockHasSignature; - blockLiteral_->invoke = executable_; - blockLiteral_->descriptor = descriptor_.get(); - blockLiteral_->callback = this; - } - } - - ~NativeApiJsiCallback() { - if (closure_ != nullptr) { - ffi_closure_free(closure_); - closure_ = nullptr; - executable_ = nullptr; - } - } - - void* functionPointer() const { - return block_ && blockLiteral_ != nullptr - ? static_cast(blockLiteral_.get()) - : executable_; - } - - const NativeApiJsiSignature& signature() const { return *signature_; } - - void retainBlockCopy(const void* blockPointer) { - if (!block_) { - return; - } - auto self = shared_from_this(); - if (bridge_ != nullptr && runtime_ != nullptr && function_ != nullptr && - blockPointer != nullptr) { - bridge_->rememberRoundTripValue(*runtime_, blockPointer, - Value(*runtime_, *function_)); - } - std::lock_guard lock(retainedBlockCopiesMutex_); - retainedBlockCopies_.push_back({blockPointer, std::move(self)}); - } - - bool releaseBlockCopy(const void* blockPointer) { - if (!block_) { - return false; - } - std::shared_ptr keepAlive; - try { - keepAlive = shared_from_this(); - } catch (const std::bad_weak_ptr&) { - return false; - } - std::lock_guard lock(retainedBlockCopiesMutex_); - auto it = retainedBlockCopies_.end(); - if (blockPointer != nullptr) { - it = std::find_if( - retainedBlockCopies_.begin(), retainedBlockCopies_.end(), - [blockPointer](const RetainedBlockCopy& retained) { - return retained.blockPointer == blockPointer; - }); - } - if (it != retainedBlockCopies_.end()) { - if (bridge_ != nullptr && it->blockPointer != nullptr) { - bridge_->forgetRoundTripValue(it->blockPointer); - } - retainedBlockCopies_.erase(it); - return true; - } - return false; - } - - void invoke(void* ret, void* args[]) { - if (runtime_ == nullptr || function_ == nullptr || signature_ == nullptr) { - throwNativeApiJsiCallbackException("Invalid JSI callback."); - } - - std::string error; - auto call = [&]() { invokeOnCurrentThread(ret, args, &error); }; - const auto& nativeCallbackInvoker = bridge_->nativeCallbackInvoker(); - const auto& jsThreadCallbackInvoker = bridge_->jsThreadCallbackInvoker(); - bool currentThreadIsJs = - std::this_thread::get_id() == bridge_->jsThreadId(); - - auto callOnNativeCallerThread = [&]() { - ScopedNativeCallerThreadJsiCallback callbackScope; - if (nativeCallbackInvoker) { - nativeCallbackInvoker(call); - } else { - call(); - } - }; - auto callOnUIThread = [&]() { - auto runOnUIThread = [&]() { - bool previous = gExecutingDispatchedUINativeCall; - gExecutingDispatchedUINativeCall = true; - callOnNativeCallerThread(); - gExecutingDispatchedUINativeCall = previous; - }; - if ([NSThread isMainThread]) { - runOnUIThread(); - } else { - dispatch_sync(dispatch_get_main_queue(), ^{ - runOnUIThread(); - }); - } - }; - auto callOnJSThread = [&]() { - if (currentThreadIsJs) { - call(); - return; - } - if (jsThreadCallbackInvoker) { - jsThreadCallbackInvoker(call); - return; - } - if (auto scheduler = bridge_->scheduler()) { - dispatch_semaphore_t done = dispatch_semaphore_create(0); - scheduler->invokeOnJS([call, done]() mutable { - call(); - dispatch_semaphore_signal(done); - }); - dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER); - return; - } - error = "Native callback was invoked off the JS thread without a JS scheduler."; - }; - - if (threadPolicy_ == NativeApiJsiCallbackThreadPolicy::UI) { - callOnUIThread(); - if (!error.empty()) { - if (!recordNativeCallbackException(error)) { - throwNativeApiJsiCallbackException(error); - } - } - return; - } - if (threadPolicy_ == NativeApiJsiCallbackThreadPolicy::JS) { - callOnJSThread(); - if (!error.empty()) { - if (!recordNativeCallbackException(error)) { - throwNativeApiJsiCallbackException(error); - } - } - return; - } - - bool returnsVoid = signature_->returnType.kind == metagen::mdTypeVoid; - bool activeSynchronousNativeInvocation = - gActiveSynchronousNativeInvocationDepth.load( - std::memory_order_acquire) > 0; - bool nativeCallerThreadCallbacks = - bridge_->invokeCallbacksOnNativeCallerThread(); - bool nativeCallerThreadCallback = - nativeCallerThreadCallbacks && !currentThreadIsJs && - (block_ || bindThis_ || - (activeSynchronousNativeInvocation && !returnsVoid)); - bool direct = currentThreadIsJs || - gExecutingDispatchedUINativeCall || - gSynchronousNativeInvocationDepth > 0 || - nativeCallerThreadCallback || - (nativeCallerThreadCallbacks && !nativeCallbackInvoker && - activeSynchronousNativeInvocation); - bool waitForNativeThreadCallback = - currentThreadIsJs && nativeCallbackInvoker && - gActiveNativeThreadJsiCallbacks.load(std::memory_order_acquire) > 0; - if (direct && !waitForNativeThreadCallback) { - if (nativeCallerThreadCallback) { - callOnNativeCallerThread(); - } else { - call(); - } - } else if (!currentThreadIsJs && !nativeCallerThreadCallbacks) { - callOnJSThread(); - } else if (!currentThreadIsJs && returnsVoid && block_ && - jsThreadCallbackInvoker) { - jsThreadCallbackInvoker(call); - } else if (nativeCallbackInvoker) { - bool nativeThreadCallback = !currentThreadIsJs; - if (nativeThreadCallback) { - gActiveNativeThreadJsiCallbacks.fetch_add(1, - std::memory_order_acq_rel); - } - try { - nativeCallbackInvoker(call); - } catch (...) { - if (nativeThreadCallback) { - gActiveNativeThreadJsiCallbacks.fetch_sub( - 1, std::memory_order_acq_rel); - } - throw; - } - if (nativeThreadCallback) { - gActiveNativeThreadJsiCallbacks.fetch_sub(1, - std::memory_order_acq_rel); - } - } else if (auto scheduler = bridge_->scheduler()) { - dispatch_semaphore_t done = dispatch_semaphore_create(0); - scheduler->invokeOnJS([call, done]() mutable { - call(); - dispatch_semaphore_signal(done); - }); - dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER); - } else { - error = "Native callback was invoked off the JS thread without a JS scheduler."; - } - - if (!error.empty()) { - if (!recordNativeCallbackException(error)) { - throwNativeApiJsiCallbackException(error); - } - } - } - - private: - void invokeOnCurrentThread(void* ret, void* args[], std::string* error) { - try { - NativeApiJsiRuntimeScope runtimeScope(*runtime_); - size_t nativeArgOffset = signature_->implicitArgumentCount; - std::vector jsArgs; - jsArgs.reserve(signature_->argumentTypes.size()); - for (size_t i = 0; i < signature_->argumentTypes.size(); i++) { - jsArgs.emplace_back(convertNativeReturnValue( - *runtime_, bridge_, signature_->argumentTypes[i], - args[i + nativeArgOffset])); - } - - Value result = Value::undefined(); - if (bindThis_ && nativeArgOffset >= 1) { - id self = *static_cast(args[0]); - Value thisValue = - makeNativeObjectValue(*runtime_, bridge_, self, false); - Object thisObject = thisValue.isObject() - ? thisValue.asObject(*runtime_) - : Object(*runtime_); - result = - jsArgs.empty() - ? function_->callWithThis(*runtime_, thisObject) - : function_->callWithThis( - *runtime_, thisObject, - static_cast(jsArgs.data()), - static_cast(jsArgs.size())); - } else { - result = - jsArgs.empty() - ? function_->call(*runtime_) - : function_->call(*runtime_, - static_cast(jsArgs.data()), - static_cast(jsArgs.size())); - } - storeReturnValue(result, ret); - if (std::this_thread::get_id() == bridge_->jsThreadId()) { - runtime_->drainMicrotasks(); - } - } catch (const std::exception& exception) { - if (isNSErrorOutJsiMethodCallback(*signature_)) { - zeroReturnValue(ret); - populateNSErrorOutArgument(args, exception.what()); - return; - } - if (error != nullptr) { - *error = exception.what(); - } - zeroReturnValue(ret); - } catch (...) { - if (isNSErrorOutJsiMethodCallback(*signature_)) { - zeroReturnValue(ret); - populateNSErrorOutArgument(args, "Unknown exception in native JSI callback."); - return; - } - if (error != nullptr) { - *error = "Unknown exception in native JSI callback."; - } - zeroReturnValue(ret); - } - } - - void populateNSErrorOutArgument(void* args[], const char* message) { - if (args == nullptr || signature_ == nullptr || - signature_->argumentTypes.empty()) { - return; - } - - size_t outArgIndex = signature_->implicitArgumentCount + - signature_->argumentTypes.size() - 1; - void* outArgValue = args[outArgIndex]; - NSError** outError = - outArgValue != nullptr ? *reinterpret_cast(outArgValue) - : nullptr; - if (outError == nullptr) { - return; - } - - NSString* nsMessage = - message != nullptr ? [NSString stringWithUTF8String:message] : nil; - if (nsMessage == nil) { - nsMessage = @"JS error"; - } - NSDictionary* userInfo = @{NSLocalizedDescriptionKey : nsMessage}; - *outError = [NSError errorWithDomain:@"TNSErrorDomain" - code:1 - userInfo:userInfo]; - } - - void zeroReturnValue(void* ret) { - if (ret == nullptr || signature_ == nullptr || - signature_->returnType.kind == metagen::mdTypeVoid) { - return; - } - size_t size = nativeSizeForType(signature_->returnType); - if (size > 0) { - std::memset(ret, 0, size); - } - } - - void storeReturnValue(const Value& result, void* ret) { - if (ret == nullptr || - signature_->returnType.kind == metagen::mdTypeVoid) { - return; - } - - zeroReturnValue(ret); - if (result.isUndefined() || result.isNull()) { - return; - } - const auto& returnType = signature_->returnType; - if (returnType.kind == metagen::mdTypeString && result.isString()) { - std::string utf8 = result.asString(*runtime_).utf8(*runtime_); - *static_cast(ret) = strdup(utf8.c_str()); - return; - } - if ((returnType.kind == metagen::mdTypePointer || - returnType.kind == metagen::mdTypeOpaquePointer) && - result.isString()) { - std::string utf8 = result.asString(*runtime_).utf8(*runtime_); - *static_cast(ret) = strdup(utf8.c_str()); - return; - } - - NativeApiJsiArgumentFrame frame(1); - convertJsiArgument(*runtime_, bridge_, returnType, result, ret, frame); - if (isObjectiveCObjectType(returnType)) { - id object = *static_cast(ret); - if (object != nil) { - [object retain]; - [object autorelease]; - } - } - } - - std::shared_ptr runtimeOwner_; - Runtime* runtime_ = nullptr; - std::shared_ptr bridge_; - std::shared_ptr signature_; - std::shared_ptr function_; - bool block_ = false; - NativeApiJsiCallbackThreadPolicy threadPolicy_ = - NativeApiJsiCallbackThreadPolicy::Default; - bool bindThis_ = false; - ffi_closure* closure_ = nullptr; - void* executable_ = nullptr; - std::string blockSignature_; - std::unique_ptr descriptor_; - std::unique_ptr blockLiteral_; - struct RetainedBlockCopy { - const void* blockPointer = nullptr; - std::shared_ptr lifetime; - }; - std::mutex retainedBlockCopiesMutex_; - std::vector retainedBlockCopies_; -}; - -void nativeApiJsiBlockCopy(void* dst, void* src) { - auto* dstBlock = static_cast(dst); - auto* srcBlock = static_cast(src); - if (dstBlock == nullptr || srcBlock == nullptr || - srcBlock->callback == nullptr) { - return; - } - dstBlock->callback = srcBlock->callback; - static_cast(srcBlock->callback) - ->retainBlockCopy(dstBlock); -} - -void nativeApiJsiBlockDispose(void* src) { - auto* block = static_cast(src); - if (block == nullptr || block->callback == nullptr) { - return; - } - bool released = - static_cast(block->callback)->releaseBlockCopy(block); - if (released) { - block->callback = nullptr; - } -} - -void nativeApiJsiCallbackTrampoline(ffi_cif*, void* ret, void* args[], - void* data) { - auto callback = static_cast(data); - if (callback == nullptr) { - return; - } - @try { - callback->invoke(ret, args); - } @catch (NSException* exception) { - const char* description = - exception.description != nil ? exception.description.UTF8String : nullptr; - std::string message = description != nullptr - ? description - : "Objective-C exception in native JSI callback."; - if (!recordNativeCallbackException(message)) { - @throw; - } - } -} - -size_t nativeSizeForType(const NativeApiJsiType& type) { - switch (type.kind) { - case metagen::mdTypeStruct: - if (type.aggregateInfo != nullptr) { - return type.aggregateInfo->size; - } - break; - case metagen::mdTypeArray: - if (type.elementType != nullptr) { - return nativeSizeForType(*type.elementType) * - static_cast(type.arraySize); - } - break; - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: - if (type.elementType != nullptr) { - size_t lanes = std::max(type.arraySize, 1); - size_t abiLanes = lanes == 3 ? 4 : lanes; - return nativeSizeForType(*type.elementType) * abiLanes; - } - break; - default: - break; - } - - if (type.ffiType != nullptr && type.ffiType->size > 0) { - return type.ffiType->size; - } - if (type.ffiType == &ffi_type_void) { - return 0; - } - return sizeof(void*); -} - -Value signedInteger64ToJsiValue(Runtime& runtime, int64_t value) { - constexpr int64_t maxSafeInteger = 9007199254740991LL; - constexpr int64_t minSafeInteger = -9007199254740991LL; - if (value >= minSafeInteger && value <= maxSafeInteger) { - return static_cast(value); - } - return BigInt::fromInt64(runtime, value); -} - -Value unsignedInteger64ToJsiValue(Runtime& runtime, uint64_t value) { - constexpr uint64_t maxSafeInteger = 9007199254740991ULL; - if (value <= maxSafeInteger) { - return static_cast(value); - } - return BigInt::fromUint64(runtime, value); -} - -bool parseIntegerTextToUintptr(const std::string& text, uintptr_t* address) { - if (address == nullptr) { - return false; - } - if (text.empty()) { - return false; - } - - char* end = nullptr; - if (text[0] == '-') { - long long signedValue = std::strtoll(text.c_str(), &end, 10); - if (end == nullptr || *end != '\0') { - return false; - } - *address = static_cast(static_cast(signedValue)); - return true; - } - - int base = 10; - const char* start = text.c_str(); - if (text.size() > 2 && text[0] == '0' && - (text[1] == 'x' || text[1] == 'X')) { - base = 16; - } - unsigned long long unsignedValue = std::strtoull(start, &end, base); - if (end == nullptr || *end != '\0') { - return false; - } - *address = static_cast(unsignedValue); - return true; -} - -bool parseBigIntToUintptr(Runtime& runtime, const BigInt& bigint, - uintptr_t* address) { - return parseIntegerTextToUintptr(bigint.toString(runtime, 10).utf8(runtime), - address); -} - -bool readJsiBuffer(Runtime& runtime, const Object& object, const uint8_t** data, - size_t* byteLength) { - if (data == nullptr || byteLength == nullptr) { - return false; - } - - if (object.isArrayBuffer(runtime)) { - ArrayBuffer buffer = object.getArrayBuffer(runtime); - *data = buffer.data(runtime); - *byteLength = buffer.size(runtime); - return true; - } - - Value bufferValue = object.getProperty(runtime, "buffer"); - if (!bufferValue.isObject()) { - return false; - } - Object bufferObject = bufferValue.asObject(runtime); - if (!bufferObject.isArrayBuffer(runtime)) { - return false; - } - - size_t byteOffset = 0; - size_t viewByteLength = 0; - Value offsetValue = object.getProperty(runtime, "byteOffset"); - if (offsetValue.isNumber()) { - byteOffset = static_cast(std::max(0, offsetValue.getNumber())); - } - Value lengthValue = object.getProperty(runtime, "byteLength"); - if (lengthValue.isNumber()) { - viewByteLength = static_cast(std::max(0, lengthValue.getNumber())); - } - - ArrayBuffer buffer = bufferObject.getArrayBuffer(runtime); - if (byteOffset > buffer.size(runtime)) { - return false; - } - if (viewByteLength == 0 || byteOffset + viewByteLength > buffer.size(runtime)) { - viewByteLength = buffer.size(runtime) - byteOffset; - } - *data = buffer.data(runtime) + byteOffset; - *byteLength = viewByteLength; - return true; -} - -uint32_t rawTypeKind(MDTypeKind kind) { - return static_cast(kind); -} - -MDTypeKind stripTypeFlags(MDTypeKind kind) { - uint32_t raw = rawTypeKind(kind); - raw &= ~static_cast(metagen::mdTypeFlagNext); - raw &= ~static_cast(metagen::mdTypeFlagVariadic); - return static_cast(raw); -} - -size_t alignUp(size_t value, size_t alignment) { - if (alignment == 0) { - return value; - } - return ((value + alignment - 1) / alignment) * alignment; -} - -ffi_type* ffiTypeForJsiKind(MDTypeKind kind) { - switch (kind) { - case metagen::mdTypeChar: - return &ffi_type_sint8; - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - case metagen::mdTypeBool: - return &ffi_type_uint8; - case metagen::mdTypeSShort: - return &ffi_type_sint16; - case metagen::mdTypeUShort: - case metagen::mdTypeUnichar: - return &ffi_type_uint16; - case metagen::mdTypeSInt: - return &ffi_type_sint32; - case metagen::mdTypeUInt: - return &ffi_type_uint32; - case metagen::mdTypeSLong: - case metagen::mdTypeSInt64: - return &ffi_type_sint64; - case metagen::mdTypeULong: - case metagen::mdTypeUInt64: - return &ffi_type_uint64; - case metagen::mdTypeFloat: - return &ffi_type_float; - case metagen::mdTypeDouble: - return &ffi_type_double; - case metagen::mdTypeVoid: - return &ffi_type_void; - case metagen::mdTypeString: - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: - case metagen::mdTypeClass: - case metagen::mdTypeSelector: - case metagen::mdTypePointer: - case metagen::mdTypeOpaquePointer: - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: - return &ffi_type_pointer; - default: - return nullptr; - } -} - -bool isSupportedJsiKind(MDTypeKind kind) { - switch (kind) { - default: - return ffiTypeForJsiKind(kind) != nullptr; - } -} - -void skipMetadataJsiTypePayload(MDMetadataReader* metadata, MDSectionOffset* offset, - MDTypeKind kind); - -void skipMetadataJsiType(MDMetadataReader* metadata, MDSectionOffset* offset) { - MDTypeKind kind = stripTypeFlags(metadata->getTypeKind(*offset)); - *offset += sizeof(MDTypeKind); - skipMetadataJsiTypePayload(metadata, offset, kind); -} - -void skipMetadataJsiTypePayload(MDMetadataReader* metadata, MDSectionOffset* offset, - MDTypeKind kind) { - switch (kind) { - case metagen::mdTypeClassObject: { - auto classOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool next = (classOffset & metagen::mdSectionOffsetNext) != 0; - while (next) { - auto protocolOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - break; - } - case metagen::mdTypeProtocolObject: { - bool next = true; - while (next) { - auto protocolOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - break; - } - case metagen::mdTypeArray: - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: - *offset += sizeof(uint16_t); - skipMetadataJsiType(metadata, offset); - break; - case metagen::mdTypeStruct: - *offset += sizeof(MDSectionOffset); - break; - case metagen::mdTypePointer: - skipMetadataJsiType(metadata, offset); - break; - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: - *offset += sizeof(MDSectionOffset); - break; - default: - break; - } -} - -NativeApiJsiType parseMetadataJsiType(MDMetadataReader* metadata, - MDSectionOffset* offset, - NativeApiJsiBridge* bridge) { - MDTypeKind rawKind = metadata->getTypeKind(*offset); - MDTypeKind kind = stripTypeFlags(rawKind); - *offset += sizeof(MDTypeKind); - - NativeApiJsiType type; - type.kind = kind; - - switch (kind) { - case metagen::mdTypeArray: { - type.arraySize = metadata->getArraySize(*offset); - *offset += sizeof(uint16_t); - type.elementType = - std::make_shared( - parseMetadataJsiType(metadata, offset, bridge)); - auto ffiOwner = std::make_shared(); - ffiOwner->elements.reserve(static_cast(type.arraySize) + 1); - ffi_type* elementFfiType = type.elementType->ffiType != nullptr - ? type.elementType->ffiType - : &ffi_type_pointer; - for (uint16_t i = 0; i < type.arraySize; i++) { - ffiOwner->elements.push_back(elementFfiType); - } - ffiOwner->finalize(); - type.ownedFfiType = ffiOwner; - type.ffiType = &ffiOwner->type; - type.supported = type.elementType->supported; - return type; - } - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: { - type.arraySize = metadata->getArraySize(*offset); - *offset += sizeof(uint16_t); - type.elementType = - std::make_shared( - parseMetadataJsiType(metadata, offset, bridge)); - auto ffiOwner = std::make_shared(); -#if defined(FFI_TYPE_EXT_VECTOR) - ffiOwner->type.type = - kind == metagen::mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_EXT_VECTOR; -#else - ffiOwner->type.type = - kind == metagen::mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_STRUCT; -#endif - ffi_type* elementFfiType = type.elementType->ffiType != nullptr - ? type.elementType->ffiType - : &ffi_type_float; - size_t lanes = std::max(type.arraySize, 1); - size_t abiLanes = lanes == 3 ? 4 : lanes; - size_t elementSize = std::max(elementFfiType->size, sizeof(float)); - size_t elementAlignment = - std::max(elementFfiType->alignment, static_cast(1)); - ffiOwner->elements.reserve(abiLanes + 1); - for (size_t i = 0; i < abiLanes; i++) { - ffiOwner->elements.push_back(elementFfiType); - } - ffiOwner->finalize(); - size_t vectorAlignment = elementAlignment; - if (kind != metagen::mdTypeComplex) { - size_t packedSize = abiLanes * elementSize; - size_t preferredAlignment = packedSize >= 16 ? 16 : packedSize; - vectorAlignment = std::max(vectorAlignment, preferredAlignment); - } - vectorAlignment = std::min(vectorAlignment, 16); - ffiOwner->type.alignment = static_cast(vectorAlignment); - ffiOwner->type.size = alignUp(abiLanes * elementSize, vectorAlignment); - type.ownedFfiType = ffiOwner; - type.ffiType = &ffiOwner->type; - type.supported = type.elementType->supported; - return type; - } - case metagen::mdTypeStruct: { - auto structOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool isUnion = (structOffset & metagen::mdSectionOffsetNext) != 0; - structOffset &= ~metagen::mdSectionOffsetNext; - if (structOffset == MD_SECTION_OFFSET_NULL || bridge == nullptr) { - type.kind = metagen::mdTypePointer; - type.ffiType = &ffi_type_pointer; - type.supported = true; - return type; - } - - MDSectionOffset absoluteOffset = - structOffset + (isUnion ? metadata->unionsOffset : metadata->structsOffset); - type.aggregateOffset = absoluteOffset; - type.aggregateIsUnion = isUnion; - type.aggregateInfo = bridge->aggregateInfoFor(absoluteOffset, isUnion); - type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr - ? &type.aggregateInfo->ffi->type - : nullptr; - type.supported = type.ffiType != nullptr; - return type; - } - case metagen::mdTypePointer: - type.elementType = - std::make_shared( - parseMetadataJsiType(metadata, offset, bridge)); - type.ffiType = &ffi_type_pointer; - type.supported = true; - return type; - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: - type.signatureOffset = metadata->getOffset(*offset) + metadata->signaturesOffset; - *offset += sizeof(MDSectionOffset); - type.ffiType = &ffi_type_pointer; - type.supported = true; - return type; - case metagen::mdTypeClassObject: { - auto classOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool next = (classOffset & metagen::mdSectionOffsetNext) != 0; - while (next) { - auto protocolOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - break; - } - case metagen::mdTypeProtocolObject: { - bool next = true; - while (next) { - auto protocolOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - break; - } - default: - break; - } - - type.ffiType = ffiTypeForJsiKind(kind); - type.supported = type.ffiType != nullptr && isSupportedJsiKind(kind); - return type; -} - -std::shared_ptr NativeApiJsiBridge::aggregateInfoFor( - MDSectionOffset aggregateOffset, bool isUnion) { - if (metadata_ == nullptr || aggregateOffset == MD_SECTION_OFFSET_NULL) { - return nullptr; - } - - auto cached = aggregateInfoByOffset_.find(aggregateOffset); - if (cached != aggregateInfoByOffset_.end()) { - return cached->second; - } - - auto info = std::make_shared(); - info->offset = aggregateOffset; - info->isUnion = isUnion; - aggregateInfoByOffset_[aggregateOffset] = info; - - if (aggregateInfoInProgress_.find(aggregateOffset) != - aggregateInfoInProgress_.end()) { - auto ffiOwner = std::make_shared(); - ffiOwner->elements.push_back(&ffi_type_pointer); - ffiOwner->finalize(); - info->ffi = ffiOwner; - return info; - } - - aggregateInfoInProgress_.insert(aggregateOffset); - - MDSectionOffset offset = aggregateOffset; - const char* name = metadata_->getString(offset); - info->name = name != nullptr ? name : ""; - offset += sizeof(MDSectionOffset); - info->size = metadata_->getArraySize(offset); - offset += sizeof(uint16_t); - - bool next = true; - while (next) { - MDSectionOffset nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - if (nameOffset == MD_SECTION_OFFSET_NULL) { - break; - } - - NativeApiJsiAggregateField field; - const char* fieldName = metadata_->resolveString(nameOffset); - field.name = fieldName != nullptr ? fieldName : ""; - if (!isUnion) { - field.offset = metadata_->getArraySize(offset); - offset += sizeof(uint16_t); - } - field.type = parseMetadataJsiType(metadata_.get(), &offset, this); - info->fields.push_back(std::move(field)); - } - - auto ffiOwner = std::make_shared(); - if (isUnion) { - ffi_type* largest = &ffi_type_uint8; - size_t largestSize = 0; - for (const auto& field : info->fields) { - size_t fieldSize = nativeSizeForType(field.type); - if (field.type.ffiType != nullptr && fieldSize >= largestSize) { - largest = field.type.ffiType; - largestSize = fieldSize; - } - } - ffiOwner->elements.push_back(largest); - } else { - for (const auto& field : info->fields) { - ffiOwner->elements.push_back(field.type.ffiType != nullptr - ? field.type.ffiType - : &ffi_type_pointer); - } - if (ffiOwner->elements.empty()) { - ffiOwner->elements.push_back(&ffi_type_uint8); - } - } - ffiOwner->finalize(); - info->ffi = ffiOwner; - aggregateInfoInProgress_.erase(aggregateOffset); - return info; -} - -ffi_type* ffiTypeForJsiArgument(const NativeApiJsiType& type) { - switch (type.kind) { - case metagen::mdTypeArray: - return &ffi_type_pointer; - default: - return type.ffiType != nullptr ? type.ffiType : &ffi_type_pointer; - } -} - -std::optional parseMetadataJsiSignature( - MDMetadataReader* metadata, MDSectionOffset signatureOffset, - unsigned int implicitArgumentCount, NativeApiJsiBridge* bridge, - bool returnOwned = false) { - if (metadata == nullptr || signatureOffset == MD_SECTION_OFFSET_NULL) { - return std::nullopt; - } - - NativeApiJsiSignature signature; - signature.implicitArgumentCount = implicitArgumentCount; - - MDSectionOffset offset = signatureOffset; - MDTypeKind returnKind = metadata->getTypeKind(offset); - uint32_t returnKindRaw = rawTypeKind(returnKind); - bool next = - (returnKindRaw & static_cast(metagen::mdTypeFlagNext)) != 0; - signature.variadic = - (returnKindRaw & static_cast(metagen::mdTypeFlagVariadic)) != 0; - signature.returnType = parseMetadataJsiType(metadata, &offset, bridge); - signature.returnType.returnOwned = returnOwned; - - while (next) { - MDTypeKind argKind = metadata->getTypeKind(offset); - next = (rawTypeKind(argKind) & - static_cast(metagen::mdTypeFlagNext)) != 0; - signature.argumentTypes.push_back(parseMetadataJsiType(metadata, &offset, bridge)); - } - - signature.ffiTypes.reserve(signature.argumentTypes.size() + - implicitArgumentCount); - for (unsigned int i = 0; i < implicitArgumentCount; i++) { - signature.ffiTypes.push_back(&ffi_type_pointer); - } - for (const auto& argType : signature.argumentTypes) { - signature.ffiTypes.push_back(ffiTypeForJsiArgument(argType)); - } - - ffi_status status = ffi_prep_cif( - &signature.cif, FFI_DEFAULT_ABI, - static_cast(signature.ffiTypes.size()), - signature.returnType.ffiType != nullptr ? signature.returnType.ffiType - : &ffi_type_void, - signature.ffiTypes.empty() ? nullptr : signature.ffiTypes.data()); - signature.prepared = status == FFI_OK; - return signature; -} - -const char* skipObjCTypeQualifiers(const char* encoding) { - while (encoding != nullptr && *encoding != '\0' && - std::strchr("rnNoORV", *encoding) != nullptr) { - encoding++; - } - return encoding; -} - -const char* skipObjCTypeFieldName(const char* encoding, std::string* name) { - if (encoding == nullptr || *encoding != '"') { - return encoding; - } - - encoding++; - const char* start = encoding; - while (*encoding != '\0' && *encoding != '"') { - encoding++; - } - if (name != nullptr) { - *name = std::string(start, static_cast(encoding - start)); - } - return *encoding == '"' ? encoding + 1 : encoding; -} - -std::string normalizedObjCAggregateName(std::string name) { - if (!name.empty() && name.front() == '_') { - name.erase(name.begin()); - } - return name; -} - -std::vector knownObjCAggregateFieldNames( - const std::string& aggregateName, size_t fieldCount) { - std::string name = normalizedObjCAggregateName(aggregateName); - std::vector fields; - if (name == "CGPoint" || name == "NSPoint") { - fields = {"x", "y"}; - } else if (name == "CGSize" || name == "NSSize") { - fields = {"width", "height"}; - } else if (name == "CGRect" || name == "NSRect") { - fields = {"origin", "size"}; - } else if (name == "CGVector") { - fields = {"dx", "dy"}; - } else if (name == "UIEdgeInsets" || name == "NSEdgeInsets") { - fields = {"top", "left", "bottom", "right"}; - } else if (name == "NSDirectionalEdgeInsets") { - fields = {"top", "leading", "bottom", "trailing"}; - } else if (name == "NSRange" || name == "CFRange") { - fields = {"location", "length"}; - } else if (name == "CGAffineTransform") { - fields = {"a", "b", "c", "d", "tx", "ty"}; - } else if (name == "CATransform3D") { - fields = {"m11", "m12", "m13", "m14", "m21", "m22", "m23", "m24", - "m31", "m32", "m33", "m34", "m41", "m42", "m43", "m44"}; - } - - if (fields.size() != fieldCount) { - fields.clear(); - } - return fields; -} - -const NativeApiSymbol* findObjCAggregateSymbol( - NativeApiJsiBridge* bridge, const std::string& name, bool isUnion) { - if (bridge == nullptr || name.empty()) { - return nullptr; - } - - std::vector candidates; - candidates.push_back(name); - std::string normalized = normalizedObjCAggregateName(name); - if (normalized != name) { - candidates.push_back(normalized); - } else { - candidates.push_back("_" + name); - } - constexpr const char* suffix = "Struct"; - if (normalized.size() > std::strlen(suffix) && - normalized.compare(normalized.size() - std::strlen(suffix), - std::strlen(suffix), suffix) == 0) { - candidates.push_back( - normalized.substr(0, normalized.size() - std::strlen(suffix))); - } else { - candidates.push_back(normalized + suffix); - } - - for (const auto& candidate : candidates) { - const NativeApiSymbol* symbol = - isUnion ? bridge->findUnion(candidate) : bridge->findStruct(candidate); - if (symbol == nullptr) { - symbol = bridge->findAggregate(candidate); - } - if (symbol != nullptr) { - return symbol; - } - } - - return nullptr; -} - -void applyObjCEncodingSizeAndAlignment(const char* encoding, - NativeApiJsiFfiType* ffiType, - uint16_t* sizeOut = nullptr) { - if (encoding == nullptr || ffiType == nullptr) { - return; - } - - NSUInteger size = 0; - NSUInteger alignment = 0; - NSGetSizeAndAlignment(encoding, &size, &alignment); - if (size > 0) { - ffiType->type.size = static_cast(size); - if (sizeOut != nullptr) { - *sizeOut = static_cast(std::min( - size, static_cast(std::numeric_limits::max()))); - } - } - if (alignment > 0) { - ffiType->type.alignment = static_cast(alignment); - } -} - -NativeApiJsiType parseObjCEncodedJsiType( - const char* encoding, NativeApiJsiBridge* bridge = nullptr, - const char** endEncoding = nullptr); - -bool unsupportedJsiType(const NativeApiJsiType& type); - -NativeApiJsiType parseObjCEncodedAggregateJsiType( - const char* encoding, NativeApiJsiBridge* bridge, const char** endEncoding) { - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - - const bool isUnion = *encoding == '('; - const char close = isUnion ? ')' : '}'; - const char* cursor = encoding + 1; - const char* nameStart = cursor; - while (*cursor != '\0' && *cursor != '=' && *cursor != close) { - cursor++; - } - std::string aggregateName(nameStart, static_cast(cursor - nameStart)); - - if (const NativeApiSymbol* symbol = - findObjCAggregateSymbol(bridge, aggregateName, isUnion)) { - type.aggregateOffset = symbol->offset; - type.aggregateIsUnion = symbol->kind == NativeApiSymbolKind::Union; - type.aggregateInfo = bridge->aggregateInfoFor(*symbol); - type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr - ? &type.aggregateInfo->ffi->type - : nullptr; - type.supported = type.ffiType != nullptr; - - int depth = 0; - const char* end = encoding; - do { - if (*end == *encoding) { - depth++; - } else if (*end == close) { - depth--; - } - end++; - } while (*end != '\0' && depth > 0); - if (endEncoding != nullptr) { - *endEncoding = end; - } - return type; - } - - auto info = std::make_shared(); - info->name = aggregateName; - info->isUnion = isUnion; - info->offset = MD_SECTION_OFFSET_NULL; - - if (*cursor == '=') { - cursor++; - } - - size_t computedOffset = 0; - size_t maxFieldSize = 0; - size_t fieldIndex = 0; - while (*cursor != '\0' && *cursor != close) { - NativeApiJsiAggregateField field; - std::string encodedFieldName; - cursor = skipObjCTypeFieldName(cursor, &encodedFieldName); - const char* fieldStart = cursor; - const char* fieldEnd = cursor; - field.type = parseObjCEncodedJsiType(cursor, bridge, &fieldEnd); - if (fieldEnd == fieldStart || unsupportedJsiType(field.type)) { - type.supported = false; - type.ffiType = nullptr; - if (endEncoding != nullptr) { - *endEncoding = fieldEnd; - } - return type; - } - - NSUInteger fieldSize = 0; - NSUInteger fieldAlignment = 0; - NSGetSizeAndAlignment(fieldStart, &fieldSize, &fieldAlignment); - size_t nativeFieldSize = - fieldSize > 0 ? static_cast(fieldSize) - : nativeSizeForType(field.type); - size_t nativeFieldAlignment = - fieldAlignment > 0 ? static_cast(fieldAlignment) - : std::max(1, field.type.ffiType != nullptr - ? field.type.ffiType->alignment - : 1); - if (isUnion) { - field.offset = 0; - maxFieldSize = std::max(maxFieldSize, nativeFieldSize); - } else { - computedOffset = alignUp(computedOffset, nativeFieldAlignment); - field.offset = static_cast(std::min( - computedOffset, std::numeric_limits::max())); - computedOffset += nativeFieldSize; - } - field.name = !encodedFieldName.empty() - ? encodedFieldName - : "field" + std::to_string(fieldIndex); - info->fields.push_back(std::move(field)); - fieldIndex++; - cursor = fieldEnd; - } - - if (*cursor == close) { - cursor++; - } - if (endEncoding != nullptr) { - *endEncoding = cursor; - } - - auto knownNames = knownObjCAggregateFieldNames(aggregateName, info->fields.size()); - for (size_t i = 0; i < knownNames.size(); i++) { - info->fields[i].name = knownNames[i]; - } - - auto ffiOwner = std::make_shared(); - if (isUnion) { - ffi_type* largest = &ffi_type_uint8; - size_t largestSize = 0; - for (const auto& field : info->fields) { - size_t fieldSize = nativeSizeForType(field.type); - if (field.type.ffiType != nullptr && fieldSize >= largestSize) { - largest = field.type.ffiType; - largestSize = fieldSize; - } - } - ffiOwner->elements.push_back(largest); - } else { - for (const auto& field : info->fields) { - ffiOwner->elements.push_back(field.type.ffiType != nullptr - ? field.type.ffiType - : &ffi_type_pointer); - } - } - if (ffiOwner->elements.empty()) { - ffiOwner->elements.push_back(&ffi_type_uint8); - } - ffiOwner->finalize(); - applyObjCEncodingSizeAndAlignment(encoding, ffiOwner.get(), &info->size); - if (info->size == 0) { - info->size = static_cast(std::min( - isUnion ? maxFieldSize : computedOffset, - std::numeric_limits::max())); - } - - info->ffi = ffiOwner; - type.aggregateInfo = info; - type.aggregateOffset = MD_SECTION_OFFSET_NULL; - type.aggregateIsUnion = isUnion; - type.ownedFfiType = ffiOwner; - type.ffiType = &ffiOwner->type; - type.supported = true; - return type; -} - -NativeApiJsiType parseObjCEncodedArrayJsiType( - const char* encoding, NativeApiJsiBridge* bridge, const char** endEncoding) { - NativeApiJsiType type; - type.kind = metagen::mdTypeArray; - - const char* cursor = encoding + 1; - uint16_t count = 0; - while (*cursor >= '0' && *cursor <= '9') { - count = static_cast( - std::min(std::numeric_limits::max(), - (count * 10) + (*cursor - '0'))); - cursor++; - } - type.arraySize = count; - - const char* elementEnd = cursor; - type.elementType = std::make_shared( - parseObjCEncodedJsiType(cursor, bridge, &elementEnd)); - cursor = elementEnd; - if (*cursor == ']') { - cursor++; - } - if (endEncoding != nullptr) { - *endEncoding = cursor; - } - - auto ffiOwner = std::make_shared(); - ffi_type* elementFfiType = - type.elementType != nullptr && type.elementType->ffiType != nullptr - ? type.elementType->ffiType - : &ffi_type_pointer; - for (uint16_t i = 0; i < count; i++) { - ffiOwner->elements.push_back(elementFfiType); - } - if (ffiOwner->elements.empty()) { - ffiOwner->elements.push_back(&ffi_type_uint8); - } - ffiOwner->finalize(); - applyObjCEncodingSizeAndAlignment(encoding, ffiOwner.get()); - - type.ownedFfiType = ffiOwner; - type.ffiType = &ffiOwner->type; - type.supported = type.elementType != nullptr && type.elementType->supported; - return type; -} - -NativeApiJsiType parseObjCEncodedJsiType( - const char* encoding, NativeApiJsiBridge* bridge, const char** endEncoding) { - encoding = skipObjCTypeQualifiers(encoding); - NativeApiJsiType type; - - if (encoding == nullptr || *encoding == '\0') { - type.kind = metagen::mdTypePointer; - type.ffiType = &ffi_type_pointer; - if (endEncoding != nullptr) { - *endEncoding = encoding; - } - return type; - } - - auto finishPrimitive = [&](const char* end) { - type.ffiType = ffiTypeForJsiKind(type.kind); - type.supported = type.ffiType != nullptr; - if (endEncoding != nullptr) { - *endEncoding = end; - } - return type; - }; - - switch (*encoding) { - case 'c': - type.kind = metagen::mdTypeChar; - break; - case 'i': - type.kind = metagen::mdTypeSInt; - break; - case 's': - type.kind = metagen::mdTypeSShort; - break; - case 'l': - case 'q': - type.kind = metagen::mdTypeSInt64; - break; - case 'C': - type.kind = metagen::mdTypeUInt8; - break; - case 'I': - type.kind = metagen::mdTypeUInt; - break; - case 'S': - type.kind = metagen::mdTypeUShort; - break; - case 'L': - case 'Q': - type.kind = metagen::mdTypeUInt64; - break; - case 'f': - type.kind = metagen::mdTypeFloat; - break; - case 'd': - type.kind = metagen::mdTypeDouble; - break; - case 'B': - type.kind = metagen::mdTypeBool; - break; - case 'v': - type.kind = metagen::mdTypeVoid; - break; - case '*': - type.kind = metagen::mdTypeString; - break; - case '@': - if (encoding[1] == '?') { - type.kind = metagen::mdTypeBlock; - return finishPrimitive(encoding + 2); - } - { - const char* objectEnd = encoding + 1; - if (*objectEnd == '"') { - objectEnd++; - while (*objectEnd != '\0' && *objectEnd != '"') { - objectEnd++; - } - if (*objectEnd == '"') { - objectEnd++; - } - } - if (std::strncmp(encoding, "@\"NSString\"", 11) == 0) { - type.kind = metagen::mdTypeNSStringObject; - } else if (std::strncmp(encoding, "@\"NSMutableString\"", 18) == 0) { - type.kind = metagen::mdTypeNSMutableStringObject; - } else { - type.kind = metagen::mdTypeAnyObject; - } - return finishPrimitive(objectEnd); - } - case '#': - type.kind = metagen::mdTypeClass; - break; - case ':': - type.kind = metagen::mdTypeSelector; - break; - case '^': - type.kind = metagen::mdTypePointer; - { - const char* elementEnd = encoding + 1; - type.elementType = std::make_shared( - parseObjCEncodedJsiType(encoding + 1, bridge, &elementEnd)); - type.ffiType = &ffi_type_pointer; - type.supported = true; - if (elementEnd == encoding + 1 && encoding[1] != '\0') { - elementEnd = encoding + 2; - } - if (endEncoding != nullptr) { - *endEncoding = elementEnd; - } - } - return type; - case '{': - case '(': - return parseObjCEncodedAggregateJsiType(encoding, bridge, endEncoding); - case '[': - return parseObjCEncodedArrayJsiType(encoding, bridge, endEncoding); - case 'b': { - type.kind = metagen::mdTypeUInt; - const char* cursor = encoding + 1; - while (*cursor >= '0' && *cursor <= '9') { - cursor++; - } - return finishPrimitive(cursor); - } - case '?': - type.kind = metagen::mdTypeOpaquePointer; - break; - default: - type.kind = metagen::mdTypePointer; - break; - } - - return finishPrimitive(encoding + 1); -} - -std::optional parseObjCMethodJsiSignature( - Method method, NativeApiJsiBridge* bridge = nullptr) { - if (method == nullptr) { - return std::nullopt; - } - - NativeApiJsiSignature signature; - signature.implicitArgumentCount = 2; - - char* returnEncoding = method_copyReturnType(method); - signature.returnType = parseObjCEncodedJsiType(returnEncoding, bridge); - if (returnEncoding != nullptr) { - free(returnEncoding); - } - - unsigned int totalArgc = method_getNumberOfArguments(method); - for (unsigned int i = 2; i < totalArgc; i++) { - char* argEncoding = method_copyArgumentType(method, i); - signature.argumentTypes.push_back(parseObjCEncodedJsiType(argEncoding, bridge)); - if (argEncoding != nullptr) { - free(argEncoding); - } - } - - signature.ffiTypes.reserve(totalArgc); - signature.ffiTypes.push_back(&ffi_type_pointer); - signature.ffiTypes.push_back(&ffi_type_pointer); - for (const auto& argType : signature.argumentTypes) { - signature.ffiTypes.push_back(ffiTypeForJsiArgument(argType)); - } - - ffi_status status = ffi_prep_cif( - &signature.cif, FFI_DEFAULT_ABI, - static_cast(signature.ffiTypes.size()), - signature.returnType.ffiType != nullptr ? signature.returnType.ffiType - : &ffi_type_void, - signature.ffiTypes.data()); - signature.prepared = status == FFI_OK; - return signature; -} - -bool prepareJsiMethodSignature(NativeApiJsiSignature* signature) { - if (signature == nullptr) { - return false; - } - signature->implicitArgumentCount = 2; - signature->ffiTypes.clear(); - signature->ffiTypes.reserve(signature->argumentTypes.size() + 2); - signature->ffiTypes.push_back(&ffi_type_pointer); - signature->ffiTypes.push_back(&ffi_type_pointer); - for (const auto& argType : signature->argumentTypes) { - ffi_type* ffiType = ffiTypeForJsiArgument(argType); - if (ffiType == nullptr) { - signature->prepared = false; - return false; - } - signature->ffiTypes.push_back(ffiType); - } - ffi_type* returnFfiType = - signature->returnType.ffiType != nullptr ? signature->returnType.ffiType - : &ffi_type_void; - signature->prepared = - ffi_prep_cif(&signature->cif, FFI_DEFAULT_ABI, - static_cast(signature->ffiTypes.size()), - returnFfiType, signature->ffiTypes.data()) == FFI_OK; - return signature->prepared; -} - -bool reconcileObjCMethodRuntimeSignature(NativeApiJsiSignature* signature, - const NativeApiJsiSignature& runtime) { - if (signature == nullptr || - signature->argumentTypes.size() != runtime.argumentTypes.size()) { - return false; - } - - bool changed = false; - for (size_t i = 0; i < signature->argumentTypes.size(); i++) { - NativeApiJsiType& metadataType = signature->argumentTypes[i]; - const NativeApiJsiType& runtimeType = runtime.argumentTypes[i]; - if (runtimeType.kind == metagen::mdTypeBlock && - metadataType.kind == metagen::mdTypeFunctionPointer) { - metadataType.kind = metagen::mdTypeBlock; - metadataType.ffiType = runtimeType.ffiType; - metadataType.supported = runtimeType.supported; - changed = true; - } - } - - return !changed || prepareJsiMethodSignature(signature); -} - -bool unsupportedJsiType(const NativeApiJsiType& type) { - if (type.kind == metagen::mdTypeStruct && type.aggregateInfo != nullptr && - type.aggregateInfo->ffi != nullptr) { - return false; - } - return !type.supported || type.ffiType == nullptr; -} - -bool signatureSupportedForJsiCallback(const NativeApiJsiSignature& signature) { - if (!signature.prepared || signature.variadic || - unsupportedJsiType(signature.returnType)) { - return false; - } - for (const auto& argType : signature.argumentTypes) { - if (unsupportedJsiType(argType)) { - return false; - } - } - return true; -} - -std::shared_ptr createJsiCallback( - Runtime& runtime, const std::shared_ptr& bridge, - const NativeApiJsiType& type, Function function, bool block, - NativeApiJsiCallbackThreadPolicy threadPolicy = - NativeApiJsiCallbackThreadPolicy::Default) { - if (bridge == nullptr || bridge->metadata() == nullptr || - type.signatureOffset == MD_SECTION_OFFSET_NULL) { - throw facebook::jsi::JSError( - runtime, "Native callback metadata is unavailable."); - } - - auto parsed = parseMetadataJsiSignature( - bridge->metadata(), type.signatureOffset, block ? 1 : 0, bridge.get()); - if (!parsed || !signatureSupportedForJsiCallback(*parsed)) { - throw facebook::jsi::JSError( - runtime, "Native callback signature is not supported by pure JSI."); - } - - auto signature = - std::make_shared(std::move(*parsed)); - auto callback = std::make_shared( - runtime, bridge, std::move(signature), std::move(function), block, - threadPolicy); - if (!block) { - bridge->retainJsiLifetime(callback); - } - return callback; -} - -std::shared_ptr createJsiMethodCallback( - Runtime& runtime, const std::shared_ptr& bridge, - const std::string& selectorName, MDSectionOffset signatureOffset, - Function function, bool returnOwned) { - if (bridge == nullptr || bridge->metadata() == nullptr || - signatureOffset == MD_SECTION_OFFSET_NULL) { - throw facebook::jsi::JSError( - runtime, "Native method callback metadata is unavailable."); - } - - auto parsed = parseMetadataJsiSignature( - bridge->metadata(), signatureOffset, 2, bridge.get(), returnOwned); - if (!parsed || !signatureSupportedForJsiCallback(*parsed)) { - throw facebook::jsi::JSError( - runtime, "Native method callback signature is not supported by pure JSI."); - } - parsed->selectorName = selectorName; - - auto signature = - std::make_shared(std::move(*parsed)); - auto threadPolicy = readJsiCallbackThreadPolicy(runtime, function); - auto callback = std::make_shared( - runtime, bridge, std::move(signature), std::move(function), false, - threadPolicy, true); - bridge->retainJsiLifetime(callback); - return callback; -} - -std::shared_ptr createJsiMethodCallback( - Runtime& runtime, const std::shared_ptr& bridge, - const std::string& selectorName, NativeApiJsiSignature signature, - Function function) { - signature.selectorName = selectorName; - prepareJsiMethodSignature(&signature); - if (!signatureSupportedForJsiCallback(signature)) { - throw facebook::jsi::JSError( - runtime, "Native method callback signature is not supported by pure JSI."); - } - - auto sharedSignature = - std::make_shared(std::move(signature)); - auto threadPolicy = readJsiCallbackThreadPolicy(runtime, function); - auto callback = std::make_shared( - runtime, bridge, std::move(sharedSignature), std::move(function), false, - threadPolicy, true); - bridge->retainJsiLifetime(callback); - return callback; -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h b/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h deleted file mode 100644 index 4a29b60f0..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h +++ /dev/null @@ -1,733 +0,0 @@ -std::string readOptionalStringProperty(Runtime& runtime, const Object& object, const char* name) { - if (name == nullptr || !object.hasProperty(runtime, name)) { - return ""; - } - Value value = object.getProperty(runtime, name); - return value.isString() ? value.asString(runtime).utf8(runtime) : ""; -} - -struct NativeApiJsiClassBuilderRegistration { - std::shared_ptr runtimeOwner; - Runtime* runtime = nullptr; - std::shared_ptr bridge; -}; - -std::mutex gNativeApiJsiClassBuilderMutex; -std::unordered_map gNativeApiJsiClassBuilders; -struct NativeApiJsiKnownExposedMethod { - std::string selectorName; - NativeApiJsiSignature signature; -}; -std::mutex gNativeApiJsiKnownExposedMethodsMutex; -std::unordered_map gNativeApiJsiKnownExposedMethods; - -void rememberNativeApiJsiClassBuilder(Runtime& runtime, - const std::shared_ptr& bridge, - Class cls) { - if (cls == Nil) { - return; - } - std::lock_guard lock(gNativeApiJsiClassBuilderMutex); - auto runtimeOwner = retainNativeApiJsiRuntime(runtime); - gNativeApiJsiClassBuilders[cls] = NativeApiJsiClassBuilderRegistration{ - .runtimeOwner = runtimeOwner, - .runtime = runtimeOwner.get(), - .bridge = bridge, - }; -} - -void rememberNativeApiJsiKnownExposedMethod(const std::string& selectorName, - const NativeApiJsiSignature& signature) { - if (selectorName.empty()) { - return; - } - NativeApiJsiKnownExposedMethod method{ - .selectorName = selectorName, - .signature = signature, - }; - std::lock_guard lock(gNativeApiJsiKnownExposedMethodsMutex); - gNativeApiJsiKnownExposedMethods[selectorName] = method; - gNativeApiJsiKnownExposedMethods[jsifySelector(selectorName.c_str())] = std::move(method); -} - -std::optional knownNativeApiJsiExposedMethod( - const std::string& name) { - std::lock_guard lock(gNativeApiJsiKnownExposedMethodsMutex); - auto it = gNativeApiJsiKnownExposedMethods.find(name); - if (it == gNativeApiJsiKnownExposedMethods.end()) { - return std::nullopt; - } - NativeApiJsiKnownExposedMethod method = it->second; - prepareJsiMethodSignature(&method.signature); - return method; -} - -std::optional findNativeApiJsiClassBuilder(id object) { - Class cls = object != nil ? object_getClass(object) : Nil; - std::lock_guard lock(gNativeApiJsiClassBuilderMutex); - while (cls != Nil) { - auto it = gNativeApiJsiClassBuilders.find(cls); - if (it != gNativeApiJsiClassBuilders.end()) { - return it->second; - } - cls = class_getSuperclass(cls); - } - return std::nullopt; -} - -const char* nativeApiJsiFastEnumerationEncoding() { - static const char* encoding = nullptr; - if (encoding == nullptr) { - struct objc_method_description desc = protocol_getMethodDescription( - @protocol(NSFastEnumeration), @selector(countByEnumeratingWithState:objects:count:), YES, - YES); - encoding = desc.types; - } - return encoding; -} - -NSUInteger nativeApiJsiSymbolIteratorCountByEnumerating(id self, SEL, NSFastEnumerationState* state, - id __unsafe_unretained stackbuf[], - NSUInteger len) { - if (len == 0 || state == nullptr || stackbuf == nullptr) { - return 0; - } - - auto registration = findNativeApiJsiClassBuilder(self); - if (!registration || registration->runtime == nullptr || registration->bridge == nullptr) { - return 0; - } - - Runtime& runtime = *registration->runtime; - NativeApiJsiRuntimeScope runtimeScope(runtime); - auto bridge = registration->bridge; - try { - Value receiver = makeNativeObjectValue(runtime, bridge, self, false); - if (!receiver.isObject()) { - return 0; - } - - Value iteratorFactoryValue = - runtime.global().getProperty(runtime, "__nativeScriptCreateNativeApiIterator"); - if (!iteratorFactoryValue.isObject() || - !iteratorFactoryValue.asObject(runtime).isFunction(runtime)) { - return 0; - } - - Function iteratorFactory = iteratorFactoryValue.asObject(runtime).asFunction(runtime); - Value prototype = bridge->findClassPrototype(runtime, object_getClass(self)); - Value iteratorValue = - prototype.isObject() - ? iteratorFactory.call(runtime, Value(runtime, receiver), Value(runtime, prototype)) - : iteratorFactory.call(runtime, Value(runtime, receiver)); - if (!iteratorValue.isObject()) { - return 0; - } - Object iterator = iteratorValue.asObject(runtime); - Value nextValue = iterator.getProperty(runtime, "next"); - if (!nextValue.isObject() || !nextValue.asObject(runtime).isFunction(runtime)) { - return 0; - } - Function next = nextValue.asObject(runtime).asFunction(runtime); - - auto callNext = [&]() -> Value { return next.callWithThis(runtime, iterator); }; - - for (unsigned long skipped = 0; skipped < state->state; skipped++) { - Value skippedResult = callNext(); - if (!skippedResult.isObject()) { - return 0; - } - Value doneValue = skippedResult.asObject(runtime).getProperty(runtime, "done"); - if (doneValue.isBool() && doneValue.getBool()) { - return 0; - } - } - - NSUInteger count = 0; - while (count < len) { - Value nextResult = callNext(); - if (!nextResult.isObject()) { - break; - } - Object nextObject = nextResult.asObject(runtime); - Value doneValue = nextObject.getProperty(runtime, "done"); - if (doneValue.isBool() && doneValue.getBool()) { - break; - } - - Value value = nextObject.getProperty(runtime, "value"); - NativeApiJsiArgumentFrame frame(1); - id nativeValue = objectFromJsiValue(runtime, bridge, value, frame, false); - if (nativeValue != nil) { - [nativeValue retain]; - [nativeValue autorelease]; - } - stackbuf[count++] = nativeValue; - } - - state->itemsPtr = stackbuf; - state->mutationsPtr = &state->extra[0]; - state->extra[0] = 0; - state->state += count; - return count; - } catch (const std::exception&) { - return 0; - } -} - -NativeApiSymbol runtimeSymbolForClass(const std::shared_ptr& bridge, - Class cls) { - if (bridge != nullptr) { - if (const NativeApiSymbol* symbol = bridge->findClassForRuntimeClass(cls)) { - return *symbol; - } - } - - const char* name = cls != Nil ? class_getName(cls) : ""; - return NativeApiSymbol{ - .kind = NativeApiSymbolKind::Class, - .offset = MD_SECTION_OFFSET_NULL, - .name = name != nullptr ? name : "", - .runtimeName = name != nullptr ? name : "", - }; -} - -std::string nextAvailableJsiClassName(const std::string& requestedName) { - if (requestedName.empty()) { - return ""; - } - if (objc_lookUpClass(requestedName.c_str()) == Nil) { - return requestedName; - } - - size_t suffix = 1; - std::string candidate; - do { - candidate = requestedName + std::to_string(suffix++); - } while (objc_lookUpClass(candidate.c_str()) != Nil); - return candidate; -} - -std::vector methodOverridesForName(const std::vector& members, - const std::string& name) { - std::vector result; - std::unordered_set selectors; - for (const auto& member : members) { - if (member.property || member.name != name || (member.flags & metagen::mdMemberStatic) != 0 || - member.selectorName.empty()) { - continue; - } - if (selectors.insert(member.selectorName).second) { - result.push_back(member); - } - } - return result; -} - -const NativeApiMember* propertyOverrideForName(const std::vector& members, - const std::string& name) { - const NativeApiMember* fallback = nullptr; - for (const auto& member : members) { - if (member.property && member.name == name && (member.flags & metagen::mdMemberStatic) == 0) { - if (fallback == nullptr) { - fallback = &member; - } - if (!member.readonly && !member.setterSelectorName.empty()) { - return &member; - } - } - } - return fallback; -} - -void addJsiOverrideMethod(Runtime& runtime, const std::shared_ptr& bridge, - Class nativeClass, Class baseClass, const std::string& selectorName, - MDSectionOffset signatureOffset, bool returnOwned, Function function) { - if (selectorName.empty() || signatureOffset == MD_SECTION_OFFSET_NULL) { - return; - } - - auto callback = createJsiMethodCallback(runtime, bridge, selectorName, signatureOffset, - std::move(function), returnOwned); - SEL selector = sel_registerName(selectorName.c_str()); - std::string metadataEncoding = objcMethodSignatureForJsiSignature(callback->signature()); - class_replaceMethod(nativeClass, selector, reinterpret_cast(callback->functionPointer()), - metadataEncoding.c_str()); -} - -Value getObjectPropertyOrUndefined(Runtime& runtime, const Object& object, - const std::string& name) { - return object.hasProperty(runtime, name.c_str()) ? object.getProperty(runtime, name.c_str()) - : Value::undefined(); -} - -Class dispatchSuperclassForJsiDerivedReceiver(id receiver, Class fallback) { - if (receiver == nil) { - return Nil; - } - - Class receiverClass = object_getClass(receiver); - if (receiverClass == Nil || - !class_conformsToProtocol(receiverClass, @protocol(NativeApiJsiClassBuilderProtocol))) { - return Nil; - } - - Class superclass = class_getSuperclass(receiverClass); - return superclass != Nil ? superclass : fallback; -} - -std::optional functionForSelector(Runtime& runtime, const Object& methods, - const std::string& selectorName) { - Value value = getObjectPropertyOrUndefined(runtime, methods, selectorName); - if (!value.isObject() || !value.asObject(runtime).isFunction(runtime)) { - std::string jsName = jsifySelector(selectorName.c_str()); - if (jsName != selectorName) { - value = getObjectPropertyOrUndefined(runtime, methods, jsName); - } - } - if (!value.isObject() || !value.asObject(runtime).isFunction(runtime)) { - return std::nullopt; - } - return value.asObject(runtime).asFunction(runtime); -} - -std::optional readExposedType(Runtime& runtime, - const std::shared_ptr& bridge, - const Object& descriptor, - const char* propertyName) { - if (!descriptor.hasProperty(runtime, propertyName)) { - return std::nullopt; - } - return interopTypeFromValue(runtime, bridge, descriptor.getProperty(runtime, propertyName)); -} - -std::optional exposedMethodSignature( - Runtime& runtime, const std::shared_ptr& bridge, - const std::string& selectorName, const Object& descriptor) { - NativeApiJsiSignature signature; - if (auto returnType = readExposedType(runtime, bridge, descriptor, "returns")) { - signature.returnType = *returnType; - } else { - signature.returnType = primitiveInteropType(metagen::mdTypeVoid); - } - - Value paramsValue = getObjectPropertyOrUndefined(runtime, descriptor, "params"); - if (!paramsValue.isUndefined() && !paramsValue.isNull()) { - if (!paramsValue.isObject() || !paramsValue.asObject(runtime).isArray(runtime)) { - throw facebook::jsi::JSError(runtime, "exposedMethods params must be an array."); - } - Array params = paramsValue.asObject(runtime).getArray(runtime); - for (size_t i = 0; i < params.size(runtime); i++) { - Value typeValue = params.getValueAtIndex(runtime, i); - auto type = interopTypeFromValue(runtime, bridge, typeValue); - if (!type) { - throw facebook::jsi::JSError(runtime, - "exposedMethods contains an unsupported parameter type."); - } - signature.argumentTypes.push_back(*type); - } - } - - // A colon-less selector may still declare params (@nativescript/core - // exposes onReceive with one NSNotification param); the Objective-C runtime - // accepts such methods and callers like NSNotificationCenter invoke them - // with the argument. Only reject a mismatch when the selector explicitly - // declares argument slots. - size_t selectorArguments = selectorArgumentCount(selectorName); - if (selectorArguments != signature.argumentTypes.size() && selectorArguments != 0) { - throw facebook::jsi::JSError(runtime, - "exposedMethods selector argument count does not match params."); - } - - prepareJsiMethodSignature(&signature); - return signature; -} - -std::optional runtimeProtocolMethodSignature(const char* types) { - if (types == nullptr) { - return std::nullopt; - } - - NSMethodSignature* methodSignature = [NSMethodSignature signatureWithObjCTypes:types]; - if (methodSignature == nil || methodSignature.numberOfArguments < 2) { - return std::nullopt; - } - - NativeApiJsiSignature signature; - signature.implicitArgumentCount = 2; - signature.returnType = parseObjCEncodedJsiType(methodSignature.methodReturnType); - for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) { - signature.argumentTypes.push_back( - parseObjCEncodedJsiType([methodSignature getArgumentTypeAtIndex:i])); - } - if (unsupportedJsiType(signature.returnType)) { - return std::nullopt; - } - for (const auto& argumentType : signature.argumentTypes) { - if (unsupportedJsiType(argumentType)) { - return std::nullopt; - } - } - return signature; -} - -std::optional protocolSymbolFromJsiValue( - Runtime& runtime, const std::shared_ptr& bridge, const Value& value) { - if (value.isString()) { - std::string name = value.asString(runtime).utf8(runtime); - if (const NativeApiSymbol* symbol = bridge->findProtocol(name)) { - return *symbol; - } - return std::nullopt; - } - if (!value.isObject()) { - return std::nullopt; - } - - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->symbol(); - } - - if (stringPropertyOrEmpty(runtime, object, "kind") != "protocol") { - return std::nullopt; - } - - std::string runtimeName = stringPropertyOrEmpty(runtime, object, "runtimeName"); - if (!runtimeName.empty()) { - if (const NativeApiSymbol* symbol = bridge->findProtocol(runtimeName)) { - return *symbol; - } - } - - std::string name = stringPropertyOrEmpty(runtime, object, "name"); - if (!name.empty()) { - if (const NativeApiSymbol* symbol = bridge->findProtocol(name)) { - return *symbol; - } - } - - return std::nullopt; -} - -void addJsiExposedMethod(Runtime& runtime, const std::shared_ptr& bridge, - Class nativeClass, const std::string& selectorName, - NativeApiJsiSignature signature, Function function) { - if (selectorName.empty()) { - return; - } - auto callback = createJsiMethodCallback(runtime, bridge, selectorName, std::move(signature), - std::move(function)); - std::string encoding = objcMethodSignatureForJsiSignature(callback->signature()); - class_replaceMethod(nativeClass, sel_registerName(selectorName.c_str()), - reinterpret_cast(callback->functionPointer()), encoding.c_str()); -} - -bool addRuntimeProtocolOverrideForName(Runtime& runtime, - const std::shared_ptr& bridge, - Class nativeClass, const std::vector& protocols, - const std::string& propertyName, Function function) { - std::unordered_set visited; - std::function visit = [&](Protocol* protocol) -> bool { - if (protocol == nullptr || !visited.insert(protocol).second) { - return false; - } - - Protocol** inherited = protocol_copyProtocolList(protocol, nullptr); - if (inherited != nullptr) { - unsigned int inheritedCount = 0; - free(inherited); - inherited = protocol_copyProtocolList(protocol, &inheritedCount); - for (unsigned int i = 0; i < inheritedCount; i++) { - if (visit(inherited[i])) { - free(inherited); - return true; - } - } - free(inherited); - } - - for (BOOL required : {YES, NO}) { - unsigned int count = 0; - objc_method_description* descriptions = - protocol_copyMethodDescriptionList(protocol, required, YES, &count); - for (unsigned int i = 0; i < count; i++) { - SEL selector = descriptions[i].name; - const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; - if (selectorName == nullptr || jsifySelector(selectorName) != propertyName) { - continue; - } - auto signature = runtimeProtocolMethodSignature(descriptions[i].types); - if (signature) { - addJsiExposedMethod(runtime, bridge, nativeClass, selectorName, std::move(*signature), - std::move(function)); - free(descriptions); - return true; - } - } - free(descriptions); - } - return false; - }; - - for (Protocol* protocol : protocols) { - if (visit(protocol)) { - return true; - } - } - return false; -} - -Object getOwnPropertyDescriptor(Runtime& runtime, const Object& object, const std::string& name) { - Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); - Function getOwnPropertyDescriptor = - objectCtor.getPropertyAsFunction(runtime, "getOwnPropertyDescriptor"); - Value args[] = {Value(runtime, object), makeString(runtime, name)}; - Value descriptorValue = getOwnPropertyDescriptor.call(runtime, static_cast(args), - static_cast(2)); - return descriptorValue.isObject() ? descriptorValue.asObject(runtime) : Object(runtime); -} - -Value extendNativeApiJsiClass(Runtime& runtime, const std::shared_ptr& bridge, - const Value* args, size_t count) { - if (count < 2 || !args[0].isObject() || !args[1].isObject()) { - throw facebook::jsi::JSError(runtime, "extendClass expects a native class and method object."); - } - - Class baseClass = classFromJsiValue(runtime, args[0]); - if (baseClass == Nil) { - throw facebook::jsi::JSError(runtime, "extendClass can only extend native class constructors."); - } - if (class_conformsToProtocol(baseClass, @protocol(NativeApiJsiClassBuilderProtocol))) { - throw facebook::jsi::JSError(runtime, "Cannot extend an already extended class."); - } - - Object methods = args[1].asObject(runtime); - Object options = count >= 3 && args[2].isObject() ? args[2].asObject(runtime) : Object(runtime); - std::string requestedName = readOptionalStringProperty(runtime, options, "name"); - if (requestedName.empty()) { - const char* baseName = class_getName(baseClass); - requestedName = std::string(baseName != nullptr ? baseName : "NSObject") + "_Extended_" + - std::to_string(rand()); - } - - std::string className = nextAvailableJsiClassName(requestedName); - Class nativeClass = objc_allocateClassPair(baseClass, className.c_str(), 0); - if (nativeClass == Nil) { - throw facebook::jsi::JSError(runtime, "Failed to allocate Objective-C class."); - } - - markNativeApiJsiExtendedClass(nativeClass); - class_addProtocol(nativeClass, @protocol(NativeApiJsiClassBuilderProtocol)); - rememberNativeApiJsiClassBuilder(runtime, bridge, nativeClass); - - NativeApiSymbol baseSymbol = runtimeSymbolForClass(bridge, baseClass); - std::vector extensionMembers = bridge->membersForClass(baseSymbol); - std::vector optionProtocols; - Value protocolsValue = getObjectPropertyOrUndefined(runtime, options, "protocols"); - if (protocolsValue.isObject() && protocolsValue.asObject(runtime).isArray(runtime)) { - Array protocols = protocolsValue.asObject(runtime).getArray(runtime); - for (size_t i = 0; i < protocols.size(runtime); i++) { - Value protocolValue = protocols.getValueAtIndex(runtime, i); - Protocol* protocol = protocolFromJsiValue(runtime, protocolValue); - std::optional protocolSymbol = - protocolSymbolFromJsiValue(runtime, bridge, protocolValue); - if (protocol != nullptr) { - optionProtocols.push_back(protocol); - class_addProtocol(nativeClass, protocol); - if (!protocolSymbol) { - if (const NativeApiSymbol* runtimeSymbol = - bridge->findProtocolForRuntimePointer(protocol)) { - protocolSymbol = *runtimeSymbol; - } - } - } - if (protocolSymbol) { - const auto& protocolMembers = bridge->membersForProtocol(*protocolSymbol); - extensionMembers.insert(extensionMembers.begin(), protocolMembers.begin(), - protocolMembers.end()); - } - } - } - const auto& members = extensionMembers; - Array propertyNames = methods.getPropertyNames(runtime); - for (size_t i = 0; i < propertyNames.size(runtime); i++) { - Value propertyNameValue = propertyNames.getValueAtIndex(runtime, i); - if (!propertyNameValue.isString()) { - continue; - } - - std::string propertyName = propertyNameValue.asString(runtime).utf8(runtime); - Object descriptor = getOwnPropertyDescriptor(runtime, methods, propertyName); - - Value value = descriptor.getProperty(runtime, "value"); - if (value.isObject() && value.asObject(runtime).isFunction(runtime)) { - auto overrides = methodOverridesForName(members, propertyName); - bool addedOverride = false; - for (const auto& member : overrides) { - if (member.selectorName.empty() || member.signatureOffset == MD_SECTION_OFFSET_NULL || - member.signatureOffset == 0) { - continue; - } - addJsiOverrideMethod(runtime, bridge, nativeClass, baseClass, member.selectorName, - member.signatureOffset, - (member.flags & metagen::mdMemberReturnOwned) != 0, - value.asObject(runtime).asFunction(runtime)); - addedOverride = true; - } - if (!addedOverride) { - bool addedRuntimeProtocolOverride = addRuntimeProtocolOverrideForName( - runtime, bridge, nativeClass, optionProtocols, propertyName, - value.asObject(runtime).asFunction(runtime)); - if (!addedRuntimeProtocolOverride) { - if (auto known = knownNativeApiJsiExposedMethod(propertyName)) { - addJsiExposedMethod(runtime, bridge, nativeClass, known->selectorName, - std::move(known->signature), - value.asObject(runtime).asFunction(runtime)); - } - } - } - } - - const NativeApiMember* propertyMember = propertyOverrideForName(members, propertyName); - - Value getter = descriptor.getProperty(runtime, "get"); - if (propertyMember != nullptr && getter.isObject() && - getter.asObject(runtime).isFunction(runtime)) { - addJsiOverrideMethod(runtime, bridge, nativeClass, baseClass, propertyMember->selectorName, - propertyMember->signatureOffset, - (propertyMember->flags & metagen::mdMemberReturnOwned) != 0, - getter.asObject(runtime).asFunction(runtime)); - } else if (propertyMember == nullptr && getter.isObject() && - getter.asObject(runtime).isFunction(runtime)) { - auto overrides = methodOverridesForName(members, propertyName); - for (const auto& member : overrides) { - if (selectorArgumentCount(member.selectorName) != 0) { - continue; - } - addJsiOverrideMethod(runtime, bridge, nativeClass, baseClass, member.selectorName, - member.signatureOffset, - (member.flags & metagen::mdMemberReturnOwned) != 0, - getter.asObject(runtime).asFunction(runtime)); - } - } - - Value setter = descriptor.getProperty(runtime, "set"); - if (propertyMember != nullptr && setter.isObject() && - setter.asObject(runtime).isFunction(runtime) && - !propertyMember->setterSelectorName.empty()) { - addJsiOverrideMethod(runtime, bridge, nativeClass, baseClass, - propertyMember->setterSelectorName, - propertyMember->setterSignatureOffset, false, - setter.asObject(runtime).asFunction(runtime)); - } - } - - Value exposedMethodsValue = getObjectPropertyOrUndefined(runtime, options, "exposedMethods"); - if (!exposedMethodsValue.isObject()) { - exposedMethodsValue = getObjectPropertyOrUndefined(runtime, methods, "ObjCExposedMethods"); - } - if (exposedMethodsValue.isObject()) { - Object exposedMethods = exposedMethodsValue.asObject(runtime); - Array exposedNames = exposedMethods.getPropertyNames(runtime); - for (size_t i = 0; i < exposedNames.size(runtime); i++) { - Value selectorValue = exposedNames.getValueAtIndex(runtime, i); - if (!selectorValue.isString()) { - continue; - } - std::string selectorName = selectorValue.asString(runtime).utf8(runtime); - Value descriptorValue = getObjectPropertyOrUndefined(runtime, exposedMethods, selectorName); - if (!descriptorValue.isObject()) { - continue; - } - auto function = functionForSelector(runtime, methods, selectorName); - if (!function) { - continue; - } - auto signature = - exposedMethodSignature(runtime, bridge, selectorName, descriptorValue.asObject(runtime)); - if (signature) { - rememberNativeApiJsiKnownExposedMethod(selectorName, *signature); - addJsiExposedMethod(runtime, bridge, nativeClass, selectorName, std::move(*signature), - std::move(*function)); - } - } - } - - Value hasIteratorValue = getObjectPropertyOrUndefined(runtime, options, "__hasIterator"); - if (hasIteratorValue.isBool() && hasIteratorValue.getBool()) { - class_addProtocol(nativeClass, @protocol(NSFastEnumeration)); - if (const char* encoding = nativeApiJsiFastEnumerationEncoding()) { - class_replaceMethod(nativeClass, @selector(countByEnumeratingWithState:objects:count:), - reinterpret_cast(nativeApiJsiSymbolIteratorCountByEnumerating), - encoding); - } - } - - objc_registerClassPair(nativeClass); - - NativeApiSymbol newSymbol = baseSymbol; - newSymbol.name = className; - newSymbol.runtimeName = className; - newSymbol.superclassOffset = baseSymbol.offset; - return makeNativeClassValue(runtime, bridge, std::move(newSymbol)); -} - -Value invokeNativeApiJsiBaseMethod(Runtime& runtime, - const std::shared_ptr& bridge, - const Value* args, size_t count) { - if (count < 3 || !args[0].isObject() || !args[1].isObject() || !args[2].isString()) { - throw facebook::jsi::JSError(runtime, - "__invokeBase expects base class, receiver, and member name."); - } - - Class baseClass = classFromJsiValue(runtime, args[0]); - if (baseClass == Nil) { - throw facebook::jsi::JSError(runtime, "__invokeBase base class is invalid."); - } - - Object receiverObject = args[1].asObject(runtime); - if (!receiverObject.isHostObject(runtime)) { - throw facebook::jsi::JSError(runtime, "__invokeBase receiver is not native."); - } - - // Dispatch through the host object so initializer selectors get their - // receiver-consumption bookkeeping (disown + round-trip/expando forget); - // calling the raw selector path here leaves dangling ownership of the - // placeholder an init consumes (e.g. UIColor.alloc().initWith...). - auto hostObject = receiverObject.getHostObject(runtime); - id receiver = hostObject->object(); - std::string memberName = args[2].asString(runtime).utf8(runtime); - size_t actualArgc = count - 3; - - NativeApiSymbol baseSymbol = runtimeSymbolForClass(bridge, baseClass); - const auto& members = bridge->membersForClass(baseSymbol); - const NativeApiMember* member = selectMethodMember(members, memberName, false, actualArgc); - if (member == nullptr) { - if (const NativeApiMember* propertyMember = - selectWritablePropertyMember(members, memberName, false)) { - if (actualArgc == 0) { - Class dispatchClass = dispatchSuperclassForJsiDerivedReceiver(receiver, baseClass); - return hostObject->callObjectSelector(runtime, propertyMember->selectorName, - propertyMember, nullptr, 0, dispatchClass); - } - if (actualArgc == 1 && !propertyMember->setterSelectorName.empty() && - !propertyMember->readonly) { - Class dispatchClass = dispatchSuperclassForJsiDerivedReceiver(receiver, baseClass); - NativeApiMember setterMember = *propertyMember; - setterMember.selectorName = propertyMember->setterSelectorName; - setterMember.signatureOffset = propertyMember->setterSignatureOffset; - return hostObject->callObjectSelector(runtime, setterMember.selectorName, &setterMember, - args + 3, actualArgc, dispatchClass); - } - } - } - if (member == nullptr) { - throw facebook::jsi::JSError(runtime, - "Objective-C base selector is not available: " + memberName); - } - - Class dispatchClass = dispatchSuperclassForJsiDerivedReceiver(receiver, baseClass); - return hostObject->callObjectSelector(runtime, member->selectorName, member, args + 3, - actualArgc, dispatchClass); -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiConversion.h b/NativeScript/ffi/shared/jsi/NativeApiJsiConversion.h deleted file mode 100644 index 6ed83df9c..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiConversion.h +++ /dev/null @@ -1,2122 +0,0 @@ -std::string stringPropertyOrEmpty(Runtime& runtime, const Object& object, - const char* name); -void* pointerFromSymbolLikeObject(Runtime& runtime, const Object& object); - -id objectFromJsiValue(Runtime& runtime, - const std::shared_ptr& bridge, - const Value& value, NativeApiJsiArgumentFrame& frame, - bool mutableString) { - if (value.isNull() || value.isUndefined()) { - return nil; - } - if (value.isString()) { - std::string utf8 = value.asString(runtime).utf8(runtime); - id string = mutableString - ? [[NSMutableString alloc] initWithBytes:utf8.data() - length:utf8.size() - encoding:NSUTF8StringEncoding] - : [[NSString alloc] initWithBytes:utf8.data() - length:utf8.size() - encoding:NSUTF8StringEncoding]; - frame.addObject(string); - return string; - } - if (value.isBool()) { - return [NSNumber numberWithBool:value.getBool()]; - } - if (value.isNumber()) { - return [NSNumber numberWithDouble:value.getNumber()]; - } - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->object(); - } - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - return static_cast(cls); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime) - ->nativeProtocol()); - } - if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { - return static_cast(symbolPointer); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime)->pointer()); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime)->data()); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime)->data()); - } - - Value getTimeValue = object.getProperty(runtime, "getTime"); - Value toISOStringValue = object.getProperty(runtime, "toISOString"); - if (getTimeValue.isObject() && - getTimeValue.asObject(runtime).isFunction(runtime) && - toISOStringValue.isObject() && - toISOStringValue.asObject(runtime).isFunction(runtime)) { - Value millisValue = getTimeValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - if (millisValue.isNumber()) { - NSDate* date = [NSDate dateWithTimeIntervalSince1970:millisValue.getNumber() / 1000.0]; - bridge->rememberRoundTripValue(runtime, date, value); - return date; - } - } - - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - Value primitiveValue = valueOfValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - if (primitiveValue.isString() || primitiveValue.isBool() || - primitiveValue.isNumber()) { - return objectFromJsiValue(runtime, bridge, primitiveValue, frame, - mutableString); - } - } - - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - NSData* data = [NSData dataWithBytes:bytes length:byteLength]; - bridge->rememberRoundTripValue(runtime, data, value); - return data; - } - - if (object.isArray(runtime)) { - Array array = object.getArray(runtime); - NSMutableArray* nativeArray = - [NSMutableArray arrayWithCapacity:array.size(runtime)]; - for (size_t i = 0; i < array.size(runtime); i++) { - id element = objectFromJsiValue(runtime, bridge, - array.getValueAtIndex(runtime, i), - frame, false); - [nativeArray addObject:element != nil ? element : [NSNull null]]; - } - bridge->rememberRoundTripValue(runtime, nativeArray, value); - return nativeArray; - } - - Value lengthValue = object.getProperty(runtime, "length"); - if (lengthValue.isNumber() && std::isfinite(lengthValue.getNumber()) && - lengthValue.getNumber() >= 0) { - size_t length = static_cast(std::floor(lengthValue.getNumber())); - NSMutableArray* nativeArray = [NSMutableArray arrayWithCapacity:length]; - for (size_t i = 0; i < length; i++) { - std::string key = std::to_string(i); - id element = objectFromJsiValue( - runtime, bridge, object.getProperty(runtime, key.c_str()), frame, - false); - [nativeArray addObject:element != nil ? element : [NSNull null]]; - } - bridge->rememberRoundTripValue(runtime, nativeArray, value); - return nativeArray; - } - - Value entriesValue = object.getProperty(runtime, "entries"); - Value sizeValue = object.getProperty(runtime, "size"); - Value getValue = object.getProperty(runtime, "get"); - if (entriesValue.isObject() && - entriesValue.asObject(runtime).isFunction(runtime) && - sizeValue.isNumber() && getValue.isObject() && - getValue.asObject(runtime).isFunction(runtime)) { - Object arrayCtor = runtime.global().getPropertyAsObject(runtime, "Array"); - Function arrayFrom = arrayCtor.getPropertyAsFunction(runtime, "from"); - Value iterator = entriesValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - Value pairsValue = arrayFrom.call(runtime, iterator); - if (pairsValue.isObject() && pairsValue.asObject(runtime).isArray(runtime)) { - Array pairs = pairsValue.asObject(runtime).getArray(runtime); - NSMutableDictionary* nativeMap = - [NSMutableDictionary dictionaryWithCapacity:pairs.size(runtime)]; - for (size_t i = 0; i < pairs.size(runtime); i++) { - Value pairValue = pairs.getValueAtIndex(runtime, i); - if (!pairValue.isObject() || - !pairValue.asObject(runtime).isArray(runtime)) { - continue; - } - Array pair = pairValue.asObject(runtime).getArray(runtime); - if (pair.size(runtime) < 2) { - continue; - } - id key = objectFromJsiValue(runtime, bridge, - pair.getValueAtIndex(runtime, 0), - frame, false); - id nativeValue = objectFromJsiValue(runtime, bridge, - pair.getValueAtIndex(runtime, 1), - frame, false); - if (key != nil) { - [nativeMap setObject:nativeValue != nil ? nativeValue : [NSNull null] - forKey:key]; - } - } - bridge->rememberRoundTripValue(runtime, nativeMap, value); - return nativeMap; - } - } - - NSMutableDictionary* dictionary = [NSMutableDictionary dictionary]; - Array propertyNames = object.getPropertyNames(runtime); - for (size_t i = 0; i < propertyNames.size(runtime); i++) { - Value propertyNameValue = propertyNames.getValueAtIndex(runtime, i); - if (!propertyNameValue.isString()) { - continue; - } - std::string key = propertyNameValue.asString(runtime).utf8(runtime); - Value propertyValue = object.getProperty(runtime, key.c_str()); - if (propertyValue.isUndefined()) { - continue; - } - id nativeValue = - objectFromJsiValue(runtime, bridge, propertyValue, frame, false); - NSString* nativeKey = [NSString stringWithUTF8String:key.c_str()]; - if (nativeKey != nil) { - [dictionary setObject:nativeValue != nil ? nativeValue : [NSNull null] - forKey:nativeKey]; - } - } - bridge->rememberRoundTripValue(runtime, dictionary, value); - return dictionary; - } - throw facebook::jsi::JSError(runtime, - "Value cannot be converted to Objective-C object."); -} - -std::string utf8StringFromNSString(NSString* string) { - if (string == nil) { - return ""; - } - NSUInteger length = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - std::string result(length, '\0'); - NSUInteger usedLength = 0; - NSRange remainingRange = NSMakeRange(0, 0); - BOOL ok = [string getBytes:result.data() - maxLength:length - usedLength:&usedLength - encoding:NSUTF8StringEncoding - options:0 - range:NSMakeRange(0, string.length) - remainingRange:&remainingRange]; - if (!ok) { - return string.UTF8String ?: ""; - } - result.resize(usedLength); - return result; -} - -bool readNativePointerProperty(Runtime& runtime, const Object& object, - void** pointer) { - if (pointer == nullptr) { - return false; - } - - Value nativePointerObjectValue = - object.getProperty(runtime, "__nativeApiPointerObject"); - if (nativePointerObjectValue.isObject()) { - Object nativePointerObject = nativePointerObjectValue.asObject(runtime); - if (nativePointerObject.isHostObject( - runtime)) { - *pointer = nativePointerObject - .getHostObject(runtime) - ->pointer(); - return true; - } - } - - Value nativePointerValue = - object.getProperty(runtime, "__nativeApiPointer"); - if (nativePointerValue.isNumber()) { - *pointer = reinterpret_cast( - static_cast(nativePointerValue.getNumber())); - return true; - } - - Value nativeAddressValue = object.getProperty(runtime, "nativeAddress"); - if (nativeAddressValue.isNumber()) { - *pointer = reinterpret_cast( - static_cast(nativeAddressValue.getNumber())); - return true; - } - - return false; -} - -std::string stringPropertyOrEmpty(Runtime& runtime, const Object& object, - const char* name) { - if (name == nullptr || !object.hasProperty(runtime, name)) { - return ""; - } - Value value = object.getProperty(runtime, name); - return value.isString() ? value.asString(runtime).utf8(runtime) : ""; -} - -void* pointerFromSymbolLikeObject(Runtime& runtime, const Object& object) { - std::string kind = stringPropertyOrEmpty(runtime, object, "kind"); - if (kind != "class" && kind != "protocol") { - return nullptr; - } - - std::string runtimeName = stringPropertyOrEmpty(runtime, object, "runtimeName"); - if (runtimeName.empty()) { - runtimeName = stringPropertyOrEmpty(runtime, object, "name"); - } - if (runtimeName.empty()) { - return nullptr; - } - - if (kind == "class") { - return objc_lookUpClass(runtimeName.c_str()); - } - return lookupProtocolByNativeName(runtimeName); -} - -void* pointerFromJsiValue(Runtime& runtime, const Value& value, - NativeApiJsiArgumentFrame& frame) { - if (value.isNull() || value.isUndefined()) { - return nullptr; - } - if (value.isNumber()) { - return reinterpret_cast(static_cast(value.getNumber())); - } - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->pointer(); - } - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->object(); - } - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - return cls; - } - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime) - ->nativeProtocol(); - } - if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { - return symbolPointer; - } - if (object.isHostObject(runtime)) { - auto reference = - object.getHostObject(runtime); - if (reference->data() == nullptr) { - reference->ensureStorage(runtime, reference->type(), frame); - } - return reference->data(); - } - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->data(); - } - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, &nativePointer)) { - return nativePointer; - } - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - return const_cast(bytes); - } - } - if (value.isString()) { - std::string utf8 = value.asString(runtime).utf8(runtime); - char* string = strdup(utf8.c_str()); - return string; - } - throw facebook::jsi::JSError(runtime, "Value cannot be converted to pointer."); -} - -bool readPointerLikeValue(Runtime& runtime, const Value& value, void** pointer) { - if (pointer == nullptr || !value.isObject()) { - return false; - } - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - *pointer = object.getHostObject(runtime)->pointer(); - return true; - } - if (object.isHostObject(runtime)) { - *pointer = object.getHostObject(runtime)->data(); - return true; - } - if (object.isHostObject(runtime)) { - *pointer = object.getHostObject(runtime)->data(); - return true; - } - if (object.isHostObject(runtime)) { - *pointer = object.getHostObject(runtime)->object(); - return true; - } - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - *pointer = cls; - return true; - } - if (object.isHostObject(runtime)) { - *pointer = - object.getHostObject(runtime)->nativeProtocol(); - return true; - } - if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { - *pointer = symbolPointer; - return true; - } - return readNativePointerProperty(runtime, object, pointer); -} - -template -void writeNumericArgument(Runtime& runtime, const Value& value, void* target, - const char* typeName) { - const Value* numericValue = &value; - Value primitiveValue = Value::undefined(); - if (value.isObject()) { - Object object = value.asObject(runtime); - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - primitiveValue = valueOfValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - numericValue = &primitiveValue; - } - } - - if (!numericValue->isNumber() && !numericValue->isBool()) { - throw facebook::jsi::JSError(runtime, - std::string("Expected numeric ") + typeName + - " argument."); - } - double number = numericValue->isBool() ? (numericValue->getBool() ? 1.0 : 0.0) - : numericValue->getNumber(); - *static_cast(target) = static_cast(number); -} - -void convertJsiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame); - -Value convertNativeReturnValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* value); - -Class classFromJsiValue(Runtime& runtime, const Value& value); -Protocol* protocolFromJsiValue(Runtime& runtime, const Value& value); - -std::optional parseArrayIndexProperty(const std::string& property) { - if (property.empty()) { - return std::nullopt; - } - size_t index = 0; - for (char c : property) { - if (!std::isdigit(static_cast(c))) { - return std::nullopt; - } - size_t digit = static_cast(c - '0'); - if (index > (std::numeric_limits::max() - digit) / 10) { - return std::nullopt; - } - index = (index * 10) + digit; - } - return index; -} - -size_t referenceElementStride(const NativeApiJsiType& type) { - return std::max(nativeSizeForType(type), 1); -} - -void convertAggregateArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame) { - size_t size = nativeSizeForType(type); - if (size == 0) { - return; - } - - std::memset(target, 0, size); - if (value.isNull() || value.isUndefined()) { - return; - } - - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - auto structObject = object.getHostObject(runtime); - if (structObject->data() != nullptr) { - std::memcpy(target, structObject->data(), - std::min(size, static_cast(structObject->info()->size))); - } - return; - } - if (object.isHostObject(runtime)) { - void* data = object.getHostObject(runtime)->data(); - if (data != nullptr) { - std::memcpy(target, data, size); - } - return; - } - if (object.isHostObject(runtime)) { - void* data = object.getHostObject(runtime)->pointer(); - if (data != nullptr) { - std::memcpy(target, data, size); - } - return; - } - - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - if (bytes != nullptr) { - std::memcpy(target, bytes, std::min(byteLength, size)); - } - return; - } - } - - if (type.aggregateInfo == nullptr) { - throw facebook::jsi::JSError(runtime, "Missing native struct metadata."); - } - if (!value.isObject()) { - throw facebook::jsi::JSError(runtime, "Expected struct descriptor object."); - } - - Object object = value.asObject(runtime); - for (const auto& field : type.aggregateInfo->fields) { - bool hasField = object.hasProperty(runtime, field.name.c_str()); - if (!hasField) { - continue; - } - Value fieldValue = object.getProperty(runtime, field.name.c_str()); - void* fieldTarget = static_cast(target) + field.offset; - convertJsiArgument(runtime, bridge, field.type, fieldValue, fieldTarget, - frame); - } -} - -void convertIndexedAggregateArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame) { - size_t size = nativeSizeForType(type); - std::memset(target, 0, size); - if (value.isNull() || value.isUndefined()) { - return; - } - if (value.isObject()) { - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, value.asObject(runtime), &bytes, &byteLength)) { - if (bytes != nullptr) { - std::memcpy(target, bytes, std::min(byteLength, size)); - } - return; - } - } - if (!value.isObject() || !value.asObject(runtime).isArray(runtime)) { - throw facebook::jsi::JSError(runtime, "Expected array, ArrayBuffer, or typed array."); - } - - Array array = value.asObject(runtime).getArray(runtime); - size_t elementSize = type.elementType != nullptr ? nativeSizeForType(*type.elementType) : 0; - if (elementSize == 0 || type.elementType == nullptr) { - throw facebook::jsi::JSError(runtime, "Invalid native array element type."); - } - size_t count = std::min(type.arraySize, array.size(runtime)); - for (size_t i = 0; i < count; i++) { - void* slot = static_cast(target) + (i * elementSize); - convertJsiArgument(runtime, bridge, *type.elementType, - array.getValueAtIndex(runtime, i), slot, frame); - } -} - -void convertJsiFfiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, const Value& value, - void* target, NativeApiJsiArgumentFrame& frame) { - if (type.kind != metagen::mdTypeArray) { - convertJsiArgument(runtime, bridge, type, value, target, frame); - return; - } - - void* pointer = nullptr; - if (!value.isNull() && !value.isUndefined()) { - if (value.isObject()) { - Object object = value.asObject(runtime); - if (!readPointerLikeValue(runtime, value, &pointer)) { - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - pointer = const_cast(bytes); - } - } - } - - if (pointer == nullptr) { - size_t byteLength = nativeSizeForType(type); - void* buffer = frame.addBuffer(byteLength); - convertIndexedAggregateArgument(runtime, bridge, type, value, buffer, - frame); - pointer = buffer; - } - } - - *static_cast(target) = pointer; -} - -void convertJsiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame) { - if (unsupportedJsiType(type)) { - throw facebook::jsi::JSError(runtime, - "This native signature is not supported by " - "the pure JSI bridge yet."); - } - - switch (type.kind) { - case metagen::mdTypeBool: - if (!value.isNumber() && !value.isBool()) { - throw facebook::jsi::JSError(runtime, - "Expected boolean or numeric argument."); - } - *static_cast(target) = - value.isBool() ? static_cast(value.getBool()) - : static_cast(value.getNumber() != 0); - break; - case metagen::mdTypeChar: - writeNumericArgument(runtime, value, target, "int8"); - break; - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - writeNumericArgument(runtime, value, target, "uint8"); - break; - case metagen::mdTypeSShort: - writeNumericArgument(runtime, value, target, "int16"); - break; - case metagen::mdTypeUShort: - case metagen::mdTypeUnichar: - if (value.isString()) { - std::string text = value.asString(runtime).utf8(runtime); - if (text.size() != 1) { - throw facebook::jsi::JSError( - runtime, "Expected a single-character string."); - } - *static_cast(target) = - static_cast(static_cast(text[0])); - } else { - writeNumericArgument(runtime, value, target, "uint16"); - } - break; - case metagen::mdTypeSInt: - writeNumericArgument(runtime, value, target, "int32"); - break; - case metagen::mdTypeUInt: - writeNumericArgument(runtime, value, target, "uint32"); - break; - case metagen::mdTypeSLong: - case metagen::mdTypeSInt64: - writeNumericArgument(runtime, value, target, "int64"); - break; - case metagen::mdTypeULong: - case metagen::mdTypeUInt64: - writeNumericArgument(runtime, value, target, "uint64"); - break; - case metagen::mdTypeFloat: - writeNumericArgument(runtime, value, target, "float"); - break; - case metagen::mdTypeDouble: - writeNumericArgument(runtime, value, target, "double"); - break; - case metagen::mdTypeString: { - if (value.isNull() || value.isUndefined()) { - *static_cast(target) = nullptr; - break; - } - if (value.isObject()) { - Object object = value.asObject(runtime); - void* pointer = nullptr; - if (readPointerLikeValue(runtime, value, &pointer)) { - *static_cast(target) = static_cast(pointer); - break; - } - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - *static_cast(target) = - reinterpret_cast(const_cast(bytes)); - break; - } - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - Value primitive = valueOfValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - if (primitive.isString()) { - std::string utf8 = primitive.asString(runtime).utf8(runtime); - char* string = strdup(utf8.c_str()); - *static_cast(target) = string; - break; - } - } - } - if (!value.isString()) { - throw facebook::jsi::JSError(runtime, "Expected string argument."); - } - std::string utf8 = value.asString(runtime).utf8(runtime); - char* string = strdup(utf8.c_str()); - *static_cast(target) = string; - break; - } - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: { - id object = objectFromJsiValue( - runtime, bridge, value, frame, - type.kind == metagen::mdTypeNSMutableStringObject); - *static_cast(target) = object; - break; - } - case metagen::mdTypeClass: { - *static_cast(target) = classFromJsiValue(runtime, value); - break; - } - case metagen::mdTypeSelector: { - if (value.isNull() || value.isUndefined()) { - *static_cast(target) = nullptr; - break; - } - if (!value.isString()) { - throw facebook::jsi::JSError(runtime, "Expected selector string."); - } - std::string selectorName = value.asString(runtime).utf8(runtime); - *static_cast(target) = sel_registerName(selectorName.c_str()); - break; - } - case metagen::mdTypePointer: - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - auto reference = object.getHostObject(runtime); - if (reference->data() == nullptr && type.elementType != nullptr) { - reference->ensureStorage(runtime, *type.elementType, frame); - } else if (reference->data() == nullptr) { - reference->ensureStorage(runtime, reference->type(), frame); - } - void* pointer = reference->data(); - frame.rememberRoundTripValue(bridge, runtime, pointer, value); - *static_cast(target) = pointer; - break; - } - if (object.isHostObject(runtime)) { - void* pointer = - object.getHostObject(runtime) - ->data(); - frame.rememberRoundTripValue(bridge, runtime, pointer, value); - *static_cast(target) = pointer; - break; - } - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - void* pointer = const_cast(bytes); - frame.rememberRoundTripValue(bridge, runtime, pointer, value); - *static_cast(target) = pointer; - break; - } - } - *static_cast(target) = pointerFromJsiValue(runtime, value, frame); - break; - case metagen::mdTypeOpaquePointer: - *static_cast(target) = pointerFromJsiValue(runtime, value, frame); - break; - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: { - if (value.isObject()) { - Object object = value.asObject(runtime); - void* nativePointer = nullptr; - if (object.isFunction(runtime)) { - std::string functionKind = stringPropertyOrEmpty(runtime, object, "kind"); - if (functionKind == "block" || functionKind == "functionPointer" || - functionKind == "functionReference") { - if (readNativePointerProperty(runtime, object, &nativePointer)) { - *static_cast(target) = nativePointer; - break; - } - } - - auto threadPolicy = readJsiCallbackThreadPolicy(runtime, object); - auto callback = - createJsiCallback(runtime, bridge, type, object.asFunction(runtime), - type.kind == metagen::mdTypeBlock, threadPolicy); - void* pointer = callback->functionPointer(); - if (type.kind == metagen::mdTypeBlock) { - frame.addLifetime(callback); - frame.rememberRoundTripValue(bridge, runtime, pointer, value); - } else { - bridge->rememberRoundTripValue(runtime, pointer, value); - } - try { - object.setProperty(runtime, "__nativeApiPointerObject", - createPointer(runtime, bridge, pointer)); - object.setProperty( - runtime, "__nativeApiPointer", - static_cast(reinterpret_cast(pointer))); - } catch (const std::exception&) { - } - *static_cast(target) = pointer; - break; - } - } - *static_cast(target) = pointerFromJsiValue(runtime, value, frame); - break; - } - case metagen::mdTypeStruct: - convertAggregateArgument(runtime, bridge, type, value, target, frame); - break; - case metagen::mdTypeArray: - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: - convertIndexedAggregateArgument(runtime, bridge, type, value, target, - frame); - break; - default: - throw facebook::jsi::JSError(runtime, "Unsupported JSI argument type."); - } -} - -Value convertNativeReturnValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* value) { - if (unsupportedJsiType(type)) { - throw facebook::jsi::JSError(runtime, - "This native return type is not supported by " - "the pure JSI bridge yet."); - } - - switch (type.kind) { - case metagen::mdTypeVoid: - return Value::undefined(); - case metagen::mdTypeBool: - return *static_cast(value) != 0; - case metagen::mdTypeChar: - return static_cast(*static_cast(value)); - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - return static_cast(*static_cast(value)); - case metagen::mdTypeSShort: - return static_cast(*static_cast(value)); - case metagen::mdTypeUShort: - return static_cast(*static_cast(value)); - case metagen::mdTypeUnichar: { - const char16_t unit = *static_cast(value); - // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates - // fall back to U+FFFD). - char buffer[4] = {0}; - size_t length = 0; - if (unit < 0x80) { - buffer[length++] = static_cast(unit); - } else if (unit < 0x800) { - buffer[length++] = static_cast(0xC0 | (unit >> 6)); - buffer[length++] = static_cast(0x80 | (unit & 0x3F)); - } else if (unit >= 0xD800 && unit <= 0xDFFF) { - buffer[length++] = static_cast(0xEF); - buffer[length++] = static_cast(0xBF); - buffer[length++] = static_cast(0xBD); - } else { - buffer[length++] = static_cast(0xE0 | (unit >> 12)); - buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); - buffer[length++] = static_cast(0x80 | (unit & 0x3F)); - } - return String::createFromUtf8( - runtime, reinterpret_cast(buffer), length); - } - case metagen::mdTypeSInt: - return static_cast(*static_cast(value)); - case metagen::mdTypeUInt: - return static_cast(*static_cast(value)); - case metagen::mdTypeSLong: - case metagen::mdTypeSInt64: - return signedInteger64ToJsiValue(runtime, *static_cast(value)); - case metagen::mdTypeULong: - case metagen::mdTypeUInt64: - return unsignedInteger64ToJsiValue(runtime, - *static_cast(value)); - case metagen::mdTypeFloat: - return static_cast(*static_cast(value)); - case metagen::mdTypeDouble: - return *static_cast(value); - case metagen::mdTypeString: { - const char* string = *static_cast(value); - if (string == nullptr) { - return Value::null(); - } - NativeApiJsiType cStringType = - primitiveInteropType(metagen::mdTypeChar); - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, cStringType, const_cast(string), false)); - } - case metagen::mdTypeClass: { - Class cls = *static_cast(value); - if (cls == nil) { - return Value::null(); - } - const char* name = class_getName(cls); - NativeApiSymbol symbol{ - .kind = NativeApiSymbolKind::Class, - .offset = MD_SECTION_OFFSET_NULL, - .name = name != nullptr ? name : "", - .runtimeName = name != nullptr ? name : "", - }; - if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { - symbol = *found; - } - return makeNativeClassValue(runtime, bridge, std::move(symbol)); - } - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: { - id object = *static_cast(value); - if (object == nil) { - return Value::null(); - } - if ([object isKindOfClass:[NSNull class]]) { - if (type.returnOwned) { - [object release]; - } - return Value::null(); - } - if ([object respondsToSelector:@selector(UTF8String)]) { - bool untypedObject = type.kind == metagen::mdTypeAnyObject; - bool explicitNSString = type.kind == metagen::mdTypeNSStringObject; - if (untypedObject || explicitNSString) { - std::string utf8 = utf8StringFromNSString(static_cast(object)); - if (type.returnOwned) { - [object release]; - } - return makeString(runtime, utf8); - } - } - if ([object isKindOfClass:[NSNumber class]] && - ![object isKindOfClass:[NSDecimalNumber class]]) { - NSNumber* number = static_cast(object); - const char* objCType = [number objCType]; - bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == - CFBooleanGetTypeID() || - (objCType != nullptr && - std::strcmp(objCType, @encode(BOOL)) == 0); - Value result = isBool ? Value(static_cast([number boolValue])) - : Value([number doubleValue]); - if (type.returnOwned) { - [object release]; - } - return result; - } - Value roundTrip = bridge->findRoundTripValue(runtime, object); - if (!roundTrip.isUndefined()) { - if (type.returnOwned) { - [object release]; - } - return roundTrip; - } - if (const NativeApiSymbol* classSymbol = - bridge->findClassForRuntimePointer((void*)object)) { - return makeNativeClassValue(runtime, bridge, *classSymbol); - } - if (const NativeApiSymbol* protocolSymbol = - bridge->findProtocolForRuntimePointer((void*)object)) { - return makeNativeProtocolValue(runtime, bridge, *protocolSymbol); - } - return makeNativeObjectValue(runtime, bridge, object, type.returnOwned); - } - case metagen::mdTypeSelector: { - SEL selector = *static_cast(value); - const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; - return selectorName != nullptr ? makeString(runtime, selectorName) - : Value::null(); - } - case metagen::mdTypePointer: - case metagen::mdTypeOpaquePointer: { - void* pointer = *static_cast(value); - if (pointer == nullptr) { - return Value::null(); - } - if (const NativeApiSymbol* classSymbol = - bridge->findClassForRuntimePointer(pointer)) { - return makeNativeClassValue(runtime, bridge, *classSymbol); - } - if (const NativeApiSymbol* protocolSymbol = - bridge->findProtocolForRuntimePointer(pointer)) { - return makeNativeProtocolValue(runtime, bridge, *protocolSymbol); - } - if (type.kind == metagen::mdTypePointer && type.elementType != nullptr) { - std::shared_ptr backingValue; - Value roundTrip = bridge->findRoundTripValue(runtime, pointer); - if (!roundTrip.isUndefined()) { - backingValue = std::make_shared(runtime, roundTrip); - } - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, *type.elementType, pointer, false, 0, nullptr, - std::move(backingValue))); - } - return createPointer(runtime, bridge, pointer); - } - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: { - void* pointer = *static_cast(value); - if (pointer == nullptr) { - return Value::null(); - } - Value roundTrip = bridge->findRoundTripValue(runtime, pointer); - if (!roundTrip.isUndefined()) { - return roundTrip; - } - return wrapNativeFunctionPointer(runtime, bridge, type, pointer, - type.kind == metagen::mdTypeBlock); - } - case metagen::mdTypeStruct: - if (type.aggregateInfo == nullptr) { - return ArrayBuffer( - runtime, std::make_shared( - value, nativeSizeForType(type))); - } - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, type.aggregateInfo, value, true)); - case metagen::mdTypeArray: - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: { - Array result(runtime, type.arraySize); - if (type.elementType == nullptr) { - return result; - } - size_t elementSize = nativeSizeForType(*type.elementType); - auto base = static_cast(value); - for (uint16_t i = 0; i < type.arraySize; i++) { - result.setValueAtIndex( - runtime, i, - convertNativeReturnValue(runtime, bridge, *type.elementType, - base + (static_cast(i) * elementSize))); - } - return result; - } - default: - throw facebook::jsi::JSError(runtime, "Unsupported JSI return type."); - } -} - -void NativeApiReferenceHostObject::ensureStorage( - Runtime& runtime, NativeApiJsiType type, NativeApiJsiArgumentFrame& frame, - size_t elements) { - size_t elementCount = std::max(elements, 1); - NativeApiJsiType storageType = std::move(type); - size_t stride = std::max(nativeSizeForType(storageType), 1); - size_t required = std::max(stride * elementCount, sizeof(void*)); - type_ = std::move(storageType); - - if (data_ == nullptr) { - data_ = calloc(1, required); - ownsData_ = true; - byteLength_ = required; - } else if (ownsData_ && byteLength_ < required) { - void* expanded = realloc(data_, required); - if (expanded == nullptr) { - throw std::bad_alloc(); - } - std::memset(static_cast(expanded) + byteLength_, 0, - required - byteLength_); - data_ = expanded; - byteLength_ = required; - } - - if (data_ != nullptr && pendingValue_ != nullptr) { - Value pending(runtime, *pendingValue_); - convertJsiArgument(runtime, bridge_, type_, pending, data_, frame); - pendingValue_.reset(); - } -} - -Value NativeApiReferenceHostObject::get(Runtime& runtime, - const PropNameID& name) { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "reference"); - } - if (property == "address") { - return static_cast(reinterpret_cast(data_)); - } - if (property == "value") { - if (data_ == nullptr) { - if (pendingValue_ != nullptr) { - return Value(runtime, *pendingValue_); - } - return Value::undefined(); - } - return convertNativeReturnValue(runtime, bridge_, type_, data_); - } - if (auto index = parseArrayIndexProperty(property)) { - if (data_ == nullptr) { - return Value::undefined(); - } - void* slot = static_cast(data_) + - (*index * referenceElementStride(type_)); - return convertNativeReturnValue(runtime, bridge_, type_, slot); - } - if (property == "toString") { - void* data = data_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [data](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", data); - return makeString(runtime, - ""); - }); - } - return Value::undefined(); -} - -void NativeApiReferenceHostObject::set(Runtime& runtime, - const PropNameID& name, - const Value& value) { - std::string property = name.utf8(runtime); - auto index = parseArrayIndexProperty(property); - if (property != "value" && !index) { - return; - } - size_t slotIndex = index.value_or(0); - NativeApiJsiArgumentFrame frame(1); - if (data_ == nullptr) { - if (slotIndex == 0) { - pendingValue_ = std::make_shared(runtime, value); - return; - } - ensureStorage(runtime, type_, frame, slotIndex + 1); - } - pendingValue_.reset(); - void* slot = static_cast(data_) + - (slotIndex * referenceElementStride(type_)); - convertJsiArgument(runtime, bridge_, type_, value, slot, frame); -} - -Value NativeApiStructObjectHostObject::get(Runtime& runtime, - const PropNameID& name) { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, info_ != nullptr && info_->isUnion ? "union" : "struct"); - } - if (property == "name") { - return makeString(runtime, info_ != nullptr ? info_->name : ""); - } - if (property == "sizeof") { - return static_cast(info_ != nullptr ? info_->size : 0); - } - if (property == "address") { - return static_cast(reinterpret_cast(data_)); - } - if (property == "toString") { - auto info = info_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [info](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return makeString(runtime, - std::string("[NativeApiJsi ") + - (info != nullptr && info->isUnion ? "Union " : "Struct ") + - (info != nullptr ? info->name : "") + "]"); - }); - } - - if (info_ != nullptr && data_ != nullptr) { - for (const auto& field : info_->fields) { - if (field.name != property) { - continue; - } - void* fieldData = static_cast(data_) + field.offset; - if (field.type.kind == metagen::mdTypeStruct && - field.type.aggregateInfo != nullptr) { - return Object::createFromHostObject( - runtime, std::make_shared( - bridge_, field.type.aggregateInfo, fieldData, false, - ownedData_, backingValue_)); - } - return convertNativeReturnValue(runtime, bridge_, field.type, fieldData); - } - } - return Value::undefined(); -} - -void NativeApiStructObjectHostObject::set(Runtime& runtime, - const PropNameID& name, - const Value& value) { - std::string property = name.utf8(runtime); - if (info_ == nullptr || data_ == nullptr) { - throw facebook::jsi::JSError(runtime, "Struct is not initialized."); - } - for (const auto& field : info_->fields) { - if (field.name != property) { - continue; - } - NativeApiJsiArgumentFrame frame(1); - convertJsiArgument(runtime, bridge_, field.type, value, - static_cast(data_) + field.offset, frame); - return; - } - throw facebook::jsi::JSError(runtime, "No native struct field: " + property); -} - -std::vector NativeApiStructObjectHostObject::getPropertyNames( - Runtime& runtime) { - std::vector names; - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "name"); - addPropertyName(runtime, names, "sizeof"); - addPropertyName(runtime, names, "address"); - addPropertyName(runtime, names, "toString"); - if (info_ != nullptr) { - for (const auto& field : info_->fields) { - addPropertyName(runtime, names, field.name.c_str()); - } - } - return names; -} - -NativeApiJsiType primitiveInteropType(MDTypeKind kind) { - NativeApiJsiType type; - type.kind = kind; - type.ffiType = ffiTypeForJsiKind(kind); - type.supported = type.ffiType != nullptr; - return type; -} - -std::optional primitiveInteropTypeFromCode(int32_t code) { - MDTypeKind kind = static_cast(code); - switch (kind) { - case metagen::mdTypeVoid: - case metagen::mdTypeBool: - case metagen::mdTypeChar: - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - case metagen::mdTypeSShort: - case metagen::mdTypeUShort: - case metagen::mdTypeUnichar: - case metagen::mdTypeSInt: - case metagen::mdTypeUInt: - case metagen::mdTypeSLong: - case metagen::mdTypeULong: - case metagen::mdTypeSInt64: - case metagen::mdTypeUInt64: - case metagen::mdTypeFloat: - case metagen::mdTypeDouble: - case metagen::mdTypeString: - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClass: - case metagen::mdTypeSelector: - case metagen::mdTypePointer: - case metagen::mdTypeOpaquePointer: - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: - return primitiveInteropType(kind); - default: - return std::nullopt; - } -} - -std::optional interopTypeFromValue( - Runtime& runtime, const std::shared_ptr& bridge, - const Value& value) { - if (value.isNumber()) { - return primitiveInteropTypeFromCode(static_cast(value.getNumber())); - } - - if (!value.isObject()) { - return std::nullopt; - } - - Object object = value.asObject(runtime); - Value typeCodeValue = object.getProperty(runtime, "__nativeApiTypeCode"); - if (typeCodeValue.isNumber()) { - return primitiveInteropTypeFromCode( - static_cast(typeCodeValue.getNumber())); - } - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - Value primitive = - valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( - runtime, object, nullptr, 0); - if (primitive.isNumber()) { - return primitiveInteropTypeFromCode( - static_cast(primitive.getNumber())); - } - } - - Class descriptorClass = nativeClassFromJsiObject(runtime, object); - if (descriptorClass == Nil && - stringPropertyOrEmpty(runtime, object, "kind") == "class") { - descriptorClass = - static_cast(pointerFromSymbolLikeObject(runtime, object)); - } - if (descriptorClass != Nil) { - return nativeObjectReturnTypeForClass(descriptorClass); - } - - if (object.isHostObject(runtime)) { - auto structObject = object.getHostObject(runtime); - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - type.aggregateInfo = structObject->info(); - type.aggregateOffset = type.aggregateInfo != nullptr - ? type.aggregateInfo->offset - : MD_SECTION_OFFSET_NULL; - type.aggregateIsUnion = type.aggregateInfo != nullptr && - type.aggregateInfo->isUnion; - type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr - ? &type.aggregateInfo->ffi->type - : nullptr; - type.supported = type.ffiType != nullptr; - return type; - } - - Value kindValue = object.getProperty(runtime, "kind"); - if (kindValue.isString()) { - std::string kindName = kindValue.asString(runtime).utf8(runtime); - if (kindName == "pointer") { - return primitiveInteropType(metagen::mdTypePointer); - } - if (kindName == "reference") { - return primitiveInteropType(metagen::mdTypePointer); - } - if (kindName == "class") { - return nativeObjectReturnType(metagen::mdTypeInstanceObject); - } - if (kindName == "selector") { - return primitiveInteropType(metagen::mdTypeSelector); - } - if (kindName == "protocol") { - return primitiveInteropType(metagen::mdTypeProtocolObject); - } - if (kindName == "block") { - return primitiveInteropType(metagen::mdTypeBlock); - } - if (kindName == "functionPointer") { - return primitiveInteropType(metagen::mdTypeFunctionPointer); - } - if (kindName == "functionReference") { - return primitiveInteropType(metagen::mdTypeFunctionPointer); - } - } - Value offsetValue = object.getProperty(runtime, "metadataOffset"); - if (kindValue.isString() && offsetValue.isNumber()) { - std::string kindName = kindValue.asString(runtime).utf8(runtime); - if (kindName == "struct" || kindName == "union") { - bool isUnion = kindName == "union"; - auto info = bridge->aggregateInfoFor( - static_cast(offsetValue.getNumber()), isUnion); - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - type.aggregateInfo = info; - type.aggregateOffset = info != nullptr ? info->offset : MD_SECTION_OFFSET_NULL; - type.aggregateIsUnion = isUnion; - type.ffiType = info != nullptr && info->ffi != nullptr ? &info->ffi->type : nullptr; - type.supported = type.ffiType != nullptr; - return type; - } - } - - return std::nullopt; -} - -Value makeAggregateConstructor(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiSymbol& symbol) { - auto info = bridge->aggregateInfoFor(symbol); - auto constructor = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, symbol.name.c_str()), 1, - [bridge, symbol, info](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (info == nullptr) { - throw facebook::jsi::JSError(runtime, - "Native aggregate metadata is unavailable: " + - symbol.name); - } - - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - type.aggregateInfo = info; - type.aggregateOffset = info->offset; - type.aggregateIsUnion = info->isUnion; - type.ffiType = info->ffi != nullptr ? &info->ffi->type : nullptr; - type.supported = type.ffiType != nullptr; - - if (count > 0 && args[0].isObject()) { - void* pointer = nullptr; - if (readPointerLikeValue(runtime, args[0], &pointer) && pointer != nullptr) { - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, info, pointer, false, nullptr, - std::make_shared(runtime, args[0]))); - } - } - - std::vector storage(info->size, 0); - if (count > 0) { - NativeApiJsiArgumentFrame frame(1); - convertAggregateArgument(runtime, bridge, type, args[0], - storage.data(), frame); - } - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, info, storage.data(), true)); - }); - - constructor.setProperty(runtime, "kind", - makeString(runtime, symbol.kind == NativeApiSymbolKind::Union - ? "union" - : "struct")); - constructor.setProperty(runtime, "runtimeName", makeString(runtime, symbol.runtimeName)); - constructor.setProperty(runtime, "metadataOffset", static_cast(symbol.offset)); - constructor.setProperty(runtime, "sizeof", - static_cast(info != nullptr ? info->size : 0)); - constructor.setProperty( - runtime, "equals", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "equals"), 2, - [bridge, info](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (info == nullptr || count < 2) { - return false; - } - - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - type.aggregateInfo = info; - type.aggregateOffset = info->offset; - type.aggregateIsUnion = info->isUnion; - type.ffiType = info->ffi != nullptr ? &info->ffi->type : nullptr; - type.supported = type.ffiType != nullptr; - - std::vector left(info->size, 0); - std::vector right(info->size, 0); - try { - NativeApiJsiArgumentFrame leftFrame(1); - convertAggregateArgument(runtime, bridge, type, args[0], - left.data(), leftFrame); - NativeApiJsiArgumentFrame rightFrame(1); - convertAggregateArgument(runtime, bridge, type, args[1], - right.data(), rightFrame); - } catch (const std::exception&) { - return false; - } - - return std::memcmp(left.data(), right.data(), info->size) == 0; - })); - Array fields(runtime, info != nullptr ? info->fields.size() : 0); - if (info != nullptr) { - for (size_t i = 0; i < info->fields.size(); i++) { - fields.setValueAtIndex(runtime, i, makeString(runtime, info->fields[i].name)); - } - } - constructor.setProperty(runtime, "fields", fields); - return constructor; -} - -size_t sizeofInteropType(Runtime& runtime, - const std::shared_ptr& bridge, - const Value& value) { - if (auto type = interopTypeFromValue(runtime, bridge, value)) { - return nativeSizeForType(*type); - } - - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime) || - object.isHostObject(runtime) || - object.isHostObject(runtime) || - nativeClassFromJsiObject(runtime, object) != Nil) { - return sizeof(void*); - } - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, &nativePointer)) { - return sizeof(void*); - } - Value sizeValue = object.getProperty(runtime, "sizeof"); - if (sizeValue.isNumber()) { - return static_cast(sizeValue.getNumber()); - } - } - - throw facebook::jsi::JSError(runtime, "Invalid type for interop.sizeof."); -} - -Object createPointer(Runtime& runtime, - const std::shared_ptr& bridge, - void* pointer, bool adopted) { - if (!adopted && bridge != nullptr) { - Value cached = bridge->findPointerValue(runtime, pointer); - if (cached.isObject()) { - return cached.asObject(runtime); - } - } - - Object result = Object::createFromHostObject( - runtime, - std::make_shared(bridge, pointer, "pointer", - adopted)); - if (!adopted && bridge != nullptr) { - bridge->rememberPointerValue(runtime, pointer, Value(runtime, result)); - } - return result; -} - -void installInteropHasInstance(Runtime& runtime, Function& constructor, - const char* kind) { - Value symbolCtorValue = runtime.global().getProperty(runtime, "Symbol"); - if (!symbolCtorValue.isObject()) { - return; - } - - Object symbolCtor = symbolCtorValue.asObject(runtime); - Value hasInstanceValue = symbolCtor.getProperty(runtime, "hasInstance"); - if (!hasInstanceValue.isSymbol()) { - return; - } - - try { - Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); - Function defineProperty = - objectCtor.getPropertyAsFunction(runtime, "defineProperty"); - Object descriptor(runtime); - descriptor.setProperty(runtime, "configurable", true); - descriptor.setProperty( - runtime, "value", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "Symbol.hasInstance"), 1, - [kind = std::string(kind)](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - return false; - } - - Object object = args[0].asObject(runtime); - Value kindValue = object.getProperty(runtime, "kind"); - return kindValue.isString() && - kindValue.asString(runtime).utf8(runtime) == kind; - })); - defineProperty.call(runtime, constructor, hasInstanceValue, descriptor); - } catch (const std::exception&) { - } -} - -Class classFromJsiValue(Runtime& runtime, const Value& value) { - if (value.isString()) { - std::string name = value.asString(runtime).utf8(runtime); - return objc_lookUpClass(name.c_str()); - } - if (!value.isObject()) { - return Nil; - } - Object object = value.asObject(runtime); - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - return cls; - } - if (stringPropertyOrEmpty(runtime, object, "kind") == "class") { - if (void* pointer = pointerFromSymbolLikeObject(runtime, object)) { - return static_cast(pointer); - } - } - if (object.isHostObject(runtime)) { - id nativeObject = object.getHostObject(runtime)->object(); - return nativeObject != nil ? object_getClass(nativeObject) : Nil; - } - return Nil; -} - -Protocol* protocolFromJsiValue(Runtime& runtime, const Value& value) { - if (value.isString()) { - std::string name = value.asString(runtime).utf8(runtime); - Protocol* protocol = objc_getProtocol(name.c_str()); - if (protocol == nullptr) { - constexpr const char* suffix = "Protocol"; - if (name.size() > std::strlen(suffix) && - name.compare(name.size() - std::strlen(suffix), std::strlen(suffix), - suffix) == 0) { - protocol = objc_getProtocol( - name.substr(0, name.size() - std::strlen(suffix)).c_str()); - } - } - return protocol; - } - if (!value.isObject()) { - return nullptr; - } - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime) - ->nativeProtocol(); - } - if (stringPropertyOrEmpty(runtime, object, "kind") == "protocol") { - return static_cast(pointerFromSymbolLikeObject(runtime, object)); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime)->pointer()); - } - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, &nativePointer)) { - return static_cast(nativePointer); - } - Value nameValue = object.getProperty(runtime, "name"); - if (nameValue.isString()) { - return protocolFromJsiValue(runtime, nameValue); - } - return nullptr; -} - -Object createInteropObject(Runtime& runtime, - const std::shared_ptr& bridge) { - Object interop(runtime); - Object types(runtime); - auto setType = [&](const char* name, MDTypeKind kind) { - Object type(runtime); - double code = static_cast(kind); - type.setProperty(runtime, "__nativeApiTypeCode", code); - type.setProperty( - runtime, "valueOf", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "valueOf"), 0, - [code](Runtime&, const Value&, const Value*, size_t) -> Value { - return code; - })); - type.setProperty( - runtime, "toString", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [code](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - char text[32] = {}; - snprintf(text, sizeof(text), "%d", static_cast(code)); - return makeString(runtime, text); - })); - types.setProperty(runtime, name, type); - }; - setType("void", metagen::mdTypeVoid); - setType("bool", metagen::mdTypeBool); - setType("int8", metagen::mdTypeChar); - setType("uint8", metagen::mdTypeUInt8); - setType("int16", metagen::mdTypeSShort); - setType("uint16", metagen::mdTypeUShort); - setType("int32", metagen::mdTypeSInt); - setType("uint32", metagen::mdTypeUInt); - setType("int64", metagen::mdTypeSInt64); - setType("uint64", metagen::mdTypeUInt64); - setType("float", metagen::mdTypeFloat); - setType("double", metagen::mdTypeDouble); - setType("UTF8CString", metagen::mdTypeString); - setType("unichar", metagen::mdTypeUnichar); - setType("id", metagen::mdTypeAnyObject); - setType("class", metagen::mdTypeClass); - setType("protocol", metagen::mdTypeProtocolObject); - setType("SEL", metagen::mdTypeSelector); - setType("selector", metagen::mdTypeSelector); - setType("pointer", metagen::mdTypePointer); - setType("block", metagen::mdTypeBlock); - setType("functionPointer", metagen::mdTypeFunctionPointer); - interop.setProperty(runtime, "types", types); - - Function pointerConstructor = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "Pointer"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count > 0 && args[0].isObject()) { - Object object = args[0].asObject(runtime); - if (object.isHostObject(runtime)) { - return Value(runtime, object); - } - } - void* pointer = nullptr; - if (count > 0 && !args[0].isNull() && !args[0].isUndefined()) { - auto readAddress = [&](const Value& value, - uintptr_t* address) -> bool { - auto readAddressFromString = [&](const Value& source) -> bool { - try { - Value stringCtorValue = - runtime.global().getProperty(runtime, "String"); - if (!stringCtorValue.isObject() || - !stringCtorValue.asObject(runtime).isFunction(runtime)) { - return false; - } - Value stringValue = - stringCtorValue.asObject(runtime).asFunction(runtime) - .call(runtime, source); - if (!stringValue.isString()) { - return false; - } - return parseIntegerTextToUintptr( - stringValue.asString(runtime).utf8(runtime), address); - } catch (const std::exception&) { - return false; - } - }; - - if (value.isNumber()) { - double number = value.getNumber(); - if (!std::isfinite(number)) { - return false; - } - *address = static_cast( - static_cast(number)); - return true; - } - if (value.isBigInt()) { - if (readAddressFromString(value)) { - return true; - } - BigInt bigint = value.getBigInt(runtime); - return parseBigIntToUintptr(runtime, bigint, address); - } - if (value.isObject()) { - Object object = value.asObject(runtime); - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - Value primitive = valueOfValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - if (primitive.isNumber()) { - double number = primitive.getNumber(); - if (!std::isfinite(number)) { - return false; - } - *address = static_cast( - static_cast(number)); - return true; - } - if (primitive.isBigInt()) { - if (readAddressFromString(primitive)) { - return true; - } - BigInt bigint = primitive.getBigInt(runtime); - return parseBigIntToUintptr(runtime, bigint, address); - } - } - return readAddressFromString(value); - } - return false; - }; - - uintptr_t address = 0; - if (!readAddress(args[0], &address)) { - throw facebook::jsi::JSError(runtime, - "Pointer expects a numeric address."); - } - pointer = reinterpret_cast(address); - } - return createPointer(runtime, bridge, pointer); - }); - Object pointerPrototype(runtime); - pointerPrototype.setProperty(runtime, "constructor", pointerConstructor); - pointerConstructor.setProperty(runtime, "prototype", pointerPrototype); - installInteropHasInstance(runtime, pointerConstructor, "pointer"); - pointerConstructor.setProperty(runtime, "kind", makeString(runtime, "pointer")); - pointerConstructor.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - interop.setProperty(runtime, "Pointer", pointerConstructor); - - Function functionReferenceConstructor = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "FunctionReference"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - throw facebook::jsi::JSError( - runtime, "FunctionReference expects a function."); - } - - Object object = args[0].asObject(runtime); - if (!object.isFunction(runtime)) { - throw facebook::jsi::JSError( - runtime, "FunctionReference expects a function."); - } - - Function function = object.asFunction(runtime); - function.setProperty(runtime, "kind", - makeString(runtime, "functionReference")); - function.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - return function; - }); - Object functionReferencePrototype(runtime); - functionReferencePrototype.setProperty(runtime, "constructor", - functionReferenceConstructor); - functionReferenceConstructor.setProperty(runtime, "prototype", - functionReferencePrototype); - installInteropHasInstance(runtime, functionReferenceConstructor, - "functionReference"); - functionReferenceConstructor.setProperty(runtime, "kind", - makeString(runtime, - "functionReference")); - functionReferenceConstructor.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - interop.setProperty(runtime, "FunctionReference", - functionReferenceConstructor); - - Function referenceConstructor = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "Reference"), 2, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - NativeApiJsiType type = primitiveInteropType(metagen::mdTypePointer); - bool firstArgumentIsType = false; - if (count > 1) { - firstArgumentIsType = true; - } else if (count == 1 && args[0].isObject()) { - Object object = args[0].asObject(runtime); - Value typeCodeValue = - object.getProperty(runtime, "__nativeApiTypeCode"); - Value kindValue = object.getProperty(runtime, "kind"); - firstArgumentIsType = - typeCodeValue.isNumber() || object.isFunction(runtime) || - nativeClassFromJsiObject(runtime, object) != Nil || - (kindValue.isString() && - (kindValue.asString(runtime).utf8(runtime) == "class" || - kindValue.asString(runtime).utf8(runtime) == "protocol")); - } - std::optional requestedType = - firstArgumentIsType - ? interopTypeFromValue(runtime, bridge, args[0]) - : std::nullopt; - bool hasType = firstArgumentIsType && requestedType.has_value(); - if (hasType) { - type = *requestedType; - } - - void* data = nullptr; - bool ownsData = false; - size_t byteLength = 0; - std::shared_ptr pendingValue; - if (hasType) { - bool usesExternalStorage = false; - Value valueToStore = Value::undefined(); - if (count > 1) { - valueToStore = Value(runtime, args[1]); - if (args[1].isObject()) { - Object object = args[1].asObject(runtime); - if (object.isHostObject(runtime)) { - data = object - .getHostObject( - runtime) - ->pointer(); - usesExternalStorage = true; - } else if (object.isHostObject( - runtime)) { - auto reference = - object.getHostObject( - runtime); - data = reference->data(); - if (data != nullptr) { - usesExternalStorage = true; - } else { - valueToStore = object.getProperty(runtime, "value"); - } - } else if (type.kind == metagen::mdTypeStruct && - object.isHostObject< - NativeApiStructObjectHostObject>(runtime)) { - data = object - .getHostObject< - NativeApiStructObjectHostObject>(runtime) - ->data(); - usesExternalStorage = true; - } else if (type.kind == metagen::mdTypePointer || - type.kind == metagen::mdTypeOpaquePointer || - type.kind == metagen::mdTypeBlock || - type.kind == metagen::mdTypeFunctionPointer) { - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, - &nativePointer)) { - data = nativePointer; - usesExternalStorage = true; - } - } - } - } - if (!usesExternalStorage) { - byteLength = std::max(nativeSizeForType(type), - sizeof(void*)); - data = calloc(1, byteLength); - if (data == nullptr) { - throw std::bad_alloc(); - } - ownsData = true; - if (count > 1) { - NativeApiJsiArgumentFrame frame(1); - convertJsiArgument(runtime, bridge, type, valueToStore, data, - frame); - } - } - } else if (count > 0) { - pendingValue = std::make_shared(runtime, args[0]); - } - - if (ownsData && data == nullptr) { - throw std::bad_alloc(); - } - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, type, data, ownsData, byteLength, - std::move(pendingValue))); - }); - Object referencePrototype(runtime); - referencePrototype.setProperty(runtime, "constructor", referenceConstructor); - referenceConstructor.setProperty(runtime, "prototype", referencePrototype); - installInteropHasInstance(runtime, referenceConstructor, "reference"); - referenceConstructor.setProperty(runtime, "kind", - makeString(runtime, "reference")); - referenceConstructor.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - interop.setProperty(runtime, "Reference", referenceConstructor); - - interop.setProperty( - runtime, "sizeof", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "sizeof"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1) { - throw facebook::jsi::JSError(runtime, "sizeof expects a type."); - } - return static_cast(sizeofInteropType(runtime, bridge, args[0])); - })); - - interop.setProperty( - runtime, "alloc", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "alloc"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isNumber()) { - throw facebook::jsi::JSError(runtime, "alloc expects a byte size."); - } - size_t size = static_cast(std::max(0, args[0].getNumber())); - return createPointer(runtime, bridge, calloc(1, size), false); - })); - - interop.setProperty( - runtime, "free", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "free"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - return Value::undefined(); - } - Object object = args[0].asObject(runtime); - if (!object.isHostObject(runtime)) { - return Value::undefined(); - } - auto pointer = object.getHostObject(runtime); - void* raw = pointer->pointer(); - if (raw != nullptr) { - free(raw); - pointer->clearWithoutFree(); - } - return Value::undefined(); - })); - - interop.setProperty( - runtime, "adopt", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "adopt"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - throw facebook::jsi::JSError(runtime, "adopt expects a Pointer."); - } - Object object = args[0].asObject(runtime); - if (!object.isHostObject(runtime)) { - throw facebook::jsi::JSError(runtime, "adopt expects a Pointer."); - } - object.getHostObject(runtime)->adopt(); - return Value(runtime, object); - })); - - interop.setProperty( - runtime, "handleof", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "handleof"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || args[0].isNull() || args[0].isUndefined()) { - return Value::null(); - } - if (args[0].isString()) { - std::string utf8 = args[0].asString(runtime).utf8(runtime); - char* data = strdup(utf8.c_str()); - return createPointer(runtime, bridge, data); - } - if (!args[0].isObject()) { - return Value::null(); - } - Object object = args[0].asObject(runtime); - if (object.isHostObject(runtime)) { - return Value(runtime, object); - } - if (object.isHostObject(runtime)) { - void* data = - object.getHostObject(runtime)->data(); - if (data == nullptr) { - throw facebook::jsi::JSError( - runtime, "Cannot get handle of empty Reference."); - } - return createPointer(runtime, bridge, data); - } - if (object.isHostObject(runtime)) { - auto structObject = - object.getHostObject(runtime); - if (structObject->backingValue() != nullptr) { - return Value(runtime, *structObject->backingValue()); - } - return createPointer(runtime, bridge, structObject->data()); - } - if (object.isHostObject(runtime)) { - return createPointer( - runtime, bridge, - object.getHostObject(runtime) - ->object()); - } - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - return createPointer(runtime, bridge, cls); - } - if (object.isHostObject(runtime)) { - return createPointer( - runtime, bridge, - object.getHostObject(runtime) - ->nativeProtocol()); - } - if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { - return createPointer(runtime, bridge, symbolPointer); - } - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, &nativePointer)) { - return createPointer(runtime, bridge, nativePointer); - } - Value kindValue = object.getProperty(runtime, "kind"); - if (kindValue.isString() && - kindValue.asString(runtime).utf8(runtime) == "functionReference") { - throw facebook::jsi::JSError( - runtime, "Cannot get handle of uninitialized FunctionReference."); - } - Value nativeName = object.getProperty(runtime, "nativeName"); - if (nativeName.isString()) { - std::string name = nativeName.asString(runtime).utf8(runtime); - void* symbol = dlsym(bridge->selfDl(), name.c_str()); - if (symbol != nullptr) { - return createPointer(runtime, bridge, symbol); - } - } - return Value::null(); - })); - - interop.setProperty( - runtime, "stringFromCString", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "stringFromCString"), 2, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || args[0].isNull() || args[0].isUndefined()) { - return Value::null(); - } - NativeApiJsiArgumentFrame frame(1); - const char* data = - static_cast(pointerFromJsiValue(runtime, args[0], frame)); - if (data == nullptr) { - return Value::null(); - } - if (count > 1 && args[1].isNumber()) { - size_t length = static_cast(std::max(0, args[1].getNumber())); - return String::createFromUtf8(runtime, - reinterpret_cast(data), - length); - } - return makeString(runtime, data); - })); - - interop.setProperty( - runtime, "bufferFromData", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "bufferFromData"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - throw facebook::jsi::JSError(runtime, "Invalid data."); - } - Object object = args[0].asObject(runtime); - if (object.isArrayBuffer(runtime)) { - return Value(runtime, object); - } - id native = nil; - if (object.isHostObject(runtime)) { - native = object.getHostObject(runtime)->object(); - } else if (object.isHostObject(runtime)) { - native = static_cast( - object.getHostObject(runtime)->pointer()); - } - if (native == nil || ![native isKindOfClass:[NSData class]]) { - throw facebook::jsi::JSError(runtime, "Invalid data."); - } - NSData* data = static_cast(native); - return ArrayBuffer( - runtime, std::make_shared( - data.bytes, static_cast(data.length))); - })); - - interop.setProperty( - runtime, "addMethod", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "addMethod"), 2, - [](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - throw facebook::jsi::JSError( - runtime, - "interop.addMethod requires the JSI class builder layer."); - })); - interop.setProperty( - runtime, "addProtocol", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "addProtocol"), 2, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 2) { - throw facebook::jsi::JSError( - runtime, "interop.addProtocol expects class and protocol."); - } - Class cls = classFromJsiValue(runtime, args[0]); - Protocol* protocol = protocolFromJsiValue(runtime, args[1]); - if (cls == Nil || protocol == nullptr) { - return false; - } - return class_addProtocol(cls, protocol); - })); - - return interop; -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiHostObject.h b/NativeScript/ffi/shared/jsi/NativeApiJsiHostObject.h deleted file mode 100644 index 06b82774d..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiHostObject.h +++ /dev/null @@ -1,560 +0,0 @@ -#ifndef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS -inline bool InstallNativeApiEngineLazyGlobal( - Runtime&, std::shared_ptr, const std::string&, - const std::string&, bool) { - return false; -} -#endif - -class NativeApiHostObject final : public HostObject { - public: - explicit NativeApiHostObject(std::shared_ptr bridge) - : bridge_(std::move(bridge)) {} - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "runtime") { - return makeString(runtime, "jsi"); - } - if (property == "backend") { - return makeString(runtime, "hermes"); - } - if (property == "metadata") { - return metadataObject(runtime); - } - if (property == "hasScheduler") { - return bridge_->scheduler() != nullptr; - } - if (property == "interop") { - return createInteropObject(runtime, bridge_); - } -#ifdef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS - if (property == "__defineLazyGlobal") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__defineLazyGlobal"), 3, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string name = readStringArg(runtime, args, count, 0, "name"); - std::string kind = readStringArg(runtime, args, count, 1, "kind"); - bool force = count > 2 && args[2].isBool() && args[2].getBool(); - return InstallNativeApiEngineLazyGlobal(runtime, bridge, name, kind, - force); - }); - } -#endif - if (property == "__fastEnumeration") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__fastEnumeration"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - throw facebook::jsi::JSError( - runtime, "Fast enumeration expects a native object."); - } - id object = NativeApiObjectHostObject::nativeObjectFromValue(runtime, args[0]); - if (object == nil) { - throw facebook::jsi::JSError( - runtime, "Fast enumeration expects a native object."); - } - if (![object conformsToProtocol:@protocol(NSFastEnumeration)]) { - throw facebook::jsi::JSError( - runtime, "Object does not conform to NSFastEnumeration."); - } - return Object::createFromHostObject( - runtime, - std::make_shared( - bridge, static_cast>(object))); - }); - } - if (property == "runOnUI") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "runOnUI"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - auto scheduler = bridge->scheduler(); - if (scheduler == nullptr) { - throw facebook::jsi::JSError( - runtime, - "NativeApiJsi was installed without a UI scheduler."); - } - - std::shared_ptr callback; - if (count > 0 && !args[0].isNull() && !args[0].isUndefined()) { - if (!args[0].isObject()) { - throw facebook::jsi::JSError( - runtime, "runOnUI expects a function callback."); - } - - Object callbackObject = args[0].asObject(runtime); - if (!callbackObject.isFunction(runtime)) { - throw facebook::jsi::JSError( - runtime, "runOnUI expects a function callback."); - } - callback = std::make_shared( - callbackObject.asFunction(runtime)); - } - - Runtime* runtimePtr = &runtime; - auto promiseCtor = - runtime.global().getPropertyAsFunction(runtime, "Promise"); - return promiseCtor.callAsConstructor( - runtime, - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "runOnUIPromise"), - 2, - [scheduler, runtimePtr, callback]( - Runtime& promiseRuntime, const Value&, - const Value* promiseArgs, - size_t promiseArgc) -> Value { - if (promiseArgc < 2 || !promiseArgs[0].isObject() || - !promiseArgs[1].isObject()) { - return Value::undefined(); - } - - auto resolve = std::make_shared( - promiseArgs[0].asObject(promiseRuntime) - .asFunction(promiseRuntime)); - auto reject = std::make_shared( - promiseArgs[1].asObject(promiseRuntime) - .asFunction(promiseRuntime)); - if (callback == nullptr) { - scheduler->invokeOnUI([scheduler, runtimePtr, resolve]() { - scheduler->invokeOnJS([runtimePtr, resolve]() { - resolve->call(*runtimePtr); - }); - }); - return Value::undefined(); - } - - scheduler->invokeOnJS([runtimePtr, callback, resolve, reject]() { - try { - { - ScopedNativeApiUINativeCallDispatch uiDispatch; - callback->call(*runtimePtr); - } - resolve->call(*runtimePtr); - } catch (const std::exception& error) { - reject->call( - *runtimePtr, - String::createFromUtf8(*runtimePtr, error.what())); - } - }); - - return Value::undefined(); - })); - }); - } - if (property == "import") { - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "import"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string path = readStringArg(runtime, args, count, 0, "path"); - std::string frameworkPath = path; - if (!frameworkPath.empty() && frameworkPath[0] != '/') { - frameworkPath = "/System/Library/Frameworks/" + frameworkPath + - ".framework"; - } - - NSBundle* bundle = [NSBundle - bundleWithPath:[NSString stringWithUTF8String:frameworkPath.c_str()]]; - if (bundle == nil || ![bundle load]) { - throw facebook::jsi::JSError( - runtime, "Could not load bundle: " + frameworkPath); - } - return true; - }); - } - if (property == "lookup") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "lookup"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string symbolName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->find(symbolName); - if (symbol == nullptr) { - return Value::null(); - } - return symbolToObject(runtime, *symbol); - }); - } - if (property == "getClass") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getClass"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string className = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findClass(className); - if (symbol == nullptr) { - Class cls = objc_lookUpClass(className.c_str()); - if (cls == nil) { - return Value::null(); - } - NativeApiSymbol runtimeSymbol{ - .kind = NativeApiSymbolKind::Class, - .offset = MD_SECTION_OFFSET_NULL, - .name = className, - .runtimeName = className, - }; - return makeNativeClassValue(runtime, bridge, - std::move(runtimeSymbol)); - } - - return makeNativeClassValue(runtime, bridge, *symbol); - }); - } - if (property == "__extendClass") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__extendClass"), 2, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - return extendNativeApiJsiClass(runtime, bridge, args, count); - }); - } - if (property == "__invokeBase") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__invokeBase"), 3, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - return invokeNativeApiJsiBaseMethod(runtime, bridge, args, count); - }); - } - if (property == "__rememberClassWrapper") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__rememberClassWrapper"), 3, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 2) { - return Value::undefined(); - } - Class cls = classFromJsiValue(runtime, args[0]); - if (cls == Nil) { - return Value::undefined(); - } - bridge->rememberClassValue(runtime, cls, args[1]); - if (count >= 3 && args[2].isObject()) { - bridge->rememberClassPrototype(runtime, cls, args[2]); - } - return Value::undefined(); - }); - } - if (property == "__rememberObjectClassWrapper") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__rememberObjectClassWrapper"), - 2, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 2) { - return Value::undefined(); - } - id object = NativeApiObjectHostObject::nativeObjectFromValue( - runtime, args[0]); - if (object == nil) { - return Value::undefined(); - } - bridge->setObjectExpando(runtime, object, - "__nativeApiClassWrapper", args[1]); - return Value::undefined(); - }); - } - if (property == "CC_SHA256") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "CC_SHA256"), 3, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 3 || !args[1].isNumber()) { - throw facebook::jsi::JSError( - runtime, "CC_SHA256 expects data, length, and output."); - } - void* commonCrypto = - dlopen("/usr/lib/system/libcommonCrypto.dylib", - RTLD_NOW | RTLD_LOCAL); - void* symbol = commonCrypto != nullptr - ? dlsym(commonCrypto, "CC_SHA256") - : nullptr; - if (symbol == nullptr && commonCrypto != nullptr) { - symbol = dlsym(commonCrypto, "_CC_SHA256"); - } - if (symbol == nullptr) { - throw facebook::jsi::JSError(runtime, - "CC_SHA256 is not available."); - } - NativeApiJsiArgumentFrame frame(3); - void* data = pointerFromJsiValue(runtime, args[0], frame); - void* output = pointerFromJsiValue(runtime, args[2], frame); - using CC_SHA256_Fn = unsigned char* (*)(const void*, unsigned long, - unsigned char*); - auto fn = reinterpret_cast(symbol); - unsigned char* result = - fn(data, static_cast(args[1].getNumber()), - static_cast(output)); - return createPointer(runtime, bridge, result); - }); - } - if (property == "getFunction") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getFunction"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string functionName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findFunction(functionName); - if (symbol == nullptr) { - return Value::null(); - } - auto function = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, symbol->name), 0, - [bridge, symbol = *symbol](Runtime& runtime, const Value&, - const Value* args, - size_t count) -> Value { - return callCFunction(runtime, bridge, symbol, args, count); - }); - function.setProperty(runtime, "kind", makeString(runtime, "function")); - function.setProperty(runtime, "nativeName", - makeString(runtime, symbol->name)); - function.setProperty(runtime, "metadataOffset", - static_cast(symbol->offset)); - function.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - return function; - }); - } - if (property == "getConstant") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getConstant"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string constantName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findConstant(constantName); - if (symbol == nullptr) { - return Value::undefined(); - } - return constantToValue(runtime, bridge, *symbol); - }); - } - if (property == "getEnum") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getEnum"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string enumName = readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findEnum(enumName); - if (symbol == nullptr) { - return Value::undefined(); - } - return enumToObject(runtime, bridge->metadata(), *symbol); - }); - } - if (property == "getProtocol") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getProtocol"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string protocolName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findProtocol(protocolName); - if (symbol == nullptr) { - Protocol* protocol = lookupProtocolByNativeName(protocolName); - if (protocol == nullptr) { - return Value::null(); - } - const char* runtimeName = protocol_getName(protocol); - NativeApiSymbol runtimeSymbol{ - .kind = NativeApiSymbolKind::Protocol, - .offset = MD_SECTION_OFFSET_NULL, - .name = protocolName, - .runtimeName = runtimeName != nullptr ? runtimeName : protocolName, - }; - return makeNativeProtocolValue(runtime, bridge, - std::move(runtimeSymbol)); - } - return makeNativeProtocolValue(runtime, bridge, *symbol); - }); - } - if (property == "getStruct" || property == "getUnion") { - auto bridge = bridge_; - bool isUnion = property == "getUnion"; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 1, - [bridge, isUnion](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string aggregateName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = - isUnion ? bridge->findUnion(aggregateName) - : bridge->findStruct(aggregateName); - if (symbol == nullptr) { - return Value::undefined(); - } - return makeAggregateConstructor(runtime, bridge, *symbol); - }); - } - - if (const NativeApiSymbol* classSymbol = bridge_->findClass(property)) { - return makeNativeClassValue(runtime, bridge_, *classSymbol); - } - - if (const NativeApiSymbol* functionSymbol = bridge_->findFunction(property)) { - auto bridge = bridge_; - Function function = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, symbol = *functionSymbol](Runtime& runtime, const Value&, - const Value* args, - size_t count) -> Value { - return callCFunction(runtime, bridge, symbol, args, count); - }); - function.setProperty(runtime, "kind", makeString(runtime, "function")); - function.setProperty(runtime, "nativeName", - makeString(runtime, functionSymbol->name)); - function.setProperty(runtime, "metadataOffset", - static_cast(functionSymbol->offset)); - function.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - return function; - } - - if (const NativeApiSymbol* constantSymbol = bridge_->findConstant(property)) { - return constantToValue(runtime, bridge_, *constantSymbol); - } - - if (const NativeApiSymbol* enumSymbol = bridge_->findEnum(property)) { - return enumToObject(runtime, bridge_->metadata(), *enumSymbol); - } - - if (const NativeApiSymbol* protocolSymbol = - bridge_->findProtocol(property)) { - return makeNativeProtocolValue(runtime, bridge_, *protocolSymbol); - } - - if (const NativeApiSymbol* aggregateSymbol = - bridge_->findAggregate(property)) { - return makeAggregateConstructor(runtime, bridge_, *aggregateSymbol); - } - - return Value::undefined(); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - names.reserve(11); - addPropertyName(runtime, names, "runtime"); - addPropertyName(runtime, names, "backend"); - addPropertyName(runtime, names, "metadata"); - addPropertyName(runtime, names, "hasScheduler"); - addPropertyName(runtime, names, "interop"); -#ifdef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS - addPropertyName(runtime, names, "__defineLazyGlobal"); -#endif - addPropertyName(runtime, names, "runOnUI"); - addPropertyName(runtime, names, "import"); - addPropertyName(runtime, names, "lookup"); - addPropertyName(runtime, names, "getClass"); - addPropertyName(runtime, names, "__extendClass"); - addPropertyName(runtime, names, "__invokeBase"); - addPropertyName(runtime, names, "__rememberClassWrapper"); - addPropertyName(runtime, names, "__rememberObjectClassWrapper"); - addPropertyName(runtime, names, "getFunction"); - addPropertyName(runtime, names, "getConstant"); - addPropertyName(runtime, names, "getEnum"); - addPropertyName(runtime, names, "getProtocol"); - addPropertyName(runtime, names, "getStruct"); - addPropertyName(runtime, names, "getUnion"); - return names; - } - - private: - Object metadataObject(Runtime& runtime) const { - Object metadata(runtime); - metadata.setProperty(runtime, "classes", - static_cast(bridge_->classCount())); - metadata.setProperty(runtime, "functions", - static_cast(bridge_->functionCount())); - metadata.setProperty(runtime, "constants", - static_cast(bridge_->constantCount())); - metadata.setProperty(runtime, "protocols", - static_cast(bridge_->protocolCount())); - metadata.setProperty(runtime, "enums", - static_cast(bridge_->enumCount())); - metadata.setProperty(runtime, "structs", - static_cast(bridge_->structCount())); - metadata.setProperty(runtime, "unions", - static_cast(bridge_->unionCount())); - - metadata.setProperty( - runtime, "classNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "classNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->classNames()); - })); - metadata.setProperty( - runtime, "functionNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "functionNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->functionNames()); - })); - metadata.setProperty( - runtime, "constantNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "constantNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->constantNames()); - })); - metadata.setProperty( - runtime, "protocolNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "protocolNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->protocolNames()); - })); - metadata.setProperty( - runtime, "enumNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "enumNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->enumNames()); - })); - metadata.setProperty( - runtime, "structNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "structNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->structNames()); - })); - metadata.setProperty( - runtime, "unionNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "unionNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->unionNames()); - })); - return metadata; - } - - std::shared_ptr bridge_; -}; diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiHostObjects.h b/NativeScript/ffi/shared/jsi/NativeApiJsiHostObjects.h deleted file mode 100644 index a1dbf289d..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiHostObjects.h +++ /dev/null @@ -1,1759 +0,0 @@ -class NativeApiPointerHostObject final - : public HostObject, - public std::enable_shared_from_this { - public: - NativeApiPointerHostObject(std::shared_ptr bridge, - void* pointer, std::string kind = "pointer", - bool adopted = false) - : bridge_(std::move(bridge)), - pointer_(pointer), - kind_(std::move(kind)), - adopted_(adopted) {} - - ~NativeApiPointerHostObject() override { - if (adopted_ && pointer_ != nullptr) { - if (bridge_ != nullptr) { - bridge_->forgetPointerValue(pointer_); - } - free(pointer_); - pointer_ = nullptr; - } - } - - void* pointer() const { return pointer_; } - bool adopted() const { return adopted_; } - void adopt() { adopted_ = true; } - void clearWithoutFree() { - if (bridge_ != nullptr) { - bridge_->forgetPointerValue(pointer_); - } - pointer_ = nullptr; - adopted_ = false; - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, kind_); - } - if (property == "address") { - return static_cast(reinterpret_cast(pointer_)); - } - if (property == "adopted") { - return adopted_; - } - if (property == "takeRetainedValue" || property == "takeUnretainedValue") { - bool retained = property == "takeRetainedValue"; - std::weak_ptr weakSelf = shared_from_this(); - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [weakSelf, retained](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - auto self = weakSelf.lock(); - if (!self || self->pointer_ == nullptr || self->consumed_) { - throw facebook::jsi::JSError(runtime, "Unmanaged value has already been consumed."); - } - id object = static_cast(self->pointer_); - self->consumed_ = true; - self->pointer_ = nullptr; - self->adopted_ = false; - return makeNativeObjectValue(runtime, self->bridge_, object, retained); - }); - } - if (property == "add" || property == "subtract") { - void* pointer = pointer_; - bool add = property == "add"; - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 1, - [bridge, pointer, add](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - if (count < 1 || !args[0].isNumber()) { - throw facebook::jsi::JSError(runtime, "Pointer offset must be a number."); - } - intptr_t offset = static_cast(args[0].getNumber()); - intptr_t base = reinterpret_cast(pointer); - void* result = reinterpret_cast(add ? base + offset : base - offset); - return createPointer(runtime, bridge, result); - }); - } - if (property == "toNumber") { - void* pointer = pointer_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toNumber"), 0, - [pointer](Runtime&, const Value&, const Value*, size_t) -> Value { - return static_cast(reinterpret_cast(pointer)); - }); - } - if (property == "toBigInt") { - void* pointer = pointer_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toBigInt"), 0, - [pointer](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return BigInt::fromUint64( - runtime, - static_cast(reinterpret_cast(pointer))); - }); - } - if (property == "toHexString" || property == "toDecimalString") { - void* pointer = pointer_; - bool hex = property == "toHexString"; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [pointer, hex](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - if (hex) { - char text[2 + sizeof(uintptr_t) * 2 + 1] = {}; - snprintf(text, sizeof(text), "0x%llx", - static_cast( - reinterpret_cast(pointer))); - return makeString(runtime, text); - } else { - char text[32] = {}; - snprintf(text, sizeof(text), "%lld", - static_cast(reinterpret_cast(pointer))); - return makeString(runtime, text); - } - }); - } - if (property == "toString") { - void* pointer = pointer_; - std::string kind = kind_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [pointer, kind](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", pointer); - if (kind == "pointer") { - return makeString(runtime, - ""); - } - return makeString(runtime, "[NativeApiJsi " + kind + " " + - std::string(address) + "]"); - }); - } - return Value::undefined(); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - names.reserve(3); - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "address"); - addPropertyName(runtime, names, "adopted"); - addPropertyName(runtime, names, "takeRetainedValue"); - addPropertyName(runtime, names, "takeUnretainedValue"); - addPropertyName(runtime, names, "add"); - addPropertyName(runtime, names, "subtract"); - addPropertyName(runtime, names, "toNumber"); - addPropertyName(runtime, names, "toBigInt"); - addPropertyName(runtime, names, "toHexString"); - addPropertyName(runtime, names, "toDecimalString"); - addPropertyName(runtime, names, "toString"); - return names; - } - - private: - std::shared_ptr bridge_; - void* pointer_ = nullptr; - std::string kind_; - bool adopted_ = false; - bool consumed_ = false; -}; - -class NativeApiReferenceHostObject final : public HostObject { - public: - NativeApiReferenceHostObject(std::shared_ptr bridge, - NativeApiJsiType type, void* data, bool ownsData, - size_t byteLength = 0, - std::shared_ptr pendingValue = nullptr, - std::shared_ptr backingValue = nullptr) - : bridge_(std::move(bridge)), - type_(std::move(type)), - data_(data), - ownsData_(ownsData), - byteLength_(byteLength), - pendingValue_(std::move(pendingValue)), - backingValue_(std::move(backingValue)) {} - - ~NativeApiReferenceHostObject() override { - if (ownsData_ && data_ != nullptr) { - free(data_); - data_ = nullptr; - } - } - - void* data() const { return data_; } - const NativeApiJsiType& type() const { return type_; } - void ensureStorage(Runtime& runtime, NativeApiJsiType type, - NativeApiJsiArgumentFrame& frame, size_t elements = 1); - - Value get(Runtime& runtime, const PropNameID& name) override; - void set(Runtime& runtime, const PropNameID& name, const Value& value) override; - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "value"); - addPropertyName(runtime, names, "address"); - addPropertyName(runtime, names, "toString"); - return names; - } - - private: - std::shared_ptr bridge_; - NativeApiJsiType type_; - void* data_ = nullptr; - bool ownsData_ = false; - size_t byteLength_ = 0; - std::shared_ptr pendingValue_; - std::shared_ptr backingValue_; -}; - -class NativeApiStructObjectHostObject final : public HostObject { - public: - NativeApiStructObjectHostObject( - std::shared_ptr bridge, - std::shared_ptr info, - const void* data = nullptr, bool ownsData = true, - std::shared_ptr> storageOwner = nullptr, - std::shared_ptr backingValue = nullptr) - : bridge_(std::move(bridge)), - info_(std::move(info)), - ownedData_(std::move(storageOwner)), - backingValue_(std::move(backingValue)), - ownsData_(ownsData) { - size_t size = info_ != nullptr ? info_->size : 0; - if (ownedData_ != nullptr) { - data_ = const_cast(data); - ownsData_ = false; - } else if (ownsData_) { - ownedData_ = std::make_shared>(size, 0); - if (data != nullptr && size > 0) { - std::memcpy(ownedData_->data(), data, size); - } - data_ = ownedData_->empty() ? nullptr : ownedData_->data(); - } else { - data_ = const_cast(data); - } - } - - void* data() const { return data_; } - std::shared_ptr info() const { return info_; } - std::shared_ptr> storageOwner() const { - return ownedData_; - } - std::shared_ptr backingValue() const { return backingValue_; } - - Value get(Runtime& runtime, const PropNameID& name) override; - void set(Runtime& runtime, const PropNameID& name, const Value& value) override; - std::vector getPropertyNames(Runtime& runtime) override; - - private: - std::shared_ptr bridge_; - std::shared_ptr info_; - std::shared_ptr> ownedData_; - std::shared_ptr backingValue_; - void* data_ = nullptr; - bool ownsData_ = true; -}; - -class NativeApiFastEnumerationIteratorHostObject final : public HostObject { - public: - NativeApiFastEnumerationIteratorHostObject( - std::shared_ptr bridge, id collection) - : bridge_(std::move(bridge)), collection_(collection) { - [(id)collection_ retain]; - } - - ~NativeApiFastEnumerationIteratorHostObject() override { - [(id)collection_ release]; - collection_ = nil; - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "next") { - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "next"), 0, - [this](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return next(runtime); - }); - } - return Value::undefined(); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - addPropertyName(runtime, names, "next"); - return names; - } - - private: - Value next(Runtime& runtime) { - Object result(runtime); - if (done_ || collection_ == nil) { - result.setProperty(runtime, "done", true); - return result; - } - - if (stackIndex_ >= stackLength_) { - stackLength_ = [collection_ countByEnumeratingWithState:&state_ - objects:stack_ - count:16]; - stackIndex_ = 0; - if (stackLength_ == 0) { - done_ = true; - result.setProperty(runtime, "done", true); - return result; - } - } - - id value = state_.itemsPtr[stackIndex_++]; - NativeApiJsiType valueType = nativeObjectReturnTypeForClass(object_getClass(value)); - result.setProperty(runtime, "value", - convertNativeReturnValue(runtime, bridge_, valueType, &value)); - result.setProperty(runtime, "done", false); - return result; - } - - std::shared_ptr bridge_; - id collection_ = nil; - NSFastEnumerationState state_ = {}; - id __unsafe_unretained stack_[16] = {}; - NSUInteger stackLength_ = 0; - NSUInteger stackIndex_ = 0; - bool done_ = false; -}; - -NativeApiSymbol nativeApiSymbolForRuntimeClass( - const std::shared_ptr& bridge, Class cls) { - const char* name = cls != Nil ? class_getName(cls) : ""; - if (bridge != nullptr) { - if (const NativeApiSymbol* symbol = bridge->findClassForRuntimePointer(cls)) { - return *symbol; - } - if (const NativeApiSymbol* symbol = bridge->findClassForRuntimeClass(cls)) { - return *symbol; - } - if (name != nullptr) { - if (const NativeApiSymbol* symbol = bridge->findClass(name)) { - return *symbol; - } - } - } - - return NativeApiSymbol{ - .kind = NativeApiSymbolKind::Class, - .offset = MD_SECTION_OFFSET_NULL, - .name = name != nullptr ? name : "", - .runtimeName = name != nullptr ? name : "", - }; -} - -class NativeApiSuperHostObject final : public HostObject { - public: - NativeApiSuperHostObject(std::shared_ptr bridge, - id receiver, Class dispatchClass) - : bridge_(std::move(bridge)), - receiver_(receiver), - dispatchClass_(dispatchClass) { - if (receiver_ != nil) { - [receiver_ retain]; - } - } - - ~NativeApiSuperHostObject() override { - if (receiver_ != nil) { - [receiver_ release]; - receiver_ = nil; - } - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "super"); - } - if (property == "toString") { - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return makeString(runtime, "[NativeApiJsiSuper]"); - }); - } - if (receiver_ == nil || dispatchClass_ == Nil) { - return Value::undefined(); - } - - if (const NativeApiSymbol* symbol = - bridge_->findClassForRuntimeClass(dispatchClass_)) { - const auto& members = bridge_->membersForClass(*symbol); - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, false)) { - SEL selector = sel_getUid(propertyMember->selectorName.c_str()); - if (class_getInstanceMethod(dispatchClass_, selector) != nullptr) { - return callObjCSelector(runtime, bridge_, receiver_, false, - propertyMember->selectorName, propertyMember, - nullptr, 0, dispatchClass_); - } - } - - if (selectMethodMember(members, property, false, 0) != nullptr) { - auto bridge = bridge_; - id receiver = receiver_; - Class dispatchClass = dispatchClass_; - std::string memberName = property; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, receiver, dispatchClass, memberName]( - Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - const NativeApiSymbol* symbol = - bridge->findClassForRuntimeClass(dispatchClass); - if (symbol == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C metadata is not available for super."); - } - const NativeApiMember* selected = selectMethodMember( - bridge->membersForClass(*symbol), memberName, false, count); - if (selected == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C super selector is not available: " + - memberName); - } - return callObjCSelector(runtime, bridge, receiver, false, - selected->selectorName, selected, args, - count, dispatchClass); - }); - } - } - - if (auto selectorName = - runtimeSelectorNameForProperty(dispatchClass_, false, property)) { - auto bridge = bridge_; - id receiver = receiver_; - Class dispatchClass = dispatchClass_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, receiver, dispatchClass, selectorName = *selectorName]( - Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - return callObjCSelector(runtime, bridge, receiver, false, - selectorName, nullptr, args, count, - dispatchClass); - }); - } - - return Value::undefined(); - } - - void set(Runtime& runtime, const PropNameID& name, const Value& value) override { - std::string property = name.utf8(runtime); - if (receiver_ == nil || dispatchClass_ == Nil) { - throw facebook::jsi::JSError(runtime, "Cannot set property on nil super."); - } - - if (const NativeApiSymbol* symbol = - bridge_->findClassForRuntimeClass(dispatchClass_)) { - const auto& members = bridge_->membersForClass(*symbol); - if (const NativeApiMember* propertyMember = - selectWritablePropertyMember(members, property, false)) { - if (propertyMember->readonly || - propertyMember->setterSelectorName.empty()) { - throw facebook::jsi::JSError( - runtime, "Attempted to assign to readonly property."); - } - NativeApiMember setterMember = *propertyMember; - setterMember.selectorName = propertyMember->setterSelectorName; - setterMember.signatureOffset = propertyMember->setterSignatureOffset; - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, receiver_, false, - setterMember.selectorName, &setterMember, args, 1, - dispatchClass_); - return; - } - } - - std::string setterSelectorName = setterSelectorForProperty(property); - SEL selector = sel_getUid(setterSelectorName.c_str()); - if (class_getInstanceMethod(dispatchClass_, selector) != nullptr) { - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, receiver_, false, setterSelectorName, - nullptr, args, 1, dispatchClass_); - return; - } - - throw facebook::jsi::JSError(runtime, - "No writable native super property: " + - property); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "toString"); - return names; - } - - private: - std::shared_ptr bridge_; - id receiver_ = nil; - Class dispatchClass_ = Nil; -}; - -class NativeApiObjectHostObject final - : public HostObject, - public std::enable_shared_from_this { - public: - NativeApiObjectHostObject(std::shared_ptr bridge, - id object, bool ownsObject) - : bridge_(std::move(bridge)), object_(object), ownsObject_(ownsObject) { - if (object_ != nil && !ownsObject_) { - [object_ retain]; - ownsObject_ = true; - } - } - - ~NativeApiObjectHostObject() override { - if (ownsObject_ && object_ != nil) { - [object_ release]; - object_ = nil; - } - } - - id object() const { return object_; } - - void disownObject(id expected) { - if (object_ == expected) { - ownsObject_ = false; - object_ = nil; - } - } - - static bool isInitializerSelector(const std::string& selectorName) { - return selectorName.rfind("init", 0) == 0; - } - - static id nativeObjectFromValue(Runtime& runtime, const Value& value) { - if (!value.isObject()) { - return nil; - } - Object object = value.asObject(runtime); - if (!object.isHostObject(runtime)) { - return nil; - } - return object.getHostObject(runtime)->object(); - } - - Value callObjectSelector(Runtime& runtime, const std::string& selectorName, - const NativeApiMember* member, const Value* args, - size_t count, Class dispatchSuperClass = Nil) { - id receiver = object_; - if (receiver == nil) { - throw facebook::jsi::JSError(runtime, - "Cannot send Objective-C selector to nil."); - } - - const bool initializer = isInitializerSelector(selectorName); - std::optional classWrapper; - if (initializer) { - Value classWrapperValue = bridge_->findObjectExpando( - runtime, receiver, "__nativeApiClassWrapper"); - if (classWrapperValue.isObject()) { - classWrapper.emplace(classWrapperValue.asObject(runtime)); - } - bridge_->forgetRoundTripValue(receiver); - bridge_->forgetObjectExpandos(receiver); - } - - Value result = - callObjCSelector(runtime, bridge_, receiver, false, selectorName, member, - args, count, dispatchSuperClass); - if (initializer) { - if (nativeObjectFromValue(runtime, result) != receiver) { - disownObject(receiver); - } else if (classWrapper) { - bridge_->setObjectExpando(runtime, receiver, "__nativeApiClassWrapper", - Value(runtime, *classWrapper)); - } - } - return result; - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "object"); - } - if (property == "className") { - return makeString(runtime, object_ != nil ? object_getClassName(object_) : ""); - } - if (property == "nativeAddress") { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", object_); - return makeString(runtime, address); - } - if (property == "class") { - auto bridge = bridge_; - id object = object_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "class"), 0, - [bridge, object](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - if (object == nil) { - return Value::undefined(); - } - Value classWrapper = bridge->findObjectExpando( - runtime, object, "__nativeApiClassWrapper"); - if (classWrapper.isObject()) { - return classWrapper; - } - NativeApiSymbol symbol = - nativeApiSymbolForRuntimeClass(bridge, object_getClass(object)); - return makeNativeClassValue(runtime, bridge, std::move(symbol)); - }); - } - if (property == "constructor") { - if (object_ == nil) { - return Value::undefined(); - } - Value classWrapper = bridge_->findObjectExpando( - runtime, object_, "__nativeApiClassWrapper"); - if (classWrapper.isObject()) { - return classWrapper; - } - NativeApiSymbol symbol = - nativeApiSymbolForRuntimeClass(bridge_, object_getClass(object_)); - return makeNativeClassValue(runtime, bridge_, std::move(symbol)); - } - if (property == "superclass") { - if (object_ == nil) { - return Value::undefined(); - } - Class superclass = class_getSuperclass(object_getClass(object_)); - if (superclass == Nil) { - return Value::null(); - } - NativeApiSymbol symbol = nativeApiSymbolForRuntimeClass(bridge_, superclass); - return makeNativeClassValue(runtime, bridge_, std::move(symbol)); - } - if (property == "super") { - Class dispatchClass = - object_ != nil ? class_getSuperclass(object_getClass(object_)) : Nil; - return Object::createFromHostObject( - runtime, - std::make_shared(bridge_, object_, - dispatchClass)); - } - if (property == "invoke" || property == "send") { - auto bridge = bridge_; - id object = object_; - std::weak_ptr weakSelf = shared_from_this(); - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 1, - [bridge, object, weakSelf](Runtime& runtime, const Value&, - const Value* args, - size_t count) -> Value { - std::string selectorName = - readStringArg(runtime, args, count, 0, "selector"); - if (auto self = weakSelf.lock()) { - return self->callObjectSelector(runtime, selectorName, nullptr, - args + 1, count - 1); - } - return callObjCSelector(runtime, bridge, object, false, selectorName, - nullptr, args + 1, count - 1); - }); - } - if (property == "takeRetainedValue" || property == "takeUnretainedValue") { - bool retained = property == "takeRetainedValue"; - std::weak_ptr weakSelf = shared_from_this(); - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [weakSelf, retained](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - auto self = weakSelf.lock(); - if (!self || self->object_ == nil || self->consumed_) { - throw facebook::jsi::JSError(runtime, "Unmanaged value has already been consumed."); - } - - id object = self->object_; - if (self->bridge_ != nullptr) { - self->bridge_->forgetRoundTripValue(object); - } - if (self->ownsObject_) { - [object release]; - } - self->object_ = nil; - self->ownsObject_ = false; - self->consumed_ = true; - return makeNativeObjectValue(runtime, self->bridge_, object, retained); - }); - } - if (property == "toString") { - id object = object_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [object](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - NSString* description = - object != nil ? [object description] : @""; - return makeString(runtime, description.UTF8String ?: ""); - }); - } - if (property == "URL" && object_ != nil && - [object_ respondsToSelector:@selector(URL)]) { - return callObjectSelector(runtime, "URL", nullptr, nullptr, 0); - } - if (property == "Symbol.iterator" || - property == "Symbol(Symbol.iterator)" || - property == "@@iterator") { - auto bridge = bridge_; - id object = object_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "Symbol.iterator"), 0, - [bridge, object](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - if (object == nil || - ![object conformsToProtocol:@protocol(NSFastEnumeration)]) { - throw facebook::jsi::JSError( - runtime, "Object does not conform to NSFastEnumeration."); - } - return Object::createFromHostObject( - runtime, - std::make_shared( - bridge, static_cast>(object))); - }); - } - -#if TARGET_OS_OSX - if (property == "initWithRedGreenBlueAlpha") { - Class nsColorClass = NSClassFromString(@"NSColor"); - if (object_ != nil && nsColorClass != Nil && - [object_ isKindOfClass:nsColorClass]) { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 4, - [bridge, nsColorClass](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - const char* selectors[] = { - "colorWithSRGBRed:green:blue:alpha:", - "colorWithCalibratedRed:green:blue:alpha:", - "colorWithDeviceRed:green:blue:alpha:", - }; - for (const char* selectorName : selectors) { - if (class_getClassMethod(nsColorClass, - sel_getUid(selectorName)) != nullptr) { - return callObjCSelector(runtime, bridge, - static_cast(nsColorClass), true, - selectorName, nullptr, args, count); - } - } - throw facebook::jsi::JSError( - runtime, "NSColor RGB initializer is not available."); - }); - } - } -#endif - - if (property == "initWithFireDateIntervalTargetSelectorUserInfoRepeats") { - Class timerClass = NSClassFromString(@"NSTimer"); - if (object_ != nil && timerClass != Nil && - [object_ isKindOfClass:timerClass]) { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 6, - [bridge, timerClass](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - if (count < 6) { - throw facebook::jsi::JSError( - runtime, "NSTimer initializer expects six arguments."); - } - return callObjCSelector( - runtime, bridge, static_cast(timerClass), true, - "timerWithTimeInterval:target:selector:userInfo:repeats:", - nullptr, args + 1, count - 1); - }); - } - } - - Value expando = bridge_->findObjectExpando(runtime, object_, property); - if (!expando.isUndefined()) { - return expando; - } - - if (object_ != nil) { - try { - Value receiver = bridge_->findRoundTripValue(runtime, object_); - Value resolverValue = runtime.global().getProperty( - runtime, "__nativeScriptGetNativeApiPrototypeProperty"); - if (receiver.isObject() && resolverValue.isObject() && - resolverValue.asObject(runtime).isFunction(runtime)) { - Value prototype = - bridge_->findClassPrototype(runtime, object_getClass(object_)); - Value prototypeOrName = prototype.isObject() - ? Value(runtime, prototype) - : Value::undefined(); - if (prototypeOrName.isUndefined()) { - Value classWrapper = bridge_->findObjectExpando( - runtime, object_, "__nativeApiClassWrapper"); - if (classWrapper.isObject()) { - Object wrapperObject = classWrapper.asObject(runtime); - Value wrapperPrototype = - wrapperObject.getProperty(runtime, "prototype"); - if (wrapperPrototype.isObject()) { - prototypeOrName = std::move(wrapperPrototype); - } - } - } - if (prototypeOrName.isUndefined()) { - const char* className = object_getClassName(object_); - prototypeOrName = makeString(runtime, - className != nullptr ? className : ""); - } - Value resolved = resolverValue.asObject(runtime) - .asFunction(runtime) - .call(runtime, std::move(prototypeOrName), - Value(runtime, receiver), - makeString(runtime, property)); - if (resolved.isObject()) { - Object result = resolved.asObject(runtime); - Value found = result.getProperty(runtime, "found"); - if (found.isBool() && found.getBool()) { - return result.getProperty(runtime, "value"); - } - } - } - } catch (const std::exception&) { - } - } - - if (object_ != nil && [object_ isKindOfClass:[NSArray class]]) { - NSArray* array = static_cast(object_); - if (property == "length") { - return static_cast(array.count); - } - if (auto index = parseArrayIndexProperty(property)) { - if (*index >= array.count) { - return Value::undefined(); - } - id element = [array objectAtIndex:*index]; - NativeApiJsiType elementType = nativeObjectReturnType(); - return convertNativeReturnValue(runtime, bridge_, elementType, &element); - } - } - - if (object_ != nil) { - if (const NativeApiSymbol* symbol = - bridge_->findClassForRuntimeClass(object_getClass(object_))) { - const auto& members = bridge_->membersForClass(*symbol); - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, false)) { - SEL selector = sel_getUid(propertyMember->selectorName.c_str()); - if ([object_ respondsToSelector:selector]) { - return callObjectSelector(runtime, propertyMember->selectorName, - propertyMember, nullptr, 0); - } - std::string booleanSelectorName = - booleanGetterSelectorForProperty(property); - if (booleanSelectorName != propertyMember->selectorName) { - SEL booleanSelector = sel_getUid(booleanSelectorName.c_str()); - if ([object_ respondsToSelector:booleanSelector]) { - NativeApiMember getterMember = *propertyMember; - getterMember.selectorName = booleanSelectorName; - return callObjectSelector(runtime, getterMember.selectorName, - &getterMember, nullptr, 0); - } - } - } - - if (selectMethodMember(members, property, false, 0) != nullptr) { - auto bridge = bridge_; - id object = object_; - std::weak_ptr weakSelf = - shared_from_this(); - std::string memberName = property; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), - 0, - [bridge, object, weakSelf, memberName](Runtime& runtime, - const Value&, - const Value* args, - size_t count) -> Value { - const NativeApiSymbol* symbol = - bridge->findClassForRuntimeClass(object_getClass(object)); - if (symbol == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C metadata is not available for object."); - } - const NativeApiMember* selected = selectMethodMember( - bridge->membersForClass(*symbol), memberName, false, count); - if (selected == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C selector is not available: " + - memberName); - } - if (auto self = weakSelf.lock()) { - return self->callObjectSelector( - runtime, selected->selectorName, selected, args, count); - } - return callObjCSelector(runtime, bridge, object, false, - selected->selectorName, selected, args, - count); - }); - } - } - - if (auto selectorName = - runtimeSelectorNameForProperty(object_getClass(object_), false, - property)) { - if (selectorArgumentCount(*selectorName) == 0 && - hasRuntimeSetterForProperty(object_getClass(object_), false, - property)) { - return callObjectSelector(runtime, *selectorName, nullptr, nullptr, 0); - } - - auto bridge = bridge_; - id object = object_; - std::weak_ptr weakSelf = shared_from_this(); - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, object, weakSelf, selectorName = *selectorName]( - Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (auto self = weakSelf.lock()) { - return self->callObjectSelector(runtime, selectorName, nullptr, - args, count); - } - return callObjCSelector(runtime, bridge, object, false, - selectorName, nullptr, args, count); - }); - } - - if ([object_ isKindOfClass:[NSDictionary class]]) { - NSString* key = [NSString stringWithUTF8String:property.c_str()]; - if (key != nil) { - id value = [static_cast(object_) objectForKey:key]; - if (value != nil) { - NativeApiJsiType valueType = nativeObjectReturnType(); - return convertNativeReturnValue(runtime, bridge_, valueType, &value); - } - } - } - } - - return Value::undefined(); - } - - void set(Runtime& runtime, const PropNameID& name, const Value& value) override { - std::string property = name.utf8(runtime); - if (object_ == nil) { - throw facebook::jsi::JSError(runtime, "Cannot set property on nil object."); - } - - if (const NativeApiSymbol* symbol = - bridge_->findClassForRuntimeClass(object_getClass(object_))) { - const auto& members = bridge_->membersForClass(*symbol); - if (const NativeApiMember* propertyMember = - selectWritablePropertyMember(members, property, false)) { - if (propertyMember->readonly) { - throw facebook::jsi::JSError( - runtime, "Attempted to assign to readonly property."); - } - NativeApiMember setterMember = *propertyMember; - setterMember.selectorName = propertyMember->setterSelectorName; - setterMember.signatureOffset = propertyMember->setterSignatureOffset; - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, object_, false, - setterMember.selectorName, &setterMember, args, 1); - return; - } - } - - std::string setterSelectorName = setterSelectorForProperty(property); - SEL selector = sel_getUid(setterSelectorName.c_str()); - if ([object_ respondsToSelector:selector]) { - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, object_, false, setterSelectorName, - nullptr, args, 1); - return; - } - - bridge_->setObjectExpando(runtime, object_, property, value); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - names.reserve(6); - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "className"); - addPropertyName(runtime, names, "nativeAddress"); - addPropertyName(runtime, names, "constructor"); - addPropertyName(runtime, names, "superclass"); - addPropertyName(runtime, names, "super"); - addPropertyName(runtime, names, "invoke"); - addPropertyName(runtime, names, "send"); - addPropertyName(runtime, names, "takeRetainedValue"); - addPropertyName(runtime, names, "takeUnretainedValue"); - addPropertyName(runtime, names, "toString"); - return names; - } - - private: - std::shared_ptr bridge_; - id object_ = nil; - bool ownsObject_ = false; - bool consumed_ = false; -}; - -class NativeApiClassHostObject final : public HostObject { - public: - NativeApiClassHostObject(std::shared_ptr bridge, - NativeApiSymbol symbol) - : bridge_(std::move(bridge)), symbol_(std::move(symbol)) {} - - Class nativeClass() const { - return objc_lookUpClass(symbol_.runtimeName.c_str()); - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "class"); - } - if (property == "name") { - return makeString(runtime, symbol_.name); - } - if (property == "runtimeName") { - return makeString(runtime, symbol_.runtimeName); - } - if (property == "available") { - return objc_lookUpClass(symbol_.runtimeName.c_str()) != nil; - } - if (property == "metadataOffset") { - return static_cast(symbol_.offset); - } - if (property == "__superclass") { - if (symbol_.superclassOffset == MD_SECTION_OFFSET_NULL) { - return Value::undefined(); - } - const NativeApiSymbol* superclass = - bridge_->findClassByOffset(symbol_.superclassOffset); - if (superclass == nullptr) { - return Value::undefined(); - } - return makeNativeClassValue(runtime, bridge_, *superclass); - } - if (property == "__staticMembers" || property == "__instanceMembers") { - bool staticMembers = property == "__staticMembers"; - const auto& members = bridge_->surfaceMembersForClass(symbol_); - Array result(runtime, members.size()); - size_t index = 0; - for (const auto& member : members) { - bool memberIsStatic = - (member.flags & metagen::mdMemberStatic) != 0; - if (memberIsStatic != staticMembers) { - continue; - } - Object descriptor(runtime); - descriptor.setProperty(runtime, "name", makeString(runtime, member.name)); - descriptor.setProperty(runtime, "selectorName", - makeString(runtime, member.selectorName)); - descriptor.setProperty( - runtime, "argumentCount", - static_cast(selectorArgumentCount(member.selectorName))); - descriptor.setProperty(runtime, "property", member.property); - descriptor.setProperty(runtime, "readonly", member.readonly); - descriptor.setProperty(runtime, "setterSelectorName", - makeString(runtime, member.setterSelectorName)); - result.setValueAtIndex(runtime, index++, descriptor); - } - Array compact(runtime, index); - for (size_t i = 0; i < index; i++) { - compact.setValueAtIndex(runtime, i, result.getValueAtIndex(runtime, i)); - } - return compact; - } - if (property == "toString") { - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [symbol = symbol_](Runtime& runtime, const Value&, - const Value*, size_t) -> Value { - return makeString(runtime, - "[NativeApiJsiClass " + symbol.name + "]"); - }); - } - if (property == "construct" || property == "alloc" || property == "new") { - auto bridge = bridge_; - auto symbol = symbol_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property), 0, - [bridge, symbol, property](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol.name); - } - - id result = nil; - if (property == "construct" && count == 1) { - void* pointer = nullptr; - if (args[0].isNumber()) { - pointer = reinterpret_cast( - static_cast(args[0].getNumber())); - } else if (args[0].isObject()) { - Object object = args[0].asObject(runtime); - if (object.isHostObject(runtime)) { - pointer = object - .getHostObject( - runtime) - ->pointer(); - } else if (object.isHostObject( - runtime)) { - pointer = object - .getHostObject( - runtime) - ->data(); - } else if (object.isHostObject( - runtime)) { - pointer = object - .getHostObject( - runtime) - ->object(); - } - } - return makeNativeObjectValue(runtime, bridge, - static_cast(pointer), false); - } - - if (property == "new") { - if (count != 0) { - throw facebook::jsi::JSError( - runtime, "new does not take arguments; use invoke for an " - "explicit Objective-C selector."); - } - result = [[cls alloc] init]; - } else { - if (count != 0) { - throw facebook::jsi::JSError( - runtime, "alloc does not take arguments; call invoke on the " - "allocated object for an explicit init selector."); - } - result = [cls alloc]; - } - - return makeNativeObjectValue(runtime, bridge, result, true); - }); - } - if (property == "invoke" || property == "send") { - auto bridge = bridge_; - auto symbol = symbol_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 1, - [bridge, symbol](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string selectorName = - readStringArg(runtime, args, count, 0, "selector"); - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol.name); - } - return callObjCSelector(runtime, bridge, static_cast(cls), true, - selectorName, nullptr, args + 1, - count - 1); - }); - } - - const auto& members = bridge_->membersForClass(symbol_); - if (const NativeApiMember* propertyMember = - selectWritablePropertyMember(members, property, true)) { - auto bridge = bridge_; - auto symbol = symbol_; - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol.name); - } - SEL selector = sel_getUid(propertyMember->selectorName.c_str()); - if (class_getClassMethod(cls, selector) != nullptr) { - return callObjCSelector(runtime, bridge, static_cast(cls), true, - propertyMember->selectorName, propertyMember, - nullptr, 0); - } - } - - if (selectMethodMember(members, property, true, 0) != nullptr) { - auto bridge = bridge_; - auto symbol = symbol_; - std::string memberName = property; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, symbol, memberName](Runtime& runtime, const Value&, - const Value* args, - size_t count) -> Value { - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol.name); - } - const NativeApiMember* selected = selectMethodMember( - bridge->membersForClass(symbol), memberName, true, count); - if (selected == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C selector is not available: " + - memberName); - } - return callObjCSelector(runtime, bridge, static_cast(cls), true, - selected->selectorName, selected, args, - count); - }); - } - - Class cls = objc_lookUpClass(symbol_.runtimeName.c_str()); - if (cls != nil) { - if (auto selectorName = - runtimeSelectorNameForProperty(cls, true, property)) { - if (selectorArgumentCount(*selectorName) == 0 && - hasRuntimeSetterForProperty(cls, true, property)) { - return callObjCSelector(runtime, bridge_, static_cast(cls), true, - *selectorName, nullptr, nullptr, 0); - } - - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, cls, selectorName = *selectorName]( - Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - return callObjCSelector(runtime, bridge, static_cast(cls), - true, selectorName, nullptr, args, - count); - }); - } - } - - return Value::undefined(); - } - - void set(Runtime& runtime, const PropNameID& name, const Value& value) override { - std::string property = name.utf8(runtime); - Class cls = objc_lookUpClass(symbol_.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol_.name); - } - - const auto& members = bridge_->membersForClass(symbol_); - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, true)) { - if (propertyMember->readonly) { - throw facebook::jsi::JSError( - runtime, "Attempted to assign to readonly property."); - } - NativeApiMember setterMember = *propertyMember; - setterMember.selectorName = propertyMember->setterSelectorName; - setterMember.signatureOffset = propertyMember->setterSignatureOffset; - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, static_cast(cls), true, - setterMember.selectorName, &setterMember, args, 1); - return; - } - - std::string setterSelectorName = setterSelectorForProperty(property); - SEL selector = sel_getUid(setterSelectorName.c_str()); - if (class_getClassMethod(cls, selector) != nullptr) { - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, static_cast(cls), true, - setterSelectorName, nullptr, args, 1); - return; - } - - throw facebook::jsi::JSError(runtime, - "No writable native property: " + property); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - names.reserve(8); - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "name"); - addPropertyName(runtime, names, "runtimeName"); - addPropertyName(runtime, names, "available"); - addPropertyName(runtime, names, "metadataOffset"); - addPropertyName(runtime, names, "toString"); - addPropertyName(runtime, names, "construct"); - addPropertyName(runtime, names, "alloc"); - addPropertyName(runtime, names, "new"); - addPropertyName(runtime, names, "invoke"); - addPropertyName(runtime, names, "send"); - return names; - } - - private: - std::shared_ptr bridge_; - NativeApiSymbol symbol_; -}; - -Value makeNativeObjectValue(Runtime& runtime, - const std::shared_ptr& bridge, - id object, bool ownsObject) { - if (object == nil) { - return Value::null(); - } - - Value cached = bridge->findRoundTripValue(runtime, object); - if (!cached.isUndefined()) { - if (ownsObject) { - [object release]; - } - return cached; - } - - Object result = Object::createFromHostObject( - runtime, - std::make_shared(bridge, object, ownsObject)); - bridge->rememberRoundTripValue(runtime, object, Value(runtime, result)); - return result; -} - -Value globalNativeSymbolValue(Runtime& runtime, const NativeApiSymbol& symbol, - const char* expectedKind) { - Object global = runtime.global(); - Value cacheValue = global.getProperty( - runtime, "__nativeScriptNativeApiGlobalCache"); - if (!cacheValue.isObject()) { - return Value::undefined(); - } - - Object cache = cacheValue.asObject(runtime); - auto readCache = [&](const std::string& name) -> Value { - if (name.empty()) { - return Value::undefined(); - } - - Value value = cache.getProperty(runtime, name.c_str()); - if (!value.isObject()) { - return Value::undefined(); - } - - try { - Object object = value.asObject(runtime); - Value kindValue = object.getProperty(runtime, "kind"); - if (kindValue.isString() && - kindValue.asString(runtime).utf8(runtime) == expectedKind) { - return value; - } - } catch (const std::exception&) { - } - - return Value::undefined(); - }; - - Value value = readCache(symbol.name); - if (!value.isUndefined()) { - return value; - } - if (symbol.runtimeName != symbol.name) { - value = readCache(symbol.runtimeName); - if (!value.isUndefined()) { - return value; - } - } - - try { - if (std::strcmp(expectedKind, "class") == 0) { - Value classResolverValue = global.getProperty( - runtime, "__nativeScriptResolveNativeApiClassWrapper"); - if (classResolverValue.isObject() && - classResolverValue.asObject(runtime).isFunction(runtime)) { - Function classResolver = - classResolverValue.asObject(runtime).asFunction(runtime); - auto resolveClassWrapper = [&](const std::string& name) -> Value { - if (name.empty()) { - return Value::undefined(); - } - Value resolved = classResolver.call(runtime, makeString(runtime, name)); - return resolved.isObject() ? std::move(resolved) : Value::undefined(); - }; - - value = resolveClassWrapper(symbol.name); - if (!value.isUndefined()) { - return value; - } - if (symbol.runtimeName != symbol.name) { - value = resolveClassWrapper(symbol.runtimeName); - if (!value.isUndefined()) { - return value; - } - } - } - } - - Value resolverValue = - global.getProperty(runtime, "__nativeScriptResolveNativeApiGlobal"); - if (resolverValue.isObject() && - resolverValue.asObject(runtime).isFunction(runtime)) { - Function resolver = resolverValue.asObject(runtime).asFunction(runtime); - auto resolveGlobal = [&](const std::string& name) -> Value { - if (name.empty()) { - return Value::undefined(); - } - Value resolved = resolver.call(runtime, makeString(runtime, name), - makeString(runtime, expectedKind)); - if (resolved.isObject()) { - return resolved; - } - return Value::undefined(); - }; - - value = resolveGlobal(symbol.name); - if (!value.isUndefined()) { - return value; - } - if (symbol.runtimeName != symbol.name) { - value = resolveGlobal(symbol.runtimeName); - if (!value.isUndefined()) { - return value; - } - } - } - } catch (const std::exception&) { - } - - return Value::undefined(); -} - -Value makeNativeClassValue(Runtime& runtime, - const std::shared_ptr& bridge, - NativeApiSymbol symbol) { - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - Value cachedClass = bridge->findClassValue(runtime, cls); - if (!cachedClass.isUndefined()) { - return cachedClass; - } - Value globalValue = globalNativeSymbolValue(runtime, symbol, "class"); - if (!globalValue.isUndefined()) { - return globalValue; - } - return Object::createFromHostObject( - runtime, - std::make_shared(bridge, std::move(symbol))); -} - -Protocol* lookupProtocolByNativeName(const std::string& name) { - Protocol* protocol = objc_getProtocol(name.c_str()); - if (protocol != nullptr) { - return protocol; - } - constexpr const char* suffix = "Protocol"; - size_t suffixLength = std::strlen(suffix); - if (name.size() > suffixLength && - name.compare(name.size() - suffixLength, suffixLength, suffix) == 0) { - protocol = objc_getProtocol( - name.substr(0, name.size() - suffixLength).c_str()); - } - return protocol; -} - -class NativeApiProtocolHostObject final : public HostObject { - public: - NativeApiProtocolHostObject(std::shared_ptr bridge, - NativeApiSymbol symbol) - : bridge_(std::move(bridge)), symbol_(std::move(symbol)) {} - - Protocol* nativeProtocol() const { - Protocol* protocol = lookupProtocolByNativeName(symbol_.runtimeName); - if (protocol == nullptr && symbol_.runtimeName != symbol_.name) { - protocol = lookupProtocolByNativeName(symbol_.name); - } - return protocol; - } - - const NativeApiSymbol& symbol() const { return symbol_; } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "protocol"); - } - if (property == "name") { - return makeString(runtime, symbol_.name); - } - if (property == "runtimeName") { - return makeString(runtime, symbol_.runtimeName); - } - if (property == "available") { - return nativeProtocol() != nullptr; - } - if (property == "metadataOffset") { - return static_cast(symbol_.offset); - } - if (property == "nativeAddress") { - return static_cast( - reinterpret_cast(nativeProtocol())); - } - if (property == "prototype") { - Object prototype(runtime); - for (const auto& member : bridge_->membersForProtocol(symbol_)) { - if (prototype.hasProperty(runtime, member.name.c_str())) { - continue; - } - if (member.property) { - defineProtocolProperty(runtime, prototype, member, false); - } else { - prototype.setProperty(runtime, member.name.c_str(), - makeProtocolMemberFunction(runtime, member, - false)); - } - } - return prototype; - } - if (property == "toString") { - auto symbol = symbol_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [symbol](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return makeString(runtime, - "[NativeApiJsiProtocol " + symbol.name + "]"); - }); - } - const auto& members = bridge_->membersForProtocol(symbol_); - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, true)) { - return makeProtocolPropertyGetter(runtime, *propertyMember, true); - } - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, false)) { - return makeProtocolPropertyGetter(runtime, *propertyMember, true); - } - if (const NativeApiMember* methodMember = - selectMethodMember(members, property, true, 0)) { - return makeProtocolMemberFunction(runtime, *methodMember, true); - } - if (const NativeApiMember* methodMember = - selectMethodMember(members, property, false, 0)) { - return makeProtocolMemberFunction(runtime, *methodMember, true); - } - return Value::undefined(); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "name"); - addPropertyName(runtime, names, "runtimeName"); - addPropertyName(runtime, names, "available"); - addPropertyName(runtime, names, "metadataOffset"); - addPropertyName(runtime, names, "nativeAddress"); - addPropertyName(runtime, names, "prototype"); - addPropertyName(runtime, names, "toString"); - for (const auto& member : bridge_->membersForProtocol(symbol_)) { - addPropertyName(runtime, names, member.name.c_str()); - } - return names; - } - - private: - static Class classReceiverFromThis(Runtime& runtime, const Value& thisValue) { - if (!thisValue.isObject()) { - return Nil; - } - - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->nativeClass(); - } - - Value wrappedClass = object.getProperty(runtime, "__nativeApiClass"); - if (wrappedClass.isObject()) { - Object wrappedObject = wrappedClass.asObject(runtime); - if (wrappedObject.isHostObject(runtime)) { - return wrappedObject.getHostObject(runtime) - ->nativeClass(); - } - } - - Value kindValue = object.getProperty(runtime, "kind"); - if (kindValue.isString() && - kindValue.asString(runtime).utf8(runtime) == "class") { - Value runtimeNameValue = object.getProperty(runtime, "runtimeName"); - if (!runtimeNameValue.isString()) { - runtimeNameValue = object.getProperty(runtime, "name"); - } - if (runtimeNameValue.isString()) { - std::string runtimeName = - runtimeNameValue.asString(runtime).utf8(runtime); - return objc_lookUpClass(runtimeName.c_str()); - } - } - - return Nil; - } - - id objectReceiverFromThis(Runtime& runtime, const Value& thisValue) const { - if (!thisValue.isObject()) { - return nil; - } - - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->object(); - } - - return nil; - } - - Value makeProtocolMemberFunction(Runtime& runtime, NativeApiMember member, - bool receiverIsClass) const { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, member.name.c_str()), 0, - [bridge, member, receiverIsClass](Runtime& runtime, - const Value& thisValue, - const Value* args, - size_t count) -> Value { - id receiver = nil; - if (receiverIsClass) { - receiver = static_cast( - classReceiverFromThis(runtime, thisValue)); - } else if (thisValue.isObject()) { - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - receiver = object.getHostObject(runtime) - ->object(); - } - } - - if (receiver == nil) { - throw facebook::jsi::JSError( - runtime, "Protocol member requires a native receiver."); - } - return callObjCSelector(runtime, bridge, receiver, receiverIsClass, - member.selectorName, &member, args, count); - }); - } - - Value makeProtocolPropertyGetter(Runtime& runtime, NativeApiMember member, - bool receiverIsClass) const { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, member.name.c_str()), 0, - [bridge, member, receiverIsClass](Runtime& runtime, - const Value& thisValue, - const Value*, size_t) -> Value { - id receiver = nil; - if (receiverIsClass) { - receiver = static_cast( - classReceiverFromThis(runtime, thisValue)); - } else if (thisValue.isObject()) { - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - receiver = object.getHostObject(runtime) - ->object(); - } - } - - if (receiver == nil) { - throw facebook::jsi::JSError( - runtime, "Protocol property requires a native receiver."); - } - return callObjCSelector(runtime, bridge, receiver, receiverIsClass, - member.selectorName, &member, nullptr, 0); - }); - } - - Value makeProtocolPropertySetter(Runtime& runtime, NativeApiMember member, - bool receiverIsClass) const { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, member.setterSelectorName.c_str()), - 1, - [bridge, member, receiverIsClass](Runtime& runtime, - const Value& thisValue, - const Value* args, - size_t count) -> Value { - id receiver = nil; - if (receiverIsClass) { - receiver = static_cast( - classReceiverFromThis(runtime, thisValue)); - } else if (thisValue.isObject()) { - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - receiver = object.getHostObject(runtime) - ->object(); - } - } - - if (receiver == nil) { - throw facebook::jsi::JSError( - runtime, "Protocol property requires a native receiver."); - } - if (count < 1) { - throw facebook::jsi::JSError( - runtime, "Protocol property setter expects a value."); - } - - NativeApiMember setterMember = member; - setterMember.selectorName = member.setterSelectorName; - setterMember.signatureOffset = member.setterSignatureOffset; - return callObjCSelector(runtime, bridge, receiver, receiverIsClass, - setterMember.selectorName, &setterMember, - args, 1); - }); - } - - void defineProtocolProperty(Runtime& runtime, Object& target, - const NativeApiMember& member, - bool receiverIsClass) const { - try { - Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); - Function defineProperty = - objectCtor.getPropertyAsFunction(runtime, "defineProperty"); - Object descriptor(runtime); - descriptor.setProperty(runtime, "configurable", true); - descriptor.setProperty(runtime, "enumerable", true); - descriptor.setProperty(runtime, "get", - makeProtocolPropertyGetter(runtime, member, - receiverIsClass)); - if (!member.readonly && !member.setterSelectorName.empty()) { - descriptor.setProperty(runtime, "set", - makeProtocolPropertySetter(runtime, member, - receiverIsClass)); - } - defineProperty.call(runtime, target, makeString(runtime, member.name), - descriptor); - } catch (const std::exception&) { - } - } - - std::shared_ptr bridge_; - NativeApiSymbol symbol_; -}; - -Value makeNativeProtocolValue(Runtime& runtime, - const std::shared_ptr& bridge, - NativeApiSymbol symbol) { - Value globalValue = globalNativeSymbolValue(runtime, symbol, "protocol"); - if (!globalValue.isUndefined()) { - return globalValue; - } - return Object::createFromHostObject( - runtime, - std::make_shared(bridge, std::move(symbol))); -} - -Class nativeClassFromJsiObject(Runtime& runtime, const Object& object) { - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->nativeClass(); - } - - Value wrappedClass = object.getProperty(runtime, "__nativeApiClass"); - if (wrappedClass.isObject()) { - Object wrappedObject = wrappedClass.asObject(runtime); - if (wrappedObject.isHostObject(runtime)) { - return wrappedObject.getHostObject(runtime) - ->nativeClass(); - } - } - return Nil; -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h b/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h deleted file mode 100644 index 022047607..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h +++ /dev/null @@ -1,1728 +0,0 @@ -Object CreateNativeApiJSI(Runtime& runtime, const NativeApiJsiConfig& config) { - auto bridge = std::make_shared(config); - return Object::createFromHostObject(runtime, - std::make_shared(std::move(bridge))); -} - -void NativeApiJsiWriteSmokeStage(const char* stage) { - const char* enabled = getenv("NATIVESCRIPT_RN_TURBO_SMOKE_MARKER"); - if (enabled == nullptr || enabled[0] == '\0') { - return; - } - - NSString* path = - [NSTemporaryDirectory() stringByAppendingPathComponent:@"NativeScriptNativeApiSmoke.marker"]; - NSString* content = [NSString stringWithFormat:@"stage=%s\n", stage != nullptr ? stage : ""]; - [content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; -} - -void InstallAggregateGlobals(Runtime& runtime, Object& api, const char* namesFunction) { - Value metadataValue = api.getProperty(runtime, "metadata"); - if (!metadataValue.isObject()) { - return; - } - Object metadata = metadataValue.asObject(runtime); - Value namesValue = metadata.getProperty(runtime, namesFunction); - if (!namesValue.isObject()) { - return; - } - Object namesObject = namesValue.asObject(runtime); - if (!namesObject.isFunction(runtime)) { - return; - } - Value namesResult = namesObject.asFunction(runtime).call(runtime); - if (!namesResult.isObject() || !namesResult.asObject(runtime).isArray(runtime)) { - return; - } - Array names = namesResult.asObject(runtime).getArray(runtime); - Object global = runtime.global(); - for (size_t i = 0; i < names.size(runtime); i++) { - Value nameValue = names.getValueAtIndex(runtime, i); - if (!nameValue.isString()) { - continue; - } - std::string name = nameValue.asString(runtime).utf8(runtime); - if (name.empty() || global.hasProperty(runtime, name.c_str())) { - continue; - } - try { - Value aggregate = api.getProperty(runtime, name.c_str()); - if (!aggregate.isUndefined()) { - global.setProperty(runtime, name.c_str(), aggregate); - } - } catch (const std::exception&) { - // Some React Native globals are read-only even when hasProperty misses - // them. Keep NativeScript initialization resilient and skip collisions. - } - } -} - -std::string jsStringLiteral(const char* value) { - std::string result = "'"; - if (value != nullptr) { - for (const char* current = value; *current != '\0'; current++) { - switch (*current) { - case '\\': - result += "\\\\"; - break; - case '\'': - result += "\\'"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - default: - result += *current; - break; - } - } - } - result += "'"; - return result; -} - -void InstallNativeApiJsiGlobalSymbols(Runtime& runtime, const char* globalName) { - NativeApiJsiWriteSmokeStage("jsi:globals:before-eval"); - static const char* GlobalInstaller = R"JSI_GLOBALS( -(function(nativeApiGlobalName) { - 'use strict'; - var api = globalThis[nativeApiGlobalName]; - var installedFlagName = '__nativeScriptNativeApiGlobalsInstalled'; - if (!api || globalThis[installedFlagName]) { - return; - } - - var cacheName = '__nativeScriptNativeApiGlobalCache'; - var typeCodeKey = '__nativeApiTypeCode'; - var classWrappers = typeof WeakMap === 'function' ? new WeakMap() : null; - var classWrappersByName = Object.create(null); - var resolvingGlobal = Object.create(null); - - function globalCache() { - var existing = globalThis[cacheName]; - if (existing && typeof existing === 'object') { - return existing; - } - var cache = Object.create(null); - Object.defineProperty(globalThis, cacheName, { - configurable: false, - enumerable: false, - writable: false, - value: cache - }); - return cache; - } - - function cacheGlobal(name, value) { - if (name && value !== undefined) { - globalCache()[name] = value; - } - } - - function resolveCachedGlobal(name, expectedKind) { - if (!name) { - return undefined; - } - var cached = globalCache()[name]; - if (cached && (typeof cached === 'object' || typeof cached === 'function') && cached.kind === expectedKind) { - return cached; - } - if (resolvingGlobal[name] || !Object.prototype.hasOwnProperty.call(globalThis, name)) { - return undefined; - } - resolvingGlobal[name] = true; - try { - var value = globalThis[name]; - if (value && (typeof value === 'object' || typeof value === 'function') && value.kind === expectedKind) { - cacheGlobal(name, value); - return value; - } - } finally { - delete resolvingGlobal[name]; - } - return undefined; - } - - function defineLazyGlobal(name, resolve, force, nativeKind) { - if (!name) { - return; - } - if (!force && Object.prototype.hasOwnProperty.call(globalThis, name)) { - try { - var existingDescriptor = Object.getOwnPropertyDescriptor(globalThis, name); - if (existingDescriptor && Object.prototype.hasOwnProperty.call(existingDescriptor, 'value')) { - cacheGlobal(name, existingDescriptor.value); - } - } catch (_) { - } - return; - } - var nativeDefineLazyGlobal = api.__defineLazyGlobal; - if (nativeKind && typeof nativeDefineLazyGlobal === 'function' && - typeof globalThis.__nativeScriptResolveNativeApiLazyGlobal === 'function') { - try { - if (nativeDefineLazyGlobal(name, nativeKind, !!force)) { - return; - } - } catch (_) { - } - } - try { - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: false, - get: function() { - var value = resolve(name); - cacheGlobal(name, value); - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: false, - writable: true, - value: value - }); - return value; - }, - set: function(value) { - // Assignment over a lazy global must behave like a plain global - // assignment (@nativescript/core writes shims such as - // global.System), not throw "no setter for property". - cacheGlobal(name, value); - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: true, - writable: true, - value: value - }); - } - }); - } catch (_) { - var value = resolve(name); - if (value !== undefined) { - cacheGlobal(name, value); - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: false, - writable: true, - value: value - }); - } - } - } - - Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiGlobal', { - configurable: false, - enumerable: false, - writable: false, - value: resolveCachedGlobal - }); - - Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiClassWrapper', { - configurable: false, - enumerable: false, - writable: false, - value: function(name) { - return name ? classWrappersByName[name] : undefined; - } - }); - - function findPrototypeDescriptor(className, property) { - var prototype; - if (className && (typeof className === 'object' || typeof className === 'function')) { - prototype = className; - } else { - var wrapper = className ? classWrappersByName[className] : undefined; - prototype = wrapper && wrapper.prototype; - } - while (prototype != null) { - var descriptor = Object.getOwnPropertyDescriptor(prototype, property); - if (descriptor) { - return descriptor; - } - prototype = Object.getPrototypeOf(prototype); - } - return undefined; - } - - Object.defineProperty(globalThis, '__nativeScriptGetNativeApiPrototypeProperty', { - configurable: false, - enumerable: false, - writable: false, - value: function(className, receiver, property) { - var descriptor = findPrototypeDescriptor(className, property); - if (!descriptor) { - return { found: false }; - } - if (typeof descriptor.get === 'function') { - return { found: true, value: descriptor.get.call(receiver) }; - } - if (typeof descriptor.value === 'function') { - return { found: true, value: descriptor.value.bind(receiver) }; - } - if ('value' in descriptor) { - return { found: true, value: descriptor.value }; - } - return { found: true, value: undefined }; - } - }); - - Object.defineProperty(globalThis, '__nativeScriptCreateNativeApiIterator', { - configurable: false, - enumerable: false, - writable: false, - value: function(receiver, prototype) { - if (!receiver || typeof Symbol !== 'function') { - return undefined; - } - var descriptor = findPrototypeDescriptor(prototype || receiver.className, Symbol.iterator); - if (descriptor && typeof descriptor.value === 'function') { - return descriptor.value.call(receiver); - } - if (descriptor && typeof descriptor.get === 'function') { - var getterValue = descriptor.get.call(receiver); - if (typeof getterValue === 'function') { - return getterValue.call(receiver); - } - } - var iteratorMethod = receiver[Symbol.iterator]; - return typeof iteratorMethod === 'function' - ? iteratorMethod.call(receiver) - : undefined; - } - }); - - function wrapAggregateConstructor(nativeConstructor) { - if (typeof nativeConstructor !== 'function') { - return nativeConstructor; - } - var aggregate = function NativeScriptAggregate(initialValue) { - return nativeConstructor(initialValue); - }; - try { - Object.defineProperty(aggregate, Symbol.hasInstance, { - configurable: true, - enumerable: false, - value: function(value) { - return !!value && - typeof value === 'object' && - value.kind === nativeConstructor.kind && - value.name === nativeConstructor.runtimeName; - } - }); - } catch (_) { - } - ['kind', 'runtimeName', 'metadataOffset', 'sizeof', 'fields', 'equals'].forEach(function(key) { - try { - Object.defineProperty(aggregate, key, { - configurable: true, - enumerable: false, - writable: false, - value: nativeConstructor[key] - }); - } catch (_) { - } - }); - return aggregate; - } - - function setDescriptorValue(target, property, receiver, value) { - var descriptor = Object.getOwnPropertyDescriptor(target, property); - if (!descriptor) { - return false; - } - if (typeof descriptor.set === 'function') { - descriptor.set.call(receiver, value); - return true; - } - if (descriptor.writable) { - if (receiver && receiver !== target) { - Object.defineProperty(receiver, property, { - configurable: true, - enumerable: true, - writable: true, - value: value - }); - } else { - target[property] = value; - } - return true; - } - return false; - } - - function isConstructorOptions(value) { - if (!value || typeof value !== 'object' || Array.isArray(value)) { - return false; - } - if (value.kind || value.nativeAddress || value instanceof Date) { - return false; - } - return Object.getPrototypeOf(value) === Object.prototype || - Object.getPrototypeOf(value) === null; - } - - function capitalizeToken(value) { - value = String(value || ''); - return value ? value.charAt(0).toUpperCase() + value.slice(1) : value; - } - - function selectorCandidatesFromOptions(options) { - var keys = Object.keys(options || {}); - if (!keys.length) { - return []; - } - var first = capitalizeToken(keys[0]); - var tail = ''; - for (var i = 1; i < keys.length; i++) { - tail += keys[i] + ':'; - } - return [ - 'initWith' + first + ':' + tail, - 'init' + first + ':' + tail - ]; - } - - function valuesFromOptions(options) { - return Object.keys(options || {}).map(function(key) { - return options[key]; - }); - } - - function selectorScoreForArguments(selectorName, args) { - if (!selectorName || selectorName.indexOf('init') !== 0) { - return -1; - } - if (selectorName === 'init') { - return args.length === 0 ? 100 : -1; - } - if (args.length === 0) { - return -1; - } - var lower = selectorName.toLowerCase(); - var first = args[0]; - var score = 1; - if (Array.isArray(first)) { - if (lower.indexOf('array') !== -1) { - score += 40; - } - } else if (typeof first === 'string') { - if (lower.indexOf('string') !== -1) { - score += 40; - } - if (lower.indexOf('url') !== -1) { - score += 10; - } - } else if (typeof first === 'number') { - if (lower.indexOf('primitive') !== -1) { - score += 50; - } - if (lower.indexOf('int') !== -1 || - lower.indexOf('integer') !== -1 || - lower.indexOf('number') !== -1 || - lower.indexOf('float') !== -1 || - lower.indexOf('double') !== -1 || - lower.indexOf('long') !== -1 || - lower.indexOf('short') !== -1) { - score += 30; - } - } else if (isConstructorOptions(first)) { - if (lower.indexOf('struct') !== -1 || - lower.indexOf('structure') !== -1) { - score += 40; - } - if (lower.indexOf('dictionary') !== -1) { - score += 20; - } - } else if (first === null || typeof first === 'undefined') { - score += 5; - } else if (lower.indexOf('object') !== -1 || - lower.indexOf('url') !== -1 || - lower.indexOf('data') !== -1) { - score += 20; - } - - var allStrings = args.length > 1 && args.every(function(value) { - return typeof value === 'string'; - }); - var allNumbers = args.length > 1 && args.every(function(value) { - return typeof value === 'number'; - }); - if (allStrings && lower.indexOf('string') !== -1) { - score += 25; - } - if (allNumbers && - (lower.indexOf('int') !== -1 || lower.indexOf('number') !== -1)) { - score += 25; - } - return score; - } - - function initializerMembers(nativeClass, argumentCount) { - var members = nativeClass.__instanceMembers || []; - var result = []; - for (var i = 0; i < members.length; i++) { - var member = members[i]; - if (!member || member.property || !member.selectorName) { - continue; - } - if (member.selectorName.indexOf('init') !== 0) { - continue; - } - if (typeof argumentCount === 'number' && - member.argumentCount !== argumentCount) { - continue; - } - result.push(member); - } - return result; - } - - function chooseInitializer(nativeClass, args, optionSelectors) { - var members = initializerMembers(nativeClass, args.length); - if (!members.length) { - return null; - } - if (optionSelectors && optionSelectors.length) { - for (var i = 0; i < optionSelectors.length; i++) { - for (var j = 0; j < members.length; j++) { - if (members[j].selectorName === optionSelectors[i]) { - return members[j]; - } - } - } - } - - var best = null; - var bestScore = -1; - for (var k = 0; k < members.length; k++) { - var score = selectorScoreForArguments(members[k].selectorName, args); - if (score > bestScore) { - bestScore = score; - best = members[k]; - } - } - return bestScore >= 0 ? best : null; - } - - function chooseInitializerBySelectors(nativeClass, args, selectors) { - if (!selectors || !selectors.length) { - return null; - } - var members = initializerMembers(nativeClass, args.length); - for (var i = 0; i < selectors.length; i++) { - for (var j = 0; j < members.length; j++) { - if (members[j].selectorName === selectors[i]) { - return members[j]; - } - } - } - return null; - } - - function unavailableInitializerError(error) { - return error && - /Objective-C selector is not available/.test(String(error.message || error)); - } - - function constructNativeInstance(nativeClass, args) { - if (args.length === 1 && - args[0] && - typeof args[0] === 'object' && - (args[0].kind === 'pointer' || args[0].kind === 'reference') && - typeof nativeClass.construct === 'function') { - return nativeClass.construct(args[0]); - } - - var actualArgs = args; - var initializer = null; - if (args.length === 1 && isConstructorOptions(args[0])) { - var optionSelectors = selectorCandidatesFromOptions(args[0]); - if (!optionSelectors.length) { - throw new Error('No initializer found that matches constructor invocation.'); - } - var optionArgs = valuesFromOptions(args[0]); - initializer = chooseInitializerBySelectors( - nativeClass, - optionArgs, - optionSelectors - ); - if (initializer) { - actualArgs = optionArgs; - } - } - if (!initializer) { - initializer = chooseInitializer(nativeClass, actualArgs, null); - } - if (!initializer) { - throw new Error('No initializer found that matches constructor invocation.'); - } - if (typeof nativeClass.alloc !== 'function') { - throw new Error('Native class cannot be allocated'); - } - var instance = nativeClass.alloc(); - if (initializer.selectorName === 'init') { - if (typeof instance.init !== 'function') { - throw new Error('No initializer found that matches constructor invocation.'); - } - return instance.init(); - } - try { - if (initializer.name && typeof instance[initializer.name] === 'function') { - return instance[initializer.name].apply(instance, actualArgs); - } - var invokeArgs = [initializer.selectorName]; - Array.prototype.push.apply(invokeArgs, actualArgs); - return instance.invoke.apply(instance, invokeArgs); - } catch (error) { - if (unavailableInitializerError(error)) { - throw new Error('No initializer found that matches constructor invocation.'); - } - throw error; - } - } - - function wrapNativeClass(nativeClass) { - if (!nativeClass || (typeof nativeClass !== 'object' && typeof nativeClass !== 'function')) { - return nativeClass; - } - var nativeClassName = nativeClass.runtimeName || nativeClass.name || ''; - if (nativeClassName && classWrappersByName[nativeClassName]) { - if (classWrappers) { - try { - classWrappers.set(nativeClass, classWrappersByName[nativeClassName]); - } catch (_) { - } - } - return classWrappersByName[nativeClassName]; - } - if (classWrappers) { - var cached = classWrappers.get(nativeClass); - if (cached) { - return cached; - } - } - var constructable = function NativeScriptNativeClass() { - var args = Array.prototype.slice.call(arguments); - var redirectConstructor = this && this.constructor; - if (redirectConstructor && - redirectConstructor !== constructable && - redirectConstructor !== wrapper && - typeof redirectConstructor.__nativeApiEnsureClass === 'function') { - var redirectedWrapper = redirectConstructor.__nativeApiEnsureClass(); - if (redirectedWrapper && - redirectedWrapper !== constructable && - redirectedWrapper !== wrapper && - typeof redirectedWrapper.apply === 'function') { - return rememberClassOnInstance( - redirectedWrapper.apply(this, args), - redirectConstructor - ); - } - } - if (args.length > 0) { - return rememberInstanceClass(constructNativeInstance(nativeClass, args)); - } - if (typeof nativeClass.alloc !== 'function') { - throw new Error('Native class cannot be allocated'); - } - var instance = nativeClass.alloc(); - if (instance && typeof instance.init === 'function') { - return rememberInstanceClass(instance.init()); - } - return rememberInstanceClass(instance); - }; - function rememberInstanceClass(instance) { - return rememberClassOnInstance(instance, wrapper || constructable); - } - // Static allocators may be invoked with a TypeScript-derived class as - // the receiver (core does `_super.new.call(this)`); those must - // materialize and allocate the derived Objective-C class, not the base. - function derivedClassWrapper(target) { - if (target && target !== constructable && target !== wrapper && - typeof target.__nativeApiEnsureClass === 'function') { - var derived = target.__nativeApiEnsureClass(); - if (derived && derived !== constructable && derived !== wrapper) { - return derived; - } - } - return undefined; - } - try { - Object.defineProperty(constructable, 'name', { - configurable: true, - enumerable: false, - value: nativeClassName || nativeClass.name || 'NativeScriptNativeClass' - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'extend', { - configurable: true, - enumerable: false, - writable: false, - value: function(methods, options) { - if (methods == null || typeof methods !== 'object') { - throw new Error('extend() first parameter must be an object'); - } - var extendOptions = options || {}; - if (typeof Symbol === 'function' && - Object.prototype.hasOwnProperty.call(methods, Symbol.iterator)) { - try { - extendOptions = Object.assign({}, extendOptions, { - __hasIterator: true - }); - } catch (_) { - extendOptions.__hasIterator = true; - } - } - var extendedNativeClass = api.__extendClass(nativeClass, methods, extendOptions); - var extended = wrapNativeClass(extendedNativeClass); - try { - Object.setPrototypeOf(extended, wrapper || constructable); - } catch (_) { - } - var extendedPrototype = Object.create(constructable.prototype || null); - try { - Object.defineProperties(extendedPrototype, Object.getOwnPropertyDescriptors(methods)); - } catch (_) { - Object.keys(methods).forEach(function(key) { - extendedPrototype[key] = methods[key]; - }); - } - try { - Object.defineProperty(extendedPrototype, 'constructor', { - configurable: true, - enumerable: false, - writable: true, - value: extended - }); - } catch (_) { - } - extended.prototype = extendedPrototype; - try { - api.__rememberClassWrapper(extendedNativeClass, extended, extendedPrototype); - } catch (_) { - } - return extended; - } - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'alloc', { - configurable: true, - enumerable: false, - writable: true, - value: function() { - var derived = derivedClassWrapper(this); - if (derived && typeof derived.alloc === 'function') { - return rememberClassOnInstance(derived.alloc.apply(derived, arguments), this); - } - return rememberInstanceClass(nativeClass.alloc.apply(nativeClass, arguments)); - } - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'new', { - configurable: true, - enumerable: false, - writable: false, - value: function() { - if (arguments.length !== 0) { - throw new Error('new does not take arguments; use invoke for an explicit Objective-C selector.'); - } - var derived = derivedClassWrapper(this); - if (derived && typeof derived.new === 'function') { - return rememberClassOnInstance(derived.new(), this); - } - if (typeof nativeClass.alloc !== 'function') { - throw new Error('Native class cannot be allocated'); - } - var instance = nativeClass.alloc(); - if (instance && typeof instance.init === 'function') { - return rememberInstanceClass(instance.init()); - } - return rememberInstanceClass(instance); - } - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'caller', { - configurable: true, - enumerable: false, - writable: false, - value: null - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'arguments', { - configurable: true, - enumerable: false, - writable: false, - value: null - }); - } catch (_) { - } - var basePrototypeTarget = {}; - var classMembersInstalled = false; - function installClassMembers(target, members, receiverIsClass) { - if (!target || !members || typeof members.length !== 'number') { - return; - } - for (var i = 0; i < members.length; i++) { - var member = members[i]; - if (!member || !member.name || Object.prototype.hasOwnProperty.call(target, member.name)) { - continue; - } - try { - if (member.property) { - var descriptor = { - configurable: true, - enumerable: false, - get: receiverIsClass - ? (function(name, selectorName) { - return function() { - return selectorName - ? nativeClass.invoke(selectorName) - : nativeClass[name]; - }; - })(member.name, member.selectorName) - : (function(name) { - return function() { - return api.__invokeBase(nativeClass, this, name); - }; - })(member.name) - }; - if (!member.readonly) { - descriptor.set = receiverIsClass - ? (function(name, setterSelectorName) { - return function(value) { - if (setterSelectorName) { - return nativeClass.invoke(setterSelectorName, value); - } - nativeClass[name] = value; - }; - })(member.name, member.setterSelectorName) - : (function(name) { - return function(value) { - return api.__invokeBase(nativeClass, this, name, value); - }; - })(member.name); - } - Object.defineProperty(target, member.name, descriptor); - } else { - Object.defineProperty(target, member.name, { - configurable: true, - enumerable: false, - writable: true, - value: receiverIsClass - ? (function(name) { - return function() { - if (this && typeof this === 'object' && this.kind === 'object') { - var baseArgs = [nativeClass, this, name]; - Array.prototype.push.apply(baseArgs, arguments); - return api.__invokeBase.apply(api, baseArgs); - } - return nativeClass[name].apply(nativeClass, arguments); - }; - })(member.name) - : (function(name) { - return function() { - var args = [nativeClass, this, name]; - Array.prototype.push.apply(args, arguments); - return api.__invokeBase.apply(api, args); - }; - })(member.name) - }); - } - } catch (_) { - } - } - } - function installNativeClassMembersIfNeeded() { - if (classMembersInstalled) { - return; - } - classMembersInstalled = true; - installClassMembers(constructable, nativeClass.__staticMembers, true); - installClassMembers(basePrototypeTarget, nativeClass.__instanceMembers, false); - try { - delete constructable.__nativeApiInstallMembers; - } catch (_) { - } - } - try { - Object.defineProperty(constructable, '__nativeApiInstallMembers', { - configurable: true, - enumerable: false, - writable: false, - value: installNativeClassMembersIfNeeded - }); - } catch (_) { - } - try { - Object.defineProperty(basePrototypeTarget, 'constructor', { - configurable: true, - enumerable: false, - writable: true, - value: constructable - }); - } catch (_) { - } - try { - Object.defineProperty(basePrototypeTarget, 'toString', { - configurable: true, - enumerable: false, - writable: true, - value: function() { - return '[object NativeScriptObject]'; - } - }); - } catch (_) { - } - try { - if (typeof Symbol === 'function' && Symbol.iterator && - typeof api.__fastEnumeration === 'function') { - Object.defineProperty(basePrototypeTarget, Symbol.iterator, { - configurable: true, - enumerable: false, - writable: true, - value: function() { - return api.__fastEnumeration(this); - } - }); - } - } catch (_) { - } - constructable.prototype = typeof Proxy === 'function' - ? new Proxy(basePrototypeTarget, { - get: function(target, property, receiver) { - installNativeClassMembersIfNeeded(); - if (property in target) { - return Reflect.get(target, property, receiver); - } - if (typeof property === 'symbol') { - return undefined; - } - return function() { - var args = [nativeClass, this, String(property)]; - Array.prototype.push.apply(args, arguments); - return api.__invokeBase.apply(api, args); - }; - }, - set: function(target, property, value, receiver) { - if (property === 'prototype') { - target[property] = value; - return true; - } - if (setDescriptorValue(target, property, receiver, value)) { - return true; - } - if (receiver && receiver !== target) { - Object.defineProperty(receiver, property, { - configurable: true, - enumerable: true, - writable: true, - value: value - }); - return true; - } - target[property] = value; - return true; - }, - has: function(target, property) { - installNativeClassMembersIfNeeded(); - return property in target; - }, - ownKeys: function(target) { - installNativeClassMembersIfNeeded(); - return Reflect.ownKeys(target); - }, - getOwnPropertyDescriptor: function(target, property) { - installNativeClassMembersIfNeeded(); - return Reflect.getOwnPropertyDescriptor(target, property); - } - }) - : basePrototypeTarget; - try { - Object.defineProperty(constructable, Symbol.hasInstance, { - configurable: true, - enumerable: false, - value: function(value) { - if (!value || typeof value !== 'object') { - return false; - } - // `this` is the constructor instanceof was invoked on. A - // TypeScript-derived native class inherits this method through the - // wrapper prototype chain until it materializes, so membership must - // be answered for the DERIVED Objective-C class — and before - // materialization no instance of it can exist. - try { - if (this && this !== constructable && this !== wrapper && - this.__nativeApiTypeScriptState) { - var derivedWrapper = this.__nativeApiTypeScriptState.wrapper; - if (!derivedWrapper) { - return false; - } - if (derivedWrapper !== constructable && derivedWrapper !== wrapper) { - return derivedWrapper[Symbol.hasInstance](value); - } - } - } catch (_) { - } - var expectedName = nativeClass.runtimeName || nativeClass.name; - try { - // Pass the proxied wrapper: the raw constructable carries no - // __nativeApiClass, so it does not marshal to the Objective-C - // Class and isKindOfClass() misreports. - if (typeof value.isKindOfClass === 'function' && - value.isKindOfClass(wrapper || constructable) === true) { - return true; - } - } catch (_) { - } - try { - var current = typeof value.class === 'function' ? value.class() : null; - while (current) { - if (current === wrapper || current === constructable) { - return true; - } - var currentName = current.runtimeName || current.name; - if (typeof expectedName === 'string' && currentName === expectedName) { - return true; - } - var next = current.superclass || null; - if (typeof next === 'function' && next.kind !== 'class') { - next = next.call(current); - } - current = next || null; - } - } catch (_) { - } - return typeof expectedName === 'string' && value.className === expectedName; - } - }); - } catch (_) { - } - var cachedNativeFunctions = typeof Map === 'function' ? new Map() : null; - var wrapper = typeof Proxy === 'function' - ? new Proxy(constructable, { - get: function(target, property, receiver) { - if (property === '__nativeApiClass') { - return nativeClass; - } - if (property === 'toString') { - return function() { - return String(nativeClass); - }; - } - if (property === 'hasOwnProperty') { - return function(key) { - installNativeClassMembersIfNeeded(); - return Object.prototype.hasOwnProperty.call(target, key); - }; - } - if (Object.prototype.hasOwnProperty.call(target, property) || - property === 'prototype' || - property === 'length' || - property === 'name') { - return Reflect.get(target, property, receiver); - } - installNativeClassMembersIfNeeded(); - if (Object.prototype.hasOwnProperty.call(target, property)) { - return Reflect.get(target, property, receiver); - } - if (cachedNativeFunctions && cachedNativeFunctions.has(property)) { - return cachedNativeFunctions.get(property); - } - var nativeValue = nativeClass[property]; - if (nativeValue !== undefined) { - if (typeof nativeValue === 'function') { - if (cachedNativeFunctions) { - cachedNativeFunctions.set(property, nativeValue); - } - try { - Object.defineProperty(target, property, { - configurable: true, - enumerable: false, - writable: false, - value: nativeValue - }); - } catch (_) { - } - } - return nativeValue; - } - var reflected = Reflect.get(target, property, receiver); - if (reflected !== undefined || property in target) { - return reflected; - } - installNativeClassMembersIfNeeded(); - reflected = Reflect.get(target, property, receiver); - if (reflected !== undefined || property in target) { - return reflected; - } - return reflected; - }, - set: function(target, property, value, receiver) { - if (property === 'prototype') { - target[property] = value; - return true; - } - if (setDescriptorValue(target, property, receiver, value)) { - return true; - } - try { - nativeClass[property] = value; - return true; - } catch (_) { - } - if (receiver && receiver !== target) { - Object.defineProperty(receiver, property, { - configurable: true, - enumerable: true, - writable: true, - value: value - }); - return true; - } - return Reflect.set(target, property, value, receiver); - }, - has: function(target, property) { - installNativeClassMembersIfNeeded(); - return property in target || property in nativeClass; - }, - ownKeys: function(target) { - installNativeClassMembersIfNeeded(); - return Reflect.ownKeys(target).filter(function(key) { - return key !== 'new' && - key !== 'hasOwnProperty' && - key !== '__nativeApiInstallMembers'; - }); - }, - getOwnPropertyDescriptor: function(target, property) { - installNativeClassMembersIfNeeded(); - return Reflect.getOwnPropertyDescriptor(target, property); - } - }) - : constructable; - if (classWrappers) { - classWrappers.set(nativeClass, wrapper); - } - try { - var nativeSuperclass = nativeClass.__superclass; - if (nativeSuperclass && nativeSuperclass !== nativeClass) { - var superclassWrapper = wrapNativeClass(nativeSuperclass); - if (superclassWrapper && superclassWrapper !== wrapper && - typeof Object.setPrototypeOf === 'function') { - Object.setPrototypeOf(wrapper, superclassWrapper); - } - } - } catch (_) { - } - try { - api.__rememberClassWrapper(nativeClass, wrapper, constructable.prototype); - } catch (_) { - } - if (nativeClassName) { - classWrappersByName[nativeClassName] = wrapper; - cacheGlobal(nativeClassName, wrapper); - if (!Object.prototype.hasOwnProperty.call(globalThis, nativeClassName)) { - try { - Object.defineProperty(globalThis, nativeClassName, { - configurable: true, - enumerable: false, - writable: false, - value: wrapper - }); - } catch (_) { - } - } - } - if (nativeClass.name && nativeClass.name !== nativeClassName) { - classWrappersByName[nativeClass.name] = wrapper; - cacheGlobal(nativeClass.name, wrapper); - } - return wrapper; - } - - function rememberClassOnInstance(instance, classWrapper) { - if (instance && typeof instance === 'object' && classWrapper) { - try { - if (typeof api.__rememberObjectClassWrapper === 'function') { - api.__rememberObjectClassWrapper(instance, classWrapper); - } else { - instance.__nativeApiClassWrapper = classWrapper; - } - } catch (_) { - } - } - return instance; - } - - function isNativeClassLike(value) { - if (!value || (typeof value !== 'object' && typeof value !== 'function')) { - return false; - } - if (value.kind === 'class') { - return true; - } - try { - return !!value.__nativeApiClass; - } catch (_) { - return false; - } - } - - function nativeClassLikeHandle(value) { - if (!value || (typeof value !== 'object' && typeof value !== 'function')) { - return value; - } - try { - if (typeof value.__nativeApiEnsureClass === 'function') { - value = value.__nativeApiEnsureClass(); - } - } catch (_) { - } - try { - return value.__nativeApiClass || value; - } catch (_) { - return value; - } - } - - function materializeTypeScriptNativeClass(constructor) { - if (!constructor || typeof constructor !== 'function') { - return undefined; - } - var state = constructor.__nativeApiTypeScriptState; - if (!state) { - return undefined; - } - if (state.wrapper) { - return state.wrapper; - } - if (state.materializing) { - return state.base; - } - - state.materializing = true; - try { - var baseWrapper = state.base; - if (baseWrapper && typeof baseWrapper.__nativeApiEnsureClass === 'function') { - baseWrapper = baseWrapper.__nativeApiEnsureClass(); - } - - var options = {}; - var className = constructor.ObjCClassName || constructor.name; - if (className) { - options.name = className; - } - if (constructor.ObjCProtocols) { - options.protocols = constructor.ObjCProtocols; - } - if (constructor.ObjCExposedMethods) { - options.exposedMethods = constructor.ObjCExposedMethods; - } - - var nativeBase = nativeClassLikeHandle(baseWrapper); - var nativeClass = api.__extendClass(nativeBase, constructor.prototype || {}, options); - var wrapper = wrapNativeClass(nativeClass); - state.wrapper = wrapper; - - try { - Object.setPrototypeOf(constructor, wrapper); - } catch (_) { - } - try { - api.__rememberClassWrapper(nativeClass, constructor, constructor.prototype || {}); - } catch (_) { - } - return wrapper; - } finally { - state.materializing = false; - } - } - - function defineTypeScriptStaticForwarder(constructor, name, isProperty, readonly) { - if (!name || name === 'length' || name === 'name' || name === 'prototype' || - Object.prototype.hasOwnProperty.call(constructor, name)) { - return; - } - - var descriptor = { - configurable: true, - enumerable: false - }; - - if (isProperty) { - descriptor.get = function() { - var wrapper = materializeTypeScriptNativeClass(constructor); - return wrapper ? wrapper[name] : undefined; - }; - if (!readonly) { - descriptor.set = function(value) { - var wrapper = materializeTypeScriptNativeClass(constructor); - if (wrapper) { - wrapper[name] = value; - } - }; - } - } else { - descriptor.writable = true; - descriptor.value = function() { - if (name === 'class') { - materializeTypeScriptNativeClass(constructor); - return constructor; - } - if (name === 'superclass') { - var state = constructor.__nativeApiTypeScriptState; - return state && state.base; - } - var wrapper = materializeTypeScriptNativeClass(constructor); - var member = wrapper && wrapper[name]; - if (typeof member !== 'function') { - throw new TypeError(String(name) + ' is not a function'); - } - var result = member.apply(wrapper, arguments); - if (name === 'alloc' || name === 'new' || name === 'construct') { - return rememberClassOnInstance(result, constructor); - } - return result; - }; - } - - try { - Object.defineProperty(constructor, name, descriptor); - } catch (_) { - } - } - - function installTypeScriptNativeClassSupport(constructor, base) { - if (!constructor || typeof constructor !== 'function' || !isNativeClassLike(base)) { - return false; - } - if (constructor.__nativeApiTypeScriptState) { - return true; - } - - try { - Object.defineProperty(constructor, '__nativeApiTypeScriptState', { - configurable: false, - enumerable: false, - writable: false, - value: { - base: base, - wrapper: null, - materializing: false - } - }); - } catch (_) { - constructor.__nativeApiTypeScriptState = { - base: base, - wrapper: null, - materializing: false - }; - } - - try { - Object.defineProperty(constructor, '__nativeApiEnsureClass', { - configurable: false, - enumerable: false, - writable: false, - value: function() { - return materializeTypeScriptNativeClass(constructor); - } - }); - } catch (_) { - } - - try { - Object.defineProperty(constructor, '__nativeApiClass', { - configurable: true, - enumerable: false, - get: function() { - var wrapper = materializeTypeScriptNativeClass(constructor); - return wrapper && wrapper.__nativeApiClass; - } - }); - } catch (_) { - } - - ['alloc', 'new', 'class', 'superclass', 'extend'].forEach(function(name) { - defineTypeScriptStaticForwarder(constructor, name, false, false); - }); - - try { - var members = base.__staticMembers || []; - for (var i = 0; i < members.length; i++) { - var member = members[i]; - if (member && member.name) { - defineTypeScriptStaticForwarder( - constructor, - member.name, - !!member.property, - !!member.readonly - ); - } - } - } catch (_) { - } - - return true; - } - - function installTypeScriptNativeHelpers() { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function(d, b) { d.__proto__ = b; }) || - function(d, b) { - for (var p in b) { - if (Object.prototype.hasOwnProperty.call(b, p)) { - d[p] = b[p]; - } - } - }; - - globalThis.__extends = function(d, b) { - if (typeof b !== 'function' && b !== null) { - throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null'); - } - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - if (b !== null) { - installTypeScriptNativeClassSupport(d, b); - } - }; - - globalThis.NativeClass = function NativeClass(constructor) { - if (constructor && typeof constructor.__nativeApiEnsureClass === 'function') { - constructor.__nativeApiEnsureClass(); - } - return constructor; - }; - - globalThis.ObjCClass = function ObjCClass() { - var protocols = Array.prototype.slice.call(arguments); - return function(constructor) { - if (constructor.ObjCProtocols) { - Array.prototype.push.apply(constructor.ObjCProtocols, protocols); - } else { - constructor.ObjCProtocols = protocols; - } - if (typeof constructor.__nativeApiEnsureClass === 'function') { - constructor.__nativeApiEnsureClass(); - } - return constructor; - }; - }; - } - - function wrapInteropFactory(nativeFactory, properties) { - if (typeof nativeFactory !== 'function' || nativeFactory.__nativeScriptConstructable) { - return nativeFactory; - } - var constructable = function NativeScriptInteropValue() { - return nativeFactory.apply(undefined, arguments); - }; - try { - if (nativeFactory.prototype) { - constructable.prototype = nativeFactory.prototype; - } - } catch (_) { - } - try { - Object.defineProperty(constructable, Symbol.hasInstance, { - configurable: true, - enumerable: false, - value: function(value) { - return !!value && typeof value === 'object' && value.kind === properties.kind; - } - }); - } catch (_) { - } - Object.keys(properties).forEach(function(key) { - try { - Object.defineProperty(constructable, key, { - configurable: true, - enumerable: false, - writable: false, - value: properties[key] - }); - } catch (_) { - } - }); - Object.defineProperty(constructable, '__nativeScriptConstructable', { - configurable: false, - enumerable: false, - writable: false, - value: true - }); - return constructable; - } - - function installInteropConstructors() { - var interop = globalThis.interop; - if (!interop || typeof interop !== 'object') { - return; - } - var pointerSize; - try { - if (typeof interop.sizeof === 'function' && interop.types && interop.types.pointer !== undefined) { - pointerSize = interop.sizeof(interop.types.pointer); - } - } catch (_) { - pointerSize = undefined; - } - interop.Pointer = wrapInteropFactory(interop.Pointer, { kind: 'pointer', sizeof: pointerSize }); - interop.Reference = wrapInteropFactory(interop.Reference, { kind: 'reference', sizeof: pointerSize }); - interop.FunctionReference = wrapInteropFactory( - interop.FunctionReference, - { kind: 'functionReference', sizeof: pointerSize } - ); - if (interop.types && typeof interop.types === 'object') { - Object.keys(interop.types).forEach(function(name) { - var value = interop.types[name]; - if (typeof value !== 'number') { - return; - } - var boxed = { - valueOf: function() { return value; }, - toString: function() { return String(value); } - }; - Object.defineProperty(boxed, typeCodeKey, { - configurable: false, - enumerable: false, - writable: false, - value: value - }); - interop.types[name] = boxed; - }); - } - } - - function defineInlineFunction(name, value) { - if (Object.prototype.hasOwnProperty.call(globalThis, name)) { - return; - } - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: false, - writable: true, - value: value - }); - } - - function installInlineFunctions() { - var makePoint = function(x, y) { return { x: x, y: y }; }; - var makeSize = function(width, height) { return { width: width, height: height }; }; - var makeRect = function(x, y, width, height) { - return { origin: { x: x, y: y }, size: { width: width, height: height } }; - }; - defineInlineFunction('CGPointMake', makePoint); - defineInlineFunction('NSMakePoint', makePoint); - defineInlineFunction('CGSizeMake', makeSize); - defineInlineFunction('NSMakeSize', makeSize); - defineInlineFunction('CGRectMake', makeRect); - defineInlineFunction('NSMakeRect', makeRect); - defineInlineFunction('NSMakeRange', function(location, length) { - return { location: location, length: length }; - }); - defineInlineFunction('UIEdgeInsetsMake', function(top, left, bottom, right) { - return { top: top, left: left, bottom: bottom, right: right }; - }); - } - - function names(kind) { - var metadata = api.metadata; - var fn = metadata && metadata[kind]; - return typeof fn === 'function' ? fn() : []; - } - - function nameSet(values) { - var result = Object.create(null); - (values || []).forEach(function(value) { - result[value] = true; - }); - return result; - } - - var classNameList = names('classNames'); - var functionNameList = names('functionNames'); - var constantNameList = names('constantNames'); - var protocolNameList = names('protocolNames'); - var enumNameList = names('enumNames'); - var functionNameSet = nameSet(functionNameList); - var constantNameSet = nameSet(constantNameList); - var classNameSet = nameSet(classNameList); - var protocolNameSet = nameSet(protocolNameList); - var enumNameSet = nameSet(enumNameList); - - function resolveNativeApiEnum(enumName) { - return (api.getEnum && api.getEnum(enumName)) || api[enumName]; - } - - Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiLazyGlobal', { - configurable: false, - enumerable: false, - writable: false, - value: function(name, kind) { - var value; - if (kind === 'class') { - value = wrapNativeClass(api[name]); - } else if (kind === 'function' || kind === 'constant') { - value = api[name]; - } else if (kind === 'protocol') { - value = (api.getProtocol && api.getProtocol(name)) || api[name]; - } else if (kind === 'enum') { - value = resolveNativeApiEnum(name); - } else if (kind === 'struct') { - value = wrapAggregateConstructor((api.getStruct && api.getStruct(name)) || api[name]); - } else if (kind === 'union') { - value = wrapAggregateConstructor((api.getUnion && api.getUnion(name)) || api[name]); - } else if (kind && kind.indexOf('enumMember:') === 0) { - var enumValue = resolveNativeApiEnum(kind.slice('enumMember:'.length)); - value = enumValue && enumValue[name]; - } else { - value = api[name]; - } - cacheGlobal(name, value); - return value; - } - }); - - classNameList.forEach(function(name) { - defineLazyGlobal(name, function(className) { - return wrapNativeClass(api[className]); - }, false, 'class'); - }); - functionNameList.forEach(function(name) { - defineLazyGlobal(name, function(functionName) { - return api[functionName]; - }, false, 'function'); - }); - constantNameList.forEach(function(name) { - defineLazyGlobal(name, function(constantName) { - return api[constantName]; - }, false, 'constant'); - }); - protocolNameList.forEach(function(name) { - defineLazyGlobal(name, function(protocolName) { - return (api.getProtocol && api.getProtocol(protocolName)) || api[protocolName]; - }, false, 'protocol'); - }); - enumNameList.forEach(function(name) { - defineLazyGlobal(name, resolveNativeApiEnum, false, 'enum'); - var enumValue = resolveNativeApiEnum(name); - if (!enumValue || typeof enumValue !== 'object') { - return; - } - Object.keys(enumValue).forEach(function(memberName) { - if (/^-?\d+$/.test(memberName)) { - return; - } - defineLazyGlobal(memberName, function() { - return enumValue[memberName]; - }, false, 'enumMember:' + name); - }); - }); - names('structNames').forEach(function(name) { - var conflictsWithValue = - !!functionNameSet[name] || !!constantNameSet[name] || !!classNameSet[name] || - !!protocolNameSet[name] || !!enumNameSet[name]; - defineLazyGlobal(name, function(structName) { - return wrapAggregateConstructor((api.getStruct && api.getStruct(structName)) || api[structName]); - }, !conflictsWithValue, 'struct'); - }); - names('unionNames').forEach(function(name) { - var conflictsWithValue = - !!functionNameSet[name] || !!constantNameSet[name] || !!classNameSet[name] || - !!protocolNameSet[name] || !!enumNameSet[name]; - defineLazyGlobal(name, function(unionName) { - return wrapAggregateConstructor((api.getUnion && api.getUnion(unionName)) || api[unionName]); - }, !conflictsWithValue, 'union'); - }); - - if (typeof globalThis.UIColor === 'undefined' && - typeof globalThis.NSColor !== 'undefined') { - globalThis.UIColor = globalThis.NSColor; - cacheGlobal('UIColor', globalThis.UIColor); - } - var colorCtor = globalThis.UIColor || globalThis.NSColor; - if (colorCtor && colorCtor.prototype && - typeof colorCtor.prototype.initWithRedGreenBlueAlpha !== 'function') { - colorCtor.prototype.initWithRedGreenBlueAlpha = function(red, green, blue, alpha) { - if (typeof this.initWithSRGBRedGreenBlueAlpha === 'function') { - return this.initWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof this.initWithCalibratedRedGreenBlueAlpha === 'function') { - return this.initWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof colorCtor.colorWithSRGBRedGreenBlueAlpha === 'function') { - return colorCtor.colorWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof colorCtor.colorWithCalibratedRedGreenBlueAlpha === 'function') { - return colorCtor.colorWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); - } - return this; - }; - } - defineLazyGlobal('CC_SHA256', function() { return api.CC_SHA256; }); - - installInteropConstructors(); - installTypeScriptNativeHelpers(); - installInlineFunctions(); - - try { - Object.defineProperty(globalThis, installedFlagName, { - configurable: false, - enumerable: false, - writable: false, - value: true - }); - } catch (_) { - } -}) -)JSI_GLOBALS"; - - std::string script(GlobalInstaller); - script += "("; - script += jsStringLiteral(globalName); - script += ");"; - runtime.evaluateJavaScript(std::make_shared(std::move(script)), - "NativeApiJsiGlobals.js"); - NativeApiJsiWriteSmokeStage("jsi:globals:after-eval"); -} - -void InstallNativeApiJSI(Runtime& runtime, const NativeApiJsiConfig& config) { - const char* globalName = config.globalName != nullptr && config.globalName[0] != '\0' - ? config.globalName - : "__nativeScriptNativeApi"; - NativeApiJsiWriteSmokeStage("jsi:create-api"); - Object api = CreateNativeApiJSI(runtime, config); - Object global = runtime.global(); - NativeApiJsiWriteSmokeStage("jsi:set-global"); - global.setProperty(runtime, globalName, api); - - NativeApiJsiWriteSmokeStage("jsi:set-interop"); - Value existingInterop = global.getProperty(runtime, "interop"); - if (existingInterop.isUndefined() || existingInterop.isNull()) { - global.setProperty(runtime, "interop", api.getProperty(runtime, "interop")); - } - if (config.installGlobalSymbols) { - NativeApiJsiWriteSmokeStage("jsi:install-globals"); - InstallNativeApiJsiGlobalSymbols(runtime, globalName); - } else { - NativeApiJsiWriteSmokeStage("jsi:install-aggregate-globals"); - InstallAggregateGlobals(runtime, api, "protocolNames"); - } - NativeApiJsiWriteSmokeStage("jsi:installed"); -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiInvocation.h b/NativeScript/ffi/shared/jsi/NativeApiJsiInvocation.h deleted file mode 100644 index fdc21b5b1..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiInvocation.h +++ /dev/null @@ -1,518 +0,0 @@ -bool isValidMetadataStringOffset(MDMetadataReader* metadata, - MDSectionOffset offset) { - if (metadata == nullptr || metadata->constantsOffset < metadata->stringsOffset) { - return false; - } - return offset < metadata->constantsOffset - metadata->stringsOffset; -} - -bool startsWith(const std::string& value, const std::string& prefix) { - return value.size() >= prefix.size() && - value.compare(0, prefix.size(), prefix) == 0; -} - -bool endsWith(const std::string& value, const std::string& suffix) { - return value.size() >= suffix.size() && - value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0; -} - -std::string stripEnumSuffix(const std::string& enumName) { - static const std::vector suffixes = { - "Options", "Option", "Enums", "Enum", "Result", "Direction", - "Orientation", "Style", "Mask", "Type", "Status", "Modes", "Mode", "s"}; - - for (const auto& suffix : suffixes) { - if (enumName.size() > suffix.size() && endsWith(enumName, suffix)) { - return enumName.substr(0, enumName.size() - suffix.size()); - } - } - - return enumName; -} - -bool isNSComparisonResultOrderingName(const std::string& enumName, - const std::string& member) { - if (enumName != "NSComparisonResult") { - return false; - } - return member == "Ascending" || member == "Same" || member == "Descending"; -} - -Value enumToObject(Runtime& runtime, MDMetadataReader* metadata, - const NativeApiSymbol& symbol) { - Object result(runtime); - if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { - return result; - } - - std::string enumName = symbol.name; - std::string strippedPrefix = stripEnumSuffix(enumName); - MDSectionOffset offset = symbol.offset + sizeof(MDSectionOffset); - bool next = true; - while (next) { - auto nameOffset = metadata->getOffset(offset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - offset += sizeof(MDSectionOffset); - - const char* memberName = metadata->resolveString(nameOffset); - int64_t value = metadata->getEnumValue(offset); - offset += sizeof(int64_t); - - std::string canonicalName = memberName != nullptr ? memberName : ""; - std::vector aliases; - aliases.push_back(canonicalName); - - if (!strippedPrefix.empty() && startsWith(canonicalName, strippedPrefix) && - canonicalName.size() > strippedPrefix.size()) { - aliases.push_back(canonicalName.substr(strippedPrefix.size())); - } else if (!strippedPrefix.empty() && - !startsWith(canonicalName, strippedPrefix)) { - aliases.push_back(strippedPrefix + canonicalName); - } - - if (startsWith(enumName, "NS") && !startsWith(canonicalName, "NS")) { - aliases.push_back(std::string("NS") + canonicalName); - } - - if (enumName == "NSStringCompareOptions" && - !endsWith(canonicalName, "Search")) { - aliases.push_back(canonicalName + "Search"); - aliases.push_back(std::string("NS") + canonicalName + "Search"); - } - - if (!startsWith(canonicalName, "k")) { - aliases.push_back(std::string("k") + enumName + canonicalName); - } - - if (isNSComparisonResultOrderingName(enumName, canonicalName)) { - aliases.push_back(std::string("Ordered") + canonicalName); - aliases.push_back(std::string("NSOrdered") + canonicalName); - } - - std::vector uniqueAliases; - std::unordered_set seenAliases; - for (const auto& alias : aliases) { - if (!alias.empty() && seenAliases.insert(alias).second) { - uniqueAliases.push_back(alias); - } - } - - for (const auto& alias : uniqueAliases) { - result.setProperty(runtime, alias.c_str(), static_cast(value)); - } - - char valueKey[32] = {}; - snprintf(valueKey, sizeof(valueKey), "%lld", static_cast(value)); - if (!result.hasProperty(runtime, valueKey)) { - std::string reverseName = - uniqueAliases.size() > 1 ? uniqueAliases[1] : canonicalName; - result.setProperty(runtime, valueKey, makeString(runtime, reverseName)); - } - } - return result; -} - -Value constantToValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiSymbol& symbol) { - MDMetadataReader* metadata = bridge->metadata(); - if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { - return Value::undefined(); - } - - MDSectionOffset offset = symbol.offset + sizeof(MDSectionOffset); - auto evalKind = metadata->getVariableEvalKind(offset); - offset += sizeof(metagen::MDVariableEvalKind); - - switch (evalKind) { - case metagen::mdEvalInt64: - return static_cast(metadata->getInt64(offset)); - case metagen::mdEvalDouble: - return metadata->getDouble(offset); - case metagen::mdEvalString: { - if (isValidMetadataStringOffset(metadata, offset)) { - auto stringOffset = metadata->getOffset(offset); - return makeString(runtime, metadata->resolveString(stringOffset)); - } - - void* symbolPtr = dlsym(bridge->selfDl(), symbol.name.c_str()); - if (symbolPtr == nullptr) { - return Value::undefined(); - } - - NativeApiJsiType stringObjectType; - stringObjectType.kind = metagen::mdTypeNSStringObject; - stringObjectType.ffiType = &ffi_type_pointer; - stringObjectType.supported = true; - return convertNativeReturnValue(runtime, bridge, stringObjectType, - symbolPtr); - } - case metagen::mdEvalNone: - break; - } - - MDSectionOffset typeOffset = offset; - NativeApiJsiType type = parseMetadataJsiType(metadata, &typeOffset, bridge.get()); - if (unsupportedJsiType(type)) { - throw facebook::jsi::JSError( - runtime, "Native constant type is not supported by pure JSI: " + - symbol.name); - } - - void* symbolPtr = dlsym(bridge->selfDl(), symbol.name.c_str()); - if (symbolPtr == nullptr) { - return Value::undefined(); - } - return convertNativeReturnValue(runtime, bridge, type, symbolPtr); -} - -void prepareJsiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, const Value& arg, - size_t index, NativeApiJsiArgumentFrame& frame) { - ffi_type* ffiType = ffiTypeForJsiArgument(type); - size_t size = - ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); - void* target = frame.storageAt(index, size); - convertJsiFfiArgument(runtime, bridge, type, arg, target, frame); -} - -void prepareJsiArguments(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiSignature& signature, - const Value* args, size_t count, - NativeApiJsiArgumentFrame& frame) { - if (count != signature.argumentTypes.size()) { - throw facebook::jsi::JSError( - runtime, "Actual arguments count: \"" + std::to_string(count) + - "\". Expected: \"" + - std::to_string(signature.argumentTypes.size()) + "\"."); - } - - for (size_t i = 0; i < signature.argumentTypes.size(); i++) { - prepareJsiArgument(runtime, bridge, signature.argumentTypes[i], args[i], i, - frame); - } -} - -Value callNativeFunctionPointer( - Runtime& runtime, const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* pointer, bool block, const Value* args, - size_t count) { - if (pointer == nullptr) { - throw facebook::jsi::JSError(runtime, "Native function pointer is null."); - } - if (bridge == nullptr || bridge->metadata() == nullptr || - type.signatureOffset == MD_SECTION_OFFSET_NULL) { - throw facebook::jsi::JSError( - runtime, "Native function pointer metadata is unavailable."); - } - - auto signature = parseMetadataJsiSignature( - bridge->metadata(), type.signatureOffset, block ? 1 : 0, bridge.get()); - if (!signature || !signature->prepared || signature->variadic || - unsupportedJsiType(signature->returnType)) { - throw facebook::jsi::JSError( - runtime, - "Native function pointer signature is not supported by pure JSI."); - } - - NativeApiJsiArgumentFrame frame(signature->argumentTypes.size()); - prepareJsiArguments(runtime, bridge, *signature, args, count, frame); - - std::vector values; - if (block) { - values.reserve(signature->argumentTypes.size() + 1); - values.push_back(&pointer); - for (size_t i = 0; i < signature->argumentTypes.size(); i++) { - values.push_back(frame.values()[i]); - } - } - - void* callable = pointer; - if (block) { - auto literal = static_cast(pointer); - if (literal == nullptr || literal->invoke == nullptr) { - throw facebook::jsi::JSError(runtime, "Native block invoke pointer is null."); - } - callable = literal->invoke; - } - - std::vector returnStorage( - std::max(nativeSizeForType(signature->returnType), sizeof(void*)), 0); - performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { - ffi_call(&signature->cif, FFI_FN(callable), returnStorage.data(), - block ? values.data() : frame.values()); - }); - - return convertNativeReturnValue(runtime, bridge, signature->returnType, - returnStorage.data()); -} - -Value wrapNativeFunctionPointer(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* pointer, - bool block) { - const char* functionName = block ? "NativeApiJsiBlock" : "NativeApiJsiFunctionPointer"; - auto function = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, functionName), 0, - [bridge, type, pointer, block](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - return callNativeFunctionPointer(runtime, bridge, type, pointer, block, - args, count); - }); - function.setProperty(runtime, "kind", - makeString(runtime, block ? "block" : "functionPointer")); - function.setProperty( - runtime, "__nativeApiPointerObject", - createPointer(runtime, bridge, pointer)); - function.setProperty( - runtime, "__nativeApiPointer", - static_cast(reinterpret_cast(pointer))); - function.setProperty( - runtime, "nativeAddress", - static_cast(reinterpret_cast(pointer))); - function.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - function.setProperty( - runtime, "toString", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [pointer, block](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", pointer); - return makeString(runtime, - std::string("[NativeApiJsi ") + - (block ? "Block " : "FunctionPointer ") + - address + "]"); - })); - return function; -} - -Value callCFunction(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiSymbol& symbol, const Value* args, - size_t count) { - MDMetadataReader* metadata = bridge->metadata(); - if (metadata == nullptr) { - throw facebook::jsi::JSError(runtime, "Native metadata is not loaded."); - } - - void* fnptr = dlsym(bridge->selfDl(), symbol.name.c_str()); - if (fnptr == nullptr) { - throw facebook::jsi::JSError(runtime, - "Native function is not available: " + - symbol.name); - } - - MDSectionOffset signatureOffset = - metadata->signaturesOffset + - metadata->getOffset(symbol.offset + sizeof(MDSectionOffset)); - auto signature = parseMetadataJsiSignature( - metadata, signatureOffset, 0, bridge.get(), - (metadata->getFunctionFlag(symbol.offset + sizeof(MDSectionOffset) * 2) & - metagen::mdFunctionReturnOwned) != 0); - if (!signature || !signature->prepared || signature->variadic || - unsupportedJsiType(signature->returnType)) { - throw facebook::jsi::JSError( - runtime, "Native function signature is not supported by pure JSI: " + - symbol.name); - } - - NativeApiJsiArgumentFrame frame(signature->argumentTypes.size()); - prepareJsiArguments(runtime, bridge, *signature, args, count, frame); - - if (symbol.name == "NSApplicationMain" || - symbol.name == "UIApplicationMain") { - runtime.drainMicrotasks(); - } - - std::vector returnStorage( - std::max(nativeSizeForType(signature->returnType), sizeof(void*)), 0); - bool dispatchingNativeCallToUI = shouldDispatchNativeCallToUI(); - bool retainedReturn = false; - performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { - ffi_call(&signature->cif, FFI_FN(fnptr), returnStorage.data(), - frame.values()); - if (dispatchingNativeCallToUI && - !signature->returnType.returnOwned && - isObjectiveCObjectType(signature->returnType)) { - id object = *reinterpret_cast(returnStorage.data()); - if (object != nil) { - [object retain]; - retainedReturn = true; - } - } - }); - - NativeApiJsiType returnType = signature->returnType; - if (retainedReturn) { - returnType.returnOwned = true; - } - if (symbol.name == "CFBagContainsValue" && - (returnType.kind == metagen::mdTypeChar || - returnType.kind == metagen::mdTypeUChar || - returnType.kind == metagen::mdTypeUInt8)) { - return *returnStorage.data() != 0; - } - return convertNativeReturnValue(runtime, bridge, returnType, - returnStorage.data()); -} - -bool signatureSupportedForJsiInvocation( - const std::optional& signature) { - if (!signature || !signature->prepared || signature->variadic || - unsupportedJsiType(signature->returnType)) { - return false; - } - for (const auto& argType : signature->argumentTypes) { - if (unsupportedJsiType(argType)) { - return false; - } - } - return true; -} - -Value callObjCSelector(Runtime& runtime, - const std::shared_ptr& bridge, - id receiver, bool receiverIsClass, - const std::string& selectorName, - const NativeApiMember* member, - const Value* args, size_t count, - Class dispatchSuperClass) { - if (receiver == nil) { - throw facebook::jsi::JSError(runtime, - "Cannot send Objective-C selector to nil."); - } - - SEL selector = sel_registerName(selectorName.c_str()); - Class receiverClass = - receiverIsClass ? static_cast(receiver) : object_getClass(receiver); - Class lookupClass = dispatchSuperClass != Nil ? dispatchSuperClass : receiverClass; - Method method = receiverIsClass ? class_getClassMethod(lookupClass, selector) - : class_getInstanceMethod(lookupClass, selector); - if (method == nullptr && - (dispatchSuperClass != Nil || ![receiver respondsToSelector:selector])) { - throw facebook::jsi::JSError(runtime, - "Objective-C selector is not available: " + - selectorName); - } - - std::optional signature; - std::optional runtimeSignature; - if (member != nullptr && - member->signatureOffset != MD_SECTION_OFFSET_NULL && - member->signatureOffset != 0) { - signature = parseMetadataJsiSignature( - bridge->metadata(), member->signatureOffset, 2, bridge.get(), - (member->flags & metagen::mdMemberReturnOwned) != 0); - } - if (method != nullptr) { - runtimeSignature = parseObjCMethodJsiSignature(method, bridge.get()); - } - if (signatureSupportedForJsiInvocation(signature) && - signatureSupportedForJsiInvocation(runtimeSignature)) { - reconcileObjCMethodRuntimeSignature(&*signature, *runtimeSignature); - } - if (!signatureSupportedForJsiInvocation(signature) && runtimeSignature) { - signature = std::move(runtimeSignature); - } - - if (!signatureSupportedForJsiInvocation(signature)) { - throw facebook::jsi::JSError( - runtime, "Objective-C signature is not supported by pure JSI: " + - selectorName); - } - signature->selectorName = selectorName; - - NativeApiJsiArgumentFrame frame(signature->argumentTypes.size()); - const bool isNSErrorOutMethod = isNSErrorOutJsiMethodSignature(*signature); - if (isNSErrorOutMethod) { - size_t expected = signature->argumentTypes.size(); - if (count > expected || count + 1 < expected) { - throw facebook::jsi::JSError( - runtime, "Actual arguments count: \"" + std::to_string(count) + - "\". Expected: \"" + std::to_string(expected) + "\"."); - } - } - - const bool hasImplicitNSErrorOutArg = - isNSErrorOutMethod && count + 1 == signature->argumentTypes.size(); - NSError* implicitNSError = nil; - if (hasImplicitNSErrorOutArg) { - for (size_t i = 0; i < count; i++) { - prepareJsiArgument(runtime, bridge, signature->argumentTypes[i], args[i], i, - frame); - } - - size_t outArgIndex = signature->argumentTypes.size() - 1; - void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); - NSError** implicitNSErrorOutArg = &implicitNSError; - *static_cast(target) = implicitNSErrorOutArg; - } else { - prepareJsiArguments(runtime, bridge, *signature, args, count, frame); - } - - std::vector values; - values.reserve(signature->argumentTypes.size() + 2); - struct objc_super superReceiver = {receiver, dispatchSuperClass}; - struct objc_super* superReceiverPtr = &superReceiver; - if (dispatchSuperClass != Nil) { - values.push_back(&superReceiverPtr); - } else { - values.push_back(&receiver); - } - values.push_back(&selector); - for (size_t i = 0; i < signature->argumentTypes.size(); i++) { - values.push_back(frame.values()[i]); - } - - std::vector returnStorage( - std::max(nativeSizeForType(signature->returnType), sizeof(void*)), 0); - bool dispatchingNativeCallToUI = shouldDispatchNativeCallToUI(); - bool retainedReturn = false; - performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { -#if defined(__x86_64__) - bool isStret = signature->returnType.ffiType->size > 16 && - signature->returnType.ffiType->type == FFI_TYPE_STRUCT; - void (*target)(void) = dispatchSuperClass != Nil - ? (isStret ? FFI_FN(objc_msgSendSuper_stret) - : FFI_FN(objc_msgSendSuper)) - : (isStret ? FFI_FN(objc_msgSend_stret) - : FFI_FN(objc_msgSend)); - ffi_call(&signature->cif, target, returnStorage.data(), values.data()); -#else - ffi_call(&signature->cif, - dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) - : FFI_FN(objc_msgSend), - returnStorage.data(), values.data()); -#endif - if (dispatchingNativeCallToUI && - !signature->returnType.returnOwned && - isObjectiveCObjectType(signature->returnType)) { - id object = *reinterpret_cast(returnStorage.data()); - if (object != nil) { - [object retain]; - retainedReturn = true; - } - } - }); - - NativeApiJsiType returnType = signature->returnType; - if ((selectorName == "valueForKey:" || selectorName == "valueForKeyPath:") && - isObjectiveCObjectType(returnType)) { - returnType.kind = metagen::mdTypeAnyObject; - } - if (retainedReturn) { - returnType.returnOwned = true; - } - if (hasImplicitNSErrorOutArg && implicitNSError != nil) { - const char* errorMessage = [[implicitNSError description] UTF8String]; - throw facebook::jsi::JSError( - runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); - } - return convertNativeReturnValue(runtime, bridge, returnType, - returnStorage.data()); -} diff --git a/NativeScript/ffi/v8/NativeApiV8.h b/NativeScript/ffi/v8/NativeApiV8.h deleted file mode 100644 index cf8c4054a..000000000 --- a/NativeScript/ffi/v8/NativeApiV8.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H -#define NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H - -#include "ffi/shared/direct/NativeApiDirect.h" -#include "v8.h" - -namespace nativescript { - -using NativeApiV8Config = NativeApiDirectConfig; - -void InstallNativeApiV8(v8::Isolate* isolate, - v8::Local context, - const NativeApiV8Config& config = NativeApiV8Config{}); - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiV8(v8::Isolate* isolate, - v8::Local context, - const char* metadataPath); - -#endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H diff --git a/NativeScript/ffi/v8/NativeApiV8.mm b/NativeScript/ffi/v8/NativeApiV8.mm deleted file mode 100644 index 154a11947..000000000 --- a/NativeScript/ffi/v8/NativeApiV8.mm +++ /dev/null @@ -1,181 +0,0 @@ -#include "NativeApiV8.h" - -#ifdef TARGET_ENGINE_V8 - -#include "NativeApiV8Runtime.h" - -namespace nativescript { - -using NativeApiJsiConfig = NativeApiDirectConfig; -using NativeApiJsiScheduler = NativeApiDirectScheduler; - -namespace { - -using facebook::jsi::Array; -using facebook::jsi::ArrayBuffer; -using facebook::jsi::BigInt; -using facebook::jsi::Function; -using facebook::jsi::HostObject; -using facebook::jsi::MutableBuffer; -using facebook::jsi::Object; -using facebook::jsi::PropNameID; -using facebook::jsi::Runtime; -using facebook::jsi::String; -using facebook::jsi::StringBuffer; -using facebook::jsi::Value; -using metagen::MDMemberFlag; -using metagen::MDMetadataReader; -using metagen::MDSectionOffset; -using metagen::MDTypeKind; - -// clang-format off -#include "jsi/NativeApiJsiBridge.h" -// clang-format on - -#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS 1 -#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 -#define NATIVESCRIPT_NATIVE_API_RUNTIME_SCOPE 1 - -struct NativeApiV8LazyGlobalData { - NativeApiV8LazyGlobalData(v8::Isolate* isolate, const std::string& name, - const std::string& kind) { - nameValue.Reset(isolate, facebook::jsi::v8direct::makeV8String(isolate, name)); - kindValue.Reset(isolate, facebook::jsi::v8direct::makeV8String(isolate, kind)); - } - - ~NativeApiV8LazyGlobalData() { - nameValue.Reset(); - kindValue.Reset(); - } - - v8::Global nameValue; - v8::Global kindValue; -}; - -std::shared_ptr retainNativeApiJsiRuntime(Runtime& runtime) { - return std::make_shared(runtime.state()); -} - -class NativeApiJsiRuntimeScope final { - public: - explicit NativeApiJsiRuntimeScope(Runtime& runtime) - : locker_(runtime.isolate()), - isolateScope_(runtime.isolate()), - handleScope_(runtime.isolate()), - context_(runtime.context()), - contextScope_(context_) {} - - private: - v8::Locker locker_; - v8::Isolate::Scope isolateScope_; - v8::HandleScope handleScope_; - v8::Local context_; - v8::Context::Scope contextScope_; -}; - -void NativeApiV8LazyGlobalGetter(v8::Local, - const v8::PropertyCallbackInfo& info) { - v8::Isolate* isolate = info.GetIsolate(); - v8::HandleScope handleScope(isolate); - v8::Local context = isolate->GetCurrentContext(); - if (!info.Data()->IsExternal()) { - return; - } - - auto* data = static_cast(info.Data().As()->Value()); - if (data == nullptr) { - return; - } - v8::Local nameValue = data->nameValue.Get(isolate); - v8::Local kindValue = data->kindValue.Get(isolate); - - v8::Local global = context->Global(); - v8::Local resolverValue; - if (!global - ->Get(context, facebook::jsi::v8direct::makeV8String( - isolate, "__nativeScriptResolveNativeApiLazyGlobal")) - .ToLocal(&resolverValue) || - !resolverValue->IsFunction()) { - return; - } - - v8::TryCatch tryCatch(isolate); - v8::Local args[] = {nameValue, kindValue}; - v8::Local result; - if (!resolverValue.As()->Call(context, global, 2, args).ToLocal(&result)) { - if (tryCatch.HasCaught()) { - isolate->ThrowException(tryCatch.Exception()); - } - return; - } - if (global->Delete(context, nameValue).FromMaybe(false)) { - global->DefineOwnProperty(context, nameValue, result, v8::DontEnum).FromMaybe(false); - } - info.GetReturnValue().Set(result); -} - -bool InstallNativeApiEngineLazyGlobal(Runtime& runtime, std::shared_ptr, - const std::string& name, const std::string& kind, - bool force) { - if (name.empty() || kind.empty()) { - return false; - } - - v8::Isolate* isolate = runtime.isolate(); - v8::EscapableHandleScope handleScope(isolate); - v8::Local context = runtime.context(); - v8::Local global = context->Global(); - v8::Local property = facebook::jsi::v8direct::makeV8String(isolate, name); - if (!force && global->HasOwnProperty(context, property).FromMaybe(false)) { - return false; - } - - auto data = std::make_shared(isolate, name, kind); - v8::Local external = v8::External::New(isolate, data.get()); - - bool installed = global - ->SetNativeDataProperty(context, property, NativeApiV8LazyGlobalGetter, - nullptr, external, v8::DontEnum) - .FromMaybe(false); - if (installed) { - runtime.state()->retainedNativeData.push_back(std::move(data)); - } - return installed; -} - -// clang-format off -#include "jsi/NativeApiJsiHostObjects.h" -#include "jsi/NativeApiJsiCallbacks.h" -#include "jsi/NativeApiJsiConversion.h" -#include "jsi/NativeApiJsiInvocation.h" -#include "jsi/NativeApiJsiClassBuilder.h" -#include "jsi/NativeApiJsiHostObject.h" -// clang-format on - -} // namespace - -#include "jsi/NativeApiJsiInstall.h" - -void InstallNativeApiV8(v8::Isolate* isolate, v8::Local context, - const NativeApiV8Config& config) { - if (isolate == nullptr || context.IsEmpty()) { - return; - } - v8::Locker locker(isolate); - v8::Isolate::Scope isolateScope(isolate); - v8::HandleScope handleScope(isolate); - v8::Context::Scope contextScope(context); - Runtime runtime(isolate, context); - InstallNativeApiJSI(runtime, config); -} - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiV8(v8::Isolate* isolate, v8::Local context, - const char* metadataPath) { - nativescript::NativeApiV8Config config; - config.metadataPath = metadataPath; - nativescript::InstallNativeApiV8(isolate, context, config); -} - -#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/v8/NativeApiV8HostObjects.mm b/NativeScript/ffi/v8/NativeApiV8HostObjects.mm deleted file mode 100644 index 8a11b15c4..000000000 --- a/NativeScript/ffi/v8/NativeApiV8HostObjects.mm +++ /dev/null @@ -1,187 +0,0 @@ -#include "NativeApiV8Runtime.h" - -#ifdef TARGET_ENGINE_V8 - -namespace facebook { -namespace jsi { - -namespace v8direct { - -Value valueFromLocal(Runtime& runtime, v8::Local value) { return Value(runtime, value); } - -v8::Local hostObjectTemplate(Runtime& runtime) { - auto state = runtime.state(); - if (state->hostObjectTemplate.IsEmpty()) { - v8::Local objectTemplate = v8::ObjectTemplate::New(runtime.isolate()); - objectTemplate->SetInternalFieldCount(1); - objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration( - [](v8::Local property, - const v8::PropertyCallbackInfo& info) -> v8::Intercepted { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return v8::Intercepted::kNo; - } - Runtime runtime(holder->state); - try { - Value result = holder->hostObject->get( - runtime, PropNameID(propertyNameToUtf8(info.GetIsolate(), property))); - if (!result.isUndefined()) { - info.GetReturnValue().Set(result.local(runtime)); - return v8::Intercepted::kYes; - } - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - return v8::Intercepted::kYes; - } - return v8::Intercepted::kNo; - }, - [](v8::Local property, v8::Local value, - const v8::PropertyCallbackInfo& info) -> v8::Intercepted { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return v8::Intercepted::kNo; - } - Runtime runtime(holder->state); - try { - holder->hostObject->set(runtime, - PropNameID(propertyNameToUtf8(info.GetIsolate(), property)), - Value(runtime, value)); - return v8::Intercepted::kYes; - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - return v8::Intercepted::kYes; - } - }, - nullptr, nullptr, - [](const v8::PropertyCallbackInfo& info) { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return; - } - Runtime runtime(holder->state); - try { - auto propertyNames = holder->hostObject->getPropertyNames(runtime); - v8::Local result = - v8::Array::New(info.GetIsolate(), static_cast(propertyNames.size())); - for (size_t i = 0; i < propertyNames.size(); i++) { - std::string name = propertyNames[i].utf8(runtime); - result - ->Set(runtime.context(), static_cast(i), - makeV8String(info.GetIsolate(), name)) - .FromMaybe(false); - } - info.GetReturnValue().Set(result); - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - } - }, - v8::Local(), v8::PropertyHandlerFlags::kNone)); - objectTemplate->SetHandler(v8::IndexedPropertyHandlerConfiguration( - [](uint32_t index, const v8::PropertyCallbackInfo& info) -> v8::Intercepted { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return v8::Intercepted::kNo; - } - Runtime runtime(holder->state); - try { - Value result = holder->hostObject->get(runtime, PropNameID(std::to_string(index))); - if (!result.isUndefined()) { - info.GetReturnValue().Set(result.local(runtime)); - return v8::Intercepted::kYes; - } - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - return v8::Intercepted::kYes; - } - return v8::Intercepted::kNo; - }, - [](uint32_t index, v8::Local value, - const v8::PropertyCallbackInfo& info) -> v8::Intercepted { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return v8::Intercepted::kNo; - } - Runtime runtime(holder->state); - try { - holder->hostObject->set(runtime, PropNameID(std::to_string(index)), - Value(runtime, value)); - return v8::Intercepted::kYes; - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - return v8::Intercepted::kYes; - } - }, - nullptr, nullptr, nullptr, v8::Local(), v8::PropertyHandlerFlags::kNone)); - state->hostObjectTemplate.Reset(runtime.isolate(), objectTemplate); - } - return state->hostObjectTemplate.Get(runtime.isolate()); -} - -void hostObjectWeakCallback(const v8::WeakCallbackInfo& info) { - delete info.GetParameter(); -} - -void functionWeakCallback(const v8::WeakCallbackInfo& info) { - delete info.GetParameter(); -} - -} // namespace v8direct - -Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken) { - v8::Local object = - v8direct::hostObjectTemplate(runtime)->NewInstance(runtime.context()).ToLocalChecked(); - auto* holder = new v8direct::HostObjectHolder(runtime.state(), std::move(host), typeToken); - object->SetAlignedPointerInInternalField(0, holder); - holder->object.Reset(runtime.isolate(), object); - holder->object.SetWeak(holder, v8direct::hostObjectWeakCallback, - v8::WeakCallbackType::kParameter); - return Object::fromValueStorage(Value(runtime, object).storage_); -} - -Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback) { - auto* holder = new v8direct::FunctionHolder(runtime.state(), std::move(callback)); - v8::Local data = v8::External::New(runtime.isolate(), holder); - v8::Local functionTemplate = v8::FunctionTemplate::New( - runtime.isolate(), - [](const v8::FunctionCallbackInfo& info) { - auto* holder = - static_cast(info.Data().As()->Value()); - Runtime runtime(holder->state); - std::vector args; - args.reserve(info.Length()); - for (int i = 0; i < info.Length(); i++) { - args.push_back(Value(runtime, info[i])); - } - try { - Value thisValue(runtime, info.This()); - Value result = holder->callback(runtime, thisValue, args.empty() ? nullptr : args.data(), - args.size()); - info.GetReturnValue().Set(result.local(runtime)); - } catch (const std::exception& exception) { - v8direct::throwV8Exception(info.GetIsolate(), exception); - } - }, - data); - v8::Local function = - functionTemplate->GetFunction(runtime.context()).ToLocalChecked(); - std::string functionName = name.utf8(runtime); - if (!functionName.empty()) { - function->SetName(v8direct::makeV8String(runtime.isolate(), functionName)); - } - holder->function.Reset(runtime.isolate(), function); - holder->function.SetWeak(holder, v8direct::functionWeakCallback, - v8::WeakCallbackType::kParameter); - return Function(Object::fromValueStorage(Value(runtime, function).storage_)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/v8/NativeApiV8Runtime.h b/NativeScript/ffi/v8/NativeApiV8Runtime.h deleted file mode 100644 index 81c95a085..000000000 --- a/NativeScript/ffi/v8/NativeApiV8Runtime.h +++ /dev/null @@ -1,736 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H -#define NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H - -#ifdef TARGET_ENGINE_V8 - -#import -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Metadata.h" -#include "MetadataReader.h" -#include "ffi.h" -#include "v8.h" - -@protocol NativeApiJsiClassBuilderProtocol -@end - -#ifdef EMBED_METADATA_SIZE -extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; -#endif - -namespace facebook { -namespace jsi { - -class Runtime; -class Value; -class Object; -class Function; -class Array; -class String; -class BigInt; -class ArrayBuffer; - -class JSError : public std::runtime_error { - public: - JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} - explicit JSError(const std::string& message) : std::runtime_error(message) {} -}; - -class StringBuffer { - public: - explicit StringBuffer(std::string value) : value_(std::move(value)) {} - const char* data() const { return value_.data(); } - size_t size() const { return value_.size(); } - - private: - std::string value_; -}; - -class MutableBuffer { - public: - virtual ~MutableBuffer() = default; - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -class PropNameID { - public: - PropNameID() = default; - explicit PropNameID(std::string value) : value_(std::move(value)) {} - - static PropNameID forAscii(Runtime&, const char* value) { - return PropNameID(value != nullptr ? value : ""); - } - - static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } - - std::string utf8(Runtime&) const { return value_; } - - private: - std::string value_; -}; - -class HostObject { - public: - virtual ~HostObject() = default; - virtual Value get(Runtime& runtime, const PropNameID& name); - virtual void set(Runtime& runtime, const PropNameID& name, const Value& value); - virtual std::vector getPropertyNames(Runtime& runtime); -}; - -using HostFunctionType = std::function; - -namespace v8direct { - -struct RuntimeState { - explicit RuntimeState(v8::Isolate* isolate, v8::Local context) : isolate(isolate) { - this->context.Reset(isolate, context); - } - - ~RuntimeState() { context.Reset(); } - - v8::Local localContext() const { return context.Get(isolate); } - - v8::Isolate* isolate = nullptr; - v8::Global context; - v8::Global hostObjectTemplate; - std::vector> retainedNativeData; -}; - -struct ValueStorage { - enum class Kind { - Undefined, - Null, - Bool, - Number, - V8, - }; - - explicit ValueStorage(Kind kind) : kind(kind) {} - - ~ValueStorage() { value.Reset(); } - - Kind kind = Kind::Undefined; - bool boolValue = false; - double numberValue = 0; - v8::Global value; -}; - -template -const void* hostObjectTypeToken() { - static int token = 0; - return &token; -} - -struct HostObjectHolder { - HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, - const void* typeToken) - : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} - - ~HostObjectHolder() { object.Reset(); } - - std::shared_ptr state; - std::shared_ptr hostObject; - const void* typeToken = nullptr; - v8::Global object; -}; - -struct FunctionHolder { - FunctionHolder(std::shared_ptr state, HostFunctionType callback) - : state(std::move(state)), callback(std::move(callback)) {} - - ~FunctionHolder() { function.Reset(); } - - std::shared_ptr state; - HostFunctionType callback; - v8::Global function; -}; - -struct ArrayBufferHolder { - explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} - - std::shared_ptr buffer; - v8::Global object; -}; - -inline v8::Local makeV8String(v8::Isolate* isolate, const std::string& value) { - return v8::String::NewFromUtf8(isolate, value.c_str(), v8::NewStringType::kNormal, - static_cast(value.size())) - .ToLocalChecked(); -} - -inline std::string toUtf8(v8::Isolate* isolate, v8::Local value) { - if (value.IsEmpty()) { - return {}; - } - v8::String::Utf8Value utf8(isolate, value); - return *utf8 != nullptr ? std::string(*utf8, utf8.length()) : std::string(); -} - -inline std::string propertyNameToUtf8(v8::Isolate* isolate, v8::Local property) { - if (property->IsSymbol() && - property.As()->StrictEquals(v8::Symbol::GetIterator(isolate))) { - return "Symbol.iterator"; - } - return toUtf8(isolate, property); -} - -inline std::string currentExceptionMessage(v8::Isolate* isolate, v8::TryCatch& tryCatch) { - if (tryCatch.HasCaught()) { - return toUtf8(isolate, tryCatch.Exception()); - } - return "NativeScript direct V8 operation failed."; -} - -inline void throwV8Exception(v8::Isolate* isolate, const std::exception& exception) { - isolate->ThrowException(v8::Exception::Error(makeV8String(isolate, exception.what()))); -} - -} // namespace v8direct - -class Runtime { - public: - Runtime(v8::Isolate* isolate, v8::Local context) - : state_(std::make_shared(isolate, context)) {} - - explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} - - v8::Isolate* isolate() const { return state_->isolate; } - v8::Local context() const { return state_->localContext(); } - std::shared_ptr state() const { return state_; } - - Object global(); - - Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); - - void drainMicrotasks() { isolate()->PerformMicrotaskCheckpoint(); } - - private: - std::shared_ptr state_; -}; - -class String { - public: - String() = default; - String(Runtime& runtime, v8::Local value); - - static String createFromUtf8(Runtime& runtime, const char* value) { - return String(runtime, - v8direct::makeV8String(runtime.isolate(), value != nullptr ? value : "")); - } - - static String createFromUtf8(Runtime& runtime, const std::string& value) { - return String(runtime, v8direct::makeV8String(runtime.isolate(), value)); - } - - static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { - return String(runtime, v8::String::NewFromUtf8( - runtime.isolate(), - reinterpret_cast( - value != nullptr ? value : reinterpret_cast("")), - v8::NewStringType::kNormal, static_cast(length)) - .ToLocalChecked()); - } - - std::string utf8(Runtime& runtime) const { - return v8direct::toUtf8(runtime.isolate(), local(runtime)); - } - - v8::Local local(Runtime& runtime) const { - return storage_->value.Get(runtime.isolate()).As(); - } - - operator Value() const; - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class Value { - public: - Value() - : storage_( - std::make_shared(v8direct::ValueStorage::Kind::Undefined)) {} - - Value(bool value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::Bool)) { - storage_->boolValue = value; - } - - Value(double value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::Number)) { - storage_->numberValue = value; - } - - Value(int value) : Value(static_cast(value)) {} - Value(uint32_t value) : Value(static_cast(value)) {} - - Value(Runtime& runtime, const Value& value) : storage_(value.storage_) {} - Value(Runtime& runtime, Value&& value) : storage_(std::move(value.storage_)) {} - Value(Runtime& runtime, const String& value) : storage_(value.storage_) {} - Value(Runtime& runtime, const Object& object); - Value(Runtime& runtime, const Function& function); - Value(Runtime& runtime, const Array& array); - Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); - Value(Runtime& runtime, const BigInt& bigint); - - static Value undefined() { return Value(); } - - static Value null() { - Value value; - value.storage_ = std::make_shared(v8direct::ValueStorage::Kind::Null); - return value; - } - - bool isUndefined() const; - bool isNull() const; - bool isBool() const; - bool getBool() const; - bool isNumber() const; - double getNumber() const; - - bool isObject() const; - bool isString() const; - bool isBigInt() const; - bool isSymbol() const; - - Object asObject(Runtime& runtime) const; - String asString(Runtime& runtime) const; - BigInt getBigInt(Runtime& runtime) const; - - v8::Local local(Runtime& runtime) const { - v8::Isolate* isolate = runtime.isolate(); - switch (storage_->kind) { - case v8direct::ValueStorage::Kind::Undefined: - return v8::Undefined(isolate); - case v8direct::ValueStorage::Kind::Null: - return v8::Null(isolate); - case v8direct::ValueStorage::Kind::Bool: - return v8::Boolean::New(isolate, storage_->boolValue); - case v8direct::ValueStorage::Kind::Number: - return v8::Number::New(isolate, storage_->numberValue); - case v8direct::ValueStorage::Kind::V8: - return storage_->value.Get(isolate); - } - } - - Value(Runtime& runtime, v8::Local value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), value); - } - - private: - friend class Runtime; - friend class Object; - friend class String; - friend class BigInt; - friend class ArrayBuffer; - friend class Function; - friend class Array; - - std::shared_ptr storage_; -}; - -class Object { - public: - Object() = default; - explicit Object(Runtime& runtime) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), v8::Object::New(runtime.isolate())); - } - - static Object fromValueStorage(std::shared_ptr storage) { - Object object; - object.storage_ = std::move(storage); - return object; - } - - template - static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - v8direct::hostObjectTypeToken()); - } - - Value getProperty(Runtime& runtime, const char* name) const { - return getProperty(runtime, - v8direct::makeV8String(runtime.isolate(), name != nullptr ? name : "")); - } - - Value getProperty(Runtime& runtime, const std::string& name) const { - return getProperty(runtime, name.c_str()); - } - - Value getProperty(Runtime& runtime, const Value& key) const { - return getProperty(runtime, key.local(runtime)); - } - - Value getProperty(Runtime& runtime, v8::Local key) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - if (!local(runtime)->Get(runtime.context(), key).ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Object getPropertyAsObject(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime); - } - - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - void setProperty(Runtime& runtime, const char* name, const Value& value) { - setProperty(runtime, v8direct::makeV8String(runtime.isolate(), name != nullptr ? name : ""), - value); - } - - void setProperty(Runtime& runtime, const char* name, const String& value) { - setProperty(runtime, name, Value(runtime, value)); - } - - void setProperty(Runtime& runtime, const char* name, const Object& value) { - setProperty(runtime, name, Value(runtime, value)); - } - - void setProperty(Runtime& runtime, const char* name, const Function& value); - void setProperty(Runtime& runtime, const char* name, const Array& value); - void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); - void setProperty(Runtime& runtime, const char* name, bool value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const char* name, double value) { - setProperty(runtime, name, Value(value)); - } - - void setProperty(Runtime& runtime, const std::string& name, const Value& value) { - setProperty(runtime, name.c_str(), value); - } - - void setProperty(Runtime& runtime, const Value& key, const Value& value) { - setProperty(runtime, key.local(runtime), value); - } - - void setProperty(Runtime& runtime, v8::Local key, const Value& value) { - v8::TryCatch tryCatch(runtime.isolate()); - if (!local(runtime)->Set(runtime.context(), key, value.local(runtime)).FromMaybe(false)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - } - - bool hasProperty(Runtime& runtime, const char* name) const { - v8::TryCatch tryCatch(runtime.isolate()); - return local(runtime) - ->Has(runtime.context(), - v8direct::makeV8String(runtime.isolate(), name != nullptr ? name : "")) - .FromMaybe(false); - } - - bool isFunction(Runtime& runtime) const { return local(runtime)->IsFunction(); } - bool isArray(Runtime& runtime) const { return local(runtime)->IsArray(); } - bool isArrayBuffer(Runtime& runtime) const { return local(runtime)->IsArrayBuffer(); } - - Function asFunction(Runtime& runtime) const; - Array getArray(Runtime& runtime) const; - ArrayBuffer getArrayBuffer(Runtime& runtime) const; - Array getPropertyNames(Runtime& runtime) const; - - template - bool isHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - return holder != nullptr && holder->typeToken == v8direct::hostObjectTypeToken(); - } - - template - std::shared_ptr getHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - if (holder == nullptr || holder->typeToken != v8direct::hostObjectTypeToken()) { - return nullptr; - } - return std::static_pointer_cast(holder->hostObject); - } - - v8::Local local(Runtime& runtime) const { - return storage_->value.Get(runtime.isolate()).As(); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - protected: - friend class Value; - friend class Runtime; - friend class Function; - friend class Array; - friend class ArrayBuffer; - - explicit Object(std::shared_ptr storage) : storage_(std::move(storage)) {} - - static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken); - - v8direct::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { - v8::Local object = local(runtime); - if (object->InternalFieldCount() < 1) { - return nullptr; - } - return static_cast(object->GetAlignedPointerFromInternalField(0)); - } - - std::shared_ptr storage_; -}; - -class Function : public Object { - public: - Function() = default; - explicit Function(Object object) : Object(std::move(object.storage_)) {} - - static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback); - - Value call(Runtime& runtime, const Value* args, size_t count) const { - v8::TryCatch tryCatch(runtime.isolate()); - std::vector> argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - v8::Local result; - if (!local(runtime) - .As() - ->Call(runtime.context(), runtime.context()->Global(), static_cast(argv.size()), - argv.data()) - .ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Value call(Runtime& runtime) const { - return call(runtime, static_cast(nullptr), 0); - } - - Value call(Runtime& runtime, std::nullptr_t, size_t) const { - return call(runtime, static_cast(nullptr), 0); - } - - template - Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { - return call(runtime, static_cast(args), count); - } - - template - Value call(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return call(runtime, static_cast(argv), sizeof...(Args)); - } - - Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, - size_t count = 0) const { - v8::TryCatch tryCatch(runtime.isolate()); - std::vector> argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - v8::Local result; - if (!local(runtime) - .As() - ->Call(runtime.context(), thisObject.local(runtime), static_cast(argv.size()), - argv.data()) - .ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { - v8::TryCatch tryCatch(runtime.isolate()); - std::vector> argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - v8::Local result; - if (!local(runtime) - .As() - ->NewInstance(runtime.context(), static_cast(argv.size()), argv.data()) - .ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { - return callAsConstructor(runtime, static_cast(nullptr), 0); - } - - template - Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { - return callAsConstructor(runtime, static_cast(args), count); - } - - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } -}; - -class Array : public Object { - public: - explicit Array(Runtime& runtime, size_t size) - : Object(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), - v8::Array::New(runtime.isolate(), static_cast(size))); - } - - explicit Array(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { return local(runtime).As()->Length(); } - - Value getValueAtIndex(Runtime& runtime, size_t index) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - if (!local(runtime)->Get(runtime.context(), static_cast(index)).ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { - v8::TryCatch tryCatch(runtime.isolate()); - if (!local(runtime) - ->Set(runtime.context(), static_cast(index), value.local(runtime)) - .FromMaybe(false)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - } - - void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { - setValueAtIndex(runtime, index, Value(runtime, value)); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } -}; - -class BigInt { - public: - BigInt() = default; - BigInt(Runtime& runtime, v8::Local value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), value); - } - - static BigInt fromInt64(Runtime& runtime, int64_t value) { - return BigInt(runtime, v8::BigInt::New(runtime.isolate(), value)); - } - - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - return BigInt(runtime, v8::BigInt::NewFromUnsigned(runtime.isolate(), value)); - } - - String toString(Runtime& runtime, int radix) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - (void)radix; - if (!local(runtime)->ToString(runtime.context()).ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return String(runtime, result); - } - - v8::Local local(Runtime& runtime) const { - return storage_->value.Get(runtime.isolate()).As(); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class ArrayBuffer : public Object { - public: - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : Object(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - auto holder = new v8direct::ArrayBufferHolder(std::move(buffer)); - auto backingStore = v8::ArrayBuffer::NewBackingStore( - holder->buffer->data(), holder->buffer->size(), - [](void*, size_t, void* deleterData) { - auto* holder = static_cast(deleterData); - holder->object.Reset(); - delete holder; - }, - holder); - v8::Local arrayBuffer = - v8::ArrayBuffer::New(runtime.isolate(), std::move(backingStore)); - storage_->value.Reset(runtime.isolate(), arrayBuffer); - holder->object.Reset(runtime.isolate(), arrayBuffer); - } - - explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { return local(runtime).As()->ByteLength(); } - - uint8_t* data(Runtime& runtime) const { - auto backingStore = local(runtime).As()->GetBackingStore(); - return static_cast(backingStore->Data()); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } -}; -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_V8 - -#endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H diff --git a/NativeScript/ffi/v8/NativeApiV8Value.mm b/NativeScript/ffi/v8/NativeApiV8Value.mm deleted file mode 100644 index d8b34428d..000000000 --- a/NativeScript/ffi/v8/NativeApiV8Value.mm +++ /dev/null @@ -1,177 +0,0 @@ -#include "NativeApiV8Runtime.h" - -#ifdef TARGET_ENGINE_V8 - -namespace facebook { -namespace jsi { - -Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } - -void HostObject::set(Runtime&, const PropNameID&, const Value&) {} - -std::vector HostObject::getPropertyNames(Runtime&) { return {}; } - -String::String(Runtime& runtime, v8::Local value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), value); -} - -String::operator Value() const { - Value value; - value.storage_ = storage_; - return value; -} - -Value::Value(Runtime&, const Object& object) : storage_(object.storage_) {} -Value::Value(Runtime&, const Function& function) : storage_(function.storage_) {} -Value::Value(Runtime&, const Array& array) : storage_(array.storage_) {} -Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) : storage_(arrayBuffer.storage_) {} -Value::Value(Runtime&, const BigInt& bigint) : storage_(bigint.storage_) {} - -bool Value::isObject() const { - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsObject(); -} - -bool Value::isUndefined() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Undefined) { - return true; - } - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsUndefined(); -} - -bool Value::isNull() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Null) { - return true; - } - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsNull(); -} - -bool Value::isBool() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Bool) { - return true; - } - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsBoolean(); -} - -bool Value::getBool() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Bool) { - return storage_->boolValue; - } - if (storage_->kind == v8direct::ValueStorage::Kind::V8 && !storage_->value.IsEmpty()) { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - if (isolate != nullptr) { - return storage_->value.Get(isolate)->BooleanValue(isolate); - } - } - return false; -} - -bool Value::isNumber() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Number) { - return true; - } - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsNumber(); -} - -double Value::getNumber() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Number) { - return storage_->numberValue; - } - if (storage_->kind == v8direct::ValueStorage::Kind::V8 && !storage_->value.IsEmpty()) { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - if (isolate != nullptr) { - return storage_->value.Get(isolate)->NumberValue(isolate->GetCurrentContext()).FromMaybe(0); - } - } - return 0; -} - -bool Value::isString() const { - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return storage_->value.Get(isolate)->IsString(); -} - -bool Value::isBigInt() const { - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return storage_->value.Get(isolate)->IsBigInt(); -} - -bool Value::isSymbol() const { - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return storage_->value.Get(isolate)->IsSymbol(); -} - -Object Value::asObject(Runtime& runtime) const { return Object::fromValueStorage(storage_); } - -String Value::asString(Runtime& runtime) const { - return String(runtime, local(runtime).As()); -} - -BigInt Value::getBigInt(Runtime& runtime) const { - return BigInt(runtime, local(runtime).As()); -} - -Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime).asFunction(runtime); -} - -Function Object::asFunction(Runtime& runtime) const { return Function(*this); } - -Array Object::getArray(Runtime& runtime) const { return Array(*this); } - -ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const { return ArrayBuffer(*this); } - -Array Object::getPropertyNames(Runtime& runtime) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - if (!local(runtime)->GetPropertyNames(runtime.context()).ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Array(Object::fromValueStorage(Value(runtime, result).storage_)); -} - -void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/APICallbackFunction.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/APICallbackFunction.h new file mode 100644 index 000000000..e5283b5b4 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/APICallbackFunction.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013, 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef APICallbackFunction_h +#define APICallbackFunction_h + +#include "APICast.h" +#include "Error.h" +#include "JSCallbackConstructor.h" +#include "JSLock.h" +#include + +namespace JSC { + +struct APICallbackFunction { + +template static EncodedJSValue JSC_HOST_CALL call(ExecState*); +template static EncodedJSValue JSC_HOST_CALL construct(ExecState*); + +}; + +template +EncodedJSValue JSC_HOST_CALL APICallbackFunction::call(ExecState* exec) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSContextRef execRef = toRef(exec); + JSObjectRef functionRef = toRef(exec->jsCallee()); + JSObjectRef thisObjRef = toRef(jsCast(exec->thisValue().toThis(exec, NotStrictMode))); + + int argumentCount = static_cast(exec->argumentCount()); + Vector arguments; + arguments.reserveInitialCapacity(argumentCount); + for (int i = 0; i < argumentCount; i++) + arguments.uncheckedAppend(toRef(exec, exec->uncheckedArgument(i))); + + JSValueRef exception = 0; + JSValueRef result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = jsCast(toJS(functionRef))->functionCallback()(execRef, functionRef, thisObjRef, argumentCount, arguments.data(), &exception); + } + if (exception) + throwException(exec, scope, toJS(exec, exception)); + + // result must be a valid JSValue. + if (!result) + return JSValue::encode(jsUndefined()); + + return JSValue::encode(toJS(exec, result)); +} + +template +EncodedJSValue JSC_HOST_CALL APICallbackFunction::construct(ExecState* exec) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSObject* constructor = exec->jsCallee(); + JSContextRef ctx = toRef(exec); + JSObjectRef constructorRef = toRef(constructor); + + JSObjectCallAsConstructorCallback callback = jsCast(constructor)->constructCallback(); + if (callback) { + size_t argumentCount = exec->argumentCount(); + Vector arguments; + arguments.reserveInitialCapacity(argumentCount); + for (size_t i = 0; i < argumentCount; ++i) + arguments.uncheckedAppend(toRef(exec, exec->uncheckedArgument(i))); + + JSValueRef exception = 0; + JSObjectRef result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = callback(ctx, constructorRef, argumentCount, arguments.data(), &exception); + } + if (exception) { + throwException(exec, scope, toJS(exec, exception)); + return JSValue::encode(toJS(exec, exception)); + } + // result must be a valid JSValue. + if (!result) + return throwVMTypeError(exec, scope); + return JSValue::encode(toJS(result)); + } + + return JSValue::encode(toJS(JSObjectMake(ctx, jsCast(constructor)->classRef(), 0))); +} + +} // namespace JSC + +#endif // APICallbackFunction_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/APICast.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/APICast.h new file mode 100644 index 000000000..b2f3888b6 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/APICast.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2006-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef APICast_h +#define APICast_h + +#include "JSAPIValueWrapper.h" +#include "JSCJSValue.h" +#include "JSCJSValueInlines.h" +#include "JSGlobalObject.h" +#include "HeapCellInlines.h" + +namespace JSC { + class ExecState; + class PropertyNameArray; + class VM; + class JSObject; + class JSValue; +} + +typedef const struct OpaqueJSContextGroup* JSContextGroupRef; +typedef const struct OpaqueJSContext* JSContextRef; +typedef struct OpaqueJSContext* JSGlobalContextRef; +typedef struct OpaqueJSPropertyNameAccumulator* JSPropertyNameAccumulatorRef; +typedef const struct OpaqueJSValue* JSValueRef; +typedef struct OpaqueJSValue* JSObjectRef; + +/* Opaque typing convenience methods */ + +inline JSC::ExecState* toJS(JSContextRef c) +{ + ASSERT(c); + return reinterpret_cast(const_cast(c)); +} + +inline JSC::ExecState* toJS(JSGlobalContextRef c) +{ + ASSERT(c); + return reinterpret_cast(c); +} + +inline JSC::JSGlobalObject* toJSGlobalObject(JSGlobalContextRef context) +{ + return toJS(context)->lexicalGlobalObject(); +} + +inline JSC::JSValue toJS(JSC::ExecState* exec, JSValueRef v) +{ + ASSERT_UNUSED(exec, exec); +#if !CPU(ADDRESS64) + JSC::JSCell* jsCell = reinterpret_cast(const_cast(v)); + if (!jsCell) + return JSC::jsNull(); + JSC::JSValue result; + if (jsCell->isAPIValueWrapper()) + result = JSC::jsCast(jsCell)->value(); + else + result = jsCell; +#else + JSC::JSValue result = bitwise_cast(v); +#endif + if (!result) + return JSC::jsNull(); + if (result.isCell()) + RELEASE_ASSERT(result.asCell()->methodTable(exec->vm())); + return result; +} + +inline JSC::JSValue toJSForGC(JSC::ExecState* exec, JSValueRef v) +{ + ASSERT_UNUSED(exec, exec); +#if !CPU(ADDRESS64) + JSC::JSCell* jsCell = reinterpret_cast(const_cast(v)); + if (!jsCell) + return JSC::JSValue(); + JSC::JSValue result = jsCell; +#else + JSC::JSValue result = bitwise_cast(v); +#endif + if (result && result.isCell()) + RELEASE_ASSERT(result.asCell()->methodTable(exec->vm())); + return result; +} + +// Used in JSObjectGetPrivate as that may be called during finalization +inline JSC::JSObject* uncheckedToJS(JSObjectRef o) +{ + return reinterpret_cast(o); +} + +inline JSC::JSObject* toJS(JSObjectRef o) +{ + JSC::JSObject* object = uncheckedToJS(o); + if (object) + RELEASE_ASSERT(object->methodTable(object->vm())); + return object; +} + +inline JSC::PropertyNameArray* toJS(JSPropertyNameAccumulatorRef a) +{ + return reinterpret_cast(a); +} + +inline JSC::VM* toJS(JSContextGroupRef g) +{ + return reinterpret_cast(const_cast(g)); +} + +inline JSValueRef toRef(JSC::VM& vm, JSC::JSValue v) +{ + ASSERT(vm.currentThreadIsHoldingAPILock()); +#if !CPU(ADDRESS64) + if (!v) + return 0; + if (!v.isCell()) + return reinterpret_cast(JSC::JSAPIValueWrapper::create(vm, v)); + return reinterpret_cast(v.asCell()); +#else + UNUSED_PARAM(vm); + return bitwise_cast(v); +#endif +} + +inline JSValueRef toRef(JSC::ExecState* exec, JSC::JSValue v) +{ + return toRef(exec->vm(), v); +} + +inline JSObjectRef toRef(JSC::JSObject* o) +{ + return reinterpret_cast(o); +} + +inline JSObjectRef toRef(const JSC::JSObject* o) +{ + return reinterpret_cast(const_cast(o)); +} + +inline JSContextRef toRef(JSC::ExecState* e) +{ + return reinterpret_cast(e); +} + +inline JSGlobalContextRef toGlobalRef(JSC::ExecState* e) +{ + ASSERT(e == e->lexicalGlobalObject()->globalExec()); + return reinterpret_cast(e); +} + +inline JSPropertyNameAccumulatorRef toRef(JSC::PropertyNameArray* l) +{ + return reinterpret_cast(l); +} + +inline JSContextGroupRef toRef(JSC::VM* g) +{ + return reinterpret_cast(g); +} + +#endif // APICast_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/APIUtils.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/APIUtils.h new file mode 100644 index 000000000..7a5e8ba8b --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/APIUtils.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef APIUtils_h +#define APIUtils_h + +#include "CatchScope.h" +#include "Exception.h" +#include "JSCJSValue.h" +#include "JSGlobalObjectInspectorController.h" +#include "JSValueRef.h" + +enum class ExceptionStatus { + DidThrow, + DidNotThrow +}; + +inline ExceptionStatus handleExceptionIfNeeded(JSC::CatchScope& scope, JSC::ExecState* exec, JSValueRef* returnedExceptionRef) +{ + if (UNLIKELY(scope.exception())) { + JSC::Exception* exception = scope.exception(); + if (returnedExceptionRef) + *returnedExceptionRef = toRef(exec, exception->value()); + scope.clearException(); +#if ENABLE(REMOTE_INSPECTOR) + scope.vm().vmEntryGlobalObject(exec)->inspectorController().reportAPIException(exec, exception); +#endif + return ExceptionStatus::DidThrow; + } + return ExceptionStatus::DidNotThrow; +} + +inline void setException(JSC::ExecState* exec, JSValueRef* returnedExceptionRef, JSC::JSValue exception) +{ + if (returnedExceptionRef) + *returnedExceptionRef = toRef(exec, exception); +#if ENABLE(REMOTE_INSPECTOR) + JSC::VM& vm = exec->vm(); + vm.vmEntryGlobalObject(exec)->inspectorController().reportAPIException(exec, JSC::Exception::create(vm, exception)); +#endif +} + +#endif /* APIUtils_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSAPIGlobalObject.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSAPIGlobalObject.h new file mode 100644 index 000000000..339e5e27f --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSAPIGlobalObject.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "JSGlobalObject.h" + +OBJC_CLASS JSScript; + +namespace JSC { + +class JSAPIGlobalObject : public JSGlobalObject { +public: + using Base = JSGlobalObject; + + DECLARE_EXPORT_INFO; + static const GlobalObjectMethodTable s_globalObjectMethodTable; + + static JSAPIGlobalObject* create(VM& vm, Structure* structure) + { + auto* object = new (NotNull, allocateCell(vm.heap)) JSAPIGlobalObject(vm, structure); + object->finishCreation(vm); + return object; + } + + static Structure* createStructure(VM& vm, JSValue prototype) + { + auto* result = Structure::create(vm, 0, prototype, TypeInfo(GlobalObjectType, StructureFlags), info()); + result->setTransitionWatchpointIsLikelyToBeFired(true); + return result; + } + + static JSInternalPromise* moduleLoaderImportModule(JSGlobalObject*, ExecState*, JSModuleLoader*, JSString* moduleNameValue, JSValue parameters, const SourceOrigin&); + static Identifier moduleLoaderResolve(JSGlobalObject*, ExecState*, JSModuleLoader*, JSValue keyValue, JSValue referrerValue, JSValue); + static JSInternalPromise* moduleLoaderFetch(JSGlobalObject*, ExecState*, JSModuleLoader*, JSValue, JSValue, JSValue); + static JSObject* moduleLoaderCreateImportMetaProperties(JSGlobalObject*, ExecState*, JSModuleLoader*, JSValue, JSModuleRecord*, JSValue); + static JSValue moduleLoaderEvaluate(JSGlobalObject*, ExecState*, JSModuleLoader*, JSValue, JSValue, JSValue); + + JSValue loadAndEvaluateJSScriptModule(const JSLockHolder&, JSScript *); + +private: + JSAPIGlobalObject(VM& vm, Structure* structure) + : Base(vm, structure, &s_globalObjectMethodTable) + { } +}; + +} diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSAPIValueWrapper.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSAPIValueWrapper.h new file mode 100644 index 000000000..aa26082ff --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSAPIValueWrapper.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) + * Copyright (C) 2001 Peter Kelly (pmk@post.com) + * Copyright (C) 2003-2019 Apple Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include "CallFrame.h" +#include "JSCJSValue.h" +#include "JSCast.h" +#include "Structure.h" + +namespace JSC { + +class JSAPIValueWrapper final : public JSCell { + friend JSValue jsAPIValueWrapper(ExecState*, JSValue); +public: + typedef JSCell Base; + static const unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal; + + JSValue value() const { return m_value.get(); } + + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + return Structure::create(vm, globalObject, prototype, TypeInfo(APIValueWrapperType, OverridesGetPropertyNames), info()); + } + + DECLARE_EXPORT_INFO; + + static JSAPIValueWrapper* create(VM& vm, JSValue value) + { + JSAPIValueWrapper* wrapper = new (NotNull, allocateCell(vm.heap)) JSAPIValueWrapper(vm); + wrapper->finishCreation(vm, value); + return wrapper; + } + +protected: + void finishCreation(VM& vm, JSValue value) + { + Base::finishCreation(vm); + m_value.set(vm, this, value); + ASSERT(!value.isCell()); + } + +private: + JSAPIValueWrapper(VM& vm) + : JSCell(vm, vm.apiWrapperStructure.get()) + { + } + + WriteBarrier m_value; +}; + +} // namespace JSC diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSAPIWrapperObject.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSAPIWrapperObject.h new file mode 100644 index 000000000..dd874dc65 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSAPIWrapperObject.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSAPIWrapperObject_h +#define JSAPIWrapperObject_h + +#include "JSBase.h" +#include "JSDestructibleObject.h" + +#if JSC_OBJC_API_ENABLED || defined(JSC_GLIB_API_ENABLED) + +namespace JSC { + +class JSAPIWrapperObject : public JSDestructibleObject { +public: + typedef JSDestructibleObject Base; + + void finishCreation(VM&); + static void visitChildren(JSCell*, JSC::SlotVisitor&); + + void* wrappedObject() { return m_wrappedObject; } + void setWrappedObject(void*); + +protected: + JSAPIWrapperObject(VM&, Structure*); + +private: + void* m_wrappedObject { nullptr }; +}; + +} // namespace JSC + +#endif // JSC_OBJC_API_ENABLED || defined(JSC_GLIB_API_ENABLED) + +#endif // JSAPIWrapperObject_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSBase.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSBase.h new file mode 100644 index 000000000..01c1b286e --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSBase.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2006 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSBase_h +#define JSBase_h + +#ifndef __cplusplus +#include +#endif + +#ifdef __OBJC__ +#import +#endif + +/* JavaScript engine interface */ + +/*! @typedef JSContextGroupRef A group that associates JavaScript contexts with one another. Contexts in the same group may share and exchange JavaScript objects. */ +typedef const struct OpaqueJSContextGroup* JSContextGroupRef; + +/*! @typedef JSContextRef A JavaScript execution context. Holds the global object and other execution state. */ +typedef const struct OpaqueJSContext* JSContextRef; + +/*! @typedef JSGlobalContextRef A global JavaScript execution context. A JSGlobalContext is a JSContext. */ +typedef struct OpaqueJSContext* JSGlobalContextRef; + +/*! @typedef JSStringRef A UTF16 character buffer. The fundamental string representation in JavaScript. */ +typedef struct OpaqueJSString* JSStringRef; + +/*! @typedef JSClassRef A JavaScript class. Used with JSObjectMake to construct objects with custom behavior. */ +typedef struct OpaqueJSClass* JSClassRef; + +/*! @typedef JSPropertyNameArrayRef An array of JavaScript property names. */ +typedef struct OpaqueJSPropertyNameArray* JSPropertyNameArrayRef; + +/*! @typedef JSPropertyNameAccumulatorRef An ordered set used to collect the names of a JavaScript object's properties. */ +typedef struct OpaqueJSPropertyNameAccumulator* JSPropertyNameAccumulatorRef; + +/*! @typedef JSTypedArrayBytesDeallocator A function used to deallocate bytes passed to a Typed Array constructor. The function should take two arguments. The first is a pointer to the bytes that were originally passed to the Typed Array constructor. The second is a pointer to additional information desired at the time the bytes are to be freed. */ +typedef void (*JSTypedArrayBytesDeallocator)(void* bytes, void* deallocatorContext); + +/* JavaScript data types */ + +/*! @typedef JSValueRef A JavaScript value. The base type for all JavaScript values, and polymorphic functions on them. */ +typedef const struct OpaqueJSValue* JSValueRef; + +/*! @typedef JSObjectRef A JavaScript object. A JSObject is a JSValue. */ +typedef struct OpaqueJSValue* JSObjectRef; + +/* Clang's __has_declspec_attribute emulation */ +/* https://clang.llvm.org/docs/LanguageExtensions.html#has-declspec-attribute */ + +#ifndef __has_declspec_attribute +#define __has_declspec_attribute(x) 0 +#endif + +/* JavaScript symbol exports */ +/* These rules should stay the same as in WebKit/Shared/API/c/WKDeclarationSpecifiers.h */ + +#undef JS_EXPORT +#if defined(JS_NO_EXPORT) +#define JS_EXPORT +#elif defined(WIN32) || defined(_WIN32) || defined(__CC_ARM) || defined(__ARMCC__) || (__has_declspec_attribute(dllimport) && __has_declspec_attribute(dllexport)) +#if defined(BUILDING_JavaScriptCore) || defined(STATICALLY_LINKED_WITH_JavaScriptCore) +#define JS_EXPORT __declspec(dllexport) +#else +#define JS_EXPORT __declspec(dllimport) +#endif +#elif defined(__GNUC__) +#define JS_EXPORT __attribute__((visibility("default"))) +#else /* !defined(JS_NO_EXPORT) */ +#define JS_EXPORT +#endif /* defined(JS_NO_EXPORT) */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Script Evaluation */ + +/*! +@function JSEvaluateScript +@abstract Evaluates a string of JavaScript. +@param ctx The execution context to use. +@param script A JSString containing the script to evaluate. +@param thisObject The object to use as "this," or NULL to use the global object as "this." +@param sourceURL A JSString containing a URL for the script's source file. This is used by debuggers and when reporting exceptions. Pass NULL if you do not care to include source file information. +@param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. The value is one-based, so the first line is line 1 and invalid values are clamped to 1. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result The JSValue that results from evaluating script, or NULL if an exception is thrown. +*/ +JS_EXPORT JSValueRef JSEvaluateScript(JSContextRef ctx, JSStringRef script, JSObjectRef thisObject, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception); + +/*! +@function JSCheckScriptSyntax +@abstract Checks for syntax errors in a string of JavaScript. +@param ctx The execution context to use. +@param script A JSString containing the script to check for syntax errors. +@param sourceURL A JSString containing a URL for the script's source file. This is only used when reporting exceptions. Pass NULL if you do not care to include source file information in exceptions. +@param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. The value is one-based, so the first line is line 1 and invalid values are clamped to 1. +@param exception A pointer to a JSValueRef in which to store a syntax error exception, if any. Pass NULL if you do not care to store a syntax error exception. +@result true if the script is syntactically correct, otherwise false. +*/ +JS_EXPORT bool JSCheckScriptSyntax(JSContextRef ctx, JSStringRef script, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception); + +/*! +@function JSGarbageCollect +@abstract Performs a JavaScript garbage collection. +@param ctx The execution context to use. +@discussion JavaScript values that are on the machine stack, in a register, + protected by JSValueProtect, set as the global object of an execution context, + or reachable from any such value will not be collected. + + During JavaScript execution, you are not required to call this function; the + JavaScript engine will garbage collect as needed. JavaScript values created + within a context group are automatically destroyed when the last reference + to the context group is released. +*/ +JS_EXPORT void JSGarbageCollect(JSContextRef ctx); + +#ifdef __cplusplus +} +#endif + +/* Enable the Objective-C API for platforms with a modern runtime. NOTE: This is duplicated in VM.h. */ +#if !defined(JSC_OBJC_API_ENABLED) +#if (defined(__clang__) && defined(__APPLE__) && ((defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && !defined(__i386__)) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE))) +#define JSC_OBJC_API_ENABLED 1 +#else +#define JSC_OBJC_API_ENABLED 0 +#endif +#endif + +#endif /* JSBase_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSBaseInternal.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSBaseInternal.h new file mode 100644 index 000000000..a274af96e --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSBaseInternal.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include + +namespace JSC { +class JSLockHolder; +class ExecState; +class SourceCode; +} + +extern "C" JSValueRef JSEvaluateScriptInternal(const JSC::JSLockHolder&, JSC::ExecState*, JSContextRef, JSObjectRef thisObject, const JSC::SourceCode&, JSValueRef* exception); diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSBasePrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSBasePrivate.h new file mode 100644 index 000000000..2fc916b70 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSBasePrivate.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSBasePrivate_h +#define JSBasePrivate_h + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +@function +@abstract Reports an object's non-GC memory payload to the garbage collector. +@param ctx The execution context to use. +@param size The payload's size, in bytes. +@discussion Use this function to notify the garbage collector that a GC object +owns a large non-GC memory region. Calling this function will encourage the +garbage collector to collect soon, hoping to reclaim that large non-GC memory +region. +*/ +JS_EXPORT void JSReportExtraMemoryCost(JSContextRef ctx, size_t size) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +JS_EXPORT void JSDisableGCTimer(void); + +#ifdef __cplusplus +} +#endif + +#endif /* JSBasePrivate_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCTestRunnerUtils.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCTestRunnerUtils.h new file mode 100644 index 000000000..c52da524b --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCTestRunnerUtils.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSCTestRunnerUtils_h +#define JSCTestRunnerUtils_h + +#include +#include + +namespace JSC { + +JS_EXPORT_PRIVATE JSValueRef failNextNewCodeBlock(JSContextRef); +JS_EXPORT_PRIVATE JSValueRef numberOfDFGCompiles(JSContextRef, JSValueRef theFunction); +JS_EXPORT_PRIVATE JSValueRef setNeverInline(JSContextRef, JSValueRef theFunction); +JS_EXPORT_PRIVATE JSValueRef setNeverOptimize(JSContextRef, JSValueRef theFunction); + +} // namespace JSC + +#endif // JSCTestRunnerUtils_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackConstructor.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackConstructor.h new file mode 100644 index 000000000..3c31e07f0 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackConstructor.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSCallbackConstructor_h +#define JSCallbackConstructor_h + +#include "JSDestructibleObject.h" +#include "JSObjectRef.h" + +namespace JSC { + +class JSCallbackConstructor final : public JSDestructibleObject { +public: + typedef JSDestructibleObject Base; + static const unsigned StructureFlags = Base::StructureFlags | ImplementsHasInstance | ImplementsDefaultHasInstance; + + static JSCallbackConstructor* create(ExecState* exec, JSGlobalObject* globalObject, Structure* structure, JSClassRef classRef, JSObjectCallAsConstructorCallback callback) + { + VM& vm = exec->vm(); + JSCallbackConstructor* constructor = new (NotNull, allocateCell(vm.heap)) JSCallbackConstructor(globalObject, structure, classRef, callback); + constructor->finishCreation(globalObject, classRef); + return constructor; + } + + ~JSCallbackConstructor(); + static void destroy(JSCell*); + JSClassRef classRef() const { return m_class; } + JSObjectCallAsConstructorCallback callback() const { return m_callback; } + DECLARE_INFO; + + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue proto) + { + return Structure::create(vm, globalObject, proto, TypeInfo(ObjectType, StructureFlags), info()); + } + +protected: + JSCallbackConstructor(JSGlobalObject*, Structure*, JSClassRef, JSObjectCallAsConstructorCallback); + void finishCreation(JSGlobalObject*, JSClassRef); + +private: + friend struct APICallbackFunction; + + static ConstructType getConstructData(JSCell*, ConstructData&); + + JSObjectCallAsConstructorCallback constructCallback() { return m_callback; } + + JSClassRef m_class; + JSObjectCallAsConstructorCallback m_callback; +}; + +} // namespace JSC + +#endif // JSCallbackConstructor_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackFunction.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackFunction.h new file mode 100644 index 000000000..f9e4f9635 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackFunction.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2006-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSCallbackFunction_h +#define JSCallbackFunction_h + +#include "InternalFunction.h" +#include "JSObjectRef.h" + +namespace JSC { + +class JSCallbackFunction final : public InternalFunction { + friend struct APICallbackFunction; +public: + typedef InternalFunction Base; + + template + static IsoSubspace* subspaceFor(VM& vm) + { + return vm.callbackFunctionSpace(); + } + + static JSCallbackFunction* create(VM&, JSGlobalObject*, JSObjectCallAsFunctionCallback, const String& name); + + DECLARE_INFO; + + // InternalFunction mish-mashes constructor and function behavior -- we should + // refactor the code so this override isn't necessary + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue proto) + { + return Structure::create(vm, globalObject, proto, TypeInfo(InternalFunctionType, StructureFlags), info()); + } + +private: + JSCallbackFunction(VM&, Structure*, JSObjectCallAsFunctionCallback); + void finishCreation(VM&, const String& name); + + JSObjectCallAsFunctionCallback functionCallback() { return m_callback; } + + JSObjectCallAsFunctionCallback m_callback { nullptr }; +}; + +} // namespace JSC + +#endif // JSCallbackFunction_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackObject.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackObject.h new file mode 100644 index 000000000..d0e625282 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackObject.h @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2006-2019 Apple Inc. All rights reserved. + * Copyright (C) 2007 Eric Seidel + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSCallbackObject_h +#define JSCallbackObject_h + +#include "JSObjectRef.h" +#include "JSValueRef.h" +#include "JSObject.h" + +namespace JSC { + +struct JSCallbackObjectData { + WTF_MAKE_FAST_ALLOCATED; +public: + JSCallbackObjectData(void* privateData, JSClassRef jsClass) + : privateData(privateData) + , jsClass(jsClass) + { + JSClassRetain(jsClass); + } + + ~JSCallbackObjectData() + { + JSClassRelease(jsClass); + } + + JSValue getPrivateProperty(const Identifier& propertyName) const + { + if (!m_privateProperties) + return JSValue(); + return m_privateProperties->getPrivateProperty(propertyName); + } + + void setPrivateProperty(VM& vm, JSCell* owner, const Identifier& propertyName, JSValue value) + { + if (!m_privateProperties) + m_privateProperties = makeUnique(); + m_privateProperties->setPrivateProperty(vm, owner, propertyName, value); + } + + void deletePrivateProperty(const Identifier& propertyName) + { + if (!m_privateProperties) + return; + m_privateProperties->deletePrivateProperty(propertyName); + } + + void visitChildren(SlotVisitor& visitor) + { + JSPrivatePropertyMap* properties = m_privateProperties.get(); + if (!properties) + return; + properties->visitChildren(visitor); + } + + void* privateData; + JSClassRef jsClass; + struct JSPrivatePropertyMap { + WTF_MAKE_FAST_ALLOCATED; + public: + JSValue getPrivateProperty(const Identifier& propertyName) const + { + PrivatePropertyMap::const_iterator location = m_propertyMap.find(propertyName.impl()); + if (location == m_propertyMap.end()) + return JSValue(); + return location->value.get(); + } + + void setPrivateProperty(VM& vm, JSCell* owner, const Identifier& propertyName, JSValue value) + { + LockHolder locker(m_lock); + WriteBarrier empty; + m_propertyMap.add(propertyName.impl(), empty).iterator->value.set(vm, owner, value); + } + + void deletePrivateProperty(const Identifier& propertyName) + { + LockHolder locker(m_lock); + m_propertyMap.remove(propertyName.impl()); + } + + void visitChildren(SlotVisitor& visitor) + { + LockHolder locker(m_lock); + for (auto& pair : m_propertyMap) { + if (pair.value) + visitor.append(pair.value); + } + } + + private: + typedef HashMap, WriteBarrier, IdentifierRepHash> PrivatePropertyMap; + PrivatePropertyMap m_propertyMap; + Lock m_lock; + }; + std::unique_ptr m_privateProperties; +}; + + +template +class JSCallbackObject final : public Parent { +protected: + JSCallbackObject(ExecState*, Structure*, JSClassRef, void* data); + JSCallbackObject(VM&, JSClassRef, Structure*); + + void finishCreation(ExecState*); + void finishCreation(VM&); + +public: + typedef Parent Base; + static const unsigned StructureFlags = Base::StructureFlags | ProhibitsPropertyCaching | OverridesGetOwnPropertySlot | InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero | ImplementsHasInstance | OverridesGetPropertyNames | OverridesGetCallData; + static_assert(!(StructureFlags & ImplementsDefaultHasInstance), "using customHasInstance"); + + ~JSCallbackObject(); + + static JSCallbackObject* create(ExecState* exec, JSGlobalObject* globalObject, Structure* structure, JSClassRef classRef, void* data) + { + VM& vm = exec->vm(); + ASSERT_UNUSED(globalObject, !structure->globalObject() || structure->globalObject() == globalObject); + JSCallbackObject* callbackObject = new (NotNull, allocateCell(vm.heap)) JSCallbackObject(exec, structure, classRef, data); + callbackObject->finishCreation(exec); + return callbackObject; + } + static JSCallbackObject* create(VM&, JSClassRef, Structure*); + + static const bool needsDestruction; + static void destroy(JSCell* cell) + { + static_cast(cell)->JSCallbackObject::~JSCallbackObject(); + } + + void setPrivate(void* data); + void* getPrivate(); + + // FIXME: We should fix the warnings for extern-template in JSObject template classes: https://bugs.webkit.org/show_bug.cgi?id=161979 + IGNORE_CLANG_WARNINGS_BEGIN("undefined-var-template") + DECLARE_INFO; + IGNORE_CLANG_WARNINGS_END + + JSClassRef classRef() const { return m_callbackObjectData->jsClass; } + bool inherits(JSClassRef) const; + + static Structure* createStructure(VM&, JSGlobalObject*, JSValue); + + JSValue getPrivateProperty(const Identifier& propertyName) const + { + return m_callbackObjectData->getPrivateProperty(propertyName); + } + + void setPrivateProperty(VM& vm, const Identifier& propertyName, JSValue value) + { + m_callbackObjectData->setPrivateProperty(vm, this, propertyName, value); + } + + void deletePrivateProperty(const Identifier& propertyName) + { + m_callbackObjectData->deletePrivateProperty(propertyName); + } + + using Parent::methodTable; + +private: + static String className(const JSObject*, VM&); + static String toStringName(const JSObject*, ExecState*); + + static JSValue defaultValue(const JSObject*, ExecState*, PreferredPrimitiveType); + + static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&); + static bool getOwnPropertySlotByIndex(JSObject*, ExecState*, unsigned propertyName, PropertySlot&); + + static bool put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&); + static bool putByIndex(JSCell*, ExecState*, unsigned, JSValue, bool shouldThrow); + + static bool deleteProperty(JSCell*, ExecState*, PropertyName); + static bool deletePropertyByIndex(JSCell*, ExecState*, unsigned); + + static bool customHasInstance(JSObject*, ExecState*, JSValue); + + static void getOwnNonIndexPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode); + + static ConstructType getConstructData(JSCell*, ConstructData&); + static CallType getCallData(JSCell*, CallData&); + + static void visitChildren(JSCell* cell, SlotVisitor& visitor) + { + JSCallbackObject* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS((static_cast(thisObject)), JSCallbackObject::info()); + Parent::visitChildren(thisObject, visitor); + thisObject->m_callbackObjectData->visitChildren(visitor); + } + + void init(ExecState*); + + static JSCallbackObject* asCallbackObject(JSValue); + static JSCallbackObject* asCallbackObject(EncodedJSValue); + + static EncodedJSValue JSC_HOST_CALL call(ExecState*); + static EncodedJSValue JSC_HOST_CALL construct(ExecState*); + + JSValue getStaticValue(ExecState*, PropertyName); + static EncodedJSValue staticFunctionGetter(ExecState*, EncodedJSValue, PropertyName); + static EncodedJSValue callbackGetter(ExecState*, EncodedJSValue, PropertyName); + + std::unique_ptr m_callbackObjectData; + const ClassInfo* m_classInfo { nullptr }; +}; + +} // namespace JSC + +// include the actual template class implementation +#include "JSCallbackObjectFunctions.h" + +#endif // JSCallbackObject_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackObjectFunctions.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackObjectFunctions.h new file mode 100644 index 000000000..c72363395 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSCallbackObjectFunctions.h @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2006-2019 Apple Inc. All rights reserved. + * Copyright (C) 2007 Eric Seidel + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "APICast.h" +#include "Error.h" +#include "ExceptionHelpers.h" +#include "JSCallbackFunction.h" +#include "JSClassRef.h" +#include "JSFunction.h" +#include "JSGlobalObject.h" +#include "JSLock.h" +#include "JSObjectRef.h" +#include "JSString.h" +#include "OpaqueJSString.h" +#include "PropertyNameArray.h" +#include + +namespace JSC { + +template +inline JSCallbackObject* JSCallbackObject::asCallbackObject(JSValue value) +{ + ASSERT(asObject(value)->inherits(value.getObject()->vm(), info())); + return jsCast(asObject(value)); +} + +template +inline JSCallbackObject* JSCallbackObject::asCallbackObject(EncodedJSValue encodedValue) +{ + JSValue value = JSValue::decode(encodedValue); + ASSERT(asObject(value)->inherits(value.getObject()->vm(), info())); + return jsCast(asObject(value)); +} + +template +JSCallbackObject::JSCallbackObject(ExecState* exec, Structure* structure, JSClassRef jsClass, void* data) + : Parent(exec->vm(), structure) + , m_callbackObjectData(makeUnique(data, jsClass)) +{ +} + +// Global object constructor. +// FIXME: Move this into a separate JSGlobalCallbackObject class derived from this one. +template +JSCallbackObject::JSCallbackObject(VM& vm, JSClassRef jsClass, Structure* structure) + : Parent(vm, structure) + , m_callbackObjectData(makeUnique(nullptr, jsClass)) +{ +} + +template +JSCallbackObject::~JSCallbackObject() +{ + VM& vm = this->HeapCell::vm(); + vm.currentlyDestructingCallbackObject = this; + ASSERT(m_classInfo); + vm.currentlyDestructingCallbackObjectClassInfo = m_classInfo; + JSObjectRef thisRef = toRef(static_cast(this)); + for (JSClassRef jsClass = classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectFinalizeCallback finalize = jsClass->finalize) + finalize(thisRef); + } + vm.currentlyDestructingCallbackObject = nullptr; + vm.currentlyDestructingCallbackObjectClassInfo = nullptr; +} + +template +void JSCallbackObject::finishCreation(ExecState* exec) +{ + VM& vm = exec->vm(); + Base::finishCreation(vm); + ASSERT(Parent::inherits(vm, info())); + init(exec); +} + +// This is just for Global object, so we can assume that Base::finishCreation is JSGlobalObject::finishCreation. +template +void JSCallbackObject::finishCreation(VM& vm) +{ + ASSERT(Parent::inherits(vm, info())); + ASSERT(Parent::isGlobalObject()); + Base::finishCreation(vm); + init(jsCast(this)->globalExec()); +} + +template +void JSCallbackObject::init(ExecState* exec) +{ + ASSERT(exec); + + Vector initRoutines; + JSClassRef jsClass = classRef(); + do { + if (JSObjectInitializeCallback initialize = jsClass->initialize) + initRoutines.append(initialize); + } while ((jsClass = jsClass->parentClass)); + + // initialize from base to derived + for (int i = static_cast(initRoutines.size()) - 1; i >= 0; i--) { + JSLock::DropAllLocks dropAllLocks(exec); + JSObjectInitializeCallback initialize = initRoutines[i]; + initialize(toRef(exec), toRef(this)); + } + + m_classInfo = this->classInfo(); +} + +template +String JSCallbackObject::className(const JSObject* object, VM& vm) +{ + const JSCallbackObject* thisObject = jsCast(object); + String thisClassName = thisObject->classRef()->className(); + if (!thisClassName.isEmpty()) + return thisClassName; + + return Parent::className(object, vm); +} + +template +String JSCallbackObject::toStringName(const JSObject* object, ExecState* exec) +{ + VM& vm = exec->vm(); + const ClassInfo* info = object->classInfo(vm); + ASSERT(info); + return info->methodTable.className(object, vm); +} + +template +bool JSCallbackObject::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCallbackObject* thisObject = jsCast(object); + JSContextRef ctx = toRef(exec); + JSObjectRef thisRef = toRef(thisObject); + RefPtr propertyNameRef; + + if (StringImpl* name = propertyName.uid()) { + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + // optional optimization to bypass getProperty in cases when we only need to know if the property exists + if (JSObjectHasPropertyCallback hasProperty = jsClass->hasProperty) { + if (!propertyNameRef) + propertyNameRef = OpaqueJSString::tryCreate(name); + JSLock::DropAllLocks dropAllLocks(exec); + if (hasProperty(ctx, thisRef, propertyNameRef.get())) { + slot.setCustom(thisObject, PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum, callbackGetter); + return true; + } + } else if (JSObjectGetPropertyCallback getProperty = jsClass->getProperty) { + if (!propertyNameRef) + propertyNameRef = OpaqueJSString::tryCreate(name); + JSValueRef exception = 0; + JSValueRef value; + { + JSLock::DropAllLocks dropAllLocks(exec); + value = getProperty(ctx, thisRef, propertyNameRef.get(), &exception); + } + if (exception) { + throwException(exec, scope, toJS(exec, exception)); + slot.setValue(thisObject, PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum, jsUndefined()); + return true; + } + if (value) { + slot.setValue(thisObject, PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum, toJS(exec, value)); + return true; + } + } + + if (OpaqueJSClassStaticValuesTable* staticValues = jsClass->staticValues(exec)) { + if (staticValues->contains(name)) { + JSValue value = thisObject->getStaticValue(exec, propertyName); + if (value) { + slot.setValue(thisObject, PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum, value); + return true; + } + } + } + + if (OpaqueJSClassStaticFunctionsTable* staticFunctions = jsClass->staticFunctions(exec)) { + if (staticFunctions->contains(name)) { + slot.setCustom(thisObject, PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum, staticFunctionGetter); + return true; + } + } + } + } + + return Parent::getOwnPropertySlot(thisObject, exec, propertyName, slot); +} + +template +bool JSCallbackObject::getOwnPropertySlotByIndex(JSObject* object, ExecState* exec, unsigned propertyName, PropertySlot& slot) +{ + VM& vm = exec->vm(); + return object->methodTable(vm)->getOwnPropertySlot(object, exec, Identifier::from(vm, propertyName), slot); +} + +template +JSValue JSCallbackObject::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType hint) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + const JSCallbackObject* thisObject = jsCast(object); + JSContextRef ctx = toRef(exec); + JSObjectRef thisRef = toRef(thisObject); + ::JSType jsHint = hint == PreferString ? kJSTypeString : kJSTypeNumber; + + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectConvertToTypeCallback convertToType = jsClass->convertToType) { + JSValueRef exception = 0; + JSValueRef result = convertToType(ctx, thisRef, jsHint, &exception); + if (exception) { + throwException(exec, scope, toJS(exec, exception)); + return jsUndefined(); + } + if (result) + return toJS(exec, result); + } + } + + return Parent::defaultValue(object, exec, hint); +} + +template +bool JSCallbackObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCallbackObject* thisObject = jsCast(cell); + JSContextRef ctx = toRef(exec); + JSObjectRef thisRef = toRef(thisObject); + RefPtr propertyNameRef; + JSValueRef valueRef = toRef(exec, value); + + if (StringImpl* name = propertyName.uid()) { + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectSetPropertyCallback setProperty = jsClass->setProperty) { + if (!propertyNameRef) + propertyNameRef = OpaqueJSString::tryCreate(name); + JSValueRef exception = 0; + bool result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = setProperty(ctx, thisRef, propertyNameRef.get(), valueRef, &exception); + } + if (exception) + throwException(exec, scope, toJS(exec, exception)); + if (result || exception) + return result; + } + + if (OpaqueJSClassStaticValuesTable* staticValues = jsClass->staticValues(exec)) { + if (StaticValueEntry* entry = staticValues->get(name)) { + if (entry->attributes & kJSPropertyAttributeReadOnly) + return false; + if (JSObjectSetPropertyCallback setProperty = entry->setProperty) { + JSValueRef exception = 0; + bool result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = setProperty(ctx, thisRef, entry->propertyNameRef.get(), valueRef, &exception); + } + if (exception) + throwException(exec, scope, toJS(exec, exception)); + if (result || exception) + return result; + } + } + } + + if (OpaqueJSClassStaticFunctionsTable* staticFunctions = jsClass->staticFunctions(exec)) { + if (StaticFunctionEntry* entry = staticFunctions->get(name)) { + PropertySlot getSlot(thisObject, PropertySlot::InternalMethodType::VMInquiry); + if (Parent::getOwnPropertySlot(thisObject, exec, propertyName, getSlot)) + return Parent::put(thisObject, exec, propertyName, value, slot); + if (entry->attributes & kJSPropertyAttributeReadOnly) + return false; + return thisObject->JSCallbackObject::putDirect(vm, propertyName, value); // put as override property + } + } + } + } + + return Parent::put(thisObject, exec, propertyName, value, slot); +} + +template +bool JSCallbackObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyIndex, JSValue value, bool shouldThrow) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCallbackObject* thisObject = jsCast(cell); + JSContextRef ctx = toRef(exec); + JSObjectRef thisRef = toRef(thisObject); + RefPtr propertyNameRef; + JSValueRef valueRef = toRef(exec, value); + Identifier propertyName = Identifier::from(vm, propertyIndex); + + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectSetPropertyCallback setProperty = jsClass->setProperty) { + if (!propertyNameRef) + propertyNameRef = OpaqueJSString::tryCreate(propertyName.impl()); + JSValueRef exception = 0; + bool result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = setProperty(ctx, thisRef, propertyNameRef.get(), valueRef, &exception); + } + if (exception) + throwException(exec, scope, toJS(exec, exception)); + if (result || exception) + return result; + } + + if (OpaqueJSClassStaticValuesTable* staticValues = jsClass->staticValues(exec)) { + if (StaticValueEntry* entry = staticValues->get(propertyName.impl())) { + if (entry->attributes & kJSPropertyAttributeReadOnly) + return false; + if (JSObjectSetPropertyCallback setProperty = entry->setProperty) { + JSValueRef exception = 0; + bool result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = setProperty(ctx, thisRef, entry->propertyNameRef.get(), valueRef, &exception); + } + if (exception) + throwException(exec, scope, toJS(exec, exception)); + if (result || exception) + return result; + } + } + } + + if (OpaqueJSClassStaticFunctionsTable* staticFunctions = jsClass->staticFunctions(exec)) { + if (StaticFunctionEntry* entry = staticFunctions->get(propertyName.impl())) { + if (entry->attributes & kJSPropertyAttributeReadOnly) + return false; + break; + } + } + } + + return Parent::putByIndex(thisObject, exec, propertyIndex, value, shouldThrow); +} + +template +bool JSCallbackObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCallbackObject* thisObject = jsCast(cell); + JSContextRef ctx = toRef(exec); + JSObjectRef thisRef = toRef(thisObject); + RefPtr propertyNameRef; + + if (StringImpl* name = propertyName.uid()) { + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectDeletePropertyCallback deleteProperty = jsClass->deleteProperty) { + if (!propertyNameRef) + propertyNameRef = OpaqueJSString::tryCreate(name); + JSValueRef exception = 0; + bool result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = deleteProperty(ctx, thisRef, propertyNameRef.get(), &exception); + } + if (exception) + throwException(exec, scope, toJS(exec, exception)); + if (result || exception) + return true; + } + + if (OpaqueJSClassStaticValuesTable* staticValues = jsClass->staticValues(exec)) { + if (StaticValueEntry* entry = staticValues->get(name)) { + if (entry->attributes & kJSPropertyAttributeDontDelete) + return false; + return true; + } + } + + if (OpaqueJSClassStaticFunctionsTable* staticFunctions = jsClass->staticFunctions(exec)) { + if (StaticFunctionEntry* entry = staticFunctions->get(name)) { + if (entry->attributes & kJSPropertyAttributeDontDelete) + return false; + return true; + } + } + } + } + + return Parent::deleteProperty(thisObject, exec, propertyName); +} + +template +bool JSCallbackObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName) +{ + VM& vm = exec->vm(); + JSCallbackObject* thisObject = jsCast(cell); + return thisObject->methodTable(vm)->deleteProperty(thisObject, exec, Identifier::from(vm, propertyName)); +} + +template +ConstructType JSCallbackObject::getConstructData(JSCell* cell, ConstructData& constructData) +{ + JSCallbackObject* thisObject = jsCast(cell); + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (jsClass->callAsConstructor) { + constructData.native.function = construct; + return ConstructType::Host; + } + } + return ConstructType::None; +} + +template +EncodedJSValue JSCallbackObject::construct(ExecState* exec) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSObject* constructor = exec->jsCallee(); + JSContextRef execRef = toRef(exec); + JSObjectRef constructorRef = toRef(constructor); + + for (JSClassRef jsClass = jsCast*>(constructor)->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectCallAsConstructorCallback callAsConstructor = jsClass->callAsConstructor) { + size_t argumentCount = exec->argumentCount(); + Vector arguments; + arguments.reserveInitialCapacity(argumentCount); + for (size_t i = 0; i < argumentCount; ++i) + arguments.uncheckedAppend(toRef(exec, exec->uncheckedArgument(i))); + JSValueRef exception = 0; + JSObject* result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = toJS(callAsConstructor(execRef, constructorRef, argumentCount, arguments.data(), &exception)); + } + if (exception) + throwException(exec, scope, toJS(exec, exception)); + return JSValue::encode(result); + } + } + + RELEASE_ASSERT_NOT_REACHED(); // getConstructData should prevent us from reaching here + return JSValue::encode(JSValue()); +} + +template +bool JSCallbackObject::customHasInstance(JSObject* object, ExecState* exec, JSValue value) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCallbackObject* thisObject = jsCast(object); + JSContextRef execRef = toRef(exec); + JSObjectRef thisRef = toRef(thisObject); + + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectHasInstanceCallback hasInstance = jsClass->hasInstance) { + JSValueRef valueRef = toRef(exec, value); + JSValueRef exception = 0; + bool result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = hasInstance(execRef, thisRef, valueRef, &exception); + } + if (exception) + throwException(exec, scope, toJS(exec, exception)); + return result; + } + } + return false; +} + +template +CallType JSCallbackObject::getCallData(JSCell* cell, CallData& callData) +{ + JSCallbackObject* thisObject = jsCast(cell); + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (jsClass->callAsFunction) { + callData.native.function = call; + return CallType::Host; + } + } + return CallType::None; +} + +template +EncodedJSValue JSCallbackObject::call(ExecState* exec) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSContextRef execRef = toRef(exec); + JSObjectRef functionRef = toRef(exec->jsCallee()); + JSObjectRef thisObjRef = toRef(jsCast(exec->thisValue().toThis(exec, NotStrictMode))); + + for (JSClassRef jsClass = jsCast*>(toJS(functionRef))->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectCallAsFunctionCallback callAsFunction = jsClass->callAsFunction) { + size_t argumentCount = exec->argumentCount(); + Vector arguments; + arguments.reserveInitialCapacity(argumentCount); + for (size_t i = 0; i < argumentCount; ++i) + arguments.uncheckedAppend(toRef(exec, exec->uncheckedArgument(i))); + JSValueRef exception = 0; + JSValue result; + { + JSLock::DropAllLocks dropAllLocks(exec); + result = toJS(exec, callAsFunction(execRef, functionRef, thisObjRef, argumentCount, arguments.data(), &exception)); + } + if (exception) + throwException(exec, scope, toJS(exec, exception)); + return JSValue::encode(result); + } + } + + RELEASE_ASSERT_NOT_REACHED(); // getCallData should prevent us from reaching here + return JSValue::encode(JSValue()); +} + +template +void JSCallbackObject::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) +{ + VM& vm = exec->vm(); + JSCallbackObject* thisObject = jsCast(object); + JSContextRef execRef = toRef(exec); + JSObjectRef thisRef = toRef(thisObject); + + for (JSClassRef jsClass = thisObject->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectGetPropertyNamesCallback getPropertyNames = jsClass->getPropertyNames) { + JSLock::DropAllLocks dropAllLocks(exec); + getPropertyNames(execRef, thisRef, toRef(&propertyNames)); + } + + if (OpaqueJSClassStaticValuesTable* staticValues = jsClass->staticValues(exec)) { + typedef OpaqueJSClassStaticValuesTable::const_iterator iterator; + iterator end = staticValues->end(); + for (iterator it = staticValues->begin(); it != end; ++it) { + StringImpl* name = it->key.get(); + StaticValueEntry* entry = it->value.get(); + if (entry->getProperty && (!(entry->attributes & kJSPropertyAttributeDontEnum) || mode.includeDontEnumProperties())) { + ASSERT(!name->isSymbol()); + propertyNames.add(Identifier::fromString(vm, String(name))); + } + } + } + + if (OpaqueJSClassStaticFunctionsTable* staticFunctions = jsClass->staticFunctions(exec)) { + typedef OpaqueJSClassStaticFunctionsTable::const_iterator iterator; + iterator end = staticFunctions->end(); + for (iterator it = staticFunctions->begin(); it != end; ++it) { + StringImpl* name = it->key.get(); + StaticFunctionEntry* entry = it->value.get(); + if (!(entry->attributes & kJSPropertyAttributeDontEnum) || mode.includeDontEnumProperties()) { + ASSERT(!name->isSymbol()); + propertyNames.add(Identifier::fromString(vm, String(name))); + } + } + } + } + + Parent::getOwnNonIndexPropertyNames(thisObject, exec, propertyNames, mode); +} + +template +void JSCallbackObject::setPrivate(void* data) +{ + m_callbackObjectData->privateData = data; +} + +template +void* JSCallbackObject::getPrivate() +{ + return m_callbackObjectData->privateData; +} + +template +bool JSCallbackObject::inherits(JSClassRef c) const +{ + for (JSClassRef jsClass = classRef(); jsClass; jsClass = jsClass->parentClass) { + if (jsClass == c) + return true; + } + return false; +} + +template +JSValue JSCallbackObject::getStaticValue(ExecState* exec, PropertyName propertyName) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSObjectRef thisRef = toRef(this); + + if (StringImpl* name = propertyName.uid()) { + for (JSClassRef jsClass = classRef(); jsClass; jsClass = jsClass->parentClass) { + if (OpaqueJSClassStaticValuesTable* staticValues = jsClass->staticValues(exec)) { + if (StaticValueEntry* entry = staticValues->get(name)) { + if (JSObjectGetPropertyCallback getProperty = entry->getProperty) { + JSValueRef exception = 0; + JSValueRef value; + { + JSLock::DropAllLocks dropAllLocks(exec); + value = getProperty(toRef(exec), thisRef, entry->propertyNameRef.get(), &exception); + } + if (exception) { + throwException(exec, scope, toJS(exec, exception)); + return jsUndefined(); + } + if (value) + return toJS(exec, value); + } + } + } + } + } + + return JSValue(); +} + +template +EncodedJSValue JSCallbackObject::staticFunctionGetter(ExecState* exec, EncodedJSValue thisValue, PropertyName propertyName) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCallbackObject* thisObj = asCallbackObject(thisValue); + + // Check for cached or override property. + PropertySlot slot2(thisObj, PropertySlot::InternalMethodType::VMInquiry); + if (Parent::getOwnPropertySlot(thisObj, exec, propertyName, slot2)) + return JSValue::encode(slot2.getValue(exec, propertyName)); + + if (StringImpl* name = propertyName.uid()) { + for (JSClassRef jsClass = thisObj->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (OpaqueJSClassStaticFunctionsTable* staticFunctions = jsClass->staticFunctions(exec)) { + if (StaticFunctionEntry* entry = staticFunctions->get(name)) { + if (JSObjectCallAsFunctionCallback callAsFunction = entry->callAsFunction) { + JSObject* o = JSCallbackFunction::create(vm, thisObj->globalObject(vm), callAsFunction, name); + thisObj->putDirect(vm, propertyName, o, entry->attributes); + return JSValue::encode(o); + } + } + } + } + } + + return JSValue::encode(throwException(exec, scope, createReferenceError(exec, "Static function property defined with NULL callAsFunction callback."_s))); +} + +template +EncodedJSValue JSCallbackObject::callbackGetter(ExecState* exec, EncodedJSValue thisValue, PropertyName propertyName) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSCallbackObject* thisObj = asCallbackObject(thisValue); + + JSObjectRef thisRef = toRef(thisObj); + RefPtr propertyNameRef; + + if (StringImpl* name = propertyName.uid()) { + for (JSClassRef jsClass = thisObj->classRef(); jsClass; jsClass = jsClass->parentClass) { + if (JSObjectGetPropertyCallback getProperty = jsClass->getProperty) { + if (!propertyNameRef) + propertyNameRef = OpaqueJSString::tryCreate(name); + JSValueRef exception = 0; + JSValueRef value; + { + JSLock::DropAllLocks dropAllLocks(exec); + value = getProperty(toRef(exec), thisRef, propertyNameRef.get(), &exception); + } + if (exception) { + throwException(exec, scope, toJS(exec, exception)); + return JSValue::encode(jsUndefined()); + } + if (value) + return JSValue::encode(toJS(exec, value)); + } + } + } + + return JSValue::encode(throwException(exec, scope, createReferenceError(exec, "hasProperty callback returned true for a property that doesn't exist."_s))); +} + +} // namespace JSC diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSClassRef.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSClassRef.h new file mode 100644 index 000000000..0dd0dca54 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSClassRef.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2006 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSClassRef_h +#define JSClassRef_h + +#include "OpaqueJSString.h" +#include "Protect.h" +#include "Weak.h" +#include +#include +#include + +struct StaticValueEntry { + WTF_MAKE_FAST_ALLOCATED; +public: + StaticValueEntry(JSObjectGetPropertyCallback _getProperty, JSObjectSetPropertyCallback _setProperty, JSPropertyAttributes _attributes, String& propertyName) + : getProperty(_getProperty) + , setProperty(_setProperty) + , attributes(_attributes) + , propertyNameRef(OpaqueJSString::tryCreate(propertyName)) + { + } + + JSObjectGetPropertyCallback getProperty; + JSObjectSetPropertyCallback setProperty; + JSPropertyAttributes attributes; + RefPtr propertyNameRef; +}; + +struct StaticFunctionEntry { + WTF_MAKE_FAST_ALLOCATED; +public: + StaticFunctionEntry(JSObjectCallAsFunctionCallback _callAsFunction, JSPropertyAttributes _attributes) + : callAsFunction(_callAsFunction), attributes(_attributes) + { + } + + JSObjectCallAsFunctionCallback callAsFunction; + JSPropertyAttributes attributes; +}; + +typedef HashMap, std::unique_ptr> OpaqueJSClassStaticValuesTable; +typedef HashMap, std::unique_ptr> OpaqueJSClassStaticFunctionsTable; + +struct OpaqueJSClass; + +// An OpaqueJSClass (JSClass) is created without a context, so it can be used with any context, even across context groups. +// This structure holds data members that vary across context groups. +struct OpaqueJSClassContextData { + WTF_MAKE_NONCOPYABLE(OpaqueJSClassContextData); WTF_MAKE_FAST_ALLOCATED; +public: + OpaqueJSClassContextData(JSC::VM&, OpaqueJSClass*); + + // It is necessary to keep OpaqueJSClass alive because of the following rare scenario: + // 1. A class is created and used, so its context data is stored in VM hash map. + // 2. The class is released, and when all JS objects that use it are collected, OpaqueJSClass + // is deleted (that's the part prevented by this RefPtr). + // 3. Another class is created at the same address. + // 4. When it is used, the old context data is found in VM and used. + RefPtr m_class; + + std::unique_ptr staticValues; + std::unique_ptr staticFunctions; + JSC::Weak cachedPrototype; +}; + +struct OpaqueJSClass : public ThreadSafeRefCounted { + static Ref create(const JSClassDefinition*); + static Ref createNoAutomaticPrototype(const JSClassDefinition*); + JS_EXPORT_PRIVATE ~OpaqueJSClass(); + + String className(); + OpaqueJSClassStaticValuesTable* staticValues(JSC::ExecState*); + OpaqueJSClassStaticFunctionsTable* staticFunctions(JSC::ExecState*); + JSC::JSObject* prototype(JSC::ExecState*); + + OpaqueJSClass* parentClass; + OpaqueJSClass* prototypeClass; + + JSObjectInitializeCallback initialize; + JSObjectFinalizeCallback finalize; + JSObjectHasPropertyCallback hasProperty; + JSObjectGetPropertyCallback getProperty; + JSObjectSetPropertyCallback setProperty; + JSObjectDeletePropertyCallback deleteProperty; + JSObjectGetPropertyNamesCallback getPropertyNames; + JSObjectCallAsFunctionCallback callAsFunction; + JSObjectCallAsConstructorCallback callAsConstructor; + JSObjectHasInstanceCallback hasInstance; + JSObjectConvertToTypeCallback convertToType; + +private: + friend struct OpaqueJSClassContextData; + + OpaqueJSClass(); + OpaqueJSClass(const OpaqueJSClass&); + OpaqueJSClass(const JSClassDefinition*, OpaqueJSClass* protoClass); + + OpaqueJSClassContextData& contextData(JSC::ExecState*); + + // Strings in these data members should not be put into any AtomStringTable. + String m_className; + std::unique_ptr m_staticValues; + std::unique_ptr m_staticFunctions; +}; + +#endif // JSClassRef_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContext.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContext.h new file mode 100644 index 000000000..6b9c5d417 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContext.h @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2013-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSContext_h +#define JSContext_h + +#include +#include + +#if JSC_OBJC_API_ENABLED + +@class JSScript, JSVirtualMachine, JSValue, JSContext; + +/*! +@interface +@discussion A JSContext is a JavaScript execution environment. All + JavaScript execution takes place within a context, and all JavaScript values + are tied to a context. +*/ +JSC_CLASS_AVAILABLE(macos(10.9), ios(7.0)) +@interface JSContext : NSObject + +/*! +@methodgroup Creating New JSContexts +*/ +/*! +@method +@abstract Create a JSContext. +@result The new context. +*/ +- (instancetype)init; + +/*! +@method +@abstract Create a JSContext in the specified virtual machine. +@param virtualMachine The JSVirtualMachine in which the context will be created. +@result The new context. +*/ +- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine; + +/*! +@methodgroup Evaluating Scripts +*/ +/*! +@method +@abstract Evaluate a string of JavaScript code. +@param script A string containing the JavaScript code to evaluate. +@result The last value generated by the script. +*/ +- (JSValue *)evaluateScript:(NSString *)script; + +/*! +@method +@abstract Evaluate a string of JavaScript code, with a URL for the script's source file. +@param script A string containing the JavaScript code to evaluate. +@param sourceURL A URL for the script's source file. Used by debuggers and when reporting exceptions. This parameter is informative only: it does not change the behavior of the script. +@result The last value generated by the script. +*/ +- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@methodgroup Callback Accessors +*/ +/*! +@method +@abstract Get the JSContext that is currently executing. +@discussion This method may be called from within an Objective-C block or method invoked + as a callback from JavaScript to retrieve the callback's context. Outside of + a callback from JavaScript this method will return nil. +@result The currently executing JSContext or nil if there isn't one. +*/ ++ (JSContext *)currentContext; + +/*! +@method +@abstract Get the JavaScript function that is currently executing. +@discussion This method may be called from within an Objective-C block or method invoked + as a callback from JavaScript to retrieve the callback's context. Outside of + a callback from JavaScript this method will return nil. +@result The currently executing JavaScript function or nil if there isn't one. +*/ ++ (JSValue *)currentCallee JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@method +@abstract Get the this value of the currently executing method. +@discussion This method may be called from within an Objective-C block or method invoked + as a callback from JavaScript to retrieve the callback's this value. Outside + of a callback from JavaScript this method will return nil. +@result The current this value or nil if there isn't one. +*/ ++ (JSValue *)currentThis; + +/*! +@method +@abstract Get the arguments to the current callback. +@discussion This method may be called from within an Objective-C block or method invoked + as a callback from JavaScript to retrieve the callback's arguments, objects + in the returned array are instances of JSValue. Outside of a callback from + JavaScript this method will return nil. +@result An NSArray of the arguments nil if there is no current callback. +*/ ++ (NSArray *)currentArguments; + +/*! +@functiongroup Global Properties +*/ + +/*! +@property +@abstract Get the global object of the context. +@discussion This method retrieves the global object of the JavaScript execution context. + Instances of JSContext originating from WebKit will return a reference to the + WindowProxy object. +@result The global object. +*/ +@property (readonly, strong) JSValue *globalObject; + +/*! +@property +@discussion The exception property may be used to throw an exception to JavaScript. + + Before a callback is made from JavaScript to an Objective-C block or method, + the prior value of the exception property will be preserved and the property + will be set to nil. After the callback has completed the new value of the + exception property will be read, and prior value restored. If the new value + of exception is not nil, the callback will result in that value being thrown. + + This property may also be used to check for uncaught exceptions arising from + API function calls (since the default behaviour of exceptionHandler is to + assign an uncaught exception to this property). +*/ +@property (strong) JSValue *exception; + +/*! +@property +@discussion If a call to an API function results in an uncaught JavaScript exception, the + exceptionHandler block will be invoked. The default implementation for the + exception handler will store the exception to the exception property on + context. As a consequence the default behaviour is for uncaught exceptions + occurring within a callback from JavaScript to be rethrown upon return. + Setting this value to nil will cause all exceptions occurring + within a callback from JavaScript to be silently caught. +*/ +@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception); + +/*! +@property +@discussion All instances of JSContext are associated with a JSVirtualMachine. +*/ +@property (readonly, strong) JSVirtualMachine *virtualMachine; + +/*! +@property +@discussion Name of the JSContext. Exposed when remote debugging the context. +*/ +@property (copy) NSString *name JSC_API_AVAILABLE(macos(10.10), ios(8.0)); +@end + +/*! +@category +@discussion Instances of JSContext implement the following methods in order to enable + support for subscript access by key and index, for example: + +@textblock + JSContext *context; + JSValue *v = context[@"X"]; // Get value for "X" from the global object. + context[@"Y"] = v; // Assign 'v' to "Y" on the global object. +@/textblock + + An object key passed as a subscript will be converted to a JavaScript value, + and then the value converted to a string used to resolve a property of the + global object. +*/ +@interface JSContext (SubscriptSupport) + +/*! +@method +@abstract Get a particular property on the global object. +@result The JSValue for the global object's property. +*/ +- (JSValue *)objectForKeyedSubscript:(id)key; + +/*! +@method +@abstract Set a particular property on the global object. +*/ +- (void)setObject:(id)object forKeyedSubscript:(NSObject *)key; + +@end + +/*! +@category +@discussion These functions are for bridging between the C API and the Objective-C API. +*/ +@interface JSContext (JSContextRefSupport) + +/*! +@method +@abstract Create a JSContext, wrapping its C API counterpart. +@result The JSContext equivalent of the provided JSGlobalContextRef. +*/ ++ (JSContext *)contextWithJSGlobalContextRef:(JSGlobalContextRef)jsGlobalContextRef; + +/*! +@property +@abstract Get the C API counterpart wrapped by a JSContext. +@result The C API equivalent of this JSContext. +*/ +@property (readonly) JSGlobalContextRef JSGlobalContextRef; + +@end + +#endif + +#endif // JSContext_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextInternal.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextInternal.h new file mode 100644 index 000000000..958c479f4 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextInternal.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +#if JSC_OBJC_API_ENABLED + +#import + +struct CallbackData { + CallbackData* next; + JSContext *context; + JSValue *preservedException; + JSValueRef calleeValue; + JSValueRef thisValue; + size_t argumentCount; + const JSValueRef *arguments; + NSArray *currentArguments; +}; + +@class JSWrapperMap; + +@interface JSContext(Internal) + +- (void)notifyException:(JSValueRef)exception; +- (JSValue *)valueFromNotifyException:(JSValueRef)exception; +- (BOOL)boolFromNotifyException:(JSValueRef)exception; + +- (void)beginCallbackWithData:(CallbackData *)callbackData calleeValue:(JSValueRef)calleeValue thisValue:(JSValueRef)thisValue argumentCount:(size_t)argumentCount arguments:(const JSValueRef *)arguments; +- (void)endCallbackWithData:(CallbackData *)callbackData; + +- (JSWrapperMap *)wrapperMap; +- (JSValue *)wrapperForObjCObject:(id)object; +- (JSValue *)wrapperForJSObject:(JSValueRef)value; + +@end + +#endif diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextPrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextPrivate.h new file mode 100644 index 000000000..75f526b97 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextPrivate.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSContextPrivate_h +#define JSContextPrivate_h + +#if JSC_OBJC_API_ENABLED + +#import + +@protocol JSModuleLoaderDelegate + +@required + +/*! @abstract Provides source code for any JS module that is actively imported. + @param context The context for which the module is being requested. + @param identifier The resolved identifier for the requested module. + @param resolve A JS function to call with the desired script for identifier. + @param reject A JS function to call when identifier cannot be fetched. + @discussion Currently, identifier will always be an absolute file URL computed from specifier of the requested module relative to the URL of the requesting script. If the requesting script does not have a URL and the module specifier is not an absolute path the module loader will fail to load the module. + + The first argument to resolve sholud always be a JSScript, otherwise the module loader will reject the module. + + Once an identifier has been resolved or rejected in a given context it will never be requested again. If a script is successfully evaluated it will not be re-evaluated on any subsequent import. + + The VM will retain all evaluated modules for the lifetime of the context. + */ +- (void)context:(JSContext *)context fetchModuleForIdentifier:(JSValue *)identifier withResolveHandler:(JSValue *)resolve andRejectHandler:(JSValue *)reject; + +@optional + +/*! @abstract This is called before the module with "key" is evaluated. + @param key The module key for the module that is about to be evaluated. + */ +- (void)willEvaluateModule:(NSURL *)key; + +/*! @abstract This is called after the module with "key" is evaluated. + @param key The module key for the module that was just evaluated. + */ +- (void)didEvaluateModule:(NSURL *)key; + +@end + +@interface JSContext(Private) + +/*! +@property +@discussion Remote inspection setting of the JSContext. Default value is YES. +*/ +@property (setter=_setRemoteInspectionEnabled:) BOOL _remoteInspectionEnabled JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@property +@discussion Set whether or not the native call stack is included when reporting exceptions. Default value is YES. +*/ +@property (setter=_setIncludesNativeCallStackWhenReportingExceptions:) BOOL _includesNativeCallStackWhenReportingExceptions JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@property +@discussion Set the run loop the Web Inspector debugger should use when evaluating JavaScript in the JSContext. +*/ +@property (setter=_setDebuggerRunLoop:) CFRunLoopRef _debuggerRunLoop JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! @abstract The delegate the context will use when trying to load a module. Note, this delegate will be ignored for contexts returned by UIWebView. */ +@property (nonatomic, weak) id moduleLoaderDelegate JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! + @method + @abstract Run a JSScript. + @param script the JSScript to evaluate. + @discussion If the provided JSScript was created with kJSScriptTypeProgram, the script will run synchronously and return the result of evaluation. + + Otherwise, if the script was created with kJSScriptTypeModule, the module will be run asynchronously and will return a promise resolved when the module and any transitive dependencies are loaded. The module loader will treat the script as if it had been returned from a delegate call to moduleLoaderDelegate. This mirrors the JavaScript dynamic import operation. + */ +// FIXME: Before making this public need to fix: https://bugs.webkit.org/show_bug.cgi?id=199714 +- (JSValue *)evaluateJSScript:(JSScript *)script JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! + @method + @abstract Get the identifiers of the modules a JSScript depends on in this context. + @param script the JSScript to determine the dependencies of. + @result An Array containing all the identifiers of modules script depends on. + @discussion If the provided JSScript was not created with kJSScriptTypeModule, an exception will be thrown. Also, if the script has not already been evaluated in this context an error will be throw. + */ +- (JSValue *)dependencyIdentifiersForModuleJSScript:(JSScript *)script JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +@end + +#endif + +#endif // JSContextInternal_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRef.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRef.h new file mode 100644 index 000000000..1ce743580 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRef.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2006 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSContextRef_h +#define JSContextRef_h + +#include +#include +#include + +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +@function +@abstract Creates a JavaScript context group. +@discussion A JSContextGroup associates JavaScript contexts with one another. + Contexts in the same group may share and exchange JavaScript objects. Sharing and/or exchanging + JavaScript objects between contexts in different groups will produce undefined behavior. + When objects from the same context group are used in multiple threads, explicit + synchronization is required. + + A JSContextGroup may need to run deferred tasks on a run loop, such as garbage collection + or resolving WebAssembly compilations. By default, calling JSContextGroupCreate will use + the run loop of the thread it was called on. Currently, there is no API to change a + JSContextGroup's run loop once it has been created. +@result The created JSContextGroup. +*/ +JS_EXPORT JSContextGroupRef JSContextGroupCreate(void) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! +@function +@abstract Retains a JavaScript context group. +@param group The JSContextGroup to retain. +@result A JSContextGroup that is the same as group. +*/ +JS_EXPORT JSContextGroupRef JSContextGroupRetain(JSContextGroupRef group) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! +@function +@abstract Releases a JavaScript context group. +@param group The JSContextGroup to release. +*/ +JS_EXPORT void JSContextGroupRelease(JSContextGroupRef group) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! +@function +@abstract Creates a global JavaScript execution context. +@discussion JSGlobalContextCreate allocates a global object and populates it with all the + built-in JavaScript objects, such as Object, Function, String, and Array. + + In WebKit version 4.0 and later, the context is created in a unique context group. + Therefore, scripts may execute in it concurrently with scripts executing in other contexts. + However, you may not use values created in the context in other contexts. +@param globalObjectClass The class to use when creating the global object. Pass + NULL to use the default object class. +@result A JSGlobalContext with a global object of class globalObjectClass. +*/ +JS_EXPORT JSGlobalContextRef JSGlobalContextCreate(JSClassRef globalObjectClass) JSC_API_AVAILABLE(macos(10.5), ios(7.0)); + +/*! +@function +@abstract Creates a global JavaScript execution context in the context group provided. +@discussion JSGlobalContextCreateInGroup allocates a global object and populates it with + all the built-in JavaScript objects, such as Object, Function, String, and Array. +@param globalObjectClass The class to use when creating the global object. Pass + NULL to use the default object class. +@param group The context group to use. The created global context retains the group. + Pass NULL to create a unique group for the context. +@result A JSGlobalContext with a global object of class globalObjectClass and a context + group equal to group. +*/ +JS_EXPORT JSGlobalContextRef JSGlobalContextCreateInGroup(JSContextGroupRef group, JSClassRef globalObjectClass) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! +@function +@abstract Retains a global JavaScript execution context. +@param ctx The JSGlobalContext to retain. +@result A JSGlobalContext that is the same as ctx. +*/ +JS_EXPORT JSGlobalContextRef JSGlobalContextRetain(JSGlobalContextRef ctx); + +/*! +@function +@abstract Releases a global JavaScript execution context. +@param ctx The JSGlobalContext to release. +*/ +JS_EXPORT void JSGlobalContextRelease(JSGlobalContextRef ctx); + +/*! +@function +@abstract Gets the global object of a JavaScript execution context. +@param ctx The JSContext whose global object you want to get. +@result ctx's global object. +*/ +JS_EXPORT JSObjectRef JSContextGetGlobalObject(JSContextRef ctx); + +/*! +@function +@abstract Gets the context group to which a JavaScript execution context belongs. +@param ctx The JSContext whose group you want to get. +@result ctx's group. +*/ +JS_EXPORT JSContextGroupRef JSContextGetGroup(JSContextRef ctx) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! +@function +@abstract Gets the global context of a JavaScript execution context. +@param ctx The JSContext whose global context you want to get. +@result ctx's global context. +*/ +JS_EXPORT JSGlobalContextRef JSContextGetGlobalContext(JSContextRef ctx) JSC_API_AVAILABLE(macos(10.7), ios(7.0)); + +/*! +@function +@abstract Gets a copy of the name of a context. +@param ctx The JSGlobalContext whose name you want to get. +@result The name for ctx. +@discussion A JSGlobalContext's name is exposed for remote debugging to make it +easier to identify the context you would like to attach to. +*/ +JS_EXPORT JSStringRef JSGlobalContextCopyName(JSGlobalContextRef ctx) JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@function +@abstract Sets the remote debugging name for a context. +@param ctx The JSGlobalContext that you want to name. +@param name The remote debugging name to set on ctx. +*/ +JS_EXPORT void JSGlobalContextSetName(JSGlobalContextRef ctx, JSStringRef name) JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +#ifdef __cplusplus +} +#endif + +#endif /* JSContextRef_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRefInspectorSupport.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRefInspectorSupport.h new file mode 100644 index 000000000..a09d828bd --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRefInspectorSupport.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSContextRefInspectorSupport_h +#define JSContextRefInspectorSupport_h + +#ifndef __cplusplus +#error Requires C++ Support. +#endif + +#include + +namespace Inspector { +class AugmentableInspectorController; +} + +extern "C" { +JS_EXPORT Inspector::AugmentableInspectorController* JSGlobalContextGetAugmentableInspectorController(JSGlobalContextRef); +} + +#endif // JSContextRefInspectorSupport_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRefInternal.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRefInternal.h new file mode 100644 index 000000000..149f70bad --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRefInternal.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSContextRefInternal_h +#define JSContextRefInternal_h + +#include "JSContextRefPrivate.h" + +#if USE(CF) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if USE(CF) +/*! +@function +@abstract Gets the run loop used by the Web Inspector debugger when evaluating JavaScript in this context. +@param ctx The JSGlobalContext whose setting you want to get. +*/ +JS_EXPORT CFRunLoopRef JSGlobalContextGetDebuggerRunLoop(JSGlobalContextRef ctx) JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@function +@abstract Sets the run loop used by the Web Inspector debugger when evaluating JavaScript in this context. +@param ctx The JSGlobalContext that you want to change. +@param runLoop The new value of the setting for the context. +*/ +JS_EXPORT void JSGlobalContextSetDebuggerRunLoop(JSGlobalContextRef ctx, CFRunLoopRef runLoop) JSC_API_AVAILABLE(macos(10.10), ios(8.0)); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // JSContextRefInternal_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRefPrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRefPrivate.h new file mode 100644 index 000000000..6ae649ef3 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSContextRefPrivate.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSContextRefPrivate_h +#define JSContextRefPrivate_h + +#include +#include +#include + +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +@function +@abstract Gets a Backtrace for the existing context +@param ctx The JSContext whose backtrace you want to get +@result A string containing the backtrace +*/ +JS_EXPORT JSStringRef JSContextCreateBacktrace(JSContextRef ctx, unsigned maxStackSize) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + + +/*! +@typedef JSShouldTerminateCallback +@abstract The callback invoked when script execution has exceeded the allowed + time limit previously specified via JSContextGroupSetExecutionTimeLimit. +@param ctx The execution context to use. +@param context User specified context data previously passed to + JSContextGroupSetExecutionTimeLimit. +@discussion If you named your function Callback, you would declare it like this: + + bool Callback(JSContextRef ctx, void* context); + + If you return true, the timed out script will terminate. + If you return false, the script will run for another period of the allowed + time limit specified via JSContextGroupSetExecutionTimeLimit. + + Within this callback function, you may call JSContextGroupSetExecutionTimeLimit + to set a new time limit, or JSContextGroupClearExecutionTimeLimit to cancel the + timeout. +*/ +typedef bool +(*JSShouldTerminateCallback) (JSContextRef ctx, void* context); + +/*! +@function +@abstract Sets the script execution time limit. +@param group The JavaScript context group that this time limit applies to. +@param limit The time limit of allowed script execution time in seconds. +@param callback The callback function that will be invoked when the time limit + has been reached. This will give you a chance to decide if you want to + terminate the script or not. If you pass a NULL callback, the script will be + terminated unconditionally when the time limit has been reached. +@param context User data that you can provide to be passed back to you + in your callback. + + In order to guarantee that the execution time limit will take effect, you will + need to call JSContextGroupSetExecutionTimeLimit before you start executing + any scripts. +*/ +JS_EXPORT void JSContextGroupSetExecutionTimeLimit(JSContextGroupRef group, double limit, JSShouldTerminateCallback callback, void* context) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! +@function +@abstract Clears the script execution time limit. +@param group The JavaScript context group that the time limit is cleared on. +*/ +JS_EXPORT void JSContextGroupClearExecutionTimeLimit(JSContextGroupRef group) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! +@function +@abstract Gets a whether or not remote inspection is enabled on the context. +@param ctx The JSGlobalContext whose setting you want to get. +@result The value of the setting, true if remote inspection is enabled, otherwise false. +@discussion Remote inspection is true by default. +*/ +JS_EXPORT bool JSGlobalContextGetRemoteInspectionEnabled(JSGlobalContextRef ctx) JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@function +@abstract Sets the remote inspection setting for a context. +@param ctx The JSGlobalContext that you want to change. +@param enabled The new remote inspection enabled setting for the context. +*/ +JS_EXPORT void JSGlobalContextSetRemoteInspectionEnabled(JSGlobalContextRef ctx, bool enabled) JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@function +@abstract Gets the include native call stack when reporting exceptions setting for a context. +@param ctx The JSGlobalContext whose setting you want to get. +@result The value of the setting, true if remote inspection is enabled, otherwise false. +@discussion This setting is true by default. +*/ +JS_EXPORT bool JSGlobalContextGetIncludesNativeCallStackWhenReportingExceptions(JSGlobalContextRef ctx) JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@function +@abstract Sets the include native call stack when reporting exceptions setting for a context. +@param ctx The JSGlobalContext that you want to change. +@param includesNativeCallStack The new value of the setting for the context. +*/ +JS_EXPORT void JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(JSGlobalContextRef ctx, bool includesNativeCallStack) JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@function +@abstract Sets the unhandled promise rejection callback for a context. +@discussion Similar to window.addEventListener('unhandledrejection'), but for contexts not associated with a web view. +@param ctx The JSGlobalContext to set the callback on. +@param function The callback function to set, which receives the promise and rejection reason as arguments. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +*/ +JS_EXPORT void JSGlobalContextSetUnhandledRejectionCallback(JSGlobalContextRef ctx, JSObjectRef function, JSValueRef* exception) JSC_API_AVAILABLE(macos(JSC_MAC_TBA), ios(JSC_IOS_TBA)); + +#ifdef __cplusplus +} +#endif + +#endif /* JSContextRefPrivate_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSExport.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSExport.h new file mode 100644 index 000000000..5caace642 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSExport.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +#if JSC_OBJC_API_ENABLED + +/*! +@protocol +@abstract JSExport provides a declarative way to export Objective-C objects and + classes -- including properties, instance methods, class methods, and + initializers -- to JavaScript. + +@discussion When an Objective-C object is exported to JavaScript, a JavaScript + wrapper object is created. + + In JavaScript, inheritance works via a chain of prototype objects. + For each Objective-C class in each JSContext, an object appropriate for use + as a prototype will be provided. For the class NSObject the prototype + will be the Object prototype. For all other Objective-C + classes a prototype will be created. The prototype for a given + Objective-C class will have its internal [Prototype] property set to point to + the prototype created for the Objective-C class's superclass. As such the + prototype chain for a JavaScript wrapper object will reflect the wrapped + Objective-C type's inheritance hierarchy. + + JavaScriptCore also produces a constructor for each Objective-C class. The + constructor has a property named 'prototype' that references the prototype, + and the prototype has a property named 'constructor' that references the + constructor. + + By default JavaScriptCore does not export any methods or properties from an + Objective-C class to JavaScript; however methods and properties may be exported + explicitly using JSExport. For each protocol that a class conforms to, if the + protocol incorporates the protocol JSExport, JavaScriptCore exports the methods + and properties in that protocol to JavaScript + + For each exported instance method JavaScriptCore will assign a corresponding + JavaScript function to the prototype. For each exported Objective-C property + JavaScriptCore will assign a corresponding JavaScript accessor to the prototype. + For each exported class method JavaScriptCore will assign a corresponding + JavaScript function to the constructor. For example: + +
+@textblock
+    @protocol MyClassJavaScriptMethods 
+    - (void)foo;
+    @end
+
+    @interface MyClass : NSObject 
+    - (void)foo;
+    - (void)bar;
+    @end
+@/textblock
+
+ + Data properties that are created on the prototype or constructor objects have + the attributes: writable:true, enumerable:false, configurable:true. + Accessor properties have the attributes: enumerable:false and configurable:true. + + If an instance of MyClass is converted to a JavaScript value, the resulting + wrapper object will (via its prototype) export the method foo to JavaScript, + since the class conforms to the MyClassJavaScriptMethods protocol, and this + protocol incorporates JSExport. bar will not be exported. + + JSExport supports properties, arguments, and return values of the following types: + + Primitive numbers: signed values up to 32-bits convert using JSValue's + valueWithInt32/toInt32. Unsigned values up to 32-bits convert using JSValue's + valueWithUInt32/toUInt32. All other numeric values convert using JSValue's + valueWithDouble/toDouble. + + BOOL: values convert using JSValue's valueWithBool/toBool. + + id: values convert using JSValue's valueWithObject/toObject. + + Objective-C instance pointers: Pointers convert using JSValue's + valueWithObjectOfClass/toObject. + + C structs: C structs for CGPoint, NSRange, CGRect, and CGSize convert using + JSValue's appropriate methods. Other C structs are not supported. + + Blocks: Blocks convert using JSValue's valueWithObject/toObject. + + All objects that conform to JSExport convert to JavaScript wrapper objects, + even if they subclass classes that would otherwise behave differently. For + example, if a subclass of NSString conforms to JSExport, it converts to + JavaScript as a wrapper object rather than a JavaScript string. +*/ +@protocol JSExport +@end + +/*! +@define +@abstract Rename a selector when it's exported to JavaScript. +@discussion When a selector that takes one or more arguments is converted to a JavaScript + property name, by default a property name will be generated by performing the + following conversion: + + - All colons are removed from the selector + + - Any lowercase letter that had followed a colon will be capitalized. + + Under the default conversion a selector doFoo:withBar: will be exported as + doFooWithBar. The default conversion may be overridden using the JSExportAs + macro, for example to export a method doFoo:withBar: as doFoo: + +
+@textblock
+    @protocol MyClassJavaScriptMethods 
+    JSExportAs(doFoo,
+    - (void)doFoo:(id)foo withBar:(id)bar
+    );
+    @end
+@/textblock
+
+ + Note that the JSExport macro may only be applied to a selector that takes one + or more argument. +*/ +#define JSExportAs(PropertyName, Selector) \ + @optional Selector __JS_EXPORT_AS__##PropertyName:(id)argument; @required Selector + +#endif diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSHeapFinalizerPrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSHeapFinalizerPrivate.h new file mode 100644 index 000000000..8c9b1525e --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSHeapFinalizerPrivate.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSHeapFinalizerPrivate_h +#define JSHeapFinalizerPrivate_h + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*JSHeapFinalizer)(JSContextGroupRef, void *userData); + +JS_EXPORT void JSContextGroupAddHeapFinalizer(JSContextGroupRef, JSHeapFinalizer, void *userData); +JS_EXPORT void JSContextGroupRemoveHeapFinalizer(JSContextGroupRef, JSHeapFinalizer, void *userData); + +#ifdef __cplusplus +} +#endif + +#endif // JSHeapFinalizerPrivate_h + diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSManagedValue.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSManagedValue.h new file mode 100644 index 000000000..3ebc7a4bd --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSManagedValue.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSManagedValue_h +#define JSManagedValue_h + +#import +#import + +#if JSC_OBJC_API_ENABLED + +@class JSValue; +@class JSContext; + +/*! +@interface +@discussion JSManagedValue represents a "conditionally retained" JSValue. + "Conditionally retained" means that as long as the JSManagedValue's + JSValue is reachable through the JavaScript object graph, + or through the Objective-C object graph reported to the JSVirtualMachine using + addManagedReference:withOwner:, the corresponding JSValue will + be retained. However, if neither graph reaches the JSManagedValue, the + corresponding JSValue will be released and set to nil. + +The primary use for a JSManagedValue is to store a JSValue in an Objective-C +or Swift object that is exported to JavaScript. It is incorrect to store a JSValue +in an object that is exported to JavaScript, since doing so creates a retain cycle. +*/ +NS_CLASS_AVAILABLE(10_9, 7_0) +@interface JSManagedValue : NSObject + +/*! +@method +@abstract Create a JSManagedValue from a JSValue. +@result The new JSManagedValue. +*/ ++ (JSManagedValue *)managedValueWithValue:(JSValue *)value; ++ (JSManagedValue *)managedValueWithValue:(JSValue *)value andOwner:(id)owner JSC_API_AVAILABLE(macos(10.10), ios(8.0)); + +/*! +@method +@abstract Create a JSManagedValue. +@result The new JSManagedValue. +*/ +- (instancetype)initWithValue:(JSValue *)value; + +/*! +@property +@abstract Get the JSValue from the JSManagedValue. +@result The corresponding JSValue for this JSManagedValue or + nil if the JSValue has been collected. +*/ +@property (readonly, strong) JSValue *value; + +@end + +#endif // JSC_OBJC_API_ENABLED + +#endif // JSManagedValue_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSManagedValueInternal.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSManagedValueInternal.h new file mode 100644 index 000000000..2443fe5a9 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSManagedValueInternal.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSManagedValueInternal_h +#define JSManagedValueInternal_h + +#import + +#if JSC_OBJC_API_ENABLED + +@interface JSManagedValue(Internal) + +- (void)didAddOwner:(id)owner; +- (void)didRemoveOwner:(id)owner; + +@end + +#endif // JSC_OBJC_API_ENABLED + +#endif // JSManagedValueInternal_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSMarkingConstraintPrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSMarkingConstraintPrivate.h new file mode 100644 index 000000000..aaf85cad1 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSMarkingConstraintPrivate.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSMarkingConstraintPrivate_h +#define JSMarkingConstraintPrivate_h + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct JSMarker; +typedef struct JSMarker JSMarker; +typedef JSMarker *JSMarkerRef; + +struct JSMarker { + bool (*IsMarked)(JSMarkerRef, JSObjectRef); + void (*Mark)(JSMarkerRef, JSObjectRef); +}; + +typedef void (*JSMarkingConstraint)(JSMarkerRef, void *userData); + +JS_EXPORT void JSContextGroupAddMarkingConstraint(JSContextGroupRef, JSMarkingConstraint, void *userData); + +#ifdef __cplusplus +} +#endif + +#endif // JSMarkingConstraintPrivate_h + diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSObjectRef.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSObjectRef.h new file mode 100644 index 000000000..330f5e3be --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSObjectRef.h @@ -0,0 +1,753 @@ +/* + * Copyright (C) 2006-2019 Apple Inc. All rights reserved. + * Copyright (C) 2008 Kelvin W Sherlock (ksherlock@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSObjectRef_h +#define JSObjectRef_h + +#include +#include +#include + +#ifndef __cplusplus +#include +#endif +#include /* for size_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +@enum JSPropertyAttribute +@constant kJSPropertyAttributeNone Specifies that a property has no special attributes. +@constant kJSPropertyAttributeReadOnly Specifies that a property is read-only. +@constant kJSPropertyAttributeDontEnum Specifies that a property should not be enumerated by JSPropertyEnumerators and JavaScript for...in loops. +@constant kJSPropertyAttributeDontDelete Specifies that the delete operation should fail on a property. +*/ +enum { + kJSPropertyAttributeNone = 0, + kJSPropertyAttributeReadOnly = 1 << 1, + kJSPropertyAttributeDontEnum = 1 << 2, + kJSPropertyAttributeDontDelete = 1 << 3 +}; + +/*! +@typedef JSPropertyAttributes +@abstract A set of JSPropertyAttributes. Combine multiple attributes by logically ORing them together. +*/ +typedef unsigned JSPropertyAttributes; + +/*! +@enum JSClassAttribute +@constant kJSClassAttributeNone Specifies that a class has no special attributes. +@constant kJSClassAttributeNoAutomaticPrototype Specifies that a class should not automatically generate a shared prototype for its instance objects. Use kJSClassAttributeNoAutomaticPrototype in combination with JSObjectSetPrototype to manage prototypes manually. +*/ +enum { + kJSClassAttributeNone = 0, + kJSClassAttributeNoAutomaticPrototype = 1 << 1 +}; + +/*! +@typedef JSClassAttributes +@abstract A set of JSClassAttributes. Combine multiple attributes by logically ORing them together. +*/ +typedef unsigned JSClassAttributes; + +/*! +@typedef JSObjectInitializeCallback +@abstract The callback invoked when an object is first created. +@param ctx The execution context to use. +@param object The JSObject being created. +@discussion If you named your function Initialize, you would declare it like this: + +void Initialize(JSContextRef ctx, JSObjectRef object); + +Unlike the other object callbacks, the initialize callback is called on the least +derived class (the parent class) first, and the most derived class last. +*/ +typedef void +(*JSObjectInitializeCallback) (JSContextRef ctx, JSObjectRef object); + +/*! +@typedef JSObjectFinalizeCallback +@abstract The callback invoked when an object is finalized (prepared for garbage collection). An object may be finalized on any thread. +@param object The JSObject being finalized. +@discussion If you named your function Finalize, you would declare it like this: + +void Finalize(JSObjectRef object); + +The finalize callback is called on the most derived class first, and the least +derived class (the parent class) last. + +You must not call any function that may cause a garbage collection or an allocation +of a garbage collected object from within a JSObjectFinalizeCallback. This includes +all functions that have a JSContextRef parameter. +*/ +typedef void +(*JSObjectFinalizeCallback) (JSObjectRef object); + +/*! +@typedef JSObjectHasPropertyCallback +@abstract The callback invoked when determining whether an object has a property. +@param ctx The execution context to use. +@param object The JSObject to search for the property. +@param propertyName A JSString containing the name of the property look up. +@result true if object has the property, otherwise false. +@discussion If you named your function HasProperty, you would declare it like this: + +bool HasProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName); + +If this function returns false, the hasProperty request forwards to object's statically declared properties, then its parent class chain (which includes the default object class), then its prototype chain. + +This callback enables optimization in cases where only a property's existence needs to be known, not its value, and computing its value would be expensive. + +If this callback is NULL, the getProperty callback will be used to service hasProperty requests. +*/ +typedef bool +(*JSObjectHasPropertyCallback) (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName); + +/*! +@typedef JSObjectGetPropertyCallback +@abstract The callback invoked when getting a property's value. +@param ctx The execution context to use. +@param object The JSObject to search for the property. +@param propertyName A JSString containing the name of the property to get. +@param exception A pointer to a JSValueRef in which to return an exception, if any. +@result The property's value if object has the property, otherwise NULL. +@discussion If you named your function GetProperty, you would declare it like this: + +JSValueRef GetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); + +If this function returns NULL, the get request forwards to object's statically declared properties, then its parent class chain (which includes the default object class), then its prototype chain. +*/ +typedef JSValueRef +(*JSObjectGetPropertyCallback) (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); + +/*! +@typedef JSObjectSetPropertyCallback +@abstract The callback invoked when setting a property's value. +@param ctx The execution context to use. +@param object The JSObject on which to set the property's value. +@param propertyName A JSString containing the name of the property to set. +@param value A JSValue to use as the property's value. +@param exception A pointer to a JSValueRef in which to return an exception, if any. +@result true if the property was set, otherwise false. +@discussion If you named your function SetProperty, you would declare it like this: + +bool SetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception); + +If this function returns false, the set request forwards to object's statically declared properties, then its parent class chain (which includes the default object class). +*/ +typedef bool +(*JSObjectSetPropertyCallback) (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception); + +/*! +@typedef JSObjectDeletePropertyCallback +@abstract The callback invoked when deleting a property. +@param ctx The execution context to use. +@param object The JSObject in which to delete the property. +@param propertyName A JSString containing the name of the property to delete. +@param exception A pointer to a JSValueRef in which to return an exception, if any. +@result true if propertyName was successfully deleted, otherwise false. +@discussion If you named your function DeleteProperty, you would declare it like this: + +bool DeleteProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); + +If this function returns false, the delete request forwards to object's statically declared properties, then its parent class chain (which includes the default object class). +*/ +typedef bool +(*JSObjectDeletePropertyCallback) (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); + +/*! +@typedef JSObjectGetPropertyNamesCallback +@abstract The callback invoked when collecting the names of an object's properties. +@param ctx The execution context to use. +@param object The JSObject whose property names are being collected. +@param propertyNames A JavaScript property name accumulator in which to accumulate the names of object's properties. +@discussion If you named your function GetPropertyNames, you would declare it like this: + +void GetPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames); + +Property name accumulators are used by JSObjectCopyPropertyNames and JavaScript for...in loops. + +Use JSPropertyNameAccumulatorAddName to add property names to accumulator. A class's getPropertyNames callback only needs to provide the names of properties that the class vends through a custom getProperty or setProperty callback. Other properties, including statically declared properties, properties vended by other classes, and properties belonging to object's prototype, are added independently. +*/ +typedef void +(*JSObjectGetPropertyNamesCallback) (JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames); + +/*! +@typedef JSObjectCallAsFunctionCallback +@abstract The callback invoked when an object is called as a function. +@param ctx The execution context to use. +@param function A JSObject that is the function being called. +@param thisObject A JSObject that is the 'this' variable in the function's scope. +@param argumentCount An integer count of the number of arguments in arguments. +@param arguments A JSValue array of the arguments passed to the function. +@param exception A pointer to a JSValueRef in which to return an exception, if any. +@result A JSValue that is the function's return value. +@discussion If you named your function CallAsFunction, you would declare it like this: + +JSValueRef CallAsFunction(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); + +If your callback were invoked by the JavaScript expression 'myObject.myFunction()', function would be set to myFunction, and thisObject would be set to myObject. + +If this callback is NULL, calling your object as a function will throw an exception. +*/ +typedef JSValueRef +(*JSObjectCallAsFunctionCallback) (JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); + +/*! +@typedef JSObjectCallAsConstructorCallback +@abstract The callback invoked when an object is used as a constructor in a 'new' expression. +@param ctx The execution context to use. +@param constructor A JSObject that is the constructor being called. +@param argumentCount An integer count of the number of arguments in arguments. +@param arguments A JSValue array of the arguments passed to the function. +@param exception A pointer to a JSValueRef in which to return an exception, if any. +@result A JSObject that is the constructor's return value. +@discussion If you named your function CallAsConstructor, you would declare it like this: + +JSObjectRef CallAsConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); + +If your callback were invoked by the JavaScript expression 'new myConstructor()', constructor would be set to myConstructor. + +If this callback is NULL, using your object as a constructor in a 'new' expression will throw an exception. +*/ +typedef JSObjectRef +(*JSObjectCallAsConstructorCallback) (JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); + +/*! +@typedef JSObjectHasInstanceCallback +@abstract hasInstance The callback invoked when an object is used as the target of an 'instanceof' expression. +@param ctx The execution context to use. +@param constructor The JSObject that is the target of the 'instanceof' expression. +@param possibleInstance The JSValue being tested to determine if it is an instance of constructor. +@param exception A pointer to a JSValueRef in which to return an exception, if any. +@result true if possibleInstance is an instance of constructor, otherwise false. +@discussion If you named your function HasInstance, you would declare it like this: + +bool HasInstance(JSContextRef ctx, JSObjectRef constructor, JSValueRef possibleInstance, JSValueRef* exception); + +If your callback were invoked by the JavaScript expression 'someValue instanceof myObject', constructor would be set to myObject and possibleInstance would be set to someValue. + +If this callback is NULL, 'instanceof' expressions that target your object will return false. + +Standard JavaScript practice calls for objects that implement the callAsConstructor callback to implement the hasInstance callback as well. +*/ +typedef bool +(*JSObjectHasInstanceCallback) (JSContextRef ctx, JSObjectRef constructor, JSValueRef possibleInstance, JSValueRef* exception); + +/*! +@typedef JSObjectConvertToTypeCallback +@abstract The callback invoked when converting an object to a particular JavaScript type. +@param ctx The execution context to use. +@param object The JSObject to convert. +@param type A JSType specifying the JavaScript type to convert to. +@param exception A pointer to a JSValueRef in which to return an exception, if any. +@result The objects's converted value, or NULL if the object was not converted. +@discussion If you named your function ConvertToType, you would declare it like this: + +JSValueRef ConvertToType(JSContextRef ctx, JSObjectRef object, JSType type, JSValueRef* exception); + +If this function returns false, the conversion request forwards to object's parent class chain (which includes the default object class). + +This function is only invoked when converting an object to number or string. An object converted to boolean is 'true.' An object converted to object is itself. +*/ +typedef JSValueRef +(*JSObjectConvertToTypeCallback) (JSContextRef ctx, JSObjectRef object, JSType type, JSValueRef* exception); + +/*! +@struct JSStaticValue +@abstract This structure describes a statically declared value property. +@field name A null-terminated UTF8 string containing the property's name. +@field getProperty A JSObjectGetPropertyCallback to invoke when getting the property's value. +@field setProperty A JSObjectSetPropertyCallback to invoke when setting the property's value. May be NULL if the ReadOnly attribute is set. +@field attributes A logically ORed set of JSPropertyAttributes to give to the property. +*/ +typedef struct { + const char* name; + JSObjectGetPropertyCallback getProperty; + JSObjectSetPropertyCallback setProperty; + JSPropertyAttributes attributes; +} JSStaticValue; + +/*! +@struct JSStaticFunction +@abstract This structure describes a statically declared function property. +@field name A null-terminated UTF8 string containing the property's name. +@field callAsFunction A JSObjectCallAsFunctionCallback to invoke when the property is called as a function. +@field attributes A logically ORed set of JSPropertyAttributes to give to the property. +*/ +typedef struct { + const char* name; + JSObjectCallAsFunctionCallback callAsFunction; + JSPropertyAttributes attributes; +} JSStaticFunction; + +/*! +@struct JSClassDefinition +@abstract This structure contains properties and callbacks that define a type of object. All fields other than the version field are optional. Any pointer may be NULL. +@field version The version number of this structure. The current version is 0. +@field attributes A logically ORed set of JSClassAttributes to give to the class. +@field className A null-terminated UTF8 string containing the class's name. +@field parentClass A JSClass to set as the class's parent class. Pass NULL use the default object class. +@field staticValues A JSStaticValue array containing the class's statically declared value properties. Pass NULL to specify no statically declared value properties. The array must be terminated by a JSStaticValue whose name field is NULL. +@field staticFunctions A JSStaticFunction array containing the class's statically declared function properties. Pass NULL to specify no statically declared function properties. The array must be terminated by a JSStaticFunction whose name field is NULL. +@field initialize The callback invoked when an object is first created. Use this callback to initialize the object. +@field finalize The callback invoked when an object is finalized (prepared for garbage collection). Use this callback to release resources allocated for the object, and perform other cleanup. +@field hasProperty The callback invoked when determining whether an object has a property. If this field is NULL, getProperty is called instead. The hasProperty callback enables optimization in cases where only a property's existence needs to be known, not its value, and computing its value is expensive. +@field getProperty The callback invoked when getting a property's value. +@field setProperty The callback invoked when setting a property's value. +@field deleteProperty The callback invoked when deleting a property. +@field getPropertyNames The callback invoked when collecting the names of an object's properties. +@field callAsFunction The callback invoked when an object is called as a function. +@field hasInstance The callback invoked when an object is used as the target of an 'instanceof' expression. +@field callAsConstructor The callback invoked when an object is used as a constructor in a 'new' expression. +@field convertToType The callback invoked when converting an object to a particular JavaScript type. +@discussion The staticValues and staticFunctions arrays are the simplest and most efficient means for vending custom properties. Statically declared properties autmatically service requests like getProperty, setProperty, and getPropertyNames. Property access callbacks are required only to implement unusual properties, like array indexes, whose names are not known at compile-time. + +If you named your getter function "GetX" and your setter function "SetX", you would declare a JSStaticValue array containing "X" like this: + +JSStaticValue StaticValueArray[] = { + { "X", GetX, SetX, kJSPropertyAttributeNone }, + { 0, 0, 0, 0 } +}; + +Standard JavaScript practice calls for storing function objects in prototypes, so they can be shared. The default JSClass created by JSClassCreate follows this idiom, instantiating objects with a shared, automatically generating prototype containing the class's function objects. The kJSClassAttributeNoAutomaticPrototype attribute specifies that a JSClass should not automatically generate such a prototype. The resulting JSClass instantiates objects with the default object prototype, and gives each instance object its own copy of the class's function objects. + +A NULL callback specifies that the default object callback should substitute, except in the case of hasProperty, where it specifies that getProperty should substitute. +*/ +typedef struct { + int version; /* current (and only) version is 0 */ + JSClassAttributes attributes; + + const char* className; + JSClassRef parentClass; + + const JSStaticValue* staticValues; + const JSStaticFunction* staticFunctions; + + JSObjectInitializeCallback initialize; + JSObjectFinalizeCallback finalize; + JSObjectHasPropertyCallback hasProperty; + JSObjectGetPropertyCallback getProperty; + JSObjectSetPropertyCallback setProperty; + JSObjectDeletePropertyCallback deleteProperty; + JSObjectGetPropertyNamesCallback getPropertyNames; + JSObjectCallAsFunctionCallback callAsFunction; + JSObjectCallAsConstructorCallback callAsConstructor; + JSObjectHasInstanceCallback hasInstance; + JSObjectConvertToTypeCallback convertToType; +} JSClassDefinition; + +/*! +@const kJSClassDefinitionEmpty +@abstract A JSClassDefinition structure of the current version, filled with NULL pointers and having no attributes. +@discussion Use this constant as a convenience when creating class definitions. For example, to create a class definition with only a finalize method: + +JSClassDefinition definition = kJSClassDefinitionEmpty; +definition.finalize = Finalize; +*/ +JS_EXPORT extern const JSClassDefinition kJSClassDefinitionEmpty; + +/*! +@function +@abstract Creates a JavaScript class suitable for use with JSObjectMake. +@param definition A JSClassDefinition that defines the class. +@result A JSClass with the given definition. Ownership follows the Create Rule. +*/ +JS_EXPORT JSClassRef JSClassCreate(const JSClassDefinition* definition); + +/*! +@function +@abstract Retains a JavaScript class. +@param jsClass The JSClass to retain. +@result A JSClass that is the same as jsClass. +*/ +JS_EXPORT JSClassRef JSClassRetain(JSClassRef jsClass); + +/*! +@function +@abstract Releases a JavaScript class. +@param jsClass The JSClass to release. +*/ +JS_EXPORT void JSClassRelease(JSClassRef jsClass); + +/*! +@function +@abstract Creates a JavaScript object. +@param ctx The execution context to use. +@param jsClass The JSClass to assign to the object. Pass NULL to use the default object class. +@param data A void* to set as the object's private data. Pass NULL to specify no private data. +@result A JSObject with the given class and private data. +@discussion The default object class does not allocate storage for private data, so you must provide a non-NULL jsClass to JSObjectMake if you want your object to be able to store private data. + +data is set on the created object before the intialize methods in its class chain are called. This enables the initialize methods to retrieve and manipulate data through JSObjectGetPrivate. +*/ +JS_EXPORT JSObjectRef JSObjectMake(JSContextRef ctx, JSClassRef jsClass, void* data); + +/*! +@function +@abstract Convenience method for creating a JavaScript function with a given callback as its implementation. +@param ctx The execution context to use. +@param name A JSString containing the function's name. This will be used when converting the function to string. Pass NULL to create an anonymous function. +@param callAsFunction The JSObjectCallAsFunctionCallback to invoke when the function is called. +@result A JSObject that is a function. The object's prototype will be the default function prototype. +*/ +JS_EXPORT JSObjectRef JSObjectMakeFunctionWithCallback(JSContextRef ctx, JSStringRef name, JSObjectCallAsFunctionCallback callAsFunction); + +/*! +@function +@abstract Convenience method for creating a JavaScript constructor. +@param ctx The execution context to use. +@param jsClass A JSClass that is the class your constructor will assign to the objects its constructs. jsClass will be used to set the constructor's .prototype property, and to evaluate 'instanceof' expressions. Pass NULL to use the default object class. +@param callAsConstructor A JSObjectCallAsConstructorCallback to invoke when your constructor is used in a 'new' expression. Pass NULL to use the default object constructor. +@result A JSObject that is a constructor. The object's prototype will be the default object prototype. +@discussion The default object constructor takes no arguments and constructs an object of class jsClass with no private data. +*/ +JS_EXPORT JSObjectRef JSObjectMakeConstructor(JSContextRef ctx, JSClassRef jsClass, JSObjectCallAsConstructorCallback callAsConstructor); + +/*! + @function + @abstract Creates a JavaScript Array object. + @param ctx The execution context to use. + @param argumentCount An integer count of the number of arguments in arguments. + @param arguments A JSValue array of data to populate the Array with. Pass NULL if argumentCount is 0. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObject that is an Array. + @discussion The behavior of this function does not exactly match the behavior of the built-in Array constructor. Specifically, if one argument + is supplied, this function returns an array with one element. + */ +JS_EXPORT JSObjectRef JSObjectMakeArray(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! + @function + @abstract Creates a JavaScript Date object, as if by invoking the built-in Date constructor. + @param ctx The execution context to use. + @param argumentCount An integer count of the number of arguments in arguments. + @param arguments A JSValue array of arguments to pass to the Date Constructor. Pass NULL if argumentCount is 0. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObject that is a Date. + */ +JS_EXPORT JSObjectRef JSObjectMakeDate(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! + @function + @abstract Creates a JavaScript Error object, as if by invoking the built-in Error constructor. + @param ctx The execution context to use. + @param argumentCount An integer count of the number of arguments in arguments. + @param arguments A JSValue array of arguments to pass to the Error Constructor. Pass NULL if argumentCount is 0. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObject that is an Error. + */ +JS_EXPORT JSObjectRef JSObjectMakeError(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! + @function + @abstract Creates a JavaScript RegExp object, as if by invoking the built-in RegExp constructor. + @param ctx The execution context to use. + @param argumentCount An integer count of the number of arguments in arguments. + @param arguments A JSValue array of arguments to pass to the RegExp Constructor. Pass NULL if argumentCount is 0. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObject that is a RegExp. + */ +JS_EXPORT JSObjectRef JSObjectMakeRegExp(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) JSC_API_AVAILABLE(macos(10.6), ios(7.0)); + +/*! + @function + @abstract Creates a JavaScript promise object by invoking the provided executor. + @param ctx The execution context to use. + @param resolve A pointer to a JSObjectRef in which to store the resolve function for the new promise. Pass NULL if you do not care to store the resolve callback. + @param reject A pointer to a JSObjectRef in which to store the reject function for the new promise. Pass NULL if you do not care to store the reject callback. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObject that is a promise or NULL if an exception occurred. + */ +JS_EXPORT JSObjectRef JSObjectMakeDeferredPromise(JSContextRef ctx, JSObjectRef* resolve, JSObjectRef* reject, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! +@function +@abstract Creates a function with a given script as its body. +@param ctx The execution context to use. +@param name A JSString containing the function's name. This will be used when converting the function to string. Pass NULL to create an anonymous function. +@param parameterCount An integer count of the number of parameter names in parameterNames. +@param parameterNames A JSString array containing the names of the function's parameters. Pass NULL if parameterCount is 0. +@param body A JSString containing the script to use as the function's body. +@param sourceURL A JSString containing a URL for the script's source file. This is only used when reporting exceptions. Pass NULL if you do not care to include source file information in exceptions. +@param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. The value is one-based, so the first line is line 1 and invalid values are clamped to 1. +@param exception A pointer to a JSValueRef in which to store a syntax error exception, if any. Pass NULL if you do not care to store a syntax error exception. +@result A JSObject that is a function, or NULL if either body or parameterNames contains a syntax error. The object's prototype will be the default function prototype. +@discussion Use this method when you want to execute a script repeatedly, to avoid the cost of re-parsing the script before each execution. +*/ +JS_EXPORT JSObjectRef JSObjectMakeFunction(JSContextRef ctx, JSStringRef name, unsigned parameterCount, const JSStringRef parameterNames[], JSStringRef body, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception); + +/*! +@function +@abstract Gets an object's prototype. +@param ctx The execution context to use. +@param object A JSObject whose prototype you want to get. +@result A JSValue that is the object's prototype. +*/ +JS_EXPORT JSValueRef JSObjectGetPrototype(JSContextRef ctx, JSObjectRef object); + +/*! +@function +@abstract Sets an object's prototype. +@param ctx The execution context to use. +@param object The JSObject whose prototype you want to set. +@param value A JSValue to set as the object's prototype. +*/ +JS_EXPORT void JSObjectSetPrototype(JSContextRef ctx, JSObjectRef object, JSValueRef value); + +/*! +@function +@abstract Tests whether an object has a given property. +@param object The JSObject to test. +@param propertyName A JSString containing the property's name. +@result true if the object has a property whose name matches propertyName, otherwise false. +*/ +JS_EXPORT bool JSObjectHasProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName); + +/*! +@function +@abstract Gets a property from an object. +@param ctx The execution context to use. +@param object The JSObject whose property you want to get. +@param propertyName A JSString containing the property's name. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result The property's value if object has the property, otherwise the undefined value. +*/ +JS_EXPORT JSValueRef JSObjectGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); + +/*! +@function +@abstract Sets a property on an object. +@param ctx The execution context to use. +@param object The JSObject whose property you want to set. +@param propertyName A JSString containing the property's name. +@param value A JSValueRef to use as the property's value. +@param attributes A logically ORed set of JSPropertyAttributes to give to the property. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +*/ +JS_EXPORT void JSObjectSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSPropertyAttributes attributes, JSValueRef* exception); + +/*! +@function +@abstract Deletes a property from an object. +@param ctx The execution context to use. +@param object The JSObject whose property you want to delete. +@param propertyName A JSString containing the property's name. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result true if the delete operation succeeds, otherwise false (for example, if the property has the kJSPropertyAttributeDontDelete attribute set). +*/ +JS_EXPORT bool JSObjectDeleteProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); + +/*! + @function + @abstract Tests whether an object has a given property using a JSValueRef as the property key. + @param object The JSObject to test. + @param propertyKey A JSValueRef containing the property key to use when looking up the property. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result true if the object has a property whose name matches propertyKey, otherwise false. + @discussion This function is the same as performing "propertyKey in object" from JavaScript. + */ +JS_EXPORT bool JSObjectHasPropertyForKey(JSContextRef ctx, JSObjectRef object, JSValueRef propertyKey, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! + @function + @abstract Gets a property from an object using a JSValueRef as the property key. + @param ctx The execution context to use. + @param object The JSObject whose property you want to get. + @param propertyKey A JSValueRef containing the property key to use when looking up the property. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result The property's value if object has the property key, otherwise the undefined value. + @discussion This function is the same as performing "object[propertyKey]" from JavaScript. + */ +JS_EXPORT JSValueRef JSObjectGetPropertyForKey(JSContextRef ctx, JSObjectRef object, JSValueRef propertyKey, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! + @function + @abstract Sets a property on an object using a JSValueRef as the property key. + @param ctx The execution context to use. + @param object The JSObject whose property you want to set. + @param propertyKey A JSValueRef containing the property key to use when looking up the property. + @param value A JSValueRef to use as the property's value. + @param attributes A logically ORed set of JSPropertyAttributes to give to the property. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @discussion This function is the same as performing "object[propertyKey] = value" from JavaScript. + */ +JS_EXPORT void JSObjectSetPropertyForKey(JSContextRef ctx, JSObjectRef object, JSValueRef propertyKey, JSValueRef value, JSPropertyAttributes attributes, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! + @function + @abstract Deletes a property from an object using a JSValueRef as the property key. + @param ctx The execution context to use. + @param object The JSObject whose property you want to delete. + @param propertyKey A JSValueRef containing the property key to use when looking up the property. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result true if the delete operation succeeds, otherwise false (for example, if the property has the kJSPropertyAttributeDontDelete attribute set). + @discussion This function is the same as performing "delete object[propertyKey]" from JavaScript. + */ +JS_EXPORT bool JSObjectDeletePropertyForKey(JSContextRef ctx, JSObjectRef object, JSValueRef propertyKey, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! +@function +@abstract Gets a property from an object by numeric index. +@param ctx The execution context to use. +@param object The JSObject whose property you want to get. +@param propertyIndex An integer value that is the property's name. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result The property's value if object has the property, otherwise the undefined value. +@discussion Calling JSObjectGetPropertyAtIndex is equivalent to calling JSObjectGetProperty with a string containing propertyIndex, but JSObjectGetPropertyAtIndex provides optimized access to numeric properties. +*/ +JS_EXPORT JSValueRef JSObjectGetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned propertyIndex, JSValueRef* exception); + +/*! +@function +@abstract Sets a property on an object by numeric index. +@param ctx The execution context to use. +@param object The JSObject whose property you want to set. +@param propertyIndex The property's name as a number. +@param value A JSValue to use as the property's value. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@discussion Calling JSObjectSetPropertyAtIndex is equivalent to calling JSObjectSetProperty with a string containing propertyIndex, but JSObjectSetPropertyAtIndex provides optimized access to numeric properties. +*/ +JS_EXPORT void JSObjectSetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned propertyIndex, JSValueRef value, JSValueRef* exception); + +/*! +@function +@abstract Gets an object's private data. +@param object A JSObject whose private data you want to get. +@result A void* that is the object's private data, if the object has private data, otherwise NULL. +*/ +JS_EXPORT void* JSObjectGetPrivate(JSObjectRef object); + +/*! +@function +@abstract Sets a pointer to private data on an object. +@param object The JSObject whose private data you want to set. +@param data A void* to set as the object's private data. +@result true if object can store private data, otherwise false. +@discussion The default object class does not allocate storage for private data. Only objects created with a non-NULL JSClass can store private data. +*/ +JS_EXPORT bool JSObjectSetPrivate(JSObjectRef object, void* data); + +/*! +@function +@abstract Tests whether an object can be called as a function. +@param ctx The execution context to use. +@param object The JSObject to test. +@result true if the object can be called as a function, otherwise false. +*/ +JS_EXPORT bool JSObjectIsFunction(JSContextRef ctx, JSObjectRef object); + +/*! +@function +@abstract Calls an object as a function. +@param ctx The execution context to use. +@param object The JSObject to call as a function. +@param thisObject The object to use as "this," or NULL to use the global object as "this." +@param argumentCount An integer count of the number of arguments in arguments. +@param arguments A JSValue array of arguments to pass to the function. Pass NULL if argumentCount is 0. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result The JSValue that results from calling object as a function, or NULL if an exception is thrown or object is not a function. +*/ +JS_EXPORT JSValueRef JSObjectCallAsFunction(JSContextRef ctx, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); + +/*! +@function +@abstract Tests whether an object can be called as a constructor. +@param ctx The execution context to use. +@param object The JSObject to test. +@result true if the object can be called as a constructor, otherwise false. +*/ +JS_EXPORT bool JSObjectIsConstructor(JSContextRef ctx, JSObjectRef object); + +/*! +@function +@abstract Calls an object as a constructor. +@param ctx The execution context to use. +@param object The JSObject to call as a constructor. +@param argumentCount An integer count of the number of arguments in arguments. +@param arguments A JSValue array of arguments to pass to the constructor. Pass NULL if argumentCount is 0. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result The JSObject that results from calling object as a constructor, or NULL if an exception is thrown or object is not a constructor. +*/ +JS_EXPORT JSObjectRef JSObjectCallAsConstructor(JSContextRef ctx, JSObjectRef object, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); + +/*! +@function +@abstract Gets the names of an object's enumerable properties. +@param ctx The execution context to use. +@param object The object whose property names you want to get. +@result A JSPropertyNameArray containing the names object's enumerable properties. Ownership follows the Create Rule. +*/ +JS_EXPORT JSPropertyNameArrayRef JSObjectCopyPropertyNames(JSContextRef ctx, JSObjectRef object); + +/*! +@function +@abstract Retains a JavaScript property name array. +@param array The JSPropertyNameArray to retain. +@result A JSPropertyNameArray that is the same as array. +*/ +JS_EXPORT JSPropertyNameArrayRef JSPropertyNameArrayRetain(JSPropertyNameArrayRef array); + +/*! +@function +@abstract Releases a JavaScript property name array. +@param array The JSPropetyNameArray to release. +*/ +JS_EXPORT void JSPropertyNameArrayRelease(JSPropertyNameArrayRef array); + +/*! +@function +@abstract Gets a count of the number of items in a JavaScript property name array. +@param array The array from which to retrieve the count. +@result An integer count of the number of names in array. +*/ +JS_EXPORT size_t JSPropertyNameArrayGetCount(JSPropertyNameArrayRef array); + +/*! +@function +@abstract Gets a property name at a given index in a JavaScript property name array. +@param array The array from which to retrieve the property name. +@param index The index of the property name to retrieve. +@result A JSStringRef containing the property name. +*/ +JS_EXPORT JSStringRef JSPropertyNameArrayGetNameAtIndex(JSPropertyNameArrayRef array, size_t index); + +/*! +@function +@abstract Adds a property name to a JavaScript property name accumulator. +@param accumulator The accumulator object to which to add the property name. +@param propertyName The property name to add. +*/ +JS_EXPORT void JSPropertyNameAccumulatorAddName(JSPropertyNameAccumulatorRef accumulator, JSStringRef propertyName); + +#ifdef __cplusplus +} +#endif + +#endif /* JSObjectRef_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSObjectRefPrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSObjectRefPrivate.h new file mode 100644 index 000000000..6e32612e3 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSObjectRefPrivate.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSObjectRefPrivate_h +#define JSObjectRefPrivate_h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + @function + @abstract Sets a private property on an object. This private property cannot be accessed from within JavaScript. + @param ctx The execution context to use. + @param object The JSObject whose private property you want to set. + @param propertyName A JSString containing the property's name. + @param value A JSValue to use as the property's value. This may be NULL. + @result true if object can store private data, otherwise false. + @discussion This API allows you to store JS values directly an object in a way that will be ensure that they are kept alive without exposing them to JavaScript code and without introducing the reference cycles that may occur when using JSValueProtect. + + The default object class does not allocate storage for private data. Only objects created with a non-NULL JSClass can store private properties. + */ +JS_EXPORT bool JSObjectSetPrivateProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value); + +/*! + @function + @abstract Gets a private property from an object. + @param ctx The execution context to use. + @param object The JSObject whose private property you want to get. + @param propertyName A JSString containing the property's name. + @result The property's value if object has the property, otherwise NULL. + */ +JS_EXPORT JSValueRef JSObjectGetPrivateProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName); + +/*! + @function + @abstract Deletes a private property from an object. + @param ctx The execution context to use. + @param object The JSObject whose private property you want to delete. + @param propertyName A JSString containing the property's name. + @result true if object can store private data, otherwise false. + @discussion The default object class does not allocate storage for private data. Only objects created with a non-NULL JSClass can store private data. + */ +JS_EXPORT bool JSObjectDeletePrivateProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName); + +JS_EXPORT JSObjectRef JSObjectGetProxyTarget(JSObjectRef); + +JS_EXPORT JSGlobalContextRef JSObjectGetGlobalContext(JSObjectRef object); + +#ifdef __cplusplus +} +#endif + +#endif // JSObjectRefPrivate_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSRemoteInspector.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSRemoteInspector.h new file mode 100644 index 000000000..85768e4d6 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSRemoteInspector.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSRemoteInspector_h +#define JSRemoteInspector_h + +#include +#include + +#if defined(WIN32) || defined(_WIN32) +typedef int JSProcessID; +#else +#include +typedef pid_t JSProcessID; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +JS_EXPORT void JSRemoteInspectorDisableAutoStart(void) JSC_API_AVAILABLE(macos(10.11), ios(9.0)); +JS_EXPORT void JSRemoteInspectorStart(void) JSC_API_AVAILABLE(macos(10.11), ios(9.0)); +JS_EXPORT void JSRemoteInspectorSetParentProcessInformation(JSProcessID, const uint8_t* auditData, size_t auditLength) JSC_API_AVAILABLE(macos(10.11), ios(9.0)); + +JS_EXPORT void JSRemoteInspectorSetLogToSystemConsole(bool) JSC_API_AVAILABLE(macos(10.11), ios(9.0)); + +JS_EXPORT bool JSRemoteInspectorGetInspectionEnabledByDefault(void) JSC_API_AVAILABLE(macos(10.11), ios(9.0)); +JS_EXPORT void JSRemoteInspectorSetInspectionEnabledByDefault(bool) JSC_API_AVAILABLE(macos(10.11), ios(9.0)); + +#ifdef __cplusplus +} +#endif + +#endif /* JSRemoteInspector_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSRetainPtr.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSRetainPtr.h new file mode 100644 index 000000000..fd8412f3e --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSRetainPtr.h @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2005-2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include + +inline void JSRetain(JSStringRef string) { JSStringRetain(string); } +inline void JSRelease(JSStringRef string) { JSStringRelease(string); } +inline void JSRetain(JSGlobalContextRef context) { JSGlobalContextRetain(context); } +inline void JSRelease(JSGlobalContextRef context) { JSGlobalContextRelease(context); } + +enum AdoptTag { Adopt }; + +template class JSRetainPtr { +public: + JSRetainPtr() = default; + JSRetainPtr(T ptr) : m_ptr(ptr) { if (ptr) JSRetain(ptr); } + JSRetainPtr(const JSRetainPtr&); + JSRetainPtr(JSRetainPtr&&); + ~JSRetainPtr(); + + T get() const { return m_ptr; } + + void clear(); + T leakRef() WARN_UNUSED_RETURN; + + T operator->() const { return m_ptr; } + + bool operator!() const { return !m_ptr; } + explicit operator bool() const { return m_ptr; } + + JSRetainPtr& operator=(const JSRetainPtr&); + JSRetainPtr& operator=(JSRetainPtr&&); + JSRetainPtr& operator=(T); + + void swap(JSRetainPtr&); + + friend JSRetainPtr adopt(JSStringRef); + friend JSRetainPtr adopt(JSGlobalContextRef); + + // FIXME: Make this private once Apple's internal code is updated to not rely on it. + // https://bugs.webkit.org/show_bug.cgi?id=189644 + JSRetainPtr(AdoptTag, T); + +private: + T m_ptr { nullptr }; +}; + +JSRetainPtr adopt(JSStringRef); +JSRetainPtr adopt(JSGlobalContextRef); + +template inline JSRetainPtr::JSRetainPtr(AdoptTag, T ptr) + : m_ptr(ptr) +{ +} + +inline JSRetainPtr adopt(JSStringRef o) +{ + return JSRetainPtr(Adopt, o); +} + +inline JSRetainPtr adopt(JSGlobalContextRef o) +{ + return JSRetainPtr(Adopt, o); +} + +template inline JSRetainPtr::JSRetainPtr(const JSRetainPtr& o) + : m_ptr(o.m_ptr) +{ + if (m_ptr) + JSRetain(m_ptr); +} + +template inline JSRetainPtr::JSRetainPtr(JSRetainPtr&& o) + : m_ptr(o.leakRef()) +{ +} + +template inline JSRetainPtr::~JSRetainPtr() +{ + if (m_ptr) + JSRelease(m_ptr); +} + +template inline void JSRetainPtr::clear() +{ + if (T ptr = leakRef()) + JSRelease(ptr); +} + +template inline T JSRetainPtr::leakRef() +{ + return std::exchange(m_ptr, nullptr); +} + +template inline JSRetainPtr& JSRetainPtr::operator=(const JSRetainPtr& o) +{ + return operator=(o.get()); +} + +template inline JSRetainPtr& JSRetainPtr::operator=(JSRetainPtr&& o) +{ + if (T ptr = std::exchange(m_ptr, o.leakRef())) + JSRelease(ptr); + return *this; +} + +template inline JSRetainPtr& JSRetainPtr::operator=(T optr) +{ + if (optr) + JSRetain(optr); + if (T ptr = std::exchange(m_ptr, optr)) + JSRelease(ptr); + return *this; +} + +template inline void JSRetainPtr::swap(JSRetainPtr& o) +{ + std::swap(m_ptr, o.m_ptr); +} + +template inline void swap(JSRetainPtr& a, JSRetainPtr& b) +{ + a.swap(b); +} + +template inline bool operator==(const JSRetainPtr& a, const JSRetainPtr& b) +{ + return a.get() == b.get(); +} + +template inline bool operator==(const JSRetainPtr& a, U* b) +{ + return a.get() == b; +} + +template inline bool operator==(T* a, const JSRetainPtr& b) +{ + return a == b.get(); +} + +template inline bool operator!=(const JSRetainPtr& a, const JSRetainPtr& b) +{ + return a.get() != b.get(); +} + +template inline bool operator!=(const JSRetainPtr& a, U* b) +{ + return a.get() != b; +} + +template inline bool operator!=(T* a, const JSRetainPtr& b) +{ + return a != b.get(); +} diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScript.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScript.h new file mode 100644 index 000000000..12ccb0593 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScript.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +#if JSC_OBJC_API_ENABLED + +NS_ASSUME_NONNULL_BEGIN + +@class JSVirtualMachine; + +/*! + @enum JSScriptType + @abstract A constant identifying the execution type of a JSScript. + @constant kJSScriptTypeProgram The type of a normal JavaScript program. + @constant kJSScriptTypeModule The type of a module JavaScript program. + */ +typedef NS_ENUM(NSInteger, JSScriptType) { + kJSScriptTypeProgram, + kJSScriptTypeModule, +}; + + +JSC_CLASS_AVAILABLE(macos(10.15), ios(13.0)) +@interface JSScript : NSObject + +/*! + @method + @abstract Create a JSScript for the specified virtual machine. + @param type The type of JavaScript source. + @param source The source code to use when the script is evaluated by the JS vm. + @param sourceURL The source URL to associate with this script. For modules, this is the module identifier. + @param cachePath A URL containing the path where the VM should cache for future execution. On creation, we use this path to load the cached bytecode off disk. If the cached bytecode at this location is stale, you should delete that file before calling this constructor. + @param vm The JSVirtualMachine the script can be evaluated in. + @param error A description of why the script could not be created if the result is nil. + @result The new script. + @discussion The file at cachePath should not be externally modified for the lifecycle of vm. + */ ++ (nullable instancetype)scriptOfType:(JSScriptType)type withSource:(NSString *)source andSourceURL:(NSURL *)sourceURL andBytecodeCache:(nullable NSURL *)cachePath inVirtualMachine:(JSVirtualMachine *)vm error:(out NSError * _Nullable * _Nullable)error; + +/*! + @method + @abstract Create a JSScript for the specified virtual machine with a path to a codesigning and bytecode caching. + @param type The type of JavaScript source. + @param filePath A URL containing the path to a JS source code file on disk. + @param sourceURL The source URL to associate with this script. For modules, this is the module identifier. + @param cachePath A URL containing the path where the VM should cache for future execution. On creation, we use this path to load the cached bytecode off disk. If the cached bytecode at this location is stale, you should delete that file before calling this constructor. + @param vm The JSVirtualMachine the script can be evaluated in. + @param error A description of why the script could not be created if the result is nil. + @result The new script. + @discussion The files at filePath and cachePath should not be externally modified for the lifecycle of vm. This method will file back the memory for the source. + + If the file at filePath is not ascii this method will return nil. + */ ++ (nullable instancetype)scriptOfType:(JSScriptType)type memoryMappedFromASCIIFile:(NSURL *)filePath withSourceURL:(NSURL *)sourceURL andBytecodeCache:(nullable NSURL *)cachePath inVirtualMachine:(JSVirtualMachine *)vm error:(out NSError * _Nullable * _Nullable)error; + +/*! + @method + @abstract Cache the bytecode for this JSScript to disk at the path passed in during creation. + @param error A description of why the script could not be cached if the result is FALSE. + */ +- (BOOL)cacheBytecodeWithError:(out NSError * _Nullable * _Nullable)error; + +/*! + @method + @abstract Returns true when evaluating this JSScript will use the bytecode cache. Returns false otherwise. + */ +- (BOOL)isUsingBytecodeCache; + +/*! + @method + @abstract Returns the JSScriptType of this JSScript. + */ +- (JSScriptType)type; + +/*! + @method + @abstract Returns the sourceURL of this JSScript. + */ +- (NSURL *)sourceURL; + +@end + +NS_ASSUME_NONNULL_END + +#endif // JSC_OBJC_API_ENABLED diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScriptInternal.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScriptInternal.h new file mode 100644 index 000000000..4a9427d28 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScriptInternal.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#import "JSScript.h" +#import "SourceCode.h" +#import + +#if JSC_OBJC_API_ENABLED + +NS_ASSUME_NONNULL_BEGIN + +namespace JSC { +class CachedBytecode; +class Identifier; +class JSSourceCode; +}; + +namespace WTF { +class String; +}; + +@interface JSScript(Internal) + +- (instancetype)init; +- (unsigned)hash; +- (const WTF::String&)source; +- (RefPtr)cachedBytecode; +- (JSC::JSSourceCode*)jsSourceCode; +- (JSC::SourceCode)sourceCode; +- (BOOL)writeCache:(String&)error; + +@end + +NS_ASSUME_NONNULL_END + +#endif // JSC_OBJC_API_ENABLED diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScriptRefPrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScriptRefPrivate.h new file mode 100644 index 000000000..e6224ce37 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScriptRefPrivate.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSScriptRefPrivate_h +#define JSScriptRefPrivate_h + +#include +#include +#include + +/*! @typedef JSScriptRef A JavaScript script reference. */ +typedef struct OpaqueJSScript* JSScriptRef; + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + @function + @abstract Creates a script reference from an ascii string, without copying or taking ownership of the string + @param contextGroup The context group the script is to be used in. + @param url The source url to be reported in errors and exceptions. + @param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. The value is one-based, so the first line is line 1 and invalid values are clamped to 1. + @param source The source string. This is required to be pure ASCII and to never be deallocated. + @param length The length of the source string. + @param errorMessage A pointer to a JSStringRef in which to store the parse error message if the source is not valid. Pass NULL if you do not care to store an error message. + @param errorLine A pointer to an int in which to store the line number of a parser error. Pass NULL if you do not care to store an error line. + @result A JSScriptRef for the provided source, or NULL if any non-ASCII character is found in source or if the source is not a valid JavaScript program. Ownership follows the Create Rule. + @discussion Use this function to create a reusable script reference with a constant + buffer as the backing string. The source string must outlive the global context. + */ +JS_EXPORT JSScriptRef JSScriptCreateReferencingImmortalASCIIText(JSContextGroupRef contextGroup, JSStringRef url, int startingLineNumber, const char* source, size_t length, JSStringRef* errorMessage, int* errorLine); + +/*! + @function + @abstract Creates a script reference from a string + @param contextGroup The context group the script is to be used in. + @param url The source url to be reported in errors and exceptions. + @param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. The value is one-based, so the first line is line 1 and invalid values are clamped to 1. + @param source The source string. + @param errorMessage A pointer to a JSStringRef in which to store the parse error message if the source is not valid. Pass NULL if you do not care to store an error message. + @param errorLine A pointer to an int in which to store the line number of a parser error. Pass NULL if you do not care to store an error line. + @result A JSScriptRef for the provided source, or NULL is the source is not a valid JavaScript program. Ownership follows the Create Rule. + */ +JS_EXPORT JSScriptRef JSScriptCreateFromString(JSContextGroupRef contextGroup, JSStringRef url, int startingLineNumber, JSStringRef source, JSStringRef* errorMessage, int* errorLine); + +/*! + @function + @abstract Retains a JavaScript script. + @param script The script to retain. + */ +JS_EXPORT void JSScriptRetain(JSScriptRef script); + +/*! + @function + @abstract Releases a JavaScript script. + @param script The script to release. + */ +JS_EXPORT void JSScriptRelease(JSScriptRef script); + +/*! + @function + @abstract Evaluates a JavaScript script. + @param ctx The execution context to use. + @param script The JSScript to evaluate. + @param thisValue The value to use as "this" when evaluating the script. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result The JSValue that results from evaluating script, or NULL if an exception is thrown. + */ +JS_EXPORT JSValueRef JSScriptEvaluate(JSContextRef ctx, JSScriptRef script, JSValueRef thisValue, JSValueRef* exception); + + +#ifdef __cplusplus +} +#endif + +#endif /* JSScriptRefPrivate_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScriptSourceProvider.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScriptSourceProvider.h new file mode 100644 index 000000000..09e4018dd --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSScriptSourceProvider.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if JSC_OBJC_API_ENABLED + +#import "SourceProvider.h" + +@class JSScript; + +class JSScriptSourceProvider : public JSC::SourceProvider { +public: + template + static Ref create(JSScript *script, Args&&... args) + { + return adoptRef(*new JSScriptSourceProvider(script, std::forward(args)...)); + } + + unsigned hash() const override; + StringView source() const override; + RefPtr cachedBytecode() const override; + +private: + template + JSScriptSourceProvider(JSScript *script, Args&&... args) + : SourceProvider(std::forward(args)...) + , m_script(script) + { } + + RetainPtr m_script; +}; + +#endif // JSC_OBJC_API_ENABLED diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRef.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRef.h new file mode 100644 index 000000000..bc03ed70d --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRef.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2006 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSStringRef_h +#define JSStringRef_h + +#include + +#ifndef __cplusplus +#include +#endif +#include /* for size_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(_NATIVE_WCHAR_T_DEFINED) /* MSVC */ \ + && (!defined(__WCHAR_MAX__) || (__WCHAR_MAX__ > 0xffffU)) /* ISO C/C++ */ \ + && (!defined(WCHAR_MAX) || (WCHAR_MAX > 0xffffU)) /* RVCT */ +/*! +@typedef JSChar +@abstract A UTF-16 code unit. One, or a sequence of two, can encode any Unicode + character. As with all scalar types, endianness depends on the underlying + architecture. +*/ + typedef unsigned short JSChar; +#else + typedef wchar_t JSChar; +#endif + +/*! +@function +@abstract Creates a JavaScript string from a buffer of Unicode characters. +@param chars The buffer of Unicode characters to copy into the new JSString. +@param numChars The number of characters to copy from the buffer pointed to by chars. +@result A JSString containing chars. Ownership follows the Create Rule. +*/ +JS_EXPORT JSStringRef JSStringCreateWithCharacters(const JSChar* chars, size_t numChars); +/*! +@function +@abstract Creates a JavaScript string from a null-terminated UTF8 string. +@param string The null-terminated UTF8 string to copy into the new JSString. +@result A JSString containing string. Ownership follows the Create Rule. +*/ +JS_EXPORT JSStringRef JSStringCreateWithUTF8CString(const char* string); + +/*! +@function +@abstract Retains a JavaScript string. +@param string The JSString to retain. +@result A JSString that is the same as string. +*/ +JS_EXPORT JSStringRef JSStringRetain(JSStringRef string); +/*! +@function +@abstract Releases a JavaScript string. +@param string The JSString to release. +*/ +JS_EXPORT void JSStringRelease(JSStringRef string); + +/*! +@function +@abstract Returns the number of Unicode characters in a JavaScript string. +@param string The JSString whose length (in Unicode characters) you want to know. +@result The number of Unicode characters stored in string. +*/ +JS_EXPORT size_t JSStringGetLength(JSStringRef string); +/*! +@function +@abstract Returns a pointer to the Unicode character buffer that + serves as the backing store for a JavaScript string. +@param string The JSString whose backing store you want to access. +@result A pointer to the Unicode character buffer that serves as string's + backing store, which will be deallocated when string is deallocated. +*/ +JS_EXPORT const JSChar* JSStringGetCharactersPtr(JSStringRef string); + +/*! +@function +@abstract Returns the maximum number of bytes a JavaScript string will + take up if converted into a null-terminated UTF8 string. +@param string The JSString whose maximum converted size (in bytes) you + want to know. +@result The maximum number of bytes that could be required to convert string into a + null-terminated UTF8 string. The number of bytes that the conversion actually ends + up requiring could be less than this, but never more. +*/ +JS_EXPORT size_t JSStringGetMaximumUTF8CStringSize(JSStringRef string); +/*! +@function +@abstract Converts a JavaScript string into a null-terminated UTF8 string, + and copies the result into an external byte buffer. +@param string The source JSString. +@param buffer The destination byte buffer into which to copy a null-terminated + UTF8 representation of string. On return, buffer contains a UTF8 string + representation of string. If bufferSize is too small, buffer will contain only + partial results. If buffer is not at least bufferSize bytes in size, + behavior is undefined. +@param bufferSize The size of the external buffer in bytes. +@result The number of bytes written into buffer (including the null-terminator byte). +*/ +JS_EXPORT size_t JSStringGetUTF8CString(JSStringRef string, char* buffer, size_t bufferSize); + +/*! +@function +@abstract Tests whether two JavaScript strings match. +@param a The first JSString to test. +@param b The second JSString to test. +@result true if the two strings match, otherwise false. +*/ +JS_EXPORT bool JSStringIsEqual(JSStringRef a, JSStringRef b); +/*! +@function +@abstract Tests whether a JavaScript string matches a null-terminated UTF8 string. +@param a The JSString to test. +@param b The null-terminated UTF8 string to test. +@result true if the two strings match, otherwise false. +*/ +JS_EXPORT bool JSStringIsEqualToUTF8CString(JSStringRef a, const char* b); + +#ifdef __cplusplus +} +#endif + +#endif /* JSStringRef_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRefBSTR.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRefBSTR.h new file mode 100644 index 000000000..066c68d53 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRefBSTR.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSStringRefBSTR_h +#define JSStringRefBSTR_h + +#include "JSBase.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* COM convenience methods */ + +/*! +@function +@abstract Creates a JavaScript string from a BSTR. +@param string The BSTR to copy into the new JSString. +@result A JSString containing string. Ownership follows the Create Rule. +*/ +JS_EXPORT JSStringRef JSStringCreateWithBSTR(const BSTR string); + +/*! +@function +@abstract Creates a BSTR from a JavaScript string. +@param string The JSString to copy into the new BSTR. +@result A BSTR containing string. Ownership follows the Create Rule. +*/ +JS_EXPORT BSTR JSStringCopyBSTR(const JSStringRef string); + +#ifdef __cplusplus +} +#endif + +#endif /* JSStringRefBSTR_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRefCF.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRefCF.h new file mode 100644 index 000000000..1e210c7a9 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRefCF.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSStringRefCF_h +#define JSStringRefCF_h + +#include "JSBase.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* CFString convenience methods */ + +/*! +@function +@abstract Creates a JavaScript string from a CFString. +@discussion This function is optimized to take advantage of cases when + CFStringGetCharactersPtr returns a valid pointer. +@param string The CFString to copy into the new JSString. +@result A JSString containing string. Ownership follows the Create Rule. +*/ +JS_EXPORT JSStringRef JSStringCreateWithCFString(CFStringRef string); +/*! +@function +@abstract Creates a CFString from a JavaScript string. +@param alloc The alloc parameter to pass to CFStringCreate. +@param string The JSString to copy into the new CFString. +@result A CFString containing string. Ownership follows the Create Rule. +*/ +JS_EXPORT CFStringRef JSStringCopyCFString(CFAllocatorRef alloc, JSStringRef string) CF_RETURNS_RETAINED; + +#ifdef __cplusplus +} +#endif + +#endif /* JSStringRefCF_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRefPrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRefPrivate.h new file mode 100644 index 000000000..f1db806ee --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSStringRefPrivate.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSStringRefPrivate_h +#define JSStringRefPrivate_h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +JS_EXPORT JSStringRef JSStringCreateWithCharactersNoCopy(const JSChar* chars, size_t numChars); + +#ifdef __cplusplus +} +#endif + +#endif /* JSStringRefPrivate_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSTypedArray.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSTypedArray.h new file mode 100644 index 000000000..7eaf76c5e --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSTypedArray.h @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2015 Dominic Szablewski (dominic@phoboslab.org) + * Copyright (C) 2015-2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSTypedArray_h +#define JSTypedArray_h + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------- Typed Array functions -------------- + +/*! + @function + @abstract Creates a JavaScript Typed Array object with the given number of elements. + @param ctx The execution context to use. + @param arrayType A value identifying the type of array to create. If arrayType is kJSTypedArrayTypeNone or kJSTypedArrayTypeArrayBuffer then NULL will be returned. + @param length The number of elements to be in the new Typed Array. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObjectRef that is a Typed Array with all elements set to zero or NULL if there was an error. + */ +JS_EXPORT JSObjectRef JSObjectMakeTypedArray(JSContextRef ctx, JSTypedArrayType arrayType, size_t length, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Creates a JavaScript Typed Array object from an existing pointer. + @param ctx The execution context to use. + @param arrayType A value identifying the type of array to create. If arrayType is kJSTypedArrayTypeNone or kJSTypedArrayTypeArrayBuffer then NULL will be returned. + @param bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object. + @param byteLength The number of bytes pointed to by the parameter bytes. + @param bytesDeallocator The allocator to use to deallocate the external buffer when the JSTypedArrayData object is deallocated. + @param deallocatorContext A pointer to pass back to the deallocator. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObjectRef Typed Array whose backing store is the same as the one pointed to by bytes or NULL if there was an error. + @discussion If an exception is thrown during this function the bytesDeallocator will always be called. + */ +JS_EXPORT JSObjectRef JSObjectMakeTypedArrayWithBytesNoCopy(JSContextRef ctx, JSTypedArrayType arrayType, void* bytes, size_t byteLength, JSTypedArrayBytesDeallocator bytesDeallocator, void* deallocatorContext, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Creates a JavaScript Typed Array object from an existing JavaScript Array Buffer object. + @param ctx The execution context to use. + @param arrayType A value identifying the type of array to create. If arrayType is kJSTypedArrayTypeNone or kJSTypedArrayTypeArrayBuffer then NULL will be returned. + @param buffer An Array Buffer object that should be used as the backing store for the created JavaScript Typed Array object. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObjectRef that is a Typed Array or NULL if there was an error. The backing store of the Typed Array will be buffer. + */ +JS_EXPORT JSObjectRef JSObjectMakeTypedArrayWithArrayBuffer(JSContextRef ctx, JSTypedArrayType arrayType, JSObjectRef buffer, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Creates a JavaScript Typed Array object from an existing JavaScript Array Buffer object with the given offset and length. + @param ctx The execution context to use. + @param arrayType A value identifying the type of array to create. If arrayType is kJSTypedArrayTypeNone or kJSTypedArrayTypeArrayBuffer then NULL will be returned. + @param buffer An Array Buffer object that should be used as the backing store for the created JavaScript Typed Array object. + @param byteOffset The byte offset for the created Typed Array. byteOffset should aligned with the element size of arrayType. + @param length The number of elements to include in the Typed Array. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObjectRef that is a Typed Array or NULL if there was an error. The backing store of the Typed Array will be buffer. + */ +JS_EXPORT JSObjectRef JSObjectMakeTypedArrayWithArrayBufferAndOffset(JSContextRef ctx, JSTypedArrayType arrayType, JSObjectRef buffer, size_t byteOffset, size_t length, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Returns a temporary pointer to the backing store of a JavaScript Typed Array object. + @param ctx The execution context to use. + @param object The Typed Array object whose backing store pointer to return. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A pointer to the raw data buffer that serves as object's backing store or NULL if object is not a Typed Array object. + @discussion The pointer returned by this function is temporary and is not guaranteed to remain valid across JavaScriptCore API calls. + */ +JS_EXPORT void* JSObjectGetTypedArrayBytesPtr(JSContextRef ctx, JSObjectRef object, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Returns the length of a JavaScript Typed Array object. + @param ctx The execution context to use. + @param object The Typed Array object whose length to return. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result The length of the Typed Array object or 0 if the object is not a Typed Array object. + */ +JS_EXPORT size_t JSObjectGetTypedArrayLength(JSContextRef ctx, JSObjectRef object, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Returns the byte length of a JavaScript Typed Array object. + @param ctx The execution context to use. + @param object The Typed Array object whose byte length to return. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result The byte length of the Typed Array object or 0 if the object is not a Typed Array object. + */ +JS_EXPORT size_t JSObjectGetTypedArrayByteLength(JSContextRef ctx, JSObjectRef object, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Returns the byte offset of a JavaScript Typed Array object. + @param ctx The execution context to use. + @param object The Typed Array object whose byte offset to return. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result The byte offset of the Typed Array object or 0 if the object is not a Typed Array object. + */ +JS_EXPORT size_t JSObjectGetTypedArrayByteOffset(JSContextRef ctx, JSObjectRef object, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Returns the JavaScript Array Buffer object that is used as the backing of a JavaScript Typed Array object. + @param ctx The execution context to use. + @param object The JSObjectRef whose Typed Array type data pointer to obtain. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObjectRef with a JSTypedArrayType of kJSTypedArrayTypeArrayBuffer or NULL if object is not a Typed Array. + */ +JS_EXPORT JSObjectRef JSObjectGetTypedArrayBuffer(JSContextRef ctx, JSObjectRef object, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +// ------------- Array Buffer functions ------------- + +/*! + @function + @abstract Creates a JavaScript Array Buffer object from an existing pointer. + @param ctx The execution context to use. + @param bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object. + @param byteLength The number of bytes pointed to by the parameter bytes. + @param bytesDeallocator The allocator to use to deallocate the external buffer when the Typed Array data object is deallocated. + @param deallocatorContext A pointer to pass back to the deallocator. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSObjectRef Array Buffer whose backing store is the same as the one pointed to by bytes or NULL if there was an error. + @discussion If an exception is thrown during this function the bytesDeallocator will always be called. + */ +JS_EXPORT JSObjectRef JSObjectMakeArrayBufferWithBytesNoCopy(JSContextRef ctx, void* bytes, size_t byteLength, JSTypedArrayBytesDeallocator bytesDeallocator, void* deallocatorContext, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Returns a pointer to the data buffer that serves as the backing store for a JavaScript Typed Array object. + @param object The Array Buffer object whose internal backing store pointer to return. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A pointer to the raw data buffer that serves as object's backing store or NULL if object is not an Array Buffer object. + @discussion The pointer returned by this function is temporary and is not guaranteed to remain valid across JavaScriptCore API calls. + */ +JS_EXPORT void* JSObjectGetArrayBufferBytesPtr(JSContextRef ctx, JSObjectRef object, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/*! + @function + @abstract Returns the number of bytes in a JavaScript data object. + @param ctx The execution context to use. + @param object The JS Arary Buffer object whose length in bytes to return. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result The number of bytes stored in the data object. + */ +JS_EXPORT size_t JSObjectGetArrayBufferByteLength(JSContextRef ctx, JSObjectRef object, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +#ifdef __cplusplus +} +#endif + +#endif /* JSTypedArray_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValue.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValue.h new file mode 100644 index 000000000..1b5845e9b --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValue.h @@ -0,0 +1,730 @@ +/* + * Copyright (C) 2013-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSValue_h +#define JSValue_h + +#if JSC_OBJC_API_ENABLED + +#import + +@class JSContext; + +/*! +@interface +@discussion A JSValue is a reference to a JavaScript value. Every JSValue + originates from a JSContext and holds a strong reference to it. + When a JSValue instance method creates a new JSValue, the new value + originates from the same JSContext. + + All JSValues values also originate from a JSVirtualMachine + (available indirectly via the context property). It is an error to pass a + JSValue to a method or property of a JSValue or JSContext originating from a + different JSVirtualMachine. Doing so will raise an Objective-C exception. +*/ +NS_CLASS_AVAILABLE(10_9, 7_0) +@interface JSValue : NSObject + +/*! +@property +@abstract The JSContext that this value originates from. +*/ +@property (readonly, strong) JSContext *context; + +/*! +@methodgroup Creating JavaScript Values +*/ +/*! +@method +@abstract Create a JSValue by converting an Objective-C object. +@discussion The resulting JSValue retains the provided Objective-C object. +@param value The Objective-C object to be converted. +@result The new JSValue. +*/ ++ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context; + +/*! +@method +@abstract Create a JavaScript value from a BOOL primitive. +@param context The JSContext in which the resulting JSValue will be created. +@result The new JSValue representing the equivalent boolean value. +*/ ++ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context; + +/*! +@method +@abstract Create a JavaScript value from a double primitive. +@param context The JSContext in which the resulting JSValue will be created. +@result The new JSValue representing the equivalent boolean value. +*/ ++ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context; + +/*! +@method +@abstract Create a JavaScript value from an int32_t primitive. +@param context The JSContext in which the resulting JSValue will be created. +@result The new JSValue representing the equivalent boolean value. +*/ ++ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context; + +/*! +@method +@abstract Create a JavaScript value from a uint32_t primitive. +@param context The JSContext in which the resulting JSValue will be created. +@result The new JSValue representing the equivalent boolean value. +*/ ++ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context; + +/*! +@method +@abstract Create a new, empty JavaScript object. +@param context The JSContext in which the resulting object will be created. +@result The new JavaScript object. +*/ ++ (JSValue *)valueWithNewObjectInContext:(JSContext *)context; + +/*! +@method +@abstract Create a new, empty JavaScript array. +@param context The JSContext in which the resulting array will be created. +@result The new JavaScript array. +*/ ++ (JSValue *)valueWithNewArrayInContext:(JSContext *)context; + +/*! +@method +@abstract Create a new JavaScript regular expression object. +@param pattern The regular expression pattern. +@param flags The regular expression flags. +@param context The JSContext in which the resulting regular expression object will be created. +@result The new JavaScript regular expression object. +*/ ++ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context; + +/*! +@method +@abstract Create a new JavaScript error object. +@param message The error message. +@param context The JSContext in which the resulting error object will be created. +@result The new JavaScript error object. +*/ ++ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context; + +/*! +@method +@abstract Create a new promise object using the provided executor callback. +@param callback A callback block invoked while the promise object is being initialized. The resolve and reject parameters are functions that can be called to notify any pending reactions about the state of the new promise object. +@param context The JSContext to which the resulting JSValue belongs. +@result The JSValue representing a new promise JavaScript object. +@discussion This method is equivalent to calling the Promise constructor in JavaScript. the resolve and reject callbacks each normally take a single value, which they forward to all relevent pending reactions. While inside the executor callback context will act as if it were in any other callback, except calleeFunction will be nil. This also means means the new promise object may be accessed via [context thisValue]. +*/ ++ (JSValue *)valueWithNewPromiseInContext:(JSContext *)context fromExecutor:(void (^)(JSValue *resolve, JSValue *reject))callback JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! +@method +@abstract Create a new resolved promise object with the provided value. +@param result The result value to be passed to any reactions. +@param context The JSContext to which the resulting JSValue belongs. +@result The JSValue representing a new promise JavaScript object. +@discussion This method is equivalent to calling [JSValue valueWithNewPromiseFromExecutor:^(JSValue *resolve, JSValue *reject) { [resolve callWithArguments:@[result]]; } inContext:context] +*/ ++ (JSValue *)valueWithNewPromiseResolvedWithResult:(id)result inContext:(JSContext *)context JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! +@method +@abstract Create a new rejected promise object with the provided value. +@param reason The result value to be passed to any reactions. +@param context The JSContext to which the resulting JSValue belongs. +@result The JSValue representing a new promise JavaScript object. +@discussion This method is equivalent to calling [JSValue valueWithNewPromiseFromExecutor:^(JSValue *resolve, JSValue *reject) { [reject callWithArguments:@[reason]]; } inContext:context] +*/ ++ (JSValue *)valueWithNewPromiseRejectedWithReason:(id)reason inContext:(JSContext *)context JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! +@method +@abstract Create a new, unique, symbol object. +@param description The description of the symbol object being created. +@param context The JSContext to which the resulting JSValue belongs. +@result The JSValue representing a unique JavaScript value with type symbol. +*/ ++ (JSValue *)valueWithNewSymbolFromDescription:(NSString *)description inContext:(JSContext *)context JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! +@method +@abstract Create the JavaScript value null. +@param context The JSContext to which the resulting JSValue belongs. +@result The JSValue representing the JavaScript value null. +*/ ++ (JSValue *)valueWithNullInContext:(JSContext *)context; + +/*! +@method +@abstract Create the JavaScript value undefined. +@param context The JSContext to which the resulting JSValue belongs. +@result The JSValue representing the JavaScript value undefined. +*/ ++ (JSValue *)valueWithUndefinedInContext:(JSContext *)context; + +/*! +@methodgroup Converting to Objective-C Types +@discussion When converting between JavaScript values and Objective-C objects a copy is + performed. Values of types listed below are copied to the corresponding + types on conversion in each direction. For NSDictionaries, entries in the + dictionary that are keyed by strings are copied onto a JavaScript object. + For dictionaries and arrays, conversion is recursive, with the same object + conversion being applied to all entries in the collection. + +
+@textblock
+   Objective-C type  |   JavaScript type
+ --------------------+---------------------
+         nil         |     undefined
+        NSNull       |        null
+       NSString      |       string
+       NSNumber      |   number, boolean
+     NSDictionary    |   Object object
+       NSArray       |    Array object
+        NSDate       |     Date object
+       NSBlock (1)   |   Function object (1)
+          id (2)     |   Wrapper object (2)
+        Class (3)    | Constructor object (3)
+@/textblock
+
+ + (1) Instances of NSBlock with supported arguments types will be presented to + JavaScript as a callable Function object. For more information on supported + argument types see JSExport.h. If a JavaScript Function originating from an + Objective-C block is converted back to an Objective-C object the block will + be returned. All other JavaScript functions will be converted in the same + manner as a JavaScript object of type Object. + + (2) For Objective-C instances that do not derive from the set of types listed + above, a wrapper object to provide a retaining handle to the Objective-C + instance from JavaScript. For more information on these wrapper objects, see + JSExport.h. When a JavaScript wrapper object is converted back to Objective-C + the Objective-C instance being retained by the wrapper is returned. + + (3) For Objective-C Class objects a constructor object containing exported + class methods will be returned. See JSExport.h for more information on + constructor objects. + + For all methods taking arguments of type id, arguments will be converted + into a JavaScript value according to the above conversion. +*/ +/*! +@method +@abstract Convert this JSValue to an Objective-C object. +@discussion The JSValue is converted to an Objective-C object according + to the conversion rules specified above. +@result The Objective-C representation of this JSValue. +*/ +- (id)toObject; + +/*! +@method +@abstract Convert a JSValue to an Objective-C object of a specific class. +@discussion The JSValue is converted to an Objective-C object of the specified Class. + If the result is not of the specified Class then nil will be returned. +@result An Objective-C object of the specified Class or nil. +*/ +- (id)toObjectOfClass:(Class)expectedClass; + +/*! +@method +@abstract Convert a JSValue to a boolean. +@discussion The JSValue is converted to a boolean according to the rules specified + by the JavaScript language. +@result The boolean result of the conversion. +*/ +- (BOOL)toBool; + +/*! +@method +@abstract Convert a JSValue to a double. +@discussion The JSValue is converted to a number according to the rules specified + by the JavaScript language. +@result The double result of the conversion. +*/ +- (double)toDouble; + +/*! +@method +@abstract Convert a JSValue to an int32_t. +@discussion The JSValue is converted to an integer according to the rules specified + by the JavaScript language. +@result The int32_t result of the conversion. +*/ +- (int32_t)toInt32; + +/*! +@method +@abstract Convert a JSValue to a uint32_t. +@discussion The JSValue is converted to an integer according to the rules specified + by the JavaScript language. +@result The uint32_t result of the conversion. +*/ +- (uint32_t)toUInt32; + +/*! +@method +@abstract Convert a JSValue to a NSNumber. +@discussion If the JSValue represents a boolean, a NSNumber value of YES or NO + will be returned. For all other types the value will be converted to a number according + to the rules specified by the JavaScript language. +@result The NSNumber result of the conversion. +*/ +- (NSNumber *)toNumber; + +/*! +@method +@abstract Convert a JSValue to a NSString. +@discussion The JSValue is converted to a string according to the rules specified + by the JavaScript language. +@result The NSString containing the result of the conversion. +*/ +- (NSString *)toString; + +/*! +@method +@abstract Convert a JSValue to a NSDate. +@discussion The value is converted to a number representing a time interval + since 1970 which is then used to create a new NSDate instance. +@result The NSDate created using the converted time interval. +*/ +- (NSDate *)toDate; + +/*! +@method +@abstract Convert a JSValue to a NSArray. +@discussion If the value is null or undefined then nil is returned. + If the value is not an object then a JavaScript TypeError will be thrown. + The property length is read from the object, converted to an unsigned + integer, and an NSArray of this size is allocated. Properties corresponding + to indicies within the array bounds will be copied to the array, with + JSValues converted to equivalent Objective-C objects as specified. +@result The NSArray containing the recursively converted contents of the + converted JavaScript array. +*/ +- (NSArray *)toArray; + +/*! +@method +@abstract Convert a JSValue to a NSDictionary. +@discussion If the value is null or undefined then nil is returned. + If the value is not an object then a JavaScript TypeError will be thrown. + All enumerable properties of the object are copied to the dictionary, with + JSValues converted to equivalent Objective-C objects as specified. +@result The NSDictionary containing the recursively converted contents of + the converted JavaScript object. +*/ +- (NSDictionary *)toDictionary; + +/*! +@functiongroup Checking JavaScript Types +*/ + +/*! +@property +@abstract Check if a JSValue corresponds to the JavaScript value undefined. +*/ +@property (readonly) BOOL isUndefined; + +/*! +@property +@abstract Check if a JSValue corresponds to the JavaScript value null. +*/ +@property (readonly) BOOL isNull; + +/*! +@property +@abstract Check if a JSValue is a boolean. +*/ +@property (readonly) BOOL isBoolean; + +/*! +@property +@abstract Check if a JSValue is a number. +@discussion In JavaScript, there is no differentiation between types of numbers. + Semantically all numbers behave like doubles except in special cases like bit + operations. +*/ +@property (readonly) BOOL isNumber; + +/*! +@property +@abstract Check if a JSValue is a string. +*/ +@property (readonly) BOOL isString; + +/*! +@property +@abstract Check if a JSValue is an object. +*/ +@property (readonly) BOOL isObject; + +/*! +@property +@abstract Check if a JSValue is an array. +*/ +@property (readonly) BOOL isArray JSC_API_AVAILABLE(macos(10.11), ios(9.0)); + +/*! +@property +@abstract Check if a JSValue is a date. +*/ +@property (readonly) BOOL isDate JSC_API_AVAILABLE(macos(10.11), ios(9.0)); + +/*! + @property + @abstract Check if a JSValue is a symbol. + */ +@property (readonly) BOOL isSymbol JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! +@method +@abstract Compare two JSValues using JavaScript's === operator. +*/ +- (BOOL)isEqualToObject:(id)value; + +/*! +@method +@abstract Compare two JSValues using JavaScript's == operator. +*/ +- (BOOL)isEqualWithTypeCoercionToObject:(id)value; + +/*! +@method +@abstract Check if a JSValue is an instance of another object. +@discussion This method has the same function as the JavaScript operator instanceof. + If an object other than a JSValue is passed, it will first be converted according to + the aforementioned rules. +*/ +- (BOOL)isInstanceOf:(id)value; + +/*! +@methodgroup Calling Functions and Constructors +*/ +/*! +@method +@abstract Invoke a JSValue as a function. +@discussion In JavaScript, if a function doesn't explicitly return a value then it + implicitly returns the JavaScript value undefined. +@param arguments The arguments to pass to the function. +@result The return value of the function call. +*/ +- (JSValue *)callWithArguments:(NSArray *)arguments; + +/*! +@method +@abstract Invoke a JSValue as a constructor. +@discussion This is equivalent to using the new syntax in JavaScript. +@param arguments The arguments to pass to the constructor. +@result The return value of the constructor call. +*/ +- (JSValue *)constructWithArguments:(NSArray *)arguments; + +/*! +@method +@abstract Invoke a method on a JSValue. +@discussion Accesses the property named method from this value and + calls the resulting value as a function, passing this JSValue as the this + value along with the specified arguments. +@param method The name of the method to be invoked. +@param arguments The arguments to pass to the method. +@result The return value of the method call. +*/ +- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments; + +@end + +/*! +@category +@discussion Objective-C methods exported to JavaScript may have argument and/or return + values of struct types, provided that conversion to and from the struct is + supported by JSValue. Support is provided for any types where JSValue + contains both a class method valueWith:inContext:, and and instance + method to- where the string in these selector names match, + with the first argument to the former being of the same struct type as the + return type of the latter. + Support is provided for structs of type CGPoint, NSRange, CGRect and CGSize. +*/ +@interface JSValue (StructSupport) + +/*! +@method +@abstract Create a JSValue from a CGPoint. +@result A newly allocated JavaScript object containing properties + named x and y, with values from the CGPoint. +*/ ++ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context; + +/*! +@method +@abstract Create a JSValue from a NSRange. +@result A newly allocated JavaScript object containing properties + named location and length, with values from the NSRange. +*/ ++ (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context; + +/*! +@method +@abstract +Create a JSValue from a CGRect. +@result A newly allocated JavaScript object containing properties + named x, y, width, and height, with values from the CGRect. +*/ ++ (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context; + +/*! +@method +@abstract Create a JSValue from a CGSize. +@result A newly allocated JavaScript object containing properties + named width and height, with values from the CGSize. +*/ ++ (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context; + +/*! +@method +@abstract Convert a JSValue to a CGPoint. +@discussion Reads the properties named x and y from + this JSValue, and converts the results to double. +@result The new CGPoint. +*/ +- (CGPoint)toPoint; + +/*! +@method +@abstract Convert a JSValue to an NSRange. +@discussion Reads the properties named location and + length from this JSValue and converts the results to double. +@result The new NSRange. +*/ +- (NSRange)toRange; + +/*! +@method +@abstract Convert a JSValue to a CGRect. +@discussion Reads the properties named x, y, + width, and height from this JSValue and converts the results to double. +@result The new CGRect. +*/ +- (CGRect)toRect; + +/*! +@method +@abstract Convert a JSValue to a CGSize. +@discussion Reads the properties named width and + height from this JSValue and converts the results to double. +@result The new CGSize. +*/ +- (CGSize)toSize; + +@end + +/*! + @category + @discussion These methods enable querying properties on a JSValue. + */ +@interface JSValue (PropertyAccess) + +#if (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500) || (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000) +typedef NSString *JSValueProperty; +#else +typedef id JSValueProperty; +#endif + +/*! + @method + @abstract Access a property of a JSValue. + @result The JSValue for the requested property or the JSValue undefined + if the property does not exist. + @discussion Corresponds to the JavaScript operation object[property]. Starting with macOS 10.15 and iOS 13, 'property' can be any 'id' and will be converted to a JSValue using the conversion rules of valueWithObject:inContext:. Prior to macOS 10.15 and iOS 13, 'property' was expected to be an NSString *. + */ +- (JSValue *)valueForProperty:(JSValueProperty)property; + +/*! + @method + @abstract Set a property on a JSValue. + @discussion Corresponds to the JavaScript operation object[property] = value. Starting with macOS 10.15 and iOS 13, 'property' can be any 'id' and will be converted to a JSValue using the conversion rules of valueWithObject:inContext:. Prior to macOS 10.15 and iOS 13, 'property' was expected to be an NSString *. + */ +- (void)setValue:(id)value forProperty:(JSValueProperty)property; + +/*! + @method + @abstract Delete a property from a JSValue. + @result YES if deletion is successful, NO otherwise. + @discussion Corresponds to the JavaScript operation delete object[property]. Starting with macOS 10.15 and iOS 13, 'property' can be any 'id' and will be converted to a JSValue using the conversion rules of valueWithObject:inContext:. Prior to macOS 10.15 and iOS 13, 'property' was expected to be an NSString *. + */ +- (BOOL)deleteProperty:(JSValueProperty)property; + +/*! + @method + @abstract Check if a JSValue has a property. + @discussion This method has the same function as the JavaScript operator in. + @result Returns YES if property is present on the value. + @discussion Corresponds to the JavaScript operation property in object. Starting with macOS 10.15 and iOS 13, 'property' can be any 'id' and will be converted to a JSValue using the conversion rules of valueWithObject:inContext:. Prior to macOS 10.15 and iOS 13, 'property' was expected to be an NSString *. + */ +- (BOOL)hasProperty:(JSValueProperty)property; + +/*! + @method + @abstract Define properties with custom descriptors on JSValues. + @discussion This method may be used to create a data or accessor property on an object. + This method operates in accordance with the Object.defineProperty method in the JavaScript language. Starting with macOS 10.15 and iOS 13, 'property' can be any 'id' and will be converted to a JSValue using the conversion rules of valueWithObject:inContext:. Prior to macOS 10.15 and iOS 13, 'property' was expected to be an NSString *. + */ +- (void)defineProperty:(JSValueProperty)property descriptor:(id)descriptor; + +/*! + @method + @abstract Access an indexed (numerical) property on a JSValue. + @result The JSValue for the property at the specified index. + Returns the JavaScript value undefined if no property exists at that index. + */ +- (JSValue *)valueAtIndex:(NSUInteger)index; + +/*! + @method + @abstract Set an indexed (numerical) property on a JSValue. + @discussion For JSValues that are JavaScript arrays, indices greater than + UINT_MAX - 1 will not affect the length of the array. + */ +- (void)setValue:(id)value atIndex:(NSUInteger)index; + +@end + +/*! +@category +@discussion Instances of JSValue implement the following methods in order to enable + support for subscript access by key and index, for example: + +@textblock + JSValue *objectA, *objectB; + JSValue *v1 = object[@"X"]; // Get value for property "X" from 'object'. + JSValue *v2 = object[42]; // Get value for index 42 from 'object'. + object[@"Y"] = v1; // Assign 'v1' to property "Y" of 'object'. + object[101] = v2; // Assign 'v2' to index 101 of 'object'. +@/textblock + + An object key passed as a subscript will be converted to a JavaScript value, + and then the value using the same rules as valueWithObject:inContext:. In macOS + 10.14 and iOS 12 and below, the key argument of + setObject:object forKeyedSubscript:key was restricted to an + NSObject * but that restriction was never used internally. +*/ +@interface JSValue (SubscriptSupport) + +- (JSValue *)objectForKeyedSubscript:(id)key; +- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index; +- (void)setObject:(id)object forKeyedSubscript:(id)key; +- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index; + +@end + +/*! +@category +@discussion These functions are for bridging between the C API and the Objective-C API. +*/ +@interface JSValue (JSValueRefSupport) + +/*! +@method +@abstract Creates a JSValue, wrapping its C API counterpart. +@result The Objective-C API equivalent of the specified JSValueRef. +*/ ++ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context; + +/*! +@property +@abstract Returns the C API counterpart wrapped by a JSContext. +@result The C API equivalent of this JSValue. +*/ +@property (readonly) JSValueRef JSValueRef; +@end + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +@group Property Descriptor Constants +@discussion These keys may assist in creating a property descriptor for use with the + defineProperty method on JSValue. + Property descriptors must fit one of three descriptions: + + Data Descriptor: + - A descriptor containing one or both of the keys value and writable, + and optionally containing one or both of the keys enumerable and + configurable. A data descriptor may not contain either the get or + set key. + A data descriptor may be used to create or modify the attributes of a + data property on an object (replacing any existing accessor property). + + Accessor Descriptor: + - A descriptor containing one or both of the keys get and set, and + optionally containing one or both of the keys enumerable and + configurable. An accessor descriptor may not contain either the value + or writable key. + An accessor descriptor may be used to create or modify the attributes of + an accessor property on an object (replacing any existing data property). + + Generic Descriptor: + - A descriptor containing one or both of the keys enumerable and + configurable. A generic descriptor may not contain any of the keys + value, writable, get, or set. + A generic descriptor may be used to modify the attributes of an existing + data or accessor property, or to create a new data property. +*/ +/*! +@const +*/ +JS_EXPORT extern NSString * const JSPropertyDescriptorWritableKey; +/*! +@const +*/ +JS_EXPORT extern NSString * const JSPropertyDescriptorEnumerableKey; +/*! +@const +*/ +JS_EXPORT extern NSString * const JSPropertyDescriptorConfigurableKey; +/*! +@const +*/ +JS_EXPORT extern NSString * const JSPropertyDescriptorValueKey; +/*! +@const +*/ +JS_EXPORT extern NSString * const JSPropertyDescriptorGetKey; +/*! +@const +*/ +JS_EXPORT extern NSString * const JSPropertyDescriptorSetKey; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif + +#endif // JSValue_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValueInternal.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValueInternal.h new file mode 100644 index 000000000..54b755eff --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValueInternal.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSValueInternal_h +#define JSValueInternal_h + +#import + +#if JSC_OBJC_API_ENABLED + +@interface JSValue(Internal) + +JSValueRef valueInternalValue(JSValue *); + +- (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context; + +JSValueRef objectToValue(JSContext *, id); +id valueToObject(JSContext *, JSValueRef); +id valueToNumber(JSGlobalContextRef, JSValueRef, JSValueRef* exception); +id valueToString(JSGlobalContextRef, JSValueRef, JSValueRef* exception); +id valueToDate(JSGlobalContextRef, JSValueRef, JSValueRef* exception); +id valueToArray(JSGlobalContextRef, JSValueRef, JSValueRef* exception); +id valueToDictionary(JSGlobalContextRef, JSValueRef, JSValueRef* exception); + ++ (SEL)selectorForStructToValue:(const char *)structTag; ++ (SEL)selectorForValueToStruct:(const char *)structTag; + +@end + +NSInvocation *typeToValueInvocationFor(const char* encodedType); +NSInvocation *valueToTypeInvocationFor(const char* encodedType); + +#endif + +#endif // JSValueInternal_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValuePrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValuePrivate.h new file mode 100644 index 000000000..35d3d1000 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValuePrivate.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if JSC_OBJC_API_ENABLED + +#import + +@interface JSValue(JSPrivate) + +// Currently empty. May be used again in the future. + +@end + +#endif // JSC_OBJC_API_ENABLED diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValueRef.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValueRef.h new file mode 100644 index 000000000..911b4bfc4 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSValueRef.h @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2006-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSValueRef_h +#define JSValueRef_h + +#include +#include + +#ifndef __cplusplus +#include +#endif + +/*! +@enum JSType +@abstract A constant identifying the type of a JSValue. +@constant kJSTypeUndefined The unique undefined value. +@constant kJSTypeNull The unique null value. +@constant kJSTypeBoolean A primitive boolean value, one of true or false. +@constant kJSTypeNumber A primitive number value. +@constant kJSTypeString A primitive string value. +@constant kJSTypeObject An object value (meaning that this JSValueRef is a JSObjectRef). +@constant kJSTypeSymbol A primitive symbol value. +*/ +typedef enum { + kJSTypeUndefined, + kJSTypeNull, + kJSTypeBoolean, + kJSTypeNumber, + kJSTypeString, + kJSTypeObject, + kJSTypeSymbol JSC_API_AVAILABLE(macos(10.15), ios(13.0)) +} JSType; + +/*! + @enum JSTypedArrayType + @abstract A constant identifying the Typed Array type of a JSObjectRef. + @constant kJSTypedArrayTypeInt8Array Int8Array + @constant kJSTypedArrayTypeInt16Array Int16Array + @constant kJSTypedArrayTypeInt32Array Int32Array + @constant kJSTypedArrayTypeUint8Array Uint8Array + @constant kJSTypedArrayTypeUint8ClampedArray Uint8ClampedArray + @constant kJSTypedArrayTypeUint16Array Uint16Array + @constant kJSTypedArrayTypeUint32Array Uint32Array + @constant kJSTypedArrayTypeFloat32Array Float32Array + @constant kJSTypedArrayTypeFloat64Array Float64Array + @constant kJSTypedArrayTypeArrayBuffer ArrayBuffer + @constant kJSTypedArrayTypeNone Not a Typed Array + + */ +typedef enum { + kJSTypedArrayTypeInt8Array, + kJSTypedArrayTypeInt16Array, + kJSTypedArrayTypeInt32Array, + kJSTypedArrayTypeUint8Array, + kJSTypedArrayTypeUint8ClampedArray, + kJSTypedArrayTypeUint16Array, + kJSTypedArrayTypeUint32Array, + kJSTypedArrayTypeFloat32Array, + kJSTypedArrayTypeFloat64Array, + kJSTypedArrayTypeArrayBuffer, + kJSTypedArrayTypeNone, +} JSTypedArrayType JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +#ifdef __cplusplus +extern "C" { +#endif + +/*! +@function +@abstract Returns a JavaScript value's type. +@param ctx The execution context to use. +@param value The JSValue whose type you want to obtain. +@result A value of type JSType that identifies value's type. +*/ +JS_EXPORT JSType JSValueGetType(JSContextRef ctx, JSValueRef value); + +/*! +@function +@abstract Tests whether a JavaScript value's type is the undefined type. +@param ctx The execution context to use. +@param value The JSValue to test. +@result true if value's type is the undefined type, otherwise false. +*/ +JS_EXPORT bool JSValueIsUndefined(JSContextRef ctx, JSValueRef value); + +/*! +@function +@abstract Tests whether a JavaScript value's type is the null type. +@param ctx The execution context to use. +@param value The JSValue to test. +@result true if value's type is the null type, otherwise false. +*/ +JS_EXPORT bool JSValueIsNull(JSContextRef ctx, JSValueRef value); + +/*! +@function +@abstract Tests whether a JavaScript value's type is the boolean type. +@param ctx The execution context to use. +@param value The JSValue to test. +@result true if value's type is the boolean type, otherwise false. +*/ +JS_EXPORT bool JSValueIsBoolean(JSContextRef ctx, JSValueRef value); + +/*! +@function +@abstract Tests whether a JavaScript value's type is the number type. +@param ctx The execution context to use. +@param value The JSValue to test. +@result true if value's type is the number type, otherwise false. +*/ +JS_EXPORT bool JSValueIsNumber(JSContextRef ctx, JSValueRef value); + +/*! +@function +@abstract Tests whether a JavaScript value's type is the string type. +@param ctx The execution context to use. +@param value The JSValue to test. +@result true if value's type is the string type, otherwise false. +*/ +JS_EXPORT bool JSValueIsString(JSContextRef ctx, JSValueRef value); + +/*! +@function +@abstract Tests whether a JavaScript value's type is the symbol type. +@param ctx The execution context to use. +@param value The JSValue to test. +@result true if value's type is the symbol type, otherwise false. +*/ +JS_EXPORT bool JSValueIsSymbol(JSContextRef ctx, JSValueRef value) JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/*! +@function +@abstract Tests whether a JavaScript value's type is the object type. +@param ctx The execution context to use. +@param value The JSValue to test. +@result true if value's type is the object type, otherwise false. +*/ +JS_EXPORT bool JSValueIsObject(JSContextRef ctx, JSValueRef value); + + +/*! +@function +@abstract Tests whether a JavaScript value is an object with a given class in its class chain. +@param ctx The execution context to use. +@param value The JSValue to test. +@param jsClass The JSClass to test against. +@result true if value is an object and has jsClass in its class chain, otherwise false. +*/ +JS_EXPORT bool JSValueIsObjectOfClass(JSContextRef ctx, JSValueRef value, JSClassRef jsClass); + +/*! +@function +@abstract Tests whether a JavaScript value is an array. +@param ctx The execution context to use. +@param value The JSValue to test. +@result true if value is an array, otherwise false. +*/ +JS_EXPORT bool JSValueIsArray(JSContextRef ctx, JSValueRef value) JSC_API_AVAILABLE(macos(10.11), ios(9.0)); + +/*! +@function +@abstract Tests whether a JavaScript value is a date. +@param ctx The execution context to use. +@param value The JSValue to test. +@result true if value is a date, otherwise false. +*/ +JS_EXPORT bool JSValueIsDate(JSContextRef ctx, JSValueRef value) JSC_API_AVAILABLE(macos(10.11), ios(9.0)); + +/*! +@function +@abstract Returns a JavaScript value's Typed Array type. +@param ctx The execution context to use. +@param value The JSValue whose Typed Array type to return. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result A value of type JSTypedArrayType that identifies value's Typed Array type, or kJSTypedArrayTypeNone if the value is not a Typed Array object. + */ +JS_EXPORT JSTypedArrayType JSValueGetTypedArrayType(JSContextRef ctx, JSValueRef value, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.12), ios(10.0)); + +/* Comparing values */ + +/*! +@function +@abstract Tests whether two JavaScript values are equal, as compared by the JS == operator. +@param ctx The execution context to use. +@param a The first value to test. +@param b The second value to test. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result true if the two values are equal, false if they are not equal or an exception is thrown. +*/ +JS_EXPORT bool JSValueIsEqual(JSContextRef ctx, JSValueRef a, JSValueRef b, JSValueRef* exception); + +/*! +@function +@abstract Tests whether two JavaScript values are strict equal, as compared by the JS === operator. +@param ctx The execution context to use. +@param a The first value to test. +@param b The second value to test. +@result true if the two values are strict equal, otherwise false. +*/ +JS_EXPORT bool JSValueIsStrictEqual(JSContextRef ctx, JSValueRef a, JSValueRef b); + +/*! +@function +@abstract Tests whether a JavaScript value is an object constructed by a given constructor, as compared by the JS instanceof operator. +@param ctx The execution context to use. +@param value The JSValue to test. +@param constructor The constructor to test against. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result true if value is an object constructed by constructor, as compared by the JS instanceof operator, otherwise false. +*/ +JS_EXPORT bool JSValueIsInstanceOfConstructor(JSContextRef ctx, JSValueRef value, JSObjectRef constructor, JSValueRef* exception); + +/* Creating values */ + +/*! +@function +@abstract Creates a JavaScript value of the undefined type. +@param ctx The execution context to use. +@result The unique undefined value. +*/ +JS_EXPORT JSValueRef JSValueMakeUndefined(JSContextRef ctx); + +/*! +@function +@abstract Creates a JavaScript value of the null type. +@param ctx The execution context to use. +@result The unique null value. +*/ +JS_EXPORT JSValueRef JSValueMakeNull(JSContextRef ctx); + +/*! +@function +@abstract Creates a JavaScript value of the boolean type. +@param ctx The execution context to use. +@param boolean The bool to assign to the newly created JSValue. +@result A JSValue of the boolean type, representing the value of boolean. +*/ +JS_EXPORT JSValueRef JSValueMakeBoolean(JSContextRef ctx, bool boolean); + +/*! +@function +@abstract Creates a JavaScript value of the number type. +@param ctx The execution context to use. +@param number The double to assign to the newly created JSValue. +@result A JSValue of the number type, representing the value of number. +*/ +JS_EXPORT JSValueRef JSValueMakeNumber(JSContextRef ctx, double number); + +/*! +@function +@abstract Creates a JavaScript value of the string type. +@param ctx The execution context to use. +@param string The JSString to assign to the newly created JSValue. The + newly created JSValue retains string, and releases it upon garbage collection. +@result A JSValue of the string type, representing the value of string. +*/ +JS_EXPORT JSValueRef JSValueMakeString(JSContextRef ctx, JSStringRef string); + +/*! + @function + @abstract Creates a JavaScript value of the symbol type. + @param ctx The execution context to use. + @param description A description of the newly created symbol value. + @result A unique JSValue of the symbol type, whose description matches the one provided. + */ +JS_EXPORT JSValueRef JSValueMakeSymbol(JSContextRef ctx, JSStringRef description) JSC_API_AVAILABLE(macos(10.15), ios(13.0)); + +/* Converting to and from JSON formatted strings */ + +/*! + @function + @abstract Creates a JavaScript value from a JSON formatted string. + @param ctx The execution context to use. + @param string The JSString containing the JSON string to be parsed. + @result A JSValue containing the parsed value, or NULL if the input is invalid. + */ +JS_EXPORT JSValueRef JSValueMakeFromJSONString(JSContextRef ctx, JSStringRef string) JSC_API_AVAILABLE(macos(10.7), ios(7.0)); + +/*! + @function + @abstract Creates a JavaScript string containing the JSON serialized representation of a JS value. + @param ctx The execution context to use. + @param value The value to serialize. + @param indent The number of spaces to indent when nesting. If 0, the resulting JSON will not contains newlines. The size of the indent is clamped to 10 spaces. + @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. + @result A JSString with the result of serialization, or NULL if an exception is thrown. + */ +JS_EXPORT JSStringRef JSValueCreateJSONString(JSContextRef ctx, JSValueRef value, unsigned indent, JSValueRef* exception) JSC_API_AVAILABLE(macos(10.7), ios(7.0)); + +/* Converting to primitive values */ + +/*! +@function +@abstract Converts a JavaScript value to boolean and returns the resulting boolean. +@param ctx The execution context to use. +@param value The JSValue to convert. +@result The boolean result of conversion. +*/ +JS_EXPORT bool JSValueToBoolean(JSContextRef ctx, JSValueRef value); + +/*! +@function +@abstract Converts a JavaScript value to number and returns the resulting number. +@param ctx The execution context to use. +@param value The JSValue to convert. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result The numeric result of conversion, or NaN if an exception is thrown. +*/ +JS_EXPORT double JSValueToNumber(JSContextRef ctx, JSValueRef value, JSValueRef* exception); + +/*! +@function +@abstract Converts a JavaScript value to string and copies the result into a JavaScript string. +@param ctx The execution context to use. +@param value The JSValue to convert. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result A JSString with the result of conversion, or NULL if an exception is thrown. Ownership follows the Create Rule. +*/ +JS_EXPORT JSStringRef JSValueToStringCopy(JSContextRef ctx, JSValueRef value, JSValueRef* exception); + +/*! +@function +@abstract Converts a JavaScript value to object and returns the resulting object. +@param ctx The execution context to use. +@param value The JSValue to convert. +@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. +@result The JSObject result of conversion, or NULL if an exception is thrown. +*/ +JS_EXPORT JSObjectRef JSValueToObject(JSContextRef ctx, JSValueRef value, JSValueRef* exception); + +/* Garbage collection */ +/*! +@function +@abstract Protects a JavaScript value from garbage collection. +@param ctx The execution context to use. +@param value The JSValue to protect. +@discussion Use this method when you want to store a JSValue in a global or on the heap, where the garbage collector will not be able to discover your reference to it. + +A value may be protected multiple times and must be unprotected an equal number of times before becoming eligible for garbage collection. +*/ +JS_EXPORT void JSValueProtect(JSContextRef ctx, JSValueRef value); + +/*! +@function +@abstract Unprotects a JavaScript value from garbage collection. +@param ctx The execution context to use. +@param value The JSValue to unprotect. +@discussion A value may be protected multiple times and must be unprotected an + equal number of times before becoming eligible for garbage collection. +*/ +JS_EXPORT void JSValueUnprotect(JSContextRef ctx, JSValueRef value); + +#ifdef __cplusplus +} +#endif + +#endif /* JSValueRef_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSVirtualMachine.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSVirtualMachine.h new file mode 100644 index 000000000..e9c75da07 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSVirtualMachine.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +#if JSC_OBJC_API_ENABLED + +/*! +@interface +@discussion An instance of JSVirtualMachine represents a single JavaScript "object space" + or set of execution resources. Thread safety is supported by locking the + virtual machine, with concurrent JavaScript execution supported by allocating + separate instances of JSVirtualMachine. + + A virtual machine may need to run deferred tasks on a run loop, such as garbage collection + or resolving WebAssembly compilations. By default, a virtual machine will use the run loop + of the thread it was initialized on. Currently, there is no API to change a + JSVirtualMachine's run loop once it has been initialized. +*/ +NS_CLASS_AVAILABLE(10_9, 7_0) +@interface JSVirtualMachine : NSObject + +/*! +@methodgroup Creating New Virtual Machines +*/ +/*! +@method +@abstract Create a new JSVirtualMachine. +*/ +- (instancetype)init; + +/*! +@methodgroup Memory Management +*/ +/*! +@method +@abstract Notify the JSVirtualMachine of an external object relationship. +@discussion Allows clients of JSVirtualMachine to make the JavaScript runtime aware of + arbitrary external Objective-C object graphs. The runtime can then use + this information to retain any JavaScript values that are referenced + from somewhere in said object graph. + + For correct behavior clients must make their external object graphs + reachable from within the JavaScript runtime. If an Objective-C object is + reachable from within the JavaScript runtime, all managed references + transitively reachable from it as recorded using + -addManagedReference:withOwner: will be scanned by the garbage collector. +@param object The object that the owner points to. +@param owner The object that owns the pointed to object. +*/ +- (void)addManagedReference:(id)object withOwner:(id)owner; + +/*! +@method +@abstract Notify the JSVirtualMachine that a previous object relationship no longer exists. +@discussion The JavaScript runtime will continue to scan any references that were + reported to it by -addManagedReference:withOwner: until those references are removed. +@param object The object that was formerly owned. +@param owner The former owner. +*/ +- (void)removeManagedReference:(id)object withOwner:(id)owner; + +@end + +#endif diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSVirtualMachineInternal.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSVirtualMachineInternal.h new file mode 100644 index 000000000..b533482ac --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSVirtualMachineInternal.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013, 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSVirtualMachineInternal_h +#define JSVirtualMachineInternal_h + +#if JSC_OBJC_API_ENABLED + +#import + +namespace JSC { +class VM; +class SlotVisitor; +} + +#if defined(__OBJC__) +@class NSMapTable; + +@interface JSVirtualMachine(Internal) + +JSContextGroupRef getGroupFromVirtualMachine(JSVirtualMachine *); + ++ (JSVirtualMachine *)virtualMachineWithContextGroupRef:(JSContextGroupRef)group; + +- (JSContext *)contextForGlobalContextRef:(JSGlobalContextRef)globalContext; +- (void)addContext:(JSContext *)wrapper forGlobalContextRef:(JSGlobalContextRef)globalContext; +- (JSC::VM&)vm; + +- (BOOL)isWebThreadAware; + +@end + +#endif // defined(__OBJC__) + +void scanExternalObjectGraph(JSC::VM&, JSC::SlotVisitor&, void* root); +void scanExternalRememberedSet(JSC::VM&, JSC::SlotVisitor&); + +#endif // JSC_OBJC_API_ENABLED + +#endif // JSVirtualMachineInternal_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSVirtualMachinePrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSVirtualMachinePrivate.h new file mode 100644 index 000000000..950afc73f --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSVirtualMachinePrivate.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "JSExportMacros.h" +#include + +#if JSC_OBJC_API_ENABLED + +#import + +@interface JSVirtualMachine(JSPrivate) + +/*! +@method +@discussion Shrinks the memory footprint of the VM by deleting various internal caches, + running synchronous garbage collection, and releasing memory back to the OS. Note: this + API waits until no JavaScript is running on the stack before it frees any memory. It's + best to call this API when no JavaScript is running on the stack for this reason. However, if + you do call this API when JavaScript is running on the stack, the API will wait until all JavaScript + on the stack finishes running to free memory back to the OS. Therefore, calling this + API may not synchronously free memory. +*/ + +- (void)shrinkFootprintWhenIdle JSC_API_AVAILABLE(macos(10.14), ios(12.0)); + +#if ENABLE(DFG_JIT) + +/*! +@method +@abstract Set the number of threads to be used by the DFG JIT compiler. +@discussion If called after the VM has been initialized, it will terminate + threads until it meets the new limit or create new threads accordingly if the + new limit is higher than the previous limit. If called before initialization, + the Options value for the number of DFG threads will be updated to ensure the + DFG compiler already starts with the up-to-date limit. +@param numberOfThreads The number of threads the DFG compiler should use going forward +@result The previous number of threads being used by the DFG compiler +*/ ++ (NSUInteger)setNumberOfDFGCompilerThreads:(NSUInteger)numberOfThreads JSC_API_AVAILABLE(macos(10.14), ios(12.0)); + +/*! +@method +@abstract Set the number of threads to be used by the FTL JIT compiler. +@discussion If called after the VM has been initialized, it will terminate + threads until it meets the new limit or create new threads accordingly if the + new limit is higher than the previous limit. If called before initialization, + the Options value for the number of FTL threads will be updated to ensure the + FTL compiler already starts with the up-to-date limit. +@param numberOfThreads The number of threads the FTL compiler should use going forward +@result The previous number of threads being used by the FTL compiler +*/ ++ (NSUInteger)setNumberOfFTLCompilerThreads:(NSUInteger)numberOfThreads JSC_API_AVAILABLE(macos(10.14), ios(12.0)); + +/*! +@method +@abstract Allows embedders of JSC to specify that JSC should crash the process if a VM is created when unexpected. +@param shouldCrash Sets process-wide state that indicates whether VM creation should crash or not. +*/ ++ (void)setCrashOnVMCreation:(BOOL)shouldCrash; + +#endif // ENABLE(DFG_JIT) + +@end + +#endif // JSC_OBJC_API_ENABLED diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakObjectMapRefInternal.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakObjectMapRefInternal.h new file mode 100644 index 000000000..9037947d7 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakObjectMapRefInternal.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSWeakObjectMapRefInternal_h +#define JSWeakObjectMapRefInternal_h + +#include "WeakGCMap.h" +#include + +namespace JSC { + +class JSObject; + +} + +typedef void (*JSWeakMapDestroyedCallback)(struct OpaqueJSWeakObjectMap*, void*); + +typedef JSC::WeakGCMap WeakMapType; + +struct OpaqueJSWeakObjectMap : public RefCounted { +public: + static Ref create(JSC::VM& vm, void* data, JSWeakMapDestroyedCallback callback) + { + return adoptRef(*new OpaqueJSWeakObjectMap(vm, data, callback)); + } + + WeakMapType& map() { return m_map; } + + ~OpaqueJSWeakObjectMap() + { + m_callback(this, m_data); + } + +private: + OpaqueJSWeakObjectMap(JSC::VM& vm, void* data, JSWeakMapDestroyedCallback callback) + : m_map(vm) + , m_data(data) + , m_callback(callback) + { + } + WeakMapType m_map; + void* m_data; + JSWeakMapDestroyedCallback m_callback; +}; + + +#endif // JSWeakObjectMapInternal_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakObjectMapRefPrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakObjectMapRefPrivate.h new file mode 100644 index 000000000..a335e23c9 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakObjectMapRefPrivate.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSWeakObjectMapRefPrivate_h +#define JSWeakObjectMapRefPrivate_h + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*! @typedef JSWeakObjectMapRef A weak map for storing JSObjectRefs */ +typedef struct OpaqueJSWeakObjectMap* JSWeakObjectMapRef; + +/*! + @typedef JSWeakMapDestroyedCallback + @abstract The callback invoked when a JSWeakObjectMapRef is being destroyed. + @param map The map that is being destroyed. + @param data The private data (if any) that was associated with the map instance. + */ +typedef void (*JSWeakMapDestroyedCallback)(JSWeakObjectMapRef map, void* data); + +/*! + @function + @abstract Creates a weak value map that can be used to reference user defined objects without preventing them from being collected. + @param ctx The execution context to use. + @param data A void* to set as the map's private data. Pass NULL to specify no private data. + @param destructor A function to call when the weak map is destroyed. + @result A JSWeakObjectMapRef bound to the given context, data and destructor. + @discussion The JSWeakObjectMapRef can be used as a storage mechanism to hold custom JS objects without forcing those objects to + remain live as JSValueProtect would. + */ +JS_EXPORT JSWeakObjectMapRef JSWeakObjectMapCreate(JSContextRef ctx, void* data, JSWeakMapDestroyedCallback destructor); + +/*! + @function + @abstract Associates a JSObjectRef with the given key in a JSWeakObjectMap. + @param ctx The execution context to use. + @param map The map to operate on. + @param key The key to associate a weak reference with. + @param object The user defined object to associate with the key. + */ +JS_EXPORT void JSWeakObjectMapSet(JSContextRef ctx, JSWeakObjectMapRef map, void* key, JSObjectRef object); + +/*! + @function + @abstract Retrieves the JSObjectRef associated with a key. + @param ctx The execution context to use. + @param map The map to query. + @param key The key to search for. + @result Either the live object associated with the provided key, or NULL. + */ +JS_EXPORT JSObjectRef JSWeakObjectMapGet(JSContextRef ctx, JSWeakObjectMapRef map, void* key); + +/*! + @function + @abstract Removes the entry for the given key if the key is present, otherwise it has no effect. + @param ctx The execution context to use. + @param map The map to use. + @param key The key to remove. + */ +JS_EXPORT void JSWeakObjectMapRemove(JSContextRef ctx, JSWeakObjectMapRef map, void* key); + +#ifdef __cplusplus +} +#endif + +#endif // JSWeakObjectMapPrivate_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakPrivate.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakPrivate.h new file mode 100644 index 000000000..9ca2ffa6d --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakPrivate.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JSWeakPrivate_h +#define JSWeakPrivate_h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef const struct OpaqueJSWeak* JSWeakRef; + +JS_EXPORT JSWeakRef JSWeakCreate(JSContextGroupRef, JSObjectRef); + +JS_EXPORT void JSWeakRetain(JSContextGroupRef, JSWeakRef); +JS_EXPORT void JSWeakRelease(JSContextGroupRef, JSWeakRef); + +JS_EXPORT JSObjectRef JSWeakGetObject(JSWeakRef); + +#ifdef __cplusplus +} +#endif + +#endif // JSWeakPrivate_h + diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakValue.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakValue.h new file mode 100644 index 000000000..177ef41cf --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWeakValue.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2013, 2016 Apple Inc. All rights reserved. + * Copyright (C) 2018 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "JSCJSValue.h" +#include "Weak.h" + +namespace JSC { + +class JSObject; +class JSString; +class WeakHandleOwner; + +class JSWeakValue { +public: + JSWeakValue() = default; + ~JSWeakValue(); + + void clear(); + bool isClear() const; + + bool isSet() const { return m_tag != WeakTypeTag::NotSet; } + bool isPrimitive() const { return m_tag == WeakTypeTag::Primitive; } + bool isObject() const { return m_tag == WeakTypeTag::Object; } + bool isString() const { return m_tag == WeakTypeTag::String; } + + void setPrimitive(JSValue); + void setObject(JSObject*, WeakHandleOwner&, void* context); + void setString(JSString*, WeakHandleOwner&, void* context); + + JSObject* object() const + { + ASSERT(isObject()); + return m_value.object.get(); + } + + JSValue primitive() const + { + ASSERT(isPrimitive()); + return m_value.primitive; + } + + JSString* string() const + { + ASSERT(isString()); + return m_value.string.get(); + } + +private: + enum class WeakTypeTag { NotSet, Primitive, Object, String }; + + WeakTypeTag m_tag { WeakTypeTag::NotSet }; + + union WeakValueUnion { + WeakValueUnion() + : primitive(JSValue()) + { + } + + ~WeakValueUnion() + { + ASSERT(!primitive); + } + + JSValue primitive; + Weak object; + Weak string; + } m_value; +}; + +} // namespace JSC diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWrapperMap.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWrapperMap.h new file mode 100644 index 000000000..6c18c64b4 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JSWrapperMap.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import +#import + +#if JSC_OBJC_API_ENABLED + +@interface JSWrapperMap : NSObject + +- (instancetype)initWithGlobalContextRef:(JSGlobalContextRef)context; + +- (JSValue *)jsWrapperForObject:(id)object inContext:(JSContext *)context; + +- (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value inContext:(JSContext *)context; + +@end + +id tryUnwrapObjcObject(JSGlobalContextRef, JSValueRef); + +bool supportsInitMethodConstructors(); +Protocol *getJSExportProtocol(); +Class getNSBlockClass(); + +#endif diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JavaScript.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JavaScript.h new file mode 100644 index 000000000..251e3937b --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JavaScript.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2006 Apple Inc. All rights reserved. + * Copyright (C) 2008 Alp Toker + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JavaScript_h +#define JavaScript_h + +#include +#include +#include +#include +#include +#include + +#endif /* JavaScript_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/JavaScriptCore.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/JavaScriptCore.h new file mode 100644 index 000000000..b2fde1dbe --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/JavaScriptCore.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JavaScriptCore_h +#define JavaScriptCore_h + +#include +#include + +#if defined(__OBJC__) && JSC_OBJC_API_ENABLED + +#import "JSContext.h" +#import "JSValue.h" +#import "JSManagedValue.h" +#import "JSVirtualMachine.h" +#import "JSExport.h" + +#endif + +#endif /* JavaScriptCore_h */ diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/ObjCCallbackFunction.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/ObjCCallbackFunction.h new file mode 100644 index 000000000..c30c15628 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/ObjCCallbackFunction.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013, 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef ObjCCallbackFunction_h +#define ObjCCallbackFunction_h + +#include + +#if JSC_OBJC_API_ENABLED + +#import + +#if defined(__OBJC__) +JSObjectRef objCCallbackFunctionForMethod(JSContext *, Class, Protocol *, BOOL isInstanceMethod, SEL, const char* types); +JSObjectRef objCCallbackFunctionForBlock(JSContext *, id); +JSObjectRef objCCallbackFunctionForInit(JSContext *, Class, Protocol *, SEL, const char* types); + +id tryUnwrapConstructor(JSC::VM*, JSObjectRef); +#endif + +namespace JSC { + +class ObjCCallbackFunctionImpl; + +class ObjCCallbackFunction : public InternalFunction { + friend struct APICallbackFunction; +public: + typedef InternalFunction Base; + + template + static IsoSubspace* subspaceFor(VM& vm) + { + return vm.objCCallbackFunctionSpace(); + } + + static ObjCCallbackFunction* create(VM&, JSGlobalObject*, const String& name, std::unique_ptr); + static void destroy(JSCell*); + + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + ASSERT(globalObject); + return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); + } + + DECLARE_EXPORT_INFO; + + ObjCCallbackFunctionImpl* impl() const { return m_impl.get(); } + +protected: + ObjCCallbackFunction(VM&, Structure*, JSObjectCallAsFunctionCallback, JSObjectCallAsConstructorCallback, std::unique_ptr); + +private: + JSObjectCallAsFunctionCallback functionCallback() { return m_functionCallback; } + JSObjectCallAsConstructorCallback constructCallback() { return m_constructCallback; } + + JSObjectCallAsFunctionCallback m_functionCallback; + JSObjectCallAsConstructorCallback m_constructCallback; + std::unique_ptr m_impl; +}; + +} // namespace JSC + +#endif + +#endif // ObjCCallbackFunction_h diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/ObjcRuntimeExtras.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/ObjcRuntimeExtras.h new file mode 100644 index 000000000..20d8b8584 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/ObjcRuntimeExtras.h @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2013, 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import +#import +#import +#import +#import +#import + +template +inline std::unique_ptr> adoptSystem(U value) +{ + return std::unique_ptr>(value); +} + +inline bool protocolImplementsProtocol(Protocol *candidate, Protocol *target) +{ + unsigned protocolProtocolsCount; + auto protocolProtocols = adoptSystem<__unsafe_unretained Protocol*[]>(protocol_copyProtocolList(candidate, &protocolProtocolsCount)); + for (unsigned i = 0; i < protocolProtocolsCount; ++i) { + if (protocol_isEqual(protocolProtocols[i], target)) + return true; + } + return false; +} + +inline void forEachProtocolImplementingProtocol(Class cls, Protocol *target, void (^callback)(Protocol *, bool& stop)) +{ + ASSERT(cls); + ASSERT(target); + + Vector worklist; + HashSet visited; + + // Initially fill the worklist with the Class's protocols. + { + unsigned protocolsCount; + auto protocols = adoptSystem<__unsafe_unretained Protocol*[]>(class_copyProtocolList(cls, &protocolsCount)); + worklist.append(protocols.get(), protocolsCount); + } + + bool stop = false; + while (!worklist.isEmpty()) { + Protocol *protocol = worklist.last(); + worklist.removeLast(); + + // Are we encountering this Protocol for the first time? + if (!visited.add((__bridge void*)protocol).isNewEntry) + continue; + + // If it implements the protocol, make the callback. + if (protocolImplementsProtocol(protocol, target)) { + callback(protocol, stop); + if (stop) + break; + } + + // Add incorporated protocols to the worklist. + { + unsigned protocolsCount; + auto protocols = adoptSystem<__unsafe_unretained Protocol*[]>(protocol_copyProtocolList(protocol, &protocolsCount)); + worklist.append(protocols.get(), protocolsCount); + } + } +} + +inline void forEachMethodInClass(Class cls, void (^callback)(Method)) +{ + unsigned count; + auto methods = adoptSystem(class_copyMethodList(cls, &count)); + for (unsigned i = 0; i < count; ++i) + callback(methods[i]); +} + +inline void forEachMethodInProtocol(Protocol *protocol, BOOL isRequiredMethod, BOOL isInstanceMethod, void (^callback)(SEL, const char*)) +{ + unsigned count; + auto methods = adoptSystem(protocol_copyMethodDescriptionList(protocol, isRequiredMethod, isInstanceMethod, &count)); + for (unsigned i = 0; i < count; ++i) + callback(methods[i].name, methods[i].types); +} + +inline void forEachPropertyInProtocol(Protocol *protocol, void (^callback)(objc_property_t)) +{ + unsigned count; + auto properties = adoptSystem(protocol_copyPropertyList(protocol, &count)); + for (unsigned i = 0; i < count; ++i) + callback(properties[i]); +} + +template +void skipPair(const char*& position) +{ + size_t count = 1; + do { + char c = *position++; + if (!c) + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Malformed type encoding" userInfo:nil]; + if (c == open) + ++count; + else if (c == close) + --count; + } while (count); +} + +class StringRange { + WTF_MAKE_NONCOPYABLE(StringRange); +public: + StringRange(const char* begin, const char* end) + : m_string(begin, end - begin) + { } + operator const char*() const { return m_string.data(); } + const char* get() const { return m_string.data(); } + +private: + CString m_string; +}; + +class StructBuffer { + WTF_MAKE_NONCOPYABLE(StructBuffer); +public: + StructBuffer(const char* encodedType) + { + NSUInteger size, alignment; + NSGetSizeAndAlignment(encodedType, &size, &alignment); + m_buffer = fastAlignedMalloc(alignment, size); + } + + ~StructBuffer() { fastAlignedFree(m_buffer); } + operator void*() const { return m_buffer; } + +private: + void* m_buffer; +}; + +template +typename DelegateType::ResultType parseObjCType(const char*& position) +{ + ASSERT(*position); + + switch (*position++) { + case 'c': + return DelegateType::template typeInteger(); + case 'i': + return DelegateType::template typeInteger(); + case 's': + return DelegateType::template typeInteger(); + case 'l': + return DelegateType::template typeInteger(); + case 'q': + return DelegateType::template typeDouble(); + case 'C': + return DelegateType::template typeInteger(); + case 'I': + return DelegateType::template typeInteger(); + case 'S': + return DelegateType::template typeInteger(); + case 'L': + return DelegateType::template typeInteger(); + case 'Q': + return DelegateType::template typeDouble(); + case 'f': + return DelegateType::template typeDouble(); + case 'd': + return DelegateType::template typeDouble(); + case 'B': + return DelegateType::typeBool(); + case 'v': + return DelegateType::typeVoid(); + + case '@': { // An object (whether statically typed or typed id) + if (position[0] == '?' && position[1] == '<') { + position += 2; + const char* begin = position; + skipPair<'<','>'>(position); + return DelegateType::typeBlock(begin, position - 1); + } + + if (*position == '"') { + const char* begin = position + 1; + const char* protocolPosition = strchr(begin, '<'); + const char* endOfType = strchr(begin, '"'); + position = endOfType + 1; + + // There's no protocol involved in this type, so just handle the class name. + if (!protocolPosition || protocolPosition > endOfType) + return DelegateType::typeOfClass(begin, endOfType); + // We skipped the class name and went straight to the protocol, so this is an id type. + if (begin == protocolPosition) + return DelegateType::typeId(); + // We have a class name with a protocol. For now, ignore the protocol. + return DelegateType::typeOfClass(begin, protocolPosition); + } + + return DelegateType::typeId(); + } + + case '{': { // {name=type...} A structure + const char* begin = position - 1; + skipPair<'{','}'>(position); + return DelegateType::typeStruct(begin, position); + } + + // NOT supporting C strings, arrays, pointers, unions, bitfields, function pointers. + case '*': // A character string (char *) + case '[': // [array type] An array + case '(': // (name=type...) A union + case 'b': // bnum A bit field of num bits + case '^': // ^type A pointer to type + case '?': // An unknown type (among other things, this code is used for function pointers) + // NOT supporting Objective-C Class, SEL + case '#': // A class object (Class) + case ':': // A method selector (SEL) + default: + return nil; + } +} + +extern "C" { + // Forward declare some Objective-C runtime internal methods that are not API. + const char *_protocol_getMethodTypeEncoding(Protocol *, SEL, BOOL isRequiredMethod, BOOL isInstanceMethod); + id objc_initWeak(id *, id); + void objc_destroyWeak(id *); + bool _Block_has_signature(void *); + const char * _Block_signature(void *); +} diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/OpaqueJSString.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/OpaqueJSString.h new file mode 100644 index 000000000..4a4b5edfb --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/OpaqueJSString.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OpaqueJSString_h +#define OpaqueJSString_h + +#include +#include +#include + +namespace JSC { + class Identifier; + class VM; +} + +struct OpaqueJSString : public ThreadSafeRefCounted { + static Ref create() + { + return adoptRef(*new OpaqueJSString); + } + + static Ref create(const LChar* characters, unsigned length) + { + return adoptRef(*new OpaqueJSString(characters, length)); + } + + static Ref create(const UChar* characters, unsigned length) + { + return adoptRef(*new OpaqueJSString(characters, length)); + } + + JS_EXPORT_PRIVATE static RefPtr tryCreate(const String&); + JS_EXPORT_PRIVATE static RefPtr tryCreate(String&&); + + JS_EXPORT_PRIVATE ~OpaqueJSString(); + + bool is8Bit() { return m_string.is8Bit(); } + const LChar* characters8() { return m_string.characters8(); } + const UChar* characters16() { return m_string.characters16(); } + unsigned length() { return m_string.length(); } + + const UChar* characters(); + + JS_EXPORT_PRIVATE String string() const; + JSC::Identifier identifier(JSC::VM*) const; + + static bool equal(const OpaqueJSString*, const OpaqueJSString*); + +private: + friend class WTF::ThreadSafeRefCounted; + + OpaqueJSString() + : m_characters(nullptr) + { + } + + OpaqueJSString(const String& string) + : m_string(string.isolatedCopy()) + , m_characters(m_string.impl() && m_string.is8Bit() ? nullptr : const_cast(m_string.characters16())) + { + } + + explicit OpaqueJSString(String&& string) + : m_string(WTFMove(string)) + , m_characters(m_string.impl() && m_string.is8Bit() ? nullptr : const_cast(m_string.characters16())) + { + } + + OpaqueJSString(const LChar* characters, unsigned length) + : m_string(characters, length) + , m_characters(nullptr) + { + } + + OpaqueJSString(const UChar* characters, unsigned length) + : m_string(characters, length) + , m_characters(m_string.impl() && m_string.is8Bit() ? nullptr : const_cast(m_string.characters16())) + { + } + + String m_string; + + // This will be initialized on demand when characters() is called if the string needs up-conversion. + std::atomic m_characters; +}; + +#endif diff --git a/NativeScript/napi/android/jsc/include/JavaScriptCore/WebKitAvailability.h b/NativeScript/napi/android/jsc/include/JavaScriptCore/WebKitAvailability.h new file mode 100644 index 000000000..2a1ce7ce1 --- /dev/null +++ b/NativeScript/napi/android/jsc/include/JavaScriptCore/WebKitAvailability.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008, 2009, 2010, 2014 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __WebKitAvailability__ +#define __WebKitAvailability__ + +#if defined(__APPLE__) + +#include +#include + +#if defined(BUILDING_GTK__) +#undef JSC_API_AVAILABLE +#define JSC_API_AVAILABLE(...) +#endif + +#else +#define JSC_API_AVAILABLE(...) +#endif + +#endif /* __WebKitAvailability__ */ diff --git a/NativeScript/napi/android/jsc/jsc-api.cpp b/NativeScript/napi/android/jsc/jsc-api.cpp new file mode 100644 index 000000000..bb79440f4 --- /dev/null +++ b/NativeScript/napi/android/jsc/jsc-api.cpp @@ -0,0 +1,2732 @@ +#include "jsc-api.h" +#include "../../common/jsc_type_tag.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct napi_callback_info__ { + napi_value newTarget; + napi_value thisArg; + napi_value* argv; + void* data; + uint16_t argc; +}; + +namespace { +class JSString { + public: + JSString(const JSString&) = delete; + + JSString(JSString&& other) { + _string = other._string; + other._string = nullptr; + } + + JSString(const char* string, size_t length = NAPI_AUTO_LENGTH) + : _string{CreateUTF8(string, length)} { + } + + JSString(const JSChar* string, size_t length = NAPI_AUTO_LENGTH) + : _string{JSStringCreateWithCharacters(string, length == NAPI_AUTO_LENGTH ? std::char_traits::length(string) : length)} { + } + + ~JSString() { + if (_string != nullptr) { + JSStringRelease(_string); + } + } + + static JSString Attach(JSStringRef string) { + return {string}; + } + + operator JSStringRef() const { + return _string; + } + + size_t Length() const { + return JSStringGetLength(_string); + } + + size_t LengthUTF8() const { + std::vector buffer(JSStringGetMaximumUTF8CStringSize(_string)); + return JSStringGetUTF8CString(_string, buffer.data(), buffer.size()) - 1; + } + + size_t LengthLatin1() const { + // Latin1 has the same length as Unicode. + return JSStringGetLength(_string); + } + + void CopyTo(JSChar* buf, size_t bufsize, size_t* result) const { + size_t length{JSStringGetLength(_string)}; + const JSChar* chars{JSStringGetCharactersPtr(_string)}; + size_t size{std::min(length, bufsize - 1)}; + std::memcpy(buf, chars, size); + buf[size] = 0; + if (result != nullptr) { + *result = size; + } + } + + void CopyToUTF8(char* buf, size_t bufsize, size_t* result) const { + size_t size{JSStringGetUTF8CString(_string, buf, bufsize)}; + if (result != nullptr) { + // JSStringGetUTF8CString returns size with null terminator. + *result = size - 1; + } + } + + void CopyToLatin1(char* buf, size_t bufsize, size_t* result) const { + size_t length{JSStringGetLength(_string)}; + const JSChar* chars{JSStringGetCharactersPtr(_string)}; + size_t size{std::min(length, bufsize - 1)}; + for (int i = 0; i < size; ++i) { + const JSChar ch{chars[i]}; + buf[i] = (ch < 256) ? ch : '?'; + } + if (result != nullptr) { + *result = size; + } + } + + private: + static JSStringRef CreateUTF8(const char* string, size_t length) { + if (length == NAPI_AUTO_LENGTH) { + return JSStringCreateWithUTF8CString(string); + } + + std::u16string u16str{std::wstring_convert< + std::codecvt_utf8_utf16, char16_t>{}.from_bytes(string, string + length)}; + return JSStringCreateWithCharacters(reinterpret_cast(u16str.data()), u16str.size()); + } + + JSString(JSStringRef string) + : _string{string} { + } + + JSStringRef _string; +}; + +inline JSValueRef ToJSValue(const napi_value value) { + return reinterpret_cast(value); +} + +inline const JSValueRef* ToJSValues(const napi_value* values) { + return reinterpret_cast(values); +} + +inline JSObjectRef ToJSObject(napi_env env, const napi_value value) { + assert(value == nullptr || JSValueIsObject(env->context, reinterpret_cast(value))); + return reinterpret_cast(value); +} + +inline JSString ToJSString(napi_env env, napi_value value, JSValueRef* exception) { + return JSString::Attach(JSValueToStringCopy(env->context, ToJSValue(value), exception)); +} + +inline napi_value ToNapi(const JSValueRef value) { + return reinterpret_cast(const_cast(value)); +} + + static inline napi_value* ToNapi(const JSValueRef* values) { + return reinterpret_cast(const_cast(values)); +} + +napi_status napi_clear_last_error(napi_env env) { + env->last_error.error_code = napi_ok; + env->last_error.engine_error_code = 0; + env->last_error.engine_reserved = nullptr; + return napi_ok; +} + +napi_status napi_set_last_error(napi_env env, napi_status error_code, uint32_t engine_error_code = 0, void* engine_reserved = nullptr) { + env->last_error.error_code = error_code; + env->last_error.engine_error_code = engine_error_code; + env->last_error.engine_reserved = engine_reserved; + return error_code; +} + +napi_status napi_set_exception(napi_env env, JSValueRef exception) { + env->last_exception = exception; + return napi_set_last_error(env, napi_pending_exception); +} + +napi_status napi_set_error_code(napi_env env, + napi_value error, + napi_value code, + const char* code_cstring) { + napi_value code_value{code}; + if (code_value == nullptr) { + code_value = ToNapi(JSValueMakeString(env->context, JSString(code_cstring))); + } else { + RETURN_STATUS_IF_FALSE(env, JSValueIsString(env->context, ToJSValue(code_value)), napi_string_expected); + } + + CHECK_NAPI(napi_set_named_property(env, error, "code", code_value)); + return napi_ok; +} + +enum class NativeType { + Constructor, + External, + Function, + Reference, + Wrapper, +}; + +class NativeInfo { + public: + NativeType Type() const { + return _type; + } + + template + static T* Get(JSObjectRef obj) { + return reinterpret_cast(JSObjectGetPrivate(obj)); + } + + template + static T* FindInPrototypeChain(JSContextRef ctx, JSObjectRef obj) { + while (true) { + JSValueRef exception{}; + JSObjectRef prototype = JSValueToObject(ctx, JSObjectGetPrototype(ctx, obj), &exception); + if (exception != nullptr) { + return nullptr; + } + + NativeInfo* info = Get(prototype); + if (info != nullptr && info->Type() == T::StaticType) { + return reinterpret_cast(info); + } + + obj = prototype; + } + } + + template + static T* GetNativeInfo(JSContextRef ctx, JSObjectRef obj, const char * propertyKey) { + JSValueRef exception {}; + JSValueRef native_info = JSObjectGetProperty(ctx, obj, JSString(propertyKey), &exception); + + NativeInfo* info = Get(JSValueToObject(ctx, native_info, &exception)); + if (info != nullptr && info->Type() == T::StaticType) { + return reinterpret_cast(info); + } + return nullptr; + } + + static void SetNativeInfoKey(JSContextRef ctx, JSObjectRef obj, JSClassRef classRef,JSValueRef propertyKey, void * data) { + JSObjectRef info{JSObjectMake(ctx, classRef, data)}; + JSObjectSetPropertyForKey(ctx, obj, propertyKey, info, kJSPropertyAttributeDontEnum | kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, nullptr); + } + + static void SetNativeInfo(JSContextRef ctx, JSObjectRef obj, JSClassRef classRef, const char * propertyKey, void * data) { + JSObjectRef info{JSObjectMake(ctx, classRef, data)}; + JSObjectSetProperty(ctx, obj, JSString(propertyKey), info, kJSPropertyAttributeDontEnum | kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, nullptr); + } + + protected: + NativeInfo(NativeType type) + : _type{type} {} + + private: + NativeType _type; +}; + +class ConstructorInfo : public NativeInfo { + public: + static const NativeType StaticType = NativeType::Constructor; + + static napi_status Create(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* data, + napi_value* result) { + ConstructorInfo* info{new ConstructorInfo(env, utf8name, length, cb, data)}; + if (info == nullptr) { + return napi_set_last_error(env, napi_generic_failure); + } + + JSObjectRef constructor{JSObjectMakeConstructor(env->context, info->_class, CallAsConstructor)}; + JSValueRef exception{}; + if (length) { + napi_value name; + napi_create_string_utf8(env, utf8name, length, &name); + JSObjectSetProperty(env->context, constructor, JSString("name"), ToJSValue(name), kJSPropertyAttributeNone, &exception); + } + JSObjectRef prototype{(JSObjectRef) JSObjectGetProperty(env->context, constructor, JSString("prototype"), &exception)}; + +// JSObjectSetPrototype(env->context, prototype, JSObjectGetPrototype(env->context, constructor)); +// JSObjectSetPrototype(env->context, constructor, prototype); + + NativeInfo::SetNativeInfo(env->context, constructor, info->_class, "[[jsc_constructor_info]]", info); + + JSObjectSetProperty(env->context, prototype, JSString("constructor"), constructor, + kJSPropertyAttributeNone, &exception); + CHECK_JSC(env, exception); + + *result = ToNapi(constructor); + return napi_ok; + } + + private: + ConstructorInfo(napi_env env, const char* name, size_t length, napi_callback cb, void* data) + : NativeInfo{NativeType::Constructor} + , _env{env} + , _name{name, (length == NAPI_AUTO_LENGTH ? std::strlen(name) : length)} + , _cb{cb} + , _data{data} { + + JSClassDefinition classDefinition{kJSClassDefinitionEmpty}; + classDefinition.className = _name.data(); + classDefinition.finalize = Finalize; + _class = JSClassCreate(&classDefinition); + } + + ~ConstructorInfo() { + JSClassRelease(_class); + } + + // JSObjectCallAsConstructorCallback + static JSObjectRef CallAsConstructor(JSContextRef ctx, + JSObjectRef constructor, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef* exception) { +// ConstructorInfo* info = NativeInfo::FindInPrototypeChain(ctx, constructor); + ConstructorInfo* info = NativeInfo::GetNativeInfo(ctx, constructor, "[[jsc_constructor_info]]"); + + // Make sure any errors encountered last time we were in N-API are gone. + napi_clear_last_error(info->_env); + + JSObjectRef instance{JSObjectMake(ctx, info->_class, nullptr)}; + +// JSObjectSetPrototype(ctx, instance, JSObjectGetPrototype(ctx, constructor)); +// +// JSObjectSetProperty(ctx, instance, JSString("prototype"), JSObjectGetProperty(ctx, constructor, JSString("prototype"), nullptr), kJSPropertyAttributeNone, +// nullptr); + + napi_callback_info__ cbinfo{}; + cbinfo.thisArg = ToNapi(instance); + cbinfo.newTarget = ToNapi(constructor); + cbinfo.argc = argumentCount; + cbinfo.argv = ToNapi(arguments); + cbinfo.data = info->_data; + + napi_value result = info->_cb(info->_env, &cbinfo); + + if (info->_env->last_exception != nullptr) { + *exception = info->_env->last_exception; + info->_env->last_exception = nullptr; + } + + return ToJSObject(info->_env, result); + } + + // JSObjectFinalizeCallback + static void Finalize(JSObjectRef object) { + ConstructorInfo* info = NativeInfo::Get(object); + if (!info) return; + assert(info->Type() == NativeType::Constructor); + delete info; + } + + private: + napi_env _env; + std::string _name; + napi_callback _cb; + void* _data; + JSClassRef _class; +}; + +namespace xyz { + static std::once_flag functionInfoOnceFlag; + JSClassRef functionInfoClass {}; +} + +class FunctionInfo : public NativeInfo { + public: + static const NativeType StaticType = NativeType::Function; + + static napi_status Create(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* data, + napi_value* result) { + FunctionInfo* info{new FunctionInfo(env, cb, data)}; + if (info == nullptr) { + return napi_set_last_error(env, napi_generic_failure); + } + JSObjectRef function = JSObjectMake(env->context, xyz::functionInfoClass, info); + *result = ToNapi(function); + return napi_ok; + } + + + private: + FunctionInfo(napi_env env, napi_callback cb, void* data) + : NativeInfo{NativeType::Function} + , _env{env} + , _cb{cb} + , _data{data} { + std::call_once(xyz::functionInfoOnceFlag, []() { + JSClassDefinition definition{kJSClassDefinitionEmpty}; + definition.className = "NapiFunctionCallback"; + definition.callAsFunction = FunctionInfo::CallAsFunction; + definition.attributes = kJSClassAttributeNoAutomaticPrototype; + definition.initialize = FunctionInfo::initialize; + definition.finalize = Finalize; + xyz::functionInfoClass = JSClassCreate(&definition); + }); + + } + + ~FunctionInfo() {} + + static void initialize(JSContextRef ctx, JSObjectRef object) { + JSObjectRef global = JSContextGetGlobalObject(ctx); + JSValueRef value = + JSObjectGetProperty(ctx, global, JSString("Function"), nullptr); + JSObjectRef funcCtor = JSValueToObject(ctx, value, nullptr); + if (!funcCtor) { + // We can't do anything if Function is not an object + return; + } + JSValueRef funcProto = JSObjectGetPrototype(ctx, funcCtor); + JSObjectSetPrototype(ctx, object, funcProto); + } + + // JSObjectCallAsFunctionCallback + static JSValueRef CallAsFunction(JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef* exception) { +// FunctionInfo* info = NativeInfo::Get(function); + FunctionInfo* info = reinterpret_cast(JSObjectGetPrivate(function)); + + // Make sure any errors encountered last time we were in N-API are gone. + napi_clear_last_error(info->_env); + + napi_callback_info__ cbinfo{}; + cbinfo.thisArg = ToNapi(thisObject); + cbinfo.newTarget = nullptr; + cbinfo.argc = argumentCount; + cbinfo.argv = ToNapi(arguments); + + cbinfo.data = info->_data; + + napi_value result = info->_cb(info->_env, &cbinfo); + + if (info->_env->last_exception != nullptr) { + *exception = info->_env->last_exception; + info->_env->last_exception = nullptr; + } + + return ToJSValue(result); + } + + // JSObjectFinalizeCallback + static void Finalize(JSObjectRef object) { + FunctionInfo* info = NativeInfo::Get(object); + assert(info->Type() == NativeType::Function); + delete info; + } + + napi_env _env; + napi_callback _cb; + void* _data; +}; + + +template +class BaseInfoT : public NativeInfo { + public: + static const NativeType StaticType = TType; + + ~BaseInfoT() = default; + + napi_env Env() const { + return _env; + } + + void Data(void* value) { + _data = value; + } + + void* Data() const { + return _data; + } + + using FinalizerT = std::function; + void AddFinalizer(FinalizerT finalizer) { + _finalizers.push_back(finalizer); + } + + protected: + BaseInfoT(napi_env env, const char* className) + : NativeInfo{TType} + , _env{env} + , _class{SharedClass(className)} { + } + + // JSObjectFinalizeCallback + static void Finalize(JSObjectRef object) { + T* info = Get(object); + assert(info->Type() == TType); + for (const FinalizerT& finalizer : info->_finalizers) { + finalizer(info); + } + delete info; + } + + static JSClassRef SharedClass(const char* className) { + struct SharedClassHolder { + explicit SharedClassHolder(const char* name) { + JSClassDefinition definition{kJSClassDefinitionEmpty}; + definition.className = name; + definition.finalize = BaseInfoT::Finalize; + value = JSClassCreate(&definition); + } + ~SharedClassHolder() { + JSClassRelease(value); + } + + JSClassRef value = nullptr; + }; + static SharedClassHolder sharedClass(className); + return sharedClass.value; + } + + napi_env _env; + void* _data{}; + std::vector _finalizers{}; + JSClassRef _class{}; +}; + +class ExternalInfo: public BaseInfoT { + public: + static napi_status Create(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + ExternalInfo* info = new ExternalInfo(env); + if (info == nullptr) { + return napi_set_last_error(env, napi_generic_failure); + } + + info->Data(data); + + if (finalize_cb != nullptr) { + info->AddFinalizer([finalize_cb, finalize_hint](ExternalInfo* info) { + finalize_cb(info->Env(), info->Data(), finalize_hint); + }); + } + + *result = ToNapi(JSObjectMake(env->context, info->_class, info)); + return napi_ok; + } + + private: + ExternalInfo(napi_env env) + : BaseInfoT{env, "Native (External)"} { + } +}; + +class ReferenceInfo : public BaseInfoT { + public: + static napi_status Initialize(napi_env env, napi_value object, FinalizerT finalizer) { + + napi_valuetype type; + napi_typeof(env, object, &type); + + if (type == napi_object || type == napi_function) { + ReferenceInfo* info = new ReferenceInfo(env); + if (info == nullptr) { + return napi_set_last_error(env, napi_generic_failure); + } + + // JSObjectRef ref{JSObjectMake(env->context, info->_class, info)}; + // JSObjectSetPrototype(env->context, prototype, JSObjectGetPrototype(env->context, ToJSObject(env, object))); + // JSObjectSetPrototype(env->context, ToJSObject(env, object), prototype); + + NativeInfo::SetNativeInfo(env->context, ToJSObject(env, object), info->_class, "[[jsc_reference_info]]", info); + + info->AddFinalizer(finalizer); + } + + return napi_ok; + } + + private: + ReferenceInfo(napi_env env) + : BaseInfoT{env, "Native (Reference)"} { + } +}; + +class WrapperInfo : public BaseInfoT { + public: + static napi_status Wrap(napi_env env, napi_value object, WrapperInfo** result) { + WrapperInfo* info{}; + + napi_value propertyKey; + napi_create_string_utf8(env, "[[jsc_wrapper_info]]", NAPI_AUTO_LENGTH, &propertyKey); + bool hasOwnProperty; + napi_has_own_property(env, object, propertyKey, &hasOwnProperty); + + if (hasOwnProperty) { + return napi_generic_failure; +// CHECK_NAPI(Unwrap(env, object, &info)); + } + + if (info == nullptr) { + info = new WrapperInfo(env); + if (info == nullptr) { + return napi_set_last_error(env, napi_generic_failure); + } + + NativeInfo::SetNativeInfoKey(env->context, ToJSObject(env, object), info->_class, ToJSValue(propertyKey), info); + } + + *result = info; + return napi_ok; + } + + static napi_status Unwrap(napi_env env, napi_value object, WrapperInfo** result) { + *result = NativeInfo::GetNativeInfo(env->context, ToJSObject(env, object), "[[jsc_wrapper_info]]"); + return napi_ok; + } + + private: + WrapperInfo(napi_env env) + : BaseInfoT{env, "Native (Wrapper)"} { + } +}; + +class ExternalArrayBufferInfo { + public: + static napi_status Create(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + ExternalArrayBufferInfo* info{new ExternalArrayBufferInfo(env, finalize_cb, finalize_hint)}; + if (info == nullptr) { + return napi_set_last_error(env, napi_generic_failure); + } + + JSValueRef exception{}; + *result = ToNapi(JSObjectMakeArrayBufferWithBytesNoCopy( + env->context, + external_data, + byte_length, + BytesDeallocator, + info, + &exception)); + CHECK_JSC(env, exception); + + return napi_ok; + } + + private: + ExternalArrayBufferInfo(napi_env env, napi_finalize finalize_cb, void* hint) + : _env{env} + , _cb{finalize_cb} + , _hint{hint} { + } + + // JSTypedArrayBytesDeallocator + static void BytesDeallocator(void* bytes, void* deallocatorContext) { + ExternalArrayBufferInfo* info{reinterpret_cast(deallocatorContext)}; + if (info->_cb != nullptr) { + info->_cb(info->_env, bytes, info->_hint); + } + delete info; + } + + napi_env _env; + napi_finalize _cb; + void* _hint; +}; +} + +struct napi_ref__ { + napi_ref__(napi_value value, uint32_t count) + : _value{value} + , _count{count} { + } + + napi_status init(napi_env env) { + // track the ref values to support weak refs + auto pair{env->active_ref_values.insert(_value)}; + if (pair.second) { + CHECK_NAPI(ReferenceInfo::Initialize(env, _value, [value = _value](ReferenceInfo* info) { + info->Env()->active_ref_values.erase(value); + })); + } + + if (_count != 0) { + protect(env); + } + + return napi_ok; + } + + void deinit(napi_env env) { + if (_count != 0) { + unprotect(env); + } + + _value = nullptr; + _count = 0; + } + + void ref(napi_env env) { + if (_count++ == 0) { + protect(env); + } + } + + void unref(napi_env env) { + if (--_count == 0) { + unprotect(env); + } + } + + uint32_t count() const { + return _count; + } + + napi_value value(napi_env env) const { + if (env->active_ref_values.find(_value) == env->active_ref_values.end()) { + return nullptr; + } + + return _value; + } + + private: + void protect(napi_env env) { + + _iter = env->strong_refs.insert(env->strong_refs.end(), this); + JSValueProtect(env->context, ToJSValue(_value)); + } + + void unprotect(napi_env env) { + env->strong_refs.erase(_iter); + JSValueUnprotect(env->context, ToJSValue(_value)); + } + + napi_value _value{}; + uint32_t _count{}; + std::list::iterator _iter{}; +}; + +void napi_env__::deinit_refs() { + while (!strong_refs.empty()) { + napi_ref ref{strong_refs.front()}; + ref->deinit(this); + } +} + +void napi_env__::init_symbol(JSValueRef &symbol, const char *description) { + symbol = JSValueMakeSymbol(context, JSString(description)); + JSValueProtect(context, symbol); +} + +void napi_env__::deinit_symbol(JSValueRef symbol) { + JSValueUnprotect(context, symbol); +} + +// Warning: Keep in-sync with napi_status enum +static const char* error_messages[] = { + nullptr, + "Invalid argument", + "An object was expected", + "A string was expected", + "A string or symbol was expected", + "A function was expected", + "A number was expected", + "A boolean was expected", + "An array was expected", + "Unknown failure", + "An exception is pending", + "The async work item was cancelled", + "napi_escape_handle already called on scope", + "Invalid handle scope usage", + "Invalid callback scope usage", + "Thread-safe function queue is full", + "Thread-safe function handle is closing", + "A bigint was expected", +}; + +napi_status napi_get_last_error_info(napi_env env, + const napi_extended_error_info** result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + // you must update this assert to reference the last message + // in the napi_status enum each time a new error message is added. + // We don't have a napi_status_last as this would result in an ABI + // change each time a message was added. + static_assert( + std::size(error_messages) == napi_bigint_expected + 1, + "Count of error messages must match count of error values"); + assert(env->last_error.error_code <= napi_callback_scope_mismatch); + + // Wait until someone requests the last error information to fetch the error + // message string + env->last_error.error_message = + error_messages[env->last_error.error_code]; + + *result = &env->last_error; + return napi_ok; +} + +napi_status napi_create_function(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* callback_data, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + CHECK_NAPI(FunctionInfo::Create(env, utf8name, length, cb, callback_data, result)); + return napi_ok; +} + +napi_status napi_define_class(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* data, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + napi_value constructor{}; + CHECK_NAPI(ConstructorInfo::Create(env, utf8name, length, cb, data, &constructor)); + + int instancePropertyCount{0}; + int staticPropertyCount{0}; + for (size_t i = 0; i < property_count; i++) { + if ((properties[i].attributes & napi_static) != 0) { + staticPropertyCount++; + } else { + instancePropertyCount++; + } + } + + std::vector staticDescriptors{}; + std::vector instanceDescriptors{}; + staticDescriptors.reserve(staticPropertyCount); + instanceDescriptors.reserve(instancePropertyCount); + + for (size_t i = 0; i < property_count; i++) { + if ((properties[i].attributes & napi_static) != 0) { + staticDescriptors.push_back(properties[i]); + } else { + instanceDescriptors.push_back(properties[i]); + } + } + + if (staticPropertyCount > 0) { + CHECK_NAPI(napi_define_properties(env, + constructor, + staticDescriptors.size(), + staticDescriptors.data())); + } + + if (instancePropertyCount > 0) { + napi_value prototype{}; + CHECK_NAPI(napi_get_named_property(env, constructor, "prototype", &prototype)); +// CHECK_NAPI(napi_get_prototype(env, constructor, &prototype)); + + CHECK_NAPI(napi_define_properties(env, + prototype, + instanceDescriptors.size(), + instanceDescriptors.data())); + } + + *result = constructor; + return napi_ok; +} + +napi_status napi_get_property_names(napi_env env, + napi_value object, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + napi_value global{}, object_ctor{}, function{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Object", &object_ctor)); + CHECK_NAPI(napi_get_named_property(env, object_ctor, "getOwnPropertyNames", &function)); + CHECK_NAPI(napi_call_function(env, object_ctor, function, 0, nullptr, result)); + + return napi_ok; +} + +napi_status napi_set_property(napi_env env, + napi_value object, + napi_value key, + napi_value value) { + CHECK_ENV(env); + CHECK_ARG(env, key); + CHECK_ARG(env, value); + + JSValueRef exception{}; + JSString key_str{ToJSString(env, key, &exception)}; + CHECK_JSC(env, exception); + + JSObjectSetProperty( + env->context, + ToJSObject(env, object), + key_str, + ToJSValue(value), + kJSPropertyAttributeNone, + &exception); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_has_property(napi_env env, + napi_value object, + napi_value key, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + CHECK_ARG(env, key); + + JSValueRef exception{}; + JSString key_str{ToJSString(env, key, &exception)}; + CHECK_JSC(env, exception); + + *result = JSObjectHasProperty( + env->context, + ToJSObject(env, object), + key_str); + return napi_ok; +} + +napi_status napi_get_property(napi_env env, + napi_value object, + napi_value key, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, key); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSString key_str{ToJSString(env, key, &exception)}; + CHECK_JSC(env, exception); + + *result = ToNapi(JSObjectGetProperty( + env->context, + ToJSObject(env, object), + key_str, + &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSString key_str{ToJSString(env, key, &exception)}; + CHECK_JSC(env, exception); + + *result = JSObjectDeleteProperty( + env->context, + ToJSObject(env, object), + key_str, + &exception); + CHECK_JSC(env, exception); + + return napi_ok; +} + +NAPI_EXTERN napi_status napi_has_own_property(napi_env env, +napi_value object, + napi_value key, +bool* result) { +CHECK_ENV(env); +CHECK_ARG(env, result); + +napi_value global{}, object_ctor{}, function{}, value{}; +CHECK_NAPI(napi_get_global(env, &global)); +CHECK_NAPI(napi_get_named_property(env, global, "Object", &object_ctor)); +CHECK_NAPI(napi_get_named_property(env, object_ctor, "hasOwnProperty", &function)); +CHECK_NAPI(napi_call_function(env, object_ctor, function, 0, nullptr, &value)); +*result = JSValueToBoolean(env->context, ToJSValue(value)); + +return napi_ok; +} + +napi_status napi_set_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value value) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + JSValueRef exception{}; + JSObjectSetProperty( + env->context, + ToJSObject(env, object), + JSString(utf8name), + ToJSValue(value), + kJSPropertyAttributeNone, + &exception); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_has_named_property(napi_env env, + napi_value object, + const char* utf8name, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + + *result = JSObjectHasProperty( + env->context, + ToJSObject(env, object), + JSString(utf8name)); + + return napi_ok; +} + +napi_status napi_get_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + + JSValueRef exception{}; + + *result = ToNapi(JSObjectGetProperty( + env->context, + ToJSObject(env, object), + JSString(utf8name), + &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_set_element(napi_env env, + napi_value object, + uint32_t index, + napi_value value) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + JSValueRef exception{}; + JSObjectSetPropertyAtIndex( + env->context, + ToJSObject(env, object), + index, + ToJSValue(value), + &exception); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_has_element(napi_env env, + napi_value object, + uint32_t index, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSValueRef value{JSObjectGetPropertyAtIndex( + env->context, + ToJSObject(env, object), + index, + &exception)}; + CHECK_JSC(env, exception); + + *result = !JSValueIsUndefined(env->context, value); + return napi_ok; +} + +napi_status napi_get_element(napi_env env, + napi_value object, + uint32_t index, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValueRef exception{}; + *result = ToNapi(JSObjectGetPropertyAtIndex( + env->context, + ToJSObject(env, object), + index, + &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + napi_value index_value{ToNapi(JSValueMakeNumber(env->context, index))}; + + JSValueRef exception{}; + JSString index_str{ToJSString(env, index_value, &exception)}; + CHECK_JSC(env, exception); + + *result = JSObjectDeleteProperty( + env->context, + ToJSObject(env, object), + index_str, + &exception); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_define_properties(napi_env env, + napi_value object, + size_t property_count, + const napi_property_descriptor* properties) { + CHECK_ENV(env); + if (property_count > 0) { + CHECK_ARG(env, properties); + } + + for (size_t i = 0; i < property_count; i++) { + const napi_property_descriptor* p{properties + i}; + + napi_value descriptor{}; + CHECK_NAPI(napi_create_object(env, &descriptor)); + + napi_value configurable{}; + CHECK_NAPI(napi_get_boolean(env, (p->attributes & napi_configurable), &configurable)); + CHECK_NAPI(napi_set_named_property(env, descriptor, "configurable", configurable)); + + napi_value enumerable{}; + CHECK_NAPI(napi_get_boolean(env, (p->attributes & napi_configurable), &enumerable)); + CHECK_NAPI(napi_set_named_property(env, descriptor, "enumerable", enumerable)); + + if (p->getter != nullptr || p->setter != nullptr) { + if (p->getter != nullptr) { + napi_value getter{}; + CHECK_NAPI(napi_create_function(env, p->utf8name, NAPI_AUTO_LENGTH, p->getter, p->data, &getter)); + CHECK_NAPI(napi_set_named_property(env, descriptor, "get", getter)); + } + if (p->setter != nullptr) { + napi_value setter{}; + CHECK_NAPI(napi_create_function(env, p->utf8name, NAPI_AUTO_LENGTH, p->setter, p->data, &setter)); + CHECK_NAPI(napi_set_named_property(env, descriptor, "set", setter)); + } + } else if (p->method != nullptr) { + napi_value method{}; + CHECK_NAPI(napi_create_function(env, p->utf8name, NAPI_AUTO_LENGTH, p->method, p->data, &method)); + CHECK_NAPI(napi_set_named_property(env, descriptor, "value", method)); + } else { + RETURN_STATUS_IF_FALSE(env, p->value != nullptr, napi_invalid_arg); + + napi_value writable{}; + CHECK_NAPI(napi_get_boolean(env, (p->attributes & napi_writable), &writable)); + CHECK_NAPI(napi_set_named_property(env, descriptor, "writable", writable)); + + CHECK_NAPI(napi_set_named_property(env, descriptor, "value", p->value)); + } + + napi_value propertyName{}; + if (p->utf8name == nullptr) { + propertyName = p->name; + } else { + CHECK_NAPI(napi_create_string_utf8(env, p->utf8name, NAPI_AUTO_LENGTH, &propertyName)); + } + + napi_value global{}, object_ctor{}, function{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Object", &object_ctor)); + CHECK_NAPI(napi_get_named_property(env, object_ctor, "defineProperty", &function)); + + napi_value args[] = { object, propertyName, descriptor }; + CHECK_NAPI(napi_call_function(env, object_ctor, function, 3, args, nullptr)); + } + + return napi_ok; +} + +napi_status napi_is_array(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + *result = JSValueIsArray( + env->context, + ToJSValue(value)); + return napi_ok; +} + +napi_status napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSValueRef length = JSObjectGetProperty( + env->context, + ToJSObject(env, value), + JSString("length"), + &exception); + CHECK_JSC(env, exception); + + *result = static_cast(JSValueToNumber(env->context, length, &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, lhs); + CHECK_ARG(env, rhs); + CHECK_ARG(env, result); + *result = JSValueIsStrictEqual( + env->context, + ToJSValue(lhs), + ToJSValue(rhs)); + return napi_ok; +} + +napi_status napi_get_prototype(napi_env env, + napi_value object, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSObjectRef prototype{JSValueToObject(env->context, JSObjectGetPrototype(env->context, ToJSObject(env, object)), &exception)}; + CHECK_JSC(env, exception); + + *result = ToNapi(prototype); + return napi_ok; +} + +napi_status napi_create_object(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSObjectMake(env->context, nullptr, nullptr)); + return napi_ok; +} + +napi_status napi_create_array(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValueRef exception{}; + *result = ToNapi(JSObjectMakeArray(env->context, 0, nullptr, &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_create_array_with_length(napi_env env, + size_t length, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSObjectRef array = JSObjectMakeArray( + env->context, + 0, + nullptr, + &exception); + CHECK_JSC(env, exception); + + JSObjectSetProperty( + env->context, + array, + JSString("length"), + JSValueMakeNumber(env->context, static_cast(length)), + kJSPropertyAttributeNone, + &exception); + CHECK_JSC(env, exception); + + *result = ToNapi(array); + return napi_ok; +} + +napi_status napi_create_string_latin1(napi_env env, + const char* str, + size_t length, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeString( + env->context, + JSString(str, length))); + return napi_ok; +} + +napi_status napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeString( + env->context, + JSString(str, length))); + return napi_ok; +} + +napi_status napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + static_assert(sizeof(char16_t) == sizeof(JSChar)); + *result = ToNapi(JSValueMakeString( + env->context, + JSString(reinterpret_cast(str), length))); + return napi_ok; +} + +napi_status napi_create_double(napi_env env, + double value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeNumber(env->context, value)); + return napi_ok; +} + +napi_status napi_create_int32(napi_env env, + int32_t value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeNumber(env->context, static_cast(value))); + return napi_ok; +} + +napi_status napi_create_uint32(napi_env env, + uint32_t value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeNumber(env->context, static_cast(value))); + return napi_ok; +} + +napi_status napi_create_int64(napi_env env, + int64_t value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeNumber(env->context, static_cast(value))); + return napi_ok; +} + +napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeBoolean(env->context, value)); + return napi_ok; +} + +napi_status napi_create_symbol(napi_env env, + napi_value description, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + napi_value global{}, symbol_func{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Symbol", &symbol_func)); + CHECK_NAPI(napi_call_function(env, global, symbol_func, 1, &description, result)); + return napi_ok; +} + +napi_status napi_create_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, msg); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSValueRef args[] = { ToJSValue(msg) }; + napi_value error = ToNapi(JSObjectMakeError(env->context, 1, args, &exception)); + CHECK_JSC(env, exception); + + CHECK_NAPI(napi_set_error_code(env, error, code, nullptr)); + + *result = error; + return napi_ok; +} + +napi_status napi_create_type_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, msg); + CHECK_ARG(env, result); + + napi_value global{}, error_ctor{}, error{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "TypeError", &error_ctor)); + CHECK_NAPI(napi_new_instance(env, error_ctor, 1, &msg, &error)); + CHECK_NAPI(napi_set_error_code(env, error, code, nullptr)); + + *result = error; + return napi_ok; +} + +napi_status napi_create_range_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, msg); + CHECK_ARG(env, result); + + napi_value global{}, error_ctor{}, error{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "RangeError", &error_ctor)); + CHECK_NAPI(napi_new_instance(env, error_ctor, 1, &msg, &error)); + CHECK_NAPI(napi_set_error_code(env, error, code, nullptr)); + + *result = error; + return napi_ok; +} + +napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + // JSC does not support BigInt + JSType valueType = JSValueGetType(env->context, ToJSValue(value)); + switch (valueType) { + case kJSTypeUndefined: *result = napi_undefined; break; + case kJSTypeNull: *result = napi_null; break; + case kJSTypeBoolean: *result = napi_boolean; break; + case kJSTypeNumber: *result = napi_number; break; + case kJSTypeString: *result = napi_string; break; + case kJSTypeSymbol: *result = napi_symbol; break; + default: + JSObjectRef object{ToJSObject(env, value)}; + if (JSObjectIsFunction(env->context, object)) { + *result = napi_function; + } else { + NativeInfo* info = NativeInfo::Get(object); + if (info != nullptr && info->Type() == NativeType::External) { + *result = napi_external; + } else { + *result = napi_object; + } + } + break; + } + + return napi_ok; +} + +napi_status napi_get_undefined(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeUndefined(env->context)); + return napi_ok; +} + +napi_status napi_get_null(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeNull(env->context)); + return napi_ok; +} + +napi_status napi_get_cb_info(napi_env env, // [in] NAPI environment handle + napi_callback_info cbinfo, // [in] Opaque callback-info handle + size_t* argc, // [in-out] Specifies the size of the provided argv array + // and receives the actual count of args. + napi_value* argv, // [out] Array of values + napi_value* this_arg, // [out] Receives the JS 'this' arg for the call + void** data) { // [out] Receives the data pointer for the callback. + CHECK_ENV(env); + CHECK_ARG(env, cbinfo); + + if (argv != nullptr) { + CHECK_ARG(env, argc); + + size_t i{0}; + size_t min{std::min(*argc, static_cast(cbinfo->argc))}; + + for (; i < min; i++) { + argv[i] = cbinfo->argv[i]; + } + + if (i < *argc) { + for (; i < *argc; i++) { + argv[i] = ToNapi(JSValueMakeUndefined(env->context)); + } + } + } + + if (argc != nullptr) { + *argc = cbinfo->argc; + } + + if (this_arg != nullptr) { + *this_arg = cbinfo->thisArg; + } + + if (data != nullptr) { + *data = cbinfo->data; + } + + return napi_ok; +} + +napi_status napi_get_new_target(napi_env env, + napi_callback_info cbinfo, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, cbinfo); + CHECK_ARG(env, result); + + *result = cbinfo->newTarget; + return napi_ok; +} + +napi_status napi_call_function(napi_env env, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, recv); + if (argc > 0) { + CHECK_ARG(env, argv); + } + + JSValueRef exception{}; + JSValueRef return_value{JSObjectCallAsFunction( + env->context, + ToJSObject(env, func), + JSValueIsUndefined(env->context, ToJSValue(recv)) ? nullptr : ToJSObject(env, recv), + argc, + ToJSValues(argv), + &exception)}; + CHECK_JSC(env, exception); + + if (result != nullptr) { + *result = ToNapi(return_value); + } + + return napi_ok; +} + +napi_status napi_get_global(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = ToNapi(JSContextGetGlobalObject(env->context)); + return napi_ok; +} + +napi_status napi_throw(napi_env env, napi_value error) { + CHECK_ENV(env); + napi_status status{napi_set_exception(env, ToJSValue(error))}; + assert(status == napi_pending_exception); + return napi_ok; +} + +napi_status napi_throw_error(napi_env env, + const char* code, + const char* msg) { + CHECK_ENV(env); + napi_value code_value{ToNapi(JSValueMakeString(env->context, JSString(code)))}; + napi_value msg_value{ToNapi(JSValueMakeString(env->context, JSString(msg)))}; + napi_value error{}; + CHECK_NAPI(napi_create_error(env, code_value, msg_value, &error)); + return napi_throw(env, error); +} + +napi_status napi_throw_type_error(napi_env env, + const char* code, + const char* msg) { + CHECK_ENV(env); + napi_value code_value{ToNapi(JSValueMakeString(env->context, JSString(code)))}; + napi_value msg_value{ToNapi(JSValueMakeString(env->context, JSString(msg)))}; + napi_value error{}; + CHECK_NAPI(napi_create_type_error(env, code_value, msg_value, &error)); + return napi_throw(env, error); +} + +napi_status napi_throw_range_error(napi_env env, + const char* code, + const char* msg) { + CHECK_ENV(env); + napi_value code_value{ToNapi(JSValueMakeString(env->context, JSString(code)))}; + napi_value msg_value{ToNapi(JSValueMakeString(env->context, JSString(msg)))}; + napi_value error{}; + CHECK_NAPI(napi_create_range_error(env, code_value, msg_value, &error)); + return napi_throw(env, error); +} + +napi_status napi_is_error(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + napi_value global{}, error_ctor{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Error", &error_ctor)); + CHECK_NAPI(napi_instanceof(env, value, error_ctor, result)); + + return napi_ok; +} + +napi_status napi_get_value_double(napi_env env, napi_value value, double* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + *result = JSValueToNumber(env->context, ToJSValue(value), &exception); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_get_value_int32(napi_env env, napi_value value, int32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + double number = JSValueToNumber(env->context, ToJSValue(value), &exception); + + if (number > INT_MAX) { + *result = -1; + } else { + *result = static_cast(number); + } + + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_get_value_uint32(napi_env env, napi_value value, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + *result = static_cast(JSValueToNumber(env->context, ToJSValue(value), &exception)); + + double number = JSValueToNumber(env->context, ToJSValue(value), &exception); + if (number > UINT32_MAX) { + *result = -1; + } else { + *result = static_cast(number); + } + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_get_value_int64(napi_env env, napi_value value, int64_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + double number = JSValueToNumber(env->context, ToJSValue(value), &exception); + CHECK_JSC(env, exception); + + if (std::isfinite(number)) { + *result = static_cast(number); + } else { + *result = 0; + } + + return napi_ok; +} + +napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + *result = JSValueToBoolean(env->context, ToJSValue(value)); + return napi_ok; +} + +// Copies a JavaScript string into a LATIN-1 string buffer. The result is the +// number of bytes (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_latin1(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + JSValueRef exception{}; + JSString string{ToJSString(env, value, &exception)}; + CHECK_JSC(env, exception); + + if (buf == nullptr) { + *result = string.LengthLatin1(); + } else { + string.CopyToLatin1(buf, bufsize, result); + } + + return napi_ok; +} + +// Copies a JavaScript string into a UTF-8 string buffer. The result is the +// number of bytes (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_utf8(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + JSValueRef exception{}; + JSString string{ToJSString(env, value, &exception)}; + CHECK_JSC(env, exception); + + if (buf == nullptr) { + *result = string.LengthUTF8(); + } else { + string.CopyToUTF8(buf, bufsize, result); + } + + return napi_ok; +} + +// Copies a JavaScript string into a UTF-16 string buffer. The result is the +// number of 2-byte code units (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in 2-byte +// code units) via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + JSValueRef exception{}; + JSString string{ToJSString(env, value, &exception)}; + CHECK_JSC(env, exception); + + if (buf == nullptr) { + *result = string.Length(); + } else { + static_assert(sizeof(char16_t) == sizeof(JSChar)); + string.CopyTo(reinterpret_cast(buf), bufsize, result); + } + + return napi_ok; +} + +napi_status napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result) { + CHECK_ARG(env, result); + *result = ToNapi(JSValueMakeBoolean(env->context, + JSValueToBoolean(env->context, ToJSValue(value)))); + return napi_ok; +} + +napi_status napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + double number{JSValueToNumber(env->context, ToJSValue(value), &exception)}; + CHECK_JSC(env, exception); + + *result = ToNapi(JSValueMakeNumber(env->context, number)); + return napi_ok; +} + +napi_status napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + *result = ToNapi(JSValueToObject(env->context, ToJSValue(value), &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSString string{ToJSString(env, value, &exception)}; + CHECK_JSC(env, exception); + + *result = ToNapi(JSValueMakeString(env->context, string)); + return napi_ok; +} + +napi_status napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result) { + CHECK_ENV(env); + CHECK_ARG(env, js_object); + if (result != nullptr) { + CHECK_ARG(env, finalize_cb); + } + + WrapperInfo* info{}; + CHECK_NAPI(WrapperInfo::Wrap(env, js_object, &info)); + RETURN_STATUS_IF_FALSE(env, info->Data() == nullptr, napi_invalid_arg); + + info->Data(native_object); + + if (finalize_cb != nullptr) { + info->AddFinalizer([finalize_cb, finalize_hint](WrapperInfo* info) { + finalize_cb(info->Env(), info->Data(), finalize_hint); + }); + } + + if (result != nullptr) { + CHECK_NAPI(napi_create_reference(env, js_object, 0, result)); + } + + return napi_ok; +} + +napi_status napi_type_tag_object(napi_env env, napi_value object, + const napi_type_tag* type_tag) { + napi_status status = + nativescript::napi::jsc::TypeTagObject(env, object, type_tag); + return env == nullptr ? status : napi_set_last_error(env, status); +} + +napi_status napi_check_object_type_tag(napi_env env, napi_value object, + const napi_type_tag* type_tag, + bool* result) { + napi_status status = nativescript::napi::jsc::CheckObjectTypeTag( + env, object, type_tag, result); + return env == nullptr ? status : napi_set_last_error(env, status); +} + +napi_status napi_unwrap(napi_env env, napi_value js_object, void** result) { + CHECK_ENV(env); + CHECK_ARG(env, js_object); + CHECK_ARG(env, result); + *result = nullptr; + + WrapperInfo* info{}; + CHECK_NAPI(WrapperInfo::Unwrap(env, js_object, &info)); + RETURN_STATUS_IF_FALSE(env, info != nullptr && info->Data() != nullptr, napi_invalid_arg); + + *result = info->Data(); + return napi_ok; +} + +napi_status napi_remove_wrap(napi_env env, napi_value js_object, void** result) { + CHECK_ENV(env); + CHECK_ARG(env, js_object); + + // Once an object is wrapped, it stays wrapped in order to support finalizer callbacks. + + WrapperInfo* info{}; + CHECK_NAPI(WrapperInfo::Unwrap(env, js_object, &info)); + RETURN_STATUS_IF_FALSE(env, info != nullptr && info->Data() != nullptr, napi_invalid_arg); + + *result = info->Data(); + info->Data(nullptr); + + return napi_ok; +} + +napi_status napi_create_external(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + CHECK_NAPI(ExternalInfo::Create(env, data, finalize_cb, finalize_hint, result)); + return napi_ok; +} + +napi_status napi_get_value_external(napi_env env, napi_value value, void** result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + ExternalInfo* info = NativeInfo::Get(ToJSObject(env, value)); + *result = (info != nullptr && info->Type() == NativeType::External) ? info->Data() : nullptr; + return napi_ok; +} + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +napi_status napi_create_reference(napi_env env, + napi_value value, + uint32_t initial_refcount, + napi_ref* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + napi_ref__* ref{new napi_ref__{value, initial_refcount}}; + if (ref == nullptr) { + return napi_set_last_error(env, napi_generic_failure); + } + + ref->init(env); + *result = ref; + + return napi_ok; +} + +// Deletes a reference. The referenced value is released, and may be GC'd +// unless there are other references to it. +napi_status napi_delete_reference(napi_env env, napi_ref ref) { + CHECK_ENV(env); + CHECK_ARG(env, ref); + + ref->deinit(env); + delete ref; + + return napi_ok; +} + +// Increments the reference count, optionally returning the resulting count. +// After this call the reference will be a strong reference because its refcount +// is >0, and the referenced object is effectively "pinned". Calling this when +// the refcount is 0 and the target is unavailable results in an error. +napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, ref); + + ref->ref(env); + if (result != nullptr) { + *result = ref->count(); + } + + return napi_ok; +} + +// Decrements the reference count, optionally returning the resulting count. +// If the result is 0 the reference is now weak and the object may be GC'd at +// any time if there are no other references. Calling this when the refcount +// is already 0 results in an error. +napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, ref); + + ref->unref(env); + if (result != nullptr) { + *result = ref->count(); + } + + return napi_ok; +} + +// Attempts to get a referenced value. If the reference is weak, the value +// might no longer be available, in that case the call is still successful but +// the result is NULL. +napi_status napi_get_reference_value(napi_env env, + napi_ref ref, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, ref); + CHECK_ARG(env, result); + + *result = ref->value(env); + return napi_ok; +} + +// Stub implementation of handle scope apis for JSC. +napi_status napi_open_handle_scope(napi_env env, + napi_handle_scope* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = reinterpret_cast(1); + return napi_ok; +} + +// Stub implementation of handle scope apis for JSC. +napi_status napi_close_handle_scope(napi_env env, + napi_handle_scope scope) { + CHECK_ENV(env); + CHECK_ARG(env, scope); + return napi_ok; +} + +// Stub implementation of handle scope apis for JSC. +napi_status napi_open_escapable_handle_scope(napi_env env, + napi_escapable_handle_scope* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = reinterpret_cast(1); + return napi_ok; +} + +// Stub implementation of handle scope apis for JSC. +napi_status napi_close_escapable_handle_scope(napi_env env, + napi_escapable_handle_scope scope) { + CHECK_ENV(env); + CHECK_ARG(env, scope); + return napi_ok; +} + +// Stub implementation of handle scope apis for JSC. +// This one will return escapee value as this is called from leveldown db. +napi_status napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, scope); + CHECK_ARG(env, escapee); + CHECK_ARG(env, result); + *result = escapee; + return napi_ok; +} + +napi_status napi_new_instance(napi_env env, + napi_value constructor, + size_t argc, + const napi_value* argv, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, constructor); + if (argc > 0) { + CHECK_ARG(env, argv); + } + CHECK_ARG(env, result); + + JSValueRef exception{}; + *result = ToNapi(JSObjectCallAsConstructor( + env->context, + ToJSObject(env, constructor), + argc, + ToJSValues(argv), + &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, result); + + JSValueRef exception{}; + *result = JSValueIsInstanceOfConstructor( + env->context, + ToJSValue(object), + ToJSObject(env, constructor), + &exception); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_is_exception_pending(napi_env env, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = (env->last_exception != nullptr); + return napi_ok; +} + +napi_status napi_get_and_clear_last_exception(napi_env env, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + if (env->last_exception == nullptr) { + return napi_get_undefined(env, result); + } else { + *result = ToNapi(env->last_exception); + env->last_exception = nullptr; + } + + return napi_clear_last_error(env); +} + +napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSTypedArrayType type{JSValueGetTypedArrayType(env->context, ToJSValue(value), &exception)}; + CHECK_JSC(env, exception); + + *result = (type == kJSTypedArrayTypeArrayBuffer); + return napi_ok; +} + +napi_status napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *data = malloc(byte_length); + JSValueRef exception{}; + *result = ToNapi(JSObjectMakeArrayBufferWithBytesNoCopy( + env->context, + *data, + byte_length, + [](void* bytes, void* deallocatorContext) { + free(bytes); + }, + nullptr, + &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_create_external_arraybuffer(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + CHECK_NAPI(ExternalArrayBufferInfo::Create(env, external_data, byte_length, finalize_cb, finalize_hint, result)); + return napi_ok; +} + +napi_status napi_get_arraybuffer_info(napi_env env, + napi_value arraybuffer, + void** data, + size_t* byte_length) { + CHECK_ENV(env); + CHECK_ARG(env, arraybuffer); + + JSValueRef exception{}; + + if (data != nullptr) { + *data = JSObjectGetArrayBufferBytesPtr(env->context, ToJSObject(env, arraybuffer), &exception); + CHECK_JSC(env, exception); + } + + if (byte_length != nullptr) { + *byte_length = JSObjectGetArrayBufferByteLength(env->context, ToJSObject(env, arraybuffer), &exception); + CHECK_JSC(env, exception); + } + + return napi_ok; +} + +napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + JSTypedArrayType type{JSValueGetTypedArrayType(env->context, ToJSValue(value), &exception)}; + CHECK_JSC(env, exception); + + *result = (type != kJSTypedArrayTypeNone && type != kJSTypedArrayTypeArrayBuffer); + return napi_ok; +} + +napi_status napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, arraybuffer); + CHECK_ARG(env, result); + + JSTypedArrayType jsType{}; + switch (type) { + case napi_int8_array: + jsType = kJSTypedArrayTypeInt8Array; + break; + case napi_uint8_array: + jsType = kJSTypedArrayTypeUint8Array; + break; + case napi_uint8_clamped_array: + jsType = kJSTypedArrayTypeUint8ClampedArray; + break; + case napi_int16_array: + jsType = kJSTypedArrayTypeInt16Array; + break; + case napi_uint16_array: + jsType = kJSTypedArrayTypeUint16Array; + break; + case napi_int32_array: + jsType = kJSTypedArrayTypeInt32Array; + break; + case napi_uint32_array: + jsType = kJSTypedArrayTypeUint32Array; + break; + case napi_float32_array: + jsType = kJSTypedArrayTypeFloat32Array; + break; + case napi_float64_array: + jsType = kJSTypedArrayTypeFloat64Array; + break; + default: + return napi_set_last_error(env, napi_invalid_arg); + } + + JSValueRef exception{}; + *result = ToNapi(JSObjectMakeTypedArrayWithArrayBufferAndOffset( + env->context, + jsType, + ToJSObject(env, arraybuffer), + byte_offset, + length, + &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_get_typedarray_info(napi_env env, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset) { + CHECK_ENV(env); + CHECK_ARG(env, typedarray); + + JSValueRef exception{}; + + JSObjectRef object{ToJSObject(env, typedarray)}; + + if (type != nullptr) { + JSTypedArrayType typedArrayType{JSValueGetTypedArrayType(env->context, object, &exception)}; + CHECK_JSC(env, exception); + + switch (typedArrayType) { + case kJSTypedArrayTypeInt8Array: + *type = napi_int8_array; + break; + case kJSTypedArrayTypeUint8Array: + *type = napi_uint8_array; + break; + case kJSTypedArrayTypeUint8ClampedArray: + *type = napi_uint8_clamped_array; + break; + case kJSTypedArrayTypeInt16Array: + *type = napi_int16_array; + break; + case kJSTypedArrayTypeUint16Array: + *type = napi_uint16_array; + break; + case kJSTypedArrayTypeInt32Array: + *type = napi_int32_array; + break; + case kJSTypedArrayTypeUint32Array: + *type = napi_uint32_array; + break; + case kJSTypedArrayTypeFloat32Array: + *type = napi_float32_array; + break; + case kJSTypedArrayTypeFloat64Array: + *type = napi_float64_array; + break; + default: + return napi_set_last_error(env, napi_generic_failure); + } + } + + if (length != nullptr) { + *length = JSObjectGetTypedArrayLength(env->context, object, &exception); + CHECK_JSC(env, exception); + } + + if (data != nullptr || byte_offset != nullptr) { + size_t data_byte_offset{JSObjectGetTypedArrayByteOffset(env->context, object, &exception)}; + CHECK_JSC(env, exception); + + if (data != nullptr) { + *data = static_cast(JSObjectGetTypedArrayBytesPtr(env->context, object, &exception)) + data_byte_offset; + CHECK_JSC(env, exception); + } + + if (byte_offset != nullptr) { + *byte_offset = data_byte_offset; + } + } + + if (arraybuffer != nullptr) { + *arraybuffer = ToNapi(JSObjectGetTypedArrayBuffer(env->context, object, &exception)); + CHECK_JSC(env, exception); + } + + return napi_ok; +} + +napi_status napi_create_dataview(napi_env env, + size_t byte_length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, arraybuffer); + CHECK_ARG(env, result); + + napi_value global{}, dataview_ctor{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "DataView", &dataview_ctor)); + + napi_value byte_offset_value{}, byte_length_value{}; + napi_create_double(env, static_cast(byte_offset), &byte_offset_value); + napi_create_double(env, static_cast(byte_length), &byte_length_value); + napi_value args[] = { arraybuffer, byte_offset_value, byte_length_value }; + CHECK_NAPI(napi_new_instance(env, dataview_ctor, 3, args, result)); + + return napi_ok; +} + +napi_status napi_is_dataview(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + napi_value global{}, dataview_ctor{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "DataView", &dataview_ctor)); + CHECK_NAPI(napi_instanceof(env, value, dataview_ctor, result)); + + return napi_ok; +} + +napi_status napi_get_dataview_info(napi_env env, + napi_value dataview, + size_t* byte_length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset) { + CHECK_ENV(env); + CHECK_ARG(env, dataview); + + if (byte_length != nullptr) { + napi_value value{}; + double doubleValue{}; + CHECK_NAPI(napi_get_named_property(env, dataview, "byteLength", &value)); + CHECK_NAPI(napi_get_value_double(env, value, &doubleValue)); + *byte_length = static_cast(doubleValue); + } + + if (data != nullptr) { + napi_value value{}; + CHECK_NAPI(napi_get_named_property(env, dataview, "buffer", &value)); + CHECK_NAPI(napi_get_arraybuffer_info(env, value, data, nullptr)); + } + + if (arraybuffer != nullptr) { + CHECK_NAPI(napi_get_named_property(env, dataview, "buffer", arraybuffer)); + } + + if (byte_offset != nullptr) { + napi_value value{}; + double doubleValue{}; + CHECK_NAPI(napi_get_named_property(env, dataview, "byteOffset", &value)); + CHECK_NAPI(napi_get_value_double(env, value, &doubleValue)); + *byte_offset = static_cast(doubleValue); + } + + return napi_ok; +} + +napi_status napi_get_version(napi_env env, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = NAPI_VERSION; + return napi_ok; +} + +napi_status napi_create_promise(napi_env env, + napi_deferred* deferred, + napi_value* promise) { + CHECK_ENV(env); + CHECK_ARG(env, deferred); + CHECK_ARG(env, promise); + + napi_value global{}, promise_ctor{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Promise", &promise_ctor)); + + struct Wrapper { + napi_value resolve{}; + napi_value reject{}; + + static napi_value Callback(napi_env env, napi_callback_info cbinfo) { + Wrapper* wrapper = reinterpret_cast(cbinfo->data); + wrapper->resolve = cbinfo->argv[0]; + wrapper->reject = cbinfo->argv[1]; + return nullptr; + } + } wrapper; + + napi_value executor{}; + CHECK_NAPI(napi_create_function(env, "executor", NAPI_AUTO_LENGTH, Wrapper::Callback, &wrapper, &executor)); + CHECK_NAPI(napi_new_instance(env, promise_ctor, 1, &executor, promise)); + + napi_value deferred_value{}; + CHECK_NAPI(napi_create_object(env, &deferred_value)); + CHECK_NAPI(napi_set_named_property(env, deferred_value, "resolve", wrapper.resolve)); + CHECK_NAPI(napi_set_named_property(env, deferred_value, "reject", wrapper.reject)); + + napi_ref deferred_ref{}; + CHECK_NAPI(napi_create_reference(env, deferred_value, 1, &deferred_ref)); + *deferred = reinterpret_cast(deferred_ref); + + return napi_ok; +} + +napi_status napi_resolve_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution) { + CHECK_ENV(env); + CHECK_ARG(env, deferred); + + napi_ref deferred_ref{reinterpret_cast(deferred)}; + napi_value undefined{}, deferred_value{}, resolve{}; + CHECK_NAPI(napi_get_undefined(env, &undefined)); + CHECK_NAPI(napi_get_reference_value(env, deferred_ref, &deferred_value)); + CHECK_NAPI(napi_get_named_property(env, deferred_value, "resolve", &resolve)); + CHECK_NAPI(napi_call_function(env, undefined, resolve, 1, &resolution, nullptr)); + CHECK_NAPI(napi_delete_reference(env, deferred_ref)); + + return napi_ok; +} + +napi_status napi_reject_deferred(napi_env env, + napi_deferred deferred, + napi_value rejection) { + CHECK_ENV(env); + CHECK_ARG(env, deferred); + + napi_ref deferred_ref{reinterpret_cast(deferred)}; + napi_value undefined{}, deferred_value{}, reject{}; + CHECK_NAPI(napi_get_undefined(env, &undefined)); + CHECK_NAPI(napi_get_reference_value(env, deferred_ref, &deferred_value)); + CHECK_NAPI(napi_get_named_property(env, deferred_value, "reject", &reject)); + CHECK_NAPI(napi_call_function(env, undefined, reject, 1, &rejection, nullptr)); + CHECK_NAPI(napi_delete_reference(env, deferred_ref)); + + return napi_ok; +} + +napi_status napi_is_promise(napi_env env, + napi_value promise, + bool* is_promise) { + CHECK_ENV(env); + CHECK_ARG(env, promise); + CHECK_ARG(env, is_promise); + + napi_value global{}, promise_ctor{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Promise", &promise_ctor)); + CHECK_NAPI(napi_instanceof(env, promise, promise_ctor, is_promise)); + + return napi_ok; +} + +napi_status napi_run_script(napi_env env, + napi_value script, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, script); + CHECK_ARG(env, result); + + JSValueRef exception{}; + + JSString script_str{ToJSString(env, script, &exception)}; + CHECK_JSC(env, exception); + + *result = ToNapi(JSEvaluateScript( + env->context, script_str, nullptr, nullptr, 0, &exception)); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_run_script_source(napi_env env, + napi_value script, + const char* source_url, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, script); + CHECK_ARG(env, result); + + JSValueRef exception{}; + + JSString script_str{ToJSString(env, script, &exception)}; + CHECK_JSC(env, exception); + + JSValueRef return_value{JSEvaluateScript( + env->context, script_str, nullptr, JSString(source_url), 0, &exception)}; + CHECK_JSC(env, exception); + + if (result != nullptr) { + *result = ToNapi(return_value); + } + + return napi_ok; +} + +napi_status napi_add_finalizer(napi_env env, + napi_value js_object, + void* finalize_data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result) { + CHECK_ENV(env); + CHECK_ARG(env, js_object); + CHECK_ARG(env, finalize_cb); + + WrapperInfo* info{}; + CHECK_NAPI(WrapperInfo::Wrap(env, js_object, &info)); + + info->AddFinalizer([finalize_cb, finalize_data, finalize_hint](WrapperInfo* info) { + finalize_cb(info->Env(), finalize_data, finalize_hint); + }); + + if (result != nullptr) { + CHECK_NAPI(napi_create_reference(env, js_object, 0, result)); + } + + return napi_ok; +} + +napi_status napi_adjust_external_memory(napi_env env, + int64_t change_in_bytes, + int64_t* adjusted_value) { + CHECK_ENV(env); + CHECK_ARG(env, adjusted_value); + + // TODO: Determine if JSC needs or is able to do anything here + // For now, we can lie and say that we always adjusted more memory + *adjusted_value = change_in_bytes; + + return napi_ok; +} + +napi_status napi_create_date(napi_env env, + double time, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + auto jsTime = JSValueMakeNumber(env->context, time); + JSValueRef exception {}; + auto jsDate = JSObjectMakeDate(env->context, 1, &jsTime, &exception); + CHECK_JSC(env, exception); + + *result = ToNapi(jsDate); + return napi_ok; +} + +napi_status napi_is_date(napi_env env, + napi_value value, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + *result = JSValueIsDate(env->context, ToJSValue(value)); + + return napi_ok; +} + +napi_status napi_get_date_value(napi_env env, + napi_value value, + double* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValueRef exception{}; + // we don't piggyback off of napi_get_value_double because that function + // SHOULDN'T coerce. + *result = JSValueToNumber(env->context, ToJSValue(value), &exception); + CHECK_JSC(env, exception); + + return napi_ok; +} + +napi_status napi_get_all_property_names(napi_env env, + napi_value object, + napi_key_collection_mode key_mode, + napi_key_filter key_filter, + napi_key_conversion key_conversion, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, result); + + napi_value array{}, push{}, global{}, object_ctor{}; + CHECK_NAPI(napi_create_array(env, &array)); + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Object", &object_ctor)); + CHECK_NAPI(napi_get_named_property(env, array, "push", &push)); + + std::vector getter_methods; + if (!(key_filter & napi_key_skip_strings)) { + napi_value method{}; + CHECK_NAPI(napi_get_named_property(env, object_ctor, "getOwnPropertyNames", &method)); + getter_methods.push_back(method); + } + if (!(key_filter & napi_key_skip_symbols)) { + napi_value method{}; + CHECK_NAPI(napi_get_named_property(env, object_ctor, "getOwnPropertySymbols", &method)); + getter_methods.push_back(method); + } + + napi_value current = object; + while (true) { + for (napi_value method : getter_methods) { + napi_value properties{}; + // Object.getOwnProperty[Names|Symbols](current) + CHECK_NAPI(napi_call_function(env, object_ctor, method, 1, ¤t, &properties)); + uint32_t length = 0; + CHECK_NAPI(napi_get_array_length(env, properties, &length)); + for (uint32_t i = 0; i != length; ++i) { + napi_value key{}; + CHECK_NAPI(napi_get_element(env, properties, i, &key)); + // TODO: coerce to number if napi_key_keep_numbers + // TODO: filter writable/enumerable/configurable +#if 0 + napi_value descriptor{}; + std::array args { current, key }; + // Object.getOwnPropertyDescriptor(current, key) + CHECK_NAPI(napi_call_function(env, object_ctor, getOwnPropertyDescriptor, 2, args.data(), &descriptor)); +#endif + CHECK_NAPI(napi_call_function(env, array, push, 1, &key, NULL)); + } + } + if (key_mode == napi_key_own_only) break; + napi_value next{}; + CHECK_NAPI(napi_get_prototype(env, current, &next)); + napi_valuetype next_type; + CHECK_NAPI(napi_typeof(env, next, &next_type)); + if (next_type == napi_null) break; + current = next; + }; + + *result = array; + + return napi_ok; +} + + +napi_status napi_object_freeze(napi_env env, + napi_value object) { + napi_value global{}, object_ctor{}, freeze{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Object", &object_ctor)); + CHECK_NAPI(napi_get_named_property(env, object_ctor, "freeze", &freeze)); + CHECK_NAPI(napi_call_function(env, object_ctor, freeze, 1, &object, nullptr)); + return napi_ok; +} + +napi_status napi_object_seal(napi_env env, + napi_value object) { + napi_value global{}, object_ctor{}, seal{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Object", &object_ctor)); + CHECK_NAPI(napi_get_named_property(env, object_ctor, "seal", &seal)); + CHECK_NAPI(napi_call_function(env, object_ctor, seal, 1, &object, nullptr)); + return napi_ok; +} diff --git a/NativeScript/napi/android/jsc/jsc-api.h b/NativeScript/napi/android/jsc/jsc-api.h new file mode 100644 index 000000000..803d67481 --- /dev/null +++ b/NativeScript/napi/android/jsc/jsc-api.h @@ -0,0 +1,110 @@ +// +// Created by Ammar Ahmed on 01/12/2024. +// + +#ifndef TEST_APP_JSC_API_H +#define TEST_APP_JSC_API_H + +#include "js_native_api.h" +#include "js_native_api_types.h" +#include +#include +#include +#include +#include +#include +#include + +struct napi_env__ { + JSGlobalContextRef context{}; + JSValueRef last_exception{}; + napi_extended_error_info last_error{nullptr, nullptr, 0, napi_ok}; + std::unordered_set active_ref_values{}; + std::list strong_refs{}; + + JSValueRef constructor_info_symbol{}; + JSValueRef function_info_symbol{}; + JSValueRef reference_info_symbol{}; + JSValueRef wrapper_info_symbol{}; + JSValueRef type_tag_symbol{}; + + const std::thread::id thread_id{std::this_thread::get_id()}; + + napi_env__(JSGlobalContextRef context) : context{context} { + { + std::lock_guard lock(napi_envs_mutex); + napi_envs[context] = this; + } + JSGlobalContextRetain(context); + init_symbol(constructor_info_symbol, "NS_ConstructorInfo"); + init_symbol(function_info_symbol, "NS_FunctionInfo"); + init_symbol(reference_info_symbol, "NS_ReferenceInfo"); + init_symbol(wrapper_info_symbol, "NS_WrapperInfo"); + init_symbol(type_tag_symbol, "NS_TypeTag"); + } + + ~napi_env__() { + deinit_refs(); + deinit_symbol(type_tag_symbol); + deinit_symbol(wrapper_info_symbol); + deinit_symbol(reference_info_symbol); + deinit_symbol(function_info_symbol); + deinit_symbol(constructor_info_symbol); + { + std::lock_guard lock(napi_envs_mutex); + napi_envs.erase(context); + } + JSGlobalContextRelease(context); + } + + static napi_env get(JSGlobalContextRef context) { + std::lock_guard lock(napi_envs_mutex); + auto it = napi_envs.find(context); + if (it != napi_envs.end()) { + return it->second; + } else { + return nullptr; + } + } + +private: + static inline std::mutex napi_envs_mutex; + static inline std::unordered_map napi_envs{}; + void deinit_refs(); + void init_symbol(JSValueRef& symbol, const char* description); + void deinit_symbol(JSValueRef symbol); +}; + +#define RETURN_STATUS_IF_FALSE(env, condition, status) \ + do { \ + if (!(condition)) { \ + return napi_set_last_error((env), (status)); \ + } \ + } while (0) + +#define CHECK_ENV(env) \ + do { \ + if ((env) == nullptr) { \ + return napi_invalid_arg; \ + } \ + } while (0) + +#define CHECK_ARG(env, arg) \ + RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), napi_invalid_arg) + +#define CHECK_JSC(env, exception) \ + do { \ + if ((exception) != nullptr) { \ + return napi_set_exception(env, exception); \ + } \ + } while (0) + +// This does not call napi_set_last_error because the expression +// is assumed to be a NAPI function call that already did. +#define CHECK_NAPI(expr) \ + do { \ + napi_status status = (expr); \ + if (status != napi_ok) return status; \ + } while (0) + +#endif //TEST_APP_JSC_API_H diff --git a/NativeScript/napi/android/jsc/jsr.cpp b/NativeScript/napi/android/jsc/jsr.cpp new file mode 100644 index 000000000..d5b66d581 --- /dev/null +++ b/NativeScript/napi/android/jsc/jsr.cpp @@ -0,0 +1,90 @@ +#include "jsr.h" + +napi_status js_create_runtime(napi_runtime *runtime) { + if (!runtime) return napi_invalid_arg; + *runtime = (napi_runtime) JSGlobalContextCreateInGroup(nullptr, nullptr); + return napi_ok; +} + +napi_status js_create_napi_env(napi_env* env, napi_runtime runtime) { + if (env == nullptr) return napi_invalid_arg; + + *env = new napi_env__((JSGlobalContextRef) runtime); + JSGlobalContextRelease((JSGlobalContextRef) runtime); + + napi_value gc; + napi_create_function(*env, "gc", strlen("gc"), [](napi_env env, napi_callback_info info) -> napi_value { + JSGarbageCollect(env->context); + napi_value undefined; + napi_get_undefined(env, &undefined); + return undefined; + }, nullptr, &gc); + napi_value global; + napi_get_global(*env, &global); + napi_set_named_property(*env, global, "gc", gc); + + + return napi_ok; + +} + +napi_status js_set_runtime_flags(const char* flags) { + return napi_ok; +} + +napi_status js_lock_env(napi_env env) { + return napi_ok; +} + +napi_status js_unlock_env(napi_env env) { + return napi_ok; +} + +napi_status js_free_napi_env(napi_env env) { + if (env == nullptr) return napi_invalid_arg; + delete env; + return napi_ok; +} + +napi_status js_free_runtime(napi_runtime runtime) { +// JSContextGroupRelease((JSContextGroupRef) runtime); + return napi_ok; +} + +napi_status js_execute_script(napi_env env, + napi_value script, + const char *file, + napi_value *result) { + + return napi_run_script_source(env, script, file, result); + +} + +napi_status js_execute_pending_jobs(napi_env env) { + return napi_ok; +} + +napi_status js_get_engine_ptr(napi_env env, int64_t *engine_ptr) { + *engine_ptr = (int64_t) 0; + return napi_ok; +} + +napi_status js_adjust_external_memory(napi_env env, int64_t changeInBytes, int64_t *externalMemory) { + return napi_ok; +} + +napi_status js_cache_script(napi_env env, const char *source, const char *file) { + return napi_ok; +} + +napi_status js_run_cached_script(napi_env env, const char *file, napi_value script, void *cache, + napi_value *result) { + return napi_ok; +} + + +napi_status js_get_runtime_version(napi_env env, napi_value* version) { + napi_create_string_utf8(env, "JSC", NAPI_AUTO_LENGTH, version); + + return napi_ok; +} diff --git a/NativeScript/napi/android/primjs/code_cache.cc b/NativeScript/napi/android/primjs/code_cache.cc new file mode 100644 index 000000000..1194bda05 --- /dev/null +++ b/NativeScript/napi/android/primjs/code_cache.cc @@ -0,0 +1,261 @@ +/** + * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved. + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file in the root of the source tree. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "code_cache.h" + +#include + +#include +#include + +#if OS_ANDROID +#include "basic/log/logging.h" +#else +#define VLOGD(...) void((__VA_ARGS__)) +#endif // OS_ANDROID + +#ifdef PROFILE_CODECACHE +#define INCREASE(target) ++(target) +#else +#define INCREASE(target) +#endif // PROFILE_CODECACHE + +constexpr double CacheBlob::MAGIC; + +bool CacheBlob::insert(const std::string& filename, const uint8_t* data, + int length) { + // too large (more than half of MAX) cached data must be discarded + if (length == 0 || data == nullptr) { + INCREASE(expired_query_); + return false; + } + CachedData* target = nullptr; + const uint8_t* old_data = nullptr; + { + std::unique_lock lock(write_mutex_); + auto it = cache_map_.find(filename); + if (it != cache_map_.end()) { + target = it->second; + current_size_ -= target->length_; + remove_from_ranking_list(target); + + if (get_enough_space(length)) { + if (mode_ == kAppending) mode_ = kWriting; + target->length_ = length; + } else { + cache_map_.erase(it); + return false; + } + + } else if (get_enough_space(length)) { + target = new CachedData(length, nullptr, filename); + cache_map_[filename] = target; + if (mode_ == kAppending) { + if (append_vec_ == nullptr) { + append_vec_ = new CacheVector(); + } + append_vec_->push_back(target); + } + } else { + return false; + } + + old_data = target->data_; + target->data_ = data; + } + + heat_ranking_.push_back(target); + current_size_ += length; + if (old_data) { + delete[] old_data; + } + INCREASE(target->used_times_); + + return true; +} + +// That len will be not nullptr is ensured by the context. +const CachedData* CacheBlob::find(const std::string& filename, int* len) const { + if (!write_mutex_.try_lock()) { + INCREASE(missed_query_); + INCREASE(total_query_); + return empty_cache_.get(); + } + auto it = cache_map_.find(filename); + write_mutex_.unlock(); + CachedData* result = nullptr; + if (it != cache_map_.end()) { + // every time a cache is searched, its heat-ranking + // rises and thus the ranking list changes + result = it->second; + *len = result->length_; + INCREASE(result->used_times_); + } else { + *len = 0; + INCREASE(missed_query_); + } + INCREASE(total_query_); + + // NOTE: + // The result cached data is safe beyond this scope, + // because JS engines got this data and then use it + // consecutively in one thread. Tasks that insert new + // data for the same filename will only be posted after + // that cached data has been already used up. + return result; +} + +void CacheBlob::remove(const std::string& filename) { + auto it = cache_map_.find(filename); + if (it != cache_map_.end()) { + current_size_ -= it->second->length_; + delete it->second; + cache_map_.erase(it); + if (mode_ == kAppending) mode_ = kWriting; + } +} + +// cache file structure: +// Header: +// | magic number | --> 8 bytes +// Body : +// | file name size | --> 2 bytes +// | file name | --> x bytes +// | cache data length | --> 4 bytes +// | cache data | --> y bytes +// ... +void CacheBlob::output() { + if (mode_ == kAppending && append_vec_ == nullptr) return; + bool appending = mode_ == kAppending && append_vec_ != nullptr; + + FILE* file_out = appending ? fopen(target_path_.c_str(), "ab") + : fopen(target_path_.c_str(), "wb"); + if (file_out) { + if (appending) { + for (auto it : *append_vec_) write_cache_unit(file_out, it); + } else { + fwrite(&MAGIC, DOUBLE_SIZE, 1, file_out); + for (auto& it : cache_map_) write_cache_unit(file_out, it.second); + } + fclose(file_out); + VLOGD("codecache: output cache file %s succeed.\n", target_path_.c_str()); + } +} + +// steps to rebuild blob: +// 1. read and check magic number; +// 2. read 2 bytes to get the size (x) of file name; +// 3. read x bytes to get the file name; +// 4. read 4 bytes to get the size (y) of cache data; +// 5. read y bytes to get the actual content of cache data; +// 6. go back to step 2 until EOF +bool CacheBlob::input() { + FILE* file_in = fopen(target_path_.c_str(), "rb"); + + bool succeeded = false; + if (file_in) { + double maybe_magic; + fread(reinterpret_cast(&maybe_magic), DOUBLE_SIZE, 1, file_in); + // check whether file is valid. + if (maybe_magic == MAGIC) { + int c; + while ((c = fgetc(file_in)) != EOF) { + ungetc(c, file_in); + read_cache_unit(file_in); + } + mode_ = kAppending; + succeeded = true; + } + fclose(file_in); + } + return succeeded; +} + +#ifdef PROFILE_CODECACHE +void CacheBlob::dump_status(void* p) { + std::vector >* status_vec = + reinterpret_cast >*>(p); + std::sort(heat_ranking_.begin(), heat_ranking_.end(), CachedData::compare); + status_vec->push_back(std::pair("Total", total_query_)); + status_vec->push_back(std::pair("Missed", missed_query_)); + status_vec->push_back(std::pair("Expired", expired_query_)); + status_vec->push_back(std::pair( + "Updated", mode_ == kAppending && append_vec_ == nullptr ? 0 : 1)); + status_vec->push_back(std::pair("Size", current_size_)); + status_vec->push_back(std::pair("Heat Ranking, total ", + heat_ranking_.size())); + for (size_t i = 0; i < heat_ranking_.size(); ++i) { + CachedData* dt = heat_ranking_[i]; + status_vec->push_back( + std::pair(dt->file_name_, dt->used_times_)); + } +} +#endif // PROFILE_CODECACHE + +void CacheBlob::write_cache_unit(FILE* file_out, const CachedData* unit) { + // write file name + uint16_t size = static_cast(unit->file_name_.size()); + fwrite(static_cast(&size), SHORT_SIZE, 1, file_out); + fwrite(unit->file_name_.c_str(), 1, unit->file_name_.size(), file_out); + + uint32_t length = unit->length_; + fwrite(static_cast(&length), INT_SIZE, 1, file_out); + fwrite(unit->data_, 1, unit->length_, file_out); +} + +void CacheBlob::read_cache_unit(FILE* file_in) { + uint16_t filename_length; + fread(&filename_length, SHORT_SIZE, 1, file_in); + std::string name(filename_length, '\0'); + fread(&name[0], 1, filename_length, file_in); + + uint32_t data_length; + fread(&data_length, INT_SIZE, 1, file_in); + uint8_t* data = new uint8_t[data_length]; + fread(data, 1, data_length, file_in); + + CachedData* cd = new CachedData(static_cast(data_length), data, name); + + current_size_ += data_length; + heat_ranking_.push_back(cd); + cache_map_[name] = cd; +} + +void CacheBlob::remove_from_ranking_list(CachedData* target) { + for (size_t i = 0; i < heat_ranking_.size(); ++i) { + if (target == heat_ranking_[i]) { + heat_ranking_.erase(heat_ranking_.begin() + i); + break; + } + } +} + +// This is a vast-time-costing function +bool CacheBlob::get_enough_space(int data_size) { + // 0. check whether available space is enough + if (current_size_ + data_size <= max_capacity_) return true; + int size_needed = data_size + current_size_ - max_capacity_; + // 1. sort the heat_ranking_ + std::sort(heat_ranking_.begin(), heat_ranking_.end(), CachedData::compare); + // 2. pick CachedDatas + for (int i = heat_ranking_.size() - 1; i >= 0; --i) { + size_needed -= heat_ranking_[i]->length_; + if (size_needed <= 0) { + for (size_t j = i; j < heat_ranking_.size(); ++j) { + CachedData* it = heat_ranking_[j]; + remove(it->file_name_); + } + heat_ranking_.erase(heat_ranking_.begin() + i, heat_ranking_.end()); + return true; + } + } + return false; +} diff --git a/NativeScript/napi/android/primjs/code_cache.h b/NativeScript/napi/android/primjs/code_cache.h new file mode 100644 index 000000000..f10a44ee6 --- /dev/null +++ b/NativeScript/napi/android/primjs/code_cache.h @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved. + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file in the root of the source tree. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_NAPI_COMMON_CODE_CACHE_H_ +#define SRC_NAPI_COMMON_CODE_CACHE_H_ + +#include +#include +#include +#include +#include + +struct CachedData { + CachedData() : length_(0), data_(nullptr), file_name_("") {} + + CachedData(int length, const uint8_t* data, const std::string& name) + : length_(length), data_(data), file_name_(name) {} + + // carefully copy and move objects of this class + ~CachedData() { + if (data_) delete[] data_; + } + + static bool compare(CachedData* left, CachedData* right) { + if (left->used_times_ > right->used_times_) { + return true; + } else if (left->used_times_ < right->used_times_) { + return false; + } else { + return left->length_ > right->length_; + } + } + + int used_times_ = 0; + int length_; + const uint8_t* data_; + const std::string file_name_; +}; + +typedef std::unordered_map CacheMap; +typedef std::vector CacheVector; +#define SHORT_SIZE 2 +#define INT_SIZE 4 +#define DOUBLE_SIZE 8 + +class CacheBlob { + private: + enum CacheMode { kWriting, kAppending }; + static constexpr double MAGIC = 3.14159265; + + public: + explicit CacheBlob(const std::string& path, int max_cap = 1 << 20) + : current_size_(0), + target_path_(path), + max_capacity_(max_cap), + empty_cache_(new CachedData) {} + + virtual ~CacheBlob() { + for (auto it : cache_map_) delete it.second; + if (append_vec_) delete append_vec_; + } + + // NOTE: modifications on CacheBlob can only happen in worker Thread. + bool insert(const std::string& filename, const uint8_t* data, int length); + const CachedData* find(const std::string& filename, int* len) const; + void remove(const std::string& filename); + void output(); + bool input(); + int size() const { return current_size_; } + +#ifdef PROFILE_CODECACHE + void dump_status(void* p); +#endif // PROFILE_CODECACHE + + private: + void write_cache_unit(FILE* file_out, const CachedData* unit); + void read_cache_unit(FILE* file_in); + void remove_from_ranking_list(CachedData* target); + // This is a vast-time-costing function + bool get_enough_space(int data_size); + + CacheMap cache_map_; + // ranking list for caches' frequency of being used. + CacheVector heat_ranking_; + int current_size_; + const std::string target_path_; + int max_capacity_; + mutable std::mutex write_mutex_; + std::unique_ptr empty_cache_; + +#ifdef PROFILE_CODECACHE + mutable int total_query_ = 0; + mutable int missed_query_ = 0; + mutable int expired_query_ = 0; +#endif // PROFILE_CODECACHE + CacheMode mode_ = kWriting; + CacheVector* append_vec_ = nullptr; +}; + +#endif // SRC_NAPI_COMMON_CODE_CACHE_H_ diff --git a/NativeScript/napi/android/primjs/include/basic/log/logging.h b/NativeScript/napi/android/primjs/include/basic/log/logging.h new file mode 100644 index 000000000..2cb633ee2 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/basic/log/logging.h @@ -0,0 +1,171 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_BASIC_LOG_LOGGING_H_ +#define SRC_BASIC_LOG_LOGGING_H_ + +#include +#include +#include + +#include "quickjs/include/base_export.h" + +#if defined(OS_ANDROID) +#include + +#define VLOGW(...) __android_log_print(ANDROID_LOG_WARN, "PRIMJS", __VA_ARGS__) +#define VLOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PRIMJS", __VA_ARGS__) +#define VLOGI(...) __android_log_print(ANDROID_LOG_INFO, "PRIMJS", __VA_ARGS__) +#define VLOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PRIMJS", __VA_ARGS__) +#else +#include + +#define VLOGW(format, ...) \ + fprintf(stderr, "[PRIMJS] " format "\n", ##__VA_ARGS__) +#define VLOGE(format, ...) \ + fprintf(stderr, "[PRIMJS] " format "\n", ##__VA_ARGS__) +#define VLOGI(format, ...) \ + fprintf(stderr, "[PRIMJS] " format "\n", ##__VA_ARGS__) +#define VLOGD(format, ...) \ + fprintf(stderr, "[PRIMJS] " format "\n", ##__VA_ARGS__) +#endif + +namespace primjs { +namespace general { +namespace logging { + +class LogMessage; +void Log(LogMessage *msg); + +QJS_HIDE void SetMinLogLevel(int level); + +QJS_EXPORT int GetMinAllLogLevel(); + +#define PRIMJS_LOG_LEVEL_VERBOSE -1 +#define PRIMJS_LOG_LEVEL_INFO 0 +#define PRIMJS_LOG_LEVEL_WARNING 1 +#define PRIMJS_LOG_LEVEL_ERROR 2 +#define PRIMJS_LOG_LEVEL_FATAL 3 +#define PRIMJS_LOG_LEVEL_NUM 4 + +typedef int LogSeverity; +const LogSeverity LOG_VERBOSE = PRIMJS_LOG_LEVEL_VERBOSE; +const LogSeverity LOG_INFO = PRIMJS_LOG_LEVEL_INFO; +const LogSeverity LOG_WARNING = PRIMJS_LOG_LEVEL_WARNING; +const LogSeverity LOG_ERROR = PRIMJS_LOG_LEVEL_ERROR; +const LogSeverity LOG_FATAL = PRIMJS_LOG_LEVEL_FATAL; +const LogSeverity LOG_NUM_SEVERITIES = PRIMJS_LOG_LEVEL_NUM; + +// This class is used to explicitly ignore values in the conditional +// logging macros. This avoids compiler warnings like "value computed +// is not used" and "statement has no effect". +class LogMessageVoidify { + public: + LogMessageVoidify() = default; + // This has to be an operator with a precedence lower than << but + // higher than ?: + void operator&(std::ostream &) {} +}; + +#define LOG_IS_ON(severity) \ + ((primjs::general::logging::LOG_##severity) >= \ + primjs::general::logging::GetMinAllLogLevel()) + +#define LOG_STREAM(severity) \ + primjs::general::logging::LogMessage( \ + __FILE__, __LINE__, primjs::general::logging::LOG_##severity) \ + .stream() + +#define LAZY_STREAM(stream, condition) \ + !(condition) ? (void)0 \ + : primjs::general::logging::LogMessageVoidify() & (stream) + +// Use this macro to suppress warning if the variable in log is not used. +#define UNUSED_LOG_VARIABLE __attribute__((unused)) + +#ifndef PRIMJS_MIN_LOG_LEVEL +#define PRIMJS_MIN_LOG_LEVEL PRIMJS_LOG_LEVEL_VERBOSE +#endif + +// TODO(zhixuan): Currently, the usage of log macros is like "LOGI("abc" << +// variable)", which is mixed of stream pattern and format string pattern. +// Change the loggin fashion entirely to format string pattern in future. +#if PRIMJS_MIN_LOG_LEVEL <= PRIMJS_LOG_LEVEL_VERBOSE +#define LOGV(msg) LAZY_STREAM(LOG_STREAM(VERBOSE), LOG_IS_ON(VERBOSE)) << msg +#define DLOGV(msg) LAZY_STREAM(LOG_STREAM(VERBOSE), LOG_IS_ON(VERBOSE)) << msg +#else +#define LOGV(msg) +#define DLOGV(msg) +#endif + +#if PRIMJS_MIN_LOG_LEVEL <= PRIMJS_LOG_LEVEL_INFO +#define LOGI(msg) LAZY_STREAM(LOG_STREAM(INFO), LOG_IS_ON(INFO)) << msg +#define DLOGI(msg) LAZY_STREAM(LOG_STREAM(INFO), LOG_IS_ON(INFO)) << msg +#else +#define LOGI(msg) +#define DLOGI(msg) +#endif + +#if PRIMJS_MIN_LOG_LEVEL <= PRIMJS_LOG_LEVEL_WARNING +#define LOGW(msg) LAZY_STREAM(LOG_STREAM(WARNING), LOG_IS_ON(WARNING)) << msg +#define DLOGW(msg) LAZY_STREAM(LOG_STREAM(WARNING), LOG_IS_ON(WARNING)) << msg +#else +#define LOGW(msg) +#define DLOGW(msg) +#endif + +#if PRIMJS_MIN_LOG_LEVEL <= PRIMJS_LOG_LEVEL_ERROR +#define LOGE(msg) LAZY_STREAM(LOG_STREAM(ERROR), LOG_IS_ON(ERROR)) << msg +#define DLOGE(msg) LAZY_STREAM(LOG_STREAM(ERROR), LOG_IS_ON(ERROR)) << msg +#else +#define LOGE(msg) +#define DLOGE(msg) +#endif + +#if PRIMJS_MIN_LOG_LEVEL <= PRIMJS_LOG_LEVEL_FATAL +#define LOGF(msg) LAZY_STREAM(LOG_STREAM(FATAL), LOG_IS_ON(FATAL)) << msg +#define DLOGF(msg) LAZY_STREAM(LOG_STREAM(FATAL), LOG_IS_ON(FATAL)) << msg +#else +#define LOGF(msg) +#define DLOGF(msg) +#endif + +#ifndef DCHECK +// for debug, if check failed, log fatal and abort +#if !defined(NDEBUG) +#define DCHECK(condition) \ + LAZY_STREAM(LOG_STREAM(FATAL), !(condition)) \ + << "Check failed: " #condition ". " +#else +// for release, do nothing +#define DCHECK(condition) !(condition) ? (void)0 : (void)0 +#endif +#endif + +#define NOTREACHED() LOGF("") + +class QJS_EXPORT LogMessage { + public: + LogMessage(const char *file, int line, LogSeverity severity); + ~LogMessage(); + std::ostringstream &stream(); + LogSeverity severity() { return severity_; } + + private: + void Init(const char *file, int line); + + LogSeverity severity_; + std::ostringstream stream_; + + const char *file_; + const int line_; + LogMessage(const LogMessage &) = delete; + LogMessage &operator=(const LogMessage &) = delete; +}; + +} // namespace logging +} // namespace general +} // namespace primjs + +#endif // SRC_BASIC_LOG_LOGGING_H_ diff --git a/NativeScript/napi/android/primjs/include/gc/allocator.h b/NativeScript/napi/android/primjs/include/gc/allocator.h new file mode 100644 index 000000000..bcf6f0d90 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/gc/allocator.h @@ -0,0 +1,282 @@ +/* + This is a version of malloc/free/realloc written by + Doug Lea and released to the public domain, as explained at + http://creativecommons.org/publicdomain/zero/1.0/ Send questions, + comments, complaints, performance data, etc to dl@cs.oswego.edu +* Version 2.8.6 Wed Aug 29 06:57:58 2012 Doug Lea + Note: There may be an updated version of this malloc obtainable at + ftp://gee.cs.oswego.edu/pub/misc/malloc.c + Check before installing! + */ +/* + * PackageLicenseDeclared: CC0-1.0 + */ + +// Copyright 2023 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GC_ALLOCATOR_H_ +#define SRC_GC_ALLOCATOR_H_ +#include +#ifndef _WIN32 +#include +#endif +#include +#include +#include + +#if defined(ANDROID) || defined(__ANDROID__) || defined(OS_IOS) +#define THREAD_NUM 3 +#define CREATE_THREAD_NUM 2 +#else +#define THREAD_NUM 6 +#define CREATE_THREAD_NUM 6 +#endif + +typedef uint64_t binmap_t; +typedef uint64_t bindex_t; +typedef uint64_t flag_t; +struct malloc_tree_chunk { + size_t prev_foot; + size_t head; + struct malloc_tree_chunk* fd; + struct malloc_tree_chunk* bk; + + struct malloc_tree_chunk* child[2]; + struct malloc_tree_chunk* parent; + bindex_t index; +}; +typedef struct malloc_tree_chunk* tbinptr; +struct malloc_segment { + char* base; + size_t size; + struct malloc_segment* next; + flag_t sflags; +}; +typedef struct malloc_segment msegment; +typedef struct malloc_segment* msegmentptr; +struct malloc_chunk { + size_t prev_foot; + size_t head; + struct malloc_chunk* fd; + struct malloc_chunk* bk; +}; + +typedef struct malloc_chunk mchunk; +typedef struct malloc_chunk* mchunkptr; + +#ifndef USE_LOCKS +#define USE_LOCKS 0 +#endif +#define NSMALLBINS (32U) +#define NTREEBINS (32U) +struct malloc_state { + binmap_t smallmap; + binmap_t local_smallmap[THREAD_NUM]; + binmap_t treemap; + binmap_t local_treemap[THREAD_NUM]; + size_t dvsize; + size_t topsize; + char* least_addr; + mchunkptr dv; + mchunkptr top; + size_t trim_check; + char* mmap_cache; + size_t mmap_cache_size; + size_t release_checks; + size_t magic; + mchunkptr* smallbins; + mchunkptr* local_smallbins[THREAD_NUM]; + tbinptr* treebins; + tbinptr* local_treebins[THREAD_NUM]; + size_t footprint; + size_t max_footprint; + size_t footprint_limit; + flag_t mflags; +#if USE_LOCKS + MLOCK_T mutex; +#endif + msegment seg; + size_t exts; + void** mmap_array; + uint32_t mmap_free_index; + uint32_t mmap_size; + uint32_t mmap_count; + size_t cur_malloc_size; // malloc size after gc + size_t footprint_before_gc; +#ifdef ENABLE_TRACING_GC_LOG + size_t malloc_size_before_gc; + size_t malloc_size_after_gc; + size_t finalizer_time; + size_t free_time; + size_t free_set_bit_time; + size_t free_gene_freelist_time; + size_t free_mmap_chunk_time; + size_t release_time; + size_t release_seg_num; +#endif + void* runtime; + void* pool; + int gc_flag[THREAD_NUM]; + int local_idx_flag[THREAD_NUM]; +#ifndef _WIN32 + pthread_mutex_t mtx; +#endif + size_t seg_count; + bool open_madvise; +#if defined(ANDROID) || defined(__ANDROID__) || defined(OS_IOS) + char mem_name[30]; +#endif +}; +typedef struct malloc_state* mstate; +void* allocate(mstate m, size_t bytes); +void gcfree(mstate m, void* mem); +void* reallocate(mstate m, void* oldmem, size_t bytes); +size_t allocate_usable_size(void* mem); +size_t allocate_usable_size_debug(void* mem); +size_t allocate_usable_size_debug_protect(void* mem); +void destroy_allocate_instance(mstate m); + +int atomic_acqurie_local_idx(mstate m); +void atomic_release_local_idx(mstate m, int local_idx); + +void set_mark_multi(void* ptr); +bool is_marked_multi(void* ptr); +void set_alloc_tag(void* ptr, int alloc_tag); +int get_alloc_tag(void* ptr); +void set_hash_size(void* ptr, int hash_size); +int get_hash_size(void* ptr); +void set_heap_obj_len(void* ptr, int len); +int get_heap_obj_len(void* ptr); + +#ifdef ENABLE_GC_DEBUG_TOOLS +#ifdef __cplusplus +extern "C" { +#endif +void delete_cur_mems(void* runtime, void* ptr); +void multi_delete_cur_mems(void* runtime, void* ptr, int local_idx); +void merge_mems(void* runtime); +void add_cur_mems(void* runtime, void* ptr); +#ifdef __cplusplus +} +#endif +#endif + +int64_t get_daytime(); +#ifdef ENABLE_TRACING_GC_LOG +size_t get_malloc_size(mstate m); +#endif + +#define SIZE_T_SIZE (sizeof(size_t)) +#define INT_SIZE (sizeof(int)) +#define SIZE_T_BITSIZE (sizeof(size_t) << 3) + +#define SIZE_T_ZERO ((size_t)0) +#define SIZE_T_ONE ((size_t)1) +#define SIZE_T_TWO ((size_t)2) +#define SIZE_T_FOUR ((size_t)4) +#define TWO_SIZE_T_SIZES (SIZE_T_SIZE << 1) +#define FOUR_SIZE_T_SIZES (SIZE_T_SIZE << 2) +#define SIX_SIZE_T_SIZES (FOUR_SIZE_T_SIZES + TWO_SIZE_T_SIZES) +#define HALF_MAX_SIZE_T (MAX_SIZE_T / 2U) + +#define PINUSE_BIT (SIZE_T_ONE) +#define CINUSE_BIT (SIZE_T_TWO) +#define FLAG4_BIT (SIZE_T_FOUR) +#define INUSE_BITS (PINUSE_BIT | CINUSE_BIT) +#define FLAG_BITS (PINUSE_BIT | CINUSE_BIT | FLAG4_BIT) + +#define FENCEPOST_HEAD (INUSE_BITS | SIZE_T_SIZE) + +#define chunk_plus_offset(p, s) ((mchunkptr)(((char*)(p)) + (s))) +#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s))) + +#define next_chunk(p) ((mchunkptr)(((char*)(p)) + ((p)->head & ~FLAG_BITS))) +#define prev_chunk(p) ((mchunkptr)(((char*)(p)) - ((p)->prev_foot))) + +#define next_pinuse(p) ((next_chunk(p)->head) & PINUSE_BIT) + +#define get_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot) +#define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s)) + +#define set_size_and_pinuse_of_free_chunk(p, s) \ + ((p)->head = (s | PINUSE_BIT), set_foot(p, s)) + +#define set_free_with_pinuse(p, s, n) \ + (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s)) + +#define overhead_for(p) (is_mmapped(p) ? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD) + +#if defined(DARWIN) || defined(_DARWIN) +#define HAVE_MMAP 1 +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)16U) +#endif +#endif /* DARWIN */ + +#ifndef MALLOC_ALIGNMENT +#if defined(__x86_64__) || defined(__aarch64__) +#define MALLOC_ALIGNMENT ((size_t)(1 * sizeof(void*))) +#else +#define MALLOC_ALIGNMENT ((size_t)(2 * sizeof(void*))) +#endif +#endif /* MALLOC_ALIGNMENT */ + +#ifndef FOOTERS +#define FOOTERS 0 +#endif /* FOOTERS */ + +#if FOOTERS +#define CHUNK_OVERHEAD (TWO_SIZE_T_SIZES + 2 * INT_SIZE) +#else /* FOOTERS */ +#define CHUNK_OVERHEAD (SIZE_T_SIZE + 2 * INT_SIZE) +#endif /* FOOTERS */ + +#define CHUNK_ALIGN_MASK (MALLOC_ALIGNMENT - SIZE_T_ONE) + +#define pad_request(req) \ + (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +#define align_offset(A) \ + ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0) \ + ? 0 \ + : ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & \ + CHUNK_ALIGN_MASK)) +#define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A))) + +#define chunk2mem(p) ((void*)((char*)(p) + (TWO_SIZE_T_SIZES + 2 * INT_SIZE))) + +#define segment_holds(S, A) \ + ((char*)(A) >= S->base && (char*)(A) < S->base + S->size) + +#define cinuse(p) ((p)->head & CINUSE_BIT) +#define pinuse(p) ((p)->head & PINUSE_BIT) +#define flag4inuse(p) ((p)->head & FLAG4_BIT) +#define is_inuse(p) (((p)->head & INUSE_BITS) != PINUSE_BIT) +#define is_mmapped(p) (((p)->head & INUSE_BITS) == 0) + +#define chunksize(p) ((p)->head & ~(FLAG_BITS)) + +#define clear_pinuse(p) ((p)->head &= ~PINUSE_BIT) +#define set_flag4(p) ((p)->head |= FLAG4_BIT) +#define clear_flag4(p) ((p)->head &= ~FLAG4_BIT) + +#define mem2chunk(mem) \ + ((mchunkptr)((char*)(mem) - (TWO_SIZE_T_SIZES + 2 * INT_SIZE))) + +#define IS_UNUSED_BIT (16U) +void init_bins(mstate m); +void local_gcfree(mstate fm, void* mem, int local_idx); +void* mmap_set_free(uint32_t v); + +size_t release_unused_segments(mstate m); + +bool mmap_is_free(const void* p); + +bool is_marked(void* ptr); +void clear_mark(void* ptr); +int get_tag(void* ptr); +void local_insert_chunk(mstate m, mchunkptr mchunk, size_t size, int local_idx); + +#endif // SRC_GC_ALLOCATOR_H_ diff --git a/NativeScript/napi/android/primjs/include/gc/base-global-handles.h b/NativeScript/napi/android/primjs/include/gc/base-global-handles.h new file mode 100644 index 000000000..348e87242 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/gc/base-global-handles.h @@ -0,0 +1,73 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GC_BASE_GLOBAL_HANDLES_H_ +#define SRC_GC_BASE_GLOBAL_HANDLES_H_ + +#define ITERATOR(NODETYPE) \ + NodeIterator final { \ + public: \ + explicit NodeIterator(NodeBlock* block) : block_(block) {} \ + NodeIterator(NodeIterator&& other) \ + : block_(other.block_), index_(other.index_) {} \ + NodeIterator(const NodeIterator&) = delete; \ + NodeIterator& operator=(const NodeIterator&) = delete; \ + bool operator==(const NodeIterator& other) const { \ + return block_ == other.block_; \ + } \ + bool operator!=(const NodeIterator& other) const { \ + return block_ != other.block_; \ + } \ + NodeIterator& operator++() { \ + if (++index_ < kBlockSize) return *this; \ + index_ = 0; \ + block_ = block_->next_used(); \ + return *this; \ + } \ + NODETYPE* operator*() { return block_->at(index_); } \ + NODETYPE* operator->() { return block_->at(index_); } \ + \ + private: \ + NodeBlock* block_ = nullptr; \ + size_t index_ = 0; \ + }; + +#define NODEBLOCK(HANDLETYPE, NODETYPE) \ + NodeBlock final { \ + public: \ + inline static const NodeBlock* From(const NODETYPE* node); \ + inline static NodeBlock* From(NODETYPE* node); \ + NodeBlock(HANDLETYPE* global_handles, HANDLETYPE::NodeSpace* space, \ + NodeBlock* next) \ + : next_(next), global_handles_(global_handles), space_(space) {} \ + ~NodeBlock() = default; \ + NodeBlock(const NodeBlock&) = delete; \ + NodeBlock& operator=(const NodeBlock&) = delete; \ + NODETYPE* at(size_t index) { return &nodes_[index]; } \ + const NODETYPE* at(size_t index) const { return &nodes_[index]; } \ + HANDLETYPE::NodeSpace* space() const { return space_; } \ + HANDLETYPE* global_handles() const { return global_handles_; } \ + inline bool IncreaseUsage(); \ + inline bool DecreaseUsage(); \ + inline void ListAdd(NodeBlock** top); \ + inline void ListRemove(NodeBlock** top); \ + NodeBlock* next() const { return next_; } \ + NodeBlock* next_used() const { return next_used_; } \ + const void* begin_address() const { return nodes_; } \ + const void* end_address() const { return &nodes_[kBlockSize]; } \ + \ + private: \ + NODETYPE nodes_[kBlockSize]; \ + NodeBlock* const next_; \ + HANDLETYPE* const global_handles_; \ + HANDLETYPE::NodeSpace* const space_; \ + NodeBlock* next_used_ = nullptr; \ + NodeBlock* prev_used_ = nullptr; \ + uint32_t used_nodes_ = 0; \ + }; +#endif // SRC_GC_BASE_GLOBAL_HANDLES_H_ diff --git a/NativeScript/napi/android/primjs/include/gc/collector.h b/NativeScript/napi/android/primjs/include/gc/collector.h new file mode 100644 index 000000000..ada95c1a1 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/gc/collector.h @@ -0,0 +1,314 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GC_COLLECTOR_H_ +#define SRC_GC_COLLECTOR_H_ + +#include + +#include "gc/trace-gc.h" +#include "quickjs/include/quickjs-inner.h" + +enum HandleType; +class Visitor; +class Finalizer; +class Sweeper; +typedef struct JSString JSAtomStruct; +struct LEPUSRuntime; +typedef struct malloc_state *mstate; +struct JSAsyncFunctionState; +struct JSProperty; +struct JSRegExp; +struct BCReaderState; +struct JSToken; +struct ValueBuffer; +class GarbageCollector { + public: + GarbageCollector(LEPUSRuntime *rt, mstate) noexcept; + ~GarbageCollector(); + // gc + void CollectGarbage(size_t size = 0) noexcept; + void DoOnlyFinalizer() noexcept; + + void Init(LEPUSRuntime *rt); + Visitor *GetVisitor() { return visitor; } + Finalizer *GetFinalizer() { return finalizer; } + // gc pause suppression mode + void SetGCPauseSuppressionMode(bool mode) { + gc_pause_suppression_mode_ = mode; + } + bool GetGCPauseSuppressionMode() { return gc_pause_suppression_mode_; } + // forbid_gc + void SetForbidGC() { forbid_gc_++; } + void ResetForbidGC() { forbid_gc_--; } + + // for debug +#ifdef ENABLE_GC_DEBUG_TOOLS + size_t mem_order_cnt; + std::unordered_map cur_mems; + std::unordered_set delete_mems[THREAD_NUM]; + size_t delete_order_cnt; + std::unordered_map del_mems; + + size_t handle_order_cnt; + std::unordered_map cur_handles; + size_t qjsvalue_order_cnt; + std::unordered_map cur_qjsvalues; +#endif + + size_t GetHandleSize() { +#ifdef ENABLE_GC_DEBUG_TOOLS + return cur_handles.size(); +#else + return 0; +#endif + } + + size_t GetQjsValueSize() { +#ifdef ENABLE_GC_DEBUG_TOOLS + return cur_qjsvalues.size(); +#else + return 0; +#endif + } + + void SetMaxLimit(size_t limit); + size_t GetMaxLimit(); + void AddGCDuration(int64_t gc_time) { total_duration += gc_time; } + int64_t GetGCDuration() { return total_duration; } + int js_ref_count; + + private: + // gc + void MarkLiveObjects() noexcept; + void SweepDeadObjects() noexcept; +#ifdef ENABLE_TRACING_GC_LOG + void PrintGCLog(int64_t mark_begin, int64_t mark_end) noexcept; +#endif + void UpdateFootprintLimit(size_t size) noexcept; + void UpdateNGFootprintLimit(size_t size) noexcept; + void UpdateGCInfo(size_t heapsize_before, int64_t duration); + + // field + LEPUSRuntime *rt_; + int forbid_gc_; + bool gc_pause_suppression_mode_ = false; + Visitor *visitor; + Finalizer *finalizer; + Sweeper *sweeper; + size_t max_limit; +#ifdef ENABLE_TRACING_GC_LOG + int64_t gc_begin_time; + int64_t last_gc_time; +#endif + int64_t total_duration; + std::stringstream gc_info; + int info_size; +}; + +class Visitor { + public: + Visitor(LEPUSRuntime *rt) noexcept + : rt_(rt), objs(nullptr), objs_len(0), idx(0) { + for (int i = 0; i < THREAD_NUM; i++) { + queue[i] = new Queue(rt_); + } + } + ~Visitor() { + for (int i = 0; i < THREAD_NUM; i++) { + delete queue[i]; + } + if (objs != nullptr) { + system_free(objs); + } + } + void ScanRoots(); + void VisitRootLEPUSValue(LEPUSValue *val, int local_idx) noexcept; + void VisitRootLEPUSValue(LEPUSValue &val, int local_idx) noexcept; + void AddObjectDuringGC(void *ptr) { + if (objs == nullptr) { + objs = static_cast(system_malloc(16 * sizeof(void *))); + objs_len = 16; + } + if (idx >= objs_len) { + int new_len = objs_len * 2; + void **new_objs = + static_cast(system_realloc(objs, new_len * sizeof(void *))); + if (!new_objs) abort(); + objs = new_objs; + objs_len = new_len; + } + objs[idx] = ptr; + idx++; + } + void VisitObjectDuringGC() { + for (int i = 0; i < idx; i++) { + VisitRootHeapObj(objs[i], 0); + } + idx = 0; + } + + private: + // private scanner + void ScanStack() noexcept; + void ScanRuntime() noexcept; + void ScanHandles() noexcept; + void ScanContext(LEPUSContext *ctx) noexcept; + + // visit root + void VisitRoot(void *ptr, HandleType type, int local_idx) noexcept; + void VisitRootHeapObj(void *ptr, int local_idx) noexcept; + void VisitRootHeapObjForTask(Queue *q, void *ptr) noexcept; + void VisitRootCString(char *cstr, int local_idx) noexcept; + void VisitRootJSToken(JSToken *token, int local_idx) noexcept; + void VisitRootBCReaderState(BCReaderState *s, int local_idx) noexcept; + void VisitRootValueBuffer(ValueBuffer *b, int local_idx) noexcept; + void VisitJSAtom(JSAtom atom, int local_idx) noexcept; + + // visitor + /* LEPUSValue with tag -> begin */ + void VisitEntry(void *ptr, int local_idx) noexcept; + void VisitLEPUSLepusRef(void *ptr, int local_idx) noexcept; + void VisitJShape(void *ptr, int local_idx) noexcept; + void VisitJSVarRef(void *ptr, int local_idx) noexcept; + void VisitJSFunctionBytecode(void *ptr, int local_idx) noexcept; + void VisitJSObject(void *ptr, int local_idx) noexcept; + /* LEPUSValue with tag -> end */ + // LEPUS_TAG_BIG_INT + // LEPUS_TAG_BIG_FLOAT + /* LEPUSObject with class_id -> begin */ + void VisitJSBoundFunction(void *ptr, + int local_idx) noexcept; // normal_free + void VisitJSCFunctionDataRecord(void *ptr, + int local_idx) noexcept; // normal_free + void VisitJSForInIterator(void *ptr, + int local_idx) noexcept; // normal_free + void VisitJSArrayBuffer(void *ptr, int local_idx) noexcept; + void VisitJSTypedArray(void *ptr, int local_idx) noexcept; + void VisitJSMapState(void *ptr, int local_idx) noexcept; + void VisitJSMapIteratorData(void *ptr, int local_idx) noexcept; + void VisitJSArrayIteratorData(void *ptr, + int local_idx) noexcept; // normal_free + void VisitJSRegExpStringIteratorData(void *ptr, + int local_idx) noexcept; // normal_free + void VisitJSGeneratorData(void *ptr, int local_idx) noexcept; + void VisitJSProxyData(void *ptr, int local_idx) noexcept; // normal_free + void VisitJSPromiseData(void *ptr, int local_idx) noexcept; // normal_free + void VisitJSPromiseReactionData(void *ptr, + int local_idx) noexcept; // normal_free + void VisitJSPromiseFunctionData(void *ptr, + int local_idx) noexcept; // normal_free + void VisitJSAsyncFunctionData(void *ptr, int local_idx) noexcept; + void VisitJSAsyncFromSyncIteratorData(void *ptr, + int local_idx) noexcept; // normal_free + void VisitJSAsyncGeneratorData(void *ptr, int local_idx) noexcept; + /* LEPUSObject with class_id -> end */ + // scan context +#ifdef ENABLE_QUICKJS_DEBUGGER + void VisitJSScriptSource(void *ptr, int local_idx) noexcept; +#endif + // other + void VisitJSPropertyEnum(void *ptr, + int local_idx) noexcept; // normal_free + void VisitJSModuleDef(void *ptr, int local_idx) noexcept; + void VisitJSFunctionDef(void *ptr, int local_idx) noexcept; + void VisitJSValueArray(void *ptr, int local_idx) noexcept; + void VisitValueSlot(void *ptr, int local_idx) noexcept; + void VisitJsonStrArray(void *ptr, int local_idx) noexcept; + + void VisitSeparableString(void *ptr, int local_idx) noexcept; + void VisitDebuggerInfo(void *, int32_t) noexcept; + void VisitFinalizationRegistryData(void *ptr, int local_idx) noexcept; + + // push ptr + void PushObjLEPUSValue(LEPUSValue &val, int local_idx) noexcept; + void PushObjLEPUSValue(LEPUSValue *val, int local_idx) noexcept; + void PushObjAtom(JSAtom atom, int local_idx) noexcept; + void PushObjJSAsyncFunctionState(JSAsyncFunctionState *s, + int local_idx) noexcept; + void PushObjJStackFrame(LEPUSStackFrame *sf, int local_idx) noexcept; + void PushBytecodeAtoms(const uint8_t *bc_buf, int bc_len, + int use_short_opcodes, int local_idx) noexcept; + void PushObjJSRegExp(JSRegExp *re, int local_idx) noexcept; + void PushObjFunc(LEPUSObject *obj, int local_idx) noexcept; + void PushObjArray(LEPUSObject *obj, int local_idx) noexcept; + void PushObjRegExp(LEPUSObject *obj, int local_idx) noexcept; + void PushObjProperty(JSProperty *pr, int prop_flags, int local_idx) noexcept; + + // tools + bool IsConstString(void *ptr) { + return get_alloc_tag(ptr) == ALLOC_TAG_JSConstString; + } + // field + LEPUSRuntime *rt_; + Queue *queue[THREAD_NUM]; // for visit + void **objs; + int objs_len; + int idx; +}; + +class Finalizer { + public: + Finalizer(LEPUSRuntime *rt) noexcept : rt_(rt) {} + void close_var_refs(LEPUSStackFrame *sf) noexcept; + void free_atom(LEPUSRuntime *rt, JSAtomStruct *p) noexcept; + // do finalizer + void DoFinalizer(void *ptr) noexcept; + void DoFinalizer2(void *ptr) noexcept; +#ifdef ENABLE_LEPUSNG + void JSLepusRefFinalizer(void *ptr) noexcept; +#endif +#ifdef CONFIG_BIGNUM + void JSBigFloatFinalizer(void *ptr) noexcept; +#endif + void JSObjectFinalizer(void *ptr) noexcept; + void JSObjectOnlyFinalizer(void *ptr) noexcept; + void JSStringFinalizer(void *ptr) noexcept; +#ifdef ENABLE_LEPUSNG + void JSStringOnlyFinalizer(void *ptr) noexcept; +#endif + void JSSymbolFinalizer(void *ptr) noexcept; + void JSShapeFinalizer(void *ptr) noexcept; + void JSVarRefFinalizer(void *ptr) noexcept; + void JSFunctionBytecodeFinalizer(void *ptr) noexcept; + void JSArrayBufferFinalizer(void *ptr) noexcept; + void JSTypedArrayFinalizer(void *ptr) noexcept; + void JSMapStateFinalizer(void *ptr) noexcept; + void JSMapIteratorDataFinalizer(void *ptr) noexcept; + void JSGeneratorDataFinalizer(void *ptr) noexcept; + void JSAsyncFunctionDataFinalizer(void *ptr) noexcept; + void JSAsyncGeneratorDataFinalizer(void *ptr) noexcept; + void JSModuleDefFinalizer(void *ptr) noexcept; + void JSFunctionDefFinalizer(void *ptr) noexcept; + void JSSeparableStringFinalizer(void *ptr) noexcept {} + void FinalizationRegistryDataFinalizer(void *ptr) noexcept; + void WeakRefDataFinalizer(void *ptr) noexcept; + + private: + LEPUSRuntime *rt_; +}; + +class MlockScope { + public: + MlockScope(Queue **q) : queue(q) { +#ifndef _WIN32 + for (int i = 0; i < THREAD_NUM; i++) { + SYSCALL_CHECK( + mlock(queue[i]->GetQueue(), queue[i]->GetSize() * sizeof(uintptr_t))) + } +#endif + } + ~MlockScope() { +#ifndef _WIN32 + for (int i = 0; i < THREAD_NUM; i++) { + SYSCALL_CHECK(munlock(queue[i]->GetQueue(), + queue[i]->GetSize() * sizeof(uintptr_t))); + } +#endif + } + + private: + Queue **queue; +}; +#endif // SRC_GC_COLLECTOR_H_ diff --git a/NativeScript/napi/android/primjs/include/gc/global-handles.h b/NativeScript/napi/android/primjs/include/gc/global-handles.h new file mode 100644 index 000000000..430151f11 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/gc/global-handles.h @@ -0,0 +1,62 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GC_GLOBAL_HANDLES_H_ +#define SRC_GC_GLOBAL_HANDLES_H_ + +// #include + +#include "gc/persistent-handle.h" + +typedef uintptr_t Addr; + +// Global handles hold handles that are independent of stack-state and can have +// callbacks and finalizers attached to them. +class GlobalHandles final { + public: + static void EnableMarkingBarrier(LEPUSRuntime* runtime); + static void DisableMarkingBarrier(LEPUSRuntime* runtime); + + GlobalHandles(const GlobalHandles&) = delete; + GlobalHandles& operator=(const GlobalHandles&) = delete; + + class NodeBlock; + + static void Destroy(LEPUSValue* location); + + explicit GlobalHandles(LEPUSRuntime* runtime); + ~GlobalHandles(); + + // Creates a new global handle that is alive until Destroy is called. + LEPUSValue* Create(LEPUSValue value, bool is_weak); + + void IterateAllRoots(int local_idx, int offset); + void GlobalRootsFinalizer(); + bool IsMarkedLEPUSValue(LEPUSValue* val); + LEPUSRuntime* runtime() const { return runtime_; } + + size_t TotalSize() const; + size_t UsedSize() const; + // Number of global handles. + size_t handles_count() const; + void SetWeak(LEPUSValue* location, void* data, void (*cb)(void*)); + void ClearWeak(LEPUSValue* location); + void SetWeakState(LEPUSValue* location); + + private: + // Internal node structures. + class NodeIterator; + class NodeSpace; + + LEPUSRuntime* const runtime_; + bool is_marking_ = false; + + NodeSpace* regular_nodes_; +}; + +#endif // SRC_GC_GLOBAL_HANDLES_H_ diff --git a/NativeScript/napi/android/primjs/include/gc/persistent-handle.h b/NativeScript/napi/android/primjs/include/gc/persistent-handle.h new file mode 100644 index 000000000..c67bd0fa6 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/gc/persistent-handle.h @@ -0,0 +1,222 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GC_PERSISTENT_HANDLE_H_ +#define SRC_GC_PERSISTENT_HANDLE_H_ +extern "C" { +#include "quickjs/include/quickjs.h" +} +#ifdef ENABLE_GC_DEBUG_TOOLS +#define DCHECK2(condition) \ + if (!(condition)) abort(); +#if defined(ANDROID) || defined(__ANDROID__) +#include +#endif +#else +#define DCHECK2(condition) ((void)0) +#endif + +const int kApiSystemPointerSize = sizeof(void*); +static const int kNodeClassIdOffset = 1 * kApiSystemPointerSize; + +class PersistentBase { + public: + /** + * If non-empty, destroy the underlying storage cell + * IsEmpty() will return true after this call. + */ + inline void Reset(LEPUSRuntime* runtime); + inline void Reset(LEPUSContext* ctx); + + /** + * If non-empty, destroy the underlying storage cell + * and create a new one with the contents of other if other is non empty + */ + inline void Reset(LEPUSRuntime* runtime, LEPUSValue other, + bool is_weak = false); + + inline void Reset(LEPUSContext* ctx, LEPUSValue other, bool is_weak = false); + + inline bool IsEmpty() const { return val_ == nullptr; } + inline void Empty() { val_ = nullptr; } + + inline LEPUSValue Get() const { + if (val_) return *val_; + return LEPUS_UNDEFINED; + } + + inline bool IsWeak() const; + + PersistentBase(const PersistentBase& other) = delete; + void operator=(const PersistentBase&) = delete; + + explicit inline PersistentBase(LEPUSValue* val) : val_(val) {} + inline static LEPUSValue* New(LEPUSRuntime* runtime, LEPUSValue that, + bool is_weak); + + LEPUSValue* val_; +}; + +class GCPersistent : public PersistentBase { + public: + /** + * A GCPersistent with no storage cell. + */ + inline GCPersistent() : PersistentBase(nullptr) {} + /** + * Construct a GCPersistent from a Local. + * When the Local is non-empty, a new storage cell is created + * pointing to the same object, and no flags are set. + */ + inline GCPersistent(LEPUSRuntime* runtime, LEPUSValue that, + bool is_weak = false) + : PersistentBase(PersistentBase::New(runtime, that, is_weak)) {} + + inline GCPersistent(LEPUSContext* ctx, LEPUSValue that, bool is_weak = false) + : PersistentBase( + PersistentBase::New(LEPUS_GetRuntime(ctx), that, is_weak)) {} + + /** + * The copy constructors and assignment operator create a GCPersistent + * exactly as the GCPersistent constructor, but the Copy function from the + * traits class is called, allowing the setting of flags based on the + * copied GCPersistent. + */ + GCPersistent(const GCPersistent& that) = delete; + + GCPersistent& operator=(const GCPersistent& that) = delete; + + /** + * The destructor will dispose the GCPersistent based on the + * kResetInDestructor flags in the traits class. Since not calling dispose + * can result in a memory leak, it is recommended to always set this flag. + */ + inline ~GCPersistent() { + // if (false) this->Reset(); + } + void SetWeak(LEPUSRuntime* runtime) { SetWeakState(runtime, this->val_); } + + private: + friend class Utils; + friend class GCPersistent; + inline LEPUSValue* operator*() const { return this->val_; } +}; + +class WASMGCPersistent : public PersistentBase { + public: + inline WASMGCPersistent() + : PersistentBase(nullptr), rt(nullptr), val(LEPUS_UNDEFINED) {} + inline WASMGCPersistent(LEPUSValue that) + : PersistentBase(nullptr), rt(nullptr), val(that) {} + + inline WASMGCPersistent(LEPUSRuntime* runtime, LEPUSValue that, + bool is_weak = false) + : PersistentBase(PersistentBase::New(runtime, that, is_weak)), + rt(runtime), + val(that) {} + + inline WASMGCPersistent(LEPUSContext* ctx, LEPUSValue that, + bool is_weak = false) + : PersistentBase( + PersistentBase::New(LEPUS_GetRuntime(ctx), that, is_weak)), + rt(LEPUS_GetRuntime(ctx)), + val(that) {} + + WASMGCPersistent(const WASMGCPersistent& that) + : PersistentBase(PersistentBase::New(that.GetRT(), that.Get(), false)) { + rt = that.GetRT(); + val = that.Get(); + } + + WASMGCPersistent& operator=(const WASMGCPersistent& that) { + if (val_) { + *val_ = that.Get(); + } else { + val_ = PersistentBase::New(that.GetRT(), that.Get(), false); + } + rt = that.GetRT(); + val = that.Get(); + return *this; + } + void Reset(LEPUSContext* ctx, LEPUSValue value) { + PersistentBase::Reset(LEPUS_GetRuntime(ctx), value, false); + rt = LEPUS_GetRuntime(ctx); + val = value; + } + + LEPUSValue Get() const { + if (val_) { + return *val_; + } else { + return val; + } + } + + LEPUSValue* GetPtr() const { + if (val_) { + return val_; + } else { + return const_cast(&val); + } + } + + virtual inline ~WASMGCPersistent() { PersistentBase::Reset(rt); } + LEPUSRuntime* GetRT() const { return rt; } + + private: + inline LEPUSValue* operator*() const { return this->val_; } + LEPUSRuntime* rt; + LEPUSValue val; +}; + +LEPUSValue* PersistentBase::New(LEPUSRuntime* runtime, LEPUSValue that, + bool is_weak) { + if (!runtime || LEPUS_IsUndefined(that)) return nullptr; + return GlobalizeReference(runtime, that, is_weak); +} + +bool PersistentBase::IsWeak() const { + *reinterpret_cast(0xdead) = 0; + return false; +} + +void PersistentBase::Reset(LEPUSRuntime* runtime) { + if (this->IsEmpty() || !runtime) return; + DisposeGlobal(runtime, this->val_); + val_ = nullptr; +} + +void PersistentBase::Reset(LEPUSContext* ctx) { Reset(LEPUS_GetRuntime(ctx)); } + +/** + * If non-empty, destroy the underlying storage cell + * and create a new one with the contents of other if other is non empty + */ + +void PersistentBase::Reset(LEPUSRuntime* runtime, LEPUSValue other, + bool is_weak) { + Reset(runtime); + if (LEPUS_IsUndefined(other)) return; + this->val_ = New(runtime, other, is_weak); +} + +void PersistentBase::Reset(LEPUSContext* ctx, LEPUSValue other, bool is_weak) { + Reset(LEPUS_GetRuntime(ctx), other, is_weak); +} + +class QJSValueValueAllocator { + public: + static void* New(LEPUSRuntime* runtime) { + return AllocateQJSValueValue(runtime); + } + static void Delete(LEPUSRuntime* runtime, void* instance) { + FreeQJSValueValue(runtime, reinterpret_cast(instance)); + } +}; + +#endif // SRC_GC_PERSISTENT_HANDLE_H_ diff --git a/NativeScript/napi/android/primjs/include/gc/qjsvaluevalue-space.h b/NativeScript/napi/android/primjs/include/gc/qjsvaluevalue-space.h new file mode 100644 index 000000000..09668f55e --- /dev/null +++ b/NativeScript/napi/android/primjs/include/gc/qjsvaluevalue-space.h @@ -0,0 +1,34 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GC_QJSVALUEVALUE_SPACE_H_ +#define SRC_GC_QJSVALUEVALUE_SPACE_H_ + +#include "gc/persistent-handle.h" +class QJSValueValueSpace final { + public: + QJSValueValueSpace(const QJSValueValueSpace&) = delete; + QJSValueValueSpace& operator=(const QJSValueValueSpace&) = delete; + + class NodeBlock; + class NodeIterator; + class NodeSpace; + + static void Destroy(void* location); + explicit QJSValueValueSpace(LEPUSRuntime* runtime); + ~QJSValueValueSpace(); + void* Create(); + void IterateAllRoots(int local_idx); + LEPUSRuntime* runtime() const { return runtime_; } + + private: + LEPUSRuntime* runtime_; + NodeSpace* regular_nodes_; +}; + +#endif // SRC_GC_QJSVALUEVALUE_SPACE_H_ diff --git a/NativeScript/napi/android/primjs/include/gc/sweeper.h b/NativeScript/napi/android/primjs/include/gc/sweeper.h new file mode 100644 index 000000000..df7dacab2 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/gc/sweeper.h @@ -0,0 +1,58 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GC_SWEEPER_H_ +#define SRC_GC_SWEEPER_H_ +#include + +#include "quickjs/include/quickjs-inner.h" + +class Sweeper { + public: + Sweeper(mstate state) : m(state) {} + void sweep_finalizer(); + void sweep_free(); + void traverse_finalizer(bool is_only, int64_t begin_time); + void traverse_chunk_for_finalizer(bool is_only = false); + void free_mmap_objects(); + void traverse_chunk_for_free(); + void reinit_freelist(); + int calculate_task_granularity(); + static void generate_freelist(mstate m, msegmentptr sp_begin, + msegmentptr sp_end); + + private: + mstate m; +}; + +void do_finalizer(void* runtime, void* ptr, bool is_only); + +void do_global_finalizer(void* rt); + +void merge_dead_objs(mstate m, msegmentptr sp_begin, msegmentptr sp_end); + +class AcquireIdxScope { + private: + int local_idx; + mstate m_; + + public: + AcquireIdxScope(mstate m) { + local_idx = atomic_acqurie_local_idx(m); + m_ = m; + while (local_idx == -1) { +#ifndef _WIN32 + sched_yield(); +#endif + local_idx = atomic_acqurie_local_idx(m); + } + } + ~AcquireIdxScope() { atomic_release_local_idx(m_, local_idx); } + operator int() { return local_idx; } +}; +void parallel_traverse_heap_segment( + mstate m, size_t segs_in_thread, ByteThreadPool* workerThreadPool, + std::function func); + +#endif // SRC_GC_SWEEPER_H_ diff --git a/NativeScript/napi/android/primjs/include/gc/thread_pool.h b/NativeScript/napi/android/primjs/include/gc/thread_pool.h new file mode 100644 index 000000000..b6b12c3b7 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/gc/thread_pool.h @@ -0,0 +1,205 @@ +/* + * Copyright (c) [2020] Huawei Technologies Co.,Ltd.All rights reserved. + * + * OpenArkCompiler is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan + * PSL v2. You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY + * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the + * Mulan PSL v2 for more details. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GC_THREAD_POOL_H_ +#define SRC_GC_THREAD_POOL_H_ + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include +#include + +class ByteTask { + public: + ByteTask() = default; + virtual ~ByteTask() = default; + virtual void Execute(size_t threadId) = 0; +}; + +class ByteLambdaTask : public ByteTask { + public: + explicit ByteLambdaTask(const std::function &function) + : func(function) {} + ~ByteLambdaTask() = default; + void Execute(size_t threadId) override { func(threadId); } + + private: + std::function func; +}; + +class ByteThreadPool; + +class BytePoolThread { + public: + // use for profiling + std::vector *schedCores; + + BytePoolThread(ByteThreadPool *threadPool, const char *threadName, + size_t threadId, size_t stackSize); + ~BytePoolThread(); + + void SetPriority(int32_t prior); + +#ifndef _WIN32 + // get pthread of thread + pthread_t GetThread() const { return pthread; } + + // get thread id of thread + pid_t GetTid() const { return tid; } +#endif + + static void *WorkerFunc(void *param); + + private: + size_t id; +#ifndef _WIN32 + pthread_t pthread; + pid_t tid; +#endif + std::string name; + ByteThreadPool *pool; +}; + +// manual +// new (SetMaxActiveThreadNum(optional) addTask startPool waitFinish)^. Exit +// delete if need to change MaxActiveThreadNum, should waitFinish or stop pool +// at first +class ByteThreadPool { + public: + // Constructor for thread pool, 1) Create threads, 2) wait all thread created + // & sleep name is the thread pool name. thread name = + // Pool_$(poolname)_ThreadId_$(threadId) maxThreadNum is the max thread number + // in pool. prior is the priority of threads in pool. + ByteThreadPool(const char *name, int32_t maxThreadNum, int32_t prior); + + // Destructor for thread pool, 1) close pool 2) wait thread in pool to exit, + // 3) release resources of class + ~ByteThreadPool(); + + // Set priority of each thread in pool. + void SetPriority(int32_t prior); + + // Set max active thread number of pool, redundant thread hangup in sleep + // condition var. notify more waitting thread get to work when pool is + // running. Range [1 - maxThreadNum]. + void SetMaxActiveThreadNum(int32_t num); + + // Get max active thread number of pool. + int32_t GetMaxActiveThreadNum() const { return maxActiveThreadNum; } + + // Get max thread number of pool, defalut = maxThreadNum. + int32_t GetMaxThreadNum() const { return maxThreadNum; } + + // Add new task to task queue , task should inherit from ByteTask. + void AddTask(ByteTask *task); + + // Add task to thread , func indicate Lambda statement. + void AddTask(std::function func); + + // Start thread pool, notify all sleep threads to get to work + void Start(); + + // Wait all task in task queue finished, if pool stopped, only wait until + // current excuting task finish after all task finished, stop pool + // addToExecute indicate whether the caller thread excute task + void WaitFinish(bool addToExecute, + std::vector *schedCores = nullptr); + + // used in none-parallel concurrent mark + void DrainTaskQueue(); + + // Notify & Wait all thread waitting for task to sleep + void Stop(); + + // Notify all thread in pool to exit , notify all waitFinish thread to + // return,nonblock ^. + void Exit(); + + // Remove all task in task queue + void ClearAllTask(); + + // Get task count in queue + size_t GetTaskNumber() { + std::unique_lock taskLock(taskMutex); + return taskQueue.size(); + } + + // Get all BytePoolThread in pool + const std::vector &GetThreads() const { return threads; } + + private: + // thread default stack size 512 KB. + static const size_t kDefaultStackSize = (512 * 1024); + // int32_t priority; + + std::string name; + // pool stop or running state + std::atomic running; + // is pool exit + std::atomic exit; + // all task put in task queue + std::queue taskQueue; + + // active thread 0 ..... maxActiveThreadNum .....maxThreadNum + // max thread number in pool + int32_t maxThreadNum; + + // max active thread number, redundant thread hang up in threadSleepingCondVar + int32_t maxActiveThreadNum; + + // current active thread, when equals to zero, no thread running, all thread + // slept + std::atomic currActiveThreadNum; + + // current waitting thread, when equals to currActiveThreadNum + // no thread excuting, all task finished + std::atomic currWaittingThreadNum; + + // single lock + std::mutex taskMutex; + + // hangup when no task available + std::condition_variable taskEmptyCondVar; + + // hangup when to much active thread or pool stopped + std::condition_variable threadSleepingCondVar; + + // hangup when there is thread excuting + std::condition_variable allWorkDoneCondVar; + + // hangup when there is thread active + std::condition_variable allThreadStopped; + + // use for profiling + std::vector threads; + + // is pool running or stopped + bool IsRunning() const { return running.load(std::memory_order_relaxed); } + + bool IsExited() const { return exit.load(std::memory_order_relaxed); } + + friend class BytePoolThread; +}; + +#endif // SRC_GC_THREAD_POOL_H_ diff --git a/NativeScript/napi/android/primjs/include/gc/trace-gc.h b/NativeScript/napi/android/primjs/include/gc/trace-gc.h new file mode 100644 index 000000000..c45f27920 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/gc/trace-gc.h @@ -0,0 +1,137 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_GC_TRACE_GC_H_ +#define SRC_GC_TRACE_GC_H_ +#ifdef __cplusplus +extern "C" { +#endif +#include "quickjs/include/quickjs.h" +#ifdef __cplusplus +} +#endif + +struct LEPUSRuntime; + +typedef struct napi_env__ *napi_env; +typedef struct napi_value__ *napi_value; +class napi_handle_scope__; +typedef void napi_func(napi_env env, napi_handle_scope__ *scope); + +enum HandleType { + HANDLE_TYPE_UNDEFINED, + HANDLE_TYPE_HEAP_OBJ, + HANDLE_TYPE_DIR_HEAP_OBJ, + HANDLE_TYPE_LEPUS_VALUE, + HANDLE_TYPE_CSTRING, + HANDLE_TYPE_LEPUS_TOKEN, + HANDLE_TYPE_BC_READER_STATE, + HANDLE_TYPE_VALUE_BUFFER +}; + +typedef struct { + void *ptr; + HandleType type; +} HeapStruct; + +class PtrHandles { + public: + PtrHandles(LEPUSRuntime *rt); + ~PtrHandles(); + // handles + void PushHandle(void *ptr, HandleType type); + void ResetHandle(void *val, HandleType type); + void PushLEPUSValueArrayHandle(LEPUSValue *array, int size, + bool need_init = true); + // special type + void PushLEPUSAtom(uint32_t atom); + void PushLEPUSPropertyDescriptor(LEPUSPropertyDescriptor *desc); + void PushLEPUSValuePtr(LEPUSValue val); + // tools + HeapStruct *GetHandles() const { return handles; } + void SetHeapObjIdx(int idx) { handle_idx = idx; } + int GetHeapObjIdx() const { return handle_idx; } + + private: + int handle_idx; + int handle_size; + HeapStruct *handles; + LEPUSRuntime *rt_; + void InitialHandles(); + void ResizeHandles(); +}; + +class HandleScope { + public: + HandleScope(LEPUSRuntime *rt); + HandleScope(LEPUSContext *ctx); + HandleScope(LEPUSContext *ctx, void *ptr, HandleType type); + ~HandleScope(); + void PushHandle(void *ptr, HandleType type); + void PushLEPUSAtom(JSAtom atom); + void PushLEPUSValueArrayHandle(LEPUSValue *array, int size, + bool need_init = true); + void ResetHandle(void *ptr, HandleType type); + void PushLEPUSPropertyDescriptor(LEPUSPropertyDescriptor *desc); + + private: + PtrHandles *ptr_handles; + int handle_prev_idx; +}; + +class napi_handle_scope__ { + public: + napi_handle_scope__(napi_env env, LEPUSContext *ctx, napi_func *func = nullptr); + explicit napi_handle_scope__(LEPUSContext *ctx) + : env_(nullptr), + ctx_(ctx), + handle_tail_(nullptr), + reset_napi_env(nullptr) { + is_gc = ctx_ == nullptr ? false : LEPUS_IsGCMode(ctx_); + if (is_gc) { + prev_ = reinterpret_cast(GetNapiScope(ctx_)); + SetNapiScope(ctx_, this); + } + } + + inline ~napi_handle_scope__() { + Handle *curr = handle_tail_; + while (curr) { + Handle *temp = curr; + if (!is_gc) { + LEPUS_FreeValue(ctx_, curr->value); + } + curr = curr->prev; + delete temp; + } + if (is_gc) { + SetNapiScope(ctx_, prev_); + } else { + reset_napi_env(env_, prev_); + } + } + + napi_handle_scope__(const napi_handle_scope__ &) = delete; + void operator=(const napi_handle_scope__ &) = delete; + + napi_value Escape(napi_value v); + + napi_value CreateHandle(LEPUSValue v); + struct Handle { + LEPUSValue value; + Handle *prev; + }; + Handle *GetHandle() { return handle_tail_; } + napi_handle_scope__ *GetPrevScope() { return prev_; } + + napi_env env_; + LEPUSContext *ctx_; + bool is_gc; + napi_handle_scope__ *prev_; + Handle *handle_tail_; + napi_func *reset_napi_env; +}; + + +#endif // SRC_GC_TRACE_GC_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/BUILD.gn b/NativeScript/napi/android/primjs/include/quickjs/BUILD.gn new file mode 100644 index 000000000..0c3cb42c7 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/BUILD.gn @@ -0,0 +1,21 @@ +# Copyright 2024 The Lynx Authors. All rights reserved. +# Licensed under the Apache License Version 2.0 that can be found in the +# LICENSE file in the root directory of this source tree. +import("//Primjs.gni") +primjs_source_set("quickjs") { + sources = [ + "source/cutils.cc", + "source/libbf.cc", + "source/libregexp.cc", + "source/libunicode.cc", + "source/primjs_monitor.cc", + "source/quickjs-libc.cc", + "source/quickjs.cc", + "source/quickjs_gc.cc", + "source/quickjs_queue.cc", + "source/quickjs_version.cc", + ] + if (use_bignum) { + sources += [ "source/libbf.cc" ] + } +} diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/base_export.h b/NativeScript/napi/android/primjs/include/quickjs/include/base_export.h new file mode 100644 index 000000000..81216f05d --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/base_export.h @@ -0,0 +1,18 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_BASE_EXPORT_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_BASE_EXPORT_H_ + +#if defined(WIN32) +#define QJS_EXPORT __declspec(dllimport) +#define QJS_EXPORT_FOR_DEVTOOL __declspec(dllimport) +#define QJS_HIDE +#else // defined(WIN32) +#define QJS_EXPORT __attribute__((visibility("default"))) +#define QJS_EXPORT_FOR_DEVTOOL __attribute__((visibility("default"))) +#define QJS_HIDE __attribute__((visibility("hidden"))) +#endif // defined(WIN32) + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_BASE_EXPORT_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/cutils.h b/NativeScript/napi/android/primjs/include/quickjs/include/cutils.h new file mode 100644 index 000000000..97fd0b478 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/cutils.h @@ -0,0 +1,255 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_CUTILS_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_CUTILS_H_ + +#include +#include + +#include "base_export.h" + +/* set if CPU is big endian */ +#undef WORDS_BIGENDIAN + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#define force_inline inline __attribute__((always_inline)) +#define no_inline __attribute__((noinline)) +#define __maybe_unused __attribute__((unused)) + +#define xglue(x, y) x##y +#define glue(x, y) xglue(x, y) +#define stringify(s) tostring(s) +#define tostring(s) #s + +#ifndef offsetof +#define offsetof(type, field) ((size_t) & ((type *)0)->field) +#endif +#ifndef countof +#define countof(x) (sizeof(x) / sizeof((x)[0])) +#endif + +typedef int BOOL; + +#ifndef FALSE +enum { + FALSE = 0, + TRUE = 1, +}; +#endif + +QJS_HIDE void pstrcpy(char *buf, int buf_size, const char *str); +QJS_HIDE char *pstrcat(char *buf, int buf_size, const char *s); +QJS_HIDE int strstart(const char *str, const char *val, const char **ptr); +QJS_HIDE int has_suffix(const char *str, const char *suffix); + +static inline int max_int(int a, int b) { + if (a > b) + return a; + else + return b; +} + +static inline int min_int(int a, int b) { + if (a < b) + return a; + else + return b; +} + +static inline uint32_t max_uint32(uint32_t a, uint32_t b) { + if (a > b) + return a; + else + return b; +} + +static inline uint32_t min_uint32(uint32_t a, uint32_t b) { + if (a < b) + return a; + else + return b; +} + +static inline int64_t max_int64(int64_t a, int64_t b) { + if (a > b) + return a; + else + return b; +} + +static inline int64_t min_int64(int64_t a, int64_t b) { + if (a < b) + return a; + else + return b; +} + +/* WARNING: undefined if a = 0 */ +static inline int clz32(unsigned int a) { return __builtin_clz(a); } + +/* WARNING: undefined if a = 0 */ +static inline int clz64(uint64_t a) { return __builtin_clzll(a); } + +/* WARNING: undefined if a = 0 */ +static inline int ctz32(unsigned int a) { return __builtin_ctz(a); } + +/* WARNING: undefined if a = 0 */ +static inline int ctz64(uint64_t a) { return __builtin_ctzll(a); } + +struct __attribute__((packed)) packed_u64 { + uint64_t v; +}; + +struct __attribute__((packed)) packed_u32 { + uint32_t v; +}; + +struct __attribute__((packed)) packed_u16 { + uint16_t v; +}; + +static inline uint64_t get_u64(const uint8_t *tab) { + return ((const struct packed_u64 *)tab)->v; +} + +static inline int64_t get_i64(const uint8_t *tab) { + return (int64_t)((const struct packed_u64 *)tab)->v; +} + +static inline void put_u64(uint8_t *tab, uint64_t val) { + ((struct packed_u64 *)tab)->v = val; +} + +static inline uint32_t get_u32(const uint8_t *tab) { + return ((const struct packed_u32 *)tab)->v; +} + +static inline int32_t get_i32(const uint8_t *tab) { + return (int32_t)((const struct packed_u32 *)tab)->v; +} + +static inline void put_u32(uint8_t *tab, uint32_t val) { + ((struct packed_u32 *)tab)->v = val; +} + +static inline uint32_t get_u16(const uint8_t *tab) { + return ((const struct packed_u16 *)tab)->v; +} + +static inline int32_t get_i16(const uint8_t *tab) { + return (int16_t)((const struct packed_u16 *)tab)->v; +} + +static inline void put_u16(uint8_t *tab, uint16_t val) { + ((struct packed_u16 *)tab)->v = val; +} + +static inline uint32_t get_u8(const uint8_t *tab) { return *tab; } + +static inline int32_t get_i8(const uint8_t *tab) { return (int8_t)*tab; } + +static inline void put_u8(uint8_t *tab, uint8_t val) { *tab = val; } + +static inline uint16_t bswap16(uint16_t x) { return (x >> 8) | (x << 8); } + +static inline uint32_t bswap32(uint32_t v) { + return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | + ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); +} + +static inline uint64_t bswap64(uint64_t v) { + return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | + ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | + ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | + ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | + ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | + ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | + ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | + ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); +} + +/* XXX: should take an extra argument to pass slack information to the caller */ +typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size, + int alloc_tag); + +typedef struct DynBuf { + uint8_t *buf; + size_t size; + size_t allocated_size; + BOOL error; /* true if a memory allocation error occurred */ + DynBufReallocFunc *realloc_func; + void *opaque; /* for realloc_func */ +} DynBuf; + +QJS_HIDE void dbuf_init(DynBuf *s); +QJS_HIDE void dbuf_init2(DynBuf *s, void *opaque, + DynBufReallocFunc *realloc_func); +QJS_HIDE int dbuf_realloc(DynBuf *s, size_t new_size, int alloc_tag = 1); +QJS_HIDE int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, + size_t len); +QJS_HIDE int dbuf_put(DynBuf *s, const uint8_t *data, size_t len); +QJS_HIDE int dbuf_put_self(DynBuf *s, size_t offset, size_t len); +QJS_HIDE int dbuf_putc(DynBuf *s, uint8_t c); +QJS_HIDE int dbuf_putstr(DynBuf *s, const char *str); +QJS_HIDE static inline int dbuf_put_u16(DynBuf *s, uint16_t val) { + return dbuf_put(s, (uint8_t *)&val, 2); +} +QJS_HIDE static inline int dbuf_put_u32(DynBuf *s, uint32_t val) { + return dbuf_put(s, (uint8_t *)&val, 4); +} +QJS_HIDE static inline int dbuf_put_u64(DynBuf *s, uint64_t val) { + return dbuf_put(s, (uint8_t *)&val, 8); +} +int __attribute__((format(printf, 2, 3))) QJS_HIDE dbuf_printf(DynBuf *s, + const char *fmt, + ...); +QJS_HIDE void dbuf_free(DynBuf *s); +static inline BOOL dbuf_error(DynBuf *s) { return s->error; } + +#define UTF8_CHAR_LEN_MAX 6 + +int unicode_to_utf8(uint8_t *buf, unsigned int c); +int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp); + +static inline int from_hex(int c) { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +void rqsort(void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *, void *), void *arg); + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_CUTILS_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/libbf.h b/NativeScript/napi/android/primjs/include/quickjs/include/libbf.h new file mode 100644 index 000000000..d1e4256e2 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/libbf.h @@ -0,0 +1,343 @@ +/* + * Tiny arbitrary precision floating point library + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_LIBBF_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_LIBBF_H_ + +#include +#include + +#if defined(__x86_64__) +#define LIMB_LOG2_BITS 6 +#else +#define LIMB_LOG2_BITS 5 +#endif + +#define LIMB_BITS (1 << LIMB_LOG2_BITS) + +#if LIMB_BITS == 64 +typedef __int128 int128_t; +typedef unsigned __int128 uint128_t; +typedef int64_t slimb_t; +typedef uint64_t limb_t; +typedef uint128_t dlimb_t; +#define EXP_MIN INT64_MIN +#define EXP_MAX INT64_MAX + +#else + +typedef int32_t slimb_t; +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; +#define EXP_MIN INT32_MIN +#define EXP_MAX INT32_MAX + +#endif + +/* in bits */ +#define BF_EXP_BITS_MIN 3 +#define BF_EXP_BITS_MAX (LIMB_BITS - 2) +#define BF_PREC_MIN 2 +#define BF_PREC_MAX (((limb_t)1 << BF_EXP_BITS_MAX) - 2) +#define BF_PREC_INF (BF_PREC_MAX + 1) /* infinite precision */ + +#if LIMB_BITS == 64 +#define BF_CHKSUM_MOD (UINT64_C(975620677) * UINT64_C(9795002197)) +#else +#define BF_CHKSUM_MOD 975620677U +#endif + +#define BF_EXP_ZERO EXP_MIN +#define BF_EXP_INF (EXP_MAX - 1) +#define BF_EXP_NAN EXP_MAX + +/* +/-zero is represented with expn = BF_EXP_ZERO and len = 0, + +/-infinity is represented with expn = BF_EXP_INF and len = 0, + NaN is represented with expn = BF_EXP_NAN and len = 0 (sign is ignored) + */ +typedef struct { + struct bf_context_t *ctx; + int sign; + slimb_t expn; + limb_t len; + limb_t *tab; +} bf_t; + +typedef enum { + BF_RNDN, /* round to nearest, ties to even */ + BF_RNDZ, /* round to zero */ + BF_RNDD, /* round to -inf */ + BF_RNDU, /* round to +inf */ + BF_RNDNA, /* round to nearest, ties away from zero */ + BF_RNDNU, /* round to nearest, ties to +inf */ + BF_RNDF, /* faithful rounding (nondeterministic, either RNDD or RNDU, + inexact flag is always set) */ +} bf_rnd_t; + +/* allow subnormal numbers (only available if the number of exponent + bits is < BF_EXP_BITS_MAX and prec != BF_PREC_INF) */ +#define BF_FLAG_SUBNORMAL (1 << 3) + +#define BF_RND_MASK 0x7 +#define BF_EXP_BITS_SHIFT 4 +#define BF_EXP_BITS_MASK 0x3f + +/* contains the rounding mode and number of exponents bits */ +typedef uint32_t bf_flags_t; + +typedef void *bf_realloc_func_t(void *opaque, void *ptr, size_t size); + +typedef struct { + bf_t val; + limb_t prec; +} BFConstCache; + +typedef struct bf_context_t { + void *realloc_opaque; + bf_realloc_func_t *realloc_func; + BFConstCache log2_cache; + BFConstCache pi_cache; + struct BFNTTState *ntt_state; +} bf_context_t; + +static inline int bf_get_exp_bits(bf_flags_t flags) { + return BF_EXP_BITS_MAX - ((flags >> BF_EXP_BITS_SHIFT) & BF_EXP_BITS_MASK); +} + +static inline bf_flags_t bf_set_exp_bits(int n) { + return (BF_EXP_BITS_MAX - n) << BF_EXP_BITS_SHIFT; +} + +/* returned status */ +#define BF_ST_INVALID_OP (1 << 0) +#define BF_ST_DIVIDE_ZERO (1 << 1) +#define BF_ST_OVERFLOW (1 << 2) +#define BF_ST_UNDERFLOW (1 << 3) +#define BF_ST_INEXACT (1 << 4) +/* not used yet, indicate that a memory allocation error occured. NaN + is returned */ +#define BF_ST_MEM_ERROR (1 << 5) + +#define BF_RADIX_MAX 36 /* maximum radix for bf_atof() and bf_ftoa() */ + +static inline slimb_t bf_max(slimb_t a, slimb_t b) { + if (a > b) + return a; + else + return b; +} + +static inline slimb_t bf_min(slimb_t a, slimb_t b) { + if (a < b) + return a; + else + return b; +} + +void bf_context_init(bf_context_t *s, bf_realloc_func_t *realloc_func, + void *realloc_opaque); +void bf_context_end(bf_context_t *s); +/* free memory allocated for the bf cache data */ +void bf_clear_cache(bf_context_t *s); + +static inline void *bf_realloc(bf_context_t *s, void *ptr, size_t size) { + return realloc(ptr, size); +} + +void bf_init(bf_context_t *s, bf_t *r); + +static inline void bf_delete(bf_t *r) { + bf_context_t *s = r->ctx; + /* we accept to delete a zeroed bf_t structure */ + if (s) { + bf_realloc(s, r->tab, 0); + } +} + +static inline void bf_neg(bf_t *r) { r->sign ^= 1; } + +static inline int bf_is_finite(const bf_t *a) { return (a->expn < BF_EXP_INF); } + +static inline int bf_is_nan(const bf_t *a) { return (a->expn == BF_EXP_NAN); } + +static inline int bf_is_zero(const bf_t *a) { return (a->expn == BF_EXP_ZERO); } + +void bf_set_ui(bf_t *r, uint64_t a); +void bf_set_si(bf_t *r, int64_t a); +void bf_set_nan(bf_t *r); +void bf_set_zero(bf_t *r, int is_neg); +void bf_set_inf(bf_t *r, int is_neg); +void bf_set(bf_t *r, const bf_t *a); +void bf_move(bf_t *r, bf_t *a); +int bf_get_float64(const bf_t *a, double *pres, bf_rnd_t rnd_mode); +void bf_set_float64(bf_t *a, double d); + +int bf_cmpu(const bf_t *a, const bf_t *b); +int bf_cmp_full(const bf_t *a, const bf_t *b); +int bf_cmp_eq(const bf_t *a, const bf_t *b); +int bf_cmp_le(const bf_t *a, const bf_t *b); +int bf_cmp_lt(const bf_t *a, const bf_t *b); +int bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags); +int bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags); +int bf_add_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, + bf_flags_t flags); +int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags); +int bf_mul_ui(bf_t *r, const bf_t *a, uint64_t b1, limb_t prec, + bf_flags_t flags); +int bf_mul_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, + bf_flags_t flags); +int bf_mul_2exp(bf_t *r, slimb_t e, limb_t prec, bf_flags_t flags); +int bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags); +#define BF_DIVREM_EUCLIDIAN BF_RNDF +int bf_divrem(bf_t *q, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode); +int bf_fmod(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags); +int bf_remainder(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags); +int bf_remquo(slimb_t *pq, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags); +int bf_pow_ui(bf_t *r, const bf_t *a, limb_t b, limb_t prec, bf_flags_t flags); +int bf_pow_ui_ui(bf_t *r, limb_t a1, limb_t b, limb_t prec, bf_flags_t flags); +int bf_rint(bf_t *r, limb_t prec, bf_flags_t flags); +int bf_round(bf_t *r, limb_t prec, bf_flags_t flags); +int bf_sqrtrem(bf_t *r, bf_t *rem1, const bf_t *a); +int bf_sqrt(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +slimb_t bf_get_exp_min(const bf_t *a); +void bf_logic_or(bf_t *r, const bf_t *a, const bf_t *b); +void bf_logic_xor(bf_t *r, const bf_t *a, const bf_t *b); +void bf_logic_and(bf_t *r, const bf_t *a, const bf_t *b); + +/* additional flags for bf_atof */ +/* do not accept hex radix prefix (0x or 0X) if radix = 0 or radix = 16 */ +#define BF_ATOF_NO_HEX (1 << 16) +/* accept binary (0b or 0B) or octal (0o or 0O) radix prefix if radix = 0 */ +#define BF_ATOF_BIN_OCT (1 << 17) +/* Only accept integers (no decimal point, no exponent, no infinity nor NaN */ +#define BF_ATOF_INT_ONLY (1 << 18) +/* Do not accept radix prefix after sign */ +#define BF_ATOF_NO_PREFIX_AFTER_SIGN (1 << 19) +/* Do not parse NaN and parse case sensitive 'Infinity' */ +#define BF_ATOF_JS_QUIRKS (1 << 20) +/* Do not round integers to the indicated precision */ +#define BF_ATOF_INT_PREC_INF (1 << 21) +/* Support legacy octal syntax for well formed numbers */ +#define BF_ATOF_LEGACY_OCTAL (1 << 22) +/* accept _ between digits as a digit separator */ +#define BF_ATOF_UNDERSCORE_SEP (1 << 23) +/* if a 'n' suffix is present, force integer parsing (XXX: remove) */ +#define BF_ATOF_INT_N_SUFFIX (1 << 24) +/* if set return NaN if empty number string (instead of 0) */ +#define BF_ATOF_NAN_IF_EMPTY (1 << 25) +/* only accept decimal floating point if radix = 0 */ +#define BF_ATOF_ONLY_DEC_FLOAT (1 << 26) + +/* additional return flags */ +/* indicate that the parsed number is an integer (only set when the + flags BF_ATOF_INT_PREC_INF or BF_ATOF_INT_N_SUFFIX are used) */ +#define BF_ATOF_ST_INTEGER (1 << 5) +/* integer parsed as legacy octal */ +#define BF_ATOF_ST_LEGACY_OCTAL (1 << 6) + +int bf_atof(bf_t *a, const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags); +/* this version accepts prec = BF_PREC_INF and returns the radix + exponent */ +int bf_atof2(bf_t *r, slimb_t *pexponent, const char *str, const char **pnext, + int radix, limb_t prec, bf_flags_t flags); +int bf_mul_pow_radix(bf_t *r, const bf_t *T, limb_t radix, slimb_t expn, + limb_t prec, bf_flags_t flags); + +#define BF_FTOA_FORMAT_MASK (3 << 16) +/* fixed format: prec significant digits rounded with (flags & + BF_RND_MASK). Exponential notation is used if too many zeros are + needed. */ +#define BF_FTOA_FORMAT_FIXED (0 << 16) +/* fractional format: prec digits after the decimal point rounded with + (flags & BF_RND_MASK) */ +#define BF_FTOA_FORMAT_FRAC (1 << 16) +/* free format: use as many digits as necessary so that bf_atof() + return the same number when using precision 'prec', rounding to + nearest and the subnormal+exponent configuration of 'flags'. The + result is meaningful only if 'a' is already rounded to the wanted + precision. + + Infinite precision (BF_PREC_INF) is supported when the radix is a + power of two. */ +#define BF_FTOA_FORMAT_FREE (2 << 16) +/* same as BF_FTOA_FORMAT_FREE but uses the minimum number of digits + (takes more computation time). */ +#define BF_FTOA_FORMAT_FREE_MIN (3 << 16) + +/* force exponential notation for fixed or free format */ +#define BF_FTOA_FORCE_EXP (1 << 20) +/* add 0x prefix for base 16, 0o prefix for base 8 or 0b prefix for + base 2 if non zero value */ +#define BF_FTOA_ADD_PREFIX (1 << 21) +#define BF_FTOA_JS_QUIRKS (1 << 22) + +size_t bf_ftoa(char **pbuf, const bf_t *a, int radix, limb_t prec, + bf_flags_t flags); + +/* modulo 2^n instead of saturation. NaN and infinity return 0 */ +#define BF_GET_INT_MOD (1 << 0) +int bf_get_int32(int *pres, const bf_t *a, int flags); +int bf_get_int64(int64_t *pres, const bf_t *a, int flags); + +/* the following functions are exported for testing only. */ +void bf_print_str(const char *str, const bf_t *a); +void bf_resize(bf_t *r, limb_t len); +int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len); +void bf_recip(bf_t *r, const bf_t *a, limb_t prec); +void bf_rsqrt(bf_t *a, const bf_t *x, limb_t prec); +int bf_normalize_and_round(bf_t *r, limb_t prec1, bf_flags_t flags); +int bf_can_round(const bf_t *a, slimb_t prec, bf_rnd_t rnd_mode, slimb_t k); +slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv, + int is_ceil1); + +/* transcendental functions */ +int bf_const_log2(bf_t *T, limb_t prec, bf_flags_t flags); +int bf_const_pi(bf_t *T, limb_t prec, bf_flags_t flags); +int bf_exp(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_log(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +#define BF_POW_JS_QUICKS (1 << 16) +int bf_pow(bf_t *r, const bf_t *x, const bf_t *y, limb_t prec, + bf_flags_t flags); +int bf_cos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_sin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_tan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_atan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_atan2(bf_t *r, const bf_t *y, const bf_t *x, limb_t prec, + bf_flags_t flags); +int bf_asin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_LIBBF_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/libregexp-opcode.h b/NativeScript/napi/android/primjs/include/quickjs/include/libregexp-opcode.h new file mode 100644 index 000000000..480c23df5 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/libregexp-opcode.h @@ -0,0 +1,61 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifdef DEF + +DEF(invalid, 1) /* never used */ +DEF(char, 3) +DEF(char32, 5) +DEF(dot, 1) +DEF(any, 1) /* same as dot but match any character including line terminator */ +DEF(line_start, 1) +DEF(line_end, 1) +DEF(goto, 5) +DEF(split_goto_first, 5) +DEF(split_next_first, 5) +DEF(match, 1) +DEF(save_start, 2) /* save start position */ +DEF(save_end, 2) /* save end position, must come after saved_start */ +DEF(save_reset, 3) /* reset save positions */ +DEF(loop, 5) /* decrement the top the stack and goto if != 0 */ +DEF(push_i32, 5) /* push integer on the stack */ +DEF(drop, 1) +DEF(word_boundary, 1) +DEF(not_word_boundary, 1) +DEF(back_reference, 2) +DEF(backward_back_reference, 2) /* must come after back_reference */ +DEF(range, 3) /* variable length */ +DEF(range32, 3) /* variable length */ +DEF(lookahead, 5) +DEF(negative_lookahead, 5) +DEF(push_char_pos, 1) /* push the character position on the stack */ +DEF(bne_char_pos, 5) /* pop one stack element and jump if equal to the character + position */ +DEF(prev, 1) /* go to the previous char */ +DEF(simple_greedy_quant, 17) + +#endif /* DEF */ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/libregexp.h b/NativeScript/napi/android/primjs/include/quickjs/include/libregexp.h new file mode 100644 index 000000000..1065fcfb2 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/libregexp.h @@ -0,0 +1,97 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_LIBREGEXP_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_LIBREGEXP_H_ + +#include + +#include "base_export.h" +#include "libunicode.h" + +#define LRE_BOOL int /* for documentation purposes */ + +#define LRE_FLAG_GLOBAL (1 << 0) +#define LRE_FLAG_IGNORECASE (1 << 1) +#define LRE_FLAG_MULTILINE (1 << 2) +#define LRE_FLAG_DOTALL (1 << 3) +#define LRE_FLAG_UTF16 (1 << 4) +#define LRE_FLAG_STICKY (1 << 5) + +#define LRE_FLAG_NAMED_GROUPS \ + (1 << 7) /* named groups are present in the regexp */ + +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque); +int lre_get_capture_count(const uint8_t *bc_buf); +QJS_HIDE int lre_get_flags(const uint8_t *bc_buf); +int lre_exec(uint8_t **capture, const uint8_t *bc_buf, const uint8_t *cbuf, + int cindex, int clen, int cbuf_type, void *opaque); + +QJS_HIDE int lre_parse_escape(const uint8_t **pp, int allow_utf16); +QJS_HIDE LRE_BOOL lre_is_space(int c); + +/* must be provided by the user */ +QJS_HIDE LRE_BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size); +void *lre_realloc(__attribute__((unused)) void *opaque, void *ptr, size_t size, + __attribute__((unused)) int alloc_tag); + +/* LEPUS identifier test */ +extern uint32_t const lre_id_start_table_ascii[4]; +extern uint32_t const lre_id_continue_table_ascii[4]; + +static inline int lre_js_is_ident_first(int c) { + if ((uint32_t)c < 128) { + return (lre_id_start_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { +#ifdef CONFIG_ALL_UNICODE + return lre_is_id_start(c); +#else + return !lre_is_space(c); +#endif + } +} + +void lre_free(void *); + +static inline int lre_js_is_ident_next(int c) { + if ((uint32_t)c < 128) { + return (lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { + /* ZWNJ and ZWJ are accepted in identifiers */ +#ifdef CONFIG_ALL_UNICODE + return lre_is_id_continue(c) || c == 0x200C || c == 0x200D; +#else + return !lre_is_space(c) || c == 0x200C || c == 0x200D; +#endif + } +} + +#undef LRE_BOOL + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_LIBREGEXP_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/libunicode-table.h b/NativeScript/napi/android/primjs/include/quickjs/include/libunicode-table.h new file mode 100644 index 000000000..9b4a5b4b8 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/libunicode-table.h @@ -0,0 +1,3495 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +/* Compressed unicode tables */ +/* Automatically generated file - do not edit */ + +#include + +static const uint32_t case_conv_table1[359] = { + 0x00209a30, 0x00309a00, 0x005a8173, 0x00601730, 0x006c0730, 0x006f81b3, + 0x00701700, 0x007c0700, 0x007f8100, 0x00803040, 0x009801c3, 0x00988190, + 0x00990640, 0x009c9040, 0x00a481b4, 0x00a52e40, 0x00bc0130, 0x00bc8640, + 0x00bf8170, 0x00c00100, 0x00c08130, 0x00c10440, 0x00c30130, 0x00c38240, + 0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130, 0x00c80130, 0x00c88240, + 0x00c98130, 0x00ca0130, 0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240, + 0x00cd0100, 0x00ce0130, 0x00ce8130, 0x00cf0100, 0x00cf8130, 0x00d00640, + 0x00d30130, 0x00d38240, 0x00d48130, 0x00d60240, 0x00d70130, 0x00d78240, + 0x00d88230, 0x00d98440, 0x00db8130, 0x00dc0240, 0x00de0240, 0x00df8100, + 0x00e20350, 0x00e38350, 0x00e50350, 0x00e69040, 0x00ee8100, 0x00ef1240, + 0x00f801b4, 0x00f88350, 0x00fa0240, 0x00fb0130, 0x00fb8130, 0x00fc2840, + 0x01100130, 0x01111240, 0x011d0131, 0x011d8240, 0x011e8130, 0x011f0131, + 0x011f8201, 0x01208240, 0x01218130, 0x01220130, 0x01228130, 0x01230a40, + 0x01280101, 0x01288101, 0x01290101, 0x01298100, 0x012a0100, 0x012b0200, + 0x012c8100, 0x012d8100, 0x012e0101, 0x01300100, 0x01308101, 0x01318100, + 0x01328101, 0x01330101, 0x01340100, 0x01348100, 0x01350101, 0x01358101, + 0x01360101, 0x01378100, 0x01388101, 0x01390100, 0x013a8100, 0x013e8101, + 0x01400100, 0x01410101, 0x01418100, 0x01438101, 0x01440100, 0x01448100, + 0x01450200, 0x01460100, 0x01490100, 0x014e8101, 0x014f0101, 0x01a28173, + 0x01b80440, 0x01bb0240, 0x01bd8300, 0x01bf8130, 0x01c30130, 0x01c40330, + 0x01c60130, 0x01c70230, 0x01c801d0, 0x01c89130, 0x01d18930, 0x01d60100, + 0x01d68300, 0x01d801d3, 0x01d89100, 0x01e10173, 0x01e18900, 0x01e60100, + 0x01e68200, 0x01e78130, 0x01e80173, 0x01e88173, 0x01ea8173, 0x01eb0173, + 0x01eb8100, 0x01ec1840, 0x01f80173, 0x01f88173, 0x01f90100, 0x01f98100, + 0x01fa01a0, 0x01fa8173, 0x01fb8240, 0x01fc8130, 0x01fd0240, 0x01fe8330, + 0x02001030, 0x02082030, 0x02182000, 0x02281000, 0x02302240, 0x02453640, + 0x02600130, 0x02608e40, 0x02678100, 0x02686040, 0x0298a630, 0x02b0a600, + 0x02c381b5, 0x08502631, 0x08638131, 0x08668131, 0x08682b00, 0x087e8300, + 0x09d05011, 0x09f80610, 0x09fc0620, 0x0e400174, 0x0e408174, 0x0e410174, + 0x0e418174, 0x0e420174, 0x0e428174, 0x0e430174, 0x0e438180, 0x0e440180, + 0x0e482b30, 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, 0x0ec70101, 0x0f007e40, + 0x0f3f1840, 0x0f4b01b5, 0x0f4b81b6, 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, + 0x0f4d8180, 0x0f4f0130, 0x0f506040, 0x0f800800, 0x0f840830, 0x0f880600, + 0x0f8c0630, 0x0f900800, 0x0f940830, 0x0f980800, 0x0f9c0830, 0x0fa00600, + 0x0fa40630, 0x0fa801b0, 0x0fa88100, 0x0fa901d3, 0x0fa98100, 0x0faa01d3, + 0x0faa8100, 0x0fab01d3, 0x0fab8100, 0x0fac8130, 0x0fad8130, 0x0fae8130, + 0x0faf8130, 0x0fb00800, 0x0fb40830, 0x0fb80200, 0x0fb90400, 0x0fbb0200, + 0x0fbc0201, 0x0fbd0201, 0x0fbe0201, 0x0fc008b7, 0x0fc40867, 0x0fc808b8, + 0x0fcc0868, 0x0fd008b8, 0x0fd40868, 0x0fd80200, 0x0fd901b9, 0x0fd981b1, + 0x0fda01b9, 0x0fdb01b1, 0x0fdb81d7, 0x0fdc0230, 0x0fdd0230, 0x0fde0161, + 0x0fdf0173, 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, 0x0fe301b2, 0x0fe381d8, + 0x0fe40430, 0x0fe60162, 0x0fe80200, 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, + 0x0feb81d0, 0x0fec0230, 0x0fed0230, 0x0ff00201, 0x0ff101d3, 0x0ff181d3, + 0x0ff201ba, 0x0ff28101, 0x0ff301b0, 0x0ff381d3, 0x0ff40230, 0x0ff50230, + 0x0ff60131, 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, 0x0ffb01b2, 0x0ffb81d9, + 0x0ffc0230, 0x0ffd0230, 0x0ffe0162, 0x109301a0, 0x109501a0, 0x109581a0, + 0x10990131, 0x10a70101, 0x10b01031, 0x10b81001, 0x10c18240, 0x125b1a31, + 0x12681a01, 0x16002f31, 0x16182f01, 0x16300240, 0x16310130, 0x16318130, + 0x16320130, 0x16328100, 0x16330100, 0x16338640, 0x16368130, 0x16370130, + 0x16378130, 0x16380130, 0x16390240, 0x163a8240, 0x163f0230, 0x16406440, + 0x16758440, 0x16790240, 0x16802600, 0x16938100, 0x16968100, 0x53202e40, + 0x53401c40, 0x53910e40, 0x53993e40, 0x53bc8440, 0x53be8130, 0x53bf0a40, + 0x53c58240, 0x53c68130, 0x53c80440, 0x53ca0101, 0x53cb1440, 0x53d50130, + 0x53d58130, 0x53d60130, 0x53d68130, 0x53d70130, 0x53d80130, 0x53d88130, + 0x53d90130, 0x53d98131, 0x53da0c40, 0x53e10240, 0x53e20131, 0x53e28130, + 0x53e30130, 0x55a98101, 0x55b85020, 0x7d8001b2, 0x7d8081b2, 0x7d8101b2, + 0x7d8181da, 0x7d8201da, 0x7d8281b3, 0x7d8301b3, 0x7d8981bb, 0x7d8a01bb, + 0x7d8a81bb, 0x7d8b01bc, 0x7d8b81bb, 0x7f909a31, 0x7fa09a01, 0x82002831, + 0x82142801, 0x82582431, 0x826c2401, 0x86403331, 0x86603301, 0x8c502031, + 0x8c602001, 0xb7202031, 0xb7302001, 0xf4802231, 0xf4912201, +}; + +static const uint8_t case_conv_table2[359] = { + 0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04, 0x10, 0x00, 0x8f, 0x0b, + 0x00, 0x00, 0x11, 0x00, 0x08, 0x00, 0x53, 0x4a, 0x51, 0x00, 0x52, 0x00, + 0x53, 0x00, 0x3a, 0x54, 0x55, 0x00, 0x57, 0x59, 0x3f, 0x5d, 0x5c, 0x00, + 0x46, 0x61, 0x63, 0x42, 0x64, 0x00, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, + 0x6c, 0x00, 0x6e, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, + 0x93, 0x00, 0x00, 0x20, 0x35, 0x00, 0x27, 0x00, 0x21, 0x00, 0x24, 0x22, + 0x2a, 0x00, 0x13, 0x6b, 0x6d, 0x00, 0x26, 0x24, 0x27, 0x14, 0x16, 0x18, + 0x1b, 0x1c, 0x3e, 0x1e, 0x3f, 0x1f, 0x39, 0x3d, 0x22, 0x21, 0x41, 0x1e, + 0x40, 0x25, 0x25, 0x26, 0x28, 0x20, 0x2a, 0x49, 0x2c, 0x43, 0x2e, 0x4b, + 0x30, 0x4c, 0x32, 0x44, 0x42, 0x99, 0x00, 0x00, 0x95, 0x8f, 0x7d, 0x7e, + 0x83, 0x84, 0x12, 0x80, 0x82, 0x76, 0x77, 0x12, 0x7b, 0xa3, 0x7c, 0x78, + 0x79, 0x8a, 0x92, 0x98, 0xa6, 0xa0, 0x85, 0x00, 0x9a, 0xa1, 0x93, 0x75, + 0x33, 0x95, 0x00, 0x8e, 0x00, 0x74, 0x99, 0x98, 0x97, 0x96, 0x00, 0x00, + 0x9e, 0x00, 0x9c, 0x00, 0xa1, 0xa0, 0x15, 0x2e, 0x2f, 0x30, 0xb4, 0xb5, + 0x4c, 0xaa, 0xa9, 0x12, 0x14, 0x1e, 0x21, 0x22, 0x22, 0x2a, 0x34, 0x35, + 0xa6, 0xa7, 0x36, 0x1f, 0x4a, 0x00, 0x00, 0x97, 0x01, 0x5a, 0xda, 0x1d, + 0x36, 0x05, 0x00, 0xc4, 0xc3, 0xc6, 0xc5, 0xc8, 0xc7, 0xca, 0xc9, 0xcc, + 0xcb, 0xc4, 0xd5, 0x45, 0xd6, 0x42, 0xd7, 0x46, 0xd8, 0xce, 0xd0, 0xd2, + 0xd4, 0xda, 0xd9, 0xee, 0xf6, 0xfe, 0x0e, 0x07, 0x0f, 0x80, 0x9f, 0x00, + 0x21, 0x80, 0xa3, 0xed, 0x00, 0xc0, 0x40, 0xc6, 0x60, 0xe7, 0xdb, 0xe6, + 0x99, 0xc0, 0x00, 0x00, 0x06, 0x60, 0xdc, 0x29, 0xfd, 0x15, 0x12, 0x06, + 0x16, 0xf8, 0xdd, 0x06, 0x15, 0x12, 0x84, 0x08, 0xc6, 0x16, 0xff, 0xdf, + 0x03, 0xc0, 0x40, 0x00, 0x46, 0x60, 0xde, 0xe0, 0x6d, 0x37, 0x38, 0x39, + 0x15, 0x14, 0x17, 0x16, 0x00, 0x1a, 0x19, 0x1c, 0x1b, 0x00, 0x5f, 0xb7, + 0x65, 0x44, 0x47, 0x00, 0x4f, 0x62, 0x4e, 0x50, 0x00, 0x00, 0x48, 0x00, + 0x00, 0x00, 0xa3, 0xa4, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x00, + 0x00, 0x5a, 0x00, 0x48, 0x00, 0x5b, 0x56, 0x58, 0x60, 0x5e, 0x70, 0x69, + 0x6f, 0x4b, 0x00, 0x00, 0x3b, 0x67, 0xb8, 0x45, 0xa8, 0x8a, 0x8b, 0x8c, + 0xab, 0xac, 0x58, 0x58, 0xaf, 0x94, 0xb0, 0x6f, 0xb2, 0x5a, 0x59, 0x5c, + 0x5b, 0x5e, 0x5d, 0x60, 0x5f, 0x62, 0x61, 0x64, 0x63, 0x66, 0x65, +}; + +static const uint16_t case_conv_ext[58] = { + 0x0399, 0x0308, 0x0301, 0x03a5, 0x0313, 0x0300, 0x0342, 0x0391, 0x0397, + 0x03a9, 0x0046, 0x0049, 0x004c, 0x0053, 0x0069, 0x0307, 0x02bc, 0x004e, + 0x004a, 0x030c, 0x0535, 0x0552, 0x0048, 0x0331, 0x0054, 0x0057, 0x030a, + 0x0059, 0x0041, 0x02be, 0x1f08, 0x1f80, 0x1f28, 0x1f90, 0x1f68, 0x1fa0, + 0x1fba, 0x0386, 0x1fb3, 0x1fca, 0x0389, 0x1fc3, 0x03a1, 0x1ffa, 0x038f, + 0x1ff3, 0x0544, 0x0546, 0x053b, 0x054e, 0x053d, 0x03b8, 0x0462, 0xa64a, + 0x1e60, 0x03c9, 0x006b, 0x00e5, +}; + +static const uint8_t unicode_prop_Cased1_table[172] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3, 0x80, 0x8c, 0x80, 0x8d, + 0x81, 0x8d, 0x02, 0x80, 0xe1, 0x80, 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, + 0x11, 0x00, 0x01, 0x04, 0x08, 0x01, 0x08, 0x30, 0x08, 0x01, 0x15, 0x20, + 0x00, 0x39, 0x99, 0x31, 0x9d, 0x84, 0x40, 0x94, 0x80, 0xd6, 0x82, 0xa6, + 0x80, 0x41, 0x62, 0x80, 0xa6, 0x80, 0x57, 0x76, 0xf8, 0x02, 0x80, 0x8f, + 0x80, 0xb0, 0x40, 0xdb, 0x08, 0x80, 0x41, 0xd0, 0x80, 0x8c, 0x80, 0x8f, + 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28, 0x10, 0x11, 0x02, 0x01, + 0x18, 0x0b, 0x24, 0x4b, 0x26, 0x01, 0x01, 0x86, 0xe5, 0x80, 0x60, 0x79, + 0xb6, 0x81, 0x40, 0x91, 0x81, 0xbd, 0x88, 0x94, 0x05, 0x80, 0x98, 0x80, + 0xc7, 0x82, 0x43, 0x34, 0xa2, 0x06, 0x80, 0x8b, 0x61, 0x28, 0x97, 0xd4, + 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, 0x41, 0x53, + 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x59, 0x63, 0x99, + 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Cased1_index[18] = { + 0xb9, 0x02, 0xe0, 0xa0, 0x1e, 0x40, 0x9e, 0xa6, 0x40, + 0xba, 0xd4, 0x01, 0x89, 0xd7, 0x01, 0x8a, 0xf1, 0x01, +}; + +static const uint8_t unicode_prop_Case_Ignorable_table[678] = { + 0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, 0xc6, 0x03, 0x00, 0x03, + 0x01, 0x81, 0x41, 0xf6, 0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40, + 0xfa, 0x86, 0x40, 0xce, 0x80, 0xb6, 0xac, 0x00, 0x01, 0x01, 0x00, 0xab, + 0x80, 0x8a, 0x85, 0x89, 0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, 0x80, + 0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0xb9, + 0x8a, 0x18, 0x08, 0x97, 0x97, 0xaa, 0x82, 0xf6, 0xaf, 0xb6, 0x00, 0x03, + 0x3b, 0x02, 0x86, 0x89, 0x81, 0x8c, 0x80, 0x8e, 0x80, 0xb9, 0x03, 0x1f, + 0x80, 0x93, 0x81, 0x99, 0x01, 0x81, 0xb8, 0x03, 0x0b, 0x09, 0x12, 0x80, + 0x9d, 0x0a, 0x80, 0x8a, 0x81, 0xb8, 0x03, 0x20, 0x0b, 0x80, 0x93, 0x81, + 0x95, 0x28, 0x80, 0xb9, 0x01, 0x00, 0x1f, 0x07, 0x80, 0x8a, 0x81, 0x9d, + 0x80, 0xbc, 0x80, 0x8b, 0x80, 0xb1, 0x02, 0x80, 0xb8, 0x14, 0x10, 0x1e, + 0x81, 0x8a, 0x81, 0x9c, 0x80, 0xb9, 0x01, 0x05, 0x04, 0x81, 0x93, 0x81, + 0x9b, 0x81, 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81, 0xe5, 0x06, 0x10, 0x80, + 0xd9, 0x01, 0x86, 0x8a, 0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x85, 0xc9, + 0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04, 0x01, 0x84, 0x8a, 0x80, + 0xa3, 0x88, 0x80, 0xe5, 0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f, + 0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd, 0x80, 0x42, 0x5f, 0x82, + 0x43, 0xb1, 0x82, 0x9c, 0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37, + 0x01, 0x8a, 0x10, 0x20, 0xac, 0x83, 0xb3, 0x80, 0xc0, 0x81, 0xa1, 0x80, + 0xf5, 0x13, 0x81, 0x88, 0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00, + 0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07, 0x8e, 0xc0, 0x83, 0xaf, + 0x00, 0x20, 0x04, 0x80, 0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, 0x08, 0x82, + 0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, 0x39, 0x81, 0xbf, 0x85, 0xd1, 0x10, + 0x8c, 0x06, 0x18, 0x28, 0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, 0xde, 0x04, + 0x41, 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, 0x82, 0x8c, 0x81, + 0x8b, 0x27, 0x81, 0x89, 0x01, 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, 0x8c, + 0x80, 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, 0x81, 0xf0, 0x82, 0xfc, 0x80, + 0x8e, 0x80, 0xdf, 0x9f, 0xae, 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, 0x24, + 0x80, 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, 0x44, 0xe1, 0x85, + 0x41, 0x0d, 0x80, 0xe1, 0x18, 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, 0x8d, + 0xa1, 0xcd, 0x80, 0x96, 0x82, 0xec, 0x0f, 0x02, 0x03, 0x80, 0x98, 0x81, + 0x40, 0x9c, 0x81, 0x99, 0x91, 0x8c, 0x80, 0xa5, 0x87, 0x98, 0x8a, 0xad, + 0x82, 0xaf, 0x01, 0x19, 0x81, 0x90, 0x80, 0x94, 0x81, 0xc1, 0x29, 0x09, + 0x81, 0x8b, 0x07, 0x80, 0xa2, 0x80, 0x8a, 0x80, 0xb2, 0x00, 0x11, 0x0c, + 0x08, 0x80, 0x9a, 0x80, 0x8d, 0x0c, 0x08, 0x80, 0xe3, 0x84, 0x40, 0x84, + 0x01, 0x03, 0x80, 0x60, 0x4f, 0x2f, 0x80, 0x40, 0x92, 0x8f, 0x42, 0x3d, + 0x8f, 0x10, 0x8b, 0x8f, 0xa1, 0x01, 0x80, 0x40, 0xa8, 0x06, 0x05, 0x80, + 0x8a, 0x80, 0xa2, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, 0x80, 0x94, + 0x82, 0x42, 0x00, 0x80, 0x40, 0xe1, 0x80, 0x40, 0x94, 0x84, 0x46, 0x85, + 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x83, + 0x42, 0x1d, 0x8a, 0x40, 0xaf, 0x80, 0xb5, 0x8e, 0xb7, 0x82, 0xb0, 0x19, + 0x09, 0x80, 0x8e, 0x80, 0xb1, 0x82, 0xa3, 0x20, 0x87, 0xbd, 0x80, 0x8b, + 0x81, 0xb3, 0x88, 0x89, 0x83, 0xe1, 0x11, 0x00, 0x0d, 0x80, 0x40, 0x9f, + 0x02, 0x87, 0x94, 0x81, 0xb8, 0x0a, 0x80, 0xa4, 0x32, 0x84, 0x40, 0xc2, + 0x39, 0x10, 0x80, 0x96, 0x80, 0xd3, 0x28, 0x03, 0x08, 0x81, 0x40, 0xed, + 0x1d, 0x08, 0x81, 0x9a, 0x81, 0xd4, 0x39, 0x00, 0x81, 0xe9, 0x00, 0x01, + 0x28, 0x80, 0xe4, 0x11, 0x18, 0x84, 0x41, 0x02, 0x88, 0x01, 0x41, 0x98, + 0x19, 0x0b, 0x80, 0x9f, 0x89, 0xa7, 0x29, 0x1f, 0x80, 0x88, 0x29, 0x82, + 0xad, 0x8c, 0x01, 0x41, 0x95, 0x30, 0x28, 0x80, 0xd1, 0x95, 0x0e, 0x01, + 0x01, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x80, 0xc7, 0x0a, 0x00, 0x80, 0x41, + 0x5a, 0x81, 0x55, 0x3a, 0x88, 0x60, 0x36, 0xb6, 0x84, 0xba, 0x86, 0x88, + 0x83, 0x44, 0x0a, 0x80, 0xbe, 0x90, 0xbf, 0x08, 0x80, 0x60, 0x4c, 0xb8, + 0x08, 0x83, 0x54, 0xc2, 0x82, 0x88, 0x8f, 0x0e, 0x9d, 0x83, 0x40, 0x93, + 0x82, 0x47, 0xba, 0xb6, 0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, + 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0x41, 0x04, 0x8d, 0x41, 0xad, + 0x83, 0x45, 0xdf, 0x86, 0xec, 0x87, 0x4a, 0xae, 0x84, 0x6c, 0x0c, 0x00, + 0x80, 0x9d, 0xdf, 0xff, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_Case_Ignorable_index[66] = { + 0xc0, 0x05, 0x00, 0x2e, 0x08, 0x20, 0x52, 0x0a, 0x00, 0x05, 0x0c, + 0x00, 0x4f, 0x0e, 0x20, 0x75, 0x10, 0x20, 0x44, 0x18, 0x00, 0x43, + 0x1b, 0x00, 0x00, 0x1e, 0x00, 0x7e, 0x2c, 0x00, 0x7e, 0xa6, 0x40, + 0x83, 0xa9, 0x20, 0xf7, 0xaa, 0x00, 0x41, 0xff, 0x20, 0x28, 0x0d, + 0x01, 0x3f, 0x12, 0x41, 0xde, 0x15, 0x21, 0x5c, 0x1a, 0x01, 0xf5, + 0x6a, 0x21, 0x37, 0xda, 0x01, 0x02, 0x00, 0x2e, 0xf0, 0x01, 0x0e, +}; + +static const uint8_t unicode_prop_ID_Start_table[1024] = { + 0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03, 0x04, 0x96, 0x80, 0x9e, + 0x80, 0x41, 0xc9, 0x83, 0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20, + 0x09, 0x18, 0x05, 0x00, 0x10, 0x00, 0x93, 0x80, 0xd2, 0x80, 0x40, 0x8a, + 0x87, 0x40, 0xa5, 0x80, 0xa5, 0x08, 0x85, 0xa8, 0xc6, 0x9a, 0x1b, 0xac, + 0xaa, 0xa2, 0x08, 0xe2, 0x00, 0x8e, 0x0e, 0x81, 0x89, 0x11, 0x80, 0x8f, + 0x00, 0x9d, 0x9c, 0xd8, 0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95, + 0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a, 0xb4, 0x94, 0x07, 0xc5, + 0xb5, 0x10, 0x91, 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, 0x95, 0x06, + 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, 0x08, 0x82, 0x8d, 0x81, 0x89, 0x07, + 0x2b, 0x09, 0x95, 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, 0x92, 0x82, + 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, 0x01, 0x04, 0x10, 0x91, 0x80, 0x8e, + 0x81, 0x96, 0x80, 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, 0x10, 0x9d, + 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, 0x2a, 0x10, 0x1a, 0x08, 0x00, 0x0a, + 0x0a, 0x12, 0x8b, 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, 0x8f, 0x10, + 0x99, 0x14, 0x81, 0x9d, 0x03, 0x38, 0x10, 0x96, 0x80, 0x89, 0x04, 0x10, + 0x9f, 0x00, 0x81, 0x8e, 0x81, 0x91, 0x38, 0x10, 0xa8, 0x08, 0x8f, 0x04, + 0x17, 0x82, 0x97, 0x2c, 0x91, 0x82, 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, + 0xaf, 0x01, 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, 0x97, 0x00, 0x80, 0x89, + 0x01, 0x88, 0x01, 0x20, 0x80, 0x94, 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, + 0x9a, 0x84, 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, 0x1a, 0x02, 0x0e, 0x13, + 0x8c, 0x8b, 0x80, 0x90, 0xa5, 0x00, 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, + 0x03, 0x0e, 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, 0xa0, 0x03, 0x0e, 0x00, + 0x03, 0x81, 0x8e, 0x80, 0xb8, 0x03, 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, + 0x0d, 0x82, 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, 0x84, 0xca, 0x82, 0x8a, + 0x86, 0x8c, 0x03, 0x8d, 0x91, 0x8d, 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, + 0xa2, 0x03, 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, 0xc5, 0x89, 0x9e, + 0xb0, 0x9d, 0x0c, 0x8a, 0xab, 0x83, 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, + 0x80, 0xdc, 0xae, 0x90, 0x86, 0xb6, 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x99, + 0xa3, 0xa8, 0x82, 0x89, 0xa3, 0x81, 0x88, 0x86, 0xaa, 0x0a, 0xa8, 0x18, + 0x28, 0x0a, 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, 0x81, 0xa5, 0x0d, + 0x0f, 0x00, 0x00, 0x00, 0x80, 0x9e, 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, + 0x13, 0x0d, 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, 0x80, 0x8f, 0x8c, + 0xe4, 0x03, 0x01, 0x89, 0x00, 0x0d, 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, + 0x24, 0x18, 0x90, 0xa8, 0x4a, 0x76, 0xae, 0x80, 0xae, 0x80, 0x40, 0x84, + 0x2b, 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7, 0x30, 0x8f, 0x96, 0x88, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98, + 0x88, 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9, 0x03, 0x84, 0xaa, 0x80, + 0xdd, 0x90, 0x9a, 0xb4, 0x8f, 0x41, 0xff, 0x59, 0xb5, 0xc9, 0x60, 0x51, + 0xef, 0x8f, 0x44, 0x8c, 0xc2, 0xad, 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, + 0x81, 0x93, 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, 0x88, 0x81, 0xe6, 0x81, + 0xb4, 0x0c, 0xaf, 0x8a, 0x02, 0x03, 0x80, 0x96, 0x9c, 0xb3, 0x8d, 0xb1, + 0xbd, 0x2a, 0x00, 0x81, 0x8a, 0x9b, 0x89, 0x96, 0x98, 0x9c, 0x86, 0xae, + 0x9b, 0x80, 0x8f, 0x20, 0x89, 0x89, 0x20, 0xa8, 0x96, 0x10, 0x87, 0x93, + 0x96, 0x10, 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, 0x00, 0x97, 0x11, 0x8a, + 0x32, 0x8b, 0x29, 0x29, 0x85, 0x88, 0x30, 0x30, 0xaa, 0x80, 0x8b, 0x87, + 0xf2, 0x9c, 0x60, 0x2b, 0xa3, 0x8b, 0x96, 0x83, 0xb0, 0x60, 0x21, 0x03, + 0x41, 0x6d, 0x81, 0xe9, 0xa5, 0x86, 0x8b, 0x24, 0x00, 0x89, 0x80, 0x8c, + 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, + 0xb5, 0xa7, 0x8b, 0xf3, 0x20, 0x40, 0x86, 0xa3, 0x99, 0x85, 0x99, 0x8a, + 0xd8, 0x15, 0x0d, 0x0d, 0x0a, 0xa2, 0x8b, 0x80, 0x99, 0x80, 0x92, 0x01, + 0x80, 0x8e, 0x81, 0x8d, 0xa1, 0xfa, 0xc4, 0xb4, 0x41, 0x0a, 0x9c, 0x82, + 0xb0, 0xae, 0x9f, 0x8c, 0x9d, 0x84, 0xa5, 0x89, 0x9d, 0x81, 0xa3, 0x1f, + 0x04, 0xa9, 0x40, 0x9d, 0x91, 0xa3, 0x83, 0xa3, 0x83, 0xa7, 0x87, 0xb3, + 0x40, 0x9b, 0x41, 0x36, 0x88, 0x95, 0x89, 0x87, 0x40, 0x97, 0x29, 0x00, + 0xab, 0x01, 0x10, 0x81, 0x96, 0x89, 0x96, 0x88, 0x9e, 0xc0, 0x92, 0x01, + 0x89, 0x95, 0x89, 0x99, 0xc5, 0xb7, 0x29, 0xbf, 0x80, 0x8e, 0x18, 0x10, + 0x9c, 0xa9, 0x9c, 0x82, 0x9c, 0xa2, 0x38, 0x9b, 0x9a, 0xb5, 0x89, 0x95, + 0x89, 0x92, 0x8c, 0x91, 0xed, 0xc8, 0xb6, 0xb2, 0x8c, 0xb2, 0x8c, 0xa3, + 0x41, 0xdb, 0x9c, 0x89, 0x07, 0x95, 0x40, 0x99, 0x96, 0x8b, 0xb4, 0xca, + 0xac, 0x9f, 0x98, 0x99, 0xa3, 0x9c, 0x80, 0x8a, 0xa2, 0x10, 0x8b, 0xaf, + 0x8d, 0x83, 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, 0xd3, 0x30, 0x00, + 0x18, 0x8e, 0x80, 0x89, 0x86, 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, + 0x04, 0x10, 0x91, 0x80, 0x8b, 0x84, 0x40, 0x9d, 0xb4, 0x91, 0x83, 0x93, + 0x80, 0x9f, 0xaf, 0x93, 0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, 0xa3, + 0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, 0xc6, 0x9a, 0x40, 0xe4, 0xab, + 0xf3, 0xbf, 0x9e, 0x80, 0x40, 0x9f, 0x39, 0xa6, 0x8f, 0x00, 0x80, 0x9b, + 0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, 0xad, 0x92, 0x80, 0xa1, 0xb8, + 0x41, 0x06, 0x88, 0x80, 0xa4, 0x90, 0x80, 0xb0, 0x9d, 0xef, 0x30, 0x08, + 0xa5, 0x94, 0x80, 0x98, 0x28, 0x08, 0x9f, 0x8d, 0x80, 0x41, 0x46, 0x92, + 0x41, 0x0c, 0x43, 0x99, 0xe5, 0xee, 0x90, 0x40, 0xc3, 0x4a, 0xbb, 0x44, + 0x2e, 0x4f, 0xd0, 0x42, 0x46, 0x60, 0x21, 0xb8, 0x42, 0x38, 0x86, 0x9e, + 0xf0, 0x9d, 0x91, 0xaf, 0x8f, 0x83, 0x9e, 0x94, 0x84, 0x92, 0x42, 0xaf, + 0xbf, 0xff, 0xca, 0x20, 0xc1, 0x8c, 0xbf, 0x08, 0x80, 0x9b, 0x57, 0xf7, + 0x87, 0x42, 0xf2, 0x60, 0x25, 0x0c, 0x41, 0x1e, 0xb0, 0x82, 0x90, 0x1f, + 0x41, 0x8b, 0x49, 0x03, 0xea, 0x84, 0x8c, 0x82, 0x88, 0x86, 0x89, 0x57, + 0x65, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, + 0x80, 0xc0, 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, + 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x49, + 0x33, 0xac, 0x89, 0x86, 0x8f, 0x80, 0x41, 0x70, 0xab, 0x45, 0x13, 0x40, + 0xc4, 0xba, 0xc3, 0x30, 0x44, 0xb3, 0x18, 0x9a, 0x01, 0x00, 0x08, 0x80, + 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, 0x80, + 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, 0x51, 0x43, 0x60, 0xa6, 0xd6, + 0xa8, 0x50, 0x34, 0x8a, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, + 0x4c, 0x1e, 0x42, 0x1d, +}; + +static const uint8_t unicode_prop_ID_Start_index[96] = { + 0xf6, 0x03, 0x20, 0xa6, 0x07, 0x00, 0xb1, 0x09, 0x00, 0xba, 0x0a, 0x00, + 0xd1, 0x0b, 0x20, 0x62, 0x0d, 0x40, 0x01, 0x0f, 0x20, 0x5e, 0x12, 0x00, + 0xf9, 0x16, 0x00, 0x17, 0x1a, 0x20, 0xc0, 0x1d, 0x20, 0x9d, 0x20, 0x00, + 0x68, 0x2d, 0x00, 0x00, 0x32, 0x20, 0xc0, 0xa7, 0x20, 0x29, 0xaa, 0x00, + 0xa4, 0xd7, 0x20, 0xc8, 0xfd, 0x20, 0x75, 0x01, 0x01, 0x37, 0x07, 0x01, + 0x36, 0x0a, 0x21, 0xf7, 0x0f, 0x21, 0xa9, 0x12, 0x01, 0x30, 0x16, 0x21, + 0x8a, 0x1a, 0x01, 0x9a, 0x23, 0x01, 0x80, 0x6e, 0x21, 0x89, 0xbc, 0x21, + 0xc1, 0xd6, 0x01, 0xc5, 0xe8, 0x21, 0x73, 0xee, 0x01, 0x1e, 0xfa, 0x02, +}; + +static const uint8_t unicode_prop_ID_Continue1_table[607] = { + 0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47, 0xef, 0x96, 0x80, 0x40, + 0xfa, 0x84, 0x41, 0x08, 0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, + 0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89, 0x96, 0x80, 0x9d, 0x9a, + 0xda, 0x8a, 0x8e, 0x89, 0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02, + 0x04, 0xaa, 0x82, 0xf6, 0x8e, 0x80, 0xa0, 0xb5, 0x10, 0x91, 0x06, 0x89, + 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, 0x80, 0x89, + 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, 0x00, 0x23, 0x09, 0x12, 0x80, 0x93, + 0x8b, 0x10, 0x8a, 0x82, 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, 0x09, 0x89, + 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x17, 0x81, 0x89, 0x09, 0x89, + 0x91, 0x80, 0xba, 0x22, 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, 0x8f, 0x84, + 0xb8, 0x30, 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, + 0x30, 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x8f, 0x83, 0xb6, 0x08, 0x30, + 0x10, 0x83, 0x88, 0x80, 0x89, 0x09, 0x89, 0x91, 0x81, 0xc5, 0x03, 0x28, + 0x00, 0x3d, 0x89, 0x09, 0xbc, 0x01, 0x86, 0x8b, 0x38, 0x89, 0xd6, 0x01, + 0x88, 0x8a, 0x29, 0x89, 0xbd, 0x0d, 0x89, 0x8a, 0x00, 0x00, 0x03, 0x81, + 0xb0, 0x93, 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe3, 0x93, 0x80, + 0x89, 0x8b, 0x1b, 0x10, 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x80, 0x8e, 0x42, + 0xbe, 0x82, 0x88, 0x88, 0x43, 0x9f, 0x82, 0x9c, 0x82, 0x9c, 0x81, 0x9d, + 0x81, 0xbf, 0x9f, 0x88, 0x01, 0x89, 0xa0, 0x11, 0x89, 0x40, 0x8e, 0x80, + 0xf5, 0x8b, 0x83, 0x8b, 0x89, 0x89, 0xff, 0x8a, 0xbb, 0x84, 0xb8, 0x89, + 0x80, 0x9c, 0x81, 0x8a, 0x85, 0x89, 0x95, 0x8d, 0xc1, 0x84, 0xae, 0x90, + 0x8a, 0x89, 0x90, 0x88, 0x8b, 0x82, 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d, + 0xaf, 0x93, 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10, 0x94, 0x18, 0x28, 0x0a, + 0x40, 0xc5, 0xb9, 0x04, 0x42, 0x3e, 0x81, 0x92, 0x80, 0xfa, 0x8c, 0x18, + 0x82, 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, 0x80, 0xdf, 0x9f, 0x42, 0x29, + 0x85, 0xe8, 0x81, 0x60, 0x75, 0x84, 0x89, 0xc4, 0x03, 0x89, 0x9f, 0x81, + 0xcf, 0x81, 0x41, 0x0f, 0x02, 0x03, 0x80, 0x96, 0x84, 0xd7, 0x81, 0xb1, + 0x91, 0x89, 0x89, 0x85, 0x91, 0x8c, 0x8a, 0x9b, 0x87, 0x98, 0x8c, 0xab, + 0x83, 0xae, 0x8d, 0x8e, 0x89, 0x8a, 0x80, 0x89, 0x89, 0xae, 0x8d, 0x8b, + 0x07, 0x09, 0x89, 0xa0, 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, 0x80, 0xa8, + 0x24, 0x81, 0x40, 0xeb, 0x38, 0x09, 0x89, 0x60, 0x4f, 0x23, 0x80, 0x42, + 0xe0, 0x8f, 0x8f, 0x8f, 0x11, 0x97, 0x82, 0x40, 0xbf, 0x89, 0xa4, 0x80, + 0x42, 0xbc, 0x80, 0x40, 0xe1, 0x80, 0x40, 0x94, 0x84, 0x41, 0x24, 0x89, + 0x45, 0x56, 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, + 0x3c, 0x1f, 0x89, 0x42, 0x0b, 0x8a, 0x40, 0xae, 0x82, 0xb4, 0x8e, 0x9e, + 0x89, 0x8e, 0x83, 0xac, 0x8a, 0xb4, 0x89, 0x2a, 0xa3, 0x8d, 0x80, 0x89, + 0x21, 0xab, 0x80, 0x8b, 0x82, 0xaf, 0x8d, 0x3b, 0x82, 0x89, 0xd1, 0x8b, + 0x28, 0x40, 0x9f, 0x8b, 0x84, 0x89, 0x2b, 0xb6, 0x08, 0x31, 0x09, 0x82, + 0x88, 0x80, 0x89, 0x09, 0x32, 0x84, 0x40, 0xbf, 0x91, 0x88, 0x89, 0x18, + 0xd0, 0x93, 0x8b, 0x89, 0x40, 0xd4, 0x31, 0x88, 0x9a, 0x81, 0xd1, 0x90, + 0x8e, 0x89, 0xd0, 0x8c, 0x87, 0x89, 0xd2, 0x8e, 0x83, 0x89, 0x40, 0xf1, + 0x8e, 0x40, 0xa4, 0x89, 0x40, 0xe6, 0x31, 0x32, 0x80, 0x9b, 0x89, 0xa7, + 0x30, 0x1f, 0x80, 0x88, 0x8a, 0xad, 0x8f, 0x41, 0x94, 0x38, 0x87, 0x8f, + 0x89, 0xb7, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x07, 0x89, + 0xaf, 0x20, 0x08, 0x27, 0x89, 0x41, 0x48, 0x83, 0x60, 0x4b, 0x68, 0x89, + 0x40, 0x85, 0x84, 0xba, 0x86, 0x98, 0x89, 0x43, 0xf4, 0x00, 0xb6, 0x33, + 0x60, 0x4d, 0x09, 0x81, 0x54, 0xc5, 0x22, 0x2f, 0x39, 0x86, 0x9d, 0x83, + 0x40, 0x93, 0x82, 0x45, 0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, 0xb1, 0x38, + 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, + 0x41, 0x04, 0x86, 0x88, 0x89, 0x41, 0xa1, 0x8d, 0x45, 0xd5, 0x86, 0xec, + 0x34, 0x89, 0x6c, 0x17, 0xa5, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_ID_Continue1_index[57] = { + 0xfa, 0x06, 0x00, 0x84, 0x09, 0x00, 0xf0, 0x0a, 0x00, 0x70, 0x0c, 0x00, + 0xf4, 0x0d, 0x00, 0x4a, 0x10, 0x20, 0x1a, 0x18, 0x20, 0x74, 0x1b, 0x00, + 0xe2, 0x20, 0x00, 0x28, 0xa8, 0x20, 0x7e, 0xaa, 0x20, 0x40, 0xff, 0x00, + 0x03, 0x10, 0x21, 0xeb, 0x12, 0x01, 0x41, 0x16, 0x01, 0x40, 0x1c, 0x61, + 0x37, 0x6b, 0x21, 0x76, 0xda, 0x01, 0xf0, 0x01, 0x0e, +}; + +#ifdef CONFIG_ALL_UNICODE + +static const uint8_t unicode_cc_table[831] = { + 0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00, 0xe8, 0x00, 0xd8, 0x04, + 0xdc, 0x01, 0xca, 0x03, 0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03, + 0xdc, 0xc7, 0x00, 0xf0, 0xc0, 0x02, 0xdc, 0xc2, 0x01, 0xdc, 0x80, 0xc2, + 0x03, 0xdc, 0xc0, 0x00, 0xe8, 0x01, 0xdc, 0xc0, 0x41, 0xe9, 0x00, 0xea, + 0x41, 0xe9, 0x00, 0xea, 0x00, 0xe9, 0xcc, 0xb0, 0xe2, 0xc4, 0xb0, 0xd8, + 0x00, 0xdc, 0xc3, 0x00, 0xdc, 0xc2, 0x00, 0xde, 0x00, 0xdc, 0xc5, 0x05, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xde, 0x00, 0xe4, 0xc0, 0x49, 0x0a, + 0x43, 0x13, 0x80, 0x00, 0x17, 0x80, 0x41, 0x18, 0x80, 0xc0, 0x00, 0xdc, + 0x80, 0x00, 0x12, 0xb0, 0x17, 0xc7, 0x42, 0x1e, 0xaf, 0x47, 0x1b, 0xc1, + 0x01, 0xdc, 0xc4, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0x8f, 0x00, 0x23, 0xb0, + 0x34, 0xc6, 0x81, 0xc3, 0x00, 0xdc, 0xc0, 0x81, 0xc1, 0x80, 0x00, 0xdc, + 0xc1, 0x00, 0xdc, 0xa2, 0x00, 0x24, 0x9d, 0xc0, 0x00, 0xdc, 0xc1, 0x00, + 0xdc, 0xc1, 0x02, 0xdc, 0xc0, 0x01, 0xdc, 0xc0, 0x00, 0xdc, 0xc2, 0x00, + 0xdc, 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc, 0x97, 0xc3, 0x80, 0xc8, + 0x80, 0xc2, 0x80, 0xc4, 0xaa, 0x02, 0xdc, 0xb0, 0x46, 0x00, 0xdc, 0xcd, + 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc2, 0x02, 0xdc, + 0x42, 0x1b, 0xc2, 0x00, 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, + 0x07, 0x8f, 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, 0x36, 0x00, + 0x07, 0x8f, 0x00, 0x09, 0xaf, 0xc0, 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, + 0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3d, 0x00, 0x07, + 0x8f, 0x00, 0x09, 0xb0, 0x4e, 0x00, 0x09, 0xb0, 0x4e, 0x00, 0x09, 0x86, + 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, + 0x3c, 0x01, 0x09, 0x8f, 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, 0x3c, + 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, 0xb0, 0x3b, 0x01, 0x76, 0x00, + 0x09, 0x8c, 0x03, 0x7a, 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, 0x80, + 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, 0x41, 0x81, 0x80, 0x00, 0x84, + 0x84, 0x03, 0x82, 0x81, 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, 0xc1, + 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, 0x07, 0x80, 0x01, 0x09, 0xb0, + 0x21, 0x00, 0xdc, 0xb2, 0x9e, 0xc2, 0xb3, 0x83, 0x00, 0x09, 0x9e, 0x00, + 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, 0xb0, 0x9a, 0x00, 0xe4, 0xb0, + 0x5e, 0x00, 0xde, 0xc0, 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, 0xb0, + 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, 0xdc, 0xaf, 0xc4, 0x05, 0xdc, + 0xc1, 0x00, 0xdc, 0xb0, 0x45, 0x00, 0x07, 0x8e, 0x00, 0x09, 0xa5, 0xc0, + 0x00, 0xdc, 0xc6, 0xb0, 0x05, 0x01, 0x09, 0xb0, 0x09, 0x00, 0x07, 0x8a, + 0x01, 0x09, 0xb0, 0x12, 0x00, 0x07, 0xb0, 0x67, 0xc2, 0x41, 0x00, 0x04, + 0xdc, 0xc1, 0x03, 0xdc, 0xc0, 0x41, 0x00, 0x05, 0x01, 0x83, 0x00, 0xdc, + 0x85, 0xc0, 0x82, 0xc1, 0xb0, 0x95, 0xc1, 0x00, 0xdc, 0xc6, 0x00, 0xdc, + 0xc1, 0x00, 0xea, 0x00, 0xd6, 0x00, 0xdc, 0x00, 0xca, 0xe4, 0x00, 0xe8, + 0x01, 0xe4, 0x00, 0xdc, 0x80, 0xc0, 0x00, 0xe9, 0x00, 0xdc, 0xc0, 0x00, + 0xdc, 0xb2, 0x9f, 0xc1, 0x01, 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, 0xc0, + 0x82, 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, 0x01, 0x01, 0x03, 0xdc, 0xc0, + 0xb8, 0x03, 0xcd, 0xc2, 0xb0, 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, 0xb1, + 0xf9, 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, 0x00, 0xde, 0x01, 0xe0, 0xb0, + 0x38, 0x01, 0x08, 0xb8, 0x6d, 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, 0xb0, + 0x1f, 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xb0, 0x8c, 0x00, 0x09, 0x9a, 0xd1, + 0xb0, 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, 0x2e, 0x00, 0x07, 0x8b, + 0x00, 0x09, 0xb0, 0xbe, 0xc0, 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, + 0xc1, 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, 0xc5, 0x00, 0x09, 0xb8, + 0x46, 0xff, 0x00, 0x1a, 0xb2, 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, + 0x00, 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, 0xc4, 0xb6, 0x61, 0x00, + 0xdc, 0x80, 0xc0, 0xa7, 0xc0, 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, + 0xb0, 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, 0xb1, 0xed, 0x01, 0xdc, + 0xc2, 0x00, 0xdc, 0xc0, 0x03, 0xdc, 0xb0, 0xc4, 0x00, 0x09, 0xb0, 0x07, + 0x00, 0x09, 0xb0, 0x08, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x14, 0xc2, 0xaf, + 0x01, 0x09, 0xb0, 0x0d, 0x00, 0x07, 0xb0, 0x1b, 0x00, 0x09, 0x88, 0x00, + 0x07, 0xb0, 0x39, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x81, 0x00, 0x07, 0x00, + 0x09, 0xb0, 0x1f, 0x01, 0x07, 0x8f, 0x00, 0x09, 0x97, 0xc6, 0x82, 0xc4, + 0xb0, 0x9c, 0x00, 0x09, 0x82, 0x00, 0x07, 0x96, 0xc0, 0xb0, 0x32, 0x00, + 0x09, 0x00, 0x07, 0xb0, 0xca, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, 0x00, + 0x09, 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x42, 0x00, 0x09, 0xb0, + 0xdc, 0x00, 0x09, 0x00, 0x07, 0xb1, 0x74, 0x00, 0x09, 0xb0, 0x22, 0x00, + 0x09, 0x91, 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x74, 0x00, 0x09, + 0xb0, 0xd1, 0x00, 0x07, 0x80, 0x01, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb8, + 0x45, 0x27, 0x04, 0x01, 0xb0, 0x0a, 0xc6, 0xb8, 0x49, 0x36, 0x00, 0x01, + 0xb8, 0x0c, 0x95, 0x01, 0xd8, 0x02, 0x01, 0x82, 0x00, 0xe2, 0x04, 0xd8, + 0x87, 0x07, 0xdc, 0x81, 0xc4, 0x01, 0xdc, 0x9d, 0xc3, 0xb0, 0x63, 0xc2, + 0xb8, 0x05, 0x8a, 0xc6, 0x80, 0xd0, 0x81, 0xc6, 0x80, 0xc1, 0x80, 0xc4, + 0xb0, 0xd4, 0xc6, 0xb1, 0x84, 0xc3, 0xb5, 0xaf, 0x06, 0xdc, 0xb0, 0x3c, + 0xc5, 0x00, 0x07, +}; + +static const uint8_t unicode_cc_index[78] = { + 0x4d, 0x03, 0x00, 0x97, 0x05, 0x20, 0xc6, 0x05, 0x00, 0xe7, 0x06, 0x00, + 0x45, 0x07, 0x00, 0xe2, 0x08, 0x00, 0x53, 0x09, 0x00, 0xcd, 0x0b, 0x20, + 0x38, 0x0e, 0x00, 0x73, 0x0f, 0x20, 0x5d, 0x13, 0x20, 0x60, 0x1a, 0x20, + 0xe6, 0x1b, 0x20, 0xfa, 0x1c, 0x00, 0x00, 0x1e, 0x20, 0x80, 0x2d, 0x00, + 0x06, 0xa8, 0x00, 0xbe, 0xaa, 0x00, 0x76, 0x03, 0x01, 0x4d, 0x0f, 0x01, + 0xcb, 0x11, 0x21, 0x5e, 0x14, 0x01, 0x3b, 0x18, 0x21, 0xf0, 0x6a, 0x41, + 0xaa, 0xd1, 0x01, 0x4b, 0xe9, 0x01, +}; + +static const uint32_t unicode_decomp_table1[687] = { + 0x00280081, 0x002a0097, 0x002a8081, 0x002bc097, 0x002c8115, 0x002d0097, + 0x002d4081, 0x002e0097, 0x002e4115, 0x002f0199, 0x00302016, 0x00400842, + 0x00448a42, 0x004a0442, 0x004c0096, 0x004c8117, 0x004d0242, 0x004e4342, + 0x004fc12f, 0x0050c342, 0x005240bf, 0x00530342, 0x00550942, 0x005a0842, + 0x005e0096, 0x005e4342, 0x005fc081, 0x00680142, 0x006bc142, 0x00710185, + 0x0071c317, 0x00734844, 0x00778344, 0x00798342, 0x007b02be, 0x007c4197, + 0x007d0142, 0x007e0444, 0x00800e42, 0x00878142, 0x00898744, 0x00ac0483, + 0x00b60317, 0x00b80283, 0x00d00214, 0x00d10096, 0x00dd0080, 0x00de8097, + 0x00df8080, 0x00e10097, 0x00e1413e, 0x00e1c080, 0x00e204be, 0x00ea83ae, + 0x00f282ae, 0x00f401ad, 0x00f4c12e, 0x00f54103, 0x00fc0303, 0x00fe4081, + 0x0100023e, 0x0101c0be, 0x010301be, 0x010640be, 0x010e40be, 0x0114023e, + 0x0115c0be, 0x011701be, 0x011d8144, 0x01304144, 0x01340244, 0x01358144, + 0x01368344, 0x01388344, 0x013a8644, 0x013e0144, 0x0161c085, 0x018882ae, + 0x019d422f, 0x01b00184, 0x01b4c084, 0x024a4084, 0x024c4084, 0x024d0084, + 0x0256042e, 0x0272c12e, 0x02770120, 0x0277c084, 0x028cc084, 0x028d8084, + 0x029641ae, 0x02978084, 0x02d20084, 0x02d2c12e, 0x02d70120, 0x02e50084, + 0x02f281ae, 0x03120084, 0x03300084, 0x0331c122, 0x0332812e, 0x035281ae, + 0x03768084, 0x037701ae, 0x038cc085, 0x03acc085, 0x03b7012f, 0x03c30081, + 0x03d0c084, 0x03d34084, 0x03d48084, 0x03d5c084, 0x03d70084, 0x03da4084, + 0x03dcc084, 0x03dd412e, 0x03ddc085, 0x03de0084, 0x03de4085, 0x03e04084, + 0x03e4c084, 0x03e74084, 0x03e88084, 0x03e9c084, 0x03eb0084, 0x03ee4084, + 0x04098084, 0x043f0081, 0x06c18484, 0x06c48084, 0x06cec184, 0x06d00120, + 0x06d0c084, 0x074b0383, 0x074cc41f, 0x074f1783, 0x075e0081, 0x0766d283, + 0x07801d44, 0x078e8942, 0x07931844, 0x079f0d42, 0x07a58216, 0x07a68085, + 0x07a6c0be, 0x07a80d44, 0x07aea044, 0x07c00122, 0x07c08344, 0x07c20122, + 0x07c28344, 0x07c40122, 0x07c48244, 0x07c60122, 0x07c68244, 0x07c8113e, + 0x07d08244, 0x07d20122, 0x07d28244, 0x07d40122, 0x07d48344, 0x07d64c3e, + 0x07dc4080, 0x07dc80be, 0x07dcc080, 0x07dd00be, 0x07dd4080, 0x07dd80be, + 0x07ddc080, 0x07de00be, 0x07de4080, 0x07de80be, 0x07dec080, 0x07df00be, + 0x07df4080, 0x07e00820, 0x07e40820, 0x07e80820, 0x07ec05be, 0x07eec080, + 0x07ef00be, 0x07ef4097, 0x07ef8080, 0x07efc117, 0x07f0443e, 0x07f24080, + 0x07f280be, 0x07f2c080, 0x07f303be, 0x07f4c080, 0x07f582ae, 0x07f6c080, + 0x07f7433e, 0x07f8c080, 0x07f903ae, 0x07fac080, 0x07fb013e, 0x07fb8102, + 0x07fc83be, 0x07fe4080, 0x07fe80be, 0x07fec080, 0x07ff00be, 0x07ff4080, + 0x07ff8097, 0x0800011e, 0x08008495, 0x08044081, 0x0805c097, 0x08090081, + 0x08094097, 0x08098099, 0x080bc081, 0x080cc085, 0x080d00b1, 0x080d8085, + 0x080dc0b1, 0x080f0197, 0x0811c197, 0x0815c0b3, 0x0817c081, 0x081c0595, + 0x081ec081, 0x081f0215, 0x0820051f, 0x08228583, 0x08254415, 0x082a0097, + 0x08400119, 0x08408081, 0x0840c0bf, 0x08414119, 0x0841c081, 0x084240bf, + 0x0842852d, 0x08454081, 0x08458097, 0x08464295, 0x08480097, 0x08484099, + 0x08488097, 0x08490081, 0x08498080, 0x084a0081, 0x084a8102, 0x084b0495, + 0x084d421f, 0x084e4081, 0x084ec099, 0x084f0283, 0x08514295, 0x08540119, + 0x0854809b, 0x0854c619, 0x0857c097, 0x08580081, 0x08584097, 0x08588099, + 0x0858c097, 0x08590081, 0x08594097, 0x08598099, 0x0859c09b, 0x085a0097, + 0x085a4081, 0x085a8097, 0x085ac099, 0x085b0295, 0x085c4097, 0x085c8099, + 0x085cc097, 0x085d0081, 0x085d4097, 0x085d8099, 0x085dc09b, 0x085e0097, + 0x085e4081, 0x085e8097, 0x085ec099, 0x085f0215, 0x08624099, 0x0866813e, + 0x086b80be, 0x087341be, 0x088100be, 0x088240be, 0x088300be, 0x088901be, + 0x088b0085, 0x088b40b1, 0x088bc085, 0x088c00b1, 0x089040be, 0x089100be, + 0x0891c1be, 0x089801be, 0x089b42be, 0x089d0144, 0x089e0144, 0x08a00144, + 0x08a10144, 0x08a20144, 0x08ab023e, 0x08b80244, 0x08ba8220, 0x08ca411e, + 0x0918049f, 0x091a4523, 0x091cc097, 0x091d04a5, 0x091f452b, 0x0921c09b, + 0x092204a1, 0x09244525, 0x0926c099, 0x09270d25, 0x092d8d1f, 0x09340d1f, + 0x093a8081, 0x0a8300b3, 0x0a9d0099, 0x0a9d4097, 0x0a9d8099, 0x0ab700be, + 0x0b1f0115, 0x0b5bc081, 0x0ba7c081, 0x0bbcc081, 0x0bc004ad, 0x0bc244ad, + 0x0bc484ad, 0x0bc6f383, 0x0be0852d, 0x0be31d03, 0x0bf1882d, 0x0c000081, + 0x0c0d8283, 0x0c130b84, 0x0c194284, 0x0c1c0122, 0x0c1cc122, 0x0c1d8122, + 0x0c1e4122, 0x0c1f0122, 0x0c250084, 0x0c26c123, 0x0c278084, 0x0c27c085, + 0x0c2b0b84, 0x0c314284, 0x0c340122, 0x0c34c122, 0x0c358122, 0x0c364122, + 0x0c370122, 0x0c3d0084, 0x0c3dc220, 0x0c3f8084, 0x0c3fc085, 0x0c4c4a2d, + 0x0c51451f, 0x0c53ca9f, 0x0c5915ad, 0x0c648703, 0x0c800741, 0x0c838089, + 0x0c83c129, 0x0c8441a9, 0x0c850089, 0x0c854129, 0x0c85c2a9, 0x0c870089, + 0x0c87408f, 0x0c87808d, 0x0c881241, 0x0c910203, 0x0c940099, 0x0c9444a3, + 0x0c968323, 0x0c98072d, 0x0c9b84af, 0x0c9dc2a1, 0x0c9f00b5, 0x0c9f40b3, + 0x0c9f8085, 0x0ca01883, 0x0cac4223, 0x0cad4523, 0x0cafc097, 0x0cb004a1, + 0x0cb241a5, 0x0cb30097, 0x0cb34099, 0x0cb38097, 0x0cb3c099, 0x0cb417ad, + 0x0cbfc085, 0x0cc001b3, 0x0cc0c0b1, 0x0cc100b3, 0x0cc14131, 0x0cc1c0b5, + 0x0cc200b3, 0x0cc241b1, 0x0cc30133, 0x0cc38131, 0x0cc40085, 0x0cc440b1, + 0x0cc48133, 0x0cc50085, 0x0cc540b5, 0x0cc580b7, 0x0cc5c0b5, 0x0cc600b1, + 0x0cc64135, 0x0cc6c0b3, 0x0cc701b1, 0x0cc7c0b3, 0x0cc800b5, 0x0cc840b3, + 0x0cc881b1, 0x0cc9422f, 0x0cca4131, 0x0ccac0b5, 0x0ccb00b1, 0x0ccb40b3, + 0x0ccb80b5, 0x0ccbc0b1, 0x0ccc012f, 0x0ccc80b5, 0x0cccc0b3, 0x0ccd00b5, + 0x0ccd40b1, 0x0ccd80b5, 0x0ccdc085, 0x0cce02b1, 0x0ccf40b3, 0x0ccf80b1, + 0x0ccfc085, 0x0cd001b1, 0x0cd0c0b3, 0x0cd101b1, 0x0cd1c0b5, 0x0cd200b3, + 0x0cd24085, 0x0cd280b5, 0x0cd2c085, 0x0cd30133, 0x0cd381b1, 0x0cd440b3, + 0x0cd48085, 0x0cd4c0b1, 0x0cd500b3, 0x0cd54085, 0x0cd580b5, 0x0cd5c0b1, + 0x0cd60521, 0x0cd88525, 0x0cdb02a5, 0x0cdc4099, 0x0cdc8117, 0x0cdd0099, + 0x0cdd4197, 0x0cde0127, 0x0cde8285, 0x0cdfc089, 0x0ce0043f, 0x0ce20099, + 0x0ce2409b, 0x0ce283bf, 0x0ce44219, 0x0ce54205, 0x0ce6433f, 0x0ce7c131, + 0x0ce84085, 0x0ce881b1, 0x0ce94085, 0x0ce98107, 0x0cea0089, 0x0cea4097, + 0x0cea8219, 0x0ceb809d, 0x0cebc08d, 0x0cec083f, 0x0cf00105, 0x0cf0809b, + 0x0cf0c197, 0x0cf1809b, 0x0cf1c099, 0x0cf20517, 0x0cf48099, 0x0cf4c117, + 0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099, 0x0cf68217, 0x0cf78119, + 0x0cf804a1, 0x0cfa4525, 0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103, + 0x29dc0081, 0x29fe0103, 0x2ad70203, 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, + 0x3e8aa102, 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, 0x3ec00197, 0x3ec0c119, + 0x3ec1413f, 0x3ec4c2af, 0x3ec74184, 0x3ec804ad, 0x3eca4081, 0x3eca8304, + 0x3ecc03a0, 0x3ece02a0, 0x3ecf8084, 0x3ed00120, 0x3ed0c120, 0x3ed184ae, + 0x3ed3c085, 0x3ed4312d, 0x3ef4cbad, 0x3efa892f, 0x3eff022d, 0x3f002f2f, + 0x3f1782a5, 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, 0x3f3c81a5, 0x3f3d64af, + 0x3f542031, 0x3f649b31, 0x3f7c0131, 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, + 0x3f7ec0bb, 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, 0x3f8cc315, 0x3f8e462d, + 0x3f91cc03, 0x3f97c695, 0x3f9c01af, 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, + 0x3fbd442f, 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, 0x3fe80081, 0x3fe84f1f, + 0x3ff0831f, 0x3ff2831f, 0x3ff4831f, 0x3ff6819f, 0x3ff80783, 0x44268192, + 0x442ac092, 0x444b8112, 0x44d2c112, 0x452ec212, 0x456e8112, 0x74578392, + 0x746ec312, 0x75000d1f, 0x75068d1f, 0x750d0d1f, 0x7513839f, 0x7515891f, + 0x751a0d1f, 0x75208d1f, 0x75271015, 0x752f439f, 0x7531459f, 0x75340d1f, + 0x753a8d1f, 0x75410395, 0x7543441f, 0x7545839f, 0x75478d1f, 0x754e0795, + 0x7552839f, 0x75548d1f, 0x755b0d1f, 0x75618d1f, 0x75680d1f, 0x756e8d1f, + 0x75750d1f, 0x757b8d1f, 0x75820d1f, 0x75888d1f, 0x758f0d1f, 0x75958d1f, + 0x759c0d1f, 0x75a28d1f, 0x75a90103, 0x75aa089f, 0x75ae4081, 0x75ae839f, + 0x75b04081, 0x75b08c9f, 0x75b6c081, 0x75b7032d, 0x75b8889f, 0x75bcc081, + 0x75bd039f, 0x75bec081, 0x75bf0c9f, 0x75c54081, 0x75c5832d, 0x75c7089f, + 0x75cb4081, 0x75cb839f, 0x75cd4081, 0x75cd8c9f, 0x75d3c081, 0x75d4032d, + 0x75d5889f, 0x75d9c081, 0x75da039f, 0x75dbc081, 0x75dc0c9f, 0x75e24081, + 0x75e2832d, 0x75e4089f, 0x75e84081, 0x75e8839f, 0x75ea4081, 0x75ea8c9f, + 0x75f0c081, 0x75f1042d, 0x75f3851f, 0x75f6051f, 0x75f8851f, 0x75fb051f, + 0x75fd851f, 0x7b80022d, 0x7b814dad, 0x7b884203, 0x7b89c081, 0x7b8a452d, + 0x7b8d0403, 0x7b908081, 0x7b91dc03, 0x7ba0052d, 0x7ba2c8ad, 0x7ba84483, + 0x7baac8ad, 0x7c400097, 0x7c404521, 0x7c440d25, 0x7c4a8087, 0x7c4ac115, + 0x7c4b4117, 0x7c4c0d1f, 0x7c528217, 0x7c538099, 0x7c53c097, 0x7c5a8197, + 0x7c640097, 0x7c80012f, 0x7c808081, 0x7c841603, 0x7c9004c1, 0x7c940103, + 0xbe0001ac, 0xbe00d110, 0xbe0947ac, 0xbe0d3910, 0xbe29872c, 0xbe2d022c, + 0xbe2e3790, 0xbe49ff90, 0xbe69bc10, +}; + +static const uint16_t unicode_decomp_table2[687] = { + 0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008, 0x000a, + 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3, 0x00c7, 0x00cb, + 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8, 0x0108, 0x010a, 0x0073, + 0x0110, 0x0112, 0x0114, 0x0120, 0x012c, 0x0144, 0x014d, 0x0153, 0x0162, + 0x0168, 0x016a, 0x0176, 0x0192, 0x0194, 0x01a9, 0x01bb, 0x01c7, 0x01d1, + 0x01d5, 0x02b9, 0x01d7, 0x003b, 0x01d9, 0x01db, 0x00b7, 0x01e1, 0x01fc, + 0x020c, 0x0218, 0x021d, 0x0223, 0x0227, 0x03a3, 0x0233, 0x023f, 0x0242, + 0x024b, 0x024e, 0x0251, 0x025d, 0x0260, 0x0269, 0x026c, 0x026f, 0x0275, + 0x0278, 0x0281, 0x028a, 0x029c, 0x029f, 0x02a3, 0x02af, 0x02b9, 0x02c5, + 0x02c9, 0x02cd, 0x02d1, 0x02d5, 0x02e7, 0x02ed, 0x02f1, 0x02f5, 0x02f9, + 0x02fd, 0x0305, 0x0309, 0x030d, 0x0313, 0x0317, 0x031b, 0x0323, 0x0327, + 0x032b, 0x032f, 0x0335, 0x033d, 0x0341, 0x0349, 0x034d, 0x0351, 0x0f0b, + 0x0357, 0x035b, 0x035f, 0x0363, 0x0367, 0x036b, 0x036f, 0x0373, 0x0379, + 0x037d, 0x0381, 0x0385, 0x0389, 0x038d, 0x0391, 0x0395, 0x0399, 0x039d, + 0x03a1, 0x10dc, 0x03a5, 0x03c9, 0x03cd, 0x03d9, 0x03dd, 0x03e1, 0x03ef, + 0x03f1, 0x043d, 0x044f, 0x0499, 0x04f0, 0x0502, 0x054a, 0x0564, 0x056c, + 0x0570, 0x0573, 0x059a, 0x05fa, 0x05fe, 0x0607, 0x060b, 0x0614, 0x0618, + 0x061e, 0x0622, 0x0628, 0x068e, 0x0694, 0x0698, 0x069e, 0x06a2, 0x06ab, + 0x03ac, 0x06f3, 0x03ad, 0x06f6, 0x03ae, 0x06f9, 0x03af, 0x06fc, 0x03cc, + 0x06ff, 0x03cd, 0x0702, 0x03ce, 0x0705, 0x0709, 0x070d, 0x0711, 0x0386, + 0x0732, 0x0735, 0x03b9, 0x0737, 0x073b, 0x0388, 0x0753, 0x0389, 0x0756, + 0x0390, 0x076b, 0x038a, 0x0777, 0x03b0, 0x0789, 0x038e, 0x0799, 0x079f, + 0x07a3, 0x038c, 0x07b8, 0x038f, 0x07bb, 0x00b4, 0x07be, 0x07c0, 0x07c2, + 0x2010, 0x07cb, 0x002e, 0x07cd, 0x07cf, 0x0020, 0x07d2, 0x07d6, 0x07db, + 0x07df, 0x07e4, 0x07ea, 0x07f0, 0x0020, 0x07f6, 0x2212, 0x0801, 0x0805, + 0x0807, 0x081d, 0x0825, 0x0827, 0x0043, 0x082d, 0x0830, 0x0190, 0x0836, + 0x0839, 0x004e, 0x0845, 0x0847, 0x084c, 0x084e, 0x0851, 0x005a, 0x03a9, + 0x005a, 0x0853, 0x0857, 0x0860, 0x0069, 0x0862, 0x0865, 0x086f, 0x0874, + 0x087a, 0x087e, 0x08a2, 0x0049, 0x08a4, 0x08a6, 0x08a9, 0x0056, 0x08ab, + 0x08ad, 0x08b0, 0x08b4, 0x0058, 0x08b6, 0x08b8, 0x08bb, 0x08c0, 0x08c2, + 0x08c5, 0x0076, 0x08c7, 0x08c9, 0x08cc, 0x08d0, 0x0078, 0x08d2, 0x08d4, + 0x08d7, 0x08db, 0x08de, 0x08e4, 0x08e7, 0x08f0, 0x08f3, 0x08f6, 0x08f9, + 0x0902, 0x0906, 0x090b, 0x090f, 0x0914, 0x0917, 0x091a, 0x0923, 0x092c, + 0x093b, 0x093e, 0x0941, 0x0944, 0x0947, 0x094a, 0x0956, 0x095c, 0x0960, + 0x0962, 0x0964, 0x0968, 0x096a, 0x0970, 0x0978, 0x097c, 0x0980, 0x0986, + 0x0989, 0x098f, 0x0991, 0x0030, 0x0993, 0x0999, 0x099c, 0x099e, 0x09a1, + 0x09a4, 0x2d61, 0x6bcd, 0x9f9f, 0x09a6, 0x09b1, 0x09bc, 0x09c7, 0x0a95, + 0x0aa1, 0x0b15, 0x0020, 0x0b27, 0x0b31, 0x0b8d, 0x0ba1, 0x0ba5, 0x0ba9, + 0x0bad, 0x0bb1, 0x0bb5, 0x0bb9, 0x0bbd, 0x0bc1, 0x0bc5, 0x0c21, 0x0c35, + 0x0c39, 0x0c3d, 0x0c41, 0x0c45, 0x0c49, 0x0c4d, 0x0c51, 0x0c55, 0x0c59, + 0x0c6f, 0x0c71, 0x0c73, 0x0ca0, 0x0cbc, 0x0cdc, 0x0ce4, 0x0cec, 0x0cf4, + 0x0cfc, 0x0d04, 0x0d0c, 0x0d14, 0x0d22, 0x0d2e, 0x0d7a, 0x0d82, 0x0d85, + 0x0d89, 0x0d8d, 0x0d9d, 0x0db1, 0x0db5, 0x0dbc, 0x0dc2, 0x0dc6, 0x0e28, + 0x0e2c, 0x0e30, 0x0e32, 0x0e36, 0x0e3c, 0x0e3e, 0x0e41, 0x0e43, 0x0e46, + 0x0e77, 0x0e7b, 0x0e89, 0x0e8e, 0x0e94, 0x0e9c, 0x0ea3, 0x0ea9, 0x0eb4, + 0x0ebe, 0x0ec6, 0x0eca, 0x0ecf, 0x0ed9, 0x0edd, 0x0ee4, 0x0eec, 0x0ef3, + 0x0ef8, 0x0f04, 0x0f0a, 0x0f15, 0x0f1b, 0x0f22, 0x0f28, 0x0f33, 0x0f3d, + 0x0f45, 0x0f4c, 0x0f51, 0x0f57, 0x0f5e, 0x0f63, 0x0f69, 0x0f70, 0x0f76, + 0x0f7d, 0x0f82, 0x0f89, 0x0f8d, 0x0f9e, 0x0fa4, 0x0fa9, 0x0fad, 0x0fb8, + 0x0fbe, 0x0fc9, 0x0fd0, 0x0fd6, 0x0fda, 0x0fe1, 0x0fe5, 0x0fef, 0x0ffa, + 0x1000, 0x1004, 0x1009, 0x100f, 0x1013, 0x101a, 0x101f, 0x1023, 0x1029, + 0x102f, 0x1032, 0x1036, 0x1039, 0x103f, 0x1045, 0x1059, 0x1061, 0x1079, + 0x107c, 0x1080, 0x1095, 0x10a1, 0x10b1, 0x10c3, 0x10cb, 0x10cf, 0x10da, + 0x10de, 0x10ea, 0x10f2, 0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, + 0x114d, 0x1153, 0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, + 0x1181, 0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab, + 0xa76f, 0x11af, 0x11b3, 0x11bb, 0x120d, 0x130b, 0x1409, 0x148d, 0x1492, + 0x1550, 0x1569, 0x156f, 0x1575, 0x157b, 0x1587, 0x1593, 0x002b, 0x159e, + 0x15b6, 0x15ba, 0x15be, 0x15c2, 0x15c6, 0x15ca, 0x15de, 0x15e2, 0x1646, + 0x165f, 0x1685, 0x168b, 0x1749, 0x174f, 0x1754, 0x1774, 0x1874, 0x187a, + 0x190e, 0x19d0, 0x1a74, 0x1a7c, 0x1a9a, 0x1a9f, 0x1ab3, 0x1abd, 0x1ac3, + 0x1ad7, 0x1adc, 0x1ae2, 0x1af0, 0x1b20, 0x1b2d, 0x1b35, 0x1b39, 0x1b4f, + 0x1bc6, 0x1bd8, 0x1bda, 0x1bdc, 0x3164, 0x1c1d, 0x1c1f, 0x1c21, 0x1c23, + 0x1c25, 0x1c27, 0x1c45, 0x1c53, 0x1c58, 0x1c61, 0x1c6a, 0x1c7c, 0x1c85, + 0x1ca5, 0x1cc0, 0x1cc2, 0x1cc4, 0x1cc6, 0x1cc8, 0x1cca, 0x1ccc, 0x1cce, + 0x1cee, 0x1cf0, 0x1cf2, 0x1cf4, 0x1cf6, 0x1cfd, 0x1cff, 0x1d01, 0x1d03, + 0x1d12, 0x1d14, 0x1d16, 0x1d18, 0x1d1a, 0x1d1c, 0x1d1e, 0x1d20, 0x1d22, + 0x1d24, 0x1d26, 0x1d28, 0x1d2a, 0x1d2c, 0x1d2e, 0x1d32, 0x03f4, 0x1d34, + 0x2207, 0x1d36, 0x2202, 0x1d38, 0x1d40, 0x03f4, 0x1d42, 0x2207, 0x1d44, + 0x2202, 0x1d46, 0x1d4e, 0x03f4, 0x1d50, 0x2207, 0x1d52, 0x2202, 0x1d54, + 0x1d5c, 0x03f4, 0x1d5e, 0x2207, 0x1d60, 0x2202, 0x1d62, 0x1d6a, 0x03f4, + 0x1d6c, 0x2207, 0x1d6e, 0x2202, 0x1d70, 0x1d7a, 0x1d7c, 0x1d7e, 0x1d80, + 0x1d82, 0x1d84, 0x1d8a, 0x1da7, 0x062d, 0x1daf, 0x1dbb, 0x062c, 0x1dcb, + 0x1e3b, 0x1e47, 0x1e5a, 0x1e6c, 0x1e7f, 0x1e81, 0x1e85, 0x1e8b, 0x1e91, + 0x1e93, 0x1e97, 0x1e99, 0x1ea1, 0x1ea4, 0x1ea6, 0x1eac, 0x1eae, 0x30b5, + 0x1eb4, 0x1f0c, 0x1f22, 0x1f26, 0x1f2b, 0x1f78, 0x1f89, 0x208a, 0x209a, + 0x20a0, 0x219a, 0x22b8, +}; + +static const uint8_t unicode_decomp_data[9158] = { + 0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81, 0x20, 0xa7, 0x31, 0x6f, + 0x31, 0xd0, 0x34, 0x31, 0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41, + 0x81, 0x41, 0x82, 0x41, 0x83, 0x41, 0x88, 0x41, 0x8a, 0x00, 0x00, 0x43, + 0xa7, 0x45, 0x80, 0x45, 0x81, 0x45, 0x82, 0x45, 0x88, 0x49, 0x80, 0x49, + 0x81, 0x49, 0x82, 0x49, 0x88, 0x00, 0x00, 0x4e, 0x83, 0x4f, 0x80, 0x4f, + 0x81, 0x4f, 0x82, 0x4f, 0x83, 0x4f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x55, + 0x80, 0x55, 0x81, 0x55, 0x82, 0x55, 0x88, 0x59, 0x81, 0x00, 0x00, 0x00, + 0x00, 0x61, 0x80, 0x61, 0x81, 0x61, 0x82, 0x61, 0x83, 0x61, 0x88, 0x61, + 0x8a, 0x00, 0x00, 0x63, 0xa7, 0x65, 0x80, 0x65, 0x81, 0x65, 0x82, 0x65, + 0x88, 0x69, 0x80, 0x69, 0x81, 0x69, 0x82, 0x69, 0x88, 0x00, 0x00, 0x6e, + 0x83, 0x6f, 0x80, 0x6f, 0x81, 0x6f, 0x82, 0x6f, 0x83, 0x6f, 0x88, 0x00, + 0x00, 0x00, 0x00, 0x75, 0x80, 0x75, 0x81, 0x75, 0x82, 0x75, 0x88, 0x79, + 0x81, 0x00, 0x00, 0x79, 0x88, 0x41, 0x84, 0x41, 0x86, 0x41, 0xa8, 0x43, + 0x81, 0x43, 0x82, 0x43, 0x87, 0x43, 0x8c, 0x44, 0x8c, 0x45, 0x84, 0x45, + 0x86, 0x45, 0x87, 0x45, 0xa8, 0x45, 0x8c, 0x47, 0x82, 0x47, 0x86, 0x47, + 0x87, 0x47, 0xa7, 0x48, 0x82, 0x49, 0x83, 0x49, 0x84, 0x49, 0x86, 0x49, + 0xa8, 0x49, 0x87, 0x49, 0x4a, 0x69, 0x6a, 0x4a, 0x82, 0x4b, 0xa7, 0x4c, + 0x81, 0x4c, 0xa7, 0x4c, 0x8c, 0x4c, 0x00, 0x00, 0x6b, 0x20, 0x6b, 0x4e, + 0x81, 0x4e, 0xa7, 0x4e, 0x8c, 0xbc, 0x02, 0x6e, 0x4f, 0x84, 0x4f, 0x86, + 0x4f, 0x8b, 0x52, 0x81, 0x52, 0xa7, 0x52, 0x8c, 0x53, 0x81, 0x53, 0x82, + 0x53, 0xa7, 0x53, 0x8c, 0x54, 0xa7, 0x54, 0x8c, 0x55, 0x83, 0x55, 0x84, + 0x55, 0x86, 0x55, 0x8a, 0x55, 0x8b, 0x55, 0xa8, 0x57, 0x82, 0x59, 0x82, + 0x59, 0x88, 0x5a, 0x81, 0x5a, 0x87, 0x5a, 0x8c, 0x4f, 0x9b, 0x55, 0x9b, + 0x44, 0x00, 0x7d, 0x01, 0x44, 0x00, 0x7e, 0x01, 0x64, 0x00, 0x7e, 0x01, + 0x4c, 0x4a, 0x4c, 0x6a, 0x6c, 0x6a, 0x4e, 0x4a, 0x4e, 0x6a, 0x6e, 0x6a, + 0x41, 0x00, 0x8c, 0x49, 0x00, 0x8c, 0x4f, 0x00, 0x8c, 0x55, 0x00, 0x8c, + 0xdc, 0x00, 0x84, 0xdc, 0x00, 0x81, 0xdc, 0x00, 0x8c, 0xdc, 0x00, 0x80, + 0xc4, 0x00, 0x84, 0x26, 0x02, 0x84, 0xc6, 0x00, 0x84, 0x47, 0x8c, 0x4b, + 0x8c, 0x4f, 0xa8, 0xea, 0x01, 0x84, 0xeb, 0x01, 0x84, 0xb7, 0x01, 0x8c, + 0x92, 0x02, 0x8c, 0x6a, 0x00, 0x8c, 0x44, 0x5a, 0x44, 0x7a, 0x64, 0x7a, + 0x47, 0x81, 0x4e, 0x00, 0x80, 0xc5, 0x00, 0x81, 0xc6, 0x00, 0x81, 0xd8, + 0x00, 0x81, 0x41, 0x8f, 0x41, 0x91, 0x45, 0x8f, 0x45, 0x91, 0x49, 0x8f, + 0x49, 0x91, 0x4f, 0x8f, 0x4f, 0x91, 0x52, 0x8f, 0x52, 0x91, 0x55, 0x8f, + 0x55, 0x91, 0x53, 0xa6, 0x54, 0xa6, 0x48, 0x8c, 0x41, 0x00, 0x87, 0x45, + 0x00, 0xa7, 0xd6, 0x00, 0x84, 0xd5, 0x00, 0x84, 0x4f, 0x00, 0x87, 0x2e, + 0x02, 0x84, 0x59, 0x00, 0x84, 0x68, 0x00, 0x66, 0x02, 0x6a, 0x00, 0x72, + 0x00, 0x79, 0x02, 0x7b, 0x02, 0x81, 0x02, 0x77, 0x00, 0x79, 0x00, 0x20, + 0x86, 0x20, 0x87, 0x20, 0x8a, 0x20, 0xa8, 0x20, 0x83, 0x20, 0x8b, 0x63, + 0x02, 0x6c, 0x00, 0x73, 0x00, 0x78, 0x00, 0x95, 0x02, 0x80, 0x81, 0x00, + 0x93, 0x88, 0x81, 0x20, 0xc5, 0x20, 0x81, 0xa8, 0x00, 0x81, 0x91, 0x03, + 0x81, 0x95, 0x03, 0x81, 0x97, 0x03, 0x81, 0x99, 0x03, 0x81, 0x00, 0x00, + 0x00, 0x9f, 0x03, 0x81, 0x00, 0x00, 0x00, 0xa5, 0x03, 0x81, 0xa9, 0x03, + 0x81, 0xca, 0x03, 0x81, 0x01, 0x03, 0x98, 0x07, 0xa4, 0x07, 0xb0, 0x00, + 0xb4, 0x00, 0xb6, 0x00, 0xb8, 0x00, 0xca, 0x00, 0x01, 0x03, 0xb8, 0x07, + 0xc4, 0x07, 0xbe, 0x00, 0xc4, 0x00, 0xc8, 0x00, 0xa5, 0x03, 0x0d, 0x13, + 0x00, 0x01, 0x03, 0xd1, 0x00, 0xd1, 0x07, 0xc6, 0x03, 0xc0, 0x03, 0xba, + 0x03, 0xc1, 0x03, 0xc2, 0x03, 0x00, 0x00, 0x98, 0x03, 0xb5, 0x03, 0x15, + 0x04, 0x80, 0x15, 0x04, 0x88, 0x00, 0x00, 0x00, 0x13, 0x04, 0x81, 0x06, + 0x04, 0x88, 0x1a, 0x04, 0x81, 0x18, 0x04, 0x80, 0x23, 0x04, 0x86, 0x18, + 0x04, 0x86, 0x38, 0x04, 0x86, 0x35, 0x04, 0x80, 0x35, 0x04, 0x88, 0x00, + 0x00, 0x00, 0x33, 0x04, 0x81, 0x56, 0x04, 0x88, 0x3a, 0x04, 0x81, 0x38, + 0x04, 0x80, 0x43, 0x04, 0x86, 0x74, 0x04, 0x8f, 0x16, 0x04, 0x86, 0x10, + 0x04, 0x86, 0x10, 0x04, 0x88, 0x15, 0x04, 0x86, 0xd8, 0x04, 0x88, 0x16, + 0x04, 0x88, 0x17, 0x04, 0x88, 0x18, 0x04, 0x84, 0x18, 0x04, 0x88, 0x1e, + 0x04, 0x88, 0xe8, 0x04, 0x88, 0x2d, 0x04, 0x88, 0x23, 0x04, 0x84, 0x23, + 0x04, 0x88, 0x23, 0x04, 0x8b, 0x27, 0x04, 0x88, 0x2b, 0x04, 0x88, 0x65, + 0x05, 0x82, 0x05, 0x27, 0x06, 0x00, 0x2c, 0x00, 0x2d, 0x21, 0x2d, 0x00, + 0x2e, 0x23, 0x2d, 0x27, 0x06, 0x00, 0x4d, 0x21, 0x4d, 0xa0, 0x4d, 0x23, + 0x4d, 0xd5, 0x06, 0x54, 0x06, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x06, 0x54, + 0x06, 0xd2, 0x06, 0x54, 0x06, 0x28, 0x09, 0x3c, 0x09, 0x30, 0x09, 0x3c, + 0x09, 0x33, 0x09, 0x3c, 0x09, 0x15, 0x09, 0x00, 0x27, 0x01, 0x27, 0x02, + 0x27, 0x07, 0x27, 0x0c, 0x27, 0x0d, 0x27, 0x16, 0x27, 0x1a, 0x27, 0xbe, + 0x09, 0x09, 0x00, 0x09, 0x19, 0xa1, 0x09, 0xbc, 0x09, 0xaf, 0x09, 0xbc, + 0x09, 0x32, 0x0a, 0x3c, 0x0a, 0x38, 0x0a, 0x3c, 0x0a, 0x16, 0x0a, 0x00, + 0x26, 0x01, 0x26, 0x06, 0x26, 0x2b, 0x0a, 0x3c, 0x0a, 0x47, 0x0b, 0x56, + 0x0b, 0x3e, 0x0b, 0x09, 0x00, 0x09, 0x19, 0x21, 0x0b, 0x3c, 0x0b, 0x92, + 0x0b, 0xd7, 0x0b, 0xbe, 0x0b, 0x08, 0x00, 0x09, 0x00, 0x08, 0x19, 0x46, + 0x0c, 0x56, 0x0c, 0xbf, 0x0c, 0xd5, 0x0c, 0xc6, 0x0c, 0xd5, 0x0c, 0xc2, + 0x0c, 0x04, 0x00, 0x08, 0x13, 0x3e, 0x0d, 0x08, 0x00, 0x09, 0x00, 0x08, + 0x19, 0xd9, 0x0d, 0xca, 0x0d, 0xca, 0x0d, 0x0f, 0x05, 0x12, 0x00, 0x0f, + 0x15, 0x4d, 0x0e, 0x32, 0x0e, 0xcd, 0x0e, 0xb2, 0x0e, 0x99, 0x0e, 0x12, + 0x00, 0x12, 0x08, 0x42, 0x0f, 0xb7, 0x0f, 0x4c, 0x0f, 0xb7, 0x0f, 0x51, + 0x0f, 0xb7, 0x0f, 0x56, 0x0f, 0xb7, 0x0f, 0x5b, 0x0f, 0xb7, 0x0f, 0x40, + 0x0f, 0xb5, 0x0f, 0x71, 0x0f, 0x72, 0x0f, 0x71, 0x0f, 0x00, 0x03, 0x41, + 0x0f, 0xb2, 0x0f, 0x81, 0x0f, 0xb3, 0x0f, 0x80, 0x0f, 0xb3, 0x0f, 0x81, + 0x0f, 0x71, 0x0f, 0x80, 0x0f, 0x92, 0x0f, 0xb7, 0x0f, 0x9c, 0x0f, 0xb7, + 0x0f, 0xa1, 0x0f, 0xb7, 0x0f, 0xa6, 0x0f, 0xb7, 0x0f, 0xab, 0x0f, 0xb7, + 0x0f, 0x90, 0x0f, 0xb5, 0x0f, 0x25, 0x10, 0x2e, 0x10, 0x05, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1b, 0x35, 0x1b, 0x00, 0x00, 0x00, + 0x00, 0x09, 0x1b, 0x35, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1b, 0x35, 0x1b, 0x11, 0x1b, 0x35, + 0x1b, 0x3a, 0x1b, 0x35, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1b, 0x35, + 0x1b, 0x3e, 0x1b, 0x35, 0x1b, 0x42, 0x1b, 0x35, 0x1b, 0x41, 0x00, 0xc6, + 0x00, 0x42, 0x00, 0x00, 0x00, 0x44, 0x00, 0x45, 0x00, 0x8e, 0x01, 0x47, + 0x00, 0x4f, 0x00, 0x22, 0x02, 0x50, 0x00, 0x52, 0x00, 0x54, 0x00, 0x55, + 0x00, 0x57, 0x00, 0x61, 0x00, 0x50, 0x02, 0x51, 0x02, 0x02, 0x1d, 0x62, + 0x00, 0x64, 0x00, 0x65, 0x00, 0x59, 0x02, 0x5b, 0x02, 0x5c, 0x02, 0x67, + 0x00, 0x00, 0x00, 0x6b, 0x00, 0x6d, 0x00, 0x4b, 0x01, 0x6f, 0x00, 0x54, + 0x02, 0x16, 0x1d, 0x17, 0x1d, 0x70, 0x00, 0x74, 0x00, 0x75, 0x00, 0x1d, + 0x1d, 0x6f, 0x02, 0x76, 0x00, 0x25, 0x1d, 0xb2, 0x03, 0xb3, 0x03, 0xb4, + 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x69, 0x00, 0x72, 0x00, 0x75, 0x00, 0x76, + 0x00, 0xb2, 0x03, 0xb3, 0x03, 0xc1, 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x52, + 0x02, 0x63, 0x00, 0x55, 0x02, 0xf0, 0x00, 0x5c, 0x02, 0x66, 0x00, 0x5f, + 0x02, 0x61, 0x02, 0x65, 0x02, 0x68, 0x02, 0x69, 0x02, 0x6a, 0x02, 0x7b, + 0x1d, 0x9d, 0x02, 0x6d, 0x02, 0x85, 0x1d, 0x9f, 0x02, 0x71, 0x02, 0x70, + 0x02, 0x72, 0x02, 0x73, 0x02, 0x74, 0x02, 0x75, 0x02, 0x78, 0x02, 0x82, + 0x02, 0x83, 0x02, 0xab, 0x01, 0x89, 0x02, 0x8a, 0x02, 0x1c, 0x1d, 0x8b, + 0x02, 0x8c, 0x02, 0x7a, 0x00, 0x90, 0x02, 0x91, 0x02, 0x92, 0x02, 0xb8, + 0x03, 0x41, 0x00, 0xa5, 0x42, 0x00, 0x87, 0x42, 0x00, 0xa3, 0x42, 0x00, + 0xb1, 0xc7, 0x00, 0x81, 0x44, 0x00, 0x87, 0x44, 0x00, 0xa3, 0x44, 0x00, + 0xb1, 0x44, 0x00, 0xa7, 0x44, 0x00, 0xad, 0x12, 0x01, 0x80, 0x12, 0x01, + 0x81, 0x45, 0x00, 0xad, 0x45, 0x00, 0xb0, 0x28, 0x02, 0x86, 0x46, 0x00, + 0x87, 0x47, 0x00, 0x84, 0x48, 0x00, 0x87, 0x48, 0x00, 0xa3, 0x48, 0x00, + 0x88, 0x48, 0x00, 0xa7, 0x48, 0x00, 0xae, 0x49, 0x00, 0xb0, 0xcf, 0x00, + 0x81, 0x4b, 0x00, 0x81, 0x4b, 0x00, 0xa3, 0x4b, 0x00, 0xb1, 0x4c, 0x00, + 0xa3, 0x36, 0x1e, 0x84, 0x4c, 0xb1, 0x4c, 0xad, 0x4d, 0x81, 0x4d, 0x87, + 0x4d, 0xa3, 0x4e, 0x87, 0x4e, 0xa3, 0x4e, 0xb1, 0x4e, 0xad, 0xd5, 0x00, + 0x81, 0xd5, 0x00, 0x88, 0x4c, 0x01, 0x80, 0x4c, 0x01, 0x81, 0x50, 0x00, + 0x81, 0x50, 0x00, 0x87, 0x52, 0x00, 0x87, 0x52, 0x00, 0xa3, 0x5a, 0x1e, + 0x84, 0x52, 0x00, 0xb1, 0x53, 0x00, 0x87, 0x53, 0x00, 0xa3, 0x5a, 0x01, + 0x87, 0x60, 0x01, 0x87, 0x62, 0x1e, 0x87, 0x54, 0x00, 0x87, 0x54, 0x00, + 0xa3, 0x54, 0x00, 0xb1, 0x54, 0x00, 0xad, 0x55, 0x00, 0xa4, 0x55, 0x00, + 0xb0, 0x55, 0x00, 0xad, 0x68, 0x01, 0x81, 0x6a, 0x01, 0x88, 0x56, 0x83, + 0x56, 0xa3, 0x57, 0x80, 0x57, 0x81, 0x57, 0x88, 0x57, 0x87, 0x57, 0xa3, + 0x58, 0x87, 0x58, 0x88, 0x59, 0x87, 0x5a, 0x82, 0x5a, 0xa3, 0x5a, 0xb1, + 0x68, 0xb1, 0x74, 0x88, 0x77, 0x8a, 0x79, 0x8a, 0x61, 0x00, 0xbe, 0x02, + 0x7f, 0x01, 0x87, 0x41, 0x00, 0xa3, 0x41, 0x00, 0x89, 0xc2, 0x00, 0x81, + 0xc2, 0x00, 0x80, 0xc2, 0x00, 0x89, 0xc2, 0x00, 0x83, 0xa0, 0x1e, 0x82, + 0x02, 0x01, 0x81, 0x02, 0x01, 0x80, 0x02, 0x01, 0x89, 0x02, 0x01, 0x83, + 0xa0, 0x1e, 0x86, 0x45, 0x00, 0xa3, 0x45, 0x00, 0x89, 0x45, 0x00, 0x83, + 0xca, 0x00, 0x81, 0xca, 0x00, 0x80, 0xca, 0x00, 0x89, 0xca, 0x00, 0x83, + 0xb8, 0x1e, 0x82, 0x49, 0x00, 0x89, 0x49, 0x00, 0xa3, 0x4f, 0x00, 0xa3, + 0x4f, 0x00, 0x89, 0xd4, 0x00, 0x81, 0xd4, 0x00, 0x80, 0xd4, 0x00, 0x89, + 0xd4, 0x00, 0x83, 0xcc, 0x1e, 0x82, 0xa0, 0x01, 0x81, 0xa0, 0x01, 0x80, + 0xa0, 0x01, 0x89, 0xa0, 0x01, 0x83, 0xa0, 0x01, 0xa3, 0x55, 0x00, 0xa3, + 0x55, 0x00, 0x89, 0xaf, 0x01, 0x81, 0xaf, 0x01, 0x80, 0xaf, 0x01, 0x89, + 0xaf, 0x01, 0x83, 0xaf, 0x01, 0xa3, 0x59, 0x00, 0x80, 0x59, 0x00, 0xa3, + 0x59, 0x00, 0x89, 0x59, 0x00, 0x83, 0xb1, 0x03, 0x13, 0x03, 0x00, 0x1f, + 0x80, 0x00, 0x1f, 0x81, 0x00, 0x1f, 0xc2, 0x91, 0x03, 0x13, 0x03, 0x08, + 0x1f, 0x80, 0x08, 0x1f, 0x81, 0x08, 0x1f, 0xc2, 0xb5, 0x03, 0x13, 0x03, + 0x10, 0x1f, 0x80, 0x10, 0x1f, 0x81, 0x95, 0x03, 0x13, 0x03, 0x18, 0x1f, + 0x80, 0x18, 0x1f, 0x81, 0xb7, 0x03, 0x93, 0xb7, 0x03, 0x94, 0x20, 0x1f, + 0x80, 0x21, 0x1f, 0x80, 0x20, 0x1f, 0x81, 0x21, 0x1f, 0x81, 0x20, 0x1f, + 0xc2, 0x21, 0x1f, 0xc2, 0x97, 0x03, 0x93, 0x97, 0x03, 0x94, 0x28, 0x1f, + 0x80, 0x29, 0x1f, 0x80, 0x28, 0x1f, 0x81, 0x29, 0x1f, 0x81, 0x28, 0x1f, + 0xc2, 0x29, 0x1f, 0xc2, 0xb9, 0x03, 0x93, 0xb9, 0x03, 0x94, 0x30, 0x1f, + 0x80, 0x31, 0x1f, 0x80, 0x30, 0x1f, 0x81, 0x31, 0x1f, 0x81, 0x30, 0x1f, + 0xc2, 0x31, 0x1f, 0xc2, 0x99, 0x03, 0x93, 0x99, 0x03, 0x94, 0x38, 0x1f, + 0x80, 0x39, 0x1f, 0x80, 0x38, 0x1f, 0x81, 0x39, 0x1f, 0x81, 0x38, 0x1f, + 0xc2, 0x39, 0x1f, 0xc2, 0xbf, 0x03, 0x93, 0xbf, 0x03, 0x94, 0x40, 0x1f, + 0x80, 0x40, 0x1f, 0x81, 0x9f, 0x03, 0x13, 0x03, 0x48, 0x1f, 0x80, 0x48, + 0x1f, 0x81, 0xc5, 0x03, 0x13, 0x03, 0x50, 0x1f, 0x80, 0x50, 0x1f, 0x81, + 0x50, 0x1f, 0xc2, 0xa5, 0x03, 0x94, 0x00, 0x00, 0x00, 0x59, 0x1f, 0x80, + 0x00, 0x00, 0x00, 0x59, 0x1f, 0x81, 0x00, 0x00, 0x00, 0x59, 0x1f, 0xc2, + 0xc9, 0x03, 0x93, 0xc9, 0x03, 0x94, 0x60, 0x1f, 0x80, 0x61, 0x1f, 0x80, + 0x60, 0x1f, 0x81, 0x61, 0x1f, 0x81, 0x60, 0x1f, 0xc2, 0x61, 0x1f, 0xc2, + 0xa9, 0x03, 0x93, 0xa9, 0x03, 0x94, 0x68, 0x1f, 0x80, 0x69, 0x1f, 0x80, + 0x68, 0x1f, 0x81, 0x69, 0x1f, 0x81, 0x68, 0x1f, 0xc2, 0x69, 0x1f, 0xc2, + 0xb1, 0x03, 0x80, 0xb5, 0x03, 0x80, 0xb7, 0x03, 0x80, 0xb9, 0x03, 0x80, + 0xbf, 0x03, 0x80, 0xc5, 0x03, 0x80, 0xc9, 0x03, 0x80, 0x00, 0x1f, 0x45, + 0x03, 0x20, 0x1f, 0x45, 0x03, 0x60, 0x1f, 0x45, 0x03, 0xb1, 0x03, 0x86, + 0xb1, 0x03, 0x84, 0x70, 0x1f, 0xc5, 0xb1, 0x03, 0xc5, 0xac, 0x03, 0xc5, + 0x00, 0x00, 0x00, 0xb1, 0x03, 0xc2, 0xb6, 0x1f, 0xc5, 0x91, 0x03, 0x86, + 0x91, 0x03, 0x84, 0x91, 0x03, 0x80, 0x91, 0x03, 0xc5, 0x20, 0x93, 0x20, + 0x93, 0x20, 0xc2, 0xa8, 0x00, 0xc2, 0x74, 0x1f, 0xc5, 0xb7, 0x03, 0xc5, + 0xae, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xb7, 0x03, 0xc2, 0xc6, 0x1f, 0xc5, + 0x95, 0x03, 0x80, 0x97, 0x03, 0x80, 0x97, 0x03, 0xc5, 0xbf, 0x1f, 0x80, + 0xbf, 0x1f, 0x81, 0xbf, 0x1f, 0xc2, 0xb9, 0x03, 0x86, 0xb9, 0x03, 0x84, + 0xca, 0x03, 0x80, 0x00, 0x03, 0xb9, 0x42, 0xca, 0x42, 0x99, 0x06, 0x99, + 0x04, 0x99, 0x00, 0xfe, 0x1f, 0x80, 0xfe, 0x1f, 0x81, 0xfe, 0x1f, 0xc2, + 0xc5, 0x03, 0x86, 0xc5, 0x03, 0x84, 0xcb, 0x03, 0x80, 0x00, 0x03, 0xc1, + 0x13, 0xc1, 0x14, 0xc5, 0x42, 0xcb, 0x42, 0xa5, 0x06, 0xa5, 0x04, 0xa5, + 0x00, 0xa1, 0x03, 0x94, 0xa8, 0x00, 0x80, 0x85, 0x03, 0x60, 0x00, 0x7c, + 0x1f, 0xc5, 0xc9, 0x03, 0xc5, 0xce, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xc9, + 0x03, 0xc2, 0xf6, 0x1f, 0xc5, 0x9f, 0x03, 0x80, 0xa9, 0x03, 0x80, 0xa9, + 0x03, 0xc5, 0x20, 0x94, 0x02, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0xb3, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x32, 0x20, + 0x32, 0x20, 0x32, 0x20, 0x00, 0x00, 0x00, 0x35, 0x20, 0x35, 0x20, 0x35, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x21, 0x00, 0x00, 0x20, 0x85, 0x3f, 0x3f, + 0x3f, 0x21, 0x21, 0x3f, 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30, 0x69, + 0x00, 0x00, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x3d, 0x28, 0x29, + 0x6e, 0x30, 0x00, 0x2b, 0x00, 0x12, 0x22, 0x3d, 0x00, 0x28, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x61, 0x00, 0x65, 0x00, 0x6f, 0x00, 0x78, 0x00, 0x59, + 0x02, 0x68, 0x6b, 0x6c, 0x6d, 0x6e, 0x70, 0x73, 0x74, 0x52, 0x73, 0x61, + 0x2f, 0x63, 0x61, 0x2f, 0x73, 0xb0, 0x00, 0x43, 0x63, 0x2f, 0x6f, 0x63, + 0x2f, 0x75, 0xb0, 0x00, 0x46, 0x48, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, + 0xdf, 0x01, 0x01, 0x04, 0x24, 0x4e, 0x6f, 0x50, 0x51, 0x52, 0x52, 0x52, + 0x53, 0x4d, 0x54, 0x45, 0x4c, 0x54, 0x4d, 0x4b, 0x00, 0xc5, 0x00, 0x42, + 0x43, 0x00, 0x65, 0x45, 0x46, 0x00, 0x4d, 0x6f, 0xd0, 0x05, 0x46, 0x41, + 0x58, 0xc0, 0x03, 0xb3, 0x03, 0x93, 0x03, 0xa0, 0x03, 0x11, 0x22, 0x44, + 0x64, 0x65, 0x69, 0x6a, 0x31, 0xd0, 0x37, 0x31, 0xd0, 0x39, 0x31, 0xd0, + 0x31, 0x30, 0x31, 0xd0, 0x33, 0x32, 0xd0, 0x33, 0x31, 0xd0, 0x35, 0x32, + 0xd0, 0x35, 0x33, 0xd0, 0x35, 0x34, 0xd0, 0x35, 0x31, 0xd0, 0x36, 0x35, + 0xd0, 0x36, 0x31, 0xd0, 0x38, 0x33, 0xd0, 0x38, 0x35, 0xd0, 0x38, 0x37, + 0xd0, 0x38, 0x31, 0xd0, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x56, 0x56, + 0x49, 0x56, 0x49, 0x49, 0x56, 0x49, 0x49, 0x49, 0x49, 0x58, 0x58, 0x49, + 0x58, 0x49, 0x49, 0x4c, 0x43, 0x44, 0x4d, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x76, 0x76, 0x69, 0x76, 0x69, 0x69, 0x76, 0x69, 0x69, 0x69, + 0x69, 0x78, 0x78, 0x69, 0x78, 0x69, 0x69, 0x6c, 0x63, 0x64, 0x6d, 0x30, + 0xd0, 0x33, 0x90, 0x21, 0xb8, 0x92, 0x21, 0xb8, 0x94, 0x21, 0xb8, 0xd0, + 0x21, 0xb8, 0xd4, 0x21, 0xb8, 0xd2, 0x21, 0xb8, 0x03, 0x22, 0xb8, 0x08, + 0x22, 0xb8, 0x0b, 0x22, 0xb8, 0x23, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x25, + 0x22, 0xb8, 0x2b, 0x22, 0x2b, 0x22, 0x2b, 0x22, 0x00, 0x00, 0x00, 0x2e, + 0x22, 0x2e, 0x22, 0x2e, 0x22, 0x00, 0x00, 0x00, 0x3c, 0x22, 0xb8, 0x43, + 0x22, 0xb8, 0x45, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x48, 0x22, 0xb8, 0x3d, + 0x00, 0xb8, 0x00, 0x00, 0x00, 0x61, 0x22, 0xb8, 0x4d, 0x22, 0xb8, 0x3c, + 0x00, 0xb8, 0x3e, 0x00, 0xb8, 0x64, 0x22, 0xb8, 0x65, 0x22, 0xb8, 0x72, + 0x22, 0xb8, 0x76, 0x22, 0xb8, 0x7a, 0x22, 0xb8, 0x82, 0x22, 0xb8, 0x86, + 0x22, 0xb8, 0xa2, 0x22, 0xb8, 0xa8, 0x22, 0xb8, 0xa9, 0x22, 0xb8, 0xab, + 0x22, 0xb8, 0x7c, 0x22, 0xb8, 0x91, 0x22, 0xb8, 0xb2, 0x22, 0x38, 0x03, + 0x08, 0x30, 0x31, 0x00, 0x31, 0x00, 0x30, 0x00, 0x32, 0x30, 0x28, 0x00, + 0x31, 0x00, 0x29, 0x00, 0x28, 0x00, 0x31, 0x00, 0x30, 0x00, 0x29, 0x00, + 0x28, 0x32, 0x30, 0x29, 0x31, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x30, 0x00, + 0x2e, 0x00, 0x32, 0x30, 0x2e, 0x28, 0x00, 0x61, 0x00, 0x29, 0x00, 0x41, + 0x00, 0x61, 0x00, 0x2b, 0x22, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x3a, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0xdd, 0x2a, 0xb8, 0x6a, 0x56, 0x00, 0x4e, + 0x00, 0x28, 0x36, 0x3f, 0x59, 0x85, 0x8c, 0xa0, 0xba, 0x3f, 0x51, 0x00, + 0x26, 0x2c, 0x43, 0x57, 0x6c, 0xa1, 0xb6, 0xc1, 0x9b, 0x52, 0x00, 0x5e, + 0x7a, 0x7f, 0x9d, 0xa6, 0xc1, 0xce, 0xe7, 0xb6, 0x53, 0xc8, 0x53, 0xe3, + 0x53, 0xd7, 0x56, 0x1f, 0x57, 0xeb, 0x58, 0x02, 0x59, 0x0a, 0x59, 0x15, + 0x59, 0x27, 0x59, 0x73, 0x59, 0x50, 0x5b, 0x80, 0x5b, 0xf8, 0x5b, 0x0f, + 0x5c, 0x22, 0x5c, 0x38, 0x5c, 0x6e, 0x5c, 0x71, 0x5c, 0xdb, 0x5d, 0xe5, + 0x5d, 0xf1, 0x5d, 0xfe, 0x5d, 0x72, 0x5e, 0x7a, 0x5e, 0x7f, 0x5e, 0xf4, + 0x5e, 0xfe, 0x5e, 0x0b, 0x5f, 0x13, 0x5f, 0x50, 0x5f, 0x61, 0x5f, 0x73, + 0x5f, 0xc3, 0x5f, 0x08, 0x62, 0x36, 0x62, 0x4b, 0x62, 0x2f, 0x65, 0x34, + 0x65, 0x87, 0x65, 0x97, 0x65, 0xa4, 0x65, 0xb9, 0x65, 0xe0, 0x65, 0xe5, + 0x65, 0xf0, 0x66, 0x08, 0x67, 0x28, 0x67, 0x20, 0x6b, 0x62, 0x6b, 0x79, + 0x6b, 0xb3, 0x6b, 0xcb, 0x6b, 0xd4, 0x6b, 0xdb, 0x6b, 0x0f, 0x6c, 0x14, + 0x6c, 0x34, 0x6c, 0x6b, 0x70, 0x2a, 0x72, 0x36, 0x72, 0x3b, 0x72, 0x3f, + 0x72, 0x47, 0x72, 0x59, 0x72, 0x5b, 0x72, 0xac, 0x72, 0x84, 0x73, 0x89, + 0x73, 0xdc, 0x74, 0xe6, 0x74, 0x18, 0x75, 0x1f, 0x75, 0x28, 0x75, 0x30, + 0x75, 0x8b, 0x75, 0x92, 0x75, 0x76, 0x76, 0x7d, 0x76, 0xae, 0x76, 0xbf, + 0x76, 0xee, 0x76, 0xdb, 0x77, 0xe2, 0x77, 0xf3, 0x77, 0x3a, 0x79, 0xb8, + 0x79, 0xbe, 0x79, 0x74, 0x7a, 0xcb, 0x7a, 0xf9, 0x7a, 0x73, 0x7c, 0xf8, + 0x7c, 0x36, 0x7f, 0x51, 0x7f, 0x8a, 0x7f, 0xbd, 0x7f, 0x01, 0x80, 0x0c, + 0x80, 0x12, 0x80, 0x33, 0x80, 0x7f, 0x80, 0x89, 0x80, 0xe3, 0x81, 0x00, + 0x07, 0x10, 0x19, 0x29, 0x38, 0x3c, 0x8b, 0x8f, 0x95, 0x4d, 0x86, 0x6b, + 0x86, 0x40, 0x88, 0x4c, 0x88, 0x63, 0x88, 0x7e, 0x89, 0x8b, 0x89, 0xd2, + 0x89, 0x00, 0x8a, 0x37, 0x8c, 0x46, 0x8c, 0x55, 0x8c, 0x78, 0x8c, 0x9d, + 0x8c, 0x64, 0x8d, 0x70, 0x8d, 0xb3, 0x8d, 0xab, 0x8e, 0xca, 0x8e, 0x9b, + 0x8f, 0xb0, 0x8f, 0xb5, 0x8f, 0x91, 0x90, 0x49, 0x91, 0xc6, 0x91, 0xcc, + 0x91, 0xd1, 0x91, 0x77, 0x95, 0x80, 0x95, 0x1c, 0x96, 0xb6, 0x96, 0xb9, + 0x96, 0xe8, 0x96, 0x51, 0x97, 0x5e, 0x97, 0x62, 0x97, 0x69, 0x97, 0xcb, + 0x97, 0xed, 0x97, 0xf3, 0x97, 0x01, 0x98, 0xa8, 0x98, 0xdb, 0x98, 0xdf, + 0x98, 0x96, 0x99, 0x99, 0x99, 0xac, 0x99, 0xa8, 0x9a, 0xd8, 0x9a, 0xdf, + 0x9a, 0x25, 0x9b, 0x2f, 0x9b, 0x32, 0x9b, 0x3c, 0x9b, 0x5a, 0x9b, 0xe5, + 0x9c, 0x75, 0x9e, 0x7f, 0x9e, 0xa5, 0x9e, 0x00, 0x16, 0x1e, 0x28, 0x2c, + 0x54, 0x58, 0x69, 0x6e, 0x7b, 0x96, 0xa5, 0xad, 0xe8, 0xf7, 0xfb, 0x12, + 0x30, 0x00, 0x00, 0x41, 0x53, 0x44, 0x53, 0x45, 0x53, 0x4b, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, 0x00, 0x51, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x53, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x55, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, 0x00, 0x57, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x59, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x61, 0x30, 0x99, 0x30, 0x64, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x66, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0x99, + 0x30, 0x6f, 0x30, 0x99, 0x30, 0x72, 0x30, 0x99, 0x30, 0x75, 0x30, 0x99, + 0x30, 0x78, 0x30, 0x99, 0x30, 0x7b, 0x30, 0x99, 0x30, 0x46, 0x30, 0x99, + 0x30, 0x20, 0x00, 0x99, 0x30, 0x9d, 0x30, 0x99, 0x30, 0x88, 0x30, 0x8a, + 0x30, 0xab, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, 0x00, 0xad, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xb1, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xb7, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xbd, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x30, 0x99, 0x30, 0xc4, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc8, 0x30, 0x99, 0x30, 0xcf, 0x30, 0x99, 0x30, 0xd2, 0x30, 0x99, + 0x30, 0xd5, 0x30, 0x99, 0x30, 0xd8, 0x30, 0x99, 0x30, 0xdb, 0x30, 0x99, + 0x30, 0xa6, 0x30, 0x99, 0x30, 0xef, 0x30, 0x99, 0x30, 0xfd, 0x30, 0x99, + 0x30, 0xb3, 0x30, 0xc8, 0x30, 0x00, 0x11, 0x00, 0x01, 0xaa, 0x02, 0xac, + 0xad, 0x03, 0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0x1a, 0x06, + 0x07, 0x08, 0x21, 0x09, 0x11, 0x61, 0x11, 0x14, 0x11, 0x4c, 0x00, 0x01, + 0xb3, 0xb4, 0xb8, 0xba, 0xbf, 0xc3, 0xc5, 0x08, 0xc9, 0xcb, 0x09, 0x0a, + 0x0c, 0x0e, 0x0f, 0x13, 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x22, + 0x2c, 0x33, 0x38, 0xdd, 0xde, 0x43, 0x44, 0x45, 0x70, 0x71, 0x74, 0x7d, + 0x7e, 0x80, 0x8a, 0x8d, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, + 0x0a, 0x4e, 0x2d, 0x4e, 0x0b, 0x4e, 0x32, 0x75, 0x59, 0x4e, 0x19, 0x4e, + 0x01, 0x4e, 0x29, 0x59, 0x30, 0x57, 0xba, 0x4e, 0x28, 0x00, 0x29, 0x00, + 0x00, 0x11, 0x02, 0x11, 0x03, 0x11, 0x05, 0x11, 0x06, 0x11, 0x07, 0x11, + 0x09, 0x11, 0x0b, 0x11, 0x0c, 0x11, 0x0e, 0x11, 0x0f, 0x11, 0x10, 0x11, + 0x11, 0x11, 0x12, 0x11, 0x28, 0x00, 0x00, 0x11, 0x61, 0x11, 0x29, 0x00, + 0x28, 0x00, 0x02, 0x11, 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x05, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x09, 0x11, 0x61, 0x11, 0x29, 0x00, + 0x28, 0x00, 0x0b, 0x11, 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0e, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0c, 0x11, 0x6e, 0x11, 0x29, 0x00, + 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11, 0x0c, 0x11, 0x65, 0x11, 0xab, 0x11, + 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11, 0x12, 0x11, 0x6e, 0x11, + 0x29, 0x00, 0x28, 0x00, 0x29, 0x00, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, + 0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e, 0x6b, 0x51, 0x5d, 0x4e, + 0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c, 0x28, 0x67, 0xd1, 0x91, + 0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67, 0x3e, 0x79, 0x0d, 0x54, + 0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52, 0xe3, 0x4e, 0x7c, 0x54, + 0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x6d, 0x79, + 0x11, 0x4f, 0xea, 0x81, 0xf3, 0x81, 0x4f, 0x55, 0x7c, 0x5e, 0x87, 0x65, + 0x8f, 0x7b, 0x50, 0x54, 0x45, 0x32, 0x00, 0x31, 0x00, 0x33, 0x00, 0x30, + 0x00, 0x00, 0x11, 0x00, 0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x0c, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x00, 0x11, 0x00, 0x61, 0x02, 0x61, 0x03, + 0x61, 0x05, 0x61, 0x06, 0x61, 0x07, 0x61, 0x09, 0x61, 0x0b, 0x61, 0x0c, + 0x61, 0x0e, 0x11, 0x61, 0x11, 0x00, 0x11, 0x0e, 0x61, 0xb7, 0x00, 0x69, + 0x0b, 0x11, 0x01, 0x63, 0x00, 0x69, 0x0b, 0x11, 0x6e, 0x11, 0x00, 0x4e, + 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e, + 0x6b, 0x51, 0x5d, 0x4e, 0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c, + 0x28, 0x67, 0xd1, 0x91, 0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67, + 0x3e, 0x79, 0x0d, 0x54, 0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52, + 0xd8, 0x79, 0x37, 0x75, 0x73, 0x59, 0x69, 0x90, 0x2a, 0x51, 0x70, 0x53, + 0xe8, 0x6c, 0x05, 0x98, 0x11, 0x4f, 0x99, 0x51, 0x63, 0x6b, 0x0a, 0x4e, + 0x2d, 0x4e, 0x0b, 0x4e, 0xe6, 0x5d, 0xf3, 0x53, 0x3b, 0x53, 0x97, 0x5b, + 0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x1c, 0x59, + 0x33, 0x00, 0x36, 0x00, 0x34, 0x00, 0x30, 0x00, 0x35, 0x30, 0x31, 0x00, + 0x08, 0x67, 0x31, 0x00, 0x30, 0x00, 0x08, 0x67, 0x48, 0x67, 0x65, 0x72, + 0x67, 0x65, 0x56, 0x4c, 0x54, 0x44, 0xa2, 0x30, 0x00, 0x02, 0x04, 0x06, + 0x08, 0x09, 0x0b, 0x0d, 0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, + 0x1f, 0x22, 0x24, 0x26, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x30, 0x33, + 0x36, 0x39, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x42, 0x44, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0xe4, 0x4e, 0x8c, 0x54, 0xa1, + 0x30, 0x01, 0x30, 0x5b, 0x27, 0x01, 0x4a, 0x34, 0x00, 0x01, 0x52, 0x39, + 0x01, 0xa2, 0x30, 0x00, 0x5a, 0x49, 0xa4, 0x30, 0x00, 0x27, 0x4f, 0x0c, + 0xa4, 0x30, 0x00, 0x4f, 0x1d, 0x02, 0x05, 0x4f, 0xa8, 0x30, 0x00, 0x11, + 0x07, 0x54, 0x21, 0xa8, 0x30, 0x00, 0x54, 0x03, 0x54, 0xa4, 0x30, 0x06, + 0x4f, 0x15, 0x06, 0x58, 0x3c, 0x07, 0x00, 0x46, 0xab, 0x30, 0x00, 0x3e, + 0x18, 0x1d, 0x00, 0x42, 0x3f, 0x51, 0xac, 0x30, 0x00, 0x41, 0x47, 0x00, + 0x47, 0x32, 0xae, 0x30, 0xac, 0x30, 0xae, 0x30, 0x00, 0x1d, 0x4e, 0xad, + 0x30, 0x00, 0x38, 0x3d, 0x4f, 0x01, 0x3e, 0x13, 0x4f, 0xad, 0x30, 0xed, + 0x30, 0xad, 0x30, 0x00, 0x40, 0x03, 0x3c, 0x33, 0xad, 0x30, 0x00, 0x40, + 0x34, 0x4f, 0x1b, 0x3e, 0xad, 0x30, 0x00, 0x40, 0x42, 0x16, 0x1b, 0xb0, + 0x30, 0x00, 0x39, 0x30, 0xa4, 0x30, 0x0c, 0x45, 0x3c, 0x24, 0x4f, 0x0b, + 0x47, 0x18, 0x00, 0x49, 0xaf, 0x30, 0x00, 0x3e, 0x4d, 0x1e, 0xb1, 0x30, + 0x00, 0x4b, 0x08, 0x02, 0x3a, 0x19, 0x02, 0x4b, 0x2c, 0xa4, 0x30, 0x11, + 0x00, 0x0b, 0x47, 0xb5, 0x30, 0x00, 0x3e, 0x0c, 0x47, 0x2b, 0xb0, 0x30, + 0x07, 0x3a, 0x43, 0x00, 0xb9, 0x30, 0x02, 0x3a, 0x08, 0x02, 0x3a, 0x0f, + 0x07, 0x43, 0x00, 0xb7, 0x30, 0x10, 0x00, 0x12, 0x34, 0x11, 0x3c, 0x13, + 0x17, 0xa4, 0x30, 0x2a, 0x1f, 0x24, 0x2b, 0x00, 0x20, 0xbb, 0x30, 0x16, + 0x41, 0x00, 0x38, 0x0d, 0xc4, 0x30, 0x0d, 0x38, 0x00, 0xd0, 0x30, 0x00, + 0x2c, 0x1c, 0x1b, 0xa2, 0x30, 0x32, 0x00, 0x17, 0x26, 0x49, 0xaf, 0x30, + 0x25, 0x00, 0x3c, 0xb3, 0x30, 0x21, 0x00, 0x20, 0x38, 0xa1, 0x30, 0x34, + 0x00, 0x48, 0x22, 0x28, 0xa3, 0x30, 0x32, 0x00, 0x59, 0x25, 0xa7, 0x30, + 0x2f, 0x1c, 0x10, 0x00, 0x44, 0xd5, 0x30, 0x00, 0x14, 0x1e, 0xaf, 0x30, + 0x29, 0x00, 0x10, 0x4d, 0x3c, 0xda, 0x30, 0xbd, 0x30, 0xb8, 0x30, 0x22, + 0x13, 0x1a, 0x20, 0x33, 0x0c, 0x22, 0x3b, 0x01, 0x22, 0x44, 0x00, 0x21, + 0x44, 0x07, 0xa4, 0x30, 0x39, 0x00, 0x4f, 0x24, 0xc8, 0x30, 0x14, 0x23, + 0x00, 0xdb, 0x30, 0xf3, 0x30, 0xc9, 0x30, 0x14, 0x2a, 0x00, 0x12, 0x33, + 0x22, 0x12, 0x33, 0x2a, 0xa4, 0x30, 0x3a, 0x00, 0x0b, 0x49, 0xa4, 0x30, + 0x3a, 0x00, 0x47, 0x3a, 0x1f, 0x2b, 0x3a, 0x47, 0x0b, 0xb7, 0x30, 0x27, + 0x3c, 0x00, 0x30, 0x3c, 0xaf, 0x30, 0x30, 0x00, 0x3e, 0x44, 0xdf, 0x30, + 0xea, 0x30, 0xd0, 0x30, 0x0f, 0x1a, 0x00, 0x2c, 0x1b, 0xe1, 0x30, 0xac, + 0x30, 0xac, 0x30, 0x35, 0x00, 0x1c, 0x47, 0x35, 0x50, 0x1c, 0x3f, 0xa2, + 0x30, 0x42, 0x5a, 0x27, 0x42, 0x5a, 0x49, 0x44, 0x00, 0x51, 0xc3, 0x30, + 0x27, 0x00, 0x05, 0x28, 0xea, 0x30, 0xe9, 0x30, 0xd4, 0x30, 0x17, 0x00, + 0x28, 0xd6, 0x30, 0x15, 0x26, 0x00, 0x15, 0xec, 0x30, 0xe0, 0x30, 0xb2, + 0x30, 0x3a, 0x41, 0x16, 0x00, 0x41, 0xc3, 0x30, 0x2c, 0x00, 0x05, 0x30, + 0x00, 0xb9, 0x70, 0x31, 0x00, 0x30, 0x00, 0xb9, 0x70, 0x32, 0x00, 0x30, + 0x00, 0xb9, 0x70, 0x68, 0x50, 0x61, 0x64, 0x61, 0x41, 0x55, 0x62, 0x61, + 0x72, 0x6f, 0x56, 0x70, 0x63, 0x64, 0x6d, 0x64, 0x00, 0x6d, 0x00, 0xb2, + 0x00, 0x49, 0x00, 0x55, 0x00, 0x73, 0x5e, 0x10, 0x62, 0x2d, 0x66, 0x8c, + 0x54, 0x27, 0x59, 0x63, 0x6b, 0x0e, 0x66, 0xbb, 0x6c, 0x2a, 0x68, 0x0f, + 0x5f, 0x1a, 0x4f, 0x3e, 0x79, 0x70, 0x00, 0x41, 0x6e, 0x00, 0x41, 0xbc, + 0x03, 0x41, 0x6d, 0x00, 0x41, 0x6b, 0x00, 0x41, 0x4b, 0x00, 0x42, 0x4d, + 0x00, 0x42, 0x47, 0x00, 0x42, 0x63, 0x61, 0x6c, 0x6b, 0x63, 0x61, 0x6c, + 0x70, 0x00, 0x46, 0x6e, 0x00, 0x46, 0xbc, 0x03, 0x46, 0xbc, 0x03, 0x67, + 0x6d, 0x00, 0x67, 0x6b, 0x00, 0x67, 0x48, 0x00, 0x7a, 0x6b, 0x48, 0x7a, + 0x4d, 0x48, 0x7a, 0x47, 0x48, 0x7a, 0x54, 0x48, 0x7a, 0xbc, 0x03, 0x13, + 0x21, 0x6d, 0x00, 0x13, 0x21, 0x64, 0x00, 0x13, 0x21, 0x6b, 0x00, 0x13, + 0x21, 0x66, 0x00, 0x6d, 0x6e, 0x00, 0x6d, 0xbc, 0x03, 0x6d, 0x6d, 0x00, + 0x6d, 0x63, 0x00, 0x6d, 0x6b, 0x00, 0x6d, 0x63, 0x00, 0x0a, 0x0a, 0x4f, + 0x00, 0x0a, 0x4f, 0x6d, 0x00, 0xb2, 0x00, 0x63, 0x00, 0x08, 0x0a, 0x4f, + 0x0a, 0x0a, 0x50, 0x00, 0x0a, 0x50, 0x6d, 0x00, 0xb3, 0x00, 0x6b, 0x00, + 0x6d, 0x00, 0xb3, 0x00, 0x6d, 0x00, 0x15, 0x22, 0x73, 0x00, 0x6d, 0x00, + 0x15, 0x22, 0x73, 0x00, 0xb2, 0x00, 0x50, 0x61, 0x6b, 0x50, 0x61, 0x4d, + 0x50, 0x61, 0x47, 0x50, 0x61, 0x72, 0x61, 0x64, 0x72, 0x61, 0x64, 0xd1, + 0x73, 0x72, 0x00, 0x61, 0x00, 0x64, 0x00, 0x15, 0x22, 0x73, 0x00, 0xb2, + 0x00, 0x70, 0x00, 0x73, 0x6e, 0x00, 0x73, 0xbc, 0x03, 0x73, 0x6d, 0x00, + 0x73, 0x70, 0x00, 0x56, 0x6e, 0x00, 0x56, 0xbc, 0x03, 0x56, 0x6d, 0x00, + 0x56, 0x6b, 0x00, 0x56, 0x4d, 0x00, 0x56, 0x70, 0x00, 0x57, 0x6e, 0x00, + 0x57, 0xbc, 0x03, 0x57, 0x6d, 0x00, 0x57, 0x6b, 0x00, 0x57, 0x4d, 0x00, + 0x57, 0x6b, 0x00, 0xa9, 0x03, 0x4d, 0x00, 0xa9, 0x03, 0x61, 0x2e, 0x6d, + 0x2e, 0x42, 0x71, 0x63, 0x63, 0x63, 0x64, 0x43, 0xd1, 0x6b, 0x67, 0x43, + 0x6f, 0x2e, 0x64, 0x42, 0x47, 0x79, 0x68, 0x61, 0x48, 0x50, 0x69, 0x6e, + 0x4b, 0x4b, 0x4b, 0x4d, 0x6b, 0x74, 0x6c, 0x6d, 0x6c, 0x6e, 0x6c, 0x6f, + 0x67, 0x6c, 0x78, 0x6d, 0x62, 0x6d, 0x69, 0x6c, 0x6d, 0x6f, 0x6c, 0x50, + 0x48, 0x70, 0x2e, 0x6d, 0x2e, 0x50, 0x50, 0x4d, 0x50, 0x52, 0x73, 0x72, + 0x53, 0x76, 0x57, 0x62, 0x56, 0xd1, 0x6d, 0x41, 0xd1, 0x6d, 0x31, 0x00, + 0xe5, 0x65, 0x31, 0x00, 0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00, + 0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65, 0x67, 0x61, 0x6c, 0x4a, + 0x04, 0x4c, 0x04, 0x26, 0x01, 0x53, 0x01, 0x27, 0xa7, 0x37, 0xab, 0x6b, + 0x02, 0x52, 0xab, 0x48, 0x8c, 0xf4, 0x66, 0xca, 0x8e, 0xc8, 0x8c, 0xd1, + 0x6e, 0x32, 0x4e, 0xe5, 0x53, 0x9c, 0x9f, 0x9c, 0x9f, 0x51, 0x59, 0xd1, + 0x91, 0x87, 0x55, 0x48, 0x59, 0xf6, 0x61, 0x69, 0x76, 0x85, 0x7f, 0x3f, + 0x86, 0xba, 0x87, 0xf8, 0x88, 0x8f, 0x90, 0x02, 0x6a, 0x1b, 0x6d, 0xd9, + 0x70, 0xde, 0x73, 0x3d, 0x84, 0x6a, 0x91, 0xf1, 0x99, 0x82, 0x4e, 0x75, + 0x53, 0x04, 0x6b, 0x1b, 0x72, 0x2d, 0x86, 0x1e, 0x9e, 0x50, 0x5d, 0xeb, + 0x6f, 0xcd, 0x85, 0x64, 0x89, 0xc9, 0x62, 0xd8, 0x81, 0x1f, 0x88, 0xca, + 0x5e, 0x17, 0x67, 0x6a, 0x6d, 0xfc, 0x72, 0xce, 0x90, 0x86, 0x4f, 0xb7, + 0x51, 0xde, 0x52, 0xc4, 0x64, 0xd3, 0x6a, 0x10, 0x72, 0xe7, 0x76, 0x01, + 0x80, 0x06, 0x86, 0x5c, 0x86, 0xef, 0x8d, 0x32, 0x97, 0x6f, 0x9b, 0xfa, + 0x9d, 0x8c, 0x78, 0x7f, 0x79, 0xa0, 0x7d, 0xc9, 0x83, 0x04, 0x93, 0x7f, + 0x9e, 0xd6, 0x8a, 0xdf, 0x58, 0x04, 0x5f, 0x60, 0x7c, 0x7e, 0x80, 0x62, + 0x72, 0xca, 0x78, 0xc2, 0x8c, 0xf7, 0x96, 0xd8, 0x58, 0x62, 0x5c, 0x13, + 0x6a, 0xda, 0x6d, 0x0f, 0x6f, 0x2f, 0x7d, 0x37, 0x7e, 0x4b, 0x96, 0xd2, + 0x52, 0x8b, 0x80, 0xdc, 0x51, 0xcc, 0x51, 0x1c, 0x7a, 0xbe, 0x7d, 0xf1, + 0x83, 0x75, 0x96, 0x80, 0x8b, 0xcf, 0x62, 0x02, 0x6a, 0xfe, 0x8a, 0x39, + 0x4e, 0xe7, 0x5b, 0x12, 0x60, 0x87, 0x73, 0x70, 0x75, 0x17, 0x53, 0xfb, + 0x78, 0xbf, 0x4f, 0xa9, 0x5f, 0x0d, 0x4e, 0xcc, 0x6c, 0x78, 0x65, 0x22, + 0x7d, 0xc3, 0x53, 0x5e, 0x58, 0x01, 0x77, 0x49, 0x84, 0xaa, 0x8a, 0xba, + 0x6b, 0xb0, 0x8f, 0x88, 0x6c, 0xfe, 0x62, 0xe5, 0x82, 0xa0, 0x63, 0x65, + 0x75, 0xae, 0x4e, 0x69, 0x51, 0xc9, 0x51, 0x81, 0x68, 0xe7, 0x7c, 0x6f, + 0x82, 0xd2, 0x8a, 0xcf, 0x91, 0xf5, 0x52, 0x42, 0x54, 0x73, 0x59, 0xec, + 0x5e, 0xc5, 0x65, 0xfe, 0x6f, 0x2a, 0x79, 0xad, 0x95, 0x6a, 0x9a, 0x97, + 0x9e, 0xce, 0x9e, 0x9b, 0x52, 0xc6, 0x66, 0x77, 0x6b, 0x62, 0x8f, 0x74, + 0x5e, 0x90, 0x61, 0x00, 0x62, 0x9a, 0x64, 0x23, 0x6f, 0x49, 0x71, 0x89, + 0x74, 0xca, 0x79, 0xf4, 0x7d, 0x6f, 0x80, 0x26, 0x8f, 0xee, 0x84, 0x23, + 0x90, 0x4a, 0x93, 0x17, 0x52, 0xa3, 0x52, 0xbd, 0x54, 0xc8, 0x70, 0xc2, + 0x88, 0xaa, 0x8a, 0xc9, 0x5e, 0xf5, 0x5f, 0x7b, 0x63, 0xae, 0x6b, 0x3e, + 0x7c, 0x75, 0x73, 0xe4, 0x4e, 0xf9, 0x56, 0xe7, 0x5b, 0xba, 0x5d, 0x1c, + 0x60, 0xb2, 0x73, 0x69, 0x74, 0x9a, 0x7f, 0x46, 0x80, 0x34, 0x92, 0xf6, + 0x96, 0x48, 0x97, 0x18, 0x98, 0x8b, 0x4f, 0xae, 0x79, 0xb4, 0x91, 0xb8, + 0x96, 0xe1, 0x60, 0x86, 0x4e, 0xda, 0x50, 0xee, 0x5b, 0x3f, 0x5c, 0x99, + 0x65, 0x02, 0x6a, 0xce, 0x71, 0x42, 0x76, 0xfc, 0x84, 0x7c, 0x90, 0x8d, + 0x9f, 0x88, 0x66, 0x2e, 0x96, 0x89, 0x52, 0x7b, 0x67, 0xf3, 0x67, 0x41, + 0x6d, 0x9c, 0x6e, 0x09, 0x74, 0x59, 0x75, 0x6b, 0x78, 0x10, 0x7d, 0x5e, + 0x98, 0x6d, 0x51, 0x2e, 0x62, 0x78, 0x96, 0x2b, 0x50, 0x19, 0x5d, 0xea, + 0x6d, 0x2a, 0x8f, 0x8b, 0x5f, 0x44, 0x61, 0x17, 0x68, 0x87, 0x73, 0x86, + 0x96, 0x29, 0x52, 0x0f, 0x54, 0x65, 0x5c, 0x13, 0x66, 0x4e, 0x67, 0xa8, + 0x68, 0xe5, 0x6c, 0x06, 0x74, 0xe2, 0x75, 0x79, 0x7f, 0xcf, 0x88, 0xe1, + 0x88, 0xcc, 0x91, 0xe2, 0x96, 0x3f, 0x53, 0xba, 0x6e, 0x1d, 0x54, 0xd0, + 0x71, 0x98, 0x74, 0xfa, 0x85, 0xa3, 0x96, 0x57, 0x9c, 0x9f, 0x9e, 0x97, + 0x67, 0xcb, 0x6d, 0xe8, 0x81, 0xcb, 0x7a, 0x20, 0x7b, 0x92, 0x7c, 0xc0, + 0x72, 0x99, 0x70, 0x58, 0x8b, 0xc0, 0x4e, 0x36, 0x83, 0x3a, 0x52, 0x07, + 0x52, 0xa6, 0x5e, 0xd3, 0x62, 0xd6, 0x7c, 0x85, 0x5b, 0x1e, 0x6d, 0xb4, + 0x66, 0x3b, 0x8f, 0x4c, 0x88, 0x4d, 0x96, 0x8b, 0x89, 0xd3, 0x5e, 0x40, + 0x51, 0xc0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x58, 0x00, 0x00, 0x74, + 0x66, 0x00, 0x00, 0x00, 0x00, 0xde, 0x51, 0x2a, 0x73, 0xca, 0x76, 0x3c, + 0x79, 0x5e, 0x79, 0x65, 0x79, 0x8f, 0x79, 0x56, 0x97, 0xbe, 0x7c, 0xbd, + 0x7f, 0x00, 0x00, 0x12, 0x86, 0x00, 0x00, 0xf8, 0x8a, 0x00, 0x00, 0x00, + 0x00, 0x38, 0x90, 0xfd, 0x90, 0xef, 0x98, 0xfc, 0x98, 0x28, 0x99, 0xb4, + 0x9d, 0xde, 0x90, 0xb7, 0x96, 0xae, 0x4f, 0xe7, 0x50, 0x4d, 0x51, 0xc9, + 0x52, 0xe4, 0x52, 0x51, 0x53, 0x9d, 0x55, 0x06, 0x56, 0x68, 0x56, 0x40, + 0x58, 0xa8, 0x58, 0x64, 0x5c, 0x6e, 0x5c, 0x94, 0x60, 0x68, 0x61, 0x8e, + 0x61, 0xf2, 0x61, 0x4f, 0x65, 0xe2, 0x65, 0x91, 0x66, 0x85, 0x68, 0x77, + 0x6d, 0x1a, 0x6e, 0x22, 0x6f, 0x6e, 0x71, 0x2b, 0x72, 0x22, 0x74, 0x91, + 0x78, 0x3e, 0x79, 0x49, 0x79, 0x48, 0x79, 0x50, 0x79, 0x56, 0x79, 0x5d, + 0x79, 0x8d, 0x79, 0x8e, 0x79, 0x40, 0x7a, 0x81, 0x7a, 0xc0, 0x7b, 0xf4, + 0x7d, 0x09, 0x7e, 0x41, 0x7e, 0x72, 0x7f, 0x05, 0x80, 0xed, 0x81, 0x79, + 0x82, 0x79, 0x82, 0x57, 0x84, 0x10, 0x89, 0x96, 0x89, 0x01, 0x8b, 0x39, + 0x8b, 0xd3, 0x8c, 0x08, 0x8d, 0xb6, 0x8f, 0x38, 0x90, 0xe3, 0x96, 0xff, + 0x97, 0x3b, 0x98, 0x75, 0x60, 0xee, 0x42, 0x18, 0x82, 0x02, 0x26, 0x4e, + 0xb5, 0x51, 0x68, 0x51, 0x80, 0x4f, 0x45, 0x51, 0x80, 0x51, 0xc7, 0x52, + 0xfa, 0x52, 0x9d, 0x55, 0x55, 0x55, 0x99, 0x55, 0xe2, 0x55, 0x5a, 0x58, + 0xb3, 0x58, 0x44, 0x59, 0x54, 0x59, 0x62, 0x5a, 0x28, 0x5b, 0xd2, 0x5e, + 0xd9, 0x5e, 0x69, 0x5f, 0xad, 0x5f, 0xd8, 0x60, 0x4e, 0x61, 0x08, 0x61, + 0x8e, 0x61, 0x60, 0x61, 0xf2, 0x61, 0x34, 0x62, 0xc4, 0x63, 0x1c, 0x64, + 0x52, 0x64, 0x56, 0x65, 0x74, 0x66, 0x17, 0x67, 0x1b, 0x67, 0x56, 0x67, + 0x79, 0x6b, 0xba, 0x6b, 0x41, 0x6d, 0xdb, 0x6e, 0xcb, 0x6e, 0x22, 0x6f, + 0x1e, 0x70, 0x6e, 0x71, 0xa7, 0x77, 0x35, 0x72, 0xaf, 0x72, 0x2a, 0x73, + 0x71, 0x74, 0x06, 0x75, 0x3b, 0x75, 0x1d, 0x76, 0x1f, 0x76, 0xca, 0x76, + 0xdb, 0x76, 0xf4, 0x76, 0x4a, 0x77, 0x40, 0x77, 0xcc, 0x78, 0xb1, 0x7a, + 0xc0, 0x7b, 0x7b, 0x7c, 0x5b, 0x7d, 0xf4, 0x7d, 0x3e, 0x7f, 0x05, 0x80, + 0x52, 0x83, 0xef, 0x83, 0x79, 0x87, 0x41, 0x89, 0x86, 0x89, 0x96, 0x89, + 0xbf, 0x8a, 0xf8, 0x8a, 0xcb, 0x8a, 0x01, 0x8b, 0xfe, 0x8a, 0xed, 0x8a, + 0x39, 0x8b, 0x8a, 0x8b, 0x08, 0x8d, 0x38, 0x8f, 0x72, 0x90, 0x99, 0x91, + 0x76, 0x92, 0x7c, 0x96, 0xe3, 0x96, 0x56, 0x97, 0xdb, 0x97, 0xff, 0x97, + 0x0b, 0x98, 0x3b, 0x98, 0x12, 0x9b, 0x9c, 0x9f, 0x4a, 0x28, 0x44, 0x28, + 0xd5, 0x33, 0x9d, 0x3b, 0x18, 0x40, 0x39, 0x40, 0x49, 0x52, 0xd0, 0x5c, + 0xd3, 0x7e, 0x43, 0x9f, 0x8e, 0x9f, 0x2a, 0xa0, 0x02, 0x66, 0x66, 0x66, + 0x69, 0x66, 0x6c, 0x66, 0x66, 0x69, 0x66, 0x66, 0x6c, 0x7f, 0x01, 0x74, + 0x73, 0x00, 0x74, 0x65, 0x05, 0x0f, 0x11, 0x0f, 0x00, 0x0f, 0x06, 0x19, + 0x11, 0x0f, 0x08, 0xd9, 0x05, 0xb4, 0x05, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x05, 0xb7, 0x05, 0xd0, 0x05, 0x12, 0x00, 0x03, 0x04, 0x0b, 0x0c, 0x0d, + 0x18, 0x1a, 0xe9, 0x05, 0xc1, 0x05, 0xe9, 0x05, 0xc2, 0x05, 0x49, 0xfb, + 0xc1, 0x05, 0x49, 0xfb, 0xc2, 0x05, 0xd0, 0x05, 0xb7, 0x05, 0xd0, 0x05, + 0xb8, 0x05, 0xd0, 0x05, 0xbc, 0x05, 0xd8, 0x05, 0xbc, 0x05, 0xde, 0x05, + 0xbc, 0x05, 0xe0, 0x05, 0xbc, 0x05, 0xe3, 0x05, 0xbc, 0x05, 0xb9, 0x05, + 0x2d, 0x03, 0x2e, 0x03, 0x2f, 0x03, 0x30, 0x03, 0x31, 0x03, 0x1c, 0x00, + 0x18, 0x06, 0x22, 0x06, 0x2b, 0x06, 0xd0, 0x05, 0xdc, 0x05, 0x71, 0x06, + 0x00, 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, 0x0d, 0x0f, 0x0f, + 0x0f, 0x0f, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x08, + 0x08, 0x08, 0x33, 0x33, 0x33, 0x33, 0x35, 0x35, 0x35, 0x35, 0x13, 0x13, + 0x13, 0x13, 0x12, 0x12, 0x12, 0x12, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, + 0x16, 0x16, 0x1c, 0x1c, 0x1b, 0x1b, 0x1d, 0x1d, 0x17, 0x17, 0x27, 0x27, + 0x20, 0x20, 0x38, 0x38, 0x38, 0x38, 0x3e, 0x3e, 0x3e, 0x3e, 0x42, 0x42, + 0x42, 0x42, 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a, + 0x4f, 0x4f, 0x50, 0x50, 0x50, 0x50, 0x4d, 0x4d, 0x4d, 0x4d, 0x61, 0x61, + 0x62, 0x62, 0x49, 0x06, 0x64, 0x64, 0x64, 0x64, 0x7e, 0x7e, 0x7d, 0x7d, + 0x7f, 0x7f, 0x2e, 0x82, 0x82, 0x7c, 0x7c, 0x80, 0x80, 0x87, 0x87, 0x87, + 0x87, 0x00, 0x00, 0x26, 0x06, 0x00, 0x01, 0x00, 0x01, 0x00, 0xaf, 0x00, + 0xaf, 0x00, 0x22, 0x00, 0x22, 0x00, 0xa1, 0x00, 0xa1, 0x00, 0xa0, 0x00, + 0xa0, 0x00, 0xa2, 0x00, 0xa2, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, + 0x23, 0x00, 0x23, 0x00, 0x23, 0xcc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x26, + 0x06, 0x00, 0x06, 0x00, 0x07, 0x00, 0x1f, 0x00, 0x23, 0x00, 0x24, 0x02, + 0x06, 0x02, 0x07, 0x02, 0x08, 0x02, 0x1f, 0x02, 0x23, 0x02, 0x24, 0x04, + 0x06, 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x23, 0x04, 0x24, 0x05, + 0x06, 0x05, 0x1f, 0x05, 0x23, 0x05, 0x24, 0x06, 0x07, 0x06, 0x1f, 0x07, + 0x06, 0x07, 0x1f, 0x08, 0x06, 0x08, 0x07, 0x08, 0x1f, 0x0d, 0x06, 0x0d, + 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, 0x0f, 0x1f, 0x10, 0x06, 0x10, + 0x07, 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x11, 0x1f, 0x12, 0x1f, 0x13, + 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, 0x1b, + 0x08, 0x1b, 0x1f, 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x07, 0x1c, 0x1f, 0x1c, + 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, + 0x1e, 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x06, 0x1e, 0x07, 0x1e, + 0x08, 0x1e, 0x1f, 0x1e, 0x23, 0x1e, 0x24, 0x1f, 0x06, 0x1f, 0x07, 0x1f, + 0x08, 0x1f, 0x1f, 0x1f, 0x23, 0x1f, 0x24, 0x20, 0x06, 0x20, 0x07, 0x20, + 0x08, 0x20, 0x1f, 0x20, 0x23, 0x20, 0x24, 0x21, 0x06, 0x21, 0x1f, 0x21, + 0x23, 0x21, 0x24, 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, 0x24, + 0x23, 0x24, 0x24, 0x0a, 0x4a, 0x0b, 0x4a, 0x23, 0x4a, 0x20, 0x00, 0x4c, + 0x06, 0x51, 0x06, 0x51, 0x06, 0xff, 0x00, 0x1f, 0x26, 0x06, 0x00, 0x0b, + 0x00, 0x0c, 0x00, 0x1f, 0x00, 0x20, 0x00, 0x23, 0x00, 0x24, 0x02, 0x0b, + 0x02, 0x0c, 0x02, 0x1f, 0x02, 0x20, 0x02, 0x23, 0x02, 0x24, 0x04, 0x0b, + 0x04, 0x0c, 0x04, 0x1f, 0x26, 0x06, 0x04, 0x20, 0x04, 0x23, 0x04, 0x24, + 0x05, 0x0b, 0x05, 0x0c, 0x05, 0x1f, 0x05, 0x20, 0x05, 0x23, 0x05, 0x24, + 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x1e, + 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x1f, 0x1e, 0x23, 0x1e, 0x24, + 0x1f, 0x01, 0x1f, 0x1f, 0x20, 0x0b, 0x20, 0x0c, 0x20, 0x1f, 0x20, 0x20, + 0x20, 0x23, 0x20, 0x24, 0x23, 0x4a, 0x24, 0x0b, 0x24, 0x0c, 0x24, 0x1f, + 0x24, 0x20, 0x24, 0x23, 0x24, 0x24, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, + 0x00, 0x1f, 0x00, 0x21, 0x02, 0x06, 0x02, 0x07, 0x02, 0x08, 0x02, 0x1f, + 0x02, 0x21, 0x04, 0x06, 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x21, + 0x05, 0x1f, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, 0x1f, 0x08, 0x06, + 0x08, 0x1f, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, + 0x0f, 0x08, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, 0x08, 0x10, 0x1f, + 0x11, 0x07, 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, + 0x1b, 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1c, 0x07, 0x1c, 0x1f, + 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x06, + 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x21, 0x1f, 0x06, 0x1f, 0x07, + 0x1f, 0x08, 0x1f, 0x1f, 0x20, 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, + 0x20, 0x21, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x4a, 0x24, 0x06, 0x24, 0x07, + 0x24, 0x08, 0x24, 0x1f, 0x24, 0x21, 0x00, 0x1f, 0x00, 0x21, 0x02, 0x1f, + 0x02, 0x21, 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x05, 0x21, 0x0d, 0x1f, + 0x0d, 0x21, 0x0e, 0x1f, 0x0e, 0x21, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x1f, + 0x20, 0x1f, 0x20, 0x21, 0x24, 0x1f, 0x24, 0x21, 0x40, 0x06, 0x4e, 0x06, + 0x51, 0x06, 0x27, 0x06, 0x10, 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, + 0x13, 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, 0x22, 0x0d, 0x23, + 0x06, 0x22, 0x06, 0x23, 0x05, 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, + 0x0e, 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, 0x05, 0x0d, 0x06, + 0x0d, 0x07, 0x0d, 0x1e, 0x0d, 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, + 0x10, 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, 0x22, 0x13, 0x23, + 0x0c, 0x22, 0x0c, 0x23, 0x0d, 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, + 0x05, 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, 0x22, 0x0e, 0x23, + 0x0f, 0x22, 0x0f, 0x23, 0x0d, 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, + 0x0d, 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x0d, 0x05, 0x0d, 0x06, + 0x0d, 0x07, 0x0d, 0x1e, 0x0c, 0x20, 0x0d, 0x20, 0x10, 0x1e, 0x0c, 0x05, + 0x0c, 0x06, 0x0c, 0x07, 0x0d, 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x10, 0x1e, + 0x11, 0x1e, 0x00, 0x24, 0x00, 0x24, 0x2a, 0x06, 0x00, 0x02, 0x1b, 0x00, + 0x03, 0x02, 0x00, 0x03, 0x02, 0x00, 0x03, 0x1b, 0x00, 0x04, 0x1b, 0x00, + 0x1b, 0x02, 0x00, 0x1b, 0x03, 0x00, 0x1b, 0x04, 0x02, 0x1b, 0x03, 0x02, + 0x1b, 0x03, 0x03, 0x1b, 0x20, 0x03, 0x1b, 0x1f, 0x09, 0x03, 0x02, 0x09, + 0x02, 0x03, 0x09, 0x02, 0x1f, 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x03, 0x09, + 0x1b, 0x02, 0x09, 0x1b, 0x1b, 0x09, 0x1b, 0x1b, 0x0b, 0x03, 0x03, 0x0b, + 0x03, 0x03, 0x0b, 0x1b, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, + 0x02, 0x20, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x1b, 0x0a, + 0x1b, 0x1b, 0x0c, 0x03, 0x1f, 0x0c, 0x04, 0x1b, 0x0c, 0x04, 0x1b, 0x0d, + 0x1b, 0x03, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, 0x1b, 0x0d, 0x1b, 0x20, 0x0f, + 0x02, 0x1b, 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1f, 0x10, + 0x1b, 0x1b, 0x10, 0x1b, 0x20, 0x10, 0x1b, 0x1f, 0x17, 0x04, 0x1b, 0x17, + 0x04, 0x1b, 0x18, 0x1b, 0x03, 0x18, 0x1b, 0x1b, 0x1a, 0x03, 0x1b, 0x1a, + 0x03, 0x20, 0x1a, 0x03, 0x1f, 0x1a, 0x02, 0x02, 0x1a, 0x02, 0x02, 0x1a, + 0x04, 0x1b, 0x1a, 0x04, 0x1b, 0x1a, 0x1b, 0x03, 0x1a, 0x1b, 0x03, 0x1b, + 0x03, 0x02, 0x1b, 0x03, 0x1b, 0x1b, 0x03, 0x20, 0x1b, 0x02, 0x03, 0x1b, + 0x02, 0x1b, 0x1b, 0x04, 0x02, 0x1b, 0x04, 0x1b, 0x28, 0x06, 0x1d, 0x04, + 0x06, 0x1f, 0x1d, 0x04, 0x1f, 0x1d, 0x1d, 0x1e, 0x05, 0x1d, 0x1e, 0x05, + 0x21, 0x1e, 0x04, 0x1d, 0x1e, 0x04, 0x1d, 0x1e, 0x04, 0x21, 0x1e, 0x1d, + 0x22, 0x1e, 0x1d, 0x21, 0x22, 0x1d, 0x1d, 0x22, 0x1d, 0x1d, 0x00, 0x06, + 0x22, 0x02, 0x04, 0x22, 0x02, 0x04, 0x21, 0x02, 0x06, 0x22, 0x02, 0x06, + 0x21, 0x02, 0x1d, 0x22, 0x02, 0x1d, 0x21, 0x04, 0x1d, 0x22, 0x04, 0x05, + 0x21, 0x04, 0x1d, 0x21, 0x0b, 0x06, 0x21, 0x0d, 0x05, 0x22, 0x0c, 0x05, + 0x22, 0x0e, 0x05, 0x22, 0x1c, 0x04, 0x22, 0x1c, 0x1d, 0x22, 0x22, 0x05, + 0x22, 0x22, 0x04, 0x22, 0x22, 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1a, 0x1d, + 0x22, 0x1e, 0x05, 0x22, 0x1a, 0x1d, 0x05, 0x1c, 0x05, 0x1d, 0x11, 0x1d, + 0x22, 0x1b, 0x1d, 0x22, 0x1e, 0x04, 0x05, 0x1d, 0x06, 0x22, 0x1c, 0x04, + 0x1d, 0x1b, 0x1d, 0x1d, 0x1c, 0x04, 0x1d, 0x1e, 0x04, 0x05, 0x04, 0x05, + 0x22, 0x05, 0x04, 0x22, 0x1d, 0x04, 0x22, 0x19, 0x1d, 0x22, 0x00, 0x05, + 0x22, 0x1b, 0x1d, 0x1d, 0x11, 0x04, 0x1d, 0x0d, 0x1d, 0x1d, 0x0b, 0x06, + 0x22, 0x1e, 0x04, 0x22, 0x35, 0x06, 0x00, 0x0f, 0x9d, 0x0d, 0x0f, 0x9d, + 0x27, 0x06, 0x00, 0x1d, 0x1d, 0x20, 0x00, 0x1c, 0x01, 0x0a, 0x1e, 0x06, + 0x1e, 0x08, 0x0e, 0x1d, 0x12, 0x1e, 0x0a, 0x0c, 0x21, 0x1d, 0x12, 0x1d, + 0x23, 0x20, 0x21, 0x0c, 0x1d, 0x1e, 0x35, 0x06, 0x00, 0x0f, 0x14, 0x27, + 0x06, 0x0e, 0x1d, 0x22, 0xff, 0x00, 0x1d, 0x1d, 0x20, 0xff, 0x12, 0x1d, + 0x23, 0x20, 0xff, 0x21, 0x0c, 0x1d, 0x1e, 0x27, 0x06, 0x05, 0x1d, 0xff, + 0x05, 0x1d, 0x00, 0x1d, 0x20, 0x27, 0x06, 0x0a, 0xa5, 0x00, 0x1d, 0x2c, + 0x00, 0x01, 0x30, 0x02, 0x30, 0x3a, 0x00, 0x3b, 0x00, 0x21, 0x00, 0x3f, + 0x00, 0x16, 0x30, 0x17, 0x30, 0x26, 0x20, 0x13, 0x20, 0x12, 0x01, 0x00, + 0x5f, 0x5f, 0x28, 0x29, 0x7b, 0x7d, 0x08, 0x30, 0x0c, 0x0d, 0x08, 0x09, + 0x02, 0x03, 0x00, 0x01, 0x04, 0x05, 0x06, 0x07, 0x5b, 0x00, 0x5d, 0x00, + 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x5f, 0x00, 0x5f, 0x00, + 0x5f, 0x00, 0x2c, 0x00, 0x01, 0x30, 0x2e, 0x00, 0x00, 0x00, 0x3b, 0x00, + 0x3a, 0x00, 0x3f, 0x00, 0x21, 0x00, 0x14, 0x20, 0x28, 0x00, 0x29, 0x00, + 0x7b, 0x00, 0x7d, 0x00, 0x14, 0x30, 0x15, 0x30, 0x23, 0x26, 0x2a, 0x2b, + 0x2d, 0x3c, 0x3e, 0x3d, 0x00, 0x5c, 0x24, 0x25, 0x40, 0x40, 0x06, 0xff, + 0x0b, 0x00, 0x0b, 0xff, 0x0c, 0x20, 0x00, 0x4d, 0x06, 0x40, 0x06, 0xff, + 0x0e, 0x00, 0x0e, 0xff, 0x0f, 0x00, 0x0f, 0xff, 0x10, 0x00, 0x10, 0xff, + 0x11, 0x00, 0x11, 0xff, 0x12, 0x00, 0x12, 0x21, 0x06, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, + 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, + 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, + 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, + 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, + 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, + 0x28, 0x28, 0x29, 0x29, 0x29, 0x29, 0x22, 0x06, 0x22, 0x00, 0x22, 0x00, + 0x22, 0x01, 0x22, 0x01, 0x22, 0x03, 0x22, 0x03, 0x22, 0x05, 0x22, 0x05, + 0x21, 0x00, 0x85, 0x29, 0x01, 0x30, 0x01, 0x0b, 0x0c, 0x00, 0xfa, 0xf1, + 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xe2, 0xe4, 0xe6, 0xc2, 0xfb, 0xa1, 0xa3, + 0xa5, 0xa7, 0xa9, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, + 0xbc, 0xbe, 0xc0, 0xc3, 0xc5, 0xc7, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xd1, 0xd4, 0xd7, 0xda, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xee, 0xf2, 0x98, 0x99, 0x31, 0x31, 0x4f, + 0x31, 0x55, 0x31, 0x5b, 0x31, 0x61, 0x31, 0xa2, 0x00, 0xa3, 0x00, 0xac, + 0x00, 0xaf, 0x00, 0xa6, 0x00, 0xa5, 0x00, 0xa9, 0x20, 0x00, 0x00, 0x02, + 0x25, 0x90, 0x21, 0x91, 0x21, 0x92, 0x21, 0x93, 0x21, 0xa0, 0x25, 0xcb, + 0x25, 0x99, 0x10, 0xba, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, 0xba, + 0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, 0x05, 0x31, 0x11, 0x27, 0x11, + 0x32, 0x11, 0x27, 0x11, 0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, 0x57, + 0x13, 0x55, 0xb9, 0x14, 0xba, 0x14, 0xb9, 0x14, 0xb0, 0x14, 0x00, 0x00, + 0x00, 0x00, 0xb9, 0x14, 0xbd, 0x14, 0x55, 0x50, 0xb8, 0x15, 0xaf, 0x15, + 0xb9, 0x15, 0xaf, 0x15, 0x55, 0x57, 0xd1, 0x65, 0xd1, 0x58, 0xd1, 0x65, + 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, 0x5f, 0xd1, 0x6f, 0xd1, 0x5f, 0xd1, 0x70, + 0xd1, 0x5f, 0xd1, 0x71, 0xd1, 0x5f, 0xd1, 0x72, 0xd1, 0x55, 0x55, 0x55, + 0x05, 0xb9, 0xd1, 0x65, 0xd1, 0xba, 0xd1, 0x65, 0xd1, 0xbb, 0xd1, 0x6e, + 0xd1, 0xbc, 0xd1, 0x6e, 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, 0xbc, 0xd1, 0x6f, + 0xd1, 0x55, 0x55, 0x55, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x69, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x43, 0x44, 0x00, 0x00, + 0x47, 0x00, 0x00, 0x4a, 0x4b, 0x00, 0x00, 0x4e, 0x4f, 0x50, 0x51, 0x00, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, + 0x00, 0x66, 0x68, 0x00, 0x70, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x42, + 0x00, 0x44, 0x45, 0x46, 0x47, 0x4a, 0x00, 0x53, 0x00, 0x61, 0x00, 0x41, + 0x42, 0x00, 0x44, 0x45, 0x46, 0x47, 0x00, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x00, 0x4f, 0x53, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x31, 0x01, 0x37, 0x02, 0x91, 0x03, + 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, + 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, + 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, + 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x0b, 0x0c, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x27, 0x06, 0x00, 0x01, + 0x05, 0x08, 0x2a, 0x06, 0x1e, 0x08, 0x03, 0x0d, 0x20, 0x19, 0x1a, 0x1b, + 0x1c, 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, + 0x0c, 0x0e, 0x10, 0x44, 0x90, 0x77, 0x45, 0x28, 0x06, 0x2c, 0x06, 0x00, + 0x00, 0x47, 0x06, 0x33, 0x06, 0x17, 0x10, 0x11, 0x12, 0x13, 0x00, 0x06, + 0x0e, 0x02, 0x0f, 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, + 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, 0x2d, 0x06, 0x00, 0x00, 0x4a, + 0x06, 0x00, 0x00, 0x44, 0x06, 0x00, 0x00, 0x46, 0x06, 0x33, 0x06, 0x39, + 0x06, 0x00, 0x00, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, 0x34, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x2e, 0x06, 0x00, 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, + 0x06, 0x00, 0x00, 0xba, 0x06, 0x00, 0x00, 0x6f, 0x06, 0x00, 0x00, 0x28, + 0x06, 0x2c, 0x06, 0x00, 0x00, 0x47, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2d, + 0x06, 0x37, 0x06, 0x4a, 0x06, 0x43, 0x06, 0x00, 0x00, 0x45, 0x06, 0x46, + 0x06, 0x33, 0x06, 0x39, 0x06, 0x41, 0x06, 0x35, 0x06, 0x42, 0x06, 0x00, + 0x00, 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, 0x36, + 0x06, 0x38, 0x06, 0x3a, 0x06, 0x6e, 0x06, 0x00, 0x00, 0xa1, 0x06, 0x27, + 0x06, 0x00, 0x01, 0x05, 0x08, 0x20, 0x21, 0x0b, 0x06, 0x10, 0x23, 0x2a, + 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, + 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x28, 0x06, 0x2c, 0x06, 0x2f, 0x06, + 0x00, 0x00, 0x48, 0x06, 0x32, 0x06, 0x2d, 0x06, 0x37, 0x06, 0x4a, 0x06, + 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, + 0x00, 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x30, 0x2e, 0x30, 0x00, 0x2c, + 0x00, 0x28, 0x00, 0x41, 0x00, 0x29, 0x00, 0x14, 0x30, 0x53, 0x00, 0x15, + 0x30, 0x43, 0x52, 0x43, 0x44, 0x57, 0x5a, 0x41, 0x00, 0x48, 0x56, 0x4d, + 0x56, 0x53, 0x44, 0x53, 0x53, 0x50, 0x50, 0x56, 0x57, 0x43, 0x4d, 0x43, + 0x4d, 0x44, 0x4d, 0x52, 0x44, 0x4a, 0x4b, 0x30, 0x30, 0x00, 0x68, 0x68, + 0x4b, 0x62, 0x57, 0x5b, 0xcc, 0x53, 0xc7, 0x30, 0x8c, 0x4e, 0x1a, 0x59, + 0xe3, 0x89, 0x29, 0x59, 0xa4, 0x4e, 0x20, 0x66, 0x21, 0x71, 0x99, 0x65, + 0x4d, 0x52, 0x8c, 0x5f, 0x8d, 0x51, 0xb0, 0x65, 0x1d, 0x52, 0x42, 0x7d, + 0x1f, 0x75, 0xa9, 0x8c, 0xf0, 0x58, 0x39, 0x54, 0x14, 0x6f, 0x95, 0x62, + 0x55, 0x63, 0x00, 0x4e, 0x09, 0x4e, 0x4a, 0x90, 0xe6, 0x5d, 0x2d, 0x4e, + 0xf3, 0x53, 0x07, 0x63, 0x70, 0x8d, 0x53, 0x62, 0x81, 0x79, 0x7a, 0x7a, + 0x08, 0x54, 0x80, 0x6e, 0x09, 0x67, 0x08, 0x67, 0x33, 0x75, 0x72, 0x52, + 0xb6, 0x55, 0x4d, 0x91, 0x14, 0x30, 0x15, 0x30, 0x2c, 0x67, 0x09, 0x4e, + 0x8c, 0x4e, 0x89, 0x5b, 0xb9, 0x70, 0x53, 0x62, 0xd7, 0x76, 0xdd, 0x52, + 0x57, 0x65, 0x97, 0x5f, 0xef, 0x53, 0x38, 0x4e, 0x05, 0x00, 0x09, 0x22, + 0x01, 0x60, 0x4f, 0xae, 0x4f, 0xbb, 0x4f, 0x02, 0x50, 0x7a, 0x50, 0x99, + 0x50, 0xe7, 0x50, 0xcf, 0x50, 0x9e, 0x34, 0x3a, 0x06, 0x4d, 0x51, 0x54, + 0x51, 0x64, 0x51, 0x77, 0x51, 0x1c, 0x05, 0xb9, 0x34, 0x67, 0x51, 0x8d, + 0x51, 0x4b, 0x05, 0x97, 0x51, 0xa4, 0x51, 0xcc, 0x4e, 0xac, 0x51, 0xb5, + 0x51, 0xdf, 0x91, 0xf5, 0x51, 0x03, 0x52, 0xdf, 0x34, 0x3b, 0x52, 0x46, + 0x52, 0x72, 0x52, 0x77, 0x52, 0x15, 0x35, 0x02, 0x00, 0x20, 0x80, 0x80, + 0x00, 0x08, 0x00, 0x00, 0xc7, 0x52, 0x00, 0x02, 0x1d, 0x33, 0x3e, 0x3f, + 0x50, 0x82, 0x8a, 0x93, 0xac, 0xb6, 0xb8, 0xb8, 0xb8, 0x2c, 0x0a, 0x70, + 0x70, 0xca, 0x53, 0xdf, 0x53, 0x63, 0x0b, 0xeb, 0x53, 0xf1, 0x53, 0x06, + 0x54, 0x9e, 0x54, 0x38, 0x54, 0x48, 0x54, 0x68, 0x54, 0xa2, 0x54, 0xf6, + 0x54, 0x10, 0x55, 0x53, 0x55, 0x63, 0x55, 0x84, 0x55, 0x84, 0x55, 0x99, + 0x55, 0xab, 0x55, 0xb3, 0x55, 0xc2, 0x55, 0x16, 0x57, 0x06, 0x56, 0x17, + 0x57, 0x51, 0x56, 0x74, 0x56, 0x07, 0x52, 0xee, 0x58, 0xce, 0x57, 0xf4, + 0x57, 0x0d, 0x58, 0x8b, 0x57, 0x32, 0x58, 0x31, 0x58, 0xac, 0x58, 0xe4, + 0x14, 0xf2, 0x58, 0xf7, 0x58, 0x06, 0x59, 0x1a, 0x59, 0x22, 0x59, 0x62, + 0x59, 0xa8, 0x16, 0xea, 0x16, 0xec, 0x59, 0x1b, 0x5a, 0x27, 0x5a, 0xd8, + 0x59, 0x66, 0x5a, 0xee, 0x36, 0xfc, 0x36, 0x08, 0x5b, 0x3e, 0x5b, 0x3e, + 0x5b, 0xc8, 0x19, 0xc3, 0x5b, 0xd8, 0x5b, 0xe7, 0x5b, 0xf3, 0x5b, 0x18, + 0x1b, 0xff, 0x5b, 0x06, 0x5c, 0x53, 0x5f, 0x22, 0x5c, 0x81, 0x37, 0x60, + 0x5c, 0x6e, 0x5c, 0xc0, 0x5c, 0x8d, 0x5c, 0xe4, 0x1d, 0x43, 0x5d, 0xe6, + 0x1d, 0x6e, 0x5d, 0x6b, 0x5d, 0x7c, 0x5d, 0xe1, 0x5d, 0xe2, 0x5d, 0x2f, + 0x38, 0xfd, 0x5d, 0x28, 0x5e, 0x3d, 0x5e, 0x69, 0x5e, 0x62, 0x38, 0x83, + 0x21, 0x7c, 0x38, 0xb0, 0x5e, 0xb3, 0x5e, 0xb6, 0x5e, 0xca, 0x5e, 0x92, + 0xa3, 0xfe, 0x5e, 0x31, 0x23, 0x31, 0x23, 0x01, 0x82, 0x22, 0x5f, 0x22, + 0x5f, 0xc7, 0x38, 0xb8, 0x32, 0xda, 0x61, 0x62, 0x5f, 0x6b, 0x5f, 0xe3, + 0x38, 0x9a, 0x5f, 0xcd, 0x5f, 0xd7, 0x5f, 0xf9, 0x5f, 0x81, 0x60, 0x3a, + 0x39, 0x1c, 0x39, 0x94, 0x60, 0xd4, 0x26, 0xc7, 0x60, 0x02, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x02, + 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x08, 0x80, 0x28, 0x80, 0x02, 0x00, + 0x00, 0x02, 0x48, 0x61, 0x00, 0x04, 0x06, 0x04, 0x32, 0x46, 0x6a, 0x5c, + 0x67, 0x96, 0xaa, 0xae, 0xc8, 0xd3, 0x5d, 0x62, 0x00, 0x54, 0x77, 0xf3, + 0x0c, 0x2b, 0x3d, 0x63, 0xfc, 0x62, 0x68, 0x63, 0x83, 0x63, 0xe4, 0x63, + 0xf1, 0x2b, 0x22, 0x64, 0xc5, 0x63, 0xa9, 0x63, 0x2e, 0x3a, 0x69, 0x64, + 0x7e, 0x64, 0x9d, 0x64, 0x77, 0x64, 0x6c, 0x3a, 0x4f, 0x65, 0x6c, 0x65, + 0x0a, 0x30, 0xe3, 0x65, 0xf8, 0x66, 0x49, 0x66, 0x19, 0x3b, 0x91, 0x66, + 0x08, 0x3b, 0xe4, 0x3a, 0x92, 0x51, 0x95, 0x51, 0x00, 0x67, 0x9c, 0x66, + 0xad, 0x80, 0xd9, 0x43, 0x17, 0x67, 0x1b, 0x67, 0x21, 0x67, 0x5e, 0x67, + 0x53, 0x67, 0xc3, 0x33, 0x49, 0x3b, 0xfa, 0x67, 0x85, 0x67, 0x52, 0x68, + 0x85, 0x68, 0x6d, 0x34, 0x8e, 0x68, 0x1f, 0x68, 0x14, 0x69, 0x9d, 0x3b, + 0x42, 0x69, 0xa3, 0x69, 0xea, 0x69, 0xa8, 0x6a, 0xa3, 0x36, 0xdb, 0x6a, + 0x18, 0x3c, 0x21, 0x6b, 0xa7, 0x38, 0x54, 0x6b, 0x4e, 0x3c, 0x72, 0x6b, + 0x9f, 0x6b, 0xba, 0x6b, 0xbb, 0x6b, 0x8d, 0x3a, 0x0b, 0x1d, 0xfa, 0x3a, + 0x4e, 0x6c, 0xbc, 0x3c, 0xbf, 0x6c, 0xcd, 0x6c, 0x67, 0x6c, 0x16, 0x6d, + 0x3e, 0x6d, 0x77, 0x6d, 0x41, 0x6d, 0x69, 0x6d, 0x78, 0x6d, 0x85, 0x6d, + 0x1e, 0x3d, 0x34, 0x6d, 0x2f, 0x6e, 0x6e, 0x6e, 0x33, 0x3d, 0xcb, 0x6e, + 0xc7, 0x6e, 0xd1, 0x3e, 0xf9, 0x6d, 0x6e, 0x6f, 0x5e, 0x3f, 0x8e, 0x3f, + 0xc6, 0x6f, 0x39, 0x70, 0x1e, 0x70, 0x1b, 0x70, 0x96, 0x3d, 0x4a, 0x70, + 0x7d, 0x70, 0x77, 0x70, 0xad, 0x70, 0x25, 0x05, 0x45, 0x71, 0x63, 0x42, + 0x9c, 0x71, 0xab, 0x43, 0x28, 0x72, 0x35, 0x72, 0x50, 0x72, 0x08, 0x46, + 0x80, 0x72, 0x95, 0x72, 0x35, 0x47, 0x02, 0x20, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, 0x02, 0x02, 0x80, 0x8a, 0x00, + 0x00, 0x20, 0x00, 0x08, 0x0a, 0x00, 0x80, 0x88, 0x80, 0x20, 0x14, 0x48, + 0x7a, 0x73, 0x8b, 0x73, 0xac, 0x3e, 0xa5, 0x73, 0xb8, 0x3e, 0xb8, 0x3e, + 0x47, 0x74, 0x5c, 0x74, 0x71, 0x74, 0x85, 0x74, 0xca, 0x74, 0x1b, 0x3f, + 0x24, 0x75, 0x36, 0x4c, 0x3e, 0x75, 0x92, 0x4c, 0x70, 0x75, 0x9f, 0x21, + 0x10, 0x76, 0xa1, 0x4f, 0xb8, 0x4f, 0x44, 0x50, 0xfc, 0x3f, 0x08, 0x40, + 0xf4, 0x76, 0xf3, 0x50, 0xf2, 0x50, 0x19, 0x51, 0x33, 0x51, 0x1e, 0x77, + 0x1f, 0x77, 0x1f, 0x77, 0x4a, 0x77, 0x39, 0x40, 0x8b, 0x77, 0x46, 0x40, + 0x96, 0x40, 0x1d, 0x54, 0x4e, 0x78, 0x8c, 0x78, 0xcc, 0x78, 0xe3, 0x40, + 0x26, 0x56, 0x56, 0x79, 0x9a, 0x56, 0xc5, 0x56, 0x8f, 0x79, 0xeb, 0x79, + 0x2f, 0x41, 0x40, 0x7a, 0x4a, 0x7a, 0x4f, 0x7a, 0x7c, 0x59, 0xa7, 0x5a, + 0xa7, 0x5a, 0xee, 0x7a, 0x02, 0x42, 0xab, 0x5b, 0xc6, 0x7b, 0xc9, 0x7b, + 0x27, 0x42, 0x80, 0x5c, 0xd2, 0x7c, 0xa0, 0x42, 0xe8, 0x7c, 0xe3, 0x7c, + 0x00, 0x7d, 0x86, 0x5f, 0x63, 0x7d, 0x01, 0x43, 0xc7, 0x7d, 0x02, 0x7e, + 0x45, 0x7e, 0x34, 0x43, 0x28, 0x62, 0x47, 0x62, 0x59, 0x43, 0xd9, 0x62, + 0x7a, 0x7f, 0x3e, 0x63, 0x95, 0x7f, 0xfa, 0x7f, 0x05, 0x80, 0xda, 0x64, + 0x23, 0x65, 0x60, 0x80, 0xa8, 0x65, 0x70, 0x80, 0x5f, 0x33, 0xd5, 0x43, + 0xb2, 0x80, 0x03, 0x81, 0x0b, 0x44, 0x3e, 0x81, 0xb5, 0x5a, 0xa7, 0x67, + 0xb5, 0x67, 0x93, 0x33, 0x9c, 0x33, 0x01, 0x82, 0x04, 0x82, 0x9e, 0x8f, + 0x6b, 0x44, 0x91, 0x82, 0x8b, 0x82, 0x9d, 0x82, 0xb3, 0x52, 0xb1, 0x82, + 0xb3, 0x82, 0xbd, 0x82, 0xe6, 0x82, 0x3c, 0x6b, 0xe5, 0x82, 0x1d, 0x83, + 0x63, 0x83, 0xad, 0x83, 0x23, 0x83, 0xbd, 0x83, 0xe7, 0x83, 0x57, 0x84, + 0x53, 0x83, 0xca, 0x83, 0xcc, 0x83, 0xdc, 0x83, 0x36, 0x6c, 0x6b, 0x6d, + 0x02, 0x00, 0x00, 0x20, 0x22, 0x2a, 0xa0, 0x0a, 0x00, 0x20, 0x80, 0x28, + 0x00, 0xa8, 0x20, 0x20, 0x00, 0x02, 0x80, 0x22, 0x02, 0x8a, 0x08, 0x00, + 0xaa, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x28, 0xd5, 0x6c, 0x2b, 0x45, + 0xf1, 0x84, 0xf3, 0x84, 0x16, 0x85, 0xca, 0x73, 0x64, 0x85, 0x2c, 0x6f, + 0x5d, 0x45, 0x61, 0x45, 0xb1, 0x6f, 0xd2, 0x70, 0x6b, 0x45, 0x50, 0x86, + 0x5c, 0x86, 0x67, 0x86, 0x69, 0x86, 0xa9, 0x86, 0x88, 0x86, 0x0e, 0x87, + 0xe2, 0x86, 0x79, 0x87, 0x28, 0x87, 0x6b, 0x87, 0x86, 0x87, 0xd7, 0x45, + 0xe1, 0x87, 0x01, 0x88, 0xf9, 0x45, 0x60, 0x88, 0x63, 0x88, 0x67, 0x76, + 0xd7, 0x88, 0xde, 0x88, 0x35, 0x46, 0xfa, 0x88, 0xbb, 0x34, 0xae, 0x78, + 0x66, 0x79, 0xbe, 0x46, 0xc7, 0x46, 0xa0, 0x8a, 0xed, 0x8a, 0x8a, 0x8b, + 0x55, 0x8c, 0xa8, 0x7c, 0xab, 0x8c, 0xc1, 0x8c, 0x1b, 0x8d, 0x77, 0x8d, + 0x2f, 0x7f, 0x04, 0x08, 0xcb, 0x8d, 0xbc, 0x8d, 0xf0, 0x8d, 0xde, 0x08, + 0xd4, 0x8e, 0x38, 0x8f, 0xd2, 0x85, 0xed, 0x85, 0x94, 0x90, 0xf1, 0x90, + 0x11, 0x91, 0x2e, 0x87, 0x1b, 0x91, 0x38, 0x92, 0xd7, 0x92, 0xd8, 0x92, + 0x7c, 0x92, 0xf9, 0x93, 0x15, 0x94, 0xfa, 0x8b, 0x8b, 0x95, 0x95, 0x49, + 0xb7, 0x95, 0x77, 0x8d, 0xe6, 0x49, 0xc3, 0x96, 0xb2, 0x5d, 0x23, 0x97, + 0x45, 0x91, 0x1a, 0x92, 0x6e, 0x4a, 0x76, 0x4a, 0xe0, 0x97, 0x0a, 0x94, + 0xb2, 0x4a, 0x96, 0x94, 0x0b, 0x98, 0x0b, 0x98, 0x29, 0x98, 0xb6, 0x95, + 0xe2, 0x98, 0x33, 0x4b, 0x29, 0x99, 0xa7, 0x99, 0xc2, 0x99, 0xfe, 0x99, + 0xce, 0x4b, 0x30, 0x9b, 0x12, 0x9b, 0x40, 0x9c, 0xfd, 0x9c, 0xce, 0x4c, + 0xed, 0x4c, 0x67, 0x9d, 0xce, 0xa0, 0xf8, 0x4c, 0x05, 0xa1, 0x0e, 0xa2, + 0x91, 0xa2, 0xbb, 0x9e, 0x56, 0x4d, 0xf9, 0x9e, 0xfe, 0x9e, 0x05, 0x9f, + 0x0f, 0x9f, 0x16, 0x9f, 0x3b, 0x9f, 0x00, 0xa6, 0x02, 0x88, 0xa0, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x28, 0x00, 0x08, 0xa0, 0x80, 0xa0, 0x80, + 0x00, 0x80, 0x80, 0x00, 0x0a, 0x88, 0x80, 0x00, 0x80, 0x00, 0x20, 0x2a, + 0x00, 0x80, +}; + +static const uint16_t unicode_comp_table[944] = { + 0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0, 0x02c2, + 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982, 0x2440, 0x2280, + 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8, 0x02ca, 0x02cc, 0x0287, + 0x228a, 0x02ce, 0x228c, 0x2290, 0x2292, 0x228e, 0x0288, 0x0289, 0x028a, + 0x2482, 0x0300, 0x0302, 0x0304, 0x028b, 0x2480, 0x0308, 0x0984, 0x0986, + 0x2458, 0x0a02, 0x0306, 0x2298, 0x229a, 0x229e, 0x0900, 0x030a, 0x22a0, + 0x030c, 0x030e, 0x0840, 0x0310, 0x0312, 0x22a2, 0x22a6, 0x09c0, 0x22a4, + 0x22a8, 0x22aa, 0x028c, 0x028d, 0x028e, 0x0340, 0x0342, 0x0344, 0x0380, + 0x028f, 0x248e, 0x07c2, 0x0988, 0x098a, 0x2490, 0x0346, 0x22ac, 0x0400, + 0x22b0, 0x0842, 0x22b2, 0x0402, 0x22b4, 0x0440, 0x0444, 0x22b6, 0x0442, + 0x22c2, 0x22c0, 0x22c4, 0x22c6, 0x22c8, 0x0940, 0x04c0, 0x0291, 0x22ca, + 0x04c4, 0x22cc, 0x04c2, 0x22d0, 0x22ce, 0x0292, 0x0293, 0x0294, 0x0295, + 0x0540, 0x0542, 0x0a08, 0x0296, 0x2494, 0x0544, 0x07c4, 0x098c, 0x098e, + 0x06c0, 0x2492, 0x0844, 0x2308, 0x230a, 0x0580, 0x230c, 0x0584, 0x0990, + 0x0992, 0x230e, 0x0582, 0x2312, 0x0586, 0x0588, 0x2314, 0x058c, 0x2316, + 0x0998, 0x058a, 0x231e, 0x0590, 0x2320, 0x099a, 0x058e, 0x2324, 0x2322, + 0x0299, 0x029a, 0x029b, 0x05c0, 0x05c2, 0x05c4, 0x029c, 0x24ac, 0x05c6, + 0x05c8, 0x07c6, 0x0994, 0x0996, 0x0700, 0x24aa, 0x2326, 0x05ca, 0x232a, + 0x2328, 0x2340, 0x2342, 0x2344, 0x2346, 0x05cc, 0x234a, 0x2348, 0x234c, + 0x234e, 0x2350, 0x24b8, 0x029d, 0x05ce, 0x24be, 0x0a0c, 0x2352, 0x0600, + 0x24bc, 0x24ba, 0x0640, 0x2354, 0x0642, 0x0644, 0x2356, 0x2358, 0x02a0, + 0x02a1, 0x02a2, 0x02a3, 0x02c1, 0x02c3, 0x0a01, 0x02a4, 0x2443, 0x02a5, + 0x07c1, 0x0981, 0x0983, 0x2441, 0x2281, 0x02c5, 0x2283, 0x2285, 0x2287, + 0x02c7, 0x02c9, 0x02cb, 0x02cd, 0x02a7, 0x228b, 0x02cf, 0x228d, 0x2291, + 0x2293, 0x228f, 0x02a8, 0x02a9, 0x02aa, 0x2483, 0x0301, 0x0303, 0x0305, + 0x02ab, 0x2481, 0x0309, 0x0985, 0x0987, 0x2459, 0x0a03, 0x0307, 0x2299, + 0x229b, 0x229f, 0x0901, 0x030b, 0x22a1, 0x030d, 0x030f, 0x0841, 0x0311, + 0x0313, 0x22a3, 0x22a7, 0x09c1, 0x22a5, 0x22a9, 0x22ab, 0x2380, 0x02ac, + 0x02ad, 0x02ae, 0x0341, 0x0343, 0x0345, 0x02af, 0x248f, 0x07c3, 0x0989, + 0x098b, 0x2491, 0x0347, 0x22ad, 0x0401, 0x0884, 0x22b1, 0x0843, 0x22b3, + 0x0403, 0x22b5, 0x0441, 0x0445, 0x22b7, 0x0443, 0x22c3, 0x22c1, 0x22c5, + 0x22c7, 0x22c9, 0x0941, 0x04c1, 0x02b1, 0x22cb, 0x04c5, 0x22cd, 0x04c3, + 0x22d1, 0x22cf, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x0541, 0x0543, 0x0a09, + 0x02b6, 0x2495, 0x0545, 0x07c5, 0x098d, 0x098f, 0x06c1, 0x2493, 0x0845, + 0x2309, 0x230b, 0x0581, 0x230d, 0x0585, 0x0991, 0x0993, 0x230f, 0x0583, + 0x2313, 0x0587, 0x0589, 0x2315, 0x058d, 0x2317, 0x0999, 0x058b, 0x231f, + 0x2381, 0x0591, 0x2321, 0x099b, 0x058f, 0x2325, 0x2323, 0x02b9, 0x02ba, + 0x02bb, 0x05c1, 0x05c3, 0x05c5, 0x02bc, 0x24ad, 0x05c7, 0x05c9, 0x07c7, + 0x0995, 0x0997, 0x0701, 0x24ab, 0x2327, 0x05cb, 0x232b, 0x2329, 0x2341, + 0x2343, 0x2345, 0x2347, 0x05cd, 0x234b, 0x2349, 0x2382, 0x234d, 0x234f, + 0x2351, 0x24b9, 0x02bd, 0x05cf, 0x24bf, 0x0a0d, 0x2353, 0x02bf, 0x24bd, + 0x2383, 0x24bb, 0x0641, 0x2355, 0x0643, 0x0645, 0x2357, 0x2359, 0x3101, + 0x0c80, 0x2e00, 0x2446, 0x2444, 0x244a, 0x2448, 0x0800, 0x0942, 0x0944, + 0x0804, 0x2288, 0x2486, 0x2484, 0x248a, 0x2488, 0x22ae, 0x2498, 0x2496, + 0x249c, 0x249a, 0x2300, 0x0a06, 0x2302, 0x0a04, 0x0946, 0x07ce, 0x07ca, + 0x07c8, 0x07cc, 0x2447, 0x2445, 0x244b, 0x2449, 0x0801, 0x0943, 0x0945, + 0x0805, 0x2289, 0x2487, 0x2485, 0x248b, 0x2489, 0x22af, 0x2499, 0x2497, + 0x249d, 0x249b, 0x2301, 0x0a07, 0x2303, 0x0a05, 0x0947, 0x07cf, 0x07cb, + 0x07c9, 0x07cd, 0x2450, 0x244e, 0x2454, 0x2452, 0x2451, 0x244f, 0x2455, + 0x2453, 0x2294, 0x2296, 0x2295, 0x2297, 0x2304, 0x2306, 0x2305, 0x2307, + 0x2318, 0x2319, 0x231a, 0x231b, 0x232c, 0x232d, 0x232e, 0x232f, 0x2400, + 0x24a2, 0x24a0, 0x24a6, 0x24a4, 0x24a8, 0x24a3, 0x24a1, 0x24a7, 0x24a5, + 0x24a9, 0x24b0, 0x24ae, 0x24b4, 0x24b2, 0x24b6, 0x24b1, 0x24af, 0x24b5, + 0x24b3, 0x24b7, 0x0882, 0x0880, 0x0881, 0x0802, 0x0803, 0x229c, 0x229d, + 0x0a0a, 0x0a0b, 0x0883, 0x0b40, 0x2c8a, 0x0c81, 0x2c89, 0x2c88, 0x2540, + 0x2541, 0x2d00, 0x2e07, 0x0d00, 0x2640, 0x2641, 0x2e80, 0x0d01, 0x26c8, + 0x26c9, 0x2f00, 0x2f84, 0x0d02, 0x2f83, 0x2f82, 0x0d40, 0x26d8, 0x26d9, + 0x3186, 0x0d04, 0x2740, 0x2741, 0x3100, 0x3086, 0x0d06, 0x3085, 0x3084, + 0x0d41, 0x2840, 0x3200, 0x0d07, 0x284f, 0x2850, 0x3280, 0x2c84, 0x2e03, + 0x2857, 0x0d42, 0x2c81, 0x2c80, 0x24c0, 0x24c1, 0x2c86, 0x2c83, 0x28c0, + 0x0d43, 0x25c0, 0x25c1, 0x2940, 0x0d44, 0x26c0, 0x26c1, 0x2e05, 0x2e02, + 0x29c0, 0x0d45, 0x2f05, 0x2f04, 0x0d80, 0x26d0, 0x26d1, 0x2f80, 0x2a40, + 0x0d82, 0x26e0, 0x26e1, 0x3080, 0x3081, 0x2ac0, 0x0d83, 0x3004, 0x3003, + 0x0d81, 0x27c0, 0x27c1, 0x3082, 0x2b40, 0x0d84, 0x2847, 0x2848, 0x3184, + 0x3181, 0x2f06, 0x0d08, 0x2f81, 0x3005, 0x0d46, 0x3083, 0x3182, 0x0e00, + 0x0e01, 0x0f40, 0x1180, 0x1182, 0x0f03, 0x0f00, 0x11c0, 0x0f01, 0x1140, + 0x1202, 0x1204, 0x0f81, 0x1240, 0x0fc0, 0x1242, 0x0f80, 0x1244, 0x1284, + 0x0f82, 0x1286, 0x1288, 0x128a, 0x12c0, 0x1282, 0x1181, 0x1183, 0x1043, + 0x1040, 0x11c1, 0x1041, 0x1141, 0x1203, 0x1205, 0x10c1, 0x1241, 0x1000, + 0x1243, 0x10c0, 0x1245, 0x1285, 0x10c2, 0x1287, 0x1289, 0x128b, 0x12c1, + 0x1283, 0x1080, 0x1100, 0x1101, 0x1200, 0x1201, 0x1280, 0x1281, 0x1340, + 0x1341, 0x1343, 0x1342, 0x1344, 0x13c2, 0x1400, 0x13c0, 0x1440, 0x1480, + 0x14c0, 0x1540, 0x1541, 0x1740, 0x1700, 0x1741, 0x17c0, 0x1800, 0x1802, + 0x1801, 0x1840, 0x1880, 0x1900, 0x18c0, 0x18c1, 0x1901, 0x1940, 0x1942, + 0x1941, 0x1980, 0x19c0, 0x19c2, 0x19c1, 0x1c80, 0x1cc0, 0x1dc0, 0x1f80, + 0x2000, 0x2002, 0x2004, 0x2006, 0x2008, 0x2040, 0x2080, 0x2082, 0x20c0, + 0x20c1, 0x2100, 0x22b8, 0x22b9, 0x2310, 0x2311, 0x231c, 0x231d, 0x244c, + 0x2456, 0x244d, 0x2457, 0x248c, 0x248d, 0x249e, 0x249f, 0x2500, 0x2502, + 0x2504, 0x2bc0, 0x2501, 0x2503, 0x2505, 0x2bc1, 0x2bc2, 0x2bc3, 0x2bc4, + 0x2bc5, 0x2bc6, 0x2bc7, 0x2580, 0x2582, 0x2584, 0x2bc8, 0x2581, 0x2583, + 0x2585, 0x2bc9, 0x2bca, 0x2bcb, 0x2bcc, 0x2bcd, 0x2bce, 0x2bcf, 0x2600, + 0x2602, 0x2601, 0x2603, 0x2680, 0x2682, 0x2681, 0x2683, 0x26c2, 0x26c4, + 0x26c6, 0x2c00, 0x26c3, 0x26c5, 0x26c7, 0x2c01, 0x2c02, 0x2c03, 0x2c04, + 0x2c05, 0x2c06, 0x2c07, 0x26ca, 0x26cc, 0x26ce, 0x2c08, 0x26cb, 0x26cd, + 0x26cf, 0x2c09, 0x2c0a, 0x2c0b, 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x26d2, + 0x26d4, 0x26d6, 0x26d3, 0x26d5, 0x26d7, 0x26da, 0x26dc, 0x26de, 0x26db, + 0x26dd, 0x26df, 0x2700, 0x2702, 0x2701, 0x2703, 0x2780, 0x2782, 0x2781, + 0x2783, 0x2800, 0x2802, 0x2804, 0x2801, 0x2803, 0x2805, 0x2842, 0x2844, + 0x2846, 0x2849, 0x284b, 0x284d, 0x2c40, 0x284a, 0x284c, 0x284e, 0x2c41, + 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2851, 0x2853, 0x2855, + 0x2c48, 0x2852, 0x2854, 0x2856, 0x2c49, 0x2c4a, 0x2c4b, 0x2c4c, 0x2c4d, + 0x2c4e, 0x2c4f, 0x2c82, 0x2e01, 0x3180, 0x2c87, 0x2f01, 0x2f02, 0x2f03, + 0x2e06, 0x3185, 0x3000, 0x3001, 0x3002, 0x4640, 0x4641, 0x4680, 0x46c0, + 0x46c2, 0x46c1, 0x4700, 0x4740, 0x4780, 0x47c0, 0x47c2, 0x4900, 0x4940, + 0x4980, 0x4982, 0x4a00, 0x49c2, 0x4a03, 0x4a04, 0x4a40, 0x4a41, 0x4a80, + 0x4a81, 0x4ac0, 0x4ac1, 0x4bc0, 0x4bc1, 0x4b00, 0x4b01, 0x4b40, 0x4b41, + 0x4bc2, 0x4bc3, 0x4b80, 0x4b81, 0x4b82, 0x4b83, 0x4c00, 0x4c01, 0x4c02, + 0x4c03, 0x5600, 0x5440, 0x5442, 0x5444, 0x5446, 0x5448, 0x544a, 0x544c, + 0x544e, 0x5450, 0x5452, 0x5454, 0x5456, 0x5480, 0x5482, 0x5484, 0x54c0, + 0x54c1, 0x5500, 0x5501, 0x5540, 0x5541, 0x5580, 0x5581, 0x55c0, 0x55c1, + 0x5680, 0x58c0, 0x5700, 0x5702, 0x5704, 0x5706, 0x5708, 0x570a, 0x570c, + 0x570e, 0x5710, 0x5712, 0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, + 0x5781, 0x57c0, 0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, + 0x5900, 0x5901, 0x5902, 0x5903, 0x5940, 0x8e40, 0x8e42, 0x8e80, 0x8ec0, + 0x8ec1, 0x8f00, 0x8f01, 0x8f41, 0x8f40, 0x8f43, 0x8f80, 0x8f81, +}; + +typedef enum { + UNICODE_GC_Cn, + UNICODE_GC_Lu, + UNICODE_GC_Ll, + UNICODE_GC_Lt, + UNICODE_GC_Lm, + UNICODE_GC_Lo, + UNICODE_GC_Mn, + UNICODE_GC_Mc, + UNICODE_GC_Me, + UNICODE_GC_Nd, + UNICODE_GC_Nl, + UNICODE_GC_No, + UNICODE_GC_Sm, + UNICODE_GC_Sc, + UNICODE_GC_Sk, + UNICODE_GC_So, + UNICODE_GC_Pc, + UNICODE_GC_Pd, + UNICODE_GC_Ps, + UNICODE_GC_Pe, + UNICODE_GC_Pi, + UNICODE_GC_Pf, + UNICODE_GC_Po, + UNICODE_GC_Zs, + UNICODE_GC_Zl, + UNICODE_GC_Zp, + UNICODE_GC_Cc, + UNICODE_GC_Cf, + UNICODE_GC_Cs, + UNICODE_GC_Co, + UNICODE_GC_LC, + UNICODE_GC_L, + UNICODE_GC_M, + UNICODE_GC_N, + UNICODE_GC_S, + UNICODE_GC_P, + UNICODE_GC_Z, + UNICODE_GC_C, + UNICODE_GC_COUNT, +} UnicodeGCEnum; + +static const char unicode_gc_name_table[] = + "Cn,Unassigned" + "\0" + "Lu,Uppercase_Letter" + "\0" + "Ll,Lowercase_Letter" + "\0" + "Lt,Titlecase_Letter" + "\0" + "Lm,Modifier_Letter" + "\0" + "Lo,Other_Letter" + "\0" + "Mn,Nonspacing_Mark" + "\0" + "Mc,Spacing_Mark" + "\0" + "Me,Enclosing_Mark" + "\0" + "Nd,Decimal_Number,digit" + "\0" + "Nl,Letter_Number" + "\0" + "No,Other_Number" + "\0" + "Sm,Math_Symbol" + "\0" + "Sc,Currency_Symbol" + "\0" + "Sk,Modifier_Symbol" + "\0" + "So,Other_Symbol" + "\0" + "Pc,Connector_Punctuation" + "\0" + "Pd,Dash_Punctuation" + "\0" + "Ps,Open_Punctuation" + "\0" + "Pe,Close_Punctuation" + "\0" + "Pi,Initial_Punctuation" + "\0" + "Pf,Final_Punctuation" + "\0" + "Po,Other_Punctuation" + "\0" + "Zs,Space_Separator" + "\0" + "Zl,Line_Separator" + "\0" + "Zp,Paragraph_Separator" + "\0" + "Cc,Control,cntrl" + "\0" + "Cf,Format" + "\0" + "Cs,Surrogate" + "\0" + "Co,Private_Use" + "\0" + "LC,Cased_Letter" + "\0" + "L,Letter" + "\0" + "M,Mark,Combining_Mark" + "\0" + "N,Number" + "\0" + "S,Symbol" + "\0" + "P,Punctuation,punct" + "\0" + "Z,Separator" + "\0" + "C,Other" + "\0"; + +static const uint8_t unicode_gc_table[3719] = { + 0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16, 0x0c, 0x16, 0x11, + 0x36, 0xe9, 0x02, 0x36, 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, + 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, 0xfa, 0x19, 0x17, 0x16, + 0x6d, 0x0f, 0x16, 0x0e, 0x0f, 0x05, 0x14, 0x0c, 0x1b, 0x0f, 0x0e, 0x0f, + 0x0c, 0x2b, 0x0e, 0x02, 0x36, 0x0e, 0x0b, 0x05, 0x15, 0x4b, 0x16, 0xe1, + 0x0f, 0x0c, 0xc1, 0xe2, 0x10, 0x0c, 0xe2, 0x00, 0xff, 0x30, 0x02, 0xff, + 0x08, 0x02, 0xff, 0x27, 0xbf, 0x22, 0x21, 0x02, 0x5f, 0x5f, 0x21, 0x22, + 0x61, 0x02, 0x21, 0x02, 0x41, 0x42, 0x21, 0x02, 0x21, 0x02, 0x9f, 0x7f, + 0x02, 0x5f, 0x5f, 0x21, 0x02, 0x5f, 0x3f, 0x02, 0x05, 0x3f, 0x22, 0x65, + 0x01, 0x03, 0x02, 0x01, 0x03, 0x02, 0x01, 0x03, 0x02, 0xff, 0x08, 0x02, + 0xff, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x5f, 0x21, 0x02, 0xff, 0x32, 0xa2, + 0x21, 0x02, 0x21, 0x22, 0x5f, 0x41, 0x02, 0xff, 0x00, 0xe2, 0x3c, 0x05, + 0xe2, 0x13, 0xe4, 0x0a, 0x6e, 0xe4, 0x04, 0xee, 0x06, 0x84, 0xce, 0x04, + 0x0e, 0x04, 0xee, 0x09, 0xe6, 0x68, 0x7f, 0x04, 0x0e, 0x3f, 0x20, 0x04, + 0x42, 0x16, 0x01, 0x60, 0x2e, 0x01, 0x16, 0x41, 0x00, 0x01, 0x00, 0x21, + 0x02, 0xe1, 0x09, 0x00, 0xe1, 0x01, 0xe2, 0x1b, 0x3f, 0x02, 0x41, 0x42, + 0xff, 0x10, 0x62, 0x3f, 0x0c, 0x5f, 0x3f, 0x02, 0xe1, 0x2b, 0xe2, 0x28, + 0xff, 0x1a, 0x0f, 0x86, 0x28, 0xff, 0x2f, 0xff, 0x06, 0x02, 0xff, 0x58, + 0x00, 0xe1, 0x1e, 0x20, 0x04, 0xb6, 0xe2, 0x21, 0x16, 0x11, 0x20, 0x2f, + 0x0d, 0x00, 0xe6, 0x25, 0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06, + 0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0, 0x03, 0xbb, 0x4c, 0x36, + 0x0d, 0x36, 0x2f, 0xe6, 0x03, 0x16, 0x1b, 0x00, 0x36, 0xe5, 0x18, 0x04, + 0xe5, 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, 0x06, 0xe5, 0x5b, 0x16, + 0x05, 0xc6, 0x1b, 0x0f, 0xa6, 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, + 0x45, 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, 0x06, 0xe5, 0x16, 0xe6, + 0x13, 0x20, 0xe5, 0x51, 0xe6, 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, + 0x19, 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, 0x06, 0x2d, 0xe5, 0x0e, + 0x66, 0x04, 0xe6, 0x01, 0x04, 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, + 0xe5, 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, 0xe0, 0x2d, 0xe5, 0x0d, + 0x00, 0xe5, 0x00, 0xe0, 0x0d, 0xe6, 0x07, 0x1b, 0xe6, 0x18, 0x07, 0xe5, + 0x2e, 0x06, 0x07, 0x06, 0x05, 0x47, 0xe6, 0x00, 0x67, 0x06, 0x27, 0x05, + 0xc6, 0xe5, 0x02, 0x26, 0x36, 0xe9, 0x02, 0x16, 0x04, 0xe5, 0x07, 0x06, + 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, + 0x05, 0x40, 0x65, 0x20, 0x06, 0x05, 0x47, 0x66, 0x20, 0x27, 0x20, 0x27, + 0x06, 0x05, 0xe0, 0x00, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, + 0x02, 0x25, 0x2d, 0xab, 0x0f, 0x0d, 0x05, 0x16, 0x06, 0x20, 0x26, 0x07, + 0x00, 0xa5, 0x60, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, + 0x25, 0x00, 0x25, 0x20, 0x06, 0x00, 0x47, 0x26, 0x60, 0x26, 0x20, 0x46, + 0x40, 0x06, 0xc0, 0x65, 0x00, 0x05, 0xc0, 0xe9, 0x02, 0x26, 0x45, 0x06, + 0x16, 0xe0, 0x02, 0x26, 0x07, 0x00, 0xe5, 0x01, 0x00, 0x45, 0x00, 0xe5, + 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x85, 0x20, 0x06, 0x05, 0x47, 0x86, + 0x00, 0x26, 0x07, 0x00, 0x27, 0x06, 0x20, 0x05, 0xe0, 0x07, 0x25, 0x26, + 0x20, 0xe9, 0x02, 0x16, 0x0d, 0xc0, 0x05, 0xa6, 0x00, 0x06, 0x27, 0x00, + 0xe5, 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, + 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x07, 0x66, 0x20, 0x27, 0x20, 0x27, + 0x06, 0xe0, 0x00, 0x06, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, + 0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, 0x06, 0x05, 0x00, 0xa5, 0x40, 0x45, + 0x00, 0x65, 0x40, 0x25, 0x00, 0x05, 0x00, 0x25, 0x40, 0x25, 0x40, 0x45, + 0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, 0x27, 0x40, 0x47, 0x00, 0x47, 0x06, + 0x20, 0x05, 0xa0, 0x07, 0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, 0x0d, 0x0f, + 0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, + 0xe5, 0x08, 0x40, 0x05, 0x46, 0x67, 0x00, 0x46, 0x00, 0x66, 0xc0, 0x26, + 0x00, 0x45, 0x80, 0x25, 0x26, 0x20, 0xe9, 0x02, 0xc0, 0x16, 0xcb, 0x0f, + 0x05, 0x06, 0x27, 0x16, 0xe5, 0x00, 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, + 0xe5, 0x02, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x87, 0x00, 0x06, + 0x27, 0x00, 0x27, 0x26, 0xc0, 0x27, 0xc0, 0x05, 0x00, 0x25, 0x26, 0x20, + 0xe9, 0x02, 0x00, 0x25, 0xe0, 0x05, 0x26, 0x27, 0x00, 0xe5, 0x00, 0x00, + 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, 0x47, 0x66, 0x00, 0x47, 0x00, 0x47, + 0x06, 0x05, 0x0f, 0x60, 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, 0xe9, 0x02, + 0xeb, 0x01, 0x0f, 0xa5, 0x20, 0x27, 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10, + 0x00, 0xe5, 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, 0x60, 0x47, 0x46, + 0x00, 0x06, 0x00, 0xe7, 0x00, 0xa0, 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0, + 0x04, 0xe5, 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, 0x04, 0xe6, 0x00, + 0x16, 0xe9, 0x02, 0x36, 0xe0, 0x1d, 0x25, 0x00, 0x05, 0x00, 0x85, 0x00, + 0xe5, 0x10, 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, 0xe6, 0x01, 0x05, + 0x20, 0x85, 0x00, 0x04, 0x00, 0xa6, 0x20, 0xe9, 0x02, 0x20, 0x65, 0xe0, + 0x18, 0x05, 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, 0xaf, 0xe9, 0x02, + 0xeb, 0x02, 0x0f, 0x06, 0x0f, 0x06, 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13, + 0x27, 0xe5, 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, 0x07, 0x86, 0x16, + 0x26, 0x85, 0xe6, 0x03, 0x00, 0xe6, 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf, + 0x00, 0x2f, 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, 0x27, 0x66, 0x07, + 0xa6, 0x07, 0x26, 0x27, 0x26, 0x05, 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26, + 0x65, 0x46, 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, 0x05, 0x06, 0x27, + 0x26, 0xa7, 0x06, 0x05, 0x07, 0xe9, 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e, + 0x00, 0x01, 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, 0x42, 0xe5, 0x80, + 0xc1, 0x00, 0x65, 0x20, 0xc5, 0x00, 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21, + 0x00, 0x65, 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, 0x00, 0x05, 0x00, + 0x65, 0x20, 0xe5, 0x07, 0x00, 0xe5, 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b, + 0x20, 0x46, 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, 0xef, 0x02, 0xa0, + 0xe1, 0x4e, 0x20, 0xa2, 0x20, 0x11, 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5, + 0x09, 0x17, 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, 0x56, 0x4a, 0xe5, + 0x00, 0xc0, 0xe5, 0x05, 0x00, 0x65, 0x46, 0xe0, 0x03, 0xe5, 0x0a, 0x46, + 0x36, 0xe0, 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, 0x05, 0x00, 0x45, + 0x00, 0x26, 0xe0, 0x04, 0xe5, 0x2c, 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06, + 0x27, 0xe6, 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, 0x20, 0xe9, 0x02, + 0xa0, 0xeb, 0x02, 0xa0, 0xb6, 0x11, 0x76, 0x46, 0x1b, 0x00, 0xe9, 0x02, + 0xa0, 0xe5, 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, 0xe5, 0x1a, 0x06, + 0x05, 0x80, 0xe5, 0x3e, 0xe0, 0x02, 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26, + 0x47, 0x60, 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, 0x36, 0xe9, 0x02, + 0xe5, 0x16, 0x20, 0x85, 0xe0, 0x03, 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0, + 0xe9, 0x02, 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, 0x27, 0x06, 0x20, + 0x36, 0xe5, 0x2d, 0x07, 0x06, 0x07, 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27, + 0xe6, 0x00, 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, 0xa0, 0xe9, 0x02, + 0xa0, 0xd6, 0x04, 0xb6, 0x20, 0xe6, 0x06, 0x08, 0xe0, 0x39, 0x66, 0x07, + 0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, 0x06, 0x87, 0x06, 0x27, 0xc5, 0x60, + 0xe9, 0x02, 0xd6, 0xef, 0x02, 0xe6, 0x01, 0xef, 0x01, 0x40, 0x26, 0x07, + 0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, 0x07, 0x46, 0x25, 0xe9, 0x02, 0xe5, + 0x24, 0x06, 0x07, 0x26, 0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, 0x00, 0x76, + 0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, 0x27, 0x26, 0x40, 0x96, 0xe9, 0x02, + 0x40, 0x45, 0xe9, 0x02, 0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, 0xc0, 0xe1, + 0x23, 0x20, 0x41, 0xf6, 0x00, 0xe0, 0x00, 0x46, 0x16, 0xe6, 0x05, 0x07, + 0xc6, 0x65, 0x06, 0xa5, 0x06, 0x25, 0x07, 0x26, 0x05, 0x80, 0xe2, 0x24, + 0xe4, 0x37, 0xe2, 0x05, 0x04, 0xe2, 0x1a, 0xe4, 0x1d, 0xe6, 0x32, 0x00, + 0x86, 0xff, 0x80, 0x0e, 0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, 0x00, + 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, 0x00, 0xe2, 0x00, 0xe1, 0x00, + 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, 0x20, 0xe2, 0x00, 0xe3, 0x00, + 0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, 0x61, + 0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, 0x61, 0x03, 0x4e, 0x62, 0x20, + 0x22, 0x61, 0x00, 0x4e, 0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, 0x22, + 0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, 0xb1, 0x36, 0x14, 0x15, 0x12, + 0x34, 0x15, 0x12, 0x14, 0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, 0x01, + 0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, 0x13, 0xf6, 0x03, 0x0c, 0x16, + 0x10, 0xf6, 0x02, 0x17, 0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, 0xab, + 0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, 0x12, 0x13, 0x00, 0xe4, 0x05, + 0x40, 0xed, 0x18, 0xe0, 0x08, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, 0x04, + 0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, 0x02, 0x41, 0x22, 0x41, 0x02, + 0x0f, 0x01, 0x2f, 0x0c, 0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, 0x2f, 0x22, 0x21, 0x8c, 0x3f, + 0x42, 0x0f, 0x0c, 0x2f, 0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, 0x6a, + 0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, 0x0c, 0x2f, 0x0c, 0x2f, 0x0c, + 0xcf, 0x0c, 0xef, 0x17, 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, 0xec, + 0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, 0x13, 0xef, 0x0c, 0x2c, 0xcf, + 0x12, 0x13, 0xef, 0x49, 0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, 0xac, + 0xef, 0x3d, 0xe0, 0x11, 0xef, 0x03, 0xe0, 0x0d, 0xeb, 0x34, 0xef, 0x46, + 0xeb, 0x0e, 0xef, 0x80, 0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, 0xec, + 0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, 0xef, + 0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, 0x12, + 0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, 0xec, 0x80, 0x7a, 0xef, 0x28, + 0xec, 0x0d, 0x2f, 0xac, 0xef, 0x1f, 0x20, 0xef, 0x18, 0x20, 0xef, 0x60, + 0xe1, 0x27, 0x00, 0xe2, 0x27, 0x00, 0x5f, 0x21, 0x22, 0xdf, 0x41, 0x02, + 0x3f, 0x02, 0x3f, 0x82, 0x24, 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, + 0x46, 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, 0x00, 0x02, 0x80, 0x02, + 0x20, 0xe5, 0x30, 0xc0, 0x04, 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, + 0x01, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, + 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, + 0x56, 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, 0x11, 0x36, 0x11, 0x16, + 0x14, 0x15, 0x36, 0x14, 0x15, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, 0x16, 0x12, 0xf6, 0x05, + 0xe0, 0x28, 0xef, 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, 0x80, 0x4e, + 0xe0, 0x12, 0xef, 0x04, 0x60, 0x17, 0x56, 0x0f, 0x04, 0x05, 0x0a, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, 0x12, 0x33, 0x0f, 0xea, 0x01, + 0x66, 0x27, 0x11, 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, 0xe5, + 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, 0xe5, 0x52, 0x16, 0x44, 0x05, + 0x80, 0xe5, 0x23, 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, 0xe5, + 0x13, 0x80, 0xef, 0x1c, 0xe0, 0x04, 0xe5, 0x08, 0xef, 0x17, 0x00, 0xeb, + 0x02, 0xef, 0x16, 0xeb, 0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, 0x02, + 0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, 0xe5, 0x99, 0x2e, 0xe0, 0x02, + 0xef, 0x38, 0xe5, 0xc0, 0x11, 0x68, 0xe0, 0x08, 0xe5, 0x0d, 0x04, 0xe5, + 0x83, 0xef, 0x40, 0xef, 0x2f, 0xe0, 0x01, 0xe5, 0x20, 0xa4, 0x36, 0xe5, + 0x80, 0x84, 0x04, 0x56, 0xe5, 0x08, 0xe9, 0x02, 0x25, 0xe0, 0x0c, 0xff, + 0x26, 0x05, 0x06, 0x48, 0x16, 0xe6, 0x02, 0x16, 0x04, 0xff, 0x14, 0x24, + 0x26, 0xe5, 0x3e, 0xea, 0x02, 0x26, 0xb6, 0xe0, 0x00, 0xee, 0x0f, 0xe4, + 0x01, 0x2e, 0xff, 0x06, 0x22, 0xff, 0x36, 0x04, 0xe2, 0x00, 0x9f, 0xff, + 0x02, 0x04, 0x2e, 0x7f, 0x05, 0x7f, 0x22, 0xff, 0x0d, 0x61, 0x02, 0x81, + 0x02, 0xff, 0x02, 0x20, 0x5f, 0x21, 0xe0, 0x28, 0x05, 0x24, 0x02, 0xc5, + 0x06, 0x45, 0x06, 0x65, 0x06, 0xe5, 0x0f, 0x27, 0x26, 0x07, 0x6f, 0x60, + 0xab, 0x2f, 0x0d, 0x0f, 0xa0, 0xe5, 0x2c, 0x76, 0xe0, 0x00, 0x27, 0xe5, + 0x2a, 0xe7, 0x08, 0x26, 0xe0, 0x00, 0x36, 0xe9, 0x02, 0xa0, 0xe6, 0x0a, + 0xa5, 0x56, 0x05, 0x16, 0x25, 0x06, 0xe9, 0x02, 0xe5, 0x14, 0xe6, 0x00, + 0x36, 0xe5, 0x0f, 0xe6, 0x03, 0x27, 0xe0, 0x03, 0x16, 0xe5, 0x15, 0x40, + 0x46, 0x07, 0xe5, 0x27, 0x06, 0x27, 0x66, 0x27, 0x26, 0x47, 0xf6, 0x05, + 0x00, 0x04, 0xe9, 0x02, 0x60, 0x36, 0x85, 0x06, 0x04, 0xe5, 0x01, 0xe9, + 0x02, 0x85, 0x00, 0xe5, 0x21, 0xa6, 0x27, 0x26, 0x27, 0x26, 0xe0, 0x01, + 0x45, 0x06, 0xe5, 0x00, 0x06, 0x07, 0x20, 0xe9, 0x02, 0x20, 0x76, 0xe5, + 0x08, 0x04, 0xa5, 0x4f, 0x05, 0x07, 0x06, 0x07, 0xe5, 0x2a, 0x06, 0x05, + 0x46, 0x25, 0x26, 0x85, 0x26, 0x05, 0x06, 0x05, 0xe0, 0x10, 0x25, 0x04, + 0x36, 0xe5, 0x03, 0x07, 0x26, 0x27, 0x36, 0x05, 0x24, 0x07, 0x06, 0xe0, + 0x02, 0xa5, 0x20, 0xa5, 0x20, 0xa5, 0xe0, 0x01, 0xc5, 0x00, 0xc5, 0x00, + 0xe2, 0x23, 0x0e, 0x64, 0xe2, 0x00, 0xe0, 0x00, 0xe2, 0x48, 0xe5, 0x1b, + 0x27, 0x06, 0x27, 0x06, 0x27, 0x16, 0x07, 0x06, 0x20, 0xe9, 0x02, 0xa0, + 0xe5, 0xab, 0x1c, 0xe0, 0x04, 0xe5, 0x0f, 0x60, 0xe5, 0x29, 0x60, 0xfc, + 0x87, 0x78, 0xfd, 0x98, 0x78, 0xe5, 0x80, 0xe6, 0x20, 0xe5, 0x62, 0xe0, + 0x1e, 0xc2, 0xe0, 0x04, 0x82, 0x80, 0x05, 0x06, 0xe5, 0x02, 0x0c, 0xe5, + 0x05, 0x00, 0x85, 0x00, 0x05, 0x00, 0x25, 0x00, 0x25, 0x00, 0xe5, 0x64, + 0xee, 0x08, 0xe0, 0x09, 0xe5, 0x80, 0xe3, 0x13, 0x12, 0xe0, 0x08, 0xe5, + 0x38, 0x20, 0xe5, 0x2e, 0xe0, 0x20, 0xe5, 0x04, 0x0d, 0x0f, 0x20, 0xe6, + 0x08, 0xd6, 0x12, 0x13, 0x16, 0xa0, 0xe6, 0x08, 0x16, 0x31, 0x30, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x36, 0x12, 0x13, 0x76, 0x50, 0x56, 0x00, 0x76, 0x11, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x56, 0x0c, 0x11, 0x4c, 0x00, 0x16, + 0x0d, 0x36, 0x60, 0x85, 0x00, 0xe5, 0x7f, 0x20, 0x1b, 0x00, 0x56, 0x0d, + 0x56, 0x12, 0x13, 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c, + 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, 0x10, 0x0e, 0xe2, 0x12, 0x12, + 0x0c, 0x13, 0x0c, 0x12, 0x13, 0x16, 0x12, 0x13, 0x36, 0xe5, 0x02, 0x04, + 0xe5, 0x25, 0x24, 0xe5, 0x17, 0x40, 0xa5, 0x20, 0xa5, 0x20, 0xa5, 0x20, + 0x45, 0x40, 0x2d, 0x0c, 0x0e, 0x0f, 0x2d, 0x00, 0x0f, 0x6c, 0x2f, 0xe0, + 0x02, 0x5b, 0x2f, 0x20, 0xe5, 0x04, 0x00, 0xe5, 0x12, 0x00, 0xe5, 0x0b, + 0x00, 0x25, 0x00, 0xe5, 0x07, 0x20, 0xe5, 0x06, 0xe0, 0x1a, 0xe5, 0x73, + 0x80, 0x56, 0x60, 0xeb, 0x25, 0x40, 0xef, 0x01, 0xea, 0x2d, 0x6b, 0xef, + 0x09, 0x2b, 0x4f, 0x00, 0xef, 0x04, 0x60, 0x0f, 0xe0, 0x27, 0xef, 0x25, + 0x06, 0xe0, 0x7a, 0xe5, 0x15, 0x40, 0xe5, 0x29, 0xe0, 0x07, 0x06, 0xeb, + 0x13, 0x60, 0xe5, 0x18, 0x6b, 0xe0, 0x01, 0xe5, 0x0c, 0x0a, 0xe5, 0x00, + 0x0a, 0x80, 0xe5, 0x1e, 0x86, 0x80, 0xe5, 0x16, 0x00, 0x16, 0xe5, 0x1c, + 0x60, 0xe5, 0x00, 0x16, 0x8a, 0xe0, 0x22, 0xe1, 0x20, 0xe2, 0x20, 0xe5, + 0x46, 0x20, 0xe9, 0x02, 0xa0, 0xe1, 0x1c, 0x60, 0xe2, 0x1c, 0x60, 0xe5, + 0x20, 0xe0, 0x00, 0xe5, 0x2c, 0xe0, 0x03, 0x16, 0xe0, 0x80, 0x08, 0xe5, + 0x80, 0xaf, 0xe0, 0x01, 0xe5, 0x0e, 0xe0, 0x02, 0xe5, 0x00, 0xe0, 0x80, + 0x10, 0xa5, 0x20, 0x05, 0x00, 0xe5, 0x24, 0x00, 0x25, 0x40, 0x05, 0x20, + 0xe5, 0x0f, 0x00, 0x16, 0xeb, 0x00, 0xe5, 0x0f, 0x2f, 0xcb, 0xe5, 0x17, + 0xe0, 0x00, 0xeb, 0x01, 0xe0, 0x28, 0xe5, 0x0b, 0x00, 0x25, 0x80, 0x8b, + 0xe5, 0x0e, 0xab, 0x40, 0x16, 0xe5, 0x12, 0x80, 0x16, 0xe0, 0x38, 0xe5, + 0x30, 0x60, 0x2b, 0x25, 0xeb, 0x08, 0x20, 0xeb, 0x26, 0x05, 0x46, 0x00, + 0x26, 0x80, 0x66, 0x65, 0x00, 0x45, 0x00, 0xe5, 0x15, 0x20, 0x46, 0x60, + 0x06, 0xeb, 0x01, 0xc0, 0xf6, 0x01, 0xc0, 0xe5, 0x15, 0x2b, 0x16, 0xe5, + 0x15, 0x4b, 0xe0, 0x18, 0xe5, 0x00, 0x0f, 0xe5, 0x14, 0x26, 0x60, 0x8b, + 0xd6, 0xe0, 0x01, 0xe5, 0x2e, 0x40, 0xd6, 0xe5, 0x0e, 0x20, 0xeb, 0x00, + 0xe5, 0x0b, 0x80, 0xeb, 0x00, 0xe5, 0x0a, 0xc0, 0x76, 0xe0, 0x04, 0xcb, + 0xe0, 0x48, 0xe5, 0x41, 0xe0, 0x2f, 0xe1, 0x2b, 0xe0, 0x05, 0xe2, 0x2b, + 0xc0, 0xab, 0xe5, 0x1c, 0x66, 0xe0, 0x00, 0xe9, 0x02, 0xe0, 0x80, 0x9e, + 0xeb, 0x17, 0xe0, 0x79, 0xe5, 0x15, 0xeb, 0x02, 0x05, 0xe0, 0x00, 0xe5, + 0x0e, 0xe6, 0x03, 0x6b, 0x96, 0xe0, 0x7e, 0xe5, 0x0f, 0xe0, 0x01, 0x07, + 0x06, 0x07, 0xe5, 0x2d, 0xe6, 0x07, 0xd6, 0x60, 0xeb, 0x0c, 0xe9, 0x02, + 0xe0, 0x07, 0x46, 0x07, 0xe5, 0x25, 0x47, 0x66, 0x27, 0x26, 0x36, 0x1b, + 0x76, 0xe0, 0x03, 0x1b, 0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, 0xa0, 0x46, + 0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, 0x00, 0xe9, 0x02, 0x76, 0x05, 0x27, + 0xe0, 0x01, 0xe5, 0x1b, 0x06, 0x36, 0x05, 0xe0, 0x01, 0x26, 0x07, 0xe5, + 0x28, 0x47, 0xe6, 0x01, 0x27, 0x65, 0x76, 0x66, 0x16, 0x20, 0xe9, 0x02, + 0x05, 0x16, 0x05, 0x56, 0x00, 0xeb, 0x0c, 0xe0, 0x03, 0xe5, 0x0a, 0x00, + 0xe5, 0x11, 0x47, 0x46, 0x27, 0x06, 0x07, 0x26, 0xb6, 0x06, 0xe0, 0x39, + 0xc5, 0x00, 0x05, 0x00, 0x65, 0x00, 0xe5, 0x07, 0x00, 0xe5, 0x02, 0x16, + 0xa0, 0xe5, 0x27, 0x06, 0x47, 0xe6, 0x00, 0x80, 0xe9, 0x02, 0xa0, 0x26, + 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, + 0x25, 0x00, 0x85, 0x00, 0x26, 0x05, 0x27, 0x06, 0x67, 0x20, 0x27, 0x20, + 0x47, 0x20, 0x05, 0xa0, 0x07, 0x80, 0x85, 0x27, 0x20, 0xc6, 0x40, 0x86, + 0xe0, 0x80, 0x03, 0xe5, 0x2d, 0x47, 0xe6, 0x00, 0x27, 0x46, 0x07, 0x06, + 0x65, 0x96, 0xe9, 0x02, 0x00, 0x16, 0x00, 0x16, 0x06, 0x05, 0xe0, 0x18, + 0xe5, 0x28, 0x47, 0xa6, 0x07, 0x06, 0x67, 0x26, 0x07, 0x26, 0x25, 0x16, + 0x05, 0xe0, 0x00, 0xe9, 0x02, 0xe0, 0x80, 0x1e, 0xe5, 0x27, 0x47, 0x66, + 0x20, 0x67, 0x26, 0x07, 0x26, 0xf6, 0x0f, 0x65, 0x26, 0xe0, 0x1a, 0xe5, + 0x28, 0x47, 0xe6, 0x00, 0x27, 0x06, 0x07, 0x26, 0x56, 0x05, 0xe0, 0x03, + 0xe9, 0x02, 0xa0, 0xf6, 0x05, 0xe0, 0x0b, 0xe5, 0x23, 0x06, 0x07, 0x06, + 0x27, 0xa6, 0x07, 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, 0x2e, 0xe5, 0x13, + 0x20, 0x46, 0x27, 0x66, 0x07, 0x86, 0x60, 0xe9, 0x02, 0x2b, 0x56, 0x0f, + 0xe0, 0x80, 0x38, 0xe5, 0x24, 0x47, 0xe6, 0x01, 0x07, 0x26, 0x16, 0xe0, + 0x5c, 0xe1, 0x18, 0xe2, 0x18, 0xe9, 0x02, 0xeb, 0x01, 0xe0, 0x04, 0x05, + 0xe0, 0x80, 0x18, 0xe5, 0x00, 0x20, 0xe5, 0x1f, 0x47, 0x66, 0x20, 0x26, + 0x67, 0x06, 0x05, 0x16, 0x05, 0x07, 0xe0, 0x13, 0x05, 0xe6, 0x02, 0xe5, + 0x20, 0xa6, 0x07, 0x05, 0x66, 0xf6, 0x00, 0x06, 0xe0, 0x00, 0x05, 0xa6, + 0x27, 0x46, 0xe5, 0x26, 0xe6, 0x05, 0x07, 0x26, 0x56, 0x05, 0x96, 0xe0, + 0x15, 0xe5, 0x31, 0xe0, 0x80, 0x7f, 0xe5, 0x01, 0x00, 0xe5, 0x1d, 0x07, + 0xc6, 0x00, 0xa6, 0x07, 0x06, 0x05, 0x96, 0xe0, 0x02, 0xe9, 0x02, 0xeb, + 0x0b, 0x40, 0x36, 0xe5, 0x16, 0x20, 0xe6, 0x0e, 0x00, 0x07, 0xc6, 0x07, + 0x26, 0x07, 0x26, 0xe0, 0x41, 0xc5, 0x00, 0x25, 0x00, 0xe5, 0x1e, 0xa6, + 0x40, 0x06, 0x00, 0x26, 0x00, 0xc6, 0x05, 0x06, 0xe0, 0x00, 0xe9, 0x02, + 0xa0, 0xa5, 0x00, 0x25, 0x00, 0xe5, 0x18, 0x87, 0x00, 0x26, 0x00, 0x27, + 0x06, 0x07, 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, 0x80, 0xae, 0xe5, 0x0b, + 0x26, 0x27, 0x36, 0xe0, 0x80, 0x3f, 0xeb, 0x0d, 0xef, 0x00, 0x6d, 0xef, + 0x09, 0xe0, 0x05, 0x16, 0xe5, 0x83, 0x12, 0xe0, 0x5e, 0xea, 0x67, 0x00, + 0x96, 0xe0, 0x03, 0xe5, 0x80, 0x3c, 0xe0, 0x8a, 0x34, 0xe5, 0x83, 0xa7, + 0x00, 0xfb, 0x01, 0xe0, 0x8f, 0x3f, 0xe5, 0x81, 0xbf, 0xe0, 0xa1, 0x31, + 0xe5, 0x81, 0xb1, 0xc0, 0xe5, 0x17, 0x00, 0xe9, 0x02, 0x60, 0x36, 0xe0, + 0x58, 0xe5, 0x16, 0x20, 0x86, 0x16, 0xe0, 0x02, 0xe5, 0x28, 0xc6, 0x96, + 0x6f, 0x64, 0x16, 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, 0xcb, 0x00, 0xe5, + 0x0d, 0x80, 0xe5, 0x0b, 0xe0, 0x82, 0x28, 0xe1, 0x18, 0xe2, 0x18, 0xeb, + 0x0f, 0x76, 0xe0, 0x5d, 0xe5, 0x43, 0x60, 0x06, 0x05, 0xe7, 0x2f, 0xc0, + 0x66, 0xe4, 0x05, 0xe0, 0x38, 0x24, 0x16, 0x04, 0xe0, 0x14, 0xe5, 0x97, + 0x70, 0xe0, 0x00, 0xe5, 0x82, 0x6b, 0xe0, 0xa4, 0x85, 0xe5, 0x80, 0x97, + 0xe0, 0x29, 0x45, 0xe0, 0x09, 0x65, 0xe0, 0x00, 0xe5, 0x81, 0x04, 0xe0, + 0x88, 0x7c, 0xe5, 0x63, 0x80, 0xe5, 0x05, 0x40, 0xe5, 0x01, 0xc0, 0xe5, + 0x02, 0x20, 0x0f, 0x26, 0x16, 0x7b, 0xe0, 0x92, 0xd4, 0xef, 0x80, 0x6e, + 0xe0, 0x02, 0xef, 0x1f, 0x20, 0xef, 0x34, 0x27, 0x46, 0x4f, 0xa7, 0xfb, + 0x00, 0xe6, 0x00, 0x2f, 0xc6, 0xef, 0x16, 0x66, 0xef, 0x33, 0xe0, 0x0f, + 0xef, 0x3a, 0x46, 0x0f, 0xe0, 0x80, 0x12, 0xeb, 0x0c, 0xe0, 0x04, 0xef, + 0x4f, 0xe0, 0x01, 0xeb, 0x11, 0xe0, 0x7f, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xc2, 0x00, 0xe2, 0x0a, 0xe1, 0x12, 0xe2, 0x12, 0x01, 0x00, 0x21, + 0x20, 0x01, 0x20, 0x21, 0x20, 0x61, 0x00, 0xe1, 0x00, 0x62, 0x00, 0x02, + 0x00, 0xc2, 0x00, 0xe2, 0x03, 0xe1, 0x12, 0xe2, 0x12, 0x21, 0x00, 0x61, + 0x20, 0xe1, 0x00, 0x00, 0xc1, 0x00, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x00, + 0x81, 0x00, 0x01, 0x40, 0xc1, 0x00, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, + 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, + 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x14, 0x20, 0xe1, 0x11, 0x0c, + 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, + 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, + 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0x3f, 0x20, 0xe9, 0x2a, + 0xef, 0x81, 0x78, 0xe6, 0x2f, 0x6f, 0xe6, 0x2a, 0xef, 0x00, 0x06, 0xef, + 0x06, 0x06, 0x2f, 0x96, 0xe0, 0x07, 0x86, 0x00, 0xe6, 0x07, 0xe0, 0x84, + 0xc8, 0xc6, 0x00, 0xe6, 0x09, 0x20, 0xc6, 0x00, 0x26, 0x00, 0x86, 0xe0, + 0x80, 0x4d, 0xe5, 0x25, 0x40, 0xc6, 0xc4, 0x20, 0xe9, 0x02, 0x60, 0x05, + 0x0f, 0xe0, 0x80, 0xe8, 0xe5, 0x24, 0x66, 0xe9, 0x02, 0x80, 0x0d, 0xe0, + 0x84, 0x78, 0xe5, 0x80, 0x3d, 0x20, 0xeb, 0x01, 0xc6, 0xe0, 0x21, 0xe1, + 0x1a, 0xe2, 0x1a, 0xc6, 0x04, 0x60, 0xe9, 0x02, 0x60, 0x36, 0xe0, 0x82, + 0x89, 0xeb, 0x33, 0x0f, 0x4b, 0x0d, 0x6b, 0xe0, 0x44, 0xeb, 0x25, 0x0f, + 0xeb, 0x07, 0xe0, 0x80, 0x3a, 0x65, 0x00, 0xe5, 0x13, 0x00, 0x25, 0x00, + 0x05, 0x20, 0x05, 0x00, 0xe5, 0x02, 0x00, 0x65, 0x00, 0x05, 0x00, 0x05, + 0xa0, 0x05, 0x60, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x45, 0x00, 0x25, + 0x00, 0x05, 0x20, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x25, 0x00, 0x05, 0x20, 0x65, 0x00, 0xc5, 0x00, 0x65, 0x00, 0x65, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x00, 0xe5, 0x09, 0x80, 0x45, 0x00, 0x85, + 0x00, 0xe5, 0x09, 0xe0, 0x2c, 0x2c, 0xe0, 0x80, 0x86, 0xef, 0x24, 0x60, + 0xef, 0x5c, 0xe0, 0x04, 0xef, 0x07, 0x20, 0xef, 0x07, 0x00, 0xef, 0x07, + 0x00, 0xef, 0x1d, 0xe0, 0x02, 0xeb, 0x05, 0x40, 0xef, 0x55, 0x40, 0xef, + 0x35, 0xe0, 0x31, 0xef, 0x15, 0xe0, 0x05, 0xef, 0x24, 0x60, 0xef, 0x01, + 0xc0, 0x2f, 0xe0, 0x06, 0xaf, 0xe0, 0x80, 0x12, 0xef, 0x80, 0x73, 0x8e, + 0xef, 0x82, 0x4e, 0xe0, 0x02, 0xef, 0x05, 0x40, 0xef, 0x03, 0x80, 0xef, + 0x6c, 0xe0, 0x04, 0xef, 0x51, 0xc0, 0xef, 0x04, 0xe0, 0x0c, 0xef, 0x04, + 0x60, 0xef, 0x30, 0xe0, 0x00, 0xef, 0x02, 0xa0, 0xef, 0x20, 0xe0, 0x00, + 0xef, 0x16, 0xe0, 0x4a, 0xef, 0x04, 0x00, 0xef, 0x5d, 0x00, 0x6f, 0x40, + 0xef, 0x21, 0x20, 0xaf, 0x40, 0xef, 0x15, 0x20, 0xef, 0x7f, 0xe0, 0x04, + 0xef, 0x06, 0x20, 0x6f, 0x60, 0x4f, 0x80, 0x4f, 0xe0, 0x05, 0xaf, 0xe0, + 0x84, 0xe2, 0xe5, 0xc0, 0x66, 0x4f, 0xe0, 0x21, 0xe5, 0x8f, 0xad, 0xe0, + 0x03, 0xe5, 0x80, 0x56, 0x20, 0xe5, 0x95, 0xfa, 0xe0, 0x06, 0xe5, 0x9c, + 0xa9, 0xe0, 0x8b, 0x97, 0xe5, 0x81, 0x96, 0xe0, 0xca, 0xc5, 0x5b, 0x1b, + 0xe0, 0x16, 0xfb, 0x58, 0xe0, 0x78, 0xe6, 0x80, 0x68, 0xe0, 0xc0, 0xbd, + 0x88, 0xfd, 0xc0, 0xbf, 0x76, 0x20, 0xfd, 0xc0, 0xbf, 0x76, 0x20, +}; + +typedef enum { + UNICODE_SCRIPT_Unknown, + UNICODE_SCRIPT_Adlam, + UNICODE_SCRIPT_Ahom, + UNICODE_SCRIPT_Anatolian_Hieroglyphs, + UNICODE_SCRIPT_Arabic, + UNICODE_SCRIPT_Armenian, + UNICODE_SCRIPT_Avestan, + UNICODE_SCRIPT_Balinese, + UNICODE_SCRIPT_Bamum, + UNICODE_SCRIPT_Bassa_Vah, + UNICODE_SCRIPT_Batak, + UNICODE_SCRIPT_Bengali, + UNICODE_SCRIPT_Bhaiksuki, + UNICODE_SCRIPT_Bopomofo, + UNICODE_SCRIPT_Brahmi, + UNICODE_SCRIPT_Braille, + UNICODE_SCRIPT_Buginese, + UNICODE_SCRIPT_Buhid, + UNICODE_SCRIPT_Canadian_Aboriginal, + UNICODE_SCRIPT_Carian, + UNICODE_SCRIPT_Caucasian_Albanian, + UNICODE_SCRIPT_Chakma, + UNICODE_SCRIPT_Cham, + UNICODE_SCRIPT_Cherokee, + UNICODE_SCRIPT_Common, + UNICODE_SCRIPT_Coptic, + UNICODE_SCRIPT_Cuneiform, + UNICODE_SCRIPT_Cypriot, + UNICODE_SCRIPT_Cyrillic, + UNICODE_SCRIPT_Deseret, + UNICODE_SCRIPT_Devanagari, + UNICODE_SCRIPT_Dogra, + UNICODE_SCRIPT_Duployan, + UNICODE_SCRIPT_Egyptian_Hieroglyphs, + UNICODE_SCRIPT_Elbasan, + UNICODE_SCRIPT_Elymaic, + UNICODE_SCRIPT_Ethiopic, + UNICODE_SCRIPT_Georgian, + UNICODE_SCRIPT_Glagolitic, + UNICODE_SCRIPT_Gothic, + UNICODE_SCRIPT_Grantha, + UNICODE_SCRIPT_Greek, + UNICODE_SCRIPT_Gujarati, + UNICODE_SCRIPT_Gunjala_Gondi, + UNICODE_SCRIPT_Gurmukhi, + UNICODE_SCRIPT_Han, + UNICODE_SCRIPT_Hangul, + UNICODE_SCRIPT_Hanifi_Rohingya, + UNICODE_SCRIPT_Hanunoo, + UNICODE_SCRIPT_Hatran, + UNICODE_SCRIPT_Hebrew, + UNICODE_SCRIPT_Hiragana, + UNICODE_SCRIPT_Imperial_Aramaic, + UNICODE_SCRIPT_Inherited, + UNICODE_SCRIPT_Inscriptional_Pahlavi, + UNICODE_SCRIPT_Inscriptional_Parthian, + UNICODE_SCRIPT_Javanese, + UNICODE_SCRIPT_Kaithi, + UNICODE_SCRIPT_Kannada, + UNICODE_SCRIPT_Katakana, + UNICODE_SCRIPT_Kayah_Li, + UNICODE_SCRIPT_Kharoshthi, + UNICODE_SCRIPT_Khmer, + UNICODE_SCRIPT_Khojki, + UNICODE_SCRIPT_Khudawadi, + UNICODE_SCRIPT_Lao, + UNICODE_SCRIPT_Latin, + UNICODE_SCRIPT_Lepcha, + UNICODE_SCRIPT_Limbu, + UNICODE_SCRIPT_Linear_A, + UNICODE_SCRIPT_Linear_B, + UNICODE_SCRIPT_Lisu, + UNICODE_SCRIPT_Lycian, + UNICODE_SCRIPT_Lydian, + UNICODE_SCRIPT_Makasar, + UNICODE_SCRIPT_Mahajani, + UNICODE_SCRIPT_Malayalam, + UNICODE_SCRIPT_Mandaic, + UNICODE_SCRIPT_Manichaean, + UNICODE_SCRIPT_Marchen, + UNICODE_SCRIPT_Masaram_Gondi, + UNICODE_SCRIPT_Medefaidrin, + UNICODE_SCRIPT_Meetei_Mayek, + UNICODE_SCRIPT_Mende_Kikakui, + UNICODE_SCRIPT_Meroitic_Cursive, + UNICODE_SCRIPT_Meroitic_Hieroglyphs, + UNICODE_SCRIPT_Miao, + UNICODE_SCRIPT_Modi, + UNICODE_SCRIPT_Mongolian, + UNICODE_SCRIPT_Mro, + UNICODE_SCRIPT_Multani, + UNICODE_SCRIPT_Myanmar, + UNICODE_SCRIPT_Nabataean, + UNICODE_SCRIPT_Nandinagari, + UNICODE_SCRIPT_New_Tai_Lue, + UNICODE_SCRIPT_Newa, + UNICODE_SCRIPT_Nko, + UNICODE_SCRIPT_Nushu, + UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong, + UNICODE_SCRIPT_Ogham, + UNICODE_SCRIPT_Ol_Chiki, + UNICODE_SCRIPT_Old_Hungarian, + UNICODE_SCRIPT_Old_Italic, + UNICODE_SCRIPT_Old_North_Arabian, + UNICODE_SCRIPT_Old_Permic, + UNICODE_SCRIPT_Old_Persian, + UNICODE_SCRIPT_Old_Sogdian, + UNICODE_SCRIPT_Old_South_Arabian, + UNICODE_SCRIPT_Old_Turkic, + UNICODE_SCRIPT_Oriya, + UNICODE_SCRIPT_Osage, + UNICODE_SCRIPT_Osmanya, + UNICODE_SCRIPT_Pahawh_Hmong, + UNICODE_SCRIPT_Palmyrene, + UNICODE_SCRIPT_Pau_Cin_Hau, + UNICODE_SCRIPT_Phags_Pa, + UNICODE_SCRIPT_Phoenician, + UNICODE_SCRIPT_Psalter_Pahlavi, + UNICODE_SCRIPT_Rejang, + UNICODE_SCRIPT_Runic, + UNICODE_SCRIPT_Samaritan, + UNICODE_SCRIPT_Saurashtra, + UNICODE_SCRIPT_Sharada, + UNICODE_SCRIPT_Shavian, + UNICODE_SCRIPT_Siddham, + UNICODE_SCRIPT_SignWriting, + UNICODE_SCRIPT_Sinhala, + UNICODE_SCRIPT_Sogdian, + UNICODE_SCRIPT_Sora_Sompeng, + UNICODE_SCRIPT_Soyombo, + UNICODE_SCRIPT_Sundanese, + UNICODE_SCRIPT_Syloti_Nagri, + UNICODE_SCRIPT_Syriac, + UNICODE_SCRIPT_Tagalog, + UNICODE_SCRIPT_Tagbanwa, + UNICODE_SCRIPT_Tai_Le, + UNICODE_SCRIPT_Tai_Tham, + UNICODE_SCRIPT_Tai_Viet, + UNICODE_SCRIPT_Takri, + UNICODE_SCRIPT_Tamil, + UNICODE_SCRIPT_Tangut, + UNICODE_SCRIPT_Telugu, + UNICODE_SCRIPT_Thaana, + UNICODE_SCRIPT_Thai, + UNICODE_SCRIPT_Tibetan, + UNICODE_SCRIPT_Tifinagh, + UNICODE_SCRIPT_Tirhuta, + UNICODE_SCRIPT_Ugaritic, + UNICODE_SCRIPT_Vai, + UNICODE_SCRIPT_Wancho, + UNICODE_SCRIPT_Warang_Citi, + UNICODE_SCRIPT_Yi, + UNICODE_SCRIPT_Zanabazar_Square, + UNICODE_SCRIPT_COUNT, +} UnicodeScriptEnum; + +static const char unicode_script_name_table[] = + "Adlam,Adlm" + "\0" + "Ahom,Ahom" + "\0" + "Anatolian_Hieroglyphs,Hluw" + "\0" + "Arabic,Arab" + "\0" + "Armenian,Armn" + "\0" + "Avestan,Avst" + "\0" + "Balinese,Bali" + "\0" + "Bamum,Bamu" + "\0" + "Bassa_Vah,Bass" + "\0" + "Batak,Batk" + "\0" + "Bengali,Beng" + "\0" + "Bhaiksuki,Bhks" + "\0" + "Bopomofo,Bopo" + "\0" + "Brahmi,Brah" + "\0" + "Braille,Brai" + "\0" + "Buginese,Bugi" + "\0" + "Buhid,Buhd" + "\0" + "Canadian_Aboriginal,Cans" + "\0" + "Carian,Cari" + "\0" + "Caucasian_Albanian,Aghb" + "\0" + "Chakma,Cakm" + "\0" + "Cham,Cham" + "\0" + "Cherokee,Cher" + "\0" + "Common,Zyyy" + "\0" + "Coptic,Copt,Qaac" + "\0" + "Cuneiform,Xsux" + "\0" + "Cypriot,Cprt" + "\0" + "Cyrillic,Cyrl" + "\0" + "Deseret,Dsrt" + "\0" + "Devanagari,Deva" + "\0" + "Dogra,Dogr" + "\0" + "Duployan,Dupl" + "\0" + "Egyptian_Hieroglyphs,Egyp" + "\0" + "Elbasan,Elba" + "\0" + "Elymaic,Elym" + "\0" + "Ethiopic,Ethi" + "\0" + "Georgian,Geor" + "\0" + "Glagolitic,Glag" + "\0" + "Gothic,Goth" + "\0" + "Grantha,Gran" + "\0" + "Greek,Grek" + "\0" + "Gujarati,Gujr" + "\0" + "Gunjala_Gondi,Gong" + "\0" + "Gurmukhi,Guru" + "\0" + "Han,Hani" + "\0" + "Hangul,Hang" + "\0" + "Hanifi_Rohingya,Rohg" + "\0" + "Hanunoo,Hano" + "\0" + "Hatran,Hatr" + "\0" + "Hebrew,Hebr" + "\0" + "Hiragana,Hira" + "\0" + "Imperial_Aramaic,Armi" + "\0" + "Inherited,Zinh,Qaai" + "\0" + "Inscriptional_Pahlavi,Phli" + "\0" + "Inscriptional_Parthian,Prti" + "\0" + "Javanese,Java" + "\0" + "Kaithi,Kthi" + "\0" + "Kannada,Knda" + "\0" + "Katakana,Kana" + "\0" + "Kayah_Li,Kali" + "\0" + "Kharoshthi,Khar" + "\0" + "Khmer,Khmr" + "\0" + "Khojki,Khoj" + "\0" + "Khudawadi,Sind" + "\0" + "Lao,Laoo" + "\0" + "Latin,Latn" + "\0" + "Lepcha,Lepc" + "\0" + "Limbu,Limb" + "\0" + "Linear_A,Lina" + "\0" + "Linear_B,Linb" + "\0" + "Lisu,Lisu" + "\0" + "Lycian,Lyci" + "\0" + "Lydian,Lydi" + "\0" + "Makasar,Maka" + "\0" + "Mahajani,Mahj" + "\0" + "Malayalam,Mlym" + "\0" + "Mandaic,Mand" + "\0" + "Manichaean,Mani" + "\0" + "Marchen,Marc" + "\0" + "Masaram_Gondi,Gonm" + "\0" + "Medefaidrin,Medf" + "\0" + "Meetei_Mayek,Mtei" + "\0" + "Mende_Kikakui,Mend" + "\0" + "Meroitic_Cursive,Merc" + "\0" + "Meroitic_Hieroglyphs,Mero" + "\0" + "Miao,Plrd" + "\0" + "Modi,Modi" + "\0" + "Mongolian,Mong" + "\0" + "Mro,Mroo" + "\0" + "Multani,Mult" + "\0" + "Myanmar,Mymr" + "\0" + "Nabataean,Nbat" + "\0" + "Nandinagari,Nand" + "\0" + "New_Tai_Lue,Talu" + "\0" + "Newa,Newa" + "\0" + "Nko,Nkoo" + "\0" + "Nushu,Nshu" + "\0" + "Nyiakeng_Puachue_Hmong,Hmnp" + "\0" + "Ogham,Ogam" + "\0" + "Ol_Chiki,Olck" + "\0" + "Old_Hungarian,Hung" + "\0" + "Old_Italic,Ital" + "\0" + "Old_North_Arabian,Narb" + "\0" + "Old_Permic,Perm" + "\0" + "Old_Persian,Xpeo" + "\0" + "Old_Sogdian,Sogo" + "\0" + "Old_South_Arabian,Sarb" + "\0" + "Old_Turkic,Orkh" + "\0" + "Oriya,Orya" + "\0" + "Osage,Osge" + "\0" + "Osmanya,Osma" + "\0" + "Pahawh_Hmong,Hmng" + "\0" + "Palmyrene,Palm" + "\0" + "Pau_Cin_Hau,Pauc" + "\0" + "Phags_Pa,Phag" + "\0" + "Phoenician,Phnx" + "\0" + "Psalter_Pahlavi,Phlp" + "\0" + "Rejang,Rjng" + "\0" + "Runic,Runr" + "\0" + "Samaritan,Samr" + "\0" + "Saurashtra,Saur" + "\0" + "Sharada,Shrd" + "\0" + "Shavian,Shaw" + "\0" + "Siddham,Sidd" + "\0" + "SignWriting,Sgnw" + "\0" + "Sinhala,Sinh" + "\0" + "Sogdian,Sogd" + "\0" + "Sora_Sompeng,Sora" + "\0" + "Soyombo,Soyo" + "\0" + "Sundanese,Sund" + "\0" + "Syloti_Nagri,Sylo" + "\0" + "Syriac,Syrc" + "\0" + "Tagalog,Tglg" + "\0" + "Tagbanwa,Tagb" + "\0" + "Tai_Le,Tale" + "\0" + "Tai_Tham,Lana" + "\0" + "Tai_Viet,Tavt" + "\0" + "Takri,Takr" + "\0" + "Tamil,Taml" + "\0" + "Tangut,Tang" + "\0" + "Telugu,Telu" + "\0" + "Thaana,Thaa" + "\0" + "Thai,Thai" + "\0" + "Tibetan,Tibt" + "\0" + "Tifinagh,Tfng" + "\0" + "Tirhuta,Tirh" + "\0" + "Ugaritic,Ugar" + "\0" + "Vai,Vaii" + "\0" + "Wancho,Wcho" + "\0" + "Warang_Citi,Wara" + "\0" + "Yi,Yiii" + "\0" + "Zanabazar_Square,Zanb" + "\0"; + +static const uint8_t unicode_script_table[2565] = { + 0xc0, 0x18, 0x99, 0x42, 0x85, 0x18, 0x99, 0x42, 0xae, 0x18, 0x80, 0x42, + 0x8e, 0x18, 0x80, 0x42, 0x84, 0x18, 0x96, 0x42, 0x80, 0x18, 0x9e, 0x42, + 0x80, 0x18, 0xe1, 0x60, 0x42, 0xa6, 0x18, 0x84, 0x42, 0x84, 0x18, 0x81, + 0x0d, 0x93, 0x18, 0xe0, 0x0f, 0x35, 0x83, 0x29, 0x80, 0x18, 0x82, 0x29, + 0x01, 0x83, 0x29, 0x80, 0x18, 0x80, 0x29, 0x03, 0x80, 0x29, 0x80, 0x18, + 0x80, 0x29, 0x80, 0x18, 0x82, 0x29, 0x00, 0x80, 0x29, 0x00, 0x93, 0x29, + 0x00, 0xbe, 0x29, 0x8d, 0x19, 0x8f, 0x29, 0xe0, 0x24, 0x1c, 0x81, 0x35, + 0xe0, 0x48, 0x1c, 0x00, 0xa5, 0x05, 0x01, 0xaf, 0x05, 0x80, 0x18, 0x80, + 0x05, 0x01, 0x82, 0x05, 0x00, 0xb6, 0x32, 0x07, 0x9a, 0x32, 0x03, 0x85, + 0x32, 0x0a, 0x84, 0x04, 0x80, 0x18, 0x85, 0x04, 0x80, 0x18, 0x8d, 0x04, + 0x80, 0x18, 0x80, 0x04, 0x00, 0x80, 0x04, 0x80, 0x18, 0x9f, 0x04, 0x80, + 0x18, 0x89, 0x04, 0x8a, 0x35, 0x99, 0x04, 0x80, 0x35, 0xe0, 0x0b, 0x04, + 0x80, 0x18, 0xa1, 0x04, 0x8d, 0x84, 0x00, 0xbb, 0x84, 0x01, 0x82, 0x84, + 0xaf, 0x04, 0xb1, 0x8e, 0x0d, 0xba, 0x60, 0x01, 0x82, 0x60, 0xad, 0x78, + 0x01, 0x8e, 0x78, 0x00, 0x9b, 0x4d, 0x01, 0x80, 0x4d, 0x00, 0x8a, 0x84, + 0x34, 0x94, 0x04, 0x00, 0x87, 0x04, 0x14, 0x8e, 0x04, 0x80, 0x18, 0x9c, + 0x04, 0xd0, 0x1e, 0x83, 0x35, 0x8e, 0x1e, 0x81, 0x18, 0x99, 0x1e, 0x83, + 0x0b, 0x00, 0x87, 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x95, 0x0b, 0x00, 0x86, + 0x0b, 0x00, 0x80, 0x0b, 0x02, 0x83, 0x0b, 0x01, 0x88, 0x0b, 0x01, 0x81, + 0x0b, 0x01, 0x83, 0x0b, 0x07, 0x80, 0x0b, 0x03, 0x81, 0x0b, 0x00, 0x84, + 0x0b, 0x01, 0x98, 0x0b, 0x01, 0x82, 0x2c, 0x00, 0x85, 0x2c, 0x03, 0x81, + 0x2c, 0x01, 0x95, 0x2c, 0x00, 0x86, 0x2c, 0x00, 0x81, 0x2c, 0x00, 0x81, + 0x2c, 0x00, 0x81, 0x2c, 0x01, 0x80, 0x2c, 0x00, 0x84, 0x2c, 0x03, 0x81, + 0x2c, 0x01, 0x82, 0x2c, 0x02, 0x80, 0x2c, 0x06, 0x83, 0x2c, 0x00, 0x80, + 0x2c, 0x06, 0x90, 0x2c, 0x09, 0x82, 0x2a, 0x00, 0x88, 0x2a, 0x00, 0x82, + 0x2a, 0x00, 0x95, 0x2a, 0x00, 0x86, 0x2a, 0x00, 0x81, 0x2a, 0x00, 0x84, + 0x2a, 0x01, 0x89, 0x2a, 0x00, 0x82, 0x2a, 0x00, 0x82, 0x2a, 0x01, 0x80, + 0x2a, 0x0e, 0x83, 0x2a, 0x01, 0x8b, 0x2a, 0x06, 0x86, 0x2a, 0x00, 0x82, + 0x6d, 0x00, 0x87, 0x6d, 0x01, 0x81, 0x6d, 0x01, 0x95, 0x6d, 0x00, 0x86, + 0x6d, 0x00, 0x81, 0x6d, 0x00, 0x84, 0x6d, 0x01, 0x88, 0x6d, 0x01, 0x81, + 0x6d, 0x01, 0x82, 0x6d, 0x07, 0x81, 0x6d, 0x03, 0x81, 0x6d, 0x00, 0x84, + 0x6d, 0x01, 0x91, 0x6d, 0x09, 0x81, 0x8b, 0x00, 0x85, 0x8b, 0x02, 0x82, + 0x8b, 0x00, 0x83, 0x8b, 0x02, 0x81, 0x8b, 0x00, 0x80, 0x8b, 0x00, 0x81, + 0x8b, 0x02, 0x81, 0x8b, 0x02, 0x82, 0x8b, 0x02, 0x8b, 0x8b, 0x03, 0x84, + 0x8b, 0x02, 0x82, 0x8b, 0x00, 0x83, 0x8b, 0x01, 0x80, 0x8b, 0x05, 0x80, + 0x8b, 0x0d, 0x94, 0x8b, 0x04, 0x8c, 0x8d, 0x00, 0x82, 0x8d, 0x00, 0x96, + 0x8d, 0x00, 0x8f, 0x8d, 0x02, 0x87, 0x8d, 0x00, 0x82, 0x8d, 0x00, 0x83, + 0x8d, 0x06, 0x81, 0x8d, 0x00, 0x82, 0x8d, 0x04, 0x83, 0x8d, 0x01, 0x89, + 0x8d, 0x06, 0x88, 0x8d, 0x8c, 0x3a, 0x00, 0x82, 0x3a, 0x00, 0x96, 0x3a, + 0x00, 0x89, 0x3a, 0x00, 0x84, 0x3a, 0x01, 0x88, 0x3a, 0x00, 0x82, 0x3a, + 0x00, 0x83, 0x3a, 0x06, 0x81, 0x3a, 0x06, 0x80, 0x3a, 0x00, 0x83, 0x3a, + 0x01, 0x89, 0x3a, 0x00, 0x81, 0x3a, 0x0c, 0x83, 0x4c, 0x00, 0x87, 0x4c, + 0x00, 0x82, 0x4c, 0x00, 0xb2, 0x4c, 0x00, 0x82, 0x4c, 0x00, 0x85, 0x4c, + 0x03, 0x8f, 0x4c, 0x01, 0x99, 0x4c, 0x01, 0x81, 0x7e, 0x00, 0x91, 0x7e, + 0x02, 0x97, 0x7e, 0x00, 0x88, 0x7e, 0x00, 0x80, 0x7e, 0x01, 0x86, 0x7e, + 0x02, 0x80, 0x7e, 0x03, 0x85, 0x7e, 0x00, 0x80, 0x7e, 0x00, 0x87, 0x7e, + 0x05, 0x89, 0x7e, 0x01, 0x82, 0x7e, 0x0b, 0xb9, 0x8f, 0x03, 0x80, 0x18, + 0x9b, 0x8f, 0x24, 0x81, 0x41, 0x00, 0x80, 0x41, 0x00, 0x84, 0x41, 0x00, + 0x97, 0x41, 0x00, 0x80, 0x41, 0x00, 0x96, 0x41, 0x01, 0x84, 0x41, 0x00, + 0x80, 0x41, 0x00, 0x85, 0x41, 0x01, 0x89, 0x41, 0x01, 0x83, 0x41, 0x1f, + 0xc7, 0x90, 0x00, 0xa3, 0x90, 0x03, 0xa6, 0x90, 0x00, 0xa3, 0x90, 0x00, + 0x8e, 0x90, 0x00, 0x86, 0x90, 0x83, 0x18, 0x81, 0x90, 0x24, 0xe0, 0x3f, + 0x5b, 0xa5, 0x25, 0x00, 0x80, 0x25, 0x04, 0x80, 0x25, 0x01, 0xaa, 0x25, + 0x80, 0x18, 0x83, 0x25, 0xe0, 0x9f, 0x2e, 0xc8, 0x24, 0x00, 0x83, 0x24, + 0x01, 0x86, 0x24, 0x00, 0x80, 0x24, 0x00, 0x83, 0x24, 0x01, 0xa8, 0x24, + 0x00, 0x83, 0x24, 0x01, 0xa0, 0x24, 0x00, 0x83, 0x24, 0x01, 0x86, 0x24, + 0x00, 0x80, 0x24, 0x00, 0x83, 0x24, 0x01, 0x8e, 0x24, 0x00, 0xb8, 0x24, + 0x00, 0x83, 0x24, 0x01, 0xc2, 0x24, 0x01, 0x9f, 0x24, 0x02, 0x99, 0x24, + 0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, 0xe2, 0x1f, 0x12, 0x9c, 0x63, + 0x02, 0xca, 0x77, 0x82, 0x18, 0x8a, 0x77, 0x06, 0x8c, 0x85, 0x00, 0x86, + 0x85, 0x0a, 0x94, 0x30, 0x81, 0x18, 0x08, 0x93, 0x11, 0x0b, 0x8c, 0x86, + 0x00, 0x82, 0x86, 0x00, 0x81, 0x86, 0x0b, 0xdd, 0x3e, 0x01, 0x89, 0x3e, + 0x05, 0x89, 0x3e, 0x05, 0x81, 0x58, 0x81, 0x18, 0x80, 0x58, 0x80, 0x18, + 0x88, 0x58, 0x00, 0x89, 0x58, 0x05, 0xd8, 0x58, 0x06, 0xaa, 0x58, 0x04, + 0xc5, 0x12, 0x09, 0x9e, 0x44, 0x00, 0x8b, 0x44, 0x03, 0x8b, 0x44, 0x03, + 0x80, 0x44, 0x02, 0x8b, 0x44, 0x9d, 0x87, 0x01, 0x84, 0x87, 0x0a, 0xab, + 0x5e, 0x03, 0x99, 0x5e, 0x05, 0x8a, 0x5e, 0x02, 0x81, 0x5e, 0x9f, 0x3e, + 0x9b, 0x10, 0x01, 0x81, 0x10, 0xbe, 0x88, 0x00, 0x9c, 0x88, 0x01, 0x8a, + 0x88, 0x05, 0x89, 0x88, 0x05, 0x8d, 0x88, 0x01, 0x8e, 0x35, 0x40, 0xcb, + 0x07, 0x03, 0xac, 0x07, 0x02, 0xbf, 0x82, 0xb3, 0x0a, 0x07, 0x83, 0x0a, + 0xb7, 0x43, 0x02, 0x8e, 0x43, 0x02, 0x82, 0x43, 0xaf, 0x64, 0x88, 0x1c, + 0x06, 0xaa, 0x25, 0x01, 0x82, 0x25, 0x87, 0x82, 0x07, 0x82, 0x35, 0x80, + 0x18, 0x8c, 0x35, 0x80, 0x18, 0x86, 0x35, 0x83, 0x18, 0x80, 0x35, 0x85, + 0x18, 0x80, 0x35, 0x82, 0x18, 0x81, 0x35, 0x80, 0x18, 0x04, 0xa5, 0x42, + 0x84, 0x29, 0x80, 0x1c, 0xb0, 0x42, 0x84, 0x29, 0x83, 0x42, 0x84, 0x29, + 0x8c, 0x42, 0x80, 0x1c, 0xc5, 0x42, 0x80, 0x29, 0xb9, 0x35, 0x00, 0x84, + 0x35, 0xe0, 0x9f, 0x42, 0x95, 0x29, 0x01, 0x85, 0x29, 0x01, 0xa5, 0x29, + 0x01, 0x85, 0x29, 0x01, 0x87, 0x29, 0x00, 0x80, 0x29, 0x00, 0x80, 0x29, + 0x00, 0x80, 0x29, 0x00, 0x9e, 0x29, 0x01, 0xb4, 0x29, 0x00, 0x8e, 0x29, + 0x00, 0x8d, 0x29, 0x01, 0x85, 0x29, 0x00, 0x92, 0x29, 0x01, 0x82, 0x29, + 0x00, 0x88, 0x29, 0x00, 0x8b, 0x18, 0x81, 0x35, 0xd6, 0x18, 0x00, 0x8a, + 0x18, 0x80, 0x42, 0x01, 0x8a, 0x18, 0x80, 0x42, 0x8e, 0x18, 0x00, 0x8c, + 0x42, 0x02, 0x9f, 0x18, 0x0f, 0xa0, 0x35, 0x0e, 0xa5, 0x18, 0x80, 0x29, + 0x82, 0x18, 0x81, 0x42, 0x85, 0x18, 0x80, 0x42, 0x9a, 0x18, 0x80, 0x42, + 0x90, 0x18, 0xa8, 0x42, 0x82, 0x18, 0x03, 0xe2, 0x36, 0x18, 0x18, 0x8a, + 0x18, 0x14, 0xe3, 0x3f, 0x18, 0xe0, 0x9f, 0x0f, 0xe2, 0x13, 0x18, 0x01, + 0x9f, 0x18, 0x01, 0xe0, 0x07, 0x18, 0xae, 0x26, 0x00, 0xae, 0x26, 0x00, + 0x9f, 0x42, 0xe0, 0x13, 0x19, 0x04, 0x86, 0x19, 0xa5, 0x25, 0x00, 0x80, + 0x25, 0x04, 0x80, 0x25, 0x01, 0xb7, 0x91, 0x06, 0x81, 0x91, 0x0d, 0x80, + 0x91, 0x96, 0x24, 0x08, 0x86, 0x24, 0x00, 0x86, 0x24, 0x00, 0x86, 0x24, + 0x00, 0x86, 0x24, 0x00, 0x86, 0x24, 0x00, 0x86, 0x24, 0x00, 0x86, 0x24, + 0x00, 0x86, 0x24, 0x00, 0x9f, 0x1c, 0xcf, 0x18, 0x2f, 0x99, 0x2d, 0x00, + 0xd8, 0x2d, 0x0b, 0xe0, 0x75, 0x2d, 0x19, 0x8b, 0x18, 0x03, 0x84, 0x18, + 0x80, 0x2d, 0x80, 0x18, 0x80, 0x2d, 0x98, 0x18, 0x88, 0x2d, 0x83, 0x35, + 0x81, 0x2e, 0x87, 0x18, 0x83, 0x2d, 0x83, 0x18, 0x00, 0xd5, 0x33, 0x01, + 0x81, 0x35, 0x81, 0x18, 0x82, 0x33, 0x80, 0x18, 0xd9, 0x3b, 0x81, 0x18, + 0x82, 0x3b, 0x04, 0xaa, 0x0d, 0x00, 0xdd, 0x2e, 0x00, 0x8f, 0x18, 0x9a, + 0x0d, 0x04, 0xa3, 0x18, 0x0b, 0x8f, 0x3b, 0x9e, 0x2e, 0x00, 0xbf, 0x18, + 0x9e, 0x2e, 0xd0, 0x18, 0xae, 0x3b, 0x80, 0x18, 0xd7, 0x3b, 0xe0, 0x47, + 0x18, 0xf0, 0x09, 0x55, 0x2d, 0x09, 0xbf, 0x18, 0xf0, 0x41, 0x8f, 0x2d, + 0x0f, 0xe4, 0x2c, 0x97, 0x02, 0xb6, 0x97, 0x08, 0xaf, 0x47, 0xe0, 0xcb, + 0x94, 0x13, 0xdf, 0x1c, 0xd7, 0x08, 0x07, 0xa1, 0x18, 0xe0, 0x05, 0x42, + 0x82, 0x18, 0xb4, 0x42, 0x01, 0x84, 0x42, 0x2f, 0x88, 0x42, 0xab, 0x83, + 0x03, 0x89, 0x18, 0x05, 0xb7, 0x73, 0x07, 0xc5, 0x79, 0x07, 0x8b, 0x79, + 0x05, 0x9f, 0x1e, 0xad, 0x3c, 0x80, 0x18, 0x80, 0x3c, 0xa3, 0x76, 0x0a, + 0x80, 0x76, 0x9c, 0x2e, 0x02, 0xcd, 0x38, 0x00, 0x80, 0x18, 0x89, 0x38, + 0x03, 0x81, 0x38, 0x9e, 0x5b, 0x00, 0xb6, 0x16, 0x08, 0x8d, 0x16, 0x01, + 0x89, 0x16, 0x01, 0x83, 0x16, 0x9f, 0x5b, 0xc2, 0x89, 0x17, 0x84, 0x89, + 0x96, 0x52, 0x09, 0x85, 0x24, 0x01, 0x85, 0x24, 0x01, 0x85, 0x24, 0x08, + 0x86, 0x24, 0x00, 0x86, 0x24, 0x00, 0xaa, 0x42, 0x80, 0x18, 0x88, 0x42, + 0x80, 0x29, 0x81, 0x42, 0x07, 0xcf, 0x17, 0xad, 0x52, 0x01, 0x89, 0x52, + 0x05, 0xf0, 0x1b, 0x43, 0x2e, 0x0b, 0x96, 0x2e, 0x03, 0xb0, 0x2e, 0x70, + 0x10, 0xa3, 0xe1, 0x0d, 0x2d, 0x01, 0xe0, 0x09, 0x2d, 0x25, 0x86, 0x42, + 0x0b, 0x84, 0x05, 0x04, 0x99, 0x32, 0x00, 0x84, 0x32, 0x00, 0x80, 0x32, + 0x00, 0x81, 0x32, 0x00, 0x81, 0x32, 0x00, 0x89, 0x32, 0xe0, 0x11, 0x04, + 0x10, 0xe1, 0x0a, 0x04, 0x81, 0x18, 0x0f, 0xbf, 0x04, 0x01, 0xb5, 0x04, + 0x27, 0x8d, 0x04, 0x01, 0x8f, 0x35, 0x89, 0x18, 0x05, 0x8d, 0x35, 0x81, + 0x1c, 0xa2, 0x18, 0x00, 0x92, 0x18, 0x00, 0x83, 0x18, 0x03, 0x84, 0x04, + 0x00, 0xe0, 0x26, 0x04, 0x01, 0x80, 0x18, 0x00, 0x9f, 0x18, 0x99, 0x42, + 0x85, 0x18, 0x99, 0x42, 0x8a, 0x18, 0x89, 0x3b, 0x80, 0x18, 0xac, 0x3b, + 0x81, 0x18, 0x9e, 0x2e, 0x02, 0x85, 0x2e, 0x01, 0x85, 0x2e, 0x01, 0x85, + 0x2e, 0x01, 0x82, 0x2e, 0x02, 0x86, 0x18, 0x00, 0x86, 0x18, 0x09, 0x84, + 0x18, 0x01, 0x8b, 0x46, 0x00, 0x99, 0x46, 0x00, 0x92, 0x46, 0x00, 0x81, + 0x46, 0x00, 0x8e, 0x46, 0x01, 0x8d, 0x46, 0x21, 0xe0, 0x1a, 0x46, 0x04, + 0x82, 0x18, 0x03, 0xac, 0x18, 0x02, 0x88, 0x18, 0xce, 0x29, 0x00, 0x8b, + 0x18, 0x03, 0x80, 0x29, 0x2e, 0xac, 0x18, 0x80, 0x35, 0x60, 0x21, 0x9c, + 0x48, 0x02, 0xb0, 0x13, 0x0e, 0x80, 0x35, 0x9a, 0x18, 0x03, 0xa3, 0x66, + 0x08, 0x82, 0x66, 0x9a, 0x27, 0x04, 0xaa, 0x68, 0x04, 0x9d, 0x93, 0x00, + 0x80, 0x93, 0xa3, 0x69, 0x03, 0x8d, 0x69, 0x29, 0xcf, 0x1d, 0xaf, 0x7b, + 0x9d, 0x6f, 0x01, 0x89, 0x6f, 0x05, 0xa3, 0x6e, 0x03, 0xa3, 0x6e, 0x03, + 0xa7, 0x22, 0x07, 0xb3, 0x14, 0x0a, 0x80, 0x14, 0x60, 0x2f, 0xe0, 0xd6, + 0x45, 0x08, 0x95, 0x45, 0x09, 0x87, 0x45, 0x60, 0x37, 0x85, 0x1b, 0x01, + 0x80, 0x1b, 0x00, 0xab, 0x1b, 0x00, 0x81, 0x1b, 0x02, 0x80, 0x1b, 0x01, + 0x80, 0x1b, 0x95, 0x34, 0x00, 0x88, 0x34, 0x9f, 0x71, 0x9e, 0x5c, 0x07, + 0x88, 0x5c, 0x2f, 0x92, 0x31, 0x00, 0x81, 0x31, 0x04, 0x84, 0x31, 0x9b, + 0x74, 0x02, 0x80, 0x74, 0x99, 0x49, 0x04, 0x80, 0x49, 0x3f, 0x9f, 0x55, + 0x97, 0x54, 0x03, 0x93, 0x54, 0x01, 0xad, 0x54, 0x83, 0x3d, 0x00, 0x81, + 0x3d, 0x04, 0x87, 0x3d, 0x00, 0x82, 0x3d, 0x00, 0x9c, 0x3d, 0x01, 0x82, + 0x3d, 0x03, 0x89, 0x3d, 0x06, 0x88, 0x3d, 0x06, 0x9f, 0x6b, 0x9f, 0x67, + 0x1f, 0xa6, 0x4e, 0x03, 0x8b, 0x4e, 0x08, 0xb5, 0x06, 0x02, 0x86, 0x06, + 0x95, 0x37, 0x01, 0x87, 0x37, 0x92, 0x36, 0x04, 0x87, 0x36, 0x91, 0x75, + 0x06, 0x83, 0x75, 0x0b, 0x86, 0x75, 0x4f, 0xc8, 0x6c, 0x36, 0xb2, 0x65, + 0x0c, 0xb2, 0x65, 0x06, 0x85, 0x65, 0xa7, 0x2f, 0x07, 0x89, 0x2f, 0x60, + 0xc5, 0x9e, 0x04, 0x60, 0x20, 0xa7, 0x6a, 0x07, 0xa9, 0x7f, 0x60, 0x25, + 0x96, 0x23, 0x08, 0xcd, 0x0e, 0x03, 0x9d, 0x0e, 0x0e, 0x80, 0x0e, 0xc1, + 0x39, 0x0a, 0x80, 0x39, 0x01, 0x98, 0x80, 0x06, 0x89, 0x80, 0x05, 0xb4, + 0x15, 0x00, 0x90, 0x15, 0x08, 0xa6, 0x4b, 0x08, 0xcd, 0x7a, 0x01, 0x8f, + 0x7a, 0x00, 0x93, 0x7e, 0x0a, 0x91, 0x3f, 0x00, 0xab, 0x3f, 0x40, 0x86, + 0x5a, 0x00, 0x80, 0x5a, 0x00, 0x83, 0x5a, 0x00, 0x8e, 0x5a, 0x00, 0x8a, + 0x5a, 0x05, 0xba, 0x40, 0x04, 0x89, 0x40, 0x05, 0x83, 0x28, 0x00, 0x87, + 0x28, 0x01, 0x81, 0x28, 0x01, 0x95, 0x28, 0x00, 0x86, 0x28, 0x00, 0x81, + 0x28, 0x00, 0x84, 0x28, 0x00, 0x80, 0x35, 0x88, 0x28, 0x01, 0x81, 0x28, + 0x01, 0x82, 0x28, 0x01, 0x80, 0x28, 0x05, 0x80, 0x28, 0x04, 0x86, 0x28, + 0x01, 0x86, 0x28, 0x02, 0x84, 0x28, 0x60, 0x2a, 0xd9, 0x5f, 0x00, 0x80, + 0x5f, 0x00, 0x82, 0x5f, 0x1f, 0xc7, 0x92, 0x07, 0x89, 0x92, 0x60, 0x45, + 0xb5, 0x7c, 0x01, 0xa5, 0x7c, 0x21, 0xc4, 0x57, 0x0a, 0x89, 0x57, 0x05, + 0x8c, 0x58, 0x12, 0xb8, 0x8a, 0x06, 0x89, 0x8a, 0x35, 0x9a, 0x02, 0x01, + 0x8e, 0x02, 0x03, 0x8f, 0x02, 0x60, 0x5f, 0xbb, 0x1f, 0x60, 0x03, 0xd2, + 0x96, 0x0b, 0x80, 0x96, 0x60, 0x3f, 0x87, 0x5d, 0x01, 0xad, 0x5d, 0x01, + 0x8a, 0x5d, 0x1a, 0xc7, 0x98, 0x07, 0xd2, 0x81, 0x1c, 0xb8, 0x72, 0x60, + 0xa6, 0x88, 0x0c, 0x00, 0xac, 0x0c, 0x00, 0x8d, 0x0c, 0x09, 0x9c, 0x0c, + 0x02, 0x9f, 0x4f, 0x01, 0x95, 0x4f, 0x00, 0x8d, 0x4f, 0x48, 0x86, 0x50, + 0x00, 0x81, 0x50, 0x00, 0xab, 0x50, 0x02, 0x80, 0x50, 0x00, 0x81, 0x50, + 0x00, 0x88, 0x50, 0x07, 0x89, 0x50, 0x05, 0x85, 0x2b, 0x00, 0x81, 0x2b, + 0x00, 0xa4, 0x2b, 0x00, 0x81, 0x2b, 0x00, 0x85, 0x2b, 0x06, 0x89, 0x2b, + 0x60, 0xd5, 0x98, 0x4a, 0x60, 0x66, 0xb1, 0x8b, 0x0c, 0x80, 0x8b, 0xe3, + 0x39, 0x1a, 0x60, 0x05, 0xe0, 0x0e, 0x1a, 0x00, 0x84, 0x1a, 0x0a, 0xe0, + 0x63, 0x1a, 0x6a, 0x5b, 0xe3, 0xce, 0x21, 0x00, 0x88, 0x21, 0x6f, 0x66, + 0xe1, 0xe6, 0x03, 0x70, 0x11, 0x58, 0xe1, 0xd8, 0x08, 0x06, 0x9e, 0x59, + 0x00, 0x89, 0x59, 0x03, 0x81, 0x59, 0x5f, 0x9d, 0x09, 0x01, 0x85, 0x09, + 0x09, 0xc5, 0x70, 0x09, 0x89, 0x70, 0x00, 0x86, 0x70, 0x00, 0x94, 0x70, + 0x04, 0x92, 0x70, 0x62, 0x4f, 0xda, 0x51, 0x60, 0x04, 0xca, 0x56, 0x03, + 0xb8, 0x56, 0x06, 0x90, 0x56, 0x3f, 0x80, 0x8c, 0x80, 0x61, 0x81, 0x18, + 0x1b, 0xf0, 0x07, 0x97, 0x8c, 0x07, 0xe2, 0x92, 0x8c, 0x70, 0x14, 0xac, + 0x80, 0x3b, 0xe0, 0xbd, 0x33, 0x30, 0x82, 0x33, 0x10, 0x83, 0x3b, 0x07, + 0xe1, 0x2b, 0x61, 0x68, 0xa3, 0xe0, 0x0a, 0x20, 0x04, 0x8c, 0x20, 0x02, + 0x88, 0x20, 0x06, 0x89, 0x20, 0x01, 0x83, 0x20, 0x83, 0x18, 0x70, 0x02, + 0xfb, 0xe0, 0x95, 0x18, 0x09, 0xa6, 0x18, 0x01, 0xbd, 0x18, 0x82, 0x35, + 0x90, 0x18, 0x87, 0x35, 0x81, 0x18, 0x86, 0x35, 0x9d, 0x18, 0x83, 0x35, + 0xba, 0x18, 0x16, 0xc5, 0x29, 0x60, 0x39, 0x93, 0x18, 0x0b, 0xd6, 0x18, + 0x08, 0x98, 0x18, 0x60, 0x26, 0xd4, 0x18, 0x00, 0xc6, 0x18, 0x00, 0x81, + 0x18, 0x01, 0x80, 0x18, 0x01, 0x81, 0x18, 0x01, 0x83, 0x18, 0x00, 0x8b, + 0x18, 0x00, 0x80, 0x18, 0x00, 0x86, 0x18, 0x00, 0xc0, 0x18, 0x00, 0x83, + 0x18, 0x01, 0x87, 0x18, 0x00, 0x86, 0x18, 0x00, 0x9b, 0x18, 0x00, 0x83, + 0x18, 0x00, 0x84, 0x18, 0x00, 0x80, 0x18, 0x02, 0x86, 0x18, 0x00, 0xe0, + 0xf3, 0x18, 0x01, 0xe0, 0xc3, 0x18, 0x01, 0xb1, 0x18, 0xe2, 0x2b, 0x7d, + 0x0e, 0x84, 0x7d, 0x00, 0x8e, 0x7d, 0x64, 0xef, 0x86, 0x26, 0x00, 0x90, + 0x26, 0x01, 0x86, 0x26, 0x00, 0x81, 0x26, 0x00, 0x84, 0x26, 0x60, 0x74, + 0xac, 0x62, 0x02, 0x8d, 0x62, 0x01, 0x89, 0x62, 0x03, 0x81, 0x62, 0x61, + 0x0f, 0xb9, 0x95, 0x04, 0x80, 0x95, 0x64, 0x9f, 0xe0, 0x64, 0x53, 0x01, + 0x8f, 0x53, 0x28, 0xcb, 0x01, 0x03, 0x89, 0x01, 0x03, 0x81, 0x01, 0x62, + 0xb0, 0xc3, 0x18, 0x4b, 0xbc, 0x18, 0x60, 0x61, 0x83, 0x04, 0x00, 0x9a, + 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, 0x89, + 0x04, 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x05, 0x80, + 0x04, 0x03, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x82, + 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, 0x80, + 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x81, + 0x04, 0x00, 0x80, 0x04, 0x01, 0x83, 0x04, 0x00, 0x86, 0x04, 0x00, 0x83, + 0x04, 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, 0x00, 0x89, 0x04, 0x00, 0x90, + 0x04, 0x04, 0x82, 0x04, 0x00, 0x84, 0x04, 0x00, 0x90, 0x04, 0x33, 0x81, + 0x04, 0x60, 0xad, 0xab, 0x18, 0x03, 0xe0, 0x03, 0x18, 0x0b, 0x8e, 0x18, + 0x01, 0x8e, 0x18, 0x00, 0x8e, 0x18, 0x00, 0xa4, 0x18, 0x09, 0x8c, 0x18, + 0x02, 0xdc, 0x18, 0x02, 0xbc, 0x18, 0x38, 0x99, 0x18, 0x80, 0x33, 0x81, + 0x18, 0x0c, 0xab, 0x18, 0x03, 0x88, 0x18, 0x06, 0x81, 0x18, 0x0d, 0x85, + 0x18, 0x60, 0x39, 0xe3, 0x75, 0x18, 0x09, 0x8c, 0x18, 0x02, 0x8a, 0x18, + 0x04, 0xe0, 0x13, 0x18, 0x0b, 0xd8, 0x18, 0x06, 0x8b, 0x18, 0x13, 0x8b, + 0x18, 0x03, 0xb7, 0x18, 0x07, 0x89, 0x18, 0x05, 0xa7, 0x18, 0x07, 0x9d, + 0x18, 0x51, 0x8b, 0x18, 0x00, 0xe0, 0x04, 0x18, 0x00, 0x83, 0x18, 0x02, + 0xa8, 0x18, 0x01, 0x85, 0x18, 0x02, 0x9c, 0x18, 0x01, 0xe0, 0x26, 0x18, + 0x0b, 0x8d, 0x18, 0x01, 0x83, 0x18, 0x03, 0x82, 0x18, 0x04, 0x82, 0x18, + 0x0c, 0x85, 0x18, 0x65, 0x09, 0xf0, 0x96, 0x76, 0x2d, 0x28, 0xef, 0xd4, + 0x2d, 0x0a, 0xe0, 0x7d, 0x2d, 0x01, 0xf0, 0x06, 0x21, 0x2d, 0x0d, 0xf0, + 0x0c, 0xd0, 0x2d, 0x6b, 0xbe, 0xe1, 0xbd, 0x2d, 0x7a, 0xf5, 0x82, 0x80, + 0x18, 0x1d, 0xdf, 0x18, 0x60, 0x1f, 0xe0, 0x8f, 0x35, +}; + +static const uint8_t unicode_script_ext_table[789] = { + 0x82, 0xc1, 0x00, 0x00, 0x01, 0x29, 0x01, 0x00, 0x00, 0x01, 0x29, 0x1c, + 0x00, 0x0c, 0x01, 0x42, 0x80, 0x92, 0x00, 0x00, 0x02, 0x1c, 0x68, 0x00, + 0x02, 0x1c, 0x26, 0x01, 0x02, 0x1c, 0x42, 0x00, 0x02, 0x1c, 0x26, 0x80, + 0x80, 0x00, 0x00, 0x02, 0x05, 0x25, 0x80, 0x01, 0x00, 0x00, 0x04, 0x04, + 0x2f, 0x84, 0x8e, 0x0d, 0x00, 0x00, 0x04, 0x04, 0x2f, 0x84, 0x8e, 0x00, + 0x03, 0x04, 0x84, 0x8e, 0x01, 0x00, 0x00, 0x04, 0x04, 0x2f, 0x84, 0x8e, + 0x1f, 0x00, 0x00, 0x08, 0x01, 0x04, 0x4d, 0x4e, 0x75, 0x2f, 0x7f, 0x84, + 0x09, 0x00, 0x0a, 0x02, 0x04, 0x84, 0x09, 0x00, 0x09, 0x02, 0x04, 0x8e, + 0x05, 0x00, 0x00, 0x02, 0x04, 0x84, 0x62, 0x00, 0x00, 0x02, 0x04, 0x2f, + 0x81, 0xfb, 0x00, 0x00, 0x0d, 0x0b, 0x1e, 0x28, 0x2a, 0x2c, 0x3a, 0x42, + 0x4c, 0x6d, 0x7a, 0x8b, 0x8d, 0x92, 0x00, 0x0c, 0x0b, 0x1e, 0x28, 0x2a, + 0x2c, 0x3a, 0x42, 0x4c, 0x6d, 0x8b, 0x8d, 0x92, 0x10, 0x00, 0x00, 0x14, + 0x0b, 0x1e, 0x1f, 0x2b, 0x50, 0x28, 0x2a, 0x2c, 0x3a, 0x4b, 0x4c, 0x5d, + 0x6d, 0x40, 0x7e, 0x83, 0x8a, 0x8b, 0x8d, 0x92, 0x00, 0x15, 0x0b, 0x1e, + 0x1f, 0x2b, 0x50, 0x28, 0x2a, 0x2c, 0x3a, 0x44, 0x4b, 0x4c, 0x5d, 0x6d, + 0x40, 0x7e, 0x83, 0x8a, 0x8b, 0x8d, 0x92, 0x09, 0x04, 0x1e, 0x1f, 0x39, + 0x4b, 0x75, 0x00, 0x09, 0x03, 0x0b, 0x15, 0x83, 0x75, 0x00, 0x09, 0x02, + 0x2c, 0x5a, 0x75, 0x00, 0x09, 0x02, 0x2a, 0x3f, 0x80, 0x75, 0x00, 0x0d, + 0x02, 0x28, 0x8b, 0x80, 0x71, 0x00, 0x09, 0x02, 0x3a, 0x5d, 0x82, 0xcf, + 0x00, 0x09, 0x03, 0x15, 0x5b, 0x87, 0x80, 0x30, 0x00, 0x00, 0x02, 0x25, + 0x42, 0x85, 0xb8, 0x00, 0x01, 0x04, 0x11, 0x30, 0x86, 0x85, 0x80, 0x4a, + 0x00, 0x01, 0x02, 0x58, 0x73, 0x00, 0x00, 0x00, 0x02, 0x58, 0x73, 0x84, + 0x49, 0x00, 0x00, 0x04, 0x0b, 0x1e, 0x28, 0x3a, 0x00, 0x01, 0x1e, 0x00, + 0x04, 0x0b, 0x1e, 0x28, 0x3a, 0x00, 0x02, 0x1e, 0x28, 0x00, 0x01, 0x1e, + 0x01, 0x02, 0x0b, 0x1e, 0x00, 0x02, 0x1e, 0x7a, 0x00, 0x02, 0x0b, 0x1e, + 0x00, 0x02, 0x1e, 0x7a, 0x00, 0x06, 0x1e, 0x3a, 0x4c, 0x6d, 0x8b, 0x8d, + 0x00, 0x01, 0x1e, 0x01, 0x02, 0x1e, 0x7a, 0x01, 0x01, 0x1e, 0x00, 0x02, + 0x1e, 0x7a, 0x00, 0x02, 0x0b, 0x1e, 0x06, 0x01, 0x1e, 0x00, 0x02, 0x1e, + 0x5d, 0x00, 0x02, 0x0b, 0x1e, 0x01, 0x01, 0x1e, 0x00, 0x02, 0x0b, 0x1e, + 0x03, 0x01, 0x1e, 0x00, 0x08, 0x0b, 0x1e, 0x28, 0x3a, 0x5d, 0x6d, 0x8d, + 0x92, 0x00, 0x02, 0x1e, 0x28, 0x00, 0x03, 0x1e, 0x28, 0x3a, 0x01, 0x02, + 0x0b, 0x1e, 0x00, 0x01, 0x0b, 0x01, 0x02, 0x1e, 0x28, 0x00, 0x01, 0x5d, + 0x80, 0x44, 0x00, 0x01, 0x01, 0x29, 0x81, 0xec, 0x00, 0x00, 0x02, 0x42, + 0x58, 0x80, 0x3f, 0x00, 0x00, 0x03, 0x1e, 0x28, 0x42, 0x8c, 0xd1, 0x00, + 0x00, 0x02, 0x1c, 0x26, 0x81, 0x3c, 0x00, 0x01, 0x06, 0x0d, 0x2e, 0x2d, + 0x33, 0x3b, 0x97, 0x00, 0x05, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x01, 0x00, + 0x00, 0x01, 0x2d, 0x00, 0x00, 0x09, 0x06, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, + 0x97, 0x00, 0x00, 0x00, 0x05, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x07, 0x06, + 0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x97, 0x03, 0x05, 0x0d, 0x2e, 0x2d, 0x33, + 0x3b, 0x09, 0x00, 0x03, 0x02, 0x0d, 0x2d, 0x01, 0x00, 0x00, 0x05, 0x0d, + 0x2e, 0x2d, 0x33, 0x3b, 0x04, 0x02, 0x33, 0x3b, 0x00, 0x00, 0x00, 0x05, + 0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x03, 0x00, 0x01, 0x03, 0x2d, 0x33, 0x3b, + 0x01, 0x01, 0x2d, 0x58, 0x00, 0x03, 0x02, 0x33, 0x3b, 0x02, 0x00, 0x00, + 0x02, 0x33, 0x3b, 0x59, 0x00, 0x00, 0x06, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, + 0x97, 0x00, 0x02, 0x33, 0x3b, 0x80, 0x12, 0x00, 0x0f, 0x01, 0x2d, 0x1f, + 0x00, 0x23, 0x01, 0x2d, 0x3b, 0x00, 0x27, 0x01, 0x2d, 0x37, 0x00, 0x30, + 0x01, 0x2d, 0x0e, 0x00, 0x0b, 0x01, 0x2d, 0x32, 0x00, 0x00, 0x01, 0x2d, + 0x57, 0x00, 0x18, 0x01, 0x2d, 0x09, 0x00, 0x04, 0x01, 0x2d, 0x5f, 0x00, + 0x1e, 0x01, 0x2d, 0xc0, 0x31, 0xef, 0x00, 0x00, 0x02, 0x1c, 0x26, 0x81, + 0x3f, 0x00, 0x02, 0x0e, 0x1e, 0x1f, 0x2a, 0x2c, 0x3f, 0x3a, 0x39, 0x4b, + 0x4c, 0x57, 0x5d, 0x40, 0x8a, 0x92, 0x02, 0x0d, 0x1e, 0x1f, 0x2a, 0x2c, + 0x3f, 0x3a, 0x39, 0x4b, 0x57, 0x5d, 0x40, 0x8a, 0x92, 0x03, 0x0b, 0x1e, + 0x1f, 0x2a, 0x2c, 0x3f, 0x39, 0x4b, 0x57, 0x40, 0x8a, 0x92, 0x80, 0x36, + 0x00, 0x00, 0x02, 0x0b, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x1e, 0x8b, 0x39, + 0x00, 0x00, 0x03, 0x3c, 0x42, 0x5b, 0x80, 0x1f, 0x00, 0x00, 0x02, 0x10, + 0x38, 0xc0, 0x13, 0xa1, 0x00, 0x00, 0x02, 0x04, 0x8e, 0x09, 0x00, 0x00, + 0x02, 0x04, 0x8e, 0x46, 0x00, 0x01, 0x05, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, + 0x80, 0x99, 0x00, 0x04, 0x06, 0x0d, 0x2e, 0x2d, 0x33, 0x3b, 0x97, 0x09, + 0x00, 0x00, 0x02, 0x33, 0x3b, 0x2c, 0x00, 0x01, 0x02, 0x33, 0x3b, 0x80, + 0xdf, 0x00, 0x02, 0x02, 0x1b, 0x46, 0x03, 0x00, 0x2c, 0x03, 0x1b, 0x45, + 0x46, 0x02, 0x00, 0x08, 0x02, 0x1b, 0x46, 0x81, 0x1f, 0x00, 0x1b, 0x02, + 0x04, 0x19, 0x8f, 0x84, 0x00, 0x00, 0x02, 0x28, 0x8b, 0x00, 0x00, 0x00, + 0x02, 0x28, 0x8b, 0x36, 0x00, 0x01, 0x02, 0x28, 0x8b, 0x8c, 0x12, 0x00, + 0x01, 0x02, 0x28, 0x8b, 0x00, 0x00, 0x00, 0x02, 0x28, 0x8b, 0xc0, 0x5c, + 0x4b, 0x00, 0x03, 0x01, 0x20, 0x96, 0x3b, 0x00, 0x11, 0x01, 0x2d, 0x9e, + 0x5d, 0x00, 0x01, 0x01, 0x2d, 0xce, 0xcd, 0x2d, 0x00, +}; + +static const uint8_t unicode_prop_Hyphen_table[28] = { + 0xac, 0x80, 0xfe, 0x80, 0x44, 0xdb, 0x80, 0x52, 0x7a, 0x80, + 0x48, 0x08, 0x81, 0x4e, 0x04, 0x80, 0x42, 0xe2, 0x80, 0x60, + 0xcd, 0x66, 0x80, 0x40, 0xa8, 0x80, 0xd6, 0x80, +}; + +static const uint8_t unicode_prop_Other_Math_table[200] = { + 0xdd, 0x80, 0x43, 0x70, 0x11, 0x80, 0x99, 0x09, 0x81, 0x5c, 0x1f, 0x80, + 0x9a, 0x82, 0x8a, 0x80, 0x9f, 0x83, 0x97, 0x81, 0x8d, 0x81, 0xc0, 0x8c, + 0x18, 0x11, 0x1c, 0x91, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28, 0x11, 0x09, + 0x02, 0x05, 0x13, 0x24, 0xca, 0x21, 0x18, 0x08, 0x08, 0x00, 0x21, 0x0b, + 0x0b, 0x91, 0x09, 0x00, 0x06, 0x00, 0x29, 0x41, 0x21, 0x83, 0x40, 0xa7, + 0x08, 0x80, 0x97, 0x80, 0x90, 0x80, 0x41, 0xbc, 0x81, 0x8b, 0x88, 0x24, + 0x21, 0x09, 0x14, 0x8d, 0x00, 0x01, 0x85, 0x97, 0x81, 0xb8, 0x00, 0x80, + 0x9c, 0x83, 0x88, 0x81, 0x41, 0x55, 0x81, 0x9e, 0x89, 0x41, 0x92, 0x95, + 0xbe, 0x83, 0x9f, 0x81, 0x60, 0xd4, 0x62, 0x00, 0x03, 0x80, 0x40, 0xd2, + 0x00, 0x80, 0x60, 0xd4, 0xc0, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, + 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, + 0x04, 0x00, 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x07, 0x81, 0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08, + 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, + 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, +}; + +static const uint8_t unicode_prop_Other_Alphabetic_table[396] = { + 0x43, 0x44, 0x80, 0x42, 0x69, 0x8d, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, + 0xaf, 0x8c, 0x06, 0x8f, 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, 0xa2, 0x80, + 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, 0x88, 0x02, 0x03, 0x40, 0xa6, 0x8b, + 0x16, 0x85, 0x93, 0xb5, 0x09, 0x8e, 0x01, 0x22, 0x89, 0x81, 0x9c, 0x82, + 0xb9, 0x31, 0x09, 0x81, 0x89, 0x80, 0x89, 0x81, 0x9c, 0x82, 0xb9, 0x23, + 0x09, 0x0b, 0x80, 0x9d, 0x0a, 0x80, 0x8a, 0x82, 0xb9, 0x38, 0x10, 0x81, + 0x94, 0x81, 0x95, 0x13, 0x82, 0xb9, 0x31, 0x09, 0x81, 0x88, 0x81, 0x89, + 0x81, 0x9d, 0x80, 0xba, 0x22, 0x10, 0x82, 0x89, 0x80, 0xa7, 0x83, 0xb9, + 0x30, 0x10, 0x17, 0x81, 0x8a, 0x81, 0x9c, 0x82, 0xb9, 0x30, 0x10, 0x17, + 0x81, 0x8a, 0x81, 0x9b, 0x83, 0xb9, 0x30, 0x10, 0x82, 0x89, 0x80, 0x89, + 0x81, 0x9d, 0x81, 0xca, 0x28, 0x00, 0x87, 0x91, 0x81, 0xbc, 0x01, 0x86, + 0x91, 0x80, 0xe2, 0x01, 0x28, 0x81, 0x8f, 0x80, 0x40, 0xa2, 0x90, 0x8a, + 0x8a, 0x80, 0xa3, 0xed, 0x8b, 0x00, 0x0b, 0x96, 0x1b, 0x10, 0x11, 0x32, + 0x83, 0x8c, 0x8b, 0x00, 0x89, 0x83, 0x46, 0x73, 0x81, 0x9d, 0x81, 0x9d, + 0x81, 0x9d, 0x81, 0xc1, 0x92, 0x40, 0xbb, 0x81, 0xa1, 0x80, 0xf5, 0x8b, + 0x83, 0x88, 0x40, 0xdd, 0x84, 0xb8, 0x89, 0x81, 0x93, 0x40, 0x8a, 0x84, + 0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, 0xb8, 0x8a, 0xb1, 0x92, 0x41, + 0xaf, 0x8d, 0x46, 0xc0, 0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, 0x87, + 0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, 0x84, 0xd7, 0x81, 0xb1, 0x8f, + 0x00, 0xb8, 0x80, 0xa5, 0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, 0xa4, + 0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, 0x82, 0xb1, 0x00, 0x11, 0x0c, + 0x80, 0xab, 0x24, 0x80, 0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, 0x48, + 0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0x43, 0x13, 0x83, 0x42, 0xd7, + 0x82, 0xb4, 0x8d, 0xbb, 0x80, 0xac, 0x88, 0xc6, 0x82, 0xa3, 0x8b, 0x91, + 0x81, 0xb8, 0x82, 0xaf, 0x8c, 0xeb, 0x88, 0x08, 0x28, 0x40, 0x9f, 0x89, + 0x96, 0x83, 0xb9, 0x31, 0x09, 0x81, 0x89, 0x80, 0x89, 0x81, 0x40, 0xd0, + 0x8c, 0x02, 0xe9, 0x91, 0x40, 0xec, 0x31, 0x86, 0x9c, 0x81, 0xd1, 0x8e, + 0x00, 0xe9, 0x8a, 0xe6, 0x8d, 0x41, 0x00, 0x8c, 0x41, 0x97, 0x31, 0x2b, + 0x80, 0x9b, 0x89, 0xa9, 0x20, 0x83, 0x91, 0x8a, 0xad, 0x8d, 0x41, 0x96, + 0x38, 0x86, 0xd2, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, 0x10, 0x02, + 0x80, 0xc1, 0x20, 0x08, 0x83, 0x41, 0x5b, 0x83, 0x60, 0x50, 0x57, 0x00, + 0xb6, 0x33, 0x60, 0x4d, 0x0a, 0x80, 0x60, 0x23, 0x60, 0x30, 0x90, 0x0e, + 0x01, 0x04, 0x49, 0x1b, 0x80, 0x47, 0xe7, 0x99, 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Lowercase_table[51] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88, 0x31, 0x9d, 0x84, + 0xdf, 0x80, 0xb3, 0x80, 0x59, 0xb0, 0xbe, 0x8c, 0x80, 0xa1, 0xa4, + 0x42, 0xb0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, 0x40, 0xd2, 0x8f, 0x43, + 0x4f, 0x99, 0x47, 0x91, 0x81, 0x60, 0x7a, 0x1d, 0x81, 0x40, 0xd1, + 0x80, 0x40, 0x86, 0x81, 0x43, 0x61, 0x83, +}; + +static const uint8_t unicode_prop_Other_Uppercase_table[15] = { + 0x60, 0x21, 0x5f, 0x8f, 0x43, 0x45, 0x99, 0x61, + 0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Grapheme_Extend_table[62] = { + 0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80, 0x97, 0x80, 0xe5, + 0x80, 0x97, 0x80, 0x40, 0xe9, 0x80, 0x91, 0x81, 0xe6, 0x80, 0x97, + 0x80, 0xf6, 0x80, 0x8e, 0x80, 0x4d, 0x54, 0x80, 0x44, 0xd5, 0x80, + 0x50, 0x20, 0x81, 0x60, 0xcf, 0x6d, 0x81, 0x53, 0x9d, 0x80, 0x97, + 0x80, 0x41, 0x57, 0x80, 0x8b, 0x80, 0x40, 0xf0, 0x80, 0x60, 0xbb, + 0xb4, 0x07, 0x84, 0x6c, 0x2e, 0xac, 0xdf, +}; + +static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table[32] = + { + 0x43, 0x4e, 0x80, 0x4e, 0x0e, 0x81, 0x46, 0x52, 0x81, 0x48, 0xae, + 0x80, 0x50, 0xfd, 0x80, 0x60, 0xce, 0x3a, 0x80, 0xce, 0x88, 0x6d, + 0x00, 0x06, 0x00, 0x9d, 0xdf, 0xff, 0x40, 0xef, 0x4e, 0x0f, +}; + +static const uint8_t unicode_prop_Other_ID_Start_table[11] = { + 0x58, 0x84, 0x81, 0x48, 0x90, 0x80, 0x94, 0x80, 0x4f, 0x6b, 0x81, +}; + +static const uint8_t unicode_prop_Other_ID_Continue_table[12] = { + 0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0, 0x88, 0x46, 0x67, 0x80, +}; + +static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table[17] = { + 0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80, 0x41, + 0xd1, 0x80, 0x61, 0x07, 0xd9, 0x80, 0x8e, 0x80, +}; + +static const uint8_t unicode_prop_XID_Start1_table[31] = { + 0x43, 0x79, 0x80, 0x4a, 0xb7, 0x80, 0xfe, 0x80, 0x60, 0x21, 0xe6, + 0x81, 0x60, 0xcb, 0xc0, 0x85, 0x41, 0x95, 0x81, 0xf3, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x41, 0x1e, 0x81, +}; + +static const uint8_t unicode_prop_XID_Continue1_table[23] = { + 0x43, 0x79, 0x80, 0x60, 0x2d, 0x1f, 0x81, 0x60, 0xcb, 0xc0, 0x85, 0x41, + 0x95, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Titlecased1_table[22] = { + 0x41, 0xc3, 0x08, 0x08, 0x81, 0xa4, 0x81, 0x4e, 0xdc, 0xaa, 0x0a, + 0x4e, 0x87, 0x3f, 0x3f, 0x87, 0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Casefolded1_table[33] = { + 0x40, 0xde, 0x80, 0xcf, 0x80, 0x97, 0x80, 0x44, 0x3c, 0x80, 0x59, + 0x11, 0x80, 0x40, 0xe4, 0x3f, 0x3f, 0x87, 0x89, 0x11, 0x05, 0x02, + 0x11, 0x80, 0xa9, 0x11, 0x80, 0x60, 0xdb, 0x07, 0x86, 0x8b, 0x84, +}; + +static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table[436] = { + 0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12, 0x10, 0x82, 0x9f, 0x80, + 0xcf, 0x01, 0x80, 0x8b, 0x07, 0x80, 0xfb, 0x01, 0x01, 0x80, 0xa5, 0x80, + 0x40, 0xbb, 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, 0x81, 0x89, 0x80, 0xa3, + 0x04, 0x02, 0x04, 0x08, 0x80, 0xc9, 0x82, 0x9c, 0x80, 0x41, 0x93, 0x80, + 0x40, 0x93, 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, 0xfb, 0x08, 0x80, 0xd2, + 0x01, 0x80, 0xa1, 0x11, 0x80, 0x40, 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, + 0x80, 0xa7, 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, 0x03, 0x03, 0x03, 0x80, + 0x8b, 0x80, 0x88, 0x00, 0x26, 0x80, 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, + 0x80, 0x8b, 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, 0x46, 0x52, 0x81, 0xd4, + 0x83, 0x45, 0x1c, 0x10, 0x8a, 0x80, 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, + 0xa4, 0x40, 0xd9, 0x80, 0x40, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x3f, 0x3f, 0x87, 0x89, 0x11, 0x04, 0x00, 0x29, 0x04, 0x12, 0x80, + 0x88, 0x12, 0x80, 0x88, 0x11, 0x11, 0x04, 0x08, 0x8f, 0x00, 0x20, 0x8b, + 0x12, 0x2a, 0x08, 0x0b, 0x00, 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, + 0x80, 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, 0x01, 0x0c, 0x0a, 0x00, + 0x10, 0x11, 0x02, 0x06, 0x05, 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, + 0x40, 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, 0x34, 0xd5, 0x99, 0x9a, + 0x45, 0x20, 0x80, 0xe6, 0x82, 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, + 0x80, 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, 0xd5, 0xa9, 0x80, 0xb4, + 0x00, 0x82, 0xdf, 0x09, 0x80, 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, + 0x9e, 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, 0x60, 0x72, 0x9b, 0x81, + 0x40, 0xd1, 0x80, 0x40, 0x86, 0x81, 0x43, 0x61, 0x83, 0x60, 0x4d, 0x9f, + 0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, 0x00, 0x00, 0x09, 0x82, 0xc3, 0x81, + 0xe9, 0xa5, 0x86, 0x8b, 0x24, 0x00, 0x97, 0x04, 0x00, 0x01, 0x01, 0x80, + 0xeb, 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, 0x8c, 0x82, 0x99, + 0x95, 0x94, 0x81, 0x8b, 0x80, 0x92, 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86, + 0x08, 0x80, 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d, 0x0d, 0x0a, 0x16, 0x06, + 0x80, 0x88, 0x60, 0xbc, 0xa6, 0x83, 0x54, 0xb9, 0x86, 0x8d, 0x87, 0xbf, + 0x85, 0x42, 0x3e, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, 0x80, 0x8b, + 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x41, 0x23, 0x81, 0xb1, 0x55, 0xff, 0x18, + 0x9a, 0x01, 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, + 0x06, 0x03, 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, + 0x42, 0x43, 0x8a, 0x84, 0x9e, 0x80, 0x9f, 0x99, 0x82, 0xa2, 0x80, 0xee, + 0x82, 0x8c, 0xab, 0x83, 0x88, 0x31, 0x61, 0x05, 0xad, 0x42, 0x1d, 0x6b, + 0x05, 0xe1, 0x4f, 0xff, +}; + +static const uint8_t unicode_prop_ASCII_Hex_Digit_table[5] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_Bidi_Control_table[10] = { + 0x46, 0x1b, 0x80, 0x59, 0xf0, 0x81, 0x99, 0x84, 0xb6, 0x83, +}; + +static const uint8_t unicode_prop_Dash_table[50] = { + 0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e, 0x40, 0x80, + 0x44, 0x04, 0x80, 0x48, 0x08, 0x85, 0xbc, 0x80, 0xa6, 0x80, + 0x8e, 0x80, 0x41, 0x85, 0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, + 0x0b, 0x80, 0x41, 0xda, 0x80, 0x92, 0x80, 0xee, 0x80, 0x60, + 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, 0x80, 0x40, 0xa8, 0x80, +}; + +static const uint8_t unicode_prop_Deprecated_table[23] = { + 0x41, 0x48, 0x80, 0x45, 0x28, 0x80, 0x49, 0x02, 0x00, 0x80, 0x48, 0x28, + 0x81, 0x48, 0xc4, 0x85, 0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80, +}; + +static const uint8_t unicode_prop_Diacritic_table[350] = { + 0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81, 0x41, 0xf6, 0x40, 0x9e, + 0x07, 0x25, 0x90, 0x0b, 0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0, + 0x80, 0xb6, 0x90, 0x80, 0x9a, 0x00, 0x01, 0x00, 0x40, 0x85, 0x3b, 0x81, + 0x40, 0x85, 0x0b, 0x0a, 0x82, 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1, + 0x81, 0x40, 0xc8, 0x9b, 0xbc, 0x80, 0x8f, 0x02, 0x83, 0x9b, 0x80, 0xc9, + 0x80, 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xae, + 0x82, 0xbb, 0x80, 0x8f, 0x80, 0xfe, 0x80, 0xfe, 0x80, 0xed, 0x80, 0x8f, + 0x80, 0xec, 0x81, 0x8f, 0x80, 0xfb, 0x80, 0xfb, 0x28, 0x80, 0xea, 0x80, + 0x8c, 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03, 0x81, 0xc1, 0x10, 0x81, + 0xbd, 0x80, 0xef, 0x00, 0x81, 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89, + 0x81, 0x42, 0xc0, 0x82, 0x44, 0x68, 0x8a, 0x88, 0x80, 0x41, 0x5a, 0x82, + 0x41, 0x38, 0x39, 0x80, 0xaf, 0x8d, 0xf5, 0x80, 0x8e, 0x80, 0xa5, 0x88, + 0xb5, 0x81, 0x40, 0x89, 0x81, 0xbf, 0x85, 0xd1, 0x98, 0x18, 0x28, 0x0a, + 0xb1, 0xbe, 0xd8, 0x8b, 0xa4, 0x22, 0x82, 0x41, 0xbc, 0x00, 0x82, 0x8a, + 0x82, 0x8c, 0x82, 0x8c, 0x82, 0x8c, 0x81, 0x4c, 0xef, 0x82, 0x41, 0x3c, + 0x80, 0x41, 0xf9, 0x85, 0xe8, 0x83, 0xde, 0x80, 0x60, 0x75, 0x71, 0x80, + 0x8b, 0x08, 0x80, 0x9b, 0x81, 0xd1, 0x81, 0x8d, 0xa1, 0xe5, 0x82, 0xec, + 0x81, 0x40, 0xc9, 0x80, 0x9a, 0x91, 0xb8, 0x83, 0xa3, 0x80, 0xde, 0x80, + 0x8b, 0x80, 0xa3, 0x80, 0x40, 0x94, 0x82, 0xc0, 0x83, 0xb2, 0x80, 0xe3, + 0x84, 0x40, 0x8b, 0x81, 0x60, 0x4f, 0x2f, 0x80, 0x43, 0x00, 0x8f, 0x41, + 0x0d, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, 0x80, 0x42, 0xfb, 0x80, + 0x48, 0x03, 0x81, 0x42, 0x3a, 0x85, 0x42, 0x1d, 0x8a, 0x41, 0x67, 0x81, + 0xf7, 0x81, 0xbd, 0x80, 0xcb, 0x80, 0x88, 0x82, 0xe7, 0x81, 0x40, 0xb1, + 0x81, 0xd0, 0x80, 0x8f, 0x80, 0x97, 0x32, 0x84, 0x40, 0xcc, 0x02, 0x80, + 0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, 0x80, 0xf5, 0x81, 0xf2, 0x80, 0x41, + 0x0c, 0x81, 0x41, 0xa4, 0x80, 0xd2, 0x80, 0x91, 0x80, 0xd0, 0x80, 0x41, + 0xa4, 0x80, 0x41, 0x01, 0x00, 0x81, 0xd0, 0x80, 0x60, 0x4d, 0x57, 0x84, + 0xba, 0x86, 0x44, 0x57, 0x90, 0x60, 0x61, 0xc6, 0x12, 0x2f, 0x39, 0x86, + 0x9d, 0x83, 0x4f, 0x81, 0x86, 0x41, 0xb4, 0x83, 0x45, 0xdf, 0x86, 0xec, + 0x10, 0x82, +}; + +static const uint8_t unicode_prop_Extender_table[86] = { + 0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d, 0x80, 0x41, 0xb8, + 0x80, 0x46, 0x4a, 0x80, 0xfe, 0x80, 0x49, 0x42, 0x80, 0xb7, 0x80, + 0x42, 0x62, 0x80, 0x41, 0x8d, 0x80, 0xc3, 0x80, 0x53, 0x88, 0x80, + 0xaa, 0x84, 0xe6, 0x81, 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, 0x45, + 0xf5, 0x80, 0x43, 0xc1, 0x80, 0x95, 0x80, 0x40, 0x88, 0x80, 0xeb, + 0x80, 0x94, 0x81, 0x60, 0x54, 0x7a, 0x80, 0x53, 0xeb, 0x80, 0x42, + 0x67, 0x82, 0x44, 0xce, 0x80, 0x60, 0x50, 0xa8, 0x81, 0x44, 0x9b, + 0x08, 0x80, 0x60, 0x71, 0x57, 0x81, 0x48, 0x05, 0x82, +}; + +static const uint8_t unicode_prop_Hex_Digit_table[12] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, 0x60, 0xfe, 0xa8, 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_IDS_Binary_Operator_table[5] = { + 0x60, 0x2f, 0xef, 0x09, 0x87, +}; + +static const uint8_t unicode_prop_IDS_Trinary_Operator_table[4] = { + 0x60, + 0x2f, + 0xf1, + 0x81, +}; + +static const uint8_t unicode_prop_Ideographic_table[58] = { + 0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82, 0x43, 0xc4, 0x59, 0xb5, + 0xc9, 0x60, 0x51, 0xef, 0x60, 0x59, 0x0f, 0x41, 0x6d, 0x81, 0xe9, 0x60, + 0x75, 0x25, 0x57, 0xf7, 0x87, 0x42, 0xf2, 0x60, 0x26, 0x7c, 0x41, 0x8b, + 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xd6, 0xa8, 0x50, 0x34, 0x8a, 0x40, 0xdd, + 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x4c, 0x1e, 0x42, 0x1d, +}; + +static const uint8_t unicode_prop_Join_Control_table[4] = { + 0x60, + 0x20, + 0x0b, + 0x81, +}; + +static const uint8_t unicode_prop_Logical_Order_Exception_table[15] = { + 0x4e, 0x3f, 0x84, 0xfa, 0x84, 0x4a, 0xef, 0x11, + 0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81, +}; + +static const uint8_t unicode_prop_Noncharacter_Code_Point_table[71] = { + 0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_Syntax_table[58] = { + 0xa0, 0x8e, 0x89, 0x86, 0x99, 0x18, 0x80, 0x99, 0x83, 0xa1, 0x30, 0x00, + 0x08, 0x00, 0x0b, 0x03, 0x02, 0x80, 0x96, 0x80, 0x9e, 0x80, 0x5f, 0x17, + 0x97, 0x87, 0x8e, 0x81, 0x92, 0x80, 0x89, 0x41, 0x30, 0x42, 0xcf, 0x40, + 0x9f, 0x42, 0x75, 0x9d, 0x44, 0x6b, 0x41, 0xff, 0xff, 0x41, 0x80, 0x13, + 0x98, 0x8e, 0x80, 0x60, 0xcd, 0x0c, 0x81, 0x41, 0x04, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_White_Space_table[11] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x5f, 0x87, 0x81, 0x97, 0x81, +}; + +static const uint8_t unicode_prop_Quotation_Mark_table[31] = { + 0xa1, 0x03, 0x80, 0x40, 0x82, 0x80, 0x8e, 0x80, 0x5f, 0x5b, 0x87, + 0x98, 0x81, 0x4e, 0x06, 0x80, 0x41, 0xc8, 0x83, 0x8c, 0x82, 0x60, + 0xce, 0x20, 0x83, 0x40, 0xbc, 0x03, 0x80, 0xd9, 0x81, +}; + +static const uint8_t unicode_prop_Radical_table[9] = { + 0x60, 0x2e, 0x7f, 0x99, 0x80, 0xd8, 0x8b, 0x40, 0xd5, +}; + +static const uint8_t unicode_prop_Regional_Indicator_table[4] = { + 0x61, + 0xf1, + 0xe5, + 0x99, +}; + +static const uint8_t unicode_prop_Sentence_Terminal_table[184] = { + 0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48, 0x80, 0x40, 0x93, 0x81, + 0x40, 0xb3, 0x80, 0xaa, 0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81, + 0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15, 0x03, 0x81, 0x43, 0x04, + 0x80, 0x40, 0xc5, 0x81, 0x40, 0xcb, 0x04, 0x80, 0x41, 0x39, 0x81, 0x41, + 0x61, 0x83, 0x40, 0xad, 0x09, 0x81, 0x40, 0xda, 0x81, 0xc0, 0x81, 0x43, + 0xbb, 0x81, 0x88, 0x82, 0x4d, 0xe3, 0x80, 0x8c, 0x80, 0x41, 0xc4, 0x80, + 0x60, 0x74, 0xfb, 0x80, 0x41, 0x0d, 0x81, 0x40, 0xe2, 0x02, 0x80, 0x41, + 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x97, 0x81, 0x40, 0x92, 0x82, + 0x40, 0x8f, 0x81, 0x40, 0xf8, 0x80, 0x60, 0x52, 0x65, 0x02, 0x81, 0x40, + 0xa8, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0xc0, 0x80, 0x4a, 0xf3, 0x81, 0x44, + 0xfc, 0x84, 0x40, 0xec, 0x81, 0xf4, 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, + 0x80, 0x8f, 0x81, 0xd7, 0x08, 0x81, 0xeb, 0x80, 0x41, 0xa0, 0x81, 0x41, + 0x74, 0x0c, 0x8e, 0xe8, 0x81, 0x40, 0xf8, 0x82, 0x43, 0x02, 0x81, 0xd6, + 0x81, 0x41, 0xa3, 0x81, 0x42, 0xb3, 0x81, 0x60, 0x4b, 0x74, 0x81, 0x40, + 0x84, 0x80, 0xc0, 0x81, 0x8a, 0x80, 0x43, 0x52, 0x80, 0x60, 0x4e, 0x05, + 0x80, 0x5d, 0xe7, 0x80, +}; + +static const uint8_t unicode_prop_Soft_Dotted_table[71] = { + 0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80, 0x9d, 0x80, 0xb3, 0x80, + 0x93, 0x80, 0x41, 0x3f, 0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2, + 0x80, 0x8c, 0x02, 0x80, 0x40, 0x83, 0x80, 0x40, 0x9c, 0x80, 0x41, 0xa4, + 0x80, 0x40, 0xd5, 0x81, 0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, +}; + +static const uint8_t unicode_prop_Terminal_Punctuation_table[237] = { + 0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, 0x43, 0x3d, 0x07, 0x80, + 0x42, 0x00, 0x80, 0xb8, 0x80, 0xc7, 0x80, 0x8d, 0x01, 0x81, 0x40, 0xb3, + 0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5, 0x8e, 0x9e, 0x80, 0x41, + 0x04, 0x81, 0x44, 0xf3, 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, 0x81, + 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, 0x82, 0xc6, 0x81, 0x40, 0x9c, + 0x12, 0x80, 0xa6, 0x19, 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, 0x40, + 0xad, 0x08, 0x82, 0x40, 0xda, 0x84, 0xbd, 0x81, 0x43, 0xbb, 0x81, 0x88, + 0x82, 0x4d, 0xe3, 0x80, 0x8c, 0x03, 0x80, 0x89, 0x00, 0x81, 0x41, 0xb0, + 0x81, 0x60, 0x74, 0xfa, 0x81, 0x41, 0x0c, 0x82, 0x40, 0xe2, 0x84, 0x41, + 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x96, 0x82, 0x40, 0x92, 0x82, + 0xfe, 0x80, 0x8f, 0x81, 0x40, 0xf8, 0x80, 0x60, 0x52, 0x63, 0x10, 0x83, + 0x40, 0xa8, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80, + 0x44, 0x39, 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80, 0x40, 0xc6, 0x80, 0x41, + 0x35, 0x81, 0x40, 0x97, 0x85, 0xc3, 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84, + 0x40, 0xec, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, + 0x81, 0xd7, 0x84, 0xeb, 0x80, 0x41, 0xa0, 0x82, 0x8c, 0x80, 0x41, 0x65, + 0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, 0x82, 0x43, 0x02, 0x81, 0xd6, 0x0b, + 0x81, 0x41, 0x9d, 0x82, 0xac, 0x80, 0x42, 0x84, 0x81, 0x45, 0x76, 0x84, + 0x60, 0x45, 0xf8, 0x81, 0x40, 0x84, 0x80, 0xc0, 0x82, 0x89, 0x80, 0x43, + 0x51, 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, 0x83, +}; + +static const uint8_t unicode_prop_Unified_Ideograph_table[38] = { + 0x60, 0x33, 0xff, 0x59, 0xb5, 0xc9, 0x60, 0x51, 0xef, 0x60, + 0x5a, 0x1d, 0x08, 0x00, 0x81, 0x89, 0x00, 0x00, 0x09, 0x82, + 0x61, 0x05, 0xd5, 0x60, 0xa6, 0xd6, 0xa8, 0x50, 0x34, 0x8a, + 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, +}; + +static const uint8_t unicode_prop_Variation_Selector_table[12] = { + 0x58, 0x0a, 0x82, 0x60, 0xe5, 0xf1, 0x8f, 0x6d, 0x02, 0xef, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_White_Space_table[22] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x99, 0x80, 0x55, 0xde, 0x80, + 0x49, 0x7e, 0x8a, 0x9c, 0x0c, 0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80, +}; + +static const uint8_t unicode_prop_Bidi_Mirrored_table[171] = { + 0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x80, 0xac, + 0x80, 0x8e, 0x80, 0x4e, 0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81, + 0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0, 0x80, 0x40, 0xbf, 0x1a, + 0x2a, 0x02, 0x0a, 0x18, 0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23, + 0x88, 0x08, 0x00, 0x39, 0x9e, 0x0b, 0x20, 0x88, 0x09, 0x92, 0x21, 0x88, + 0x21, 0x0b, 0x97, 0x81, 0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d, + 0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d, 0x41, 0x92, 0x95, 0x0d, + 0x80, 0x8d, 0x38, 0x35, 0x10, 0x1c, 0x01, 0x0c, 0x18, 0x02, 0x09, 0x89, + 0x29, 0x81, 0x8b, 0x92, 0x03, 0x08, 0x00, 0x08, 0x03, 0x21, 0x2a, 0x97, + 0x81, 0x8a, 0x0b, 0x18, 0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00, + 0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80, 0x42, 0x02, 0x1a, 0x08, + 0x81, 0x8d, 0x09, 0x89, 0x41, 0xdd, 0x89, 0x0f, 0x60, 0xce, 0x3c, 0x2c, + 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, + 0x00, 0x08, 0x81, 0x60, 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, 0x80, 0xb8, + 0x80, 0xb8, 0x80, +}; + +static const uint8_t unicode_prop_Emoji_table[236] = { + 0xa2, 0x05, 0x04, 0x89, 0xee, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, 0x80, + 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81, + 0x8b, 0x80, 0x40, 0xa5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6, 0x80, 0x40, + 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, 0xb9, 0x18, 0x84, 0x88, 0x01, 0x01, + 0x09, 0x03, 0x01, 0x00, 0x09, 0x02, 0x02, 0x0f, 0x14, 0x00, 0x04, 0x8b, + 0x8a, 0x09, 0x00, 0x08, 0x80, 0x91, 0x01, 0x81, 0x91, 0x28, 0x00, 0x0a, + 0x0f, 0x0b, 0x81, 0x8a, 0x0c, 0x09, 0x04, 0x08, 0x00, 0x81, 0x93, 0x0c, + 0x28, 0x19, 0x03, 0x01, 0x01, 0x28, 0x01, 0x00, 0x00, 0x05, 0x02, 0x05, + 0x80, 0x89, 0x81, 0x8e, 0x01, 0x03, 0x00, 0x03, 0x10, 0x80, 0x8a, 0x81, + 0xaf, 0x82, 0x88, 0x80, 0x8d, 0x80, 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, + 0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, 0x80, 0x44, 0xd9, 0x80, 0x8b, 0x80, + 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd, 0x69, 0x80, 0x40, 0xc9, 0x80, 0x40, + 0x9f, 0x81, 0x8b, 0x81, 0x8d, 0x01, 0x89, 0xca, 0x99, 0x01, 0x96, 0x80, + 0x93, 0x01, 0x88, 0x94, 0x81, 0x40, 0xad, 0xa1, 0x81, 0xef, 0x09, 0x02, + 0x81, 0xd2, 0x0a, 0x80, 0x41, 0x06, 0x80, 0xbe, 0x8a, 0x28, 0x97, 0x31, + 0x0f, 0x8b, 0x01, 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, 0x04, + 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, 0x02, 0x05, 0xd5, 0xaf, 0xc5, + 0x27, 0x08, 0x89, 0x2a, 0x00, 0x0a, 0x01, 0x87, 0x40, 0xe4, 0x8b, 0x41, + 0x20, 0xad, 0x80, 0x89, 0x80, 0xaa, 0x03, 0x82, 0xa8, 0x0d, 0x82, 0x9c, + 0x81, 0xb2, 0xef, 0x1b, 0x14, 0x82, 0x8c, 0x85, +}; + +static const uint8_t unicode_prop_Emoji_Component_table[28] = { + 0xa2, 0x05, 0x04, 0x89, 0x5f, 0xd2, 0x80, 0x40, 0xd4, 0x80, + 0x60, 0xdd, 0x2a, 0x80, 0x60, 0xf3, 0xd5, 0x99, 0x41, 0xfa, + 0x84, 0x45, 0xaf, 0x83, 0x6c, 0x06, 0x6b, 0xdf, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_table[4] = { + 0x61, + 0xf3, + 0xfa, + 0x84, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_Base_table[63] = { + 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, 0x83, 0x61, 0xcc, + 0x76, 0x80, 0xbb, 0x11, 0x01, 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, + 0x10, 0x1a, 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, 0xc8, 0x0b, 0x80, + 0x94, 0x03, 0x81, 0x40, 0xad, 0x12, 0x84, 0xd2, 0x80, 0x8f, 0x82, + 0x88, 0x80, 0x8a, 0x80, 0x42, 0x41, 0x07, 0x3d, 0x80, 0x88, 0x89, + 0x0a, 0xf5, 0x08, 0x08, 0x80, 0x90, 0x10, 0x8c, +}; + +static const uint8_t unicode_prop_Emoji_Presentation_table[143] = { + 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, 0x80, 0x42, 0x08, 0x81, + 0x94, 0x81, 0xb1, 0x8b, 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, + 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, 0x01, 0x06, 0x03, 0x81, + 0x9b, 0x80, 0xa2, 0x00, 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, + 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, 0xc4, 0xad, 0x80, 0x40, + 0xc9, 0x80, 0x40, 0xbd, 0x01, 0x89, 0xca, 0x99, 0x00, 0x97, 0x80, 0x93, + 0x01, 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, 0x8b, 0x88, 0x80, 0xc5, + 0x80, 0x95, 0x8b, 0xaa, 0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, + 0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, 0x80, 0x99, 0x81, 0x8c, + 0x80, 0xd5, 0xd4, 0xaf, 0xc5, 0x28, 0x12, 0x08, 0x94, 0x0e, 0x86, 0x40, + 0xe4, 0x8b, 0x41, 0x20, 0xad, 0x80, 0x89, 0x80, 0xaa, 0x03, 0x82, 0xa8, + 0x0d, 0x82, 0x9c, 0x81, 0xb2, 0xef, 0x1b, 0x14, 0x82, 0x8c, 0x85, +}; + +static const uint8_t unicode_prop_Extended_Pictographic_table[152] = { + 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, 0x80, 0x40, 0xd7, 0x80, + 0x95, 0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81, 0x8b, 0x80, 0xde, + 0x80, 0xc5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6, 0x80, 0x40, 0xe6, 0x81, + 0x89, 0x80, 0x88, 0x80, 0xb9, 0x18, 0x28, 0x8b, 0x80, 0xf1, 0x89, 0xf5, + 0x81, 0x8a, 0x00, 0x00, 0x28, 0x10, 0x28, 0x89, 0x81, 0x8e, 0x01, 0x03, + 0x00, 0x03, 0x10, 0x80, 0x8a, 0x84, 0xac, 0x82, 0x88, 0x80, 0x8d, 0x80, + 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, 0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, + 0x80, 0x44, 0xd9, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd, + 0x65, 0x40, 0xff, 0x8c, 0x82, 0x9e, 0x80, 0xbb, 0x85, 0x8b, 0x81, 0x8d, + 0x01, 0x89, 0x91, 0xb8, 0x9a, 0x8e, 0x89, 0x80, 0x93, 0x01, 0x88, 0x03, + 0x88, 0x41, 0xb1, 0x84, 0x41, 0x3d, 0x87, 0x41, 0x09, 0xaf, 0xff, 0xf3, + 0x8b, 0xd4, 0xaa, 0x8b, 0x83, 0xb7, 0x87, 0x89, 0x85, 0xa7, 0x87, 0x9d, + 0xd1, 0x8b, 0xae, 0x80, 0x89, 0x80, 0x46, 0xb6, +}; + +static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table[51] = { + 0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb, 0x80, 0x4b, 0x41, + 0x81, 0x46, 0x52, 0x81, 0xd4, 0x83, 0x47, 0xfb, 0x84, 0x99, 0x84, + 0xb0, 0x8f, 0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40, 0xee, + 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60, 0xbc, 0xa6, 0x83, 0x54, + 0xce, 0x87, 0x6c, 0x2e, 0x84, 0x4f, 0xff, +}; + +typedef enum { + UNICODE_PROP_Hyphen, + UNICODE_PROP_Other_Math, + UNICODE_PROP_Other_Alphabetic, + UNICODE_PROP_Other_Lowercase, + UNICODE_PROP_Other_Uppercase, + UNICODE_PROP_Other_Grapheme_Extend, + UNICODE_PROP_Other_Default_Ignorable_Code_Point, + UNICODE_PROP_Other_ID_Start, + UNICODE_PROP_Other_ID_Continue, + UNICODE_PROP_Prepended_Concatenation_Mark, + UNICODE_PROP_ID_Continue1, + UNICODE_PROP_XID_Start1, + UNICODE_PROP_XID_Continue1, + UNICODE_PROP_Changes_When_Titlecased1, + UNICODE_PROP_Changes_When_Casefolded1, + UNICODE_PROP_Changes_When_NFKC_Casefolded1, + UNICODE_PROP_ASCII_Hex_Digit, + UNICODE_PROP_Bidi_Control, + UNICODE_PROP_Dash, + UNICODE_PROP_Deprecated, + UNICODE_PROP_Diacritic, + UNICODE_PROP_Extender, + UNICODE_PROP_Hex_Digit, + UNICODE_PROP_IDS_Binary_Operator, + UNICODE_PROP_IDS_Trinary_Operator, + UNICODE_PROP_Ideographic, + UNICODE_PROP_Join_Control, + UNICODE_PROP_Logical_Order_Exception, + UNICODE_PROP_Noncharacter_Code_Point, + UNICODE_PROP_Pattern_Syntax, + UNICODE_PROP_Pattern_White_Space, + UNICODE_PROP_Quotation_Mark, + UNICODE_PROP_Radical, + UNICODE_PROP_Regional_Indicator, + UNICODE_PROP_Sentence_Terminal, + UNICODE_PROP_Soft_Dotted, + UNICODE_PROP_Terminal_Punctuation, + UNICODE_PROP_Unified_Ideograph, + UNICODE_PROP_Variation_Selector, + UNICODE_PROP_White_Space, + UNICODE_PROP_Bidi_Mirrored, + UNICODE_PROP_Emoji, + UNICODE_PROP_Emoji_Component, + UNICODE_PROP_Emoji_Modifier, + UNICODE_PROP_Emoji_Modifier_Base, + UNICODE_PROP_Emoji_Presentation, + UNICODE_PROP_Extended_Pictographic, + UNICODE_PROP_Default_Ignorable_Code_Point, + UNICODE_PROP_ID_Start, + UNICODE_PROP_Case_Ignorable, + UNICODE_PROP_ASCII, + UNICODE_PROP_Alphabetic, + UNICODE_PROP_Any, + UNICODE_PROP_Assigned, + UNICODE_PROP_Cased, + UNICODE_PROP_Changes_When_Casefolded, + UNICODE_PROP_Changes_When_Casemapped, + UNICODE_PROP_Changes_When_Lowercased, + UNICODE_PROP_Changes_When_NFKC_Casefolded, + UNICODE_PROP_Changes_When_Titlecased, + UNICODE_PROP_Changes_When_Uppercased, + UNICODE_PROP_Grapheme_Base, + UNICODE_PROP_Grapheme_Extend, + UNICODE_PROP_ID_Continue, + UNICODE_PROP_Lowercase, + UNICODE_PROP_Math, + UNICODE_PROP_Uppercase, + UNICODE_PROP_XID_Continue, + UNICODE_PROP_XID_Start, + UNICODE_PROP_Cased1, + UNICODE_PROP_COUNT, +} UnicodePropertyEnum; + +static const char unicode_prop_name_table[] = + "ASCII_Hex_Digit,AHex" + "\0" + "Bidi_Control,Bidi_C" + "\0" + "Dash" + "\0" + "Deprecated,Dep" + "\0" + "Diacritic,Dia" + "\0" + "Extender,Ext" + "\0" + "Hex_Digit,Hex" + "\0" + "IDS_Binary_Operator,IDSB" + "\0" + "IDS_Trinary_Operator,IDST" + "\0" + "Ideographic,Ideo" + "\0" + "Join_Control,Join_C" + "\0" + "Logical_Order_Exception,LOE" + "\0" + "Noncharacter_Code_Point,NChar" + "\0" + "Pattern_Syntax,Pat_Syn" + "\0" + "Pattern_White_Space,Pat_WS" + "\0" + "Quotation_Mark,QMark" + "\0" + "Radical" + "\0" + "Regional_Indicator,RI" + "\0" + "Sentence_Terminal,STerm" + "\0" + "Soft_Dotted,SD" + "\0" + "Terminal_Punctuation,Term" + "\0" + "Unified_Ideograph,UIdeo" + "\0" + "Variation_Selector,VS" + "\0" + "White_Space,space" + "\0" + "Bidi_Mirrored,Bidi_M" + "\0" + "Emoji" + "\0" + "Emoji_Component" + "\0" + "Emoji_Modifier" + "\0" + "Emoji_Modifier_Base" + "\0" + "Emoji_Presentation" + "\0" + "Extended_Pictographic" + "\0" + "Default_Ignorable_Code_Point,DI" + "\0" + "ID_Start,IDS" + "\0" + "Case_Ignorable,CI" + "\0" + "ASCII" + "\0" + "Alphabetic,Alpha" + "\0" + "Any" + "\0" + "Assigned" + "\0" + "Cased" + "\0" + "Changes_When_Casefolded,CWCF" + "\0" + "Changes_When_Casemapped,CWCM" + "\0" + "Changes_When_Lowercased,CWL" + "\0" + "Changes_When_NFKC_Casefolded,CWKCF" + "\0" + "Changes_When_Titlecased,CWT" + "\0" + "Changes_When_Uppercased,CWU" + "\0" + "Grapheme_Base,Gr_Base" + "\0" + "Grapheme_Extend,Gr_Ext" + "\0" + "ID_Continue,IDC" + "\0" + "Lowercase,Lower" + "\0" + "Math" + "\0" + "Uppercase,Upper" + "\0" + "XID_Continue,XIDC" + "\0" + "XID_Start,XIDS" + "\0"; + +static const uint8_t* const unicode_prop_table[] = { + unicode_prop_Hyphen_table, + unicode_prop_Other_Math_table, + unicode_prop_Other_Alphabetic_table, + unicode_prop_Other_Lowercase_table, + unicode_prop_Other_Uppercase_table, + unicode_prop_Other_Grapheme_Extend_table, + unicode_prop_Other_Default_Ignorable_Code_Point_table, + unicode_prop_Other_ID_Start_table, + unicode_prop_Other_ID_Continue_table, + unicode_prop_Prepended_Concatenation_Mark_table, + unicode_prop_ID_Continue1_table, + unicode_prop_XID_Start1_table, + unicode_prop_XID_Continue1_table, + unicode_prop_Changes_When_Titlecased1_table, + unicode_prop_Changes_When_Casefolded1_table, + unicode_prop_Changes_When_NFKC_Casefolded1_table, + unicode_prop_ASCII_Hex_Digit_table, + unicode_prop_Bidi_Control_table, + unicode_prop_Dash_table, + unicode_prop_Deprecated_table, + unicode_prop_Diacritic_table, + unicode_prop_Extender_table, + unicode_prop_Hex_Digit_table, + unicode_prop_IDS_Binary_Operator_table, + unicode_prop_IDS_Trinary_Operator_table, + unicode_prop_Ideographic_table, + unicode_prop_Join_Control_table, + unicode_prop_Logical_Order_Exception_table, + unicode_prop_Noncharacter_Code_Point_table, + unicode_prop_Pattern_Syntax_table, + unicode_prop_Pattern_White_Space_table, + unicode_prop_Quotation_Mark_table, + unicode_prop_Radical_table, + unicode_prop_Regional_Indicator_table, + unicode_prop_Sentence_Terminal_table, + unicode_prop_Soft_Dotted_table, + unicode_prop_Terminal_Punctuation_table, + unicode_prop_Unified_Ideograph_table, + unicode_prop_Variation_Selector_table, + unicode_prop_White_Space_table, + unicode_prop_Bidi_Mirrored_table, + unicode_prop_Emoji_table, + unicode_prop_Emoji_Component_table, + unicode_prop_Emoji_Modifier_table, + unicode_prop_Emoji_Modifier_Base_table, + unicode_prop_Emoji_Presentation_table, + unicode_prop_Extended_Pictographic_table, + unicode_prop_Default_Ignorable_Code_Point_table, + unicode_prop_ID_Start_table, + unicode_prop_Case_Ignorable_table, +}; + +static const uint16_t unicode_prop_len_table[] = { + countof(unicode_prop_Hyphen_table), + countof(unicode_prop_Other_Math_table), + countof(unicode_prop_Other_Alphabetic_table), + countof(unicode_prop_Other_Lowercase_table), + countof(unicode_prop_Other_Uppercase_table), + countof(unicode_prop_Other_Grapheme_Extend_table), + countof(unicode_prop_Other_Default_Ignorable_Code_Point_table), + countof(unicode_prop_Other_ID_Start_table), + countof(unicode_prop_Other_ID_Continue_table), + countof(unicode_prop_Prepended_Concatenation_Mark_table), + countof(unicode_prop_ID_Continue1_table), + countof(unicode_prop_XID_Start1_table), + countof(unicode_prop_XID_Continue1_table), + countof(unicode_prop_Changes_When_Titlecased1_table), + countof(unicode_prop_Changes_When_Casefolded1_table), + countof(unicode_prop_Changes_When_NFKC_Casefolded1_table), + countof(unicode_prop_ASCII_Hex_Digit_table), + countof(unicode_prop_Bidi_Control_table), + countof(unicode_prop_Dash_table), + countof(unicode_prop_Deprecated_table), + countof(unicode_prop_Diacritic_table), + countof(unicode_prop_Extender_table), + countof(unicode_prop_Hex_Digit_table), + countof(unicode_prop_IDS_Binary_Operator_table), + countof(unicode_prop_IDS_Trinary_Operator_table), + countof(unicode_prop_Ideographic_table), + countof(unicode_prop_Join_Control_table), + countof(unicode_prop_Logical_Order_Exception_table), + countof(unicode_prop_Noncharacter_Code_Point_table), + countof(unicode_prop_Pattern_Syntax_table), + countof(unicode_prop_Pattern_White_Space_table), + countof(unicode_prop_Quotation_Mark_table), + countof(unicode_prop_Radical_table), + countof(unicode_prop_Regional_Indicator_table), + countof(unicode_prop_Sentence_Terminal_table), + countof(unicode_prop_Soft_Dotted_table), + countof(unicode_prop_Terminal_Punctuation_table), + countof(unicode_prop_Unified_Ideograph_table), + countof(unicode_prop_Variation_Selector_table), + countof(unicode_prop_White_Space_table), + countof(unicode_prop_Bidi_Mirrored_table), + countof(unicode_prop_Emoji_table), + countof(unicode_prop_Emoji_Component_table), + countof(unicode_prop_Emoji_Modifier_table), + countof(unicode_prop_Emoji_Modifier_Base_table), + countof(unicode_prop_Emoji_Presentation_table), + countof(unicode_prop_Extended_Pictographic_table), + countof(unicode_prop_Default_Ignorable_Code_Point_table), + countof(unicode_prop_ID_Start_table), + countof(unicode_prop_Case_Ignorable_table), +}; + +#endif /* CONFIG_ALL_UNICODE */ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/libunicode.h b/NativeScript/napi/android/primjs/include/quickjs/include/libunicode.h new file mode 100644 index 000000000..6b76c75f7 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/libunicode.h @@ -0,0 +1,131 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_LIBUNICODE_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_LIBUNICODE_H_ + +#include +#include + +#include "base_export.h" + +#define LRE_BOOL int /* for documentation purposes */ + +/* define it to include all the unicode tables (40KB larger) */ +#if LYNX_SIMPLIFY +#define CONFIG_ALL_UNICODE +#endif + +#define LRE_CC_RES_LEN_MAX 3 + +typedef enum { + UNICODE_NFC, + UNICODE_NFD, + UNICODE_NFKC, + UNICODE_NFKD, +} UnicodeNormalizationEnum; + +QJS_HIDE int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); +QJS_HIDE LRE_BOOL lre_is_cased(uint32_t c); +QJS_HIDE LRE_BOOL lre_is_case_ignorable(uint32_t c); + +/* char ranges */ + +typedef struct { + int len; /* in points, always even */ + int size; + uint32_t *points; /* points sorted by increasing value */ + void *mem_opaque; + void *(*realloc_func)(void *opaque, void *ptr, size_t size, int alloc_tag); +} CharRange; + +typedef enum { + CR_OP_UNION, + CR_OP_INTER, + CR_OP_XOR, +} CharRangeOpEnum; + +QJS_HIDE void cr_init(CharRange *cr, void *mem_opaque, + void *(*realloc_func)(void *opaque, void *ptr, + size_t size, int alloc_tag)); +QJS_HIDE void cr_free(CharRange *cr); +QJS_HIDE int cr_realloc(CharRange *cr, int size, int alloc_tag = 0); +QJS_HIDE int cr_copy(CharRange *cr, const CharRange *cr1); + +static inline int cr_add_point(CharRange *cr, uint32_t v) { + if (cr->len >= cr->size) { + if (cr_realloc(cr, cr->len + 1, 1)) return -1; + } + cr->points[cr->len++] = v; + return 0; +} + +static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2) { + if ((cr->len + 2) > cr->size) { + if (cr_realloc(cr, cr->len + 2, 1)) return -1; + } + cr->points[cr->len++] = c1; + cr->points[cr->len++] = c2; + return 0; +} + +QJS_HIDE int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len); + +static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) { + uint32_t b_pt[2]; + b_pt[0] = c1; + b_pt[1] = c2 + 1; + return cr_union1(cr, b_pt, 2); +} + +QJS_HIDE int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op); + +QJS_HIDE int cr_invert(CharRange *cr); + +#ifdef CONFIG_ALL_UNICODE + +LRE_BOOL lre_is_id_start(uint32_t c); +LRE_BOOL lre_is_id_continue(uint32_t c); + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, void *opaque, + void *mem_opaque, + void *(*realloc_func)(void *opaque, void *ptr, + size_t size, int alloc_tag)); + +/* Unicode character range functions */ + +int unicode_script(CharRange *cr, const char *script_name, LRE_BOOL is_ext); +int unicode_general_category(CharRange *cr, const char *gc_name); +int unicode_prop(CharRange *cr, const char *prop_name); + +#endif /* CONFIG_ALL_UNICODE */ + +#undef LRE_BOOL + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_LIBUNICODE_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/list.h b/NativeScript/napi/android/primjs/include/quickjs/include/list.h new file mode 100644 index 000000000..0f24df4b1 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/list.h @@ -0,0 +1,97 @@ +/* + * Linux klist like system + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_LIST_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_LIST_H_ + +#ifndef NULL +#include +#endif + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +#define LIST_HEAD_INIT(el) \ + { &(el), &(el) } + +/* return the pointer of type 'type *' containing 'el' as field 'member' */ +#define list_entry(el, type, member) \ + ((type *)((uint8_t *)(el)-offsetof(type, member))) + +static inline void init_list_head(struct list_head *head) { + head->prev = head; + head->next = head; +} + +/* insert 'el' between 'prev' and 'next' */ +static inline void __list_add(struct list_head *el, struct list_head *prev, + struct list_head *next) { + prev->next = el; + el->prev = prev; + el->next = next; + next->prev = el; +} + +/* add 'el' at the head of the list 'head' (= after element head) */ +static inline void list_add(struct list_head *el, struct list_head *head) { + __list_add(el, head, head->next); +} + +/* add 'el' at the end of the list 'head' (= before element head) */ +static inline void list_add_tail(struct list_head *el, struct list_head *head) { + __list_add(el, head->prev, head); +} + +static inline void list_del(struct list_head *el) { + struct list_head *prev, *next; + prev = el->prev; + next = el->next; + prev->next = next; + next->prev = prev; + el->prev = NULL; /* fail safe */ + el->next = NULL; /* fail safe */ +} + +static inline int list_empty(struct list_head *el) { return el->next == el; } + +#define list_for_each(el, head) \ + for (el = (head)->next; el != (head); el = el->next) + +#define list_for_each_safe(el, el1, head) \ + for (el = (head)->next, el1 = el->next; el != (head); \ + el = el1, el1 = el->next) + +#define list_for_each_prev(el, head) \ + for (el = (head)->prev; el != (head); el = el->prev) + +#define list_for_each_prev_safe(el, el1, head) \ + for (el = (head)->prev, el1 = el->prev; el != (head); \ + el = el1, el1 = el->prev) + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_LIST_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/primjs_monitor.h b/NativeScript/napi/android/primjs/include/quickjs/include/primjs_monitor.h new file mode 100644 index 000000000..d26cdc794 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/primjs_monitor.h @@ -0,0 +1,19 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_PRIMJS_MONITOR_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_PRIMJS_MONITOR_H_ + +#define MODULE_PRIMJS "primjs" +#define MODULE_QUICK "quickjs" + +#define MODULE_NAPI "napi" +#define DEFAULT_BIZ_NAME "unknown_biz_name" + +void MonitorEvent(const char* moduleName, const char* bizName, + const char* dataKey, const char* dataValue); +bool GetSettingsWithKey(const char* key); +int GetSettingsFlag(); + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_PRIMJS_MONITOR_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-atom.h b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-atom.h new file mode 100644 index 000000000..c6c6367d2 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-atom.h @@ -0,0 +1,286 @@ +/* + * QuickJS atom definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifdef DEF + +/* Note: first atoms are considered as keywords in the parser */ +DEF(null, "null") /* must be first */ +DEF(false, "false") +DEF(true, "true") +DEF(if, "if") +DEF(else, "else") +DEF(return, "return") +DEF(var, "var") +DEF(this, "this") +DEF(delete, "delete") +DEF(void, "void") +DEF(typeof, "typeof") +DEF(new, "new") +DEF(in, "in") +DEF(instanceof, "instanceof") +DEF(do, "do") +DEF(while, "while") +DEF(for, "for") +DEF(break, "break") +DEF(continue, "continue") +DEF(switch, "switch") +DEF(case, "case") +DEF(default, "default") +DEF(throw, "throw") +DEF(try, "try") +DEF(catch, "catch") +DEF(finally, "finally") +DEF(function, "function") +DEF(debugger, "debugger") +DEF(with, "with") +/* FutureReservedWord */ +DEF(class, "class") +DEF(const, "const") +DEF(enum, "enum") +DEF(export, "export") +DEF(extends, "extends") +DEF(import, "import") +DEF(super, "super") +/* FutureReservedWords when parsing strict mode code */ +DEF(implements, "implements") +DEF(interface, "interface") +DEF(let, "let") +DEF(package, "package") +DEF(private, "private") +DEF(protected, "protected") +DEF(public, "public") +DEF(static, "static") +DEF(yield, "yield") +DEF(await, "await") + +/* empty string */ +DEF(empty_string, "") +/* identifiers */ +DEF(length, "length") +DEF(fileName, "fileName") +DEF(lineNumber, "lineNumber") +DEF(message, "message") +DEF(stack, "stack") +DEF(name, "name") +DEF(toString, "toString") +DEF(toLocaleString, "toLocaleString") +DEF(valueOf, "valueOf") +DEF(eval, "eval") +DEF(prototype, "prototype") +DEF(constructor, "constructor") +DEF(configurable, "configurable") +DEF(writable, "writable") +DEF(enumerable, "enumerable") +DEF(value, "value") +DEF(get, "get") +DEF(set, "set") +DEF(of, "of") +DEF(__proto__, "__proto__") +DEF(undefined, "undefined") +DEF(number, "number") +DEF(boolean, "boolean") +DEF(string, "string") +DEF(object, "object") +DEF(symbol, "symbol") +DEF(integer, "integer") +DEF(unknown, "unknown") +DEF(arguments, "arguments") +DEF(callee, "callee") +DEF(caller, "caller") +DEF(_eval_, "") +DEF(_ret_, "") +DEF(_var_, "") +DEF(_with_, "") +DEF(lastIndex, "lastIndex") +DEF(target, "target") +DEF(index, "index") +DEF(input, "input") +DEF(defineProperties, "defineProperties") +DEF(apply, "apply") +DEF(join, "join") +DEF(concat, "concat") +DEF(split, "split") +DEF(construct, "construct") +DEF(getPrototypeOf, "getPrototypeOf") +DEF(setPrototypeOf, "setPrototypeOf") +DEF(isExtensible, "isExtensible") +DEF(preventExtensions, "preventExtensions") +DEF(has, "has") +DEF(deleteProperty, "deleteProperty") +DEF(defineProperty, "defineProperty") +DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") +DEF(ownKeys, "ownKeys") +DEF(add, "add") +DEF(done, "done") +DEF(next, "next") +DEF(values, "values") +DEF(source, "source") +DEF(flags, "flags") +DEF(global, "global") +DEF(unicode, "unicode") +DEF(raw, "raw") +DEF(new_target, "new.target") +DEF(this_active_func, "this.active_func") +DEF(home_object, "") +DEF(computed_field, "") +DEF(static_computed_field, + "") /* must come after computed_fields */ +DEF(class_fields_init, "") +DEF(brand, "") +DEF(hash_constructor, "#constructor") +DEF(as, "as") +DEF(from, "from") +DEF(_default_, "*default*") +DEF(_star_, "*") +DEF(Module, "Module") +DEF(then, "then") +DEF(resolve, "resolve") +DEF(reject, "reject") +DEF(promise, "promise") +DEF(proxy, "proxy") +DEF(revoke, "revoke") +DEF(async, "async") +DEF(exec, "exec") +DEF(groups, "groups") +DEF(status, "status") +DEF(reason, "reason") +#ifdef CONFIG_BIGNUM +DEF(bigint, "bigint") +DEF(bigfloat, "bigfloat") +#endif +#ifdef CONFIG_ATOMICS +DEF(not_equal, "not-equal") +DEF(timed_out, "timed-out") +DEF(ok, "ok") +#endif +DEF(toJSON, "toJSON") +/* class names */ +DEF(Object, "Object") +DEF(Array, "Array") +DEF(Error, "Error") +DEF(Number, "Number") +DEF(String, "String") +DEF(Boolean, "Boolean") +DEF(Symbol, "Symbol") +DEF(Arguments, "Arguments") +DEF(Math, "Math") +DEF(JSON, "JSON") +DEF(Date, "Date") +DEF(Function, "Function") +DEF(GeneratorFunction, "GeneratorFunction") +DEF(ForInIterator, "ForInIterator") +DEF(RegExp, "RegExp") +DEF(ArrayBuffer, "ArrayBuffer") +DEF(SharedArrayBuffer, "SharedArrayBuffer") +/* must keep same order as class IDs for typed arrays */ +DEF(Uint8ClampedArray, "Uint8ClampedArray") +DEF(Int8Array, "Int8Array") +DEF(Uint8Array, "Uint8Array") +DEF(Int16Array, "Int16Array") +DEF(Uint16Array, "Uint16Array") +DEF(Int32Array, "Int32Array") +DEF(Uint32Array, "Uint32Array") +#ifdef CONFIG_BIGNUM +DEF(BigInt64Array, "BigInt64Array") +DEF(BigUint64Array, "BigUint64Array") +#endif +DEF(Float32Array, "Float32Array") +DEF(Float64Array, "Float64Array") +DEF(DataView, "DataView") +#ifdef CONFIG_BIGNUM +DEF(BigInt, "BigInt") +DEF(BigFloat, "BigFloat") +DEF(BigFloatEnv, "BigFloatEnv") +#endif +DEF(Map, "Map") +DEF(Set, "Set") /* Map + 1 */ +DEF(WeakMap, "WeakMap") /* Map + 2 */ +DEF(WeakSet, "WeakSet") /* Map + 3 */ +DEF(Map_Iterator, "Map Iterator") +DEF(Set_Iterator, "Set Iterator") +DEF(Array_Iterator, "Array Iterator") +DEF(String_Iterator, "String Iterator") +DEF(RegExp_String_Iterator, "RegExp String Iterator") +DEF(Generator, "Generator") +DEF(Proxy, "Proxy") +DEF(Promise, "Promise") +DEF(PromiseResolveFunction, "PromiseResolveFunction") +DEF(PromiseRejectFunction, "PromiseRejectFunction") +DEF(AsyncFunction, "AsyncFunction") +DEF(AsyncFunctionResolve, "AsyncFunctionResolve") +DEF(AsyncFunctionReject, "AsyncFunctionReject") +DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") +DEF(AsyncGenerator, "AsyncGenerator") +DEF(EvalError, "EvalError") +DEF(RangeError, "RangeError") +DEF(ReferenceError, "ReferenceError") +DEF(SyntaxError, "SyntaxError") +DEF(TypeError, "TypeError") +DEF(URIError, "URIError") +DEF(InternalError, "InternalError") +/* private symbols */ +DEF(Private_brand, "") +/* symbols */ +DEF(Symbol_toPrimitive, "Symbol.toPrimitive") +DEF(Symbol_iterator, "Symbol.iterator") +DEF(Symbol_match, "Symbol.match") +DEF(Symbol_matchAll, "Symbol.matchAll") +DEF(Symbol_replace, "Symbol.replace") +DEF(Symbol_search, "Symbol.search") +DEF(Symbol_split, "Symbol.split") +DEF(Symbol_toStringTag, "Symbol.toStringTag") +DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") +DEF(Symbol_hasInstance, "Symbol.hasInstance") +DEF(Symbol_species, "Symbol.species") +DEF(Symbol_unscopables, "Symbol.unscopables") +DEF(Symbol_asyncIterator, "Symbol.asyncIterator") +#ifdef CONFIG_BIGNUM +DEF(Symbol_operatorOrder, "Symbol.operatorOrder") +DEF(Symbol_operatorAdd, "Symbol.operatorAdd") +DEF(Symbol_operatorSub, "Symbol.operatorSub") +DEF(Symbol_operatorMul, "Symbol.operatorMul") +DEF(Symbol_operatorDiv, "Symbol.operatorDiv") +DEF(Symbol_operatorMod, "Symbol.operatorMod") +DEF(Symbol_operatorPow, "Symbol.operatorPow") +DEF(Symbol_operatorShl, "Symbol.operatorShl") +DEF(Symbol_operatorShr, "Symbol.operatorShr") +DEF(Symbol_operatorAnd, "Symbol.operatorAnd") +DEF(Symbol_operatorOr, "Symbol.operatorOr") +DEF(Symbol_operatorXor, "Symbol.operatorXor") +DEF(Symbol_operatorCmpLT, "Symbol.operatorCmpLT") +DEF(Symbol_operatorCmpLE, "Symbol.operatorCmpLE") +DEF(Symbol_operatorCmpEQ, "Symbol.operatorCmpEQ") +DEF(Symbol_operatorPlus, "Symbol.operatorPlus") +DEF(Symbol_operatorNeg, "Symbol.operatorNeg") +DEF(Symbol_operatorNot, "Symbol.operatorNot") +DEF(Symbol_operatorInc, "Symbol.operatorInc") +DEF(Symbol_operatorDec, "Symbol.operatorDec") +DEF(Symbol_operatorMathMod, "Symbol.operatorMathMod") +#endif + +#endif /* DEF */ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-inner.h b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-inner.h new file mode 100644 index 000000000..c3dd6e485 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-inner.h @@ -0,0 +1,3204 @@ +/* + * QuickJS Javascript Engine + * + * Copyright (c) 2017-2019 Fabrice Bellard + * Copyright (c) 2017-2019 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_INNER_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_INNER_H_ + +#include "quickjs/include/base_export.h" +#ifdef __cplusplus +extern "C" { +#endif + +#include "quickjs/include/cutils.h" +#include "quickjs/include/list.h" +#include "quickjs/include/quickjs.h" + +#ifdef CONFIG_BIGNUM +#include "quickjs/include/libbf.h" +#endif + +#ifdef __cplusplus +} +#endif + +#include "gc/allocator.h" +#include "quickjs/include/primjs_monitor.h" +#include "quickjs/include/quickjs_queue.h" + +#ifndef _WIN32 +#include + +#include +#endif +#include + +#ifdef ENABLE_GC_DEBUG_TOOLS +#include +#ifndef DCHECK +#define DCHECK(condition) \ + if (!(condition)) abort(); +#endif +#else +#ifndef DCHECK +#define DCHECK(condition) ((void)0) +#endif +#endif + +typedef int BOOL; +#define SYSCALL_CHECK(condition) \ + if ((condition) == -1) { \ + /*abort()*/ \ + } + +#ifdef ENABLE_QUICKJS_DEBUGGER +#include "inspector/debugger_inner.h" +#endif + +#include "gc/qjsvaluevalue-space.h" + +#if defined(CONFIG_BIGNUM) and defined(ENABLE_LEPUSNG) +#error bignum and lepusng are now conflict! +#endif +#if defined(QJS_UNITTEST) || defined(__WASI_SDK__) +#define QJS_STATIC +#else +#define QJS_STATIC static +#endif + +#define OPTIMIZE 1 +#define SHORT_OPCODES 1 + +#if !defined(__aarch64__) +#if defined(ENABLE_PRIMJS_SNAPSHOT) +#undef ENABLE_PRIMJS_SNAPSHOT +#endif +#if defined(ENABLE_COMPATIBLE_MM) +#undef ENABLE_COMPATIBLE_MM +#endif +#endif + +#ifdef ENABLE_COMPATIBLE_MM +#undef DUMP_LEAKS +#undef DEBUG_MEMORY +#endif + +#define KB (1024) +#define MB (1024 * KB) +#define MS (1000) + +#define BUF_LEN (100) + +#ifdef ENABLE_GC_DEBUG_TOOLS +size_t get_cur_cnt(void *runtime, void *ptr); +size_t get_del_cnt(void *runtime, void *ptr); +#endif + +#define __exception __attribute__((warn_unused_result)) + +enum JS_CLASS_ID { + /* classid tag */ /* union usage | properties */ + JS_CLASS_OBJECT = 1, /* must be first */ + JS_CLASS_ARRAY, /* u.array | length */ + JS_CLASS_ERROR, + JS_CLASS_NUMBER, /* u.object_data */ + JS_CLASS_STRING, /* u.object_data */ + JS_CLASS_BOOLEAN, /* u.object_data */ + JS_CLASS_SYMBOL, /* u.object_data */ + JS_CLASS_ARGUMENTS, /* u.array | length */ + JS_CLASS_MAPPED_ARGUMENTS, /* | length */ + JS_CLASS_DATE, /* u.object_data */ + JS_CLASS_MODULE_NS, + JS_CLASS_C_FUNCTION, /* u.cfunc */ + JS_CLASS_BYTECODE_FUNCTION, /* u.func */ + JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ + JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ + JS_CLASS_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ + JS_CLASS_REGEXP, /* u.regexp */ + JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ +#ifdef CONFIG_BIGNUM + JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ +#endif + JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_DATAVIEW, /* u.typed_array */ +#ifdef CONFIG_BIGNUM + JS_CLASS_BIG_INT, /* u.object_data */ + JS_CLASS_BIG_FLOAT, /* u.object_data */ + JS_CLASS_FLOAT_ENV, /* u.float_env */ +#endif + JS_CLASS_MAP, /* u.map_state */ + JS_CLASS_SET, /* u.map_state */ + JS_CLASS_WEAKMAP, /* u.map_state */ + JS_CLASS_WEAKSET, /* u.map_state */ + JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ + JS_CLASS_GENERATOR, /* u.generator_data */ + JS_CLASS_PROXY, /* u.proxy_data */ + JS_CLASS_PROMISE, /* u.promise_data */ + JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ + JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ + JS_CLASS_ASYNC_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ + JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ + JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ + JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ + JS_CLASS_WeakRef, + JS_CLASS_FinalizationRegistry, + + JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ +}; + +typedef enum JSErrorEnum { + JS_EVAL_ERROR, + JS_RANGE_ERROR, + JS_REFERENCE_ERROR, + JS_SYNTAX_ERROR, + JS_TYPE_ERROR, + JS_URI_ERROR, + JS_INTERNAL_ERROR, + JS_AGGREGATE_ERROR, + + JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ +} JSErrorEnum; + +#define BUILD_ASYNC_STACK + +typedef struct JSShape JSShape; +typedef struct JSString JSString; +typedef struct JSString JSAtomStruct; + +typedef struct JSLepusType { + int32_t array_typeid_; + int32_t table_typeid_; + int32_t refcounted_typeid_; + LEPUSClassID refcounted_cid_; +} JSLepusType; + +typedef struct PrimjsCallbacks { + void (*print_by_alog)(char *msg); + int32_t (*js_has_property)(LEPUSContext *, LEPUSValue, JSAtom, int32_t); + int32_t (*js_delete_property)(LEPUSContext *, LEPUSValue, JSAtom, int32_t); + int32_t (*js_get_own_property_names)(LEPUSContext *, LEPUSValue, uint32_t *, + struct LEPUSPropertyEnum **, int32_t); + int32_t (*js_deep_equal_callback)(LEPUSContext *, LEPUSValue, LEPUSValue); + LEPUSValue (*jsarray_push)(LEPUSContext *, LEPUSValue, int32_t, + LEPUSValueConst *, int32_t); + LEPUSValue (*jsarray_pop)(LEPUSContext *, LEPUSValue, int32_t); + int64_t (*jsarray_find)(LEPUSContext *, LEPUSValue, LEPUSValue, int64_t, + int32_t); + LEPUSValue (*jsarray_reverse)(LEPUSContext *, LEPUSValue); + LEPUSValue (*jsarray_slice)(LEPUSContext *, LEPUSValue, size_t, size_t, + size_t, LEPUSValue *, int32_t); +} PrimjsCallbacks; + +typedef struct JSMallocState { + size_t malloc_count; + // + uint64_t malloc_size; + uint64_t malloc_limit; + struct malloc_state allocate_state; + // + void *opaque; /* user opaque */ +} JSMallocState; + +typedef struct LEPUSMallocFunctions { + void *(*lepus_malloc)(JSMallocState *s, size_t size, int alloc_tag); + void (*lepus_free)(JSMallocState *s, void *ptr); + void *(*lepus_realloc)(JSMallocState *s, void *ptr, size_t size, + int alloc_tag); + size_t (*lepus_malloc_usable_size)(const void *ptr); +} LEPUSMallocFunctions; + +#ifdef ENABLE_QUICKJS_DEBUGGER +typedef struct QJSDebuggerCallbacks2 { + // callbacks for quickjs debugger + void (*run_message_loop_on_pause)(LEPUSContext *ctx); + void (*quit_message_loop_on_pause)(LEPUSContext *ctx); + void (*get_messages)(LEPUSContext *ctx); + void (*send_response)(LEPUSContext *ctx, int32_t message_id, + const char *message); + void (*send_notification)(LEPUSContext *ctx, const char *message); + void (*free_messages)(LEPUSContext *ctx, char **messages, int32_t size); + + void (*inspector_check)(LEPUSContext *ctx); + void (*debugger_exception)(LEPUSContext *ctx); + void (*console_message)(LEPUSContext *ctx, int tag, LEPUSValueConst *argv, + int argc); + void (*script_parsed_ntfy)(LEPUSContext *ctx, LEPUSScriptSource *source); + void (*console_api_called_ntfy)(LEPUSContext *ctx, LEPUSValue *msg); + void (*script_fail_parse_ntfy)(LEPUSContext *ctx, LEPUSScriptSource *source); + void (*debugger_paused)(LEPUSContext *ctx, const uint8_t *cur_pc); + uint8_t (*is_devtool_on)(LEPUSRuntime *rt); + void (*send_response_with_view_id)(LEPUSContext *ctx, int32_t message_id, + const char *message, int32_t view_id); + void (*send_ntfy_with_view_id)(LEPUSContext *ctx, const char *message, + int32_t view_id); + void (*script_parsed_ntfy_with_view_id)(LEPUSContext *ctx, + LEPUSScriptSource *source, + int32_t view_id); + void (*script_fail_parse_ntfy_with_view_id)(LEPUSContext *ctx, + LEPUSScriptSource *source, + int32_t view_id); + void (*set_session_enable_state)(LEPUSContext *ctx, int32_t view_id, + int32_t protocol_type); + void (*get_session_state)(LEPUSContext *ctx, int32_t view_id, + bool *is_already_enabled, bool *is_paused); + void (*console_api_called_ntfy_with_rid)(LEPUSContext *ctx, LEPUSValue *msg); + void (*get_session_enable_state)(LEPUSContext *ctx, int32_t view_id, + int32_t protocol_type, bool *ret); + void (*get_console_stack_trace)(LEPUSContext *ctx, LEPUSValue *ret); + void (*on_console_message)(LEPUSContext *ctx, LEPUSValue console_message, + int32_t); +} QJSDebuggerCallbacks2; +#endif + +typedef struct SettingsOption { + /* + If this value is true, quickjs will not adjust stack size + when stack size is unconsistent. + */ + bool disable_adjust_stacksize; + bool disable_json_opt; + bool disable_deepclone_opt; + bool disable_separable_string; +} SettingsOption; +class GlobalHandles; +class GarbageCollector; +class PtrHandles; +class ByteThreadPool; +class napi_handle_scope__; + +struct LEPUSRuntime { + LEPUSMallocFunctions mf; + const char *rt_info; + + // for trace gc + + int atom_hash_size; /* power of two */ + int atom_count; + int atom_size; + int atom_count_resize; /* resize hash table at this count */ + uint32_t *atom_hash; + JSAtomStruct **atom_array; + int atom_free_index; /* 0 = none */ + + int class_count; /* size of class_array */ + LEPUSClass *class_array; + + struct list_head context_list; /* list of LEPUSContext.link */ + /* list of JSGCObjectHeader.link. List of allocated GC objects (used + by the garbage collector) */ + /* list of allocated objects (used by the garbage collector) */ + struct list_head obj_list; /* list of LEPUSObject.link */ + // + struct list_head gc_bytecode_list; + struct list_head gc_obj_list; + // + struct list_head tmp_obj_list; /* used during gc */ + struct list_head free_obj_list; /* used during gc */ + struct list_head *el_next; /* used during gc */ + BOOL in_gc_sweep : 8; + int c_stack_depth; + uint64_t malloc_gc_threshold; + /* stack limitation */ + const uint8_t *stack_top; + size_t stack_size; /* in bytes */ + + LEPUSValue current_exception; + /* true if a backtrace needs to be added to the current exception + (the backtrace generation cannot be done immediately in a bytecode + function) */ + BOOL exception_needs_backtrace; + /* true if inside an out of memory error, to avoid recursing */ + BOOL in_out_of_memory : 8; + + struct LEPUSStackFrame *current_stack_frame; + + LEPUSInterruptHandler *interrupt_handler; + void *interrupt_opaque; + + struct list_head job_list; /* list of JSJobEntry.link */ + + LEPUSModuleNormalizeFunc *module_normalize_func; + LEPUSModuleLoaderFunc *module_loader_func; + void *module_loader_opaque; + + BOOL can_block : 8; /* TRUE if Atomics.wait can block */ + + /* Shape hash table */ + int shape_hash_bits; + int shape_hash_size; + int shape_hash_count; /* number of hashed shapes */ + JSShape **shape_hash; + PrimjsCallbacks primjs_callbacks_; + + struct list_head + unhandled_rejections; // record the first unhandled rejection error + struct list_head async_func_sf; // record all async functions' stack frame. + +#ifdef BUILD_ASYNC_STACK + LEPUSValue *current_micro_task; +#endif + +#ifdef ENABLE_LEPUSNG + LEPUSLepusRefCallbacks js_callbacks_; + JSLepusType js_type_; +#endif + +#ifdef ENABLE_QUICKJS_DEBUGGER + QJSDebuggerCallbacks2 debugger_callbacks_; + int32_t next_script_id; // next script id that can be used +#endif +#ifdef CONFIG_BIGNUM + bf_context_t bf_ctx; +#endif + // +#ifdef ENABLE_PRIMJS_SNAPSHOT + bool use_primjs; +#endif +#ifdef DUMP_LEAKS + struct list_head string_list; /* list of JSString.link */ +#endif + // + void (*update_gc_info)(const char *, int); + char gc_info_start_[BUF_LEN]; + char gc_info_end_[BUF_LEN]; + int64_t init_time; + + ByteThreadPool *workerThreadPool; + GlobalHandles *global_handles_ = nullptr; + + QJSValueValueSpace *qjsvaluevalue_allocator = nullptr; + PtrHandles *ptr_handles; + GarbageCollector *gc; + size_t gc_cnt; + void *mem_for_oom; + bool gc_enable; + bool is_lepusng; + void *user_opaque; + // Primjs begin + SettingsOption settings_option; + // Primjs end + JSMallocState malloc_state; +#ifdef ENABLE_TRACING_GC + LEPUSObject *boilerplateArg0; + LEPUSObject *boilerplateArg1; + LEPUSObject *boilerplateArg2; + LEPUSObject *boilerplateArg3; +#endif +}; + +static const char *const native_error_name[JS_NATIVE_ERROR_COUNT] = { + "EvalError", "RangeError", "ReferenceError", "SyntaxError", + "TypeError", "URIError", "InternalError", "AggregateError"}; + +/* Set/Map/WeakSet/WeakMap */ + +typedef struct JSMapState { + BOOL is_weak; /* TRUE if WeakSet/WeakMap */ + struct list_head records; /* list of JSMapRecord.link */ + uint32_t record_count; + struct list_head *hash_table; + uint32_t hash_size; /* must be a power of two */ + uint32_t record_count_threshold; /* count at which a hash table + resize is needed */ +} JSMapState; + +typedef struct JSUnhandledRejectionEntry { + struct list_head link; + LEPUSValue error; + LEPUSValue promise; +} JSUnhandledRejectionEntry; + +struct LEPUSClass { + uint32_t class_id; /* 0 means free entry */ + JSAtom class_name; + LEPUSClassFinalizer *finalizer; + LEPUSClassGCMark *gc_mark; + LEPUSClassCall *call; + /* pointers for exotic behavior, can be NULL if none are present */ + const LEPUSClassExoticMethods *exotic; +}; + +#define JS_MODE_STRICT (1 << 0) +#define JS_MODE_STRIP (1 << 1) +#define JS_MODE_BIGINT (1 << 2) +#define JS_MODE_MATH (1 << 3) + +typedef struct LEPUSStackFrame { + struct LEPUSStackFrame *prev_frame; /* NULL if first stack frame */ + LEPUSValue + cur_func; /* current function, LEPUS_UNDEFINED if the frame is detached */ + LEPUSValue *arg_buf; /* arguments */ + LEPUSValue *var_buf; /* variables */ + struct list_head var_ref_list; /* list of JSVarRef.link */ + const uint8_t *cur_pc; /* only used in bytecode functions : PC of the + instruction after the call */ + int arg_count; + int js_mode; /* for C functions: 0 */ + /* only used in generators. Current stack pointer value. NULL if + the function is running. */ + LEPUSValue *cur_sp; + LEPUSValue *sp = nullptr; +#ifdef ENABLE_QUICKJS_DEBUGGER + // for debugger: this_obj of the stack frame + LEPUSValue pthis; +#endif + struct JSVarRef **var_refs = nullptr; + uint32_t ref_size = 0; +} LEPUSStackFrame; + +enum TOK { + TOK_NUMBER = -128, + TOK_STRING, + TOK_TEMPLATE, + TOK_IDENT, + TOK_REGEXP, + /* warning: order matters (see js_parse_assign_expr) */ + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_MOD_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_SHL_ASSIGN, + TOK_SAR_ASSIGN, + TOK_SHR_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, +#ifdef CONFIG_BIGNUM + TOK_MATH_POW_ASSIGN, +#endif + TOK_POW_ASSIGN, + TOK_DOUBLE_QUESTION_MARK_ASSIGN, + TOK_DEC, + TOK_INC, + TOK_SHL, + TOK_SAR, + TOK_SHR, + TOK_LT, + TOK_LTE, + TOK_GT, + TOK_GTE, + TOK_EQ, + TOK_STRICT_EQ, + TOK_NEQ, + TOK_STRICT_NEQ, + TOK_LAND, + TOK_LOR, +#ifdef CONFIG_BIGNUM + TOK_MATH_POW, +#endif + TOK_POW, + TOK_ARROW, + TOK_ELLIPSIS, + TOK_DOUBLE_QUESTION_MARK, + TOK_QUESTION_MARK_DOT, + TOK_ERROR, + TOK_PRIVATE_NAME, + TOK_EOF, + /* keywords: WARNING: same order as atoms */ + TOK_NULL, /* must be first */ + TOK_FALSE, + TOK_TRUE, + TOK_IF, + TOK_ELSE, + TOK_RETURN, + TOK_VAR, + TOK_THIS, + TOK_DELETE, + TOK_VOID, + TOK_TYPEOF, + TOK_NEW, + TOK_IN, + TOK_INSTANCEOF, + TOK_DO, + TOK_WHILE, + TOK_FOR, + TOK_BREAK, + TOK_CONTINUE, + TOK_SWITCH, + TOK_CASE, + TOK_DEFAULT, + TOK_THROW, + TOK_TRY, + TOK_CATCH, + TOK_FINALLY, + TOK_FUNCTION, + TOK_DEBUGGER, + TOK_WITH, + /* FutureReservedWord */ + TOK_CLASS, + TOK_CONST, + TOK_ENUM, + TOK_EXPORT, + TOK_EXTENDS, + TOK_IMPORT, + TOK_SUPER, + /* FutureReservedWords when parsing strict mode code */ + TOK_IMPLEMENTS, + TOK_INTERFACE, + TOK_LET, + TOK_PACKAGE, + TOK_PRIVATE, + TOK_PROTECTED, + TOK_PUBLIC, + TOK_STATIC, + TOK_YIELD, + TOK_AWAIT, /* must be last */ + TOK_OF, /* only used for js_parse_skip_parens_token() */ +}; +#define TOK_FIRST_KEYWORD TOK_NULL +#define TOK_LAST_KEYWORD TOK_AWAIT +struct JSString { + LEPUSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len : 31; + uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ + /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, + for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 + XXX: could change encoding to have one more bit in hash */ + uint32_t hash : 30; + uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ + uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ +#ifdef DUMP_LEAKS + struct list_head link; /* string list */ +#endif + +#ifdef ENABLE_LEPUSNG + // Primjs add + void *cache_; // add to convert to jsString +#endif + union { + uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */ + uint16_t str16[0]; + } u; +}; + +typedef struct JSGCHeader { + uint8_t mark; +} JSGCHeader; + +typedef struct JSVarRef { + LEPUSRefCountHeader header; /* must come first, 32-bit */ + JSGCHeader gc_header; /* must come after LEPUSRefCountHeader, 8-bit */ + uint8_t is_arg : 1; + /* + * 0: the VarRef is on the stack. + * 1: the VarRef is detached, pvalue == &value; + */ + uint8_t is_detached : 1; + int var_idx; /* index of the corresponding function variable on + the stack */ + struct list_head link; + LEPUSValue *pvalue; /* pointer to the value, either on the stack or + to 'value' */ + LEPUSValue value; /* used when the variable is no longer on the stack */ +} JSVarRef; + +#ifdef CONFIG_BIGNUM +typedef struct JSFloatEnv { + limb_t prec; + bf_flags_t flags; + unsigned int status; +} JSFloatEnv; + +typedef struct JSBigFloat { + LEPUSRefCountHeader header; /* must come first, 32-bit */ + bf_t num; +} JSBigFloat; + +/* the same structure is used for big integers and big floats. Big + integers are never infinite or NaNs */ +#else +#ifdef ENABLE_LEPUSNG +// +typedef struct JSBigFloat { + LEPUSRefCountHeader header; /* must come first, 32-bit */ + uint64_t num; +} JSBigFloat; + +// +#endif +#endif + +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define JS_INTERRUPT_COUNTER_INIT 10000 +// +#define DEFAULT_VIRTUAL_STACK_SIZE 1024 * 1024 * 4 +#define FALLBACK_VIRTUAL_STACK_SIZE 1024 * 1024 * 1 +#define MINIFY_VIRTUAL_STACK_SIZE 1024 * 1024 * 2 +// + +// +typedef unsigned char u_char; +typedef u_char *address; +// + +typedef enum OPCodeFormat { +#define FMT(f) OP_FMT_##f, +#define DEF(id, size, n_pop, n_push, f) +#include "quickjs/include/quickjs-opcode.h" +#undef DEF +#undef FMT +} OPCodeFormat; + +typedef enum OPCodeEnum { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) OP_##id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs/include/quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_COUNT, /* excluding temporary opcodes */ + /* temporary opcodes : overlap with the short opcodes */ + OP_TEMP_START = OP_nop + 1, + OP___dummy = OP_TEMP_START - 1, +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) +#define def(id, size, n_pop, n_push, f) OP_##id, +#include "quickjs/include/quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_TEMP_END, + +// The following opcode cannot be dumped into the bianry production. +#define COMPILE_TIME_OPCODE(id, size, n_pop, n_push, f) OP_##id, +#include "quickjs/include/quickjs-opcode.h" +#undef COMPILE_TIME_OPCODE + +} OPCodeEnum; + +struct FinalizationRegistryContext; + +struct LEPUSContext { +#ifdef ENABLE_PRIMJS_SNAPSHOT + address (*dispatch_table)[OP_COUNT]; +#endif +// +#ifndef ALLOCATE_WINDOWS + mstate allocate_state; +#endif + LEPUSRuntime *rt; + struct list_head link; + + uint16_t binary_object_count; + int binary_object_size; + + JSShape *array_shape; /* initial shape for Array objects */ + + LEPUSValue *class_proto; + LEPUSValue function_proto; + LEPUSValue function_ctor; + LEPUSValue regexp_ctor; + LEPUSValue promise_ctor; + LEPUSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; + LEPUSValue iterator_proto; + LEPUSValue async_iterator_proto; + LEPUSValue array_proto_values; + LEPUSValue throw_type_error; + LEPUSValue eval_obj; + + LEPUSValue global_obj; /* global object */ + LEPUSValue global_var_obj; /* contains the global let/const definitions */ + + uint64_t random_state; +#ifdef CONFIG_BIGNUM + bf_context_t *bf_ctx; /* points to rt->bf_ctx, shared by all contexts */ + JSFloatEnv fp_env; /* global FP environment */ +#endif + /* when the counter reaches zero, JSRutime.interrupt_handler is called */ + int interrupt_counter; + BOOL is_error_property_enabled; + + struct list_head loaded_modules; /* list of LEPUSModuleDef.link */ + + /* if NULL, RegExp compilation is not supported */ + LEPUSValue (*compile_regexp)(LEPUSContext *ctx, LEPUSValueConst pattern, + LEPUSValueConst flags); + /* if NULL, eval is not supported */ + LEPUSValue (*eval_internal)(LEPUSContext *ctx, LEPUSValueConst this_obj, + const char *input, size_t input_len, + const char *filename, int flags, int scope_idx, + bool debugger_eval, LEPUSStackFrame *sf); + + void *user_opaque; + // + int64_t napi_env; + BOOL no_lepus_strict_mode; +#if defined(__APPLE__) && !defined(GEN_ANDROID_EMBEDDED) + uint32_t stack_pos; + uint8_t *stack; +#endif +#ifdef ENABLE_QUICKJS_DEBUGGER + LEPUSDebuggerInfo *debugger_info; // structure for quickjs debugger +#endif + uint32_t next_function_id; // for lepusng debugger encode. + uint8_t + debuginfo_outside; // for lepusng debugger encode to avoid break change. + char *lynx_target_sdk_version; + BOOL debugger_mode; + BOOL debugger_parse_script; // for shared context debugger + BOOL debugger_need_polling; + BOOL console_inspect; + // + + PtrHandles *ptr_handles; + napi_handle_scope__ *napi_scope; + bool gc_enable; + bool is_lepusng; + uint64_t binary_version; + struct FinalizationRegistryContext *fg_ctx = nullptr; +}; + +typedef union JSFloat64Union { + double d; + uint64_t u64; + uint32_t u32[2]; +} JSFloat64Union; + +enum { + JS_ATOM_TYPE_STRING = 1, + JS_ATOM_TYPE_GLOBAL_SYMBOL, + JS_ATOM_TYPE_SYMBOL, + JS_ATOM_TYPE_PRIVATE, +}; + +enum { + JS_ATOM_HASH_SYMBOL, + JS_ATOM_HASH_PRIVATE, +}; + +typedef enum { + JS_ATOM_KIND_STRING, + JS_ATOM_KIND_SYMBOL, + JS_ATOM_KIND_PRIVATE, +} JSAtomKindEnum; + +#define JS_ATOM_HASH_MASK ((1 << 30) - 1) + +typedef struct LEPUSClosureVar { + uint8_t is_local : 1; + uint8_t is_arg : 1; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* 9 bits available */ + uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the + parent function. otherwise: index to a closure + variable of the parent function */ + JSAtom var_name; +} LEPUSClosureVar; + +#define ARG_SCOPE_INDEX 1 +#define ARG_SCOPE_END (-2) +#define DEBUG_SCOPE_INDEX (-3) + +typedef struct JSVarScope { + int parent; /* index into fd->scopes of the enclosing scope */ + int first; /* index into fd->vars of the last variable in this scope */ +} JSVarScope; + +typedef enum { + /* XXX: add more variable kinds here instead of using bit fields */ + JS_VAR_NORMAL, + JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ + JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator function + declaration */ + JS_VAR_CATCH, + JS_VAR_FUNCTION_NAME, + JS_VAR_PRIVATE_FIELD, + JS_VAR_PRIVATE_METHOD, + JS_VAR_PRIVATE_GETTER, + JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */ + JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER + */ +} JSVarKindEnum; + +typedef struct JSVarDef { + JSAtom var_name; + int scope_level; /* index into fd->scopes of this variable lexical scope */ + int scope_next; /* index into fd->vars of the next variable in the + * same or enclosing lexical scope */ + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t is_captured : 1; + uint8_t var_kind : 4; /* see JSVarKindEnum */ + int func_pool_idx : 24; /* only used during compilation */ +} JSVarDef; + +/* for the encoding of the pc2line table */ +#define PC2LINE_BASE (-1) +#define PC2LINE_RANGE 5 +#define PC2LINE_OP_FIRST 1 +#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) + +// +#define LINE_NUMBER_BITS_COUNT 24 +#define COLUMN_NUMBER_BITS_COUNT 40 +// for compatibility +#define OLD_LINE_NUMBER_BITS_COUNT 12 +// use 2 bits for type. +#define LINE_COLUMN_TYPE_SHIFT 62 +// + +typedef enum JSFunctionKindEnum { + JS_FUNC_NORMAL = 0, + JS_FUNC_GENERATOR = (1 << 0), + JS_FUNC_ASYNC = (1 << 1), + JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), +} JSFunctionKindEnum; + +// +enum class EntryMode { INTERPRETER, BASELINE }; + +#define JIT_THRESHOLD 6 + +// + +// settings key opt + +enum { + PRIMJS_SNAPSHOT_ENABLE = 0b000000001, + JSON_OPT_DISABLE = 0b000000010, + GC_INFO_ENABLE = 0b000000100, + DEEPCLONE_OPT_DISABLE = 0b000001000, + LEPUSNG_HEAP_20 = 0b000010000, + LEPUSNG_HEAP_24 = 0b000100000, + DISABLE_ADJUST_STACKSIZE = 0b001000000, + DISABLE_SEPARABLE_STRING = 0b010000000, + GC_ENABLE = 0b100000000, + EFFECT_ENABLE = 0b1000000000, + MINIFY_STACK_ENABLE = 0b10000000000, + ENABLE_LEPUSNG_STRAGETY = 0b100000000000, + LEPUSNG_HEAP_12 = 0b1000000000000, + LEPUSNG_GC_DISABLE = 0b10000000000000, +}; + +inline int settingsFlag = 0; +// settings key opt end + +typedef struct CallerStrSlot { + uint32_t pc; + uint32_t size : 31; + uint32_t is_str : 1; + union { + const char *str; + uint32_t off; + }; +} CallerStrSlot; + +typedef struct LEPUSFunctionBytecode { + LEPUSRefCountHeader header; /* must come first, 32-bit */ + JSGCHeader gc_header; /* must come after header, 8-bit */ + uint8_t js_mode; + uint8_t has_prototype : 1; /* true if a prototype field is necessary */ + uint8_t has_simple_parameter_list : 1; + uint8_t is_derived_class_constructor : 1; + /* true if home_object needs to be initialized */ + uint8_t need_home_object : 1; + uint8_t func_kind : 2; + uint8_t new_target_allowed : 1; + uint8_t super_call_allowed : 1; + uint8_t super_allowed : 1; + uint8_t arguments_allowed : 1; + uint8_t has_debug : 1; + uint8_t read_only_bytecode : 1; + /* XXX: 4 bits available */ + uint8_t *byte_code_buf; /* (self pointer) */ + int byte_code_len; + JSAtom func_name; + JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) + (self pointer) */ + LEPUSClosureVar + *closure_var; /* list of variables in the closure (self pointer) */ + uint16_t arg_count; + uint16_t var_count; + uint16_t defined_arg_count; /* for length function property */ + uint16_t stack_size; /* maximum stack size */ + LEPUSValue *cpool; /* constant pool (self pointer) */ + int cpool_count; + int closure_var_count; + +#ifdef ENABLE_QUICKJS_DEBUGGER + DebuggerFuncLevelState func_level_state; + struct list_head link; /*ctx->debugger_info->bytecode_list*/ + LEPUSScriptSource *script; + int32_t bp_num; +#endif + // + struct list_head gc_link; + uint32_t function_id; // for lepusNG debugger encode + // + struct { + /* debug info, move to separate structure to save memory? */ + JSAtom filename; + int line_num; + int source_len; + int pc2line_len; +#ifdef ENABLE_QUICKJS_DEBUGGER + int64_t column_num; +#endif + uint8_t *pc2line_buf; + char *source; + struct list_head link; + // for cpu profiler to use. + JSString *file_name; + JSString *func_name; + + CallerStrSlot *caller_slots; + size_t caller_size; + // end. + } debug; + // ATTENTION: NEW MEMBERS MUST BE ADDED IN FRONT OF DEBUG FIELD! +} LEPUSFunctionBytecode; + +typedef struct JSBoundFunction { + LEPUSValue func_obj; + LEPUSValue this_val; + int argc; + LEPUSValue argv[0]; +} JSBoundFunction; + +typedef enum JSIteratorKindEnum { + JS_ITERATOR_KIND_KEY, + JS_ITERATOR_KIND_VALUE, + JS_ITERATOR_KIND_KEY_AND_VALUE, +} JSIteratorKindEnum; + +typedef struct JSForInIterator { + LEPUSValue obj; + BOOL is_array; + uint32_t array_length; + uint32_t idx; +} JSForInIterator; + +typedef struct JSRegExp { + JSString *pattern; + JSString *bytecode; /* also contains the flags */ +} JSRegExp; + +typedef struct JSProxyData { + LEPUSValue target; + LEPUSValue handler; + LEPUSValue proto; + uint8_t is_func; + uint8_t is_revoked; +} JSProxyData; + +typedef struct JSArrayBuffer { + int byte_length; /* 0 if detached */ + uint8_t detached; + uint8_t shared; /* if shared, the array buffer cannot be detached */ + uint8_t *data; /* NULL if detached */ + struct list_head array_list; + void *opaque; + LEPUSFreeArrayBufferDataFunc *free_func; + BOOL from_js_heap; +} JSArrayBuffer; + +typedef struct JSTypedArray { + struct list_head link; /* link to arraybuffer */ + LEPUSObject *obj; /* back pointer to the TypedArray/DataView object */ + LEPUSObject *buffer; /* based array buffer */ + uint32_t offset; /* offset in the array buffer */ + uint32_t length; /* length in the array buffer */ +} JSTypedArray; + +typedef struct JSAsyncFunctionState { + LEPUSValue this_val; /* 'this' generator argument */ + int argc; /* number of function arguments */ + BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */ + LEPUSStackFrame frame; + list_head link; +#ifdef ENABLE_PRIMJS_SNAPSHOT + LEPUSValue *_arg_buf; +#endif +} JSAsyncFunctionState; + +/* XXX: could use an object instead to avoid the + LEPUS_TAG_ASYNC_FUNCTION tag for the GC */ +typedef struct JSAsyncFunctionData { + LEPUSRefCountHeader header; /* must come first, 32-bit */ + JSGCHeader gc_header; /* must come after LEPUSRefCountHeader, 8-bit */ + LEPUSValue resolving_funcs[2]; + BOOL is_active; /* true if the async function state is valid */ + JSAsyncFunctionState func_state; +} JSAsyncFunctionData; + +typedef struct JSReqModuleEntry { + JSAtom module_name; + LEPUSModuleDef *module; /* used using resolution */ +} JSReqModuleEntry; + +typedef enum JSExportTypeEnum { + JS_EXPORT_TYPE_LOCAL, + JS_EXPORT_TYPE_INDIRECT, +} JSExportTypeEnum; + +typedef struct JSExportEntry { + union { + struct { + int var_idx; /* closure variable index */ + JSVarRef *var_ref; /* if != NULL, reference to the variable */ + } local; /* for local export */ + int req_module_idx; /* module for indirect export */ + } u; + JSExportTypeEnum export_type; + JSAtom local_name; /* '*' if export ns from. not used for local + export after compilation */ + JSAtom export_name; /* exported variable name */ +} JSExportEntry; + +typedef struct JSStarExportEntry { + int req_module_idx; /* in req_module_entries */ +} JSStarExportEntry; + +typedef struct JSImportEntry { + int var_idx; /* closure variable index */ + JSAtom import_name; + int req_module_idx; /* in req_module_entries */ +} JSImportEntry; + +struct LEPUSModuleDef { + LEPUSRefCountHeader header; /* must come first, 32-bit */ + JSAtom module_name; + struct list_head link; + + JSReqModuleEntry *req_module_entries; + int req_module_entries_count; + int req_module_entries_size; + + JSExportEntry *export_entries; + int export_entries_count; + int export_entries_size; + + JSStarExportEntry *star_export_entries; + int star_export_entries_count; + int star_export_entries_size; + + JSImportEntry *import_entries; + int import_entries_count; + int import_entries_size; + + LEPUSValue module_ns; + LEPUSValue func_obj; /* only used for LEPUS modules */ + LEPUSModuleInitFunc *init_func; /* only used for C modules */ + BOOL resolved : 8; + BOOL instantiated : 8; + BOOL evaluated : 8; + BOOL eval_mark : 8; /* temporary use during js_evaluate_module() */ + /* true if evaluation yielded an exception. It is saved in + eval_exception */ + BOOL eval_has_exception : 8; + LEPUSValue eval_exception; +}; + +typedef struct JSJobEntry { + struct list_head link; + LEPUSContext *ctx; + LEPUSJobFunc *job_func; + int argc; + LEPUSValue argv[0]; +} JSJobEntry; + +typedef enum { + WEAK_REF_KIND_WEAK_MAP, + WEAK_REF_KIND_WEAK_REF, + WEAK_REF_KIND_FINALIZATION_REGISTRY, +} WeakRefRecordKind; + +typedef struct FinalizationRegistryContext { + int32_t ref_count; + LEPUSContext *ctx; +} FinalizationRegistryContext; + +typedef struct FinalizationRegistryData { + struct FinalizationRegistryContext *fg_ctx; + list_head entries; + LEPUSValue cbs; +} FinalizationRegistryData; + +typedef struct FinalizationRegistryEntry { + struct list_head link; + FinalizationRegistryData *data; // FinalizationRegistryObj + LEPUSValue target; // registerd object + LEPUSValue held_value; + LEPUSValue token; +} FinalizationRegistryEntry; + +typedef struct WeakRefData { + LEPUSValue target; +} WeakRefData; + +typedef struct WeakRefRecord { + WeakRefRecordKind kind; + struct WeakRefRecord *next_weak_ref; + union { + JSMapRecord *map_record; + FinalizationRegistryEntry *fin_node; + WeakRefData *weak_ref; + void *ptr; + } u; +} WeakRefRecord; + +typedef enum JSGeneratorStateEnum { + JS_GENERATOR_STATE_SUSPENDED_START, + JS_GENERATOR_STATE_SUSPENDED_YIELD, + JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_GENERATOR_STATE_EXECUTING, + JS_GENERATOR_STATE_COMPLETED, +} JSGeneratorStateEnum; + +typedef struct JSGeneratorData { + JSGeneratorStateEnum state; + JSAsyncFunctionState func_state; +} JSGeneratorData; + +typedef struct JSProperty { + union { + LEPUSValue value; /* LEPUS_PROP_NORMAL */ + struct { /* LEPUS_PROP_GETSET */ + LEPUSObject *getter; /* NULL if undefined */ + LEPUSObject *setter; /* NULL if undefined */ + } getset; + JSVarRef *var_ref; /* LEPUS_PROP_VARREF */ + struct { /* LEPUS_PROP_AUTOINIT */ + LEPUSValue (*init_func)(LEPUSContext *ctx, LEPUSObject *obj, JSAtom prop, + void *opaque); + void *opaque; + } init; + } u; +} JSProperty; + +#define JS_PROP_INITIAL_SIZE 2 +#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ +#define JS_ARRAY_INITIAL_SIZE 2 + +typedef struct JSShapeProperty { + uint32_t hash_next : 26; /* 0 if last in list */ + uint32_t flags : 6; /* JS_PROP_XXX */ + JSAtom atom; /* JS_ATOM_NULL = free property entry */ +} JSShapeProperty; + +struct JSShape { + uint32_t prop_hash_end[0]; /* hash table of size hash_mask + 1 + before the start of the structure. */ + LEPUSRefCountHeader header; /* must come first, 32-bit */ + JSGCHeader gc_header; /* must come after LEPUSRefCountHeader, 8-bit */ + /* true if the shape is inserted in the shape hash table. If not, + JSShape.hash is not valid */ + uint8_t is_hashed; + /* If true, the shape may have small array index properties 'n' with 0 + <= n <= 2^31-1. If false, the shape is guaranteed not to have + small array index properties */ + uint8_t has_small_array_index; + uint32_t hash; /* current hash value */ + uint32_t prop_hash_mask; + int prop_size; /* allocated properties */ + int prop_count; + JSShape *shape_hash_next; /* in LEPUSRuntime.shape_hash[h] list */ + LEPUSObject *proto; + JSShapeProperty prop[0]; /* prop_size elements */ +}; +struct LEPUSObject { + LEPUSRefCountHeader header; /* must come first, 32-bit */ + JSGCHeader gc_header; /* must come after LEPUSRefCountHeader, 8-bit */ + uint8_t extensible : 1; + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ + uint8_t fast_array : 1; /* TRUE if u.array is used for get/put */ + uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ + uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */ + uint8_t is_class : 1; /* TRUE if object is a class constructor */ + uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ + uint16_t class_id; /* see JS_CLASS_x */ + /* byte offsets: 8/8 */ + struct list_head link; /* object list */ + /* byte offsets: 16/24 */ + JSShape *shape; /* prototype and property names + flag */ + JSProperty *prop; /* array of properties */ + /* byte offsets: 24/40 */ + struct WeakRefRecord + *first_weak_ref; /* XXX: use a bit and an external hash table? */ + /* byte offsets: 28/48 */ + union { + void *opaque; + struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ + struct JSCFunctionDataRecord + *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ + struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ + struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, + JS_CLASS_SHARED_ARRAY_BUFFER */ + struct JSTypedArray + *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ +#ifdef CONFIG_BIGNUM + struct JSFloatEnv *float_env; /* JS_CLASS_FLOAT_ENV */ +#endif + struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ + struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, + JS_CLASS_SET_ITERATOR */ + struct JSArrayIteratorData + *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, + JS_CLASS_STRING_ITERATOR */ + struct JSRegExpStringIteratorData + *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ + struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ + struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ + struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ + struct JSPromiseFunctionData + *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, + JS_CLASS_PROMISE_REJECT_FUNCTION */ + struct JSAsyncFunctionData + *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, + JS_CLASS_ASYNC_FUNCTION_REJECT */ + struct JSAsyncFromSyncIteratorData + *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR + */ + struct JSAsyncGeneratorData + *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ + struct FinalizationRegistryData + *fin_reg_data; // JS_CLASS_FinalizationRegistry + struct WeakRefData *weak_ref_data; + struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 + bytes */ + /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION + * and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ + struct LEPUSFunctionBytecode *function_bytecode; + JSVarRef **var_refs; + LEPUSObject *home_object; /* for 'super' access */ + } func; + struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ + LEPUSCFunctionType c_function; + uint8_t length; + uint8_t cproto; + int16_t magic; + } cfunc; + /* array part for fast arrays and typed arrays */ + struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, + JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + union { + uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + struct JSTypedArray + *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY + */ + } u1; + union { + LEPUSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */ + uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ + int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */ + uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ + int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */ + uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ + int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ + uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ + float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ + double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ + } u; + uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ + } array; /* 12/20 bytes */ + JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ + LEPUSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ + } u; + /* byte sizes: 40/48/72 */ +}; + +constexpr const char *lepusjs_filename = "file://lepus.js"; +constexpr const char *lepusng_functionid_str = "__lepusNG_function_id__"; + +enum { + JS_ATOM_NULL, +#define DEF(name, str) JS_ATOM_##name, +#include "quickjs/include/quickjs-atom.h" +#undef DEF + JS_ATOM_END, +}; +#define JS_ATOM_LAST_KEYWORD JS_ATOM_super +#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield + +static const char js_atom_init[] = +#define DEF(name, str) str "\0" +#include "quickjs/include/quickjs-atom.h" +#undef DEF + ; + +typedef struct JSOpCode { +#if defined(ENABLE_PRIMJS_SNAPSHOT) || defined(DUMP_BYTECODE) + const char *name; +#endif + + uint8_t size; /* in bytes */ + /* the opcodes remove n_pop items from the top of the stack, then + pushes n_push items */ + uint8_t n_pop; + uint8_t n_push; + uint8_t fmt; +} JSOpCode; + +extern const JSOpCode opcode_info[]; + +#if SHORT_OPCODES +/* After the final compilation pass, short opcodes are used. Their + opcodes overlap with the temporary opcodes which cannot appear in + the final bytecode. Their description is after the temporary + opcodes in opcode_info[]. */ +#define short_opcode_info(op) \ + opcode_info[(op) >= OP_TEMP_START ? (op) + (OP_TEMP_END - OP_TEMP_START) \ + : (op)] +#else +#define short_opcode_info(op) opcode_info[op] +#endif + +// + +#if defined(ANDROID) || defined(__ANDROID__) +#include + +#define LOG_TAG "primjs" + +#ifndef LOGD +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#endif +#ifndef LOGE +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#endif +#ifndef LOGI +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#endif +#endif + +#define PRINT_LOG_TO_FILE 0 + +#ifdef ENABLE_PRIMJS_TRACE +#if defined(ANDROID) || defined(__ANDROID__) +#if PRINT_LOG_TO_FILE +extern FILE *log_f; +#define PRIM_LOG(...) \ + do { \ + fprintf(log_f, __VA_ARGS__); \ + fflush(log_f); \ + } while (0) +#else +#define PRIM_LOG(...) \ + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#endif // PRINT_LOG_TO_FILE +#else +#define PRIM_LOG printf +#endif // ANDROID +#else +#define PRIM_LOG(...) +#endif // ENABLE_PRIMJS_TRACE + +#define OP_DEFINE_METHOD_METHOD 0 +#define OP_DEFINE_METHOD_GETTER 1 +#define OP_DEFINE_METHOD_SETTER 2 +#define OP_DEFINE_METHOD_ENUMERABLE 4 + +#define JS_THROW_VAR_RO 0 +#define JS_THROW_VAR_REDECL 1 +#define JS_THROW_VAR_UNINITIALIZED 2 +#define JS_THROW_ERROR_DELETE_SUPER 3 + +#define LEPUS_CALL_FLAG_CONSTRUCTOR (1 << 0) +#define JS_CALL_FLAG_COPY_ARGV (1 << 1) +#define JS_CALL_FLAG_GENERATOR (1 << 2) + +#define __exception __attribute__((warn_unused_result)) + +/* JSAtom support */ +#define JS_ATOM_TAG_INT (1U << 31) +#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) +#define JS_ATOM_MAX ((1U << 30) - 1) + +/* return the max count from the hash size */ +#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) + +/* argument of OP_special_object */ +typedef enum { + OP_SPECIAL_OBJECT_ARGUMENTS, + OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS, + OP_SPECIAL_OBJECT_THIS_FUNC, + OP_SPECIAL_OBJECT_NEW_TARGET, + OP_SPECIAL_OBJECT_HOME_OBJECT, + OP_SPECIAL_OBJECT_VAR_OBJECT, +} OPSpecialObjectEnum; + +#define FUNC_RET_AWAIT 0 +#define FUNC_RET_YIELD 1 +#define FUNC_RET_YIELD_STAR 2 + +#define HINT_STRING 0 +#define HINT_NUMBER 1 +#define HINT_NONE 2 +#ifdef CONFIG_BIGNUM +#define HINT_INTEGER 3 +#endif +/* don't try Symbol.toPrimitive */ +#define HINT_FORCE_ORDINARY (1 << 4) + +#define prim_abort() \ + { \ + printf("[%s:%d] Abort\n", __FILE__, __LINE__); \ + abort(); \ + } + +#ifdef __cplusplus +extern "C" { +#endif + +inline void *system_malloc(size_t size) { + void *ptr; + ptr = malloc(size); + if (!ptr) return NULL; + return ptr; +} +inline void *system_mallocz(size_t size) { + void *ptr; + ptr = system_malloc(size); + if (!ptr) return NULL; + return memset(ptr, 0, size); +} +inline __attribute__((unused)) void *system_realloc(void *ptr, size_t size) { + if (!ptr) { + if (size == 0) return NULL; + return system_malloc(size); + } + if (size == 0) { + free(ptr); + return NULL; + } + + ptr = realloc(ptr, size); + if (!ptr) return NULL; + return ptr; +} +inline void system_free(void *ptr) { + if (!ptr) return; + free(ptr); +} + +typedef struct JSSeparableString { + LEPUSRefCountHeader header; /* ref count for gc*/ + uint32_t len : 31; + uint8_t is_wide_char : 1; + uint32_t depth; + LEPUSValue left_op; + LEPUSValue right_op; + LEPUSValue flat_content; +} JSSeparableString; + +class CStack { + using element = JSSeparableString *; + element *base_ = nullptr; + element *top_ = nullptr; + size_t stack_size_ = 0; + LEPUSRuntime *runtime_ = nullptr; + static constexpr size_t STACK_INIT_SIZE = 64; + + public: + explicit CStack(LEPUSRuntime *rt, uint32_t depth = STACK_INIT_SIZE) { + runtime_ = rt; + base_ = static_cast(system_malloc(sizeof(element) * depth)); + if (!base_) return; + top_ = base_; + stack_size_ = depth; + } + + ~CStack() { system_free(base_); } + + void Push(element node) { + if (static_cast(top_ - base_) >= stack_size_) { + base_ = static_cast( + system_realloc(base_, 2 * stack_size_ * sizeof(element))); + if (!base_) { + stack_size_ = 0; + top_ = nullptr; + return; + } + top_ = base_ + stack_size_; + stack_size_ *= 2; + } + *top_++ = node; + } + + void Pop() { + if (!Empty()) { + --top_; + } + } + + element Top() { + if (!Empty()) { + return *(top_ - 1); + } + return nullptr; + } + + int32_t Empty() { return top_ == base_; } +}; + +QJS_STATIC inline int32_t JS_IsSeparableString(LEPUSValue val) { + return LEPUS_VALUE_IS_SEPARABLE_STRING(val); +} + +QJS_STATIC inline JSSeparableString *JS_GetSeparableString(LEPUSValue val) { + return reinterpret_cast(LEPUS_VALUE_GET_PTR(val)); +} + +LEPUSValue JS_GetSeparableStringContent(LEPUSContext *ctx, LEPUSValue val); +LEPUSValue JS_GetSeparableStringContentNotDup(LEPUSContext *ctx, + LEPUSValue val); + +QJS_HIDE LEPUSValue JS_GetPropertyInternalImpl(LEPUSContext *ctx, + LEPUSValueConst obj, JSAtom prop, + LEPUSValueConst this_obj, + BOOL throw_ref_error); +QJS_HIDE LEPUSValue JS_GetPropertyInternalImpl_GC(LEPUSContext *ctx, + LEPUSValueConst obj, + JSAtom prop, + LEPUSValueConst this_obj, + BOOL throw_ref_error); +QJS_HIDE void prim_js_print(const char *msg); +QJS_HIDE void prim_js_print_gc(const char *msg); + +QJS_HIDE void prim_js_print_register(uint64_t reg_val); + +QJS_HIDE void prim_js_print_trace(int bytecode, int tos); +QJS_HIDE void prim_js_print_trace_gc(int bytecode, int tos); + +QJS_HIDE void prim_js_print_func(LEPUSContext *ctx, LEPUSValue func_obj); +QJS_HIDE void prim_js_print_func_gc(LEPUSContext *ctx, LEPUSValue func_obj); + +QJS_HIDE void JS_FreeValueRef(LEPUSContext *ctx, LEPUSValue v); + +QJS_HIDE LEPUSValue js_closure(LEPUSContext *ctx, LEPUSValue bfunc, + JSVarRef **cur_var_refs, LEPUSStackFrame *sf); +LEPUSValue js_closure_gc(LEPUSContext *ctx, LEPUSValue bfunc, + JSVarRef **cur_var_refs, LEPUSStackFrame *sf); + +QJS_HIDE void DebuggerPause(LEPUSContext *ctx, LEPUSValue val, + const uint8_t *pc); + +QJS_HIDE void DebuggerCallEachOp(LEPUSContext *ctx, const uint8_t *pc, + LEPUSFunctionBytecode *b); + +QJS_HIDE void DebuggerCallEachFunc(LEPUSContext *ctx, const uint8_t *pc); + +QJS_HIDE LEPUSValue __JS_AtomToValue(LEPUSContext *ctx, JSAtom atom, + BOOL force_string); +QJS_HIDE LEPUSValue __JS_AtomToValue_GC(LEPUSContext *ctx, JSAtom atom, + BOOL force_string); +QJS_HIDE BOOL js_check_stack_overflow(LEPUSContext *ctx, size_t alloca_size); +QJS_HIDE LEPUSValue JS_ThrowStackOverflow(LEPUSContext *ctx); +QJS_HIDE LEPUSValue JS_ThrowStackOverflow_GC(LEPUSContext *ctx); +QJS_HIDE void build_backtrace( + LEPUSContext *ctx, LEPUSValueConst error_obj, const char *filename, + /* */ int64_t line_num, /* */ + const uint8_t *cur_pc, int backtrace_flags, uint8_t is_parse_error = 0); +QJS_HIDE LEPUSValue JS_NewSymbolFromAtom(LEPUSContext *ctx, JSAtom descr, + int atom_type); +QJS_HIDE LEPUSValue JS_NewSymbolFromAtom_GC(LEPUSContext *ctx, JSAtom descr, + int atom_type); +QJS_HIDE LEPUSValue JS_ToObject_GC(LEPUSContext *ctx, LEPUSValueConst val); +QJS_HIDE LEPUSValue PRIM_JS_NewObject(LEPUSContext *ctx); +QJS_HIDE LEPUSValue PRIM_JS_NewObject_GC(LEPUSContext *ctx); +QJS_HIDE LEPUSValue js_build_arguments(LEPUSContext *ctx, int argc, + LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_build_arguments_gc(LEPUSContext *ctx, int argc, + LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_build_mapped_arguments(LEPUSContext *ctx, int argc, + LEPUSValueConst *argv, + LEPUSStackFrame *sf, + int arg_count); +QJS_HIDE LEPUSValue js_build_mapped_arguments_gc(LEPUSContext *ctx, int argc, + LEPUSValueConst *argv, + LEPUSStackFrame *sf, + int arg_count); +QJS_HIDE void prim_close_var_refs(LEPUSContext *ctx, LEPUSStackFrame *sf); +QJS_HIDE void prim_close_var_refs_gc(LEPUSContext *ctx, LEPUSStackFrame *sf); +QJS_HIDE LEPUSValue js_build_rest(LEPUSContext *ctx, int first, int argc, + LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_build_rest_gc(LEPUSContext *ctx, int first, int argc, + LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_function_apply(LEPUSContext *ctx, + LEPUSValueConst this_val, int argc, + LEPUSValueConst *argv, int magic); +QJS_HIDE LEPUSValue js_function_apply_gc(LEPUSContext *ctx, + LEPUSValueConst this_val, int argc, + LEPUSValueConst *argv, int magic); +QJS_HIDE int JS_CheckBrand(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst func); +QJS_HIDE int JS_CheckBrand_GC(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst func); +QJS_HIDE int JS_AddBrand(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst home_obj); +QJS_HIDE int JS_AddBrand_GC(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst home_obj); +QJS_HIDE int JS_ThrowTypeErrorReadOnly(LEPUSContext *ctx, int flags, + JSAtom atom); +QJS_HIDE LEPUSValue JS_ThrowSyntaxErrorVarRedeclaration(LEPUSContext *ctx, + JSAtom prop); +QJS_HIDE LEPUSValue JS_ThrowSyntaxErrorVarRedeclaration_GC(LEPUSContext *ctx, + JSAtom prop); +QJS_HIDE LEPUSValue JS_ThrowReferenceErrorUninitialized(LEPUSContext *ctx, + JSAtom name); +QJS_HIDE LEPUSValue JS_ThrowReferenceErrorUninitialized_GC(LEPUSContext *ctx, + JSAtom name); +QJS_HIDE int JS_IteratorClose(LEPUSContext *ctx, LEPUSValueConst enum_obj, + BOOL is_exception_pending); + +QJS_HIDE LEPUSValue JS_CallConstructorInternal(LEPUSContext *ctx, + LEPUSValueConst func_obj, + LEPUSValueConst new_target, + int argc, LEPUSValue *argv, + int flags); +QJS_HIDE LEPUSValue JS_CallConstructorInternal_GC(LEPUSContext *ctx, + LEPUSValueConst func_obj, + LEPUSValueConst new_target, + int argc, LEPUSValue *argv, + int flags); +QJS_HIDE int JS_CheckGlobalVar(LEPUSContext *ctx, JSAtom prop); +QJS_HIDE int JS_CheckGlobalVar_GC(LEPUSContext *ctx, JSAtom prop); +QJS_HIDE LEPUSValue JS_GetPropertyValue(LEPUSContext *ctx, + LEPUSValueConst this_obj, + LEPUSValue prop); + +QJS_HIDE LEPUSValue JS_GetPropertyValue_GC(LEPUSContext *ctx, + LEPUSValueConst this_obj, + LEPUSValue prop); +QJS_HIDE int JS_DefineGlobalVar(LEPUSContext *ctx, JSAtom prop, int def_flags); +QJS_HIDE int JS_DefineGlobalVar_GC(LEPUSContext *ctx, JSAtom prop, + int def_flags); +QJS_HIDE LEPUSValue PRIM_JS_NewArray(LEPUSContext *ctx); +QJS_HIDE LEPUSValue PRIM_JS_NewArray_GC(LEPUSContext *ctx); + +QJS_HIDE LEPUSValue js_regexp_constructor_internal(LEPUSContext *ctx, + LEPUSValueConst ctor, + LEPUSValue pattern, + LEPUSValue bc); +QJS_HIDE LEPUSValue js_regexp_constructor_internal_gc(LEPUSContext *ctx, + LEPUSValueConst ctor, + LEPUSValue pattern, + LEPUSValue bc); +QJS_HIDE int JS_SetPropertyValue(LEPUSContext *ctx, LEPUSValueConst this_obj, + LEPUSValue prop, LEPUSValue val, int flags); +QJS_HIDE int JS_SetPropertyValue_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + LEPUSValue prop, LEPUSValue val, int flags); +QJS_HIDE int JS_CheckDefineGlobalVar(LEPUSContext *ctx, JSAtom prop, int flags); +QJS_HIDE int JS_CheckDefineGlobalVar_GC(LEPUSContext *ctx, JSAtom prop, + int flags); +QJS_HIDE int JS_DefineGlobalFunction(LEPUSContext *ctx, JSAtom prop, + LEPUSValueConst func, int def_flags); +QJS_HIDE int JS_DefineGlobalFunction_GC(LEPUSContext *ctx, JSAtom prop, + LEPUSValueConst func, int def_flags); +QJS_HIDE LEPUSValue JS_GetPrivateField(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst name); +QJS_HIDE LEPUSValue JS_GetPrivateField_GC(LEPUSContext *ctx, + LEPUSValueConst obj, + LEPUSValueConst name); +QJS_HIDE int JS_DefinePrivateField(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst name, LEPUSValue val); +QJS_HIDE int JS_DefinePrivateField_GC(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst name, LEPUSValue val); +QJS_HIDE int JS_DefineObjectName(LEPUSContext *ctx, LEPUSValueConst obj, + JSAtom name, int flags); +QJS_HIDE int JS_DefineObjectNameComputed(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst str, int flags); +QJS_HIDE int JS_DefineObjectNameComputed_GC(LEPUSContext *ctx, + LEPUSValueConst obj, + LEPUSValueConst str, int flags); +QJS_HIDE int JS_SetPrototypeInternal(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst proto_val, + BOOL throw_flag); +QJS_HIDE int JS_SetPrototypeInternal_GC(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst proto_val, + BOOL throw_flag); +QJS_HIDE void js_method_set_home_object(LEPUSContext *ctx, + LEPUSValueConst func_obj, + LEPUSValueConst home_obj); +QJS_HIDE void js_method_set_home_object_gc(LEPUSContext *ctx, + LEPUSValueConst func_obj, + LEPUSValueConst home_obj); +QJS_HIDE int JS_DefinePropertyValueValue(LEPUSContext *ctx, + LEPUSValueConst this_obj, + LEPUSValue prop, LEPUSValue val, + int flags); +QJS_HIDE int JS_DefinePropertyValueValue_GC(LEPUSContext *ctx, + LEPUSValueConst this_obj, + LEPUSValue prop, LEPUSValue val, + int flags); +QJS_HIDE __exception int js_append_enumerate(LEPUSContext *ctx, LEPUSValue *sp); +QJS_HIDE __exception int js_append_enumerate_gc(LEPUSContext *ctx, + LEPUSValue *sp); +QJS_HIDE int js_method_set_properties(LEPUSContext *ctx, + LEPUSValueConst func_obj, JSAtom name, + int flags, LEPUSValueConst home_obj); +QJS_HIDE int js_method_set_properties_gc(LEPUSContext *ctx, + LEPUSValueConst func_obj, JSAtom name, + int flags, LEPUSValueConst home_obj); +QJS_HIDE int prim_js_copy_data_properties(LEPUSContext *ctx, LEPUSValue *sp, + int mask); +QJS_HIDE int prim_js_copy_data_properties_gc(LEPUSContext *ctx, LEPUSValue *sp, + int mask); +QJS_HIDE int JS_ToBoolFree(LEPUSContext *ctx, LEPUSValue val); +QJS_HIDE int JS_ToBoolFree_GC(LEPUSContext *ctx, LEPUSValue val); +QJS_HIDE int js_op_define_class(LEPUSContext *ctx, LEPUSValue *sp, + JSAtom class_name, int class_flags, + JSVarRef **cur_var_refs, LEPUSStackFrame *sf); +QJS_HIDE void close_lexical_var(LEPUSContext *ctx, LEPUSStackFrame *sf, + int idx); +QJS_HIDE int JS_SetPropertyGeneric(LEPUSContext *ctx, LEPUSObject *p, + JSAtom prop, LEPUSValue val, + LEPUSValueConst this_obj, int flags); +QJS_HIDE int JS_SetPropertyGeneric_GC(LEPUSContext *ctx, LEPUSObject *p, + JSAtom prop, LEPUSValue val, + LEPUSValueConst this_obj, int flags); +QJS_HIDE int JS_SetPrivateField(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst name, LEPUSValue val); +QJS_HIDE int JS_SetPrivateField_GC(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst name, LEPUSValue val); +QJS_HIDE LEPUSValue JS_ThrowTypeErrorNotAnObject(LEPUSContext *ctx); +QJS_HIDE int prim_js_with_op_gc(LEPUSContext *ctx, LEPUSValue *sp, JSAtom atom, + int is_with, int opcode); +QJS_HIDE JSVarRef *get_var_ref(LEPUSContext *ctx, LEPUSStackFrame *sf, + int var_idx, BOOL is_arg); +QJS_HIDE JSVarRef *get_var_ref_gc(LEPUSContext *ctx, LEPUSStackFrame *sf, + int var_idx, BOOL is_arg); +QJS_HIDE void free_var_ref(LEPUSRuntime *rt, JSVarRef *var_ref); + +QJS_HIDE JSProperty *add_property(LEPUSContext *ctx, LEPUSObject *p, + JSAtom prop, int prop_flags); +QJS_HIDE JSProperty *add_property_gc(LEPUSContext *ctx, LEPUSObject *p, + JSAtom prop, int prop_flags); +QJS_HIDE LEPUSValue prim_js_op_eval(LEPUSContext *ctx, int scope_idx, + LEPUSValue op1); +QJS_HIDE LEPUSValue prim_js_op_eval_gc(LEPUSContext *ctx, int scope_idx, + LEPUSValue op1); +QJS_HIDE int prim_js_with_op(LEPUSContext *ctx, LEPUSValue *sp, JSAtom atom, + int is_with, int opcode); + +QJS_HIDE LEPUSValue JS_GetGlobalVarImpl(LEPUSContext *ctx, JSAtom prop, + BOOL throw_ref_error); +QJS_HIDE LEPUSValue JS_GetGlobalVarImpl_GC(LEPUSContext *ctx, JSAtom prop, + BOOL throw_ref_error); +QJS_HIDE int JS_GetGlobalVarRef(LEPUSContext *ctx, JSAtom prop, LEPUSValue *sp); +QJS_HIDE int JS_GetGlobalVarRef_GC(LEPUSContext *ctx, JSAtom prop, + LEPUSValue *sp); +QJS_HIDE LEPUSValue prim_js_for_in_start(LEPUSContext *ctx, LEPUSValue op); +QJS_HIDE LEPUSValue prim_js_for_in_start_gc(LEPUSContext *ctx, LEPUSValue op); +QJS_HIDE int js_for_in_next(LEPUSContext *ctx, LEPUSValue *sp); +QJS_HIDE int js_for_in_next_gc(LEPUSContext *ctx, LEPUSValue *sp); +QJS_HIDE int js_for_await_of_next(LEPUSContext *ctx, LEPUSValue *sp); +QJS_HIDE int js_for_await_of_next_gc(LEPUSContext *ctx, LEPUSValue *sp); +QJS_HIDE int js_iterator_get_value_done(LEPUSContext *ctx, LEPUSValue *sp); +QJS_HIDE int js_iterator_get_value_done_gc(LEPUSContext *ctx, LEPUSValue *sp); +QJS_HIDE int js_for_of_start(LEPUSContext *ctx, LEPUSValue *sp, BOOL is_async); +QJS_HIDE int js_for_of_start_gc(LEPUSContext *ctx, LEPUSValue *sp, + BOOL is_async); +QJS_HIDE int js_for_of_next(LEPUSContext *ctx, LEPUSValue *sp, int offset); +QJS_HIDE int js_for_of_next_gc(LEPUSContext *ctx, LEPUSValue *sp, int offset); +QJS_HIDE LEPUSValue *prim_js_iterator_close_return(LEPUSContext *ctx, + LEPUSValue *sp, + LEPUSValue *stack_buf); +QJS_HIDE LEPUSValue *prim_js_iterator_close_return_gc(LEPUSContext *ctx, + LEPUSValue *sp); +QJS_HIDE int prim_js_async_iterator_close(LEPUSContext *ctx, LEPUSValue *sp); +QJS_HIDE int prim_js_async_iterator_close_gc(LEPUSContext *ctx, LEPUSValue *sp); +QJS_HIDE int prim_js_async_iterator_get(LEPUSContext *ctx, LEPUSValue *sp, + int flags); +QJS_HIDE int prim_js_async_iterator_get_gc(LEPUSContext *ctx, LEPUSValue *sp, + int flags); +QJS_HIDE LEPUSValue primjs_get_super_ctor(LEPUSContext *ctx, LEPUSValue op); +QJS_HIDE LEPUSValue primjs_get_super_ctor_gc(LEPUSContext *ctx, LEPUSValue op); +QJS_HIDE int prim_js_iterator_call(LEPUSContext *ctx, LEPUSValue *sp, + int flags); + +QJS_HIDE LEPUSValue JS_ToPrimitiveFree(LEPUSContext *ctx, LEPUSValue val, + int hint); +QJS_HIDE LEPUSValue JS_ToPrimitiveFree_GC(LEPUSContext *ctx, LEPUSValue val, + int hint); +QJS_HIDE LEPUSValue JS_ConcatString(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE LEPUSValue JS_ConcatString_GC(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE LEPUSValue prim_js_unary_arith_slow(LEPUSContext *ctx, LEPUSValue op1, + OPCodeEnum op); +QJS_HIDE LEPUSValue prim_js_unary_arith_slow_gc(LEPUSContext *ctx, + LEPUSValue op1, OPCodeEnum op); +QJS_HIDE LEPUSValue prim_js_add_slow(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE LEPUSValue prim_js_add_slow_gc(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE no_inline LEPUSValue prim_js_not_slow(LEPUSContext *ctx, + LEPUSValue op); +QJS_HIDE no_inline LEPUSValue prim_js_not_slow_gc(LEPUSContext *ctx, + LEPUSValue op); +QJS_HIDE LEPUSValue prim_js_binary_arith_slow(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2, OPCodeEnum op); +QJS_HIDE LEPUSValue prim_js_binary_arith_slow_gc(LEPUSContext *ctx, + LEPUSValue op1, LEPUSValue op2, + OPCodeEnum op); +QJS_HIDE double prim_js_fmod_double(double a, double b); +QJS_HIDE double prim_js_fmod_double_gc(double a, double b); +QJS_HIDE int js_post_inc_slow(LEPUSContext *ctx, LEPUSValue *sp, OPCodeEnum op); +QJS_HIDE int js_post_inc_slow_gc(LEPUSContext *ctx, LEPUSValue *sp, + OPCodeEnum op); +QJS_HIDE LEPUSValue prim_js_binary_logic_slow(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2, OPCodeEnum op); +QJS_HIDE LEPUSValue prim_js_binary_logic_slow_gc(LEPUSContext *ctx, + LEPUSValue op1, LEPUSValue op2, + OPCodeEnum op); +QJS_HIDE LEPUSValue prim_js_shr_slow(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE LEPUSValue prim_js_shr_slow_gc(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE LEPUSValue prim_js_relation_slow(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2, OPCodeEnum op); +QJS_HIDE LEPUSValue prim_js_relation_slow_gc(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2, OPCodeEnum op); +QJS_HIDE LEPUSValue prim_js_eq_slow(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2, int is_neq); +QJS_HIDE LEPUSValue prim_js_eq_slow_gc(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2, int is_neq); +QJS_HIDE LEPUSValue prim_js_strict_eq_slow(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2, BOOL is_neq); +QJS_HIDE LEPUSValue prim_js_strict_eq_slow_gc(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2, BOOL is_neq); +QJS_HIDE LEPUSValue prim_js_operator_instanceof(LEPUSContext *ctx, + LEPUSValue op1, LEPUSValue op2); +QJS_HIDE LEPUSValue prim_js_operator_instanceof_gc(LEPUSContext *ctx, + LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE LEPUSValue prim_js_operator_in(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE LEPUSValue prim_js_operator_in_gc(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE __exception int js_operator_typeof(LEPUSContext *ctx, LEPUSValue op1); +QJS_HIDE __exception int js_operator_typeof_gc(LEPUSContext *ctx, + LEPUSValue op1); +QJS_HIDE LEPUSValue prim_js_operator_delete(LEPUSContext *ctx, LEPUSValue op1, + LEPUSValue op2); +QJS_HIDE LEPUSValue prim_js_operator_delete_gc(LEPUSContext *ctx, + LEPUSValue op1, LEPUSValue op2); +QJS_HIDE int JS_SetPropertyInternalImpl(LEPUSContext *ctx, + LEPUSValueConst this_obj, JSAtom prop, + LEPUSValue val, int flags); +QJS_HIDE int JS_SetPropertyInternalImpl_GC(LEPUSContext *ctx, + LEPUSValueConst this_obj, + JSAtom prop, LEPUSValue val, + int flags); + +QJS_HIDE int set_array_length(LEPUSContext *ctx, LEPUSObject *p, + JSProperty *prop, LEPUSValue val, int flags); + +QJS_HIDE BOOL JS_IsUncatchableError(LEPUSContext *ctx, LEPUSValueConst val); +QJS_HIDE BOOL JS_IsUncatchableError_GC(LEPUSContext *ctx, LEPUSValueConst val); +QJS_HIDE JSAtom js_value_to_atom(LEPUSContext *ctx, LEPUSValueConst val); +QJS_HIDE JSAtom js_value_to_atom_gc(LEPUSContext *ctx, LEPUSValueConst val); +QJS_HIDE LEPUSValue JS_ThrowReferenceErrorNotDefined(LEPUSContext *ctx, + JSAtom name); +QJS_HIDE LEPUSValue JS_ThrowReferenceErrorNotDefined_GC(LEPUSContext *ctx, + JSAtom name); +LEPUSValue JS_ThrowTypeErrorNotFunction(LEPUSContext *ctx); + +#ifdef ENABLE_PRIMJS_SNAPSHOT +typedef LEPUSValue (*QuickJsCallStub)(LEPUSValue this_arg, + LEPUSValue new_target, + LEPUSValue func_obj, address entry_point, + int argc, LEPUSValue *argv, int flags); + +extern QuickJsCallStub entry; +QJS_HIDE void compile_function(LEPUSContext *, LEPUSFunctionBytecode *bytecode); +#endif + +// + +typedef struct JSToken { + int val; + int line_num; /* line number of token start */ + const uint8_t *ptr; + union { + struct { + LEPUSValue str; + int sep; + } str; + struct { + LEPUSValue val; +#ifdef CONFIG_BIGNUM + slimb_t exponent; /* may be != 0 only if val is a float */ +#endif + } num; + struct { + JSAtom atom; + BOOL has_escape; + BOOL is_reserved; + } ident; + struct { + LEPUSValue body; + LEPUSValue flags; + } regexp; + } u; +} JSToken; + +#ifndef NO_QUICKJS_COMPILER +LEPUSValue js_dynamic_import(LEPUSContext *ctx, LEPUSValueConst specifier); +#endif + +#ifdef __cplusplus +} +#endif + +/* */ +static __attribute__((unused)) int JS_DefineProperty_RC( + LEPUSContext *ctx, LEPUSValueConst this_obj, JSAtom prop, + LEPUSValueConst val, LEPUSValueConst getter, LEPUSValueConst setter, + int flags); +static __attribute__((unused)) int JS_DefinePropertyValue_RC( + LEPUSContext *ctx, LEPUSValueConst this_obj, JSAtom prop, LEPUSValue val, + int flags); +static __attribute__((unused)) void JS_MarkValue_RC(LEPUSRuntime *rt, + LEPUSValueConst val, + LEPUS_MarkFunc *mark_func, + int local_idx = -1); +static __attribute__((unused)) int JS_ToBoolFree_RC(LEPUSContext *ctx, + LEPUSValue val); +static __attribute__((unused)) int JS_IsInstanceOf_RC(LEPUSContext *ctx, + LEPUSValueConst val, + LEPUSValueConst obj); +static __attribute__((unused)) LEPUSValue JS_ToString_RC(LEPUSContext *ctx, + LEPUSValueConst val); +/* */ + +/* */ +LEPUSValue JS_GetSeparableStringContent_GC(LEPUSContext *ctx, LEPUSValue val); +LEPUSValue JS_GetSeparableStringContentNotDup_GC(LEPUSContext *ctx, + LEPUSValue val); +QJS_HIDE int set_array_length_gc(LEPUSContext *ctx, LEPUSObject *p, + JSProperty *prop, LEPUSValue val, int flags); +bool gc_enabled(); +LEPUSRuntime *JS_NewRuntime_GC(); +LEPUSRuntime *JS_NewRuntime2_GC(const LEPUSMallocFunctions *mf, void *opaque); +void JS_FreeRuntime_GC(LEPUSRuntime *rt); +void JS_FreeRuntimeForEffect(LEPUSRuntime *rt); +LEPUSContext *JS_NewContext_GC(LEPUSRuntime *rt); +void JS_FreeContext_GC(LEPUSContext *ctx); +LEPUSContext *JS_NewContextRaw_GC(LEPUSRuntime *rt); + +void JS_SetMemoryLimit_GC(LEPUSRuntime *rt, size_t limit); +void JS_RunGC_GC(LEPUSRuntime *rt); + +void JS_AddIntrinsicBaseObjects_GC(LEPUSContext *ctx); +void JS_AddIntrinsicDate_GC(LEPUSContext *ctx); +void JS_AddIntrinsicEval_GC(LEPUSContext *ctx); +void JS_AddIntrinsicStringNormalize_GC(LEPUSContext *ctx); +void JS_AddIntrinsicRegExp_GC(LEPUSContext *ctx); +void JS_AddIntrinsicJSON_GC(LEPUSContext *ctx); +void JS_AddIntrinsicProxy_GC(LEPUSContext *ctx); +void JS_AddIntrinsicMapSet_GC(LEPUSContext *ctx); +void JS_AddIntrinsicTypedArrays_GC(LEPUSContext *ctx); +void JS_AddIntrinsicPromise_GC(LEPUSContext *ctx); + +void JS_SetClassProto_GC(LEPUSContext *ctx, LEPUSClassID class_id, + LEPUSValue obj); +LEPUSValue JS_GetClassProto_GC(LEPUSContext *ctx, LEPUSClassID class_id); +int JS_MoveUnhandledRejectionToException_GC(LEPUSContext *ctx); +void JS_AddIntrinsicRegExpCompiler_GC(LEPUSContext *ctx); +#ifdef QJS_UNITTEST +LEPUSValue js_string_codePointRange_GC(LEPUSContext *ctx, + LEPUSValueConst this_val, int argc, + LEPUSValueConst *argv); +#endif + +JSAtom LEPUS_NewAtom(LEPUSContext *ctx, const char *str); +JSAtom JS_NewAtomUInt32_GC(LEPUSContext *ctx, uint32_t n); +LEPUSValue JS_AtomToValue_GC(LEPUSContext *ctx, JSAtom atom); +LEPUSValue JS_AtomToString_GC(LEPUSContext *ctx, JSAtom atom); +typedef struct JSClassShortDef { + JSAtom class_name; + LEPUSClassFinalizer *finalizer; + LEPUSClassGCMark *gc_mark; +} JSClassShortDef; +QJS_HIDE int init_class_range(LEPUSRuntime *rt, JSClassShortDef const *tab, + int start, int count); +const char *JS_AtomToCString_GC(LEPUSContext *ctx, JSAtom atom); + +void JS_MarkValue_GC(LEPUSRuntime *rt, LEPUSValueConst val, + LEPUS_MarkFunc *mark_func, int local_idx = -1); + +LEPUSValue JS_NewInt64_GC(LEPUSContext *ctx, int64_t v); +LEPUSValue JS_NewError_GC(LEPUSContext *ctx); + +void JS_SetVirtualStackSize_GC(LEPUSContext *ctx, uint32_t stack_size); +int JS_ToBool_GC(LEPUSContext *ctx, LEPUSValueConst val); +int JS_ToInt32_GC(LEPUSContext *ctx, int32_t *pres, LEPUSValueConst val); +int JS_ToInt64_GC(LEPUSContext *ctx, int64_t *pres, LEPUSValueConst val); +int JS_ToIndex_GC(LEPUSContext *ctx, uint64_t *plen, LEPUSValueConst val); +int JS_ToFloat64_GC(LEPUSContext *ctx, double *pres, LEPUSValueConst val); +int JS_ToBigInt64_GC(LEPUSContext *ctx, int64_t *pres, LEPUSValueConst val); +LEPUSValue JS_NewStringLen_GC(LEPUSContext *ctx, const char *buf, + size_t buf_len); +LEPUSValue JS_NewString_GC(LEPUSContext *ctx, const char *str); +LEPUSValue JS_NewAtomString_GC(LEPUSContext *ctx, const char *str); +LEPUSValue JS_ToString_GC(LEPUSContext *ctx, LEPUSValueConst val); +LEPUSValue JS_ToPropertyKey_GC(LEPUSContext *ctx, LEPUSValueConst val); +const char *JS_ToCStringLen2_GC(LEPUSContext *ctx, size_t *plen, + LEPUSValueConst val1, BOOL cesu8); + +LEPUSValue __attribute__((always_inline)) +JS_NewObjectProtoClass_GC(LEPUSContext *ctx, LEPUSValueConst proto_val, + LEPUSClassID class_id); +LEPUSValue JS_NewObjectClass_GC(LEPUSContext *ctx, int class_id); +LEPUSValue JS_NewObjectProto_GC(LEPUSContext *ctx, LEPUSValueConst proto); +LEPUSValue JS_NewObject_GC(LEPUSContext *ctx); +LEPUSValue JS_NewArray_GC(LEPUSContext *ctx); +int JS_IsArray_GC(LEPUSContext *ctx, LEPUSValueConst val); + +LEPUSValue JS_GetPropertyInternal_GC(LEPUSContext *ctx, LEPUSValueConst obj, + JSAtom prop, LEPUSValueConst this_obj, + BOOL throw_ref_error); +LEPUSValue JS_GetPropertyStr_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + const char *prop); +LEPUSValue JS_GetPropertyUint32_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + uint32_t idx); +int JS_SetPropertyInternal_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValue val, int flags); + +int JS_SetPropertyUint32_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + uint32_t idx, LEPUSValue val); +int JS_SetPropertyInt64_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + int64_t idx, LEPUSValue val); + +int JS_SetPropertyStr_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + const char *prop, LEPUSValue val); +int JS_HasProperty_GC(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom prop); +int JS_IsExtensible_GC(LEPUSContext *ctx, LEPUSValueConst obj); +int JS_PreventExtensions_GC(LEPUSContext *ctx, LEPUSValueConst obj); +int JS_DeleteProperty_GC(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom prop, + int flags); +int JS_SetPrototype_GC(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst proto_val); +LEPUSValueConst JS_GetPrototype_GC(LEPUSContext *ctx, LEPUSValueConst val); +int JS_GetOwnPropertyNames_GC(LEPUSContext *ctx, LEPUSPropertyEnum **ptab, + uint32_t *plen, LEPUSValueConst obj, int flags); +int JS_GetOwnProperty_GC(LEPUSContext *ctx, LEPUSPropertyDescriptor *desc, + LEPUSValueConst obj, JSAtom prop); +LEPUSValue JS_Call_GC(LEPUSContext *ctx, LEPUSValueConst func_obj, + LEPUSValueConst this_obj, int argc, + LEPUSValueConst *argv); +LEPUSValue JS_CallInternalTI_GC(LEPUSContext *caller_ctx, LEPUSValue func_obj, + LEPUSValue this_obj, LEPUSValue new_target, + int argc, LEPUSValue *argv, int flags); + +LEPUSValue JS_Invoke_GC(LEPUSContext *ctx, LEPUSValueConst this_val, + JSAtom atom, int argc, LEPUSValueConst *argv); +LEPUSValue JS_CallConstructor_GC(LEPUSContext *ctx, LEPUSValueConst func_obj, + int argc, LEPUSValueConst *argv); +LEPUSValue JS_CallConstructor2_GC(LEPUSContext *ctx, LEPUSValueConst func_obj, + LEPUSValueConst new_target, int argc, + LEPUSValueConst *argv); +LEPUSValue JS_Eval_GC(LEPUSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags); +LEPUSValue JS_EvalBinary_GC(LEPUSContext *ctx, const uint8_t *buf, + size_t buf_len, int flags); +LEPUSValue JS_GetGlobalObject_GC(LEPUSContext *ctx); +LEPUSValue JS_GetGlobalVar_GC(LEPUSContext *ctx, JSAtom prop, + BOOL throw_ref_error); +void JS_SetStringCache_GC(LEPUSContext *ctx, LEPUSValue val, void *p); +int JS_SetGlobalVar_GC(LEPUSContext *ctx, JSAtom prop, LEPUSValue val, + int flag); +LEPUSValue JS_DeepEqual_GC(LEPUSContext *ctx, LEPUSValueConst obj1, + LEPUSValueConst obj2); +LEPUSValue JS_DeepCopy_GC(LEPUSContext *ctx, LEPUSValueConst obj); +void JS_IterateObject_GC(LEPUSContext *ctx, LEPUSValue obj, + IterateObject callback, void *pfunc, void *raw_data); +int JS_GetLength_GC(LEPUSContext *ctx, LEPUSValue val); + +int JS_IsInstanceOf_GC(LEPUSContext *ctx, LEPUSValueConst val, + LEPUSValueConst obj); + +int JS_DefineProperty_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValueConst val, + LEPUSValueConst getter, LEPUSValueConst setter, + int flags); +int JS_DefinePropertyValue_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValue val, int flags); +int JS_DefinePropertyValueUint32_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + uint32_t idx, LEPUSValue val, int flags); +int JS_DefinePropertyValueStr_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + const char *prop, LEPUSValue val, int flags); +int JS_DefinePropertyGetSet_GC(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValue getter, + LEPUSValue setter, int flags); +void *JS_GetOpaque2_GC(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSClassID class_id); +LEPUSValue JS_NewArrayBuffer_GC(LEPUSContext *ctx, uint8_t *buf, size_t len, + LEPUSFreeArrayBufferDataFunc *free_func, + void *opaque, BOOL is_shared); +LEPUSValue JS_NewArrayBufferCopy_GC(LEPUSContext *ctx, const uint8_t *buf, + size_t len); +void JS_DetachArrayBuffer_GC(LEPUSContext *ctx, LEPUSValueConst obj); +uint8_t *JS_GetArrayBuffer_GC(LEPUSContext *ctx, size_t *psize, + LEPUSValueConst obj); +uint8_t *JS_MoveArrayBuffer_GC(LEPUSContext *ctx, size_t *psize, + LEPUSValueConst obj); +LEPUS_BOOL JS_StrictEq_GC(LEPUSContext *ctx, LEPUSValueConst op1, + LEPUSValueConst op2); +LEPUS_BOOL JS_SameValue_GC(LEPUSContext *ctx, LEPUSValueConst op1, + LEPUSValueConst op2); +LEPUSValue JS_NewPromiseCapability_GC(LEPUSContext *ctx, + LEPUSValue *resolving_funcs); +int JS_EnqueueJob_GC(LEPUSContext *ctx, LEPUSJobFunc *job_func, int argc, + LEPUSValueConst *argv); +int JS_ExecutePendingJob_GC(LEPUSRuntime *rt, LEPUSContext **pctx); + +uint8_t *JS_WriteObject_GC(LEPUSContext *ctx, size_t *psize, + LEPUSValueConst obj, int flags); +LEPUSValue JS_EvalFunction_GC(LEPUSContext *ctx, LEPUSValue fun_obj, + LEPUSValueConst this_obj); +LEPUSValue JS_NewWString_GC(LEPUSContext *ctx, const uint16_t *buf, + size_t length); +QJS_HIDE LEPUSValue js_json_stringify_opt_GC(LEPUSContext *, LEPUSValue, + int32_t, LEPUSValue *); +JSAtom JS_ValueToAtom_GC(LEPUSContext *ctx, LEPUSValueConst val); +const uint16_t *JS_GetStringChars_GC(LEPUSContext *ctx, LEPUSValueConst str); +QJS_HIDE LEPUSValue JS_NewArrayWithValue_GC(LEPUSContext *ctx, uint32_t length, + LEPUSValueConst *values); +QJS_HIDE LEPUSValue JS_NewTypedArray_GC(LEPUSContext *ctx, uint32_t length, + LEPUSClassID class_id); +QJS_HIDE LEPUSValue JS_NewTypedArrayWithBuffer_GC(LEPUSContext *ctx, + LEPUSValueConst buffer, + uint32_t byteOffset, + uint32_t length, + LEPUSClassID class_id); + +QJS_HIDE LEPUSValue JS_CallConstructorV_GC(LEPUSContext *ctx, + LEPUSValueConst func_obj, int argc, + LEPUSValue *argv); +QJS_HIDE LEPUSValue JS_NewCFunction2_GC(LEPUSContext *ctx, LEPUSCFunction *func, + const char *name, int length, + LEPUSCFunctionEnum cproto, int magic); +QJS_HIDE LEPUSValue JS_NewCFunctionData_GC(LEPUSContext *ctx, + LEPUSCFunctionData *func, int length, + int magic, int data_len, + LEPUSValueConst *data); +QJS_HIDE void JS_SetPropertyFunctionList_GC(LEPUSContext *ctx, + LEPUSValueConst obj, + const LEPUSCFunctionListEntry *tab, + int len); +QJS_HIDE LEPUSValue js_object_getOwnPropertyDescriptor_GC( + LEPUSContext *ctx, LEPUSValueConst this_val, int argc, + LEPUSValueConst *argv, int magic); +QJS_HIDE int js_get_length32_gc(LEPUSContext *ctx, uint32_t *pres, + LEPUSValueConst obj); + +QJS_HIDE LEPUSValue JS_NewObjectWithArgs_GC(LEPUSContext *ctx, int32_t size, + const char **keys, + LEPUSValue *values); +QJS_HIDE LEPUSValue JS_NewArrayWithArgs_GC(LEPUSContext *ctx, int32_t size, + LEPUSValue *values); + +QJS_HIDE __exception int js_append_enumerate_gc(LEPUSContext *ctx, + LEPUSValue *sp); +QJS_HIDE __exception int js_iterator_get_value_done_gc(LEPUSContext *ctx, + LEPUSValue *sp); + +QJS_HIDE int JS_ToBoolFree_GC(LEPUSContext *ctx, LEPUSValue val); +QJS_HIDE LEPUSValue JS_ToPrimitiveFree_GC(LEPUSContext *ctx, LEPUSValue val, + int hint); +QJS_HIDE LEPUSValue JS_NewBigUint64_GC(LEPUSContext *ctx, uint64_t v); + +QJS_HIDE void JS_VisitLEPUSValue_GC(LEPUSRuntime *rt, LEPUSValue *val, + int local_idx); +QJS_HIDE void DisposeGlobal_GC(LEPUSRuntime *runtime, + LEPUSValue *global_handle); +QJS_HIDE LEPUSValue *GlobalizeReference_GC(LEPUSRuntime *runtime, + LEPUSValue val, bool is_weak); +QJS_HIDE void FreeNapiScope_GC(LEPUSContext *ctx); +QJS_HIDE void ClearGlobalWeak_GC(LEPUSRuntime *runtime, + LEPUSValue *global_handle); +QJS_HIDE void SetGlobalWeak_GC(LEPUSRuntime *runtime, LEPUSValue *global_handle, + void *data, void (*cb)(void *)); +QJS_HIDE void *GetNapiScope_GC(LEPUSContext *ctx); +QJS_HIDE void InitNapiScope_GC(LEPUSContext *ctx); +QJS_HIDE void SetNapiScope_GC(LEPUSContext *ctx, void *scope); +QJS_HIDE void SetWeakState_GC(LEPUSRuntime *runtime, LEPUSValue *global_handle); + +QJS_HIDE void JS_SetGCPauseSuppressionMode_GC(LEPUSRuntime *rt, bool mode); +QJS_HIDE bool JS_GetGCPauseSuppressionMode_GC(LEPUSRuntime *rt); +QJS_HIDE __attribute__((unused)) bool CheckValidPtr_GC(void *runtime, + void *ptr); +QJS_HIDE JSString *js_alloc_string(LEPUSContext *ctx, int max_len, + int is_wide_char); +QJS_HIDE int JS_InitAtoms(LEPUSRuntime *rt); +QJS_HIDE int JS_isConcatSpreadable(LEPUSContext *ctx, LEPUSValueConst obj); +typedef struct StringBuffer { + LEPUSContext *ctx; + JSString *str; + int len; + int size; + int is_wide_char; + int error_status; +} StringBuffer; +QJS_HIDE int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len); +QJS_HIDE int string_buffer_concat(StringBuffer *s, const JSString *p, + uint32_t from, uint32_t to); +QJS_HIDE int string_buffer_putc8(StringBuffer *s, uint32_t c); +QJS_HIDE int string_buffer_putc16(StringBuffer *s, uint32_t c); +QJS_HIDE int string_getc(const JSString *p, int *pidx); +QJS_HIDE BOOL test_final_sigma(JSString *p, int sigma_pos); +QJS_HIDE int string_buffer_putc(StringBuffer *s, uint32_t c); +QJS_HIDE int string_buffer_fill(StringBuffer *s, int c, int count); +QJS_HIDE int seal_template_obj_GC(LEPUSContext *ctx, LEPUSValueConst obj); +struct BCReaderState; +QJS_HIDE LEPUSValue JS_ReadError_GC(BCReaderState *s); +QJS_HIDE LEPUSValue JS_ReadRegExp(BCReaderState *s); +QJS_HIDE LEPUSValue JS_ReadMap_GC(BCReaderState *s); +QJS_HIDE LEPUSValue js_typed_array_constructor_GC(LEPUSContext *ctx, + LEPUSValueConst new_target, + int argc, + LEPUSValueConst *argv, + int classid); +QJS_HIDE LEPUSValue JS_ReadObjectRec(BCReaderState *s); +QJS_HIDE LEPUSValue JS_ReadRegExp_GC(BCReaderState *s); +QJS_HIDE JSAtom __JS_NewAtom(LEPUSRuntime *rt, JSString *str, int atom_type); +QJS_HIDE int js_string_compare(LEPUSContext *ctx, const JSString *p1, + const JSString *p2); + +QJS_HIDE JSAtom JS_NewAtomStr(LEPUSContext *ctx, JSString *p); +QJS_HIDE BOOL JS_AtomIsArrayIndex(LEPUSContext *ctx, uint32_t *pval, + JSAtom atom); + +QJS_HIDE JSAtom js_get_atom_index(LEPUSRuntime *rt, JSAtomStruct *p); +QJS_HIDE LEPUSModuleDef *js_new_module_def(LEPUSContext *ctx, JSAtom name); +struct JSFunctionDef; +QJS_HIDE int resolve_labels(LEPUSContext *ctx, JSFunctionDef *s); +QJS_HIDE int resolve_variables(LEPUSContext *ctx, JSFunctionDef *s); +QJS_HIDE int new_label_fd(JSFunctionDef *fd, int label); +struct JSParseState; +QJS_HIDE int js_parse_unary_GC(JSParseState *s, int parse_flags); +QJS_HIDE int js_parse_array_literal(JSParseState *s); +QJS_HIDE int js_parse_cond_expr(JSParseState *s, int parse_flags); +QJS_HIDE int js_parse_assign_expr(JSParseState *s, int parse_flags); +QJS_HIDE int js_parse_expr(JSParseState *s); +QJS_HIDE int js_parse_expr2(JSParseState *s, int parse_flags); +QJS_HIDE __exception int next_token(JSParseState *s); +struct JSToken; +QJS_HIDE int js_parse_string(JSParseState *s, int sep, BOOL do_throw, + const uint8_t *p, JSToken *token, + const uint8_t **pp); + +QJS_HIDE int cpool_add(JSParseState *s, LEPUSValue val); +QJS_HIDE int emit_push_const(JSParseState *s, LEPUSValueConst val, + BOOL as_atom); +QJS_HIDE void emit_op(JSParseState *s, uint8_t val); +QJS_HIDE int emit_label(JSParseState *s, int label); +QJS_HIDE void emit_return(JSParseState *s, BOOL hasval); +QJS_HIDE int emit_goto(JSParseState *s, int opcode, int label); +QJS_HIDE int emit_break(JSParseState *s, JSAtom name, int is_cont); +QJS_HIDE void optional_chain_test(JSParseState *s, + int *poptional_chaining_label, + int drop_count); +QJS_HIDE int js_parse_template_part(JSParseState *s, const uint8_t *p); +QJS_HIDE int js_parse_template(JSParseState *s, int call, int *argc); +struct JSParsePos; +QJS_HIDE int js_parse_get_pos(JSParseState *s, JSParsePos *sp); +QJS_HIDE int js_parse_seek_token(JSParseState *s, const JSParsePos *sp); +QJS_HIDE int js_parse_regexp(JSParseState *s); +QJS_HIDE int js_parse_skip_parens_token(JSParseState *s, int *pbits, + BOOL no_line_terminator, + BOOL *has_ellipsis = nullptr); +QJS_HIDE int js_resize_array(LEPUSContext *ctx, void **parray, int elem_size, + int *psize, int *pcount, int new_count); +QJS_HIDE JSExportEntry *find_export_entry(LEPUSContext *ctx, LEPUSModuleDef *m, + JSAtom export_name); + +QJS_HIDE JSExportEntry *add_export_entry(JSParseState *s, LEPUSModuleDef *m, + JSAtom local_name, JSAtom export_name, + JSExportTypeEnum export_type); +QJS_HIDE void set_object_name_computed(JSParseState *s); +QJS_HIDE BOOL set_object_name(JSParseState *s, JSAtom name); +QJS_HIDE int add_scope_var(LEPUSContext *ctx, JSFunctionDef *fd, JSAtom name, + JSVarKindEnum var_kind); +QJS_HIDE int add_var(LEPUSContext *ctx, JSFunctionDef *fd, JSAtom name); +typedef struct JSHoistedDef { // called JSGlobalVar in latest version + int cpool_idx; /* -1 means variable global definition */ + uint8_t force_init : 1; /* initialize to undefined */ + uint8_t is_lexical : 1; /* global let/const definition */ + uint8_t is_const : 1; /* const definition */ + int var_idx; /* function object index if cpool_idx >= 0 */ + int scope_level; /* scope of definition */ + JSAtom var_name; /* variable name if cpool_idx < 0 */ +} JSHoistedDef; +QJS_HIDE JSHoistedDef *add_hoisted_def(LEPUSContext *ctx, JSFunctionDef *s, + int cpool_idx, JSAtom name, int var_idx, + BOOL is_lexical); +QJS_HIDE int peek_token(JSParseState *s, BOOL no_line_terminator); +QJS_HIDE int push_scope(JSParseState *s); +QJS_HIDE int find_private_class_field(LEPUSContext *ctx, JSFunctionDef *fd, + JSAtom name, int scope_level); +typedef enum JSParseFunctionEnum { + JS_PARSE_FUNC_STATEMENT, + JS_PARSE_FUNC_VAR, + JS_PARSE_FUNC_EXPR, + JS_PARSE_FUNC_ARROW, + JS_PARSE_FUNC_GETTER, + JS_PARSE_FUNC_SETTER, + JS_PARSE_FUNC_METHOD, + JS_PARSE_FUNC_CLASS_CONSTRUCTOR, + JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, +} JSParseFunctionEnum; +typedef enum JSParseExportEnum { + JS_PARSE_EXPORT_NONE, + JS_PARSE_EXPORT_NAMED, + JS_PARSE_EXPORT_DEFAULT, +} JSParseExportEnum; +typedef enum { + JS_VAR_DEF_WITH, + JS_VAR_DEF_LET, + JS_VAR_DEF_CONST, + JS_VAR_DEF_FUNCTION_DECL, /* function declaration */ + JS_VAR_DEF_NEW_FUNCTION_DECL, /* async/generator function declaration */ + JS_VAR_DEF_CATCH, + JS_VAR_DEF_VAR, +} JSVarDefEnum; +QJS_HIDE int js_parse_function_decl2_GC( + JSParseState *s, JSParseFunctionEnum func_type, + JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr, + int function_line_num, JSParseExportEnum export_flag, JSFunctionDef **pfd); +QJS_HIDE int js_parse_class_default_ctor(JSParseState *s, BOOL has_super, + JSFunctionDef **pfd); + +QJS_HIDE int define_var_GC(JSParseState *s, JSFunctionDef *fd, JSAtom name, + JSVarDefEnum var_def_type); + +QJS_HIDE uint64_t compute_column(JSParseState *s, BOOL is_get_var); +QJS_HIDE BOOL js_is_live_code(JSParseState *s); +QJS_HIDE int js_parse_directives(JSParseState *s); +QJS_HIDE int add_closure_var(LEPUSContext *ctx, JSFunctionDef *s, BOOL is_local, + BOOL is_arg, int var_idx, JSAtom var_name, + BOOL is_const, BOOL is_lexical, + JSVarKindEnum var_kind); +QJS_HIDE BOOL is_var_in_arg_scope(LEPUSContext *ctx, const JSVarDef *vd); +QJS_HIDE LEPUSValue js_create_function(LEPUSContext *ctx, JSFunctionDef *fd); +QJS_HIDE void skip_shebang(JSParseState *s); +QJS_HIDE const char *JS_AtomGetStrRT(LEPUSRuntime *rt, char *buf, int buf_size, + JSAtom atom); +QJS_HIDE const char *JS_AtomGetStr(LEPUSContext *ctx, char *buf, int buf_size, + JSAtom atom); +QJS_HIDE int js_parse_error_reserved_identifier(JSParseState *s); +QJS_HIDE BOOL token_is_pseudo_keyword(JSParseState *s, JSAtom atom); +QJS_HIDE BOOL token_is_ident(int tok); +QJS_HIDE int js_parse_expect(JSParseState *s, int tok); +QJS_HIDE int js_parse_object_literal_GC(JSParseState *s); +QJS_HIDE int js_parse_expr_paren(JSParseState *s); +QJS_HIDE __exception JSAtom js_parse_from_clause(JSParseState *s); +QJS_HIDE int add_req_module_entry(LEPUSContext *ctx, LEPUSModuleDef *m, + JSAtom module_name); +QJS_HIDE int js_parse_expect_semi(JSParseState *s); +QJS_HIDE __exception int js_parse_export(JSParseState *s); +QJS_HIDE __exception int js_parse_import(JSParseState *s); +QJS_HIDE LEPUSValue JS_CallFree_GC(LEPUSContext *ctx, LEPUSValue func_obj, + LEPUSValueConst this_obj, int argc, + LEPUSValueConst *argv); +QJS_HIDE int add_star_export_entry(LEPUSContext *ctx, LEPUSModuleDef *m, + int req_module_idx); +QJS_HIDE int js_define_var(JSParseState *s, JSAtom name, int tok); +QJS_HIDE int get_prev_opcode(JSFunctionDef *fd); +QJS_HIDE int update_label(JSFunctionDef *s, int label, int delta); +QJS_HIDE int get_lvalue(JSParseState *s, int *popcode, int *pscope, + JSAtom *pname, int *plabel, int *pdepth, BOOL keep, + int tok); +typedef enum { + PUT_LVALUE_NOKEEP, /* [depth] v -> */ + PUT_LVALUE_NOKEEP_DEPTH, /* [depth] v -> , keep depth (currently + just disable optimizations) */ + PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */ + PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */ + PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */ +} PutLValueEnum; +QJS_HIDE void put_lvalue(JSParseState *s, int opcode, int scope, JSAtom name, + int label, PutLValueEnum special, BOOL is_let); +QJS_HIDE JSAtom js_parse_destructing_var(JSParseState *s, int tok, int is_arg); +QJS_HIDE int js_parse_check_duplicate_parameter(JSParseState *s, JSAtom name); +QJS_HIDE int js_parse_destructing_element_GC(JSParseState *s, int tok, + int is_arg, int hasval, + int has_ellipsis, + BOOL allow_initializer); +QJS_HIDE int js_parse_var(JSParseState *s, int parse_flags, int tok, + BOOL export_flag); +QJS_HIDE LEPUSValue js_evaluate_module(LEPUSContext *ctx, LEPUSModuleDef *m); +QJS_HIDE JSVarRef *js_create_module_var(LEPUSContext *ctx, BOOL is_lexical); +QJS_HIDE int js_create_module_function(LEPUSContext *ctx, LEPUSModuleDef *m); +QJS_HIDE void set_value_gc(LEPUSContext *ctx, LEPUSValue *pval, + LEPUSValue new_val); +QJS_HIDE int JS_DefineAutoInitProperty_GC( + LEPUSContext *ctx, LEPUSValueConst this_obj, JSAtom prop, + LEPUSValue (*init_func)(LEPUSContext *ctx, LEPUSObject *obj, JSAtom prop, + void *opaque), + void *opaque, int flags); +QJS_HIDE int js_link_module(LEPUSContext *ctx, LEPUSModuleDef *m); +QJS_HIDE int skip_spaces(const char *pc); +int JS_DefineObjectName_GC(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom name, + int flags); +QJS_HIDE LEPUSValue js_atod(LEPUSContext *ctx, const char *str, const char **pp, + int radix, int flags); +QJS_HIDE int js_resolve_module(LEPUSContext *ctx, LEPUSModuleDef *m); +QJS_HIDE void close_scopes(JSParseState *s, int scope, int scope_stop); +QJS_HIDE JSFunctionDef *js_new_function_def_GC(LEPUSContext *ctx, + JSFunctionDef *parent, + BOOL is_eval, BOOL is_func_expr, + const char *filename, + int line_num); +QJS_HIDE void pop_scope(JSParseState *s); +QJS_HIDE JSHoistedDef *find_hoisted_def(JSFunctionDef *fd, JSAtom name); +QJS_HIDE BOOL is_child_scope(LEPUSContext *ctx, JSFunctionDef *fd, int scope, + int parent_scope); +QJS_HIDE int find_lexical_decl(LEPUSContext *ctx, JSFunctionDef *fd, + JSAtom name, int scope_idx, + BOOL check_catch_var); +QJS_HIDE int find_arg(LEPUSContext *ctx, JSFunctionDef *fd, JSAtom name); +QJS_HIDE int find_var(LEPUSContext *ctx, JSFunctionDef *fd, JSAtom name); +QJS_HIDE int js_parse_class(JSParseState *s, BOOL is_class_expr, + JSParseExportEnum export_flag); +QJS_HIDE int js_parse_left_hand_side_expr_GC(JSParseState *s); +QJS_HIDE int js_parse_property_name_GC(JSParseState *s, JSAtom *pname, + BOOL allow_method, BOOL allow_var, + BOOL allow_private); +QJS_HIDE int js_parse_function_check_names(JSParseState *s, JSFunctionDef *fd, + JSAtom func_name); +QJS_HIDE int is_let(JSParseState *s, int decl_mask); +QJS_HIDE int __attribute__((format(printf, 2, 3))) QJS_HIDE js_parse_error( + JSParseState *s, const char *fmt, ...); +QJS_HIDE int find_var_in_child_scope(LEPUSContext *ctx, JSFunctionDef *fd, + JSAtom name, int scope_level); +QJS_HIDE int add_arg(LEPUSContext *ctx, JSFunctionDef *fd, JSAtom name); +#ifndef NO_QUICKJS_COMPILER +typedef struct JSParseState { + LEPUSContext *ctx; + int last_line_num; /* line number of last token */ + int line_num; /* line number of current offset */ + const char *filename; + JSToken token; + BOOL got_lf; /* true if got line feed before the current token */ + const uint8_t *last_ptr; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + // + int debugger_last_line_num; + const uint8_t *line_begin_ptr; + const uint8_t *last_line_begin_ptr; + const uint8_t *last_emit_ptr; + const uint8_t *func_call_ptr; + const uint8_t *utf8_parse_front; + int utf8_adapte_size; + int func_call_adapte_size; + int last_utf8_adapte_size; + const uint8_t *last_last_ptr; + // + /* current function code */ + JSFunctionDef *cur_func; + BOOL is_module; /* parsing a module */ + BOOL allow_html_comments; +} JSParseState; +#endif +QJS_HIDE LEPUSValue js_finalizationRegistry_unregister(LEPUSContext *ctx, + LEPUSValueConst this_val, + int argc, + LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_finalizationRegistry_unregister_gc( + LEPUSContext *ctx, LEPUSValueConst this_val, int argc, + LEPUSValueConst *argv); + +QJS_HIDE LEPUSValue js_finalizationRegistry_register(LEPUSContext *ctx, + LEPUSValueConst this_val, + int argc, + LEPUSValueConst *argv); +QJS_HIDE LEPUSValue +js_finalizationRegistry_register_gc(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); + +QJS_HIDE void AddReferenceRecord(LEPUSContext *ctx, LEPUSObject *obj, + LEPUSValue val); +QJS_HIDE const char *generate_json_str(LEPUSContext *ctx, LEPUSValue obj, + size_t &str_len, const char ***str_arr, + size_t &ts, size_t &cs); +typedef union json_val_uni { + int64_t i64; + double f64; + const char *str; + size_t ofs; + LEPUSValue bigf; // for JSON.stringify bigfloat + LEPUSValue num; // for JSON.parse number +} json_val_uni; + +struct json_val { + uint64_t tag; /**< type, subtype and length */ + json_val_uni uni; /**< payload */ +}; +QJS_HIDE int make_json_val_incr(LEPUSContext *ctx, size_t &alc_len, + json_val **val_hdr, json_val **val, + json_val **ctn, json_val **val_end); +QJS_HIDE uint8_t *json_val_write_format(LEPUSContext *ctx, json_val *root, + const char *gap_str); +QJS_HIDE uint8_t *json_val_write(LEPUSContext *ctx, json_val *root); +QJS_HIDE void parse_json_space(JSParseState *s, const uint8_t **cur); +QJS_HIDE bool read_string(JSParseState *s, uint8_t **ptr, json_val *val); +QJS_HIDE json_val *json_parse_value(JSParseState *s, size_t dat_len); +QJS_HIDE void js_parse_init(LEPUSContext *ctx, JSParseState *s, + const char *input, size_t input_len, + const char *filename); +QJS_HIDE LEPUSValue JS_ParseJSONOPT(LEPUSContext *ctx, const char *buf, + size_t buf_len, const char *filename); +QJS_HIDE LEPUSObject *get_typed_array(LEPUSContext *ctx, + LEPUSValueConst this_val, + int is_dataview); +QJS_HIDE BOOL typed_array_is_detached(LEPUSContext *ctx, LEPUSObject *p); +QJS_HIDE LEPUSValue js_dataview_getValue(LEPUSContext *ctx, + LEPUSValueConst this_obj, int argc, + LEPUSValueConst *argv, int class_id); +QJS_HIDE LEPUSValue js_dataview_setValue(LEPUSContext *ctx, + LEPUSValueConst this_obj, int argc, + LEPUSValueConst *argv, int class_id); +QJS_HIDE JSArrayBuffer *js_get_array_buffer(LEPUSContext *ctx, + LEPUSValueConst obj); +QJS_HIDE LEPUSValue js_create_from_ctor_GC(LEPUSContext *ctx, + LEPUSValueConst ctor, int class_id); +QJS_HIDE LEPUSValue js_dataview_constructor(LEPUSContext *ctx, + LEPUSValueConst new_target, + int argc, LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_typed_array_get_buffer(LEPUSContext *ctx, + LEPUSValueConst this_val, + int is_dataview); +QJS_HIDE LEPUSValue js_typed_array_get_length(LEPUSContext *ctx, + LEPUSValueConst this_val); +QJS_HIDE LEPUSValue js_typed_array_get_byteLength(LEPUSContext *ctx, + LEPUSValueConst this_val, + int is_dataview); +QJS_HIDE LEPUSValue js_typed_array_get_byteOffset(LEPUSContext *ctx, + LEPUSValueConst this_val, + int is_dataview); +const LEPUSCFunctionListEntry js_dataview_proto_funcs[] = { + LEPUS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 1), + LEPUS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, + 1), + LEPUS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, + 1), + LEPUS_CFUNC_MAGIC_DEF("getInt8", 1, js_dataview_getValue, + JS_CLASS_INT8_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("getUint8", 1, js_dataview_getValue, + JS_CLASS_UINT8_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("getInt16", 1, js_dataview_getValue, + JS_CLASS_INT16_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("getUint16", 1, js_dataview_getValue, + JS_CLASS_UINT16_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("getInt32", 1, js_dataview_getValue, + JS_CLASS_INT32_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("getUint32", 1, js_dataview_getValue, + JS_CLASS_UINT32_ARRAY), +#ifdef CONFIG_BIGNUM + LEPUS_CFUNC_MAGIC_DEF("getBigInt64", 1, js_dataview_getValue, + JS_CLASS_BIG_INT64_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("getBigUint64", 1, js_dataview_getValue, + JS_CLASS_BIG_UINT64_ARRAY), +#endif + LEPUS_CFUNC_MAGIC_DEF("getFloat32", 1, js_dataview_getValue, + JS_CLASS_FLOAT32_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("getFloat64", 1, js_dataview_getValue, + JS_CLASS_FLOAT64_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("setInt8", 2, js_dataview_setValue, + JS_CLASS_INT8_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("setUint8", 2, js_dataview_setValue, + JS_CLASS_UINT8_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("setInt16", 2, js_dataview_setValue, + JS_CLASS_INT16_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("setUint16", 2, js_dataview_setValue, + JS_CLASS_UINT16_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("setInt32", 2, js_dataview_setValue, + JS_CLASS_INT32_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("setUint32", 2, js_dataview_setValue, + JS_CLASS_UINT32_ARRAY), +#ifdef CONFIG_BIGNUM + LEPUS_CFUNC_MAGIC_DEF("setBigInt64", 2, js_dataview_setValue, + JS_CLASS_BIG_INT64_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("setBigUint64", 2, js_dataview_setValue, + JS_CLASS_BIG_UINT64_ARRAY), +#endif + LEPUS_CFUNC_MAGIC_DEF("setFloat32", 2, js_dataview_setValue, + JS_CLASS_FLOAT32_ARRAY), + LEPUS_CFUNC_MAGIC_DEF("setFloat64", 2, js_dataview_setValue, + JS_CLASS_FLOAT64_ARRAY), + LEPUS_PROP_STRING_DEF("[Symbol.toStringTag]", "DataView", + LEPUS_PROP_CONFIGURABLE), +}; +QJS_HIDE void js_dtoa1(char *buf, double d, int radix, int n_digits, int flags); +QJS_HIDE LEPUSValue js_closure2(LEPUSContext *ctx, LEPUSValue func_obj, + LEPUSFunctionBytecode *b, + JSVarRef **cur_var_refs, LEPUSStackFrame *sf); +QJS_HIDE void js_random_init(LEPUSContext *ctx); +QJS_HIDE LEPUSValue js_math_random(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); +QJS_HIDE int getTimezoneOffset(int64_t time); +QJS_HIDE int JS_SetPrototypeInternal_GC(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst proto_val, + BOOL throw_flag); +QJS_HIDE LEPUSValue js_reflect_setPrototypeOf(LEPUSContext *ctx, + LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_reflect_has(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_reflect_set(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_reflect_get(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); +QJS_HIDE LEPUSValue js_reflect_deleteProperty(LEPUSContext *ctx, + LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); +QJS_HIDE int JS_SetPropertyGeneric_GC(LEPUSContext *ctx, LEPUSObject *p, + JSAtom prop, LEPUSValue val, + LEPUSValueConst this_obj, int flags); +QJS_HIDE void js_shape_hash_unlink(LEPUSRuntime *rt, JSShape *sh); +QJS_HIDE void js_shape_hash_link(LEPUSRuntime *rt, JSShape *sh); +QJS_HIDE int resize_shape_hash(LEPUSRuntime *rt, int new_shape_hash_bits); +QJS_HIDE int resize_properties(LEPUSContext *ctx, JSShape **psh, LEPUSObject *p, + uint32_t count); +QJS_HIDE BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size); +QJS_HIDE void build_backtrace_frame(LEPUSContext *ctx, LEPUSStackFrame *sf, + DynBuf *dbuf, const uint8_t *cur_pc, + BOOL is_async, BOOL is_debug_mode, + LEPUSValueConst error_obj); +QJS_HIDE void get_backtrace(LEPUSContext *ctx, DynBuf *dbuf, BOOL is_debug_mode, + LEPUSValueConst error_obj, const uint8_t *cur_pc, + int backtrace_flags); +QJS_HIDE LEPUSValue JS_GetIterator(LEPUSContext *ctx, LEPUSValueConst obj, + BOOL is_async); +QJS_HIDE LEPUSValue JS_GetIterator2(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst method); +QJS_HIDE LEPUSValue JS_IteratorNext2(LEPUSContext *ctx, + LEPUSValueConst enum_obj, + LEPUSValueConst method, int argc, + LEPUSValueConst *argv, int *pdone); +QJS_HIDE LEPUSValue JS_IteratorNext(LEPUSContext *ctx, LEPUSValueConst enum_obj, + LEPUSValueConst method, int argc, + LEPUSValueConst *argv, BOOL *pdone); +QJS_HIDE JSShapeProperty *find_own_property1(LEPUSObject *p, JSAtom atom); +QJS_HIDE LEPUSValue JS_ThrowError(LEPUSContext *ctx, JSErrorEnum error_num, + const char *fmt, va_list ap); +QJS_HIDE int __attribute__((format(printf, 3, 4))) +JS_ThrowTypeErrorOrFalse(LEPUSContext *ctx, int flags, const char *fmt, ...); +QJS_HIDE int __attribute__((format(printf, 2, 3))) QJS_HIDE js_throw_URIError( + LEPUSContext *ctx, const char *fmt, ...); +QJS_HIDE void *LEPUS_GetOpaque2(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSClassID class_id); +QJS_HIDE JSRegExp *js_get_regexp(LEPUSContext *ctx, LEPUSValueConst obj, + BOOL throw_error); +QJS_HIDE LEPUSValue __attribute__((format(printf, 3, 4))) +__JS_ThrowTypeErrorAtom(LEPUSContext *ctx, JSAtom atom, const char *fmt, ...); +QJS_HIDE LEPUSValue JS_ThrowTypeErrorPrivateNotFound(LEPUSContext *ctx, + JSAtom atom); +typedef struct BlockEnv { + struct BlockEnv *prev; + JSAtom label_name; /* JS_ATOM_NULL if none */ + int label_break; /* -1 if none */ + int label_cont; /* -1 if none */ + int drop_count; /* number of stack elements to drop */ + int label_finally; /* -1 if none */ + int scope_level; + int has_iterator; +} BlockEnv; + +typedef struct RelocEntry { + struct RelocEntry *next; + uint32_t addr; /* address to patch */ + int size; /* address size: 1, 2 or 4 bytes */ +} RelocEntry; + +typedef struct JumpSlot { + int op; + int size; + int pos; + int label; +} JumpSlot; + +typedef struct LabelSlot { + int ref_count; + int pos; /* phase 1 address, -1 means not resolved yet */ + int pos2; /* phase 2 address, -1 means not resolved yet */ + int addr; /* phase 3 address, -1 means not resolved yet */ + RelocEntry *first_reloc; +} LabelSlot; + +typedef struct LineNumberSlot { + uint32_t pc; + // + uint64_t line_num; + // +} LineNumberSlot; + +typedef struct JSFunctionDef { + LEPUSContext *ctx; + struct JSFunctionDef *parent; + int parent_cpool_idx; /* index in the constant pool of the parent + or -1 if none */ + int parent_scope_level; /* scope level in parent at point of definition */ + struct list_head child_list; /* list of JSFunctionDef.link */ + struct list_head link; + + BOOL is_eval; /* TRUE if eval code */ + int eval_type; /* only valid if is_eval = TRUE */ + BOOL is_global_var; /* TRUE if variables are not defined locally: + eval global, eval module or non strict eval */ + BOOL is_func_expr; /* TRUE if function expression */ + BOOL has_home_object; /* TRUE if the home object is available */ + BOOL has_prototype; /* true if a prototype field is necessary */ + BOOL has_simple_parameter_list; + BOOL has_parameter_expressions; /* if true, an argument scope is created */ + BOOL has_use_strict; /* to reject directive in special cases */ + BOOL has_eval_call; /* true if the function contains a call to eval() */ + BOOL has_arguments_binding; /* true if the 'arguments' binding is + available in the function */ + BOOL has_this_binding; /* true if the 'this' and new.target binding are + available in the function */ + BOOL new_target_allowed; /* true if the 'new.target' does not + throw a syntax error */ + BOOL super_call_allowed; /* true if super() is allowed */ + BOOL super_allowed; /* true if super. or super[] is allowed */ + BOOL arguments_allowed; /* true if the 'arguments' identifier is allowed */ + BOOL is_derived_class_constructor; + BOOL in_function_body; + JSFunctionKindEnum func_kind : 8; + JSParseFunctionEnum func_type : 8; + uint8_t js_mode; /* bitmap of JS_MODE_x */ + JSAtom func_name; /* JS_ATOM_NULL if no name */ + + JSVarDef *vars; + int var_size; /* allocated size for vars[] */ + int var_count; + JSVarDef *args; + int arg_size; /* allocated size for args[] */ + int arg_count; /* number of arguments */ + int defined_arg_count; + int var_object_idx; /* -1 if none */ + int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ + int arguments_var_idx; /* -1 if none */ + int arguments_arg_idx; /* argument variable definition in argument scope, -1 + if none */ + int func_var_idx; /* variable containing the current function (-1 + if none, only used if is_func_expr is true) */ + int eval_ret_idx; /* variable containing the return value of the eval, -1 if + none */ + int this_var_idx; /* variable containg the 'this' value, -1 if none */ + int new_target_var_idx; /* variable containg the 'new.target' value, -1 if + none */ + int this_active_func_var_idx; /* variable containg the 'this.active_func' + value, -1 if none */ + int home_object_var_idx; + BOOL need_home_object; + + int scope_level; /* index into fd->scopes if the current lexical scope */ + int scope_first; /* index into vd->vars of first lexically scoped variable */ + int scope_size; /* allocated size of fd->scopes array */ + int scope_count; /* number of entries used in the fd->scopes array */ + JSVarScope *scopes; + JSVarScope def_scope_array[4]; + int body_scope; /* scope of the body of the function or eval */ + + int hoisted_def_count; + int hoisted_def_size; + JSHoistedDef *hoisted_def; + + DynBuf byte_code; + int last_opcode_pos; /* -1 if no last opcode */ + int last_opcode_line_num; + BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */ + + LabelSlot *label_slots; + int label_size; /* allocated size for label_slots[] */ + int label_count; + BlockEnv *top_break; /* break/continue label stack */ + + /* constant pool (strings, functions, numbers) */ + LEPUSValue *cpool; + uint32_t cpool_count; + uint32_t cpool_size; + + /* list of variables in the closure */ + int closure_var_count; + int closure_var_size; + LEPUSClosureVar *closure_var; + + JumpSlot *jump_slots; + int jump_size; + int jump_count; + + LineNumberSlot *line_number_slots; + int line_number_size; + int line_number_count; + // + int64_t line_number_last; + int64_t line_number_last_pc; + // + + /* pc2line table */ + JSAtom filename; + int line_num; +#ifdef ENABLE_QUICKJS_DEBUGGER + int64_t column_num; + LEPUSScriptSource *script; +#endif + DynBuf pc2line; + + CallerStrSlot *caller_slots; + int32_t caller_size; + int32_t caller_count; + int32_t resolve_caller_count; + bool should_add_slot; + + const char *src_start; + char *source; /* raw source, utf-8 encoded */ + int source_len; + + LEPUSModuleDef *module; /* != NULL when parsing a module */ +} JSFunctionDef; + +QJS_HIDE int add_closure_variables(LEPUSContext *ctx, JSFunctionDef *s, + LEPUSFunctionBytecode *b, int scope_idx); +QJS_HIDE int JS_DefinePropertyValueInt64_GC(LEPUSContext *ctx, + LEPUSValueConst this_obj, + int64_t idx, LEPUSValue val, + int flags); +QJS_HIDE LEPUSValue js_error_constructor(LEPUSContext *ctx, + LEPUSValueConst new_target, int argc, + LEPUSValueConst *argv, int magic); + +QJS_HIDE BOOL JS_AtomIsString(LEPUSContext *ctx, JSAtom v); + +QJS_HIDE LEPUSValue JS_NewObjectFromShape(LEPUSContext *, JSShape *, + LEPUSClassID); + +QJS_HIDE LEPUSValue JS_NewObjectFromShape_GC(LEPUSContext *, JSShape *, + LEPUSClassID); + +QJS_HIDE LEPUSFunctionBytecode *JS_GetFunctionBytecode(LEPUSValueConst); +QJS_HIDE JSAtom js_symbol_to_atom(LEPUSContext *, LEPUSValue); +QJS_HIDE LEPUSValueConst JS_GetActiveFunction(LEPUSContext *ctx); +QJS_HIDE LEPUSValue js_array_buffer_get_byteLength(LEPUSContext *, + LEPUSValueConst, int32_t); +#ifndef NO_QUICKJS_COMPILER +// the last two parameters are needed for qjs debugger, default value: false, +// NULL +QJS_HIDE LEPUSValue JS_EvalInternal(LEPUSContext *, LEPUSValue, const char *, + size_t, const char *, int, int, + bool = false, LEPUSStackFrame * = nullptr); +#endif + +LEPUSValue js_array_reduce_gc(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv, int special); + +LEPUSValue js_array_concat_gc(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); +/* Shape support */ +QJS_STATIC inline JSShapeProperty *get_shape_prop(JSShape *sh) { + return sh->prop; +} + +QJS_STATIC inline size_t get_shape_size(size_t hash_size, size_t prop_size) { + return hash_size * sizeof(uint32_t) + sizeof(JSShape) + + prop_size * sizeof(JSShapeProperty); +} + +QJS_STATIC inline JSShape *get_shape_from_alloc(void *sh_alloc, + size_t hash_size) { + return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); +} + +QJS_STATIC inline void *get_alloc_from_shape(JSShape *sh) { + return sh->prop_hash_end - ((intptr_t)sh->prop_hash_mask + 1); +} + +QJS_STATIC inline BOOL atom_is_free(const JSAtomStruct *p) { + return (uintptr_t)p & 1; +} + +QJS_STATIC inline BOOL __JS_AtomIsTaggedInt(JSAtom v) { + return (v & JS_ATOM_TAG_INT) != 0; +} + +QJS_STATIC inline JSAtom __JS_AtomFromUInt32(uint32_t v) { + return v | JS_ATOM_TAG_INT; +} + +QJS_STATIC inline uint32_t __JS_AtomToUInt32(JSAtom atom) { + return atom & ~JS_ATOM_TAG_INT; +} + +QJS_HIDE JSAtomKindEnum JS_AtomGetKind(LEPUSContext *, JSAtom); +QJS_HIDE LEPUS_BOOL JS_LepusRefIsArray(LEPUSRuntime *rt, LEPUSValue v); +QJS_HIDE LEPUS_BOOL JS_LepusRefIsTable(LEPUSRuntime *rt, LEPUSValue v); +QJS_HIDE JSShape *js_dup_shape(JSShape *sh); + +typedef struct JSCFunctionDataRecord { + LEPUSCFunctionData *func; + uint8_t length; + uint8_t data_len; + uint16_t magic; + LEPUSValue data[0]; +} JSCFunctionDataRecord; + +typedef struct ValueBuffer { + LEPUSContext *ctx; + LEPUSValue *arr; + LEPUSValue def[4]; + int len; + int size; + int error_status; +} ValueBuffer; + +typedef enum JSPromiseStateEnum { + JS_PROMISE_PENDING, + JS_PROMISE_FULFILLED, + JS_PROMISE_REJECTED, +} JSPromiseStateEnum; + +typedef struct JSPromiseData { + JSPromiseStateEnum promise_state; + /* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */ + struct list_head promise_reactions[2]; + BOOL is_handled; /* Note: only useful to debug */ + LEPUSValue promise_result; +} JSPromiseData; + +typedef struct JSPromiseFunctionDataResolved { + int ref_count; + BOOL already_resolved; +} JSPromiseFunctionDataResolved; + +typedef struct JSPromiseFunctionData { + LEPUSValue promise; + JSPromiseFunctionDataResolved *presolved; +} JSPromiseFunctionData; + +typedef struct JSPromiseReactionData { + struct list_head link; /* not used in promise_reaction_job */ + LEPUSValue resolving_funcs[2]; + LEPUSValue handler; +} JSPromiseReactionData; + +typedef struct ReferenceRecord { + int max_size; + LEPUSValue *references; + int length; +} ReferenceRecord; + +typedef struct RegistryRecord { + struct ReferenceRecord *registra; + struct ReferenceRecord *heldvalue; + struct ReferenceRecord *target; + struct ReferenceRecord *token; + struct JSFinalizationRegistryEntry *entry; + int *idx; +} RegistryRecord; + +typedef struct JSMapRecord { + int ref_count; /* used during enumeration to avoid freeing the record */ + BOOL empty; /* TRUE if the record is deleted */ + struct JSMapState *map; + struct list_head link; + struct list_head hash_link; + LEPUSValue key; + LEPUSValue value; +} JSMapRecord; + +typedef struct ValueSlot { + LEPUSValue val; + JSString *str; + int64_t pos; +} ValueSlot; + +struct array_sort_context { + LEPUSContext *ctx; + int exception; + int has_method; + LEPUSValueConst method; +}; + +typedef struct FinalizerOpaque { + LEPUSContext *ctx; +} FinalizerOpaque; + +/* */ +#define BC_NEW_PREFIX 0x8 +#define VERSION_PLACEHOLDER 0xCAB00000 +#define NEW_DEBUGINFO_FLAG 0x100000000 + +bool JS_IsNewVersion(LEPUSContext *ctx); +bool JS_CheckBytecodeVersion(uint64_t v64); + +QJS_HIDE bool primjs_snapshot_enabled(); + +QJS_HIDE void DeleteCurNode(LEPUSRuntime *rt, void *node, int type); +QJS_HIDE bool CheckValidNode(LEPUSRuntime *rt, void *node, int type); + +// #sec-tointgerorinfinity +inline double DoubleToInteger(double x) { + if (isnan(x) || x == 0.0) return 0; + if (!isfinite(x)) return x; + return ((x > 0) ? floor(x) : ceil(x)) + 0.0; +} + +inline void insert_weakref_record(LEPUSObject *p, + struct WeakRefRecord *record) { + record->next_weak_ref = p->first_weak_ref; + p->first_weak_ref = record; + return; +} + +char *js_strmalloc(const char *s, size_t n); +void AddLepusRefCount(LEPUSContext *ctx); + +class LynxTraceInstance { + public: + using BeginPtr = void *(*)(const char *); + using EndPtr = void (*)(void *); + static auto &GetInstance() { + static LynxTraceInstance instance; + return instance; + } + void InitBeginPtr(BeginPtr begin) { trace_start_ = begin; } + void InitEndPtr(EndPtr end) { trace_end_ = end; } + + auto GetBeginPtr() const { return trace_start_; } + auto GetEndPtr() const { return trace_end_; } + + private: + BeginPtr trace_start_{nullptr}; + EndPtr trace_end_{nullptr}; +}; + +class TraceManager { + public: + explicit TraceManager(const char *name) { + if (auto call_begin = LynxTraceInstance::GetInstance().GetBeginPtr()) { + ptr = call_begin(name); + } + } + + ~TraceManager() { + if (auto call_end = LynxTraceInstance::GetInstance().GetEndPtr()) { + call_end(ptr); + } + } + + private: + void *ptr{nullptr}; +}; + +#define JS_OBJECT_IS_OUTER(obj) (obj->class_id >= JS_CLASS_INIT_COUNT) + +#ifdef ENABLE_QUICKJS_DEBUGGER +#define TRACE_EVENT(name) auto tracer = TraceManager{name}; + +#ifdef ENABLE_COMPATIBLE_MM +#define DEBUGGER_COMPATIBLE_CALL_RET(ctx, name, args...) \ + (ctx->rt->gc_enable) ? (name##_GC(args)) : (name(args)) +#else +#define DEBUGGER_COMPATIBLE_CALL_RET(ctx, name, args...) (name(args)) +#endif /* ENABLE_COMPATIBLE_MM */ + +#else +#define TRACE_EVENT(name) +#define DEBUGGER_COMPATIBLE_CALL_RET +#endif /* ENABLE_QUICKJS_DEBUGGER */ + +int64_t date_now(); + +inline bool json_opt_disabled() { return JSON_OPT_DISABLE & settingsFlag; } +inline bool json_opt_disabled(LEPUSRuntime *rt) { + return rt->settings_option.disable_json_opt; +} +inline bool deepclone_opt_disabled() { + return DEEPCLONE_OPT_DISABLE & settingsFlag; +} + +inline bool deepclone_opt_disabled(LEPUSRuntime *rt) { + return rt->settings_option.disable_deepclone_opt; +} + +inline bool separable_string_disabled() { + return DISABLE_SEPARABLE_STRING & settingsFlag; +} + +inline bool minify_virtual_stack_size_enabled() { + return settingsFlag & MINIFY_STACK_ENABLE; +} + +inline bool separable_string_disabled(LEPUSRuntime *rt) { + return rt->settings_option.disable_separable_string; +} + +inline bool adjust_stacksize_disabled() { + return DISABLE_ADJUST_STACKSIZE & settingsFlag; +} + +inline void js_init_settings_options(LEPUSRuntime *rt) { + rt->settings_option.disable_adjust_stacksize = adjust_stacksize_disabled(); + rt->settings_option.disable_json_opt = json_opt_disabled(); + rt->settings_option.disable_deepclone_opt = deepclone_opt_disabled(); + rt->settings_option.disable_separable_string = separable_string_disabled(); + return; +} + +inline bool js_is_bytecode_function(LEPUSValue obj) { + return (LEPUS_VALUE_IS_OBJECT(obj)) && + (LEPUS_VALUE_GET_OBJ(obj)->class_id == JS_CLASS_BYTECODE_FUNCTION); +} + +bool emit_name_str(JSParseState *s, const uint8_t *start, const uint8_t *end); +void get_caller_string(JSFunctionDef *s); + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_INNER_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-libc.h b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-libc.h new file mode 100644 index 000000000..f9844d866 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-libc.h @@ -0,0 +1,66 @@ +/* + * QuickJS C library + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_LIBC_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_LIBC_H_ + +#include +#include + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include // portable: uint64_t MSVC: __int64 +#include +#endif + +#include "quickjs.h" + +QJS_HIDE LEPUSModuleDef *lepus_init_module_std(LEPUSContext *ctx, + const char *module_name); +QJS_HIDE LEPUSModuleDef *lepus_init_module_os(LEPUSContext *ctx, + const char *module_name); +QJS_HIDE void lepus_std_add_helpers(LEPUSContext *ctx, int argc, char **argv); +void lepus_std_loop(LEPUSContext *ctx); +QJS_HIDE void lepus_std_free_handlers(LEPUSRuntime *rt); +QJS_HIDE void lepus_std_dump_error(LEPUSContext *ctx); + +#if LYNX_SIMPLIFY +uint8_t *lepus_load_file(LEPUSContext *ctx, size_t *pbuf_len, + const char *filename); +LEPUSModuleDef *lepus_module_loader(LEPUSContext *ctx, const char *module_name, + void *opaque); +void lepus_std_eval_binary(LEPUSContext *ctx, const uint8_t *buf, + size_t buf_len, int flags); +#endif + +#if defined(_WIN32) +int gettimeofday(struct timeval *tp, void *tzp); +#endif + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_LIBC_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-opcode.h b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-opcode.h new file mode 100644 index 000000000..871b32afd --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-opcode.h @@ -0,0 +1,385 @@ +/* + * QuickJS opcode definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +// +// clang-format off +#ifdef FMT +FMT(none) +FMT(none_int) +FMT(none_loc) +FMT(none_arg) +FMT(none_var_ref) +FMT(u8) +FMT(i8) +FMT(loc8) +FMT(const8) +FMT(label8) +FMT(u16) +FMT(i16) +FMT(label16) +FMT(npop) +FMT(npopx) +FMT(loc) +FMT(arg) +FMT(var_ref) +FMT(u32) +FMT(i32) +FMT(const) +FMT(label) +FMT(atom) +FMT(atom_u8) +FMT(atom_u16) +FMT(atom_label_u8) +FMT(atom_label_u16) +FMT(label_u16) +// +FMT(u64) +// +#undef FMT +#endif /* FMT */ + +#ifdef DEF + +#ifndef def +#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f) +#endif + +DEF(invalid, 1, 0, 0, none) /* never emitted */ + +/* push values */ +DEF(push_i32, 5, 0, 1, i32) +DEF(push_const, 5, 0, 1, const) +DEF(fclosure, 5, 0, 1, const) /* must follow push_const */ +DEF(push_atom_value, 5, 0, 1, atom) +DEF(private_symbol, 5, 0, 1, atom) +DEF(undefined, 1, 0, 1, none) +DEF(null, 1, 0, 1, none) +DEF(push_this, 1, 0, 1, none) /* only used at the start of a function */ +DEF(push_false, 1, 0, 1, none) +DEF(push_true, 1, 0, 1, none) +DEF(object, 1, 0, 1, none) +DEF(special_object, 2, 0, 1, u8) /* only used at the start of a function */ +DEF(rest, 3, 0, 1, u16) /* only used at the start of a function */ + +DEF(drop, 1, 1, 0, none) /* a -> */ +DEF(nip, 1, 2, 1, none) /* a b -> b */ +DEF(nip1, 1, 3, 2, none) /* a b c -> b c */ +DEF(dup, 1, 1, 2, none) /* a -> a a */ +DEF(dup1, 1, 2, 3, none) /* a b -> a a b */ +DEF(dup2, 1, 2, 4, none) /* a b -> a b a b */ +DEF(dup3, 1, 3, 6, none) /* a b c -> a b c a b c */ +DEF(insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */ +DEF(insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */ +DEF(insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */ +DEF(perm3, 1, 3, 3, none) /* obj a b -> a obj b */ +DEF(perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */ +DEF(perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */ +DEF(swap, 1, 2, 2, none) /* a b -> b a */ +DEF(swap2, 1, 4, 4, none) /* a b c d -> c d a b */ +DEF(rot3l, 1, 3, 3, none) /* x a b -> a b x */ +DEF(rot3r, 1, 3, 3, none) /* a b x -> x a b */ +DEF(rot4l, 1, 4, 4, none) /* x a b c -> a b c x */ +DEF(rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */ + +DEF(call_constructor, 3, 2, 1, + npop) /* func new.target args -> ret. arguments are not counted in n_pop */ +DEF(call, 3, 1, 1, npop) /* arguments are not counted in n_pop */ +DEF(tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */ +DEF(call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */ +DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */ +DEF(array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ +DEF(apply, 3, 3, 1, u16) +DEF(return, 1, 1, 0, none) +DEF(return_undef, 1, 0, 0, none) +DEF(check_ctor_return, 1, 1, 2, none) +DEF(check_ctor, 1, 0, 0, none) +DEF(check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */ +DEF(add_brand, 1, 2, 0, none) /* this_obj home_obj -> */ +DEF(return_async, 1, 1, 0, none) +DEF(throw, 1, 1, 0, none) +DEF(throw_var, 6, 0, 0, atom_u8) +DEF(eval, 3, 1, 1, u16) +DEF(regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a + bytecode string */ +DEF(get_super_ctor, 1, 1, 1, none) +DEF(get_super, 1, 1, 1, none) +DEF(import, 1, 1, 1, none) /* dynamic module import */ + +DEF(check_var, 5, 0, 1, atom) /* check if a variable exists */ + +DEF(get_var_undef, 5, 0, 1, + atom) /* push undefined if the variable does not exist */ +DEF(get_var, 5, 0, 1, + atom) /* throw an exception if the variable does not exist */ + +DEF(put_var, 5, 1, 0, atom) /* must come after get_var */ +DEF(put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize + a global lexical variable */ +DEF(put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */ + +DEF(get_ref_value, 1, 2, 3, none) +DEF(put_ref_value, 1, 3, 0, none) + +DEF(define_var, 6, 0, 0, atom_u8) +DEF(check_define_var, 6, 0, 0, atom_u8) +DEF(define_func, 6, 1, 0, atom_u8) + +DEF(get_field, 5, 1, 1, atom) +DEF(get_field2, 5, 1, 2, atom) +DEF(put_field, 5, 2, 0, atom) + +DEF(get_private_field, 1, 2, 1, none) /* obj prop -> value */ +DEF(put_private_field, 1, 3, 0, none) /* obj value prop -> */ +DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ +DEF(get_array_el, 1, 2, 1, none) +DEF(get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ +DEF(put_array_el, 1, 3, 0, none) +DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */ +DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */ +DEF(define_field, 5, 2, 1, atom) +DEF(set_name, 5, 1, 1, atom) +DEF(set_name_computed, 1, 2, 2, none) +DEF(set_proto, 1, 2, 1, none) +DEF(set_home_object, 1, 2, 2, none) +DEF(define_array_el, 1, 3, 2, none) +DEF(append, 1, 3, 2, none) /* append enumerated object, update length */ +DEF(copy_data_properties, 2, 3, 3, u8) +DEF(define_method, 6, 2, 1, atom_u8) +DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */ +DEF(define_class, 6, 2, 2, atom_u8) + +DEF(get_loc, 3, 0, 1, loc) +DEF(put_loc, 3, 1, 0, loc) /* must come after get_loc */ +DEF(set_loc, 3, 1, 1, loc) /* must come after put_loc */ +DEF(get_arg, 3, 0, 1, arg) +DEF(put_arg, 3, 1, 0, arg) /* must come after get_arg */ +DEF(set_arg, 3, 1, 1, arg) /* must come after put_arg */ +DEF(get_var_ref, 3, 0, 1, var_ref) +DEF(put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ +DEF(set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ +DEF(set_loc_uninitialized, 3, 0, 0, loc) +DEF(get_loc_check, 3, 0, 1, loc) +DEF(put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ +DEF(put_loc_check_init, 3, 1, 0, loc) +DEF(get_var_ref_check, 3, 0, 1, var_ref) +DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ +DEF(put_var_ref_check_init, 3, 1, 0, var_ref) +DEF(close_loc, 3, 0, 0, loc) +DEF(if_false, 5, 1, 0, label) +DEF(if_true, 5, 1, 0, label) /* must come after if_false */ +DEF(goto, 5, 0, 0, label) /* must come after if_true */ +DEF(catch, 5, 0, 1, label) +DEF(gosub, 5, 0, 0, label) /* used to execute the finally block */ +DEF(ret, 1, 1, 0, none) /* used to return from the finally block */ + +DEF(to_object, 1, 1, 1, none) +// DEF( to_string, 1, 1, 1, none) +DEF(to_propkey, 1, 1, 1, none) +DEF(to_propkey2, 1, 2, 2, none) + +DEF(with_get_var, 10, 1, 0, + atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_put_var, 10, 2, 1, + atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_delete_var, 10, 1, 0, + atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_make_ref, 10, 1, 0, + atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_get_ref, 10, 1, 0, + atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_get_ref_undef, 10, 1, 0, atom_label_u8) + +DEF(make_loc_ref, 7, 0, 2, atom_u16) +DEF(make_arg_ref, 7, 0, 2, atom_u16) +DEF(make_var_ref_ref, 7, 0, 2, atom_u16) +DEF(make_var_ref, 5, 0, 2, atom) + +DEF(for_in_start, 1, 1, 1, none) +DEF(for_of_start, 1, 1, 3, none) +DEF(for_await_of_start, 1, 1, 3, none) +DEF(for_in_next, 1, 1, 3, none) +DEF(for_of_next, 2, 3, 5, u8) +DEF(for_await_of_next, 1, 3, 4, none) +DEF(iterator_get_value_done, 1, 1, 2, none) +DEF(iterator_close, 1, 3, 0, none) +DEF(iterator_close_return, 1, 4, 4, none) +DEF(async_iterator_close, 1, 3, 2, none) +DEF(async_iterator_next, 1, 4, 4, none) +DEF(async_iterator_get, 2, 4, 5, u8) +DEF(initial_yield, 1, 0, 0, none) +DEF(yield, 1, 1, 2, none) +DEF(yield_star, 1, 2, 2, none) +DEF(async_yield_star, 1, 1, 2, none) +DEF(await, 1, 1, 1, none) + +/* arithmetic/logic operations */ +DEF(neg, 1, 1, 1, none) +DEF(plus, 1, 1, 1, none) +DEF(dec, 1, 1, 1, none) +DEF(inc, 1, 1, 1, none) +DEF(post_dec, 1, 1, 2, none) +DEF(post_inc, 1, 1, 2, none) +DEF(dec_loc, 2, 0, 0, loc8) +DEF(inc_loc, 2, 0, 0, loc8) +DEF(add_loc, 2, 1, 0, loc8) +DEF(not, 1, 1, 1, none) +DEF(lnot, 1, 1, 1, none) +DEF(typeof, 1, 1, 1, none) +DEF(delete, 1, 2, 1, none) +DEF(delete_var, 5, 0, 1, atom) + +DEF(mul, 1, 2, 1, none) +DEF(div, 1, 2, 1, none) +DEF(mod, 1, 2, 1, none) +DEF(add, 1, 2, 1, none) +DEF(sub, 1, 2, 1, none) +DEF(pow, 1, 2, 1, none) +DEF(shl, 1, 2, 1, none) +DEF(sar, 1, 2, 1, none) +DEF(shr, 1, 2, 1, none) +DEF(lt, 1, 2, 1, none) +DEF(lte, 1, 2, 1, none) +DEF(gt, 1, 2, 1, none) +DEF(gte, 1, 2, 1, none) +DEF(instanceof, 1, 2, 1, none) +DEF(in, 1, 2, 1, none) +DEF(eq, 1, 2, 1, none) +DEF(neq, 1, 2, 1, none) +DEF(strict_eq, 1, 2, 1, none) +DEF(strict_neq, 1, 2, 1, none) +DEF(and, 1, 2, 1, none) +DEF(xor, 1, 2, 1, none) +DEF(or, 1, 2, 1, none) +#ifdef CONFIG_BIGNUM +DEF(mul_pow10, 1, 2, 1, none) +DEF(math_div, 1, 2, 1, none) +DEF(math_mod, 1, 2, 1, none) +DEF(math_pow, 1, 2, 1, none) +#endif +/* must be the last non short and non temporary opcode */ +DEF(nop, 1, 0, 0, none) +/* temporary opcodes: never emitted in the final bytecode */ + +def(set_arg_valid_upto, 3, 0, 0, arg) /* emitted in phase 1, removed in phase 2 */ + +def(close_var_object, 1, 0, 0, none) /* emitted in phase 1, removed in phase 2 */ +def(enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ +def(leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ +def(label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ +def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ +def(scope_put_private_field, 7, 1, 1, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ + +// +def(line_num, 9, 0, 0, u64) /* emitted in phase 1, removed in phase 3 */ + // + +#if SHORT_OPCODES +DEF(push_minus1, 1, 0, 1, none_int) +DEF(push_0, 1, 0, 1, none_int) +DEF(push_1, 1, 0, 1, none_int) +DEF(push_2, 1, 0, 1, none_int) +DEF(push_3, 1, 0, 1, none_int) +DEF(push_4, 1, 0, 1, none_int) +DEF(push_5, 1, 0, 1, none_int) +DEF(push_6, 1, 0, 1, none_int) +DEF(push_7, 1, 0, 1, none_int) +DEF(push_i8, 2, 0, 1, i8) +DEF(push_i16, 3, 0, 1, i16) +DEF(push_const8, 2, 0, 1, const8) +DEF(fclosure8, 2, 0, 1, const8) /* must follow push_const8 */ +DEF(push_empty_string, 1, 0, 1, none) +DEF(get_loc8, 2, 0, 1, loc8) +DEF(put_loc8, 2, 1, 0,loc8) +DEF(set_loc8, 2, 1, 1, loc8) +DEF(get_loc0, 1, 0, 1, none_loc) +DEF(get_loc1, 1, 0, 1, none_loc) +DEF(get_loc2, 1, 0, 1, none_loc) +DEF(get_loc3, 1, 0, 1, none_loc) +DEF(put_loc0, 1, 1, 0, none_loc) +DEF(put_loc1, 1, 1, 0, none_loc) +DEF(put_loc2, 1, 1, 0, none_loc) +DEF(put_loc3, 1, 1, 0, none_loc) +DEF(set_loc0, 1, 1, 1, none_loc) +DEF(set_loc1, 1, 1, 1, none_loc) +DEF(set_loc2, 1, 1, 1, none_loc) +DEF(set_loc3, 1, 1, 1, none_loc) +DEF(get_arg0, 1, 0, 1, none_arg) +DEF(get_arg1, 1, 0, 1, none_arg) +DEF(get_arg2, 1, 0, 1, none_arg) +DEF(get_arg3, 1, 0, 1, none_arg) +DEF(put_arg0, 1, 1, 0, none_arg) +DEF(put_arg1, 1, 1, 0, none_arg) +DEF(put_arg2, 1, 1, 0, none_arg) +DEF(put_arg3, 1, 1, 0, none_arg) +DEF(set_arg0, 1, 1, 1, none_arg) +DEF(set_arg1, 1, 1, 1, none_arg) +DEF(set_arg2, 1, 1, 1, none_arg) +DEF(set_arg3, 1, 1, 1, none_arg) +DEF(get_var_ref0, 1, 0, 1, none_var_ref) +DEF(get_var_ref1, 1, 0, 1, none_var_ref) +DEF(get_var_ref2, 1, 0, 1, none_var_ref) +DEF(get_var_ref3, 1, 0, 1, none_var_ref) +DEF(put_var_ref0, 1, 1, 0, none_var_ref) +DEF(put_var_ref1, 1, 1, 0, none_var_ref) +DEF(put_var_ref2, 1, 1, 0, none_var_ref) +DEF(put_var_ref3, 1, 1, 0, none_var_ref) +DEF(set_var_ref0, 1, 1, 1, none_var_ref) +DEF(set_var_ref1, 1, 1, 1, none_var_ref) +DEF(set_var_ref2, 1, 1, 1, none_var_ref) +DEF(set_var_ref3, 1, 1, 1, none_var_ref) + +DEF(get_length, 1, 1, 1, none) + +DEF(if_false8, 2, 1, 0, label8) +DEF(if_true8, 2, 1, 0, label8) /* must come after if_false8 */ +DEF(goto8, 2, 0, 0, label8) /* must come after if_true8 */ +DEF(goto16, 3, 0, 0, label16) +DEF(call0, 1, 1, 1, npopx) +DEF(call1, 1, 1, 1, npopx) +DEF(call2, 1, 1, 1, npopx) +DEF(call3, 1, 1, 1, npopx) +DEF(is_undefined, 1, 1, 1, none) +DEF(is_null, 1, 1, 1, none) +DEF(is_function, 1, 1, 1, none) +#endif + +#undef DEF +#undef def +#endif /* DEF */ + +#ifdef COMPILE_TIME_OPCODE +COMPILE_TIME_OPCODE(caller_str, 9, 0, 0, none) +#endif diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-tag.h b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-tag.h new file mode 100644 index 000000000..960b17c38 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs-tag.h @@ -0,0 +1,64 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +// +// clang-format off +#ifdef DEFTAG + +#ifndef deftag +#define deftag(type, description) DEFTAG(type, description) +#endif +// LEPUSValue tag +DEFTAG(LEPUSObject, "LEPUSObject") +DEFTAG(LEPUSLepusRef, "LEPUSLepusRef") +DEFTAG(JSString, "JSString") +DEFTAG(JSShape, "JSShape") +DEFTAG(LEPUSFunctionBytecode, "LEPUSFunctionBytecode") +DEFTAG(JSTypedArray, "JSTypedArray") +DEFTAG(JSMapState, "JSMapState") +DEFTAG(JSMapIteratorData, "JSMapIteratorData") +DEFTAG(JSFunctionDef, "JSFunctionDef") +DEFTAG(JSArrayBuffer, "JSArrayBuffer") +DEFTAG(LEPUSScriptSource, "LEPUSScriptSource") +DEFTAG(LEPUSModuleDef, "LEPUSModuleDef") +DEFTAG(JSGeneratorData, "JSGeneratorData") +DEFTAG(JSAsyncFunctionData, "JSAsyncFunctionData") +DEFTAG(JSVarRef, "JSVarRef") +// LEPUSObject, class_id, finalizer +DEFTAG(JSBoundFunction, "JSBoundFunction") +DEFTAG(JSCFunctionDataRecord, "JSCFunctionDataRecord") +DEFTAG(JSForInIterator, "JSForInIterator") +DEFTAG(JSSeparableString, "JSSeparableString") +DEFTAG(JSArrayIteratorData, "JSArrayIteratorData") +DEFTAG(JSRegExpStringIteratorData, "JSRegExpStringIteratorData") +DEFTAG(JSProxyData, "JSProxyData") +DEFTAG(JSPromiseData, "JSPromiseData") +DEFTAG(JSPromiseReactionData, "JSPromiseReactionData") +DEFTAG(JSPromiseFunctionData, "JSPromiseFunctionData") +DEFTAG(JSAsyncFromSyncIteratorData, "JSAsyncFromSyncIteratorData") +DEFTAG(JSAsyncGeneratorData, "JSAsyncGeneratorData") +// other +DEFTAG(LEPUSPropertyEnum, "LEPUSPropertyEnum") +DEFTAG(JSMapRecord, "JSMapRecord") +DEFTAG(ValueSlot, "ValueSlot") +DEFTAG(LEPUSDebuggerInfo, "DebuggerInfo") + +DEFTAG(FinalizationRegistryData, "FinalizationRegistryData") +DEFTAG(WeakRefData, "WeakRefData") + +#ifdef CONFIG_BIGNUM +// big number +DEFTAG(JSBigFloat, "JSBigFloat") +#endif + +DEFTAG(JSOSRWHandler, "JSOSRWHandler") +DEFTAG(JSOSSignalHandler, "JSOSSignalHandler") +DEFTAG(JSOSTimer, "JSOSTimer") +DEFTAG(JSSTDFile, "JSSTDFile") + +deftag(JSSymbol, "JSSymbol") +deftag(JSValueArray, "JSValueArray") +deftag(JSConstString, "JSConstString") +deftag(JsonStrArray, "JsonStrArray") +#undef deftag +#endif diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/quickjs.h b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs.h new file mode 100644 index 000000000..01f961f77 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs.h @@ -0,0 +1,1611 @@ +/* + * QuickJS Javascript Engine + * + * Copyright (c) 2017-2019 Fabrice Bellard + * Copyright (c) 2017-2019 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_H_ + +#include +#include +#include + +#include "base_export.h" +#include "list.h" +// #define DUMP_LEAKS 0 +// for Debug +// +// if want to dump atoms/shapes/strings, should open blew marco +// #define DUMP_QJS_VALUE + +#ifdef DEBUG_MEMORY +#ifdef __ANDROID__ +#include +// #define DUMP_LEAKS 0 +// log +#define printf(...) __android_log_print(ANDROID_LOG_ERROR, "LYNX", __VA_ARGS__); +#endif +#else +#define printf(...) +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define lepus_likely(x) __builtin_expect(!!(x), 1) +#define lepus_unlikely(x) __builtin_expect(!!(x), 0) +#define lepus_force_inline inline __attribute__((always_inline)) +#define __js_printf_like(f, a) __attribute__((format(printf, f, a))) +#else +#define lepus_likely(x) (x) +#define lepus_unlikely(x) (x) +#define lepus_force_inline inline +#define __js_printf_like(a, b) +#endif + +#define LEPUS_BOOL int + +typedef struct LEPUSRuntime LEPUSRuntime; +typedef struct LEPUSContext LEPUSContext; +typedef struct LEPUSObject LEPUSObject; +typedef struct LEPUSClass LEPUSClass; +typedef uint32_t LEPUSClassID; +typedef uint32_t JSAtom; +typedef uint32_t LEPUSAtom; + +struct LEPUSFunctionBytecode; +struct LEPUSStackFrame; +typedef struct LEPUSDebuggerInfo LEPUSDebuggerInfo; +struct qjs_queue; +struct DebuggerSuspendedState; +struct LEPUSClosureVar; +typedef struct LEPUSBreakpoint LEPUSBreakpoint; +typedef struct LEPUSScriptSource LEPUSScriptSource; + +#if defined(__x86_64__) || defined(__aarch64__) +#define LEPUS_PTR64 +#define LEPUS_PTR64_DEF(a) a +#else +#define LEPUS_PTR64_DEF(a) +#endif + +#if !defined(LEPUS_PTR64) +#define LEPUS_NAN_BOXING +#endif + +enum { + ALLOC_TAG_WITHOUT_PTR = 1, +#define DEFTAG(name, str) ALLOC_TAG_##name, +#include "quickjs-tag.h" +#undef DEFTAG + ALLOC_TAG_END, +}; + +// +typedef enum LEPUSTypedArrayType { + LEPUS_TYPED_UNKNOW, + LEPUS_TYPED_UINT8C_ARRAY, /* u.array (typed_array) */ + LEPUS_TYPED_INT8_ARRAY, /* u.array (typed_array) */ + LEPUS_TYPED_UINT8_ARRAY, /* u.array (typed_array) */ + LEPUS_TYPED_INT16_ARRAY, /* u.array (typed_array) */ + LEPUS_TYPED_UINT16_ARRAY, /* u.array (typed_array) */ + LEPUS_TYPED_INT32_ARRAY, /* u.array (typed_array) */ + LEPUS_TYPED_UINT32_ARRAY, /* u.array (typed_array) */ + LEPUS_TYPED_FLOAT32_ARRAY, /* u.array (typed_array) */ + LEPUS_TYPED_FLOAT64_ARRAY /* u.array (typed_array) */ +} LEPUSTypedArrayType; +// + +typedef struct LEPUSRefCountHeader { + int ref_count; +} LEPUSRefCountHeader; + +#if defined(__aarch64__) && !defined(OS_WIN) && !defined(CONFIG_BIGNUM) && \ + !DISABLE_NANBOX + +static const int64_t LEPUS_FLOAT64_NAN_BITS = 0x7ff8000000000000; + +static inline double pure_nan() { return (*(double *)&LEPUS_FLOAT64_NAN_BITS); } +#define LEPUS_FLOAT64_NAN (pure_nan()) + +static const int DOUBLE_ENCODE_OFFSET_BIT = 49; +static const int64_t DOUBLE_ENCODE_OFFSET = (1ll << DOUBLE_ENCODE_OFFSET_BIT); +static const int64_t NUMBER_TAG = 0xfffe000000000000; +static const int64_t LEPUS_TAG_Atom = 0xfffc000000000000; +static const int64_t NOT_NUMBER_MASK = 0xffff000000000000; +static const int64_t OTHER_TAG = 0x2; + +static const int64_t BOOL_TAG = 0x4; +static const int64_t BOOL_TRUE_TAG = (OTHER_TAG | BOOL_TAG | 0x1); +static const int64_t BOOL_FALSE_TAG = (OTHER_TAG | BOOL_TAG); +static const int64_t UNDEFINED_TAG = (OTHER_TAG | 0x10); +static const int64_t UNINITIALIZED_TAG = (OTHER_TAG | 0x20); +static const int64_t CATCH_OFFSET_TAG = (OTHER_TAG | 0x40); +static const int64_t EXCEPTION_TAG = (OTHER_TAG | 0x80); + +#define VALUE_FALSE ((LEPUSValue){.as_int64 = BOOL_FALSE_TAG}) +#define VALUE_TRUE ((LEPUSValue){.as_int64 = BOOL_TRUE_TAG}) +#define VALUE_UNDEFINED ((LEPUSValue){.as_int64 = UNDEFINED_TAG}) +#define VALUE_NULL ((LEPUSValue){.as_int64 = OTHER_TAG}) +#define VALUE_EXCEPTION ((LEPUSValue){.as_int64 = EXCEPTION_TAG}) +#define VALUE_UNINITIALIZED ((LEPUSValue){.as_int64 = UNINITIALIZED_TAG}) + +static const int64_t OTHER_PTR_TAG = 0x0001000000000000ll; +static const int64_t SYMBOL_TAG = (0x1 | OTHER_PTR_TAG); +static const int64_t STRING_TAG = (0x2 | OTHER_PTR_TAG); +static const int64_t MODULE_TAG = (0x3 | OTHER_PTR_TAG); +static const int64_t FUNCTION_BYTECODE_TAG = (0x0 | OTHER_PTR_TAG); +static const int64_t OTHER_PTR_MASK = 0x0000fffffffffffc; +static const int64_t NOT_OTHER_PTR_MASK = 0xffff000000000003; +static const int64_t NOT_CELL_MASK = (OTHER_TAG | NUMBER_TAG); +static const int64_t NOT_CELL_OTHER_PTR_MASK = + (OTHER_TAG | NUMBER_TAG | OTHER_PTR_TAG); +static const int64_t LEPUS_PTR_TAG = 0xffff000000000000ll; +static const int64_t LEPUS_REF_TAG = (0x0 | LEPUS_PTR_TAG); +static const int64_t LEPUS_CPOINTER_TAG = (0x1 | LEPUS_PTR_TAG); +static const int64_t LEPUS_BIG_INT_TAG = (0x2 | LEPUS_PTR_TAG); +static const int64_t SEPARABLE_STRING_TAG = (0x3 | LEPUS_PTR_TAG); +static const int64_t LEPUS_PTR_MASK = 0x0000fffffffffffc; +static const int64_t NOT_LEPUS_PTR_MASK = 0xffff000000000003; +static const int64_t INTERNAL_GC_TAG = 0xfffd000000000000ll; +// #define NOT_OTHER_MASK (NUMBER_TAG | OTHER_PTR_TAG) + +// +static const int64_t LEPUS_TAG_LEPUS_REF = + LEPUS_REF_TAG; /* Primjs add for lepus */ +static const int64_t LEPUS_TAG_LEPUS_CPOINTER = LEPUS_CPOINTER_TAG; +static const int64_t LEPUS_TAG_BIG_INT = LEPUS_BIG_INT_TAG; +static const int64_t LEPUS_TAG_BIG_FLOAT = -9; +static const int64_t LEPUS_TAG_SYMBOL = SYMBOL_TAG; +static const int64_t LEPUS_TAG_STRING = STRING_TAG; +static const int64_t LEPUS_TAG_MODULE = MODULE_TAG; +static const int64_t LEPUS_TAG_FUNCTION_BYTECODE = FUNCTION_BYTECODE_TAG; +static const int64_t LEPUS_TAG_OBJECT = 0; +static const int64_t LEPUS_TAG_INT = NUMBER_TAG; +static const int64_t LEPUS_TAG_BOOL = BOOL_TAG | OTHER_TAG; +static const int64_t LEPUS_TAG_NULL = OTHER_TAG; +static const int64_t LEPUS_TAG_UNDEFINED = UNDEFINED_TAG | OTHER_TAG; +static const int64_t LEPUS_TAG_UNINITIALIZED = UNINITIALIZED_TAG; +static const int64_t LEPUS_TAG_CATCH_OFFSET = CATCH_OFFSET_TAG; +static const int64_t LEPUS_TAG_EXCEPTION = EXCEPTION_TAG; +static const int64_t LEPUS_TAG_SHAPE = + (0x0 | INTERNAL_GC_TAG); /* used internally during GC */ +static const int64_t LEPUS_TAG_ASYNC_FUNCTION = + (0x1 | INTERNAL_GC_TAG); /* used internally during GC */ +static const int64_t LEPUS_TAG_VAR_REF = + (0x2 | INTERNAL_GC_TAG); /* used internally during GC */ +static const int64_t LEPUS_TAG_FLOAT64 = 1; +static const int64_t LEPUS_TAG_SEPARABLE_STRING = SEPARABLE_STRING_TAG; + +typedef union LEPUSValue { + int64_t as_int64; + void *ptr; + double as_double; +} LEPUSValue; + +#define LEPUSValueConst LEPUSValue + +inline int64_t LEPUS_VALUE_GET_TAG(LEPUSValue v) { + if ((v.as_int64 & NUMBER_TAG) == 0 && (v.as_int64 & OTHER_PTR_TAG)) { + return v.as_int64 & NOT_OTHER_PTR_MASK; + } else if ((v.as_int64 & NOT_CELL_MASK) == 0) { + return LEPUS_TAG_OBJECT; + } else if ((v.as_int64 & LEPUS_PTR_TAG) == LEPUS_PTR_TAG) { + return v.as_int64 & (~LEPUS_PTR_MASK); + } else if ((v.as_int64 & NUMBER_TAG) == 0) { + // true false undefined null + return v.as_int64 & 0xfe; + } else if ((v.as_int64 & NOT_NUMBER_MASK) == NUMBER_TAG) { + return LEPUS_TAG_INT; + } else if ((v.as_int64 & NOT_NUMBER_MASK) == LEPUS_TAG_Atom) { + return LEPUS_TAG_Atom; + } else if ((v.as_int64 & INTERNAL_GC_TAG) == INTERNAL_GC_TAG) { + return v.as_int64 & (~LEPUS_PTR_MASK); // shape, async_function, var_ref + } else { + return LEPUS_TAG_FLOAT64; + } +} + +#define LEPUS_VALUE_IS_STRING(v) \ + (((v).as_int64 & NOT_OTHER_PTR_MASK) == LEPUS_TAG_STRING) +#define LEPUS_VALUE_IS_SEPARABLE_STRING(v) \ + (((v).as_int64 & NOT_LEPUS_PTR_MASK) == LEPUS_TAG_SEPARABLE_STRING) +#define LEPUS_VALUE_IS_ATOM(v) \ + (((v).as_int64 & NOT_NUMBER_MASK) == LEPUS_TAG_Atom) +#define LEPUS_VALUE_IS_OBJECT(v) \ + (!((v).as_int64 & NOT_CELL_OTHER_PTR_MASK) && (v).as_int64) +#define LEPUS_VALUE_IS_NOT_OBJECT(v) \ + ((v).as_int64 & NOT_CELL_OTHER_PTR_MASK || (v).as_int64 == 0) +#define LEPUS_VALUE_IS_NULL(v) ((v).as_int64 == VALUE_NULL.as_int64) +#define LEPUS_VALUE_IS_UNDEFINED(v) ((v).as_int64 == VALUE_UNDEFINED.as_int64) +#define LEPUS_VALUE_IS_SYMBOL(v) \ + (((v).as_int64 & NOT_OTHER_PTR_MASK) == LEPUS_TAG_SYMBOL) +#define LEPUS_VALUE_IS_INT(v) (((v).as_int64 & NOT_NUMBER_MASK) == NUMBER_TAG) +// Note: This is faster than (((v).as_int64 & 0xffff0000000000f7) == +// LEPUS_TAG_CATCH_OFFSET) +#define LEPUS_VALUE_IS_CATCH_OFFSET(v) \ + (!((v).as_int64 & 0xffff000000000000) && \ + ((v).as_int64 & 0xf7) == LEPUS_TAG_CATCH_OFFSET) +// || ((v).as_int64 & 0xf7)!=LEPUS_TAG_CATCH_OFFSET) +#define LEPUS_VALUE_IS_FLOAT64(v) \ + (((v).as_int64 & NUMBER_TAG) && ((v).as_int64 & NUMBER_TAG) != NUMBER_TAG) +#define LEPUS_VALUE_IS_EXCEPTION(v) ((v).as_int64 == VALUE_EXCEPTION.as_int64) +#define LEPUS_VALUE_IS_BOOL(v) \ + ((v).as_int64 == VALUE_TRUE.as_int64 || (v).as_int64 == VALUE_FALSE.as_int64) +#define LEPUS_VALUE_IS_UNINITIALIZED(v) \ + ((v).as_int64 == VALUE_UNINITIALIZED.as_int64) +#define LEPUS_VALUE_IS_FUNCTION_BYTECODE(v) \ + (((v).as_int64 & NOT_OTHER_PTR_MASK) == LEPUS_TAG_FUNCTION_BYTECODE) +#define LEPUS_VALUE_IS_MODULE(v) \ + (((v).as_int64 & NOT_OTHER_PTR_MASK) == LEPUS_TAG_MODULE) + +#define LEPUS_VALUE_IS_LEPUS_REF(v) \ + (((v).as_int64 & NOT_LEPUS_PTR_MASK) == LEPUS_TAG_LEPUS_REF) + +#define LEPUS_VALUE_IS_LEPUS_CPOINTER(v) \ + (((v).as_int64 & NOT_LEPUS_PTR_MASK) == LEPUS_TAG_LEPUS_CPOINTER) + +#define LEPUS_VALUE_IS_BIG_INT(v) \ + (((v).as_int64 & NOT_LEPUS_PTR_MASK) == LEPUS_TAG_BIG_INT) + +#define LEPUS_VALUE_GET_NORM_TAG(v) (LEPUS_VALUE_GET_TAG(v)) + +#define LEPUS_VALUE_GET_INT(v) ((int)((v).as_int64)) + +#define LEPUS_VALUE_GET_CATCH_OFFSET(v) ((int)(v.as_int64 >> 16)) + +#define LEPUS_VALUE_GET_BOOL(v) (v.as_int64 == VALUE_TRUE.as_int64) + +// +#define LEPUS_VALUE_GET_INT64(v) (v.as_int64) + +// #define LEPUS_MKVAL(tag, val) \ +// ((LEPUSValue) { (((uint64_t)(tag) << 32) | (uintptr_t)(val)) } +static inline LEPUSValue LEPUS_MKVAL(int64_t tag, int32_t val) { + if (tag != LEPUS_TAG_CATCH_OFFSET) { + return (LEPUSValue){.as_int64 = + (int64_t)((tag) | (uint64_t)(uint32_t)(val))}; + } else { + return (LEPUSValue){.as_int64 = + (int64_t)((tag) | (uint64_t)(uint32_t)(val) << 16)}; + } +} + +#define LEPUS_MKPTR(tag, p) \ + ((LEPUSValue){.as_int64 = (int64_t)(((int64_t)(p) & LEPUS_PTR_MASK) | (tag))}) +// + +static inline double LEPUS_VALUE_GET_FLOAT64(LEPUSValue v) { + int64_t val = (v.as_int64 - DOUBLE_ENCODE_OFFSET); + return *(double *)&val; +} + +#if defined(ANDROID) || defined(__ANDROID__) +extern int64_t HEAP_TAG_OUTER; +extern int64_t HEAP_TAG_INNER; + +#define LEPUS_VALUE_GET_CPOINTER(v) \ + ((void *)(((int64_t)v.ptr & OTHER_PTR_MASK) | HEAP_TAG_OUTER)) + +#define LEPUS_VALUE_GET_PTR(v) \ + ((void *)(((int64_t)(v).ptr & OTHER_PTR_MASK) | HEAP_TAG_INNER)) +#else +#define LEPUS_VALUE_GET_PTR(v) ((void *)(((int64_t)(v).ptr & OTHER_PTR_MASK))) +#define LEPUS_VALUE_GET_CPOINTER(v) LEPUS_VALUE_GET_PTR(v) +#endif + +static inline LEPUSValue __JS_NewFloat64(LEPUSContext *ctx, double d) { + LEPUSValue v; + if (isnan(d)) { + d = pure_nan(); + } + v.as_double = d; + v.as_int64 = v.as_int64 + DOUBLE_ENCODE_OFFSET; + return v; +} + +#define LEPUS_NAN \ + ((LEPUSValue){.as_int64 = LEPUS_FLOAT64_NAN_BITS + DOUBLE_ENCODE_OFFSET}) + +// #undef isnan +// #define isnan(d) ((*(int64_t*)&d) == LEPUS_FLOAT64_NAN_BITS) + +inline LEPUS_BOOL LEPUS_VALUE_IS_NAN(LEPUSValue v) { + return v.as_int64 == LEPUS_NAN.as_int64; +} + +#define LEPUS_TAG_IS_FLOAT64(tag) ((tag) == LEPUS_TAG_FLOAT64) + +#define LEPUS_VALUE_HAS_REF_COUNT(v) \ + (((v.as_int64 & NUMBER_TAG) == 0 && (v.as_int64 & OTHER_PTR_TAG)) || \ + ((v.as_int64 & NOT_CELL_MASK) == 0 && v.as_int64 != 0) || \ + (((v.as_int64 & LEPUS_PTR_TAG) == LEPUS_PTR_TAG) && \ + ((v.as_int64 & 0x03) != 1)) || \ + ((v.as_int64 & LEPUS_PTR_TAG) == INTERNAL_GC_TAG)) + +#define LEPUS_VALUE_IS_BOTH_INT(v1, v2) \ + (LEPUS_VALUE_IS_INT((v1)) && LEPUS_VALUE_IS_INT(v2)) +#define LEPUS_VALUE_IS_BOTH_FLOAT(v1, v2) \ + (LEPUS_VALUE_IS_FLOAT64(v1) && LEPUS_VALUE_IS_FLOAT64(v2)) + +#define LEPUS_VALUE_GET_OBJ(v) ((LEPUSObject *)LEPUS_VALUE_GET_PTR(v)) +#define LEPUS_VALUE_GET_STRING(v) ((JSString *)LEPUS_VALUE_GET_PTR(v)) +// #define LEPUS_VALUE_HAS_REF_COUNT(v) ((unsigned)LEPUS_VALUE_GET_TAG(v) >= +// (unsigned)LEPUS_TAG_FIRST) + +/* special values */ +#define LEPUS_NULL VALUE_NULL +#define LEPUS_UNDEFINED VALUE_UNDEFINED +#define LEPUS_FALSE VALUE_FALSE +#define LEPUS_TRUE VALUE_TRUE +#define LEPUS_EXCEPTION VALUE_EXCEPTION +#define LEPUS_UNINITIALIZED VALUE_UNINITIALIZED + +#else /* !defined(__aarch64__) || defined(OS_WIN) || DISABLE_NANBOX */ + +enum { + /* all tags with a reference count are negative */ + LEPUS_TAG_FIRST = -12, /* first negative tag */ + LEPUS_TAG_SEPARABLE_STRING = -12, /* separable string tag */ + LEPUS_TAG_LEPUS_REF = -11, /* Primjs add for lepus */ + LEPUS_TAG_BIG_INT = -10, + LEPUS_TAG_BIG_FLOAT = -9, + LEPUS_TAG_SYMBOL = -8, + LEPUS_TAG_STRING = -7, + /* shape, async_function, var_ref is not need for PrimJs */ + LEPUS_TAG_SHAPE = -6, /* used internally during GC */ + LEPUS_TAG_ASYNC_FUNCTION = -5, /* used internally during GC */ + LEPUS_TAG_VAR_REF = -4, /* used internally during GC */ + LEPUS_TAG_MODULE = -3, /* used internally */ + LEPUS_TAG_FUNCTION_BYTECODE = -2, /* used internally */ + LEPUS_TAG_OBJECT = -1, + + LEPUS_TAG_INT = 0, + LEPUS_TAG_BOOL = 1, + LEPUS_TAG_NULL = 2, + LEPUS_TAG_UNDEFINED = 3, + LEPUS_TAG_UNINITIALIZED = 4, + LEPUS_TAG_CATCH_OFFSET = 5, + LEPUS_TAG_EXCEPTION = 6, + + /* below is lepus type */ + LEPUS_TAG_LEPUS_CPOINTER = 7, // lepus CFunction + LEPUS_TAG_FLOAT64 = 12, + LEPUS_TAG_Atom = 13, + /* any larger tag is FLOAT64 if LEPUS_NAN_BOXING */ +}; + +#define LEPUS_FLOAT64_NAN NAN + +#ifdef CONFIG_CHECK_JSVALUE +/* LEPUSValue consistency : it is not possible to run the code in this + mode, but it is useful to detect simple reference counting + errors. It would be interesting to modify a static C analyzer to + handle specific annotations (clang has such annotations but only + for objective C) */ +typedef struct __JSValue *LEPUSValue; +typedef const struct __JSValue *LEPUSValueConst; + +#define LEPUS_VALUE_GET_TAG(v) (int)((uintptr_t)(v) & 0xf) +/* same as LEPUS_VALUE_GET_TAG, but return LEPUS_TAG_FLOAT64 with NaN boxing */ +#define LEPUS_VALUE_GET_NORM_TAG(v) LEPUS_VALUE_GET_TAG(v) +#define LEPUS_VALUE_GET_INT(v) (int)((intptr_t)(v) >> 4) +#define LEPUS_VALUE_GET_BOOL(v) LEPUS_VALUE_GET_INT(v) +#define LEPUS_VALUE_GET_FLOAT64(v) (double)LEPUS_VALUE_GET_INT(v) +#define LEPUS_VALUE_GET_PTR(v) (void *)((intptr_t)(v) & ~0xf) +#define LEPUS_VALUE_GET_CPOINTER(v) LEPUS_VALUE_GET_PTR(v) + +#define LEPUS_MKVAL(tag, val) (LEPUSValue)(intptr_t)(((val) << 4) | (tag)) +#define LEPUS_MKPTR(tag, p) (LEPUSValue)((intptr_t)(p) | (tag)) + +#define LEPUS_TAG_IS_FLOAT64(tag) ((unsigned)(tag) == LEPUS_TAG_FLOAT64) + +#define LEPUS_NAN LEPUS_MKVAL(LEPUS_TAG_FLOAT64, 1) + +static inline LEPUSValue __JS_NewFloat64(LEPUSContext *ctx, double d) { + return LEPUS_MKVAL(LEPUS_TAG_FLOAT64, (int)d); +} + +#elif defined(LEPUS_NAN_BOXING) +typedef uint64_t LEPUSValue; + +#define LEPUSValueConst LEPUSValue + +#define LEPUS_VALUE_GET_TAG(v) ((int)((v) >> 32)) +#define LEPUS_VALUE_GET_INT(v) ((int)(v)) +#define LEPUS_VALUE_GET_BOOL(v) ((int)(v)) +#define LEPUS_VALUE_GET_PTR(v) ((void *)(intptr_t)(v)) +#define LEPUS_VALUE_GET_CPOINTER(v) LEPUS_VALUE_GET_PTR(v) + +#define LEPUS_MKVAL(tag, val) (((uint64_t)(tag) << 32) | (uint32_t)(val)) +#define LEPUS_MKPTR(tag, ptr) (((uint64_t)(tag) << 32) | (uintptr_t)(ptr)) + +// +#define LEPUS_VALUE_GET_INT64(v) ((uint64_t)v) +// + +#define LEPUS_FLOAT64_TAG_ADDEND \ + (0x7ff80000 - LEPUS_TAG_FIRST + 1) /* quiet NaN encoding */ + +static inline double LEPUS_VALUE_GET_FLOAT64(LEPUSValue v) { + union { + LEPUSValue v; + double d; + } u; + u.v = v; + u.v += (uint64_t)LEPUS_FLOAT64_TAG_ADDEND << 32; + return u.d; +} + +#define LEPUS_NAN \ + (0x7ff8000000000000 - ((uint64_t)LEPUS_FLOAT64_TAG_ADDEND << 32)) + +static inline LEPUSValue __JS_NewFloat64(LEPUSContext *ctx, double d) { + union { + double d; + uint64_t u64; + } u; + LEPUSValue v; + u.d = d; + /* normalize NaN */ + if (lepus_unlikely((u.u64 & 0x7fffffffffffffff) > 0x7ff0000000000000)) + v = LEPUS_NAN; + else + v = u.u64 - ((uint64_t)LEPUS_FLOAT64_TAG_ADDEND << 32); + return v; +} + +#define LEPUS_TAG_IS_FLOAT64(tag) \ + ((unsigned)((tag)-LEPUS_TAG_FIRST) >= (LEPUS_TAG_FLOAT64 - LEPUS_TAG_FIRST)) + +/* same as LEPUS_VALUE_GET_TAG, but return LEPUS_TAG_FLOAT64 with NaN boxing */ +static inline int LEPUS_VALUE_GET_NORM_TAG(LEPUSValue v) { + uint32_t tag; + tag = LEPUS_VALUE_GET_TAG(v); + if (LEPUS_TAG_IS_FLOAT64(tag)) + return LEPUS_TAG_FLOAT64; + else + return tag; +} + +#else /* !LEPUS_NAN_BOXING */ + +typedef union LEPUSValueUnion { + int32_t int32; + double float64; + int64_t int64; + void *ptr; +} LEPUSValueUnion; + +typedef struct LEPUSValue { + LEPUSValueUnion u; + int64_t tag; +} LEPUSValue; + +#define LEPUSValueConst LEPUSValue + +#define LEPUS_VALUE_GET_TAG(v) ((int32_t)(v).tag) +/* same as LEPUS_VALUE_GET_TAG, but return LEPUS_TAG_FLOAT64 with NaN boxing */ +#define LEPUS_VALUE_GET_NORM_TAG(v) LEPUS_VALUE_GET_TAG(v) +#define LEPUS_VALUE_GET_INT(v) ((v).u.int32) +#define LEPUS_VALUE_GET_BOOL(v) ((v).u.int32) +#define LEPUS_VALUE_GET_FLOAT64(v) ((v).u.float64) +#define LEPUS_VALUE_GET_PTR(v) ((v).u.ptr) +#define LEPUS_VALUE_GET_CPOINTER(v) LEPUS_VALUE_GET_PTR(v) + +// +#define LEPUS_VALUE_GET_INT64(v) ((v).u.int64) +// + +#define LEPUS_MKVAL(tag, val) \ + (LEPUSValue) { .u.int32 = val, tag } +#define LEPUS_MKPTR(tag, p) \ + (LEPUSValue) { .u.ptr = p, tag } + +#define LEPUS_TAG_IS_FLOAT64(tag) ((unsigned)(tag) == LEPUS_TAG_FLOAT64) + +#define LEPUS_NAN \ + (LEPUSValue) { .u.float64 = LEPUS_FLOAT64_NAN, LEPUS_TAG_FLOAT64 } + +static inline LEPUSValue __JS_NewFloat64(LEPUSContext *ctx, double d) { + LEPUSValue v; + v.tag = LEPUS_TAG_FLOAT64; + v.u.float64 = d; + return v; +} + +#endif /* !LEPUS_NAN_BOXING */ + +#define LEPUS_VALUE_IS_BOTH_INT(v1, v2) \ + ((LEPUS_VALUE_GET_TAG(v1) | LEPUS_VALUE_GET_TAG(v2)) == 0) +#define LEPUS_VALUE_IS_BOTH_FLOAT(v1, v2) \ + (LEPUS_TAG_IS_FLOAT64(LEPUS_VALUE_GET_TAG(v1)) && \ + LEPUS_TAG_IS_FLOAT64(LEPUS_VALUE_GET_TAG(v2))) + +// add macro to avoid accessing LEPUS_TAG directly +#define LEPUS_VALUE_IS_STRING(v) (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_STRING) +#define LEPUS_VALUE_IS_SEPARABLE_STRING(v) \ + (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_SEPARABLE_STRING) +#define LEPUS_VALUE_IS_OBJECT(v) (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_OBJECT) +#define LEPUS_VALUE_IS_NOT_OBJECT(v) \ + (LEPUS_VALUE_GET_TAG(v) != LEPUS_TAG_OBJECT) +#define LEPUS_VALUE_IS_NULL(v) (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_NULL) +#define LEPUS_VALUE_IS_UNDEFINED(v) \ + (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_UNDEFINED) +#define LEPUS_VALUE_IS_SYMBOL(v) (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_SYMBOL) +#define LEPUS_VALUE_IS_INT(v) (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_INT) +#define LEPUS_VALUE_IS_CATCH_OFFSET(v) \ + (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_CATCH_OFFSET) +#define LEPUS_VALUE_IS_FLOAT64(v) (LEPUS_TAG_IS_FLOAT64(LEPUS_VALUE_GET_TAG(v))) +#define LEPUS_VALUE_IS_EXCEPTION(v) \ + (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_EXCEPTION) +#define LEPUS_VALUE_IS_BOOL(v) (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_BOOL) +#define LEPUS_VALUE_IS_UNINITIALIZED(v) \ + (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_UNINITIALIZED) +#define LEPUS_VALUE_IS_FUNCTION_BYTECODE(v) \ + (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_FUNCTION_BYTECODE) +#define LEPUS_VALUE_IS_MODULE(v) (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_MODULE) +#define LEPUS_VALUE_IS_LEPUS_REF(v) \ + (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_LEPUS_REF) +#define LEPUS_VALUE_IS_LEPUS_CPOINTER(v) \ + (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_LEPUS_CPOINTER) +#define LEPUS_VALUE_IS_BIG_INT(v) (LEPUS_VALUE_GET_TAG(v) == LEPUS_TAG_BIG_INT) + +#define LEPUS_VALUE_GET_OBJ(v) ((LEPUSObject *)LEPUS_VALUE_GET_PTR(v)) +#define LEPUS_VALUE_GET_STRING(v) ((JSString *)LEPUS_VALUE_GET_PTR(v)) +#define LEPUS_VALUE_GET_CATCH_OFFSET(v) LEPUS_VALUE_GET_INT((v)) +#define LEPUS_VALUE_HAS_REF_COUNT(v) \ + ((unsigned)LEPUS_VALUE_GET_TAG(v) >= (unsigned)LEPUS_TAG_FIRST) + +/* special values */ +#define LEPUS_NULL LEPUS_MKVAL(LEPUS_TAG_NULL, 0) +#define LEPUS_UNDEFINED LEPUS_MKVAL(LEPUS_TAG_UNDEFINED, 0) +#define LEPUS_FALSE LEPUS_MKVAL(LEPUS_TAG_BOOL, 0) +#define LEPUS_TRUE LEPUS_MKVAL(LEPUS_TAG_BOOL, 1) +#define LEPUS_EXCEPTION LEPUS_MKVAL(LEPUS_TAG_EXCEPTION, 0) +#define LEPUS_UNINITIALIZED LEPUS_MKVAL(LEPUS_TAG_UNINITIALIZED, 0) + +#endif /* !defined(__aarch64__) || defined(OS_WIN) || DISABLE_NANBOX */ + +/* flags for object properties */ +#define LEPUS_PROP_CONFIGURABLE (1 << 0) +#define LEPUS_PROP_WRITABLE (1 << 1) +#define LEPUS_PROP_ENUMERABLE (1 << 2) +#define LEPUS_PROP_C_W_E \ + (LEPUS_PROP_CONFIGURABLE | LEPUS_PROP_WRITABLE | LEPUS_PROP_ENUMERABLE) +#define LEPUS_PROP_LENGTH (1 << 3) /* used internally in Arrays */ +#define LEPUS_PROP_TMASK \ + (3 << 4) /* mask for NORMAL, GETSET, VARREF, AUTOINIT */ +#define LEPUS_PROP_NORMAL (0 << 4) +#define LEPUS_PROP_GETSET (1 << 4) +#define LEPUS_PROP_VARREF (2 << 4) /* used internally */ +#define LEPUS_PROP_AUTOINIT (3 << 4) /* used internally */ + +/* flags for LEPUS_DefineProperty */ +#define LEPUS_PROP_HAS_SHIFT 8 +#define LEPUS_PROP_HAS_CONFIGURABLE (1 << 8) +#define LEPUS_PROP_HAS_WRITABLE (1 << 9) +#define LEPUS_PROP_HAS_ENUMERABLE (1 << 10) +#define LEPUS_PROP_HAS_GET (1 << 11) +#define LEPUS_PROP_HAS_SET (1 << 12) +#define LEPUS_PROP_HAS_VALUE (1 << 13) + +/* throw an exception if false would be returned + (LEPUS_DefineProperty/LEPUS_SetProperty) */ +#define LEPUS_PROP_THROW (1 << 14) +/* throw an exception if false would be returned in strict mode + (LEPUS_SetProperty) */ +#define LEPUS_PROP_THROW_STRICT (1 << 15) + +#define LEPUS_PROP_NO_ADD (1 << 16) /* internal use */ +#define LEPUS_PROP_NO_EXOTIC (1 << 17) /* internal use */ + +#define LEPUS_DEFAULT_STACK_SIZE (256 * 1024) + +/* LEPUS_Eval() flags */ +#define LEPUS_EVAL_TYPE_GLOBAL (0 << 0) /* global code (default) */ +#define LEPUS_EVAL_TYPE_MODULE (1 << 0) /* module code */ +#define LEPUS_EVAL_TYPE_DIRECT (2 << 0) /* direct call (internal use) */ +#define LEPUS_EVAL_TYPE_INDIRECT (3 << 0) /* indirect call (internal use) */ +#define LEPUS_EVAL_TYPE_MASK (3 << 0) + +#define LEPUS_EVAL_FLAG_STRICT (1 << 3) /* force 'strict' mode */ +#define LEPUS_EVAL_FLAG_STRIP (1 << 4) /* force 'strip' mode */ +#define LEPUS_EVAL_FLAG_COMPILE_ONLY (1 << 5) /* internal use */ +/* use for runtime.compileScript */ +#define LEPUS_DEBUGGER_NO_PERSIST_SCRIPT (1 << 6) + +typedef LEPUSValue LEPUSCFunction(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); +typedef LEPUSValue LEPUSCFunctionMagic(LEPUSContext *ctx, + LEPUSValueConst this_val, int argc, + LEPUSValueConst *argv, int magic); +typedef LEPUSValue LEPUSCFunctionData(LEPUSContext *ctx, + LEPUSValueConst this_val, int argc, + LEPUSValueConst *argv, int magic, + LEPUSValue *func_data); + +struct LEPUSMallocFunctions; +// +typedef struct LEPUSDebuggerCallbacks { + // callbacks for lepusNG debugger + void (*run_message_loop_on_pause)(LEPUSContext *ctx); + void (*quit_message_loop_on_pause)(LEPUSContext *ctx); + void (*get_messages)(LEPUSContext *ctx); + void (*send_response)(LEPUSContext *ctx, int32_t message_id, + const char *message); + void (*send_notification)(LEPUSContext *ctx, const char *message); + void (*free_messages)(LEPUSContext *ctx, char **messages, int32_t size); + + void (*inspector_check)(LEPUSContext *ctx); + void (*debugger_exception)(LEPUSContext *ctx); + uint8_t (*is_devtool_on)(LEPUSContext *ctx); +} LEPUSDebuggerCallbacks; + +typedef struct LEPUSLepusRefCallbacks { + LEPUSValue (*free_value)(LEPUSRuntime *rt, LEPUSValue val); + LEPUSValue (*get_property)(LEPUSContext *ctx, LEPUSValue thisObj, JSAtom prop, + int idx); + size_t (*get_length)(LEPUSContext *ctx, LEPUSValue val); + LEPUSValue (*convert_to_object)(LEPUSContext *ctx, LEPUSValue val); + LEPUSValue (*set_property)(LEPUSContext *ctx, LEPUSValue thisObj, + LEPUSValue prop, int idx, LEPUSValue val); + void (*free_str_cache)(void *old_ptr, void *new_ptr); + size_t (*lepus_ref_equal)(LEPUSValue val1, LEPUSValue val2); + LEPUSValue (*lepus_ref_tostring)(LEPUSContext *ctx, LEPUSValue val); + // void (*free_string_cache)(); +} LEPUSLepusRefCallbacks; + +typedef struct LEPUSLepusRef { + LEPUSRefCountHeader header; + int tag; // lepus value tag + void *p; // lepus value reference + LEPUSValue lepus_val; // convert to lepusvalue cache, default is undefined +} LEPUSLepusRef; + +void RegisterLepusType(LEPUSRuntime *rt, int32_t array_typeid, + int32_t table_typeid); + +void RegisterGCInfoCallback(LEPUSRuntime *rt, void (*func)(const char *, int)); + +void RegisterLepusRefCallbacks(LEPUSRuntime *rt, LEPUSLepusRefCallbacks *funcs); + +void RegisterPrimJSCallbacks(LEPUSRuntime *rt, void **funcs, + int32_t callback_size); +void RegisterQJSDebuggerCallbacks(LEPUSRuntime *rt, void **funcs, + int32_t callback_size); +void PrepareQJSDebuggerDefer(LEPUSContext *ctx, void **funcs, + int32_t callback_size); +// for shared context +void PrepareQJSDebuggerForSharedContext(LEPUSContext *ctx, void **funcs, + int32_t callback_size, + bool devtool_connect); + +// + +LEPUSRuntime *LEPUS_NewRuntime(void); +LEPUSRuntime *LEPUS_NewRuntimeWithMode(uint32_t mode); +/* info lifetime must exceed that of rt */ +void LEPUS_SetRuntimeInfo(LEPUSRuntime *rt, const char *info); +void LEPUS_SetMemoryLimit(LEPUSRuntime *rt, size_t limit); +void LEPUS_SetGCThreshold(LEPUSRuntime *rt, size_t gc_threshold); +LEPUSRuntime *LEPUS_NewRuntime2(const struct LEPUSMallocFunctions *mf, + void *opaque, uint32_t mode); +void LEPUS_FreeRuntime(LEPUSRuntime *rt); +typedef void LEPUS_MarkFunc(LEPUSRuntime *rt, LEPUSValueConst val, + int local_idx); +void LEPUS_MarkValue(LEPUSRuntime *rt, LEPUSValueConst val, + LEPUS_MarkFunc *mark_func, int local_idx); +void LEPUS_RunGC(LEPUSRuntime *rt); +LEPUS_BOOL LEPUS_IsInGCSweep(LEPUSRuntime *rt); + +LEPUSContext *LEPUS_NewContext(LEPUSRuntime *rt); +void LEPUS_FreeContext(LEPUSContext *s); +void *LEPUS_GetContextOpaque(LEPUSContext *ctx); +void LEPUS_SetContextOpaque(LEPUSContext *ctx, void *opaque); +LEPUSRuntime *LEPUS_GetRuntime(LEPUSContext *ctx); +void LEPUS_SetMaxStackSize(LEPUSContext *ctx, size_t stack_size); +void LEPUS_SetClassProto(LEPUSContext *ctx, LEPUSClassID class_id, + LEPUSValue obj); +LEPUSValue LEPUS_GetClassProto(LEPUSContext *ctx, LEPUSClassID class_id); +int LEPUS_MoveUnhandledRejectionToException(LEPUSContext *ctx); +size_t LEPUS_GetHeapSize(LEPUSRuntime *rt); +/* the following functions are used to select the intrinsic object to + save memory */ +LEPUSContext *LEPUS_NewContextRaw(LEPUSRuntime *rt); +QJS_HIDE void LEPUS_AddIntrinsicBaseObjects(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicDate(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicEval(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicStringNormalize(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicRegExpCompiler(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicRegExp(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicJSON(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicProxy(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicMapSet(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicTypedArrays(LEPUSContext *ctx); +QJS_HIDE void LEPUS_AddIntrinsicPromise(LEPUSContext *ctx); +#ifdef QJS_UNITTEST +QJS_HIDE LEPUSValue lepus_string_codePointRange(LEPUSContext *ctx, + LEPUSValueConst this_val, + int argc, + LEPUSValueConst *argv); + +QJS_HIDE LEPUSValue lepus_gc(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv); +#endif +QJS_HIDE void *lepus_malloc_rt(LEPUSRuntime *rt, size_t size, int alloc_tag); +void lepus_free_rt(LEPUSRuntime *rt, void *ptr); +QJS_HIDE void *lepus_realloc_rt(LEPUSRuntime *rt, void *ptr, size_t size, + int alloc_tag); +QJS_HIDE size_t lepus_malloc_usable_size_rt(LEPUSRuntime *rt, const void *ptr); +QJS_HIDE void *lepus_mallocz_rt(LEPUSRuntime *rt, size_t size, int alloc_tag); + +void *lepus_malloc(LEPUSContext *ctx, size_t size, int alloc_tag); +void lepus_free(LEPUSContext *ctx, void *ptr); +void *lepus_mallocz(LEPUSContext *ctx, size_t size, int alloc_tag); +QJS_HIDE void *lepus_realloc(LEPUSContext *ctx, void *ptr, size_t size, + int alloc_tag); +QJS_HIDE size_t lepus_malloc_usable_size(LEPUSContext *ctx, const void *ptr); +QJS_HIDE void *lepus_realloc2(LEPUSContext *ctx, void *ptr, size_t size, + size_t *pslack, int alloc_tag); +QJS_HIDE char *lepus_strdup(LEPUSContext *ctx, const char *str, int alloc_tag); +QJS_HIDE char *lepus_strndup(LEPUSContext *ctx, const char *s, size_t n, + int alloc_tag); + +#if LYNX_SIMPLIFY +typedef struct LEPUSMemoryUsage { + int64_t malloc_size, malloc_limit, memory_used_size; + int64_t malloc_count; + int64_t memory_used_count; + int64_t atom_count, atom_size; + int64_t str_count, str_size; + int64_t obj_count, obj_size; + int64_t prop_count, prop_size; + int64_t shape_count, shape_size; + int64_t lepus_func_count, lepus_func_size, lepus_func_code_size; + int64_t lepus_func_pc2line_count, lepus_func_pc2line_size; + int64_t c_func_count, array_count; + int64_t fast_array_count, fast_array_elements; + int64_t binary_object_count, binary_object_size; +} LEPUSMemoryUsage; + +void LEPUS_ComputeMemoryUsage(LEPUSRuntime *rt, LEPUSMemoryUsage *s); +void LEPUS_DumpMemoryUsage(FILE *fp, const LEPUSMemoryUsage *s, + LEPUSRuntime *rt); + +#endif + +/* atom support */ +JSAtom LEPUS_NewAtomLen(LEPUSContext *ctx, const char *str, size_t len); +JSAtom LEPUS_NewAtom(LEPUSContext *ctx, const char *str); +JSAtom LEPUS_NewAtomUInt32(LEPUSContext *ctx, uint32_t n); +JSAtom LEPUS_DupAtom(LEPUSContext *ctx, JSAtom v); +void LEPUS_FreeAtom(LEPUSContext *ctx, JSAtom v); +void LEPUS_FreeAtomRT(LEPUSRuntime *rt, JSAtom v); +LEPUSValue LEPUS_AtomToValue(LEPUSContext *ctx, JSAtom atom); +LEPUSValue LEPUS_AtomToString(LEPUSContext *ctx, JSAtom atom); +const char *LEPUS_AtomToCString(LEPUSContext *ctx, JSAtom atom); + +/* object class support */ + +typedef struct LEPUSPropertyEnum { + LEPUS_BOOL is_enumerable; + JSAtom atom; +} LEPUSPropertyEnum; + +typedef struct LEPUSPropertyDescriptor { + int flags; + LEPUSValue value; + LEPUSValue getter; + LEPUSValue setter; +} LEPUSPropertyDescriptor; + +typedef struct LEPUSClassExoticMethods { + /* Return -1 if exception (can only happen in case of Proxy object), + FALSE if the property does not exists, TRUE if it exists. If 1 is + returned, the property descriptor 'desc' is filled if != NULL. */ + int (*get_own_property)(LEPUSContext *ctx, LEPUSPropertyDescriptor *desc, + LEPUSValueConst obj, JSAtom prop); + /* '*ptab' should hold the '*plen' property keys. Return 0 if OK, + -1 if exception. The 'is_enumerable' field is ignored. + */ + int (*get_own_property_names)(LEPUSContext *ctx, LEPUSPropertyEnum **ptab, + uint32_t *plen, LEPUSValueConst obj); + /* return < 0 if exception, or TRUE/FALSE */ + int (*delete_property)(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom prop); + /* return < 0 if exception or TRUE/FALSE */ + int (*define_own_property)(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValueConst val, + LEPUSValueConst getter, LEPUSValueConst setter, + int flags); + /* The following methods can be emulated with the previous ones, + so they are usually not needed */ + /* return < 0 if exception or TRUE/FALSE */ + int (*has_property)(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom atom); + LEPUSValue (*get_property)(LEPUSContext *ctx, LEPUSValueConst obj, + JSAtom atom, LEPUSValueConst receiver); + /* return < 0 if exception or TRUE/FALSE */ + int (*set_property)(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom atom, + LEPUSValueConst value, LEPUSValueConst receiver, + int flags); +} LEPUSClassExoticMethods; + +typedef void LEPUSClassFinalizer(LEPUSRuntime *rt, LEPUSValue val); +typedef void LEPUSClassGCMark(LEPUSRuntime *rt, LEPUSValueConst val, + LEPUS_MarkFunc *mark_func, int local_idx); +#define LEPUS_CALL_FLAG_CONSTRUCTOR (1 << 0) +typedef LEPUSValue LEPUSClassCall(LEPUSContext *ctx, LEPUSValueConst func_obj, + LEPUSValueConst this_val, int argc, + LEPUSValueConst *argv, int flags); +typedef struct LEPUSClassDef { + const char *class_name; + LEPUSClassFinalizer *finalizer; + LEPUSClassGCMark *gc_mark; + /* if call != NULL, the object is a function. If (flags & + JS_CALL_FLAG_CONSTRUCTOR) != 0, the function is called as a + constructor. In this case, 'this_val' is new.target. A + constructor call only happens if the object constructor bit is + set (see JS_SetConstructorBit()). */ + LEPUSClassCall *call; + /* XXX: suppress this indirection ? It is here only to save memory + because only a few classes need these methods */ + LEPUSClassExoticMethods *exotic; +} LEPUSClassDef; + +LEPUSClassID LEPUS_NewClassID(LEPUSClassID *pclass_id); +int LEPUS_NewClass(LEPUSRuntime *rt, LEPUSClassID class_id, + const LEPUSClassDef *class_def); +int LEPUS_IsRegisteredClass(LEPUSRuntime *rt, LEPUSClassID class_id); + +/* value handling */ + +static lepus_force_inline LEPUSValue LEPUS_NewBool(LEPUSContext *ctx, + LEPUS_BOOL val) { +#if defined(__aarch64__) && !defined(OS_WIN) && !defined(CONFIG_BIGNUM) && \ + !DISABLE_NANBOX + return LEPUS_MKVAL(LEPUS_TAG_BOOL, val != 0); +#else + return LEPUS_MKVAL(LEPUS_TAG_BOOL, val); +#endif +} + +static lepus_force_inline LEPUSValue LEPUS_NewInt32(LEPUSContext *ctx, + int32_t val) { + return LEPUS_MKVAL(LEPUS_TAG_INT, val); +} + +static lepus_force_inline LEPUSValue LEPUS_NewCatchOffset(LEPUSContext *ctx, + int32_t val) { + return LEPUS_MKVAL(LEPUS_TAG_CATCH_OFFSET, val); +} + +LEPUSValue LEPUS_NewInt64(LEPUSContext *ctx, int64_t v); +LEPUSValue LEPUS_NewBigInt64(LEPUSContext *ctx, int64_t v); +LEPUSValue LEPUS_NewBigUint64(LEPUSContext *ctx, uint64_t v); + +static lepus_force_inline LEPUSValue LEPUS_NewFloat64(LEPUSContext *ctx, + double d) { + LEPUSValue v; + int32_t val; + union { + double d; + uint64_t u; + } u, t; + u.d = d; + val = (int32_t)d; + t.d = val; + /* -0 cannot be represented as integer, so we compare the bit + representation */ + if (u.u == t.u) { + v = LEPUS_MKVAL(LEPUS_TAG_INT, val); + } else { + v = __JS_NewFloat64(ctx, d); + } + return v; +} + +LEPUS_BOOL LEPUS_IsNumber(LEPUSValueConst v); + +static inline LEPUS_BOOL LEPUS_IsInteger(LEPUSValueConst v) { + return LEPUS_VALUE_IS_INT(v) || LEPUS_VALUE_IS_BIG_INT(v); +} + +#ifdef CONFIG_BIGNUM +static inline LEPUS_BOOL LEPUS_IsBigFloat(LEPUSValueConst v) { + int tag = LEPUS_VALUE_GET_TAG(v); + return tag == LEPUS_TAG_BIG_FLOAT; +} +#endif + +static inline LEPUS_BOOL LEPUS_IsBool(LEPUSValueConst v) { + return LEPUS_VALUE_IS_BOOL(v); +} + +static inline LEPUS_BOOL LEPUS_IsNull(LEPUSValueConst v) { + return LEPUS_VALUE_IS_NULL(v); +} + +static inline LEPUS_BOOL LEPUS_IsUndefined(LEPUSValueConst v) { + return LEPUS_VALUE_IS_UNDEFINED(v); +} + +static inline LEPUS_BOOL LEPUS_IsException(LEPUSValueConst v) { + return (LEPUS_BOOL)(lepus_unlikely(LEPUS_VALUE_IS_EXCEPTION(v))); +} + +static inline LEPUS_BOOL LEPUS_IsUninitialized(LEPUSValueConst v) { + return (LEPUS_BOOL)(lepus_unlikely(LEPUS_VALUE_IS_UNINITIALIZED(v))); +} + +static inline LEPUS_BOOL LEPUS_IsString(LEPUSValueConst v) { + return LEPUS_VALUE_IS_STRING(v) || LEPUS_VALUE_IS_SEPARABLE_STRING(v); +} + +static inline LEPUS_BOOL LEPUS_IsSymbol(LEPUSValueConst v) { + return LEPUS_VALUE_IS_SYMBOL(v); +} + +static inline LEPUS_BOOL LEPUS_IsObject(LEPUSValueConst v) { + return LEPUS_VALUE_IS_OBJECT(v); +} + +LEPUSValue LEPUS_Throw(LEPUSContext *ctx, LEPUSValue obj); +LEPUSValue LEPUS_GetException(LEPUSContext *ctx); +LEPUS_BOOL LEPUS_IsError(LEPUSContext *ctx, LEPUSValueConst val); +#ifdef QJS_UNITTEST +void LEPUS_EnableIsErrorProperty(LEPUSContext *ctx, LEPUS_BOOL enable); +#endif +void LEPUS_ResetUncatchableError(LEPUSContext *ctx); +LEPUSValue LEPUS_NewError(LEPUSContext *ctx); +LEPUSValue __js_printf_like(2, 3) + LEPUS_ThrowSyntaxError(LEPUSContext *ctx, const char *fmt, ...); +LEPUSValue __js_printf_like(2, 3) + LEPUS_ThrowTypeError(LEPUSContext *ctx, const char *fmt, ...); +LEPUSValue __js_printf_like(2, 3) + LEPUS_ThrowReferenceError(LEPUSContext *ctx, const char *fmt, ...); +LEPUSValue __js_printf_like(2, 3) + LEPUS_ThrowRangeError(LEPUSContext *ctx, const char *fmt, ...); +LEPUSValue __js_printf_like(2, 3) + LEPUS_ThrowInternalError(LEPUSContext *ctx, const char *fmt, ...); +LEPUSValue LEPUS_ThrowOutOfMemory(LEPUSContext *ctx); + +void __JS_FreeValue(LEPUSContext *ctx, LEPUSValue v); +void LEPUS_FreeValue(LEPUSContext *ctx, LEPUSValue v); +QJS_HIDE void __JS_FreeValueRT(LEPUSRuntime *rt, LEPUSValue v); +void LEPUS_FreeValueRT(LEPUSRuntime *rt, LEPUSValue v); + +bool LEPUS_IsGCMode(LEPUSContext *ctx); +bool LEPUS_IsGCModeRT(LEPUSRuntime *rt); + +char *LEPUS_GetGCTimingInfo(LEPUSContext *ctx, bool is_start); + +void LEPUS_PushHandle(LEPUSContext *ctx, void *ptr, int type); +void LEPUS_ResetHandle(LEPUSContext *ctx, void *ptr, int type); + +static inline LEPUSValue LEPUS_DupValue(LEPUSContext *ctx, LEPUSValueConst v) { + if (LEPUS_VALUE_HAS_REF_COUNT(v)) { + LEPUSRefCountHeader *p = (LEPUSRefCountHeader *)LEPUS_VALUE_GET_PTR(v); + p->ref_count++; + } + return (LEPUSValue)v; +} + +// +// if no_lepus_strict_mode is set to true, these conditions will handle +// differently: if the object is null or undefined, read properties will return +// null if the object is null or undefined, write properties will not throw +// exception more info: +void LEPUS_SetNoStrictMode(LEPUSContext *ctx); +// + +// +void LEPUS_SetVirtualStackSize(LEPUSContext *ctx, uint32_t stack_size); +// + +static inline LEPUSValue LEPUS_DupValueRT(LEPUSRuntime *rt, LEPUSValueConst v) { + if (LEPUS_VALUE_HAS_REF_COUNT(v)) { + LEPUSRefCountHeader *p = (LEPUSRefCountHeader *)LEPUS_VALUE_GET_PTR(v); + p->ref_count++; + } + return (LEPUSValue)v; +} + +int LEPUS_ToBool(LEPUSContext *ctx, + LEPUSValueConst val); /* return -1 for LEPUS_EXCEPTION */ +int LEPUS_ToInt32(LEPUSContext *ctx, int32_t *pres, LEPUSValueConst val); +static int inline LEPUS_ToUint32(LEPUSContext *ctx, uint32_t *pres, + LEPUSValueConst val) { + return LEPUS_ToInt32(ctx, (int32_t *)pres, val); +} +int LEPUS_ToInt64(LEPUSContext *ctx, int64_t *pres, LEPUSValueConst val); +int LEPUS_ToIndex(LEPUSContext *ctx, uint64_t *plen, LEPUSValueConst val); +int LEPUS_ToFloat64(LEPUSContext *ctx, double *pres, LEPUSValueConst val); +int LEPUS_ToBigInt64(LEPUSContext *ctx, int64_t *pres, LEPUSValueConst val); + +LEPUSValue LEPUS_NewStringLen(LEPUSContext *ctx, const char *str1, size_t len1); +LEPUSValue LEPUS_NewString(LEPUSContext *ctx, const char *str); +LEPUSValue LEPUS_NewAtomString(LEPUSContext *ctx, const char *str); +LEPUSValue LEPUS_ToString(LEPUSContext *ctx, LEPUSValueConst val); +LEPUSValue LEPUS_ToPropertyKey(LEPUSContext *ctx, LEPUSValueConst val); +LEPUSValue LEPUS_ToObject(LEPUSContext *ctx, LEPUSValueConst val); +const char *LEPUS_ToCStringLen2(LEPUSContext *ctx, size_t *plen, + LEPUSValueConst val1, LEPUS_BOOL cesu8); +static inline const char *LEPUS_ToCStringLen(LEPUSContext *ctx, size_t *plen, + LEPUSValueConst val1) { + return LEPUS_ToCStringLen2(ctx, plen, val1, 0); +} +static inline const char *LEPUS_ToCString(LEPUSContext *ctx, + LEPUSValueConst val1) { + return LEPUS_ToCStringLen2(ctx, NULL, val1, 0); +} +void LEPUS_FreeCString(LEPUSContext *ctx, const char *ptr); + +LEPUSValue LEPUS_NewObjectProtoClass(LEPUSContext *ctx, LEPUSValueConst proto, + LEPUSClassID class_id); +LEPUSValue LEPUS_NewObjectClass(LEPUSContext *ctx, int class_id); +LEPUSValue LEPUS_NewObjectProto(LEPUSContext *ctx, LEPUSValueConst proto); +LEPUSValue LEPUS_NewObject(LEPUSContext *ctx); + +LEPUS_BOOL LEPUS_IsFunction(LEPUSContext *ctx, LEPUSValueConst val); +LEPUS_BOOL LEPUS_IsConstructor(LEPUSContext *ctx, LEPUSValueConst val); +LEPUS_BOOL LEPUS_SetConstructorBit(LEPUSContext *ctx, LEPUSValueConst func_obj, + LEPUS_BOOL val); + +LEPUSValue LEPUS_NewArray(LEPUSContext *ctx); +int LEPUS_IsArray(LEPUSContext *ctx, LEPUSValueConst val); + +LEPUSValue LEPUS_GetPropertyInternal(LEPUSContext *ctx, LEPUSValueConst obj, + JSAtom prop, LEPUSValueConst receiver, + LEPUS_BOOL throw_ref_error); +static lepus_force_inline LEPUSValue LEPUS_GetProperty(LEPUSContext *ctx, + LEPUSValueConst this_obj, + JSAtom prop) { + return LEPUS_GetPropertyInternal(ctx, this_obj, prop, this_obj, 0); +} +LEPUSValue LEPUS_GetPropertyStr(LEPUSContext *ctx, LEPUSValueConst this_obj, + const char *prop); +LEPUSValue LEPUS_GetPropertyUint32(LEPUSContext *ctx, LEPUSValueConst this_obj, + uint32_t idx); + +int LEPUS_SetPropertyInternal(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValue val, int flags); +static inline int LEPUS_SetProperty(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValue val) { + return LEPUS_SetPropertyInternal(ctx, this_obj, prop, val, LEPUS_PROP_THROW); +} +int LEPUS_SetPropertyUint32(LEPUSContext *ctx, LEPUSValueConst this_obj, + uint32_t idx, LEPUSValue val); +int LEPUS_SetPropertyInt64(LEPUSContext *ctx, LEPUSValueConst this_obj, + int64_t idx, LEPUSValue val); +int LEPUS_SetPropertyStr(LEPUSContext *ctx, LEPUSValueConst this_obj, + const char *prop, LEPUSValue val); +int LEPUS_HasProperty(LEPUSContext *ctx, LEPUSValueConst this_obj, JSAtom prop); +int LEPUS_IsExtensible(LEPUSContext *ctx, LEPUSValueConst obj); +int LEPUS_PreventExtensions(LEPUSContext *ctx, LEPUSValueConst obj); +int LEPUS_DeleteProperty(LEPUSContext *ctx, LEPUSValueConst obj, JSAtom prop, + int flags); +int LEPUS_SetPrototype(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSValueConst proto_val); +LEPUSValueConst LEPUS_GetPrototype(LEPUSContext *ctx, LEPUSValueConst val); + +#define LEPUS_GPN_STRING_MASK (1 << 0) +#define LEPUS_GPN_SYMBOL_MASK (1 << 1) +#define LEPUS_GPN_PRIVATE_MASK (1 << 2) +/* only include the enumerable properties */ +#define LEPUS_GPN_ENUM_ONLY (1 << 4) +/* set theJSPropertyEnum.is_enumerable field */ +#define LEPUS_GPN_SET_ENUM (1 << 5) + +int LEPUS_GetOwnPropertyNames(LEPUSContext *ctx, LEPUSPropertyEnum **ptab, + uint32_t *plen, LEPUSValueConst obj, int flags); +int LEPUS_GetOwnProperty(LEPUSContext *ctx, LEPUSPropertyDescriptor *desc, + LEPUSValueConst obj, JSAtom prop); + +LEPUSValue LEPUS_ParseJSON(LEPUSContext *ctx, const char *buf, size_t buf_len, + const char *filename); +LEPUSValue LEPUS_Call(LEPUSContext *ctx, LEPUSValueConst func_obj, + LEPUSValueConst this_obj, int argc, + LEPUSValueConst *argv); +LEPUSValue LEPUS_Invoke(LEPUSContext *ctx, LEPUSValueConst this_val, + JSAtom atom, int argc, LEPUSValueConst *argv); +LEPUSValue LEPUS_CallConstructor(LEPUSContext *ctx, LEPUSValueConst func_obj, + int argc, LEPUSValueConst *argv); +LEPUSValue LEPUS_CallConstructor2(LEPUSContext *ctx, LEPUSValueConst func_obj, + LEPUSValueConst new_target, int argc, + LEPUSValueConst *argv); +LEPUSValue LEPUS_Eval(LEPUSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags); +#define LEPUS_EVAL_BINARY_LOAD_ONLY (1 << 0) /* only load the module */ +LEPUSValue LEPUS_EvalBinary(LEPUSContext *ctx, const uint8_t *buf, + size_t buf_len, int flags); +LEPUSValue LEPUS_GetGlobalObject(LEPUSContext *ctx); + +/* trace gc begin */ +void LEPUS_SetGCPauseSuppressionMode(LEPUSRuntime *rt, bool mode); +bool LEPUS_GetGCPauseSuppressionMode(LEPUSRuntime *rt); + +void DisposeGlobal(LEPUSRuntime *runtime, LEPUSValue *global_handle); +LEPUSValue *GlobalizeReference(LEPUSRuntime *runtime, LEPUSValue val, + bool is_weak); + +void *AllocateQJSValueValue(LEPUSRuntime *runtime); +void FreeQJSValueValue(LEPUSRuntime *runtime, void *instance); + +void SetGlobalWeak(LEPUSRuntime *runtime, LEPUSValue *global_handle, void *data, + void (*cb)(void *)); +void ClearGlobalWeak(LEPUSRuntime *runtime, LEPUSValue *global_handle); +void SetWeakState(LEPUSRuntime *runtime, LEPUSValue *global_handle); + +void *GetNapiScope(LEPUSContext *ctx); +void SetNapiScope(LEPUSContext *ctx, void *scope); +void InitNapiScope(LEPUSContext *ctx); +void FreeNapiScope(LEPUSContext *ctx); + +void LEPUS_VisitLEPUSValue(LEPUSRuntime *rt, LEPUSValue *val, int local_idx); + +void AddCurNode(LEPUSRuntime *rt, void *node, int type); + +bool CheckValidPtr(void *runtime, void *ptr); + +void LEPUS_TrigGC(LEPUSRuntime *rt); +/* trace gc end*/ + +LEPUSValue LEPUS_GetGlobalVar(LEPUSContext *ctx, JSAtom prop, + LEPUS_BOOL throw_ref_error); +// +// #ifdef ENABLE_LEPUSNG +/* flag = 0: normal variable write + flag = 1: initialize lexical variable + flag = 2: normal variable write, strict check was done before +*/ +typedef void (*IterateObject)(LEPUSContext *ctx, LEPUSValue key, + LEPUSValue value, void *p, void *raw_data); +void LEPUS_SetStringCache(LEPUSContext *ctx, LEPUSValue val, void *p); +void *LEPUS_GetStringCache(LEPUSValue val); +void *LEPUS_GetStringCache_GC(LEPUSValue val); +int LEPUS_SetGlobalVar(LEPUSContext *ctx, JSAtom prop, LEPUSValue val, + int flag); +LEPUSValue LEPUS_DeepEqual(LEPUSContext *ctx, LEPUSValueConst obj1, + LEPUSValueConst obj2); +LEPUSValue LEPUS_DeepCopy(LEPUSContext *ctx, LEPUSValueConst val); +void LEPUS_IterateObject(LEPUSContext *ctx, LEPUSValue obj, + IterateObject callback, void *p, void *raw_data); +int LEPUS_GetLength(LEPUSContext *ctx, LEPUSValue val); + +LEPUSValue LEPUS_NewLepusWrap(LEPUSContext *ctx, void *p, int tag); +// check if has lepus refs count header +static inline LEPUS_BOOL LEPUS_IsLepusRef(LEPUSValue val) { + return LEPUS_VALUE_IS_LEPUS_REF(val); +} + +static inline int LEPUS_GetLepusRefTag(LEPUSValue val) { + if (!LEPUS_IsLepusRef(val)) return -1; + LEPUSLepusRef *pref = (LEPUSLepusRef *)(LEPUS_VALUE_GET_PTR(val)); + return pref->tag; +} + +static inline void *LEPUS_GetLepusRefPoint(LEPUSValue val) { + if (!LEPUS_IsLepusRef(val)) return NULL; + LEPUSLepusRef *pref = (LEPUSLepusRef *)(LEPUS_VALUE_GET_PTR(val)); + return pref->p; +} + +// #define CONFIG_BIGNUM +// #endif +// +int LEPUS_IsInstanceOf(LEPUSContext *ctx, LEPUSValueConst val, + LEPUSValueConst obj); +int LEPUS_DefineProperty(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValueConst val, + LEPUSValueConst getter, LEPUSValueConst setter, + int flags); +// It is recommended to call LEPUS_DupValue to refer the original LEPUSValue val +// before calling the following define methods, otherwise it may crash +int LEPUS_DefinePropertyValue(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValue val, int flags); +int LEPUS_DefinePropertyValueUint32(LEPUSContext *ctx, LEPUSValueConst this_obj, + uint32_t idx, LEPUSValue val, int flags); +int LEPUS_DefinePropertyValueStr(LEPUSContext *ctx, LEPUSValueConst this_obj, + const char *prop, LEPUSValue val, int flags); +int LEPUS_DefinePropertyGetSet(LEPUSContext *ctx, LEPUSValueConst this_obj, + JSAtom prop, LEPUSValue getter, + LEPUSValue setter, int flags); +void LEPUS_SetOpaque(LEPUSValue obj, void *opaque); +void *LEPUS_GetOpaque(LEPUSValueConst obj, LEPUSClassID class_id); +void *LEPUS_GetOpaque2(LEPUSContext *ctx, LEPUSValueConst obj, + LEPUSClassID class_id); + +typedef void LEPUSFreeArrayBufferDataFunc(LEPUSRuntime *rt, void *opaque, + void *ptr); +LEPUSValue LEPUS_NewArrayBuffer(LEPUSContext *ctx, uint8_t *buf, size_t len, + LEPUSFreeArrayBufferDataFunc *free_func, + void *opaque, LEPUS_BOOL is_shared); +LEPUSValue LEPUS_NewArrayBufferCopy(LEPUSContext *ctx, const uint8_t *buf, + size_t len); +void LEPUS_DetachArrayBuffer(LEPUSContext *ctx, LEPUSValueConst obj); +uint8_t *LEPUS_GetArrayBuffer(LEPUSContext *ctx, size_t *psize, + LEPUSValueConst obj); +LEPUSValue LEPUS_GetTypedArrayBuffer(LEPUSContext *ctx, LEPUSValueConst obj, + size_t *pbyte_offset, size_t *pbyte_length, + size_t *pbytes_per_element); + +// +LEPUS_BOOL LEPUS_IsArrayBuffer(LEPUSValueConst v); +LEPUSTypedArrayType LEPUS_GetTypedArrayType(LEPUSContext *ctx, + LEPUSValueConst val); +LEPUS_BOOL LEPUS_IsDataView(LEPUSContext *ctx, LEPUSValueConst val); +LEPUS_BOOL LEPUS_IsTypedArray(LEPUSContext *ctx, LEPUSValueConst val); + +LEPUSClassID LEPUS_GetTypedArrayClassID(LEPUSTypedArrayType type); + +// must be freed by lepus_free() / lepus_free_rt() +uint8_t *LEPUS_MoveArrayBuffer(LEPUSContext *, size_t *, LEPUSValue); +LEPUS_BOOL LEPUS_StrictEq(LEPUSContext *ctx, LEPUSValueConst op1, + LEPUSValueConst op2); +LEPUS_BOOL LEPUS_SameValue(LEPUSContext *ctx, LEPUSValueConst op1, + LEPUSValueConst op2); +// + +LEPUSValue LEPUS_NewPromiseCapability(LEPUSContext *ctx, + LEPUSValue *resolving_funcs); + +/* return != 0 if the LEPUS code needs to be interrupted */ +typedef int LEPUSInterruptHandler(LEPUSRuntime *rt, void *opaque); +void LEPUS_SetInterruptHandler(LEPUSRuntime *rt, LEPUSInterruptHandler *cb, + void *opaque); +/* if can_block is TRUE, Atomics.wait() can be used */ +void LEPUS_SetCanBlock(LEPUSRuntime *rt, LEPUS_BOOL can_block); + +typedef struct LEPUSModuleDef LEPUSModuleDef; + +/* return the module specifier (allocated with lepus_malloc()) or NULL if + exception */ +typedef char *LEPUSModuleNormalizeFunc(LEPUSContext *ctx, + const char *module_base_name, + const char *module_name, void *opaque); +typedef LEPUSModuleDef *LEPUSModuleLoaderFunc(LEPUSContext *ctx, + const char *module_name, + void *opaque); + +/* module_normalize = NULL is allowed and invokes the default module + filename normalizer */ +void LEPUS_SetModuleLoaderFunc(LEPUSRuntime *rt, + LEPUSModuleNormalizeFunc *module_normalize, + LEPUSModuleLoaderFunc *module_loader, + void *opaque); + +/* LEPUS Job support */ + +typedef LEPUSValue LEPUSJobFunc(LEPUSContext *ctx, int argc, + LEPUSValueConst *argv); +int LEPUS_EnqueueJob(LEPUSContext *ctx, LEPUSJobFunc *job_func, int argc, + LEPUSValueConst *argv); + +LEPUS_BOOL LEPUS_IsJobPending(LEPUSRuntime *rt); +int LEPUS_ExecutePendingJob(LEPUSRuntime *rt, LEPUSContext **pctx); + +uint64_t LEPUS_GetPrimjsVersion(); + +/* Object Writer/Reader (currently only used to handle precompiled code) */ +#define LEPUS_WRITE_OBJ_BYTECODE (1 << 0) /* allow function/module */ +#define LEPUS_WRITE_OBJ_BSWAP (1 << 1) /* byte swapped output */ +uint8_t *LEPUS_WriteObject(LEPUSContext *ctx, size_t *psize, + LEPUSValueConst obj, int flags); +#define LEPUS_READ_OBJ_BYTECODE (1 << 0) /* allow function/module */ +#define LEPUS_READ_OBJ_ROM_DATA (1 << 1) /* avoid duplicating 'buf' data */ +LEPUSValue LEPUS_ReadObject(LEPUSContext *ctx, const uint8_t *buf, + size_t buf_len, int flags); +LEPUSValue LEPUS_EvalFunction(LEPUSContext *ctx, LEPUSValue fun_obj, + LEPUSValueConst this_obj); + +LEPUSValue LEPUS_NewWString(LEPUSContext *, const uint16_t *, size_t length); +LEPUSValue LEPUS_FromJSON(LEPUSContext *, const char *); +LEPUSValue LEPUS_ToJSON(LEPUSContext *, LEPUSValueConst, int indent); +LEPUSValue LEPUS_ToWString(LEPUSContext *, LEPUSValueConst); +JSAtom LEPUS_ValueToAtom(LEPUSContext *ctx, LEPUSValueConst val); + +const uint16_t *LEPUS_GetStringChars(LEPUSContext *, LEPUSValueConst); +uint32_t LEPUS_GetStringLength(LEPUSContext *, LEPUSValueConst); +LEPUSClassID LEPUS_GetClassID(LEPUSContext *, LEPUSValueConst); +LEPUSValue LEPUS_NewArrayWithValue(LEPUSContext *, uint32_t length, + LEPUSValueConst *values); +LEPUSValue LEPUS_NewTypedArray(LEPUSContext *, uint32_t length, LEPUSClassID); +LEPUSValue LEPUS_NewTypedArrayWithBuffer(LEPUSContext *, LEPUSValueConst buffer, + uint32_t byteOffset, uint32_t length, + LEPUSClassID class_id); +LEPUSValue LEPUS_CallV(LEPUSContext *ctx, LEPUSValueConst func_obj, + LEPUSValueConst this_obj, int argc, LEPUSValue *argv); +LEPUSValue LEPUS_CallConstructorV(LEPUSContext *ctx, LEPUSValueConst func_obj, + int argc, LEPUSValue *argv); + +/* C function definition */ +typedef enum LEPUSCFunctionEnum { /* XXX: should rename for namespace isolation + */ + LEPUS_CFUNC_generic, + LEPUS_CFUNC_generic_magic, + LEPUS_CFUNC_constructor, + LEPUS_CFUNC_constructor_magic, + LEPUS_CFUNC_constructor_or_func, + LEPUS_CFUNC_constructor_or_func_magic, + LEPUS_CFUNC_f_f, + LEPUS_CFUNC_f_f_f, + LEPUS_CFUNC_getter, + LEPUS_CFUNC_setter, + LEPUS_CFUNC_getter_magic, + LEPUS_CFUNC_setter_magic, + LEPUS_CFUNC_iterator_next, +} LEPUSCFunctionEnum; + +typedef union LEPUSCFunctionType { + LEPUSCFunction *generic; + LEPUSValue (*generic_magic)(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv, int magic); + LEPUSCFunction *constructor; + LEPUSValue (*constructor_magic)(LEPUSContext *ctx, LEPUSValueConst new_target, + int argc, LEPUSValueConst *argv, int magic); + LEPUSCFunction *constructor_or_func; + double (*f_f)(double); + double (*f_f_f)(double, double); + LEPUSValue (*getter)(LEPUSContext *ctx, LEPUSValueConst this_val); + LEPUSValue (*setter)(LEPUSContext *ctx, LEPUSValueConst this_val, + LEPUSValueConst val); + LEPUSValue (*getter_magic)(LEPUSContext *ctx, LEPUSValueConst this_val, + int magic); + LEPUSValue (*setter_magic)(LEPUSContext *ctx, LEPUSValueConst this_val, + LEPUSValueConst val, int magic); + LEPUSValue (*iterator_next)(LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv, int *pdone, + int magic); +} LEPUSCFunctionType; + +LEPUSValue LEPUS_NewCFunction2(LEPUSContext *ctx, LEPUSCFunction *func, + const char *name, int length, + LEPUSCFunctionEnum cproto, int magic); +LEPUSValue LEPUS_NewCFunctionData(LEPUSContext *ctx, LEPUSCFunctionData *func, + int length, int magic, int data_len, + LEPUSValueConst *data); + +static inline LEPUSValue LEPUS_NewCFunction(LEPUSContext *ctx, + LEPUSCFunction *func, + const char *name, int length) { + return LEPUS_NewCFunction2(ctx, func, name, length, LEPUS_CFUNC_generic, 0); +} + +static inline LEPUSValue LEPUS_NewCFunctionMagic(LEPUSContext *ctx, + LEPUSCFunctionMagic *func, + const char *name, int length, + LEPUSCFunctionEnum cproto, + int magic) { + return LEPUS_NewCFunction2(ctx, (LEPUSCFunction *)func, name, length, cproto, + magic); +} + +/* C property definition */ + +typedef struct LEPUSCFunctionListEntry { + const char *name; + uint8_t prop_flags; + uint8_t def_type; + int16_t magic; + union { + struct { + uint8_t length; /* XXX: should move outside union */ + uint8_t cproto; /* XXX: should move outside union */ + LEPUSCFunctionType cfunc; + } func; + struct { + LEPUSCFunctionType get; + LEPUSCFunctionType set; + } getset; + struct { + const char *name; + int base; + } alias; + struct { + const struct LEPUSCFunctionListEntry *tab; + int len; + } prop_list; + const char *str; + int32_t i32; + int64_t i64; + double f64; + } u; +} LEPUSCFunctionListEntry; + +#define LEPUS_DEF_CFUNC 0 +#define LEPUS_DEF_CGETSET 1 +#define LEPUS_DEF_CGETSET_MAGIC 2 +#define LEPUS_DEF_PROP_STRING 3 +#define LEPUS_DEF_PROP_INT32 4 +#define LEPUS_DEF_PROP_INT64 5 +#define LEPUS_DEF_PROP_DOUBLE 6 +#define LEPUS_DEF_PROP_UNDEFINED 7 +#define LEPUS_DEF_OBJECT 8 +#define LEPUS_DEF_ALIAS 9 + +#define LEPUS_CFUNC_DEF(name, length, func1) \ + { \ + name, LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE, LEPUS_DEF_CFUNC, 0, \ + .u.func = { \ + length, \ + LEPUS_CFUNC_generic, \ + {.generic = func1} \ + } \ + } +#define LEPUS_CFUNC_MAGIC_DEF(name, length, func1, magic) \ + { \ + name, LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE, LEPUS_DEF_CFUNC, \ + magic, .u.func = { \ + length, \ + LEPUS_CFUNC_generic_magic, \ + {.generic_magic = func1} \ + } \ + } +#define LEPUS_CFUNC_SPECIAL_DEF(name, length, cproto, func1) \ + { \ + name, LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE, LEPUS_DEF_CFUNC, 0, \ + .u.func = { \ + length, \ + LEPUS_CFUNC_##cproto, \ + {.cproto = func1} \ + } \ + } +#define LEPUS_ITERATOR_NEXT_DEF(name, length, func1, magic) \ + { \ + name, LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE, LEPUS_DEF_CFUNC, \ + magic, .u.func = { \ + length, \ + LEPUS_CFUNC_iterator_next, \ + {.iterator_next = func1} \ + } \ + } +#define LEPUS_CGETSET_DEF(name, fgetter, fsetter) \ + { \ + name, LEPUS_PROP_CONFIGURABLE, LEPUS_DEF_CGETSET, 0, \ + .u.getset.get.getter = fgetter, .u.getset.set.setter = fsetter \ + } +#define LEPUS_CGETSET_MAGIC_DEF(name, fgetter, fsetter, magic) \ + { \ + name, LEPUS_PROP_CONFIGURABLE, LEPUS_DEF_CGETSET_MAGIC, magic, \ + .u.getset.get.getter_magic = fgetter, \ + .u.getset.set.setter_magic = fsetter \ + } +#define LEPUS_PROP_STRING_DEF(name, cstr, prop_flags) \ + { name, prop_flags, LEPUS_DEF_PROP_STRING, 0, .u.str = cstr } +#define LEPUS_PROP_INT32_DEF(name, val, prop_flags) \ + { name, prop_flags, LEPUS_DEF_PROP_INT32, 0, .u.i32 = val } +#define LEPUS_PROP_INT64_DEF(name, val, prop_flags) \ + { name, prop_flags, LEPUS_DEF_PROP_INT64, 0, .u.i64 = val } +#define LEPUS_PROP_DOUBLE_DEF(name, val, prop_flags) \ + { name, prop_flags, LEPUS_DEF_PROP_DOUBLE, 0, .u.f64 = val } +#define LEPUS_PROP_UNDEFINED_DEF(name, prop_flags) \ + { name, prop_flags, LEPUS_DEF_PROP_UNDEFINED, 0, .u.i32 = 0 } +#define LEPUS_OBJECT_DEF(name, tab, len, prop_flags) \ + { \ + name, prop_flags, LEPUS_DEF_OBJECT, 0, .u.prop_list = { tab, len } \ + } +#define LEPUS_ALIAS_DEF(name, from) \ + { \ + name, LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE, LEPUS_DEF_ALIAS, 0, \ + .u.alias = { \ + from, \ + -1 \ + } \ + } +#define LEPUS_ALIAS_BASE_DEF(name, from, base) \ + { \ + name, LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE, LEPUS_DEF_ALIAS, 0, \ + .u.alias = { \ + from, \ + base \ + } \ + } + +void LEPUS_SetPropertyFunctionList(LEPUSContext *ctx, LEPUSValueConst obj, + const LEPUSCFunctionListEntry *tab, int len); + +/* C module definition */ + +typedef int LEPUSModuleInitFunc(LEPUSContext *ctx, LEPUSModuleDef *m); + +LEPUSModuleDef *LEPUS_NewCModule(LEPUSContext *ctx, const char *name_str, + LEPUSModuleInitFunc *func); +/* can only be called before the module is instantiated */ +QJS_HIDE int LEPUS_AddModuleExport(LEPUSContext *ctx, LEPUSModuleDef *m, + const char *name_str); +QJS_HIDE int LEPUS_AddModuleExportList(LEPUSContext *ctx, LEPUSModuleDef *m, + const LEPUSCFunctionListEntry *tab, + int len); +/* can only be called after the module is instantiated */ +int LEPUS_SetModuleExport(LEPUSContext *ctx, LEPUSModuleDef *m, + const char *export_name, LEPUSValue val); +int LEPUS_SetModuleExportList(LEPUSContext *ctx, LEPUSModuleDef *m, + const LEPUSCFunctionListEntry *tab, int len); + +/* expose for napi use */ +LEPUSValue lepus_object_getOwnPropertyDescriptor(LEPUSContext *ctx, + LEPUSValueConst this_val, + int argc, + LEPUSValueConst *argv, + int magic); + +// +QJS_EXPORT_FOR_DEVTOOL int64_t find_line_num(LEPUSContext *ctx, + struct LEPUSFunctionBytecode *b, + uint32_t pc_value); + +QJS_EXPORT_FOR_DEVTOOL int lepus_class_has_bytecode(LEPUSClassID class_id); + +QJS_EXPORT_FOR_DEVTOOL const char *get_func_name(LEPUSContext *ctx, + LEPUSValueConst func); + +QJS_EXPORT_FOR_DEVTOOL int get_leb128_u64(uint64_t *pval, const uint8_t *buf, + const uint8_t *buf_end); + +QJS_EXPORT_FOR_DEVTOOL int get_sleb128_u64(int64_t *pval, const uint8_t *buf, + const uint8_t *buf_end); +QJS_EXPORT_FOR_DEVTOOL __attribute__((warn_unused_result)) int +lepus_get_length32(LEPUSContext *ctx, uint32_t *pres, LEPUSValueConst obj); +void SetLynxTargetSdkVersion(LEPUSContext *ctx, const char *version); + +/* + register lepus::Value::ValueType's value in qjs runtime for lepusng, + because these type value are used in qjs +*/ +void LEPUS_RegisterNgType(LEPUSRuntime *, int32_t *, uint32_t size); + +/* +when an object of type lepus::RefCounted is destructed, + call this function to free all MapRecords +*/ +void LEPUS_FreeRefCountedWeakRef(LEPUSRuntime *, struct JSMapRecord *); + +LEPUS_BOOL LEPUS_IsPrimjsEnabled(LEPUSRuntime *rt); + +/* + This function doesn't change the rc of value; +*/ +LEPUSValue LEPUS_NewObjectWithArgs(LEPUSContext *, int32_t size, const char **, + LEPUSValue *); +/* + This function doesn't change the rc of value; +*/ + +LEPUSValue LEPUS_NewArrayWithArgs(LEPUSContext *, int32_t, LEPUSValue *); + +/* + If input is an ASCII string, return pointer to string, otherwise + return nullptr; +*/ +const char *LEPUS_GetStringUtf8(LEPUSContext *, const struct JSString *); +void LEPUS_SetFuncFileName(LEPUSContext *, LEPUSValue, const char *); + +void InitLynxTraceEnv(void *(*)(const char *), void (*)(void *)); +// + +#undef lepus_unlikely +#undef lepus_force_inline + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/quickjs_queue.h b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs_queue.h new file mode 100644 index 000000000..b19956fdd --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs_queue.h @@ -0,0 +1,54 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_QUEUE_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_QUEUE_H_ + +#include + +#define DEFAULT_INIT_SIZE 2048 + +#ifdef ENABLE_GC_DEBUG_TOOLS +#include +#ifdef __cplusplus +extern "C" { +#endif +bool check_valid_ptr(void *runtime, void *ptr); +#ifdef __cplusplus +} +#endif +#endif + +// rear is emptry, capacity == size - 1 +class Queue { + public: + Queue(void *runtime, int initial_size); + explicit Queue(void *runtime); + ~Queue(); + + bool IsEmpty(); + bool IsFull(); + void EnQueue(void *ptr); + void *DeQueue(); + void *Front(); + void ResizeQueue(); + // void Display(); + int GetCount() { return count; } + int GetSize() { return size; } + const void *GetQueue() { return queue; } + void Split(int cnt, Queue *q) { + for (int i = 0; i < cnt; i++) { + q->EnQueue(DeQueue()); + } + } + + private: + void **queue; + void *rt_; + int front; + int rear; + int count; + int size; +}; + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_QUEUE_H_ diff --git a/NativeScript/napi/android/primjs/include/quickjs/include/quickjs_version.h b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs_version.h new file mode 100644 index 000000000..8c7a32924 --- /dev/null +++ b/NativeScript/napi/android/primjs/include/quickjs/include/quickjs_version.h @@ -0,0 +1,21 @@ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_VERSION_H_ +#define SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_VERSION_H_ + +#include +#include +#define FEATURE_LEPUSNG_DEBUGINFO_OUTSIDE "2.5" +#define PRIMJS_ADD_VERSION_CODE "2.14" + +typedef struct Version { + int major, minor, revision, build; +} Version; + +void VersionInit(Version* v, const char* version); +uint8_t VersionLessOrEqual(Version v1, Version other); +uint8_t IsHigherOrEqual(const char* targetV, const char* baseV); + +#endif // SRC_INTERPRETER_QUICKJS_INCLUDE_QUICKJS_VERSION_H_ diff --git a/NativeScript/napi/android/primjs/jsr.cpp b/NativeScript/napi/android/primjs/jsr.cpp new file mode 100644 index 000000000..a3e1921d6 --- /dev/null +++ b/NativeScript/napi/android/primjs/jsr.cpp @@ -0,0 +1,111 @@ +#include "napi_env_quickjs.h" +#include "jsr.h" + +JSR::JSR() = default; +tns::ConcurrentMap JSR::env_to_jsr_cache; + +struct napi_runtime__ { + LEPUSRuntime* runtime; + LEPUSContext* context; +}; + +napi_status js_create_runtime(napi_runtime *runtime) { + auto _runtime = new napi_runtime__(); + LEPUSRuntime* rt = LEPUS_NewRuntimeWithMode(0); + LEPUS_SetRuntimeInfo(rt, "Lynx_LepusNG"); + _runtime->context = LEPUS_NewContext(rt); + LEPUS_SetMaxStackSize(_runtime->context, 1024 * 1024 * 1024); + _runtime->runtime = rt; + *runtime = _runtime; + + LEPUS_Eval( _runtime->context, "var a;", strlen("var a;"), "a", LEPUS_EVAL_TYPE_GLOBAL); + + + return napi_ok; +} +napi_status js_create_napi_env(napi_env *env, napi_runtime runtime) { + + *env = napi_new_env(); + napi_attach_quickjs((*env), runtime->context); + + + JSR::env_to_jsr_cache.Insert((*env), new JSR()); + LEPUS_Eval( runtime->context, "var a;", strlen("var a;"), "a", LEPUS_EVAL_TYPE_GLOBAL); + + const char *requireFactoryScript = "var a;"; + + napi_value source; + napi_create_string_utf8((*env), requireFactoryScript, strlen(requireFactoryScript), &source); + +// napi_value global; +// napi_get_global(env, &global); + + napi_value result; + napi_status status = js_execute_script((*env), source, "a", &result); + + return napi_ok; +} + +napi_status js_set_runtime_flags(const char *flags) { + return napi_ok; +} + +napi_status js_lock_env(napi_env env) { + auto jsr = JSR::env_to_jsr_cache.Get(env); + if (jsr) jsr->lock(); + return napi_ok; +} + +napi_status js_unlock_env(napi_env env) { + auto jsr = JSR::env_to_jsr_cache.Get(env); + if (jsr) jsr->unlock(); + + return napi_ok; +} + +napi_status js_free_napi_env(napi_env env) { + JSR* jsr = JSR::env_to_jsr_cache.Get(env); + delete jsr; + JSR::env_to_jsr_cache.Remove(env); + napi_detach_quickjs(env); + return napi_ok; +} + +napi_status js_free_runtime(napi_runtime runtime) { + LEPUS_FreeContext(runtime->context); + LEPUS_FreeRuntime(runtime->runtime); + return napi_ok; +} + +napi_status js_execute_script(napi_env env, + napi_value script, + const char *file, + napi_value *result) { + + return napi_run_script_source(env, script, file, result); +} + +napi_status js_execute_pending_jobs(napi_env env) { + return primjs_execute_pending_jobs(env); +} + +napi_status +js_adjust_external_memory(napi_env env, int64_t changeInBytes, int64_t *externalMemory) { + napi_adjust_external_memory(env, changeInBytes, externalMemory); + return napi_ok; +} + +napi_status js_cache_script(napi_env env, const char *source, const char *file) { + return napi_ok; +} + +napi_status js_run_cached_script(napi_env env, const char *file, napi_value script, void *cache, + napi_value *result) { + return napi_ok; +} + + +napi_status js_get_runtime_version(napi_env env, napi_value *version) { + napi_create_string_utf8(env, "PrimJS", NAPI_AUTO_LENGTH, version); + return napi_ok; +} diff --git a/NativeScript/napi/android/primjs/jsr.h b/NativeScript/napi/android/primjs/jsr.h new file mode 100644 index 000000000..5398201d3 --- /dev/null +++ b/NativeScript/napi/android/primjs/jsr.h @@ -0,0 +1,55 @@ +// +// Created by Ammar Ahmed on 01/12/2024. +// + +#ifndef TEST_APP_JSR_H +#define TEST_APP_JSR_H +#include "jsr_common.h" +#include "napi_env_quickjs.h" +#include "mutex" +#include +#include "ConcurrentMap.h" + +class JSR { +public: + JSR(); + std::recursive_mutex js_mutex; + void lock() { + js_mutex.lock(); + } + void unlock() { + js_mutex.unlock(); + } + + static tns::ConcurrentMap env_to_jsr_cache; +}; + +class NapiScope { +public: + explicit NapiScope(napi_env env, bool open_handle = true) + : env_(env) + { + js_lock_env(env_); + // TODO: UPDATE STACK TOP HERE + if (open_handle) { + napi_open_handle_scope(env_, &napiHandleScope_); + } else { + napiHandleScope_ = nullptr; + } + } + + ~NapiScope() { + if (napiHandleScope_) { + napi_close_handle_scope(env_, napiHandleScope_); + } + js_unlock_env(env_); + } + +private: + napi_env env_; + napi_handle_scope napiHandleScope_; +}; + +#define JSEnterScope + +#endif //TEST_APP_JSR_H diff --git a/NativeScript/napi/android/primjs/napi_env.cc b/NativeScript/napi/android/primjs/napi_env.cc new file mode 100644 index 000000000..287de1ed3 --- /dev/null +++ b/NativeScript/napi/android/primjs/napi_env.cc @@ -0,0 +1,170 @@ +/** + * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved. + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file in the root of the source tree. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "napi_env.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "napi_env_quickjs.h" +#include "primjs-api.h" + +struct napi_env_data__ { + void AddCleanupHook(void (*fun)(void* arg), void* arg) { + cleanup_hooks.insert(CleanupHook{fun, arg, cleanup_hooks.size()}); + } + + void RemoveCleanupHook(void (*fun)(void* arg), void* arg) { + cleanup_hooks.erase(CleanupHook{fun, arg, 0}); + } + + ~napi_env_data__() { RunCleanup(); } + + struct CleanupHook { + void (*fun)(void*); + void* arg; + uint64_t insertion_order_counter; + + struct Equal { + bool operator()(const CleanupHook& lhs, const CleanupHook& rhs) const { + return lhs.fun == rhs.fun && lhs.arg == rhs.arg; + } + }; + + struct Hash { + std::size_t operator()(CleanupHook const& s) const { + std::size_t h1 = + std::hash{}(reinterpret_cast(s.fun)); + std::size_t h2 = + std::hash{}(reinterpret_cast(s.arg)); + return h1 ^ (h2 << 1); + } + }; + }; + + void RunCleanup() { + while (!cleanup_hooks.empty()) { + // Copy into a vector, since we can't sort an unordered_set in-place. + std::vector callbacks(cleanup_hooks.begin(), + cleanup_hooks.end()); + // We can't erase the copied elements from `cleanup_hooks_` yet, because + // we need to be able to check whether they were un-scheduled by another + // hook. + + std::sort(callbacks.begin(), callbacks.end(), + [](const CleanupHook& a, const CleanupHook& b) { + // Sort in descending order so that the most recently inserted + // callbacks are run first. + return a.insertion_order_counter > b.insertion_order_counter; + }); + + for (const auto& cb : callbacks) { + if (cleanup_hooks.count(cb) == 0) { + // This hook was removed from the `cleanup_hooks_` set during another + // hook that was run earlier. Nothing to do here. + continue; + } + + cb.fun(cb.arg); + cleanup_hooks.erase(cb); + } + } + } + + std::unordered_set + cleanup_hooks; +}; + +napi_status napi_get_version(napi_env env, uint32_t* version) { + *version = NAPI_VERSION_EXPERIMENTAL; + return napi_clear_last_error(env); +} + +napi_status napi_add_env_cleanup_hook(napi_env env, void (*fun)(void* arg), + void* arg) { + env->state.env_data->AddCleanupHook(fun, arg); + return napi_clear_last_error(env); +} + +napi_status napi_remove_env_cleanup_hook(napi_env env, void (*fun)(void* arg), + void* arg) { + env->state.env_data->RemoveCleanupHook(fun, arg); + return napi_clear_last_error(env); +} + +// Warning: Keep in-sync with napi_status enum +static const char* error_messages[] = { + nullptr, + "Invalid argument", + "An object was expected", + "A string was expected", + "A string or symbol was expected", + "A function was expected", + "A number was expected", + "A boolean was expected", + "An array was expected", + "Unknown failure", + "An exception is pending", + "The async work item was cancelled", + "napi_escape_handle already called on scope", + "Invalid handle scope usage", + "Invalid callback scope usage", + "Thread-safe function queue is full", + "Thread-safe function handle is closing", + "A bigint was expected", + "A date was expected", + "An arraybuffer was expected", + "A detachable arraybuffer was expected", + "Napi deadlock", + "napi_no_external_buffers_allowed", + "napi_cannot_run_js", + "napi_handle_scope_empty", + "napi_memory_error", + "napi_promise_exception"}; + +#define NAPI_ARRAYSIZE(array) (sizeof(array) / sizeof(array[0])) + +napi_status napi_get_last_error_info(napi_env env, + const napi_extended_error_info** result) { + // you must update this assert to reference the last message + // in the napi_status enum each time a new error message is added. + // We don't have a napi_status_last as this would result in an ABI + // change each time a message was added. + const int last_status = napi_promise_exception; + + static_assert(NAPI_ARRAYSIZE(error_messages) == last_status + 1, + "Count of error messages must match count of error values"); + assert(env->state.last_error.error_code <= last_status); + + // Wait until someone requests the last error information to fetch the error + // message string + env->state.last_error.error_message = + error_messages[env->state.last_error.error_code]; + + *result = &(env->state.last_error); + return napi_ok; +} + +napi_env napi_new_env() { + napi_env env = new napi_env__{}; + env->state.env_data = new napi_env_data__{}; + return env; +} + +void napi_free_env(napi_env env) { + delete env->state.env_data; + delete env; +} \ No newline at end of file diff --git a/NativeScript/napi/android/primjs/napi_env.h b/NativeScript/napi/android/primjs/napi_env.h new file mode 100644 index 000000000..9a123453b --- /dev/null +++ b/NativeScript/napi/android/primjs/napi_env.h @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved. + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file in the root of the source tree. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_NAPI_ENV_NAPI_ENV_H_ +#define SRC_NAPI_ENV_NAPI_ENV_H_ + +//#include "js_native_api.h" +// +//EXTERN_C_START +// +//NAPI_EXTERN napi_env napi_new_env(); +// +//NAPI_EXTERN void napi_free_env(napi_env); +// +//NAPI_EXTERN void napi_setup_loader(napi_env env, const char* name); +// +//NAPI_EXTERN napi_status primjs_execute_pending_jobs(napi_env env); +// +//EXTERN_C_END + + +#endif // SRC_NAPI_ENV_NAPI_ENV_H_ diff --git a/NativeScript/napi/android/primjs/napi_env_quickjs.h b/NativeScript/napi/android/primjs/napi_env_quickjs.h new file mode 100644 index 000000000..88c860b97 --- /dev/null +++ b/NativeScript/napi/android/primjs/napi_env_quickjs.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved. + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file in the root of the source tree. + */ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_NAPI_QUICKJS_NAPI_ENV_QUICKJS_H_ +#define SRC_NAPI_QUICKJS_NAPI_ENV_QUICKJS_H_ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus +#include "quickjs/include/quickjs.h" +#ifdef __cplusplus +} +#endif // __cplusplus + +#include "js_native_api.h" +EXTERN_C_START + +NAPI_EXTERN napi_env napi_new_env(); + +NAPI_EXTERN void napi_free_env(napi_env); + +NAPI_EXTERN void napi_setup_loader(napi_env env, const char* name); + + +NAPI_EXTERN void napi_attach_quickjs(napi_env env, LEPUSContext* ctx); + +NAPI_EXTERN void napi_detach_quickjs(napi_env env); + +NAPI_EXTERN LEPUSContext* napi_get_env_context_quickjs(napi_env env); + +// return value should be freed by caller +NAPI_EXTERN LEPUSValue napi_js_value_to_quickjs_value(napi_env env, + napi_value value); + +// input value is freed by napi +// return value is only valid in current handle scope +NAPI_EXTERN napi_value napi_quickjs_value_to_js_value(napi_env env, + LEPUSValue value); + +NAPI_EXTERN napi_status primjs_execute_pending_jobs(napi_env env); + +EXTERN_C_END + +#endif // SRC_NAPI_QUICKJS_NAPI_ENV_QUICKJS_H_ diff --git a/NativeScript/napi/android/primjs/napi_state.h b/NativeScript/napi/android/primjs/napi_state.h new file mode 100644 index 000000000..20b82448f --- /dev/null +++ b/NativeScript/napi/android/primjs/napi_state.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved. + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file in the root of the source tree. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef SRC_NAPI_COMMON_NAPI_STATE_H_ +#define SRC_NAPI_COMMON_NAPI_STATE_H_ + +//#include "primjs-api.h" + +//typedef struct napi_env_data__* napi_env_data; +// +//struct napi_state__ { +// napi_extended_error_info last_error; +// napi_env_data env_data; +//}; +// +//inline napi_status napi_clear_last_error(napi_env env) { +// env->state->last_error.error_code = napi_ok; +// +// env->state->last_error.engine_error_code = 0; +// return napi_ok; +//} +// +//inline napi_status napi_set_last_error(napi_env env, napi_status error_code) { +// env->state->last_error.error_code = error_code; +// return error_code; +//} + +#endif // SRC_NAPI_COMMON_NAPI_STATE_H_ diff --git a/NativeScript/napi/android/primjs/primjs-api.cc b/NativeScript/napi/android/primjs/primjs-api.cc new file mode 100644 index 000000000..9dc08f372 --- /dev/null +++ b/NativeScript/napi/android/primjs/primjs-api.cc @@ -0,0 +1,2881 @@ +/** + * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved. + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file in the root of the source tree. + */ + +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#include "primjs-api.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "basic/log/logging.h" +#include "napi_env_quickjs.h" +#include "quickjs/include/quickjs-inner.h" + + +std::unordered_map napi_context__::rt_to_env_cache; +struct napi_callback_info__ { + napi_value newTarget; + napi_value thisArg; + napi_value *argv; + void *data; + uint16_t argc; +}; + +static inline void js_enter(napi_env env) { + env->js_enter_state++; +} + +static inline void js_exit(napi_env env) { + if (--env->js_enter_state <= 0) { + primjs_execute_pending_jobs(env); + } +} + +namespace { + napi_status napi_set_exception(napi_env env, LEPUSValue exception) { + if (env->ctx->last_exception) { + JS_FreeValue_Comp(env->ctx->ctx, *env->ctx->last_exception); + } + env->ctx->last_exception.reset(new LEPUSValue(exception)); + + env->ctx->last_exception_pVal.Reset(nullptr, exception, nullptr, + env->ctx->ctx, true); + + return napi_set_last_error(env, napi_pending_exception); + } + + napi_status napi_set_error_msg_code(napi_env env, napi_value error, + napi_value code, napi_value msg, + const char *code_cstring) { + { + LEPUSValue msg_value = JS_DupValue_Comp(env->ctx->ctx, ToJSValue(msg)); + env->ctx->CreateHandle(msg_value, true); + + CHECK_QJS(env, LEPUS_SetProperty(env->ctx->ctx, ToJSValue(error), + env->ctx->PROP_MESSAGE, msg_value) != -1); + } + + if (code || code_cstring) { + LEPUSValue code_value; + if (code == nullptr) { + code_value = LEPUS_NewString(env->ctx->ctx, code_cstring); + env->ctx->CreateHandle(code_value, true); + } else { + code_value = ToJSValue(code); + RETURN_STATUS_IF_FALSE(env, + LEPUS_IsString(code_value) || LEPUS_IsUndefined(code_value), + napi_string_expected); + + code_value = JS_DupValue_Comp(env->ctx->ctx, code_value); + } + + if (!LEPUS_IsUndefined(code_value)) { + CHECK_QJS(env, LEPUS_SetProperty(env->ctx->ctx, ToJSValue(error), + env->ctx->PROP_CODE, code_value) != -1); + } + + } + + return napi_ok; + } + + template + class ArgsConverter { + public: + ArgsConverter(size_t argc, In *argv) { + Out *destination = inline_; + if (argc > kMaxStackArgs) { + outOfLine_ = std::make_unique(argc); + destination = outOfLine_.get(); + } + + for (size_t i = 0; i < argc; ++i) { + destination[i] = Convert(argv + i); + } + } + + operator Out *() { return outOfLine_ ? outOfLine_.get() : inline_; } + + private: + constexpr static unsigned kMaxStackArgs = 8; + Out inline_[kMaxStackArgs]; + std::unique_ptr outOfLine_; + }; +} // namespace + +namespace qjsimpl { + + enum NativeType { + External, Wrapper + }; + + class NativeInfo final { + public: + NativeInfo(napi_env env, NativeType type) + : _env(env), _type(type), _data(nullptr) {} + + std::list::const_iterator AddWeakRef(NAPIPersistent *ref) { + return _weakRefs.insert(_weakRefs.end(), ref); + } + + void RemoveWeakRef(std::list::const_iterator iter) { + _weakRefs.erase(iter); + } + + napi_env Env() const { return _env; } + + void Data(void *value) { _data = value; } + + void *Data() const { return _data; } + + NativeType Type() const { return _type; } + + static bool IsInstance(LEPUSClassID id) { return id == class_id; } + + static NativeInfo *Get(LEPUSValue val) { + return static_cast(LEPUS_GetOpaque(val, class_id)); + } + + static LEPUSClassID ClassId(napi_env env) { + static std::once_flag once_flag; + std::call_once(once_flag, [&] { LEPUS_NewClassID(&class_id); }); + auto rt = LEPUS_GetRuntime(env->ctx->ctx); + if (!LEPUS_IsRegisteredClass(rt, class_id)) { + static LEPUSClassDef def = {.class_name = "NAPIMagicNative", + .finalizer = NativeInfo::OnFinalize}; + if (LEPUS_NewClass(rt, class_id, &def) != 0) { + return 0; + } + } + + return class_id; + } + + private: + ~NativeInfo() { + // ref will remove itself when finalize, so copy is needed + for (NAPIPersistent *ref: std::vector( + std::begin(_weakRefs), std::end(_weakRefs))) { + NAPIPersistent::OnFinalize(ref); + } + } + + private: + const napi_env _env; + const NativeType _type; + + void *_data; + std::list _weakRefs; + + static LEPUSClassID class_id; + + static void OnFinalize(LEPUSRuntime *rt, LEPUSValue val) { + NativeInfo *info = static_cast(LEPUS_GetOpaque(val, class_id)); + LEPUS_SetOpaque(val, nullptr); + delete info; + } + }; + + LEPUSClassID NativeInfo::class_id = 0; + + class External { + public: + static LEPUSValue Create(napi_env env, NativeInfo **result) { + LEPUSClassID id = NativeInfo::ClassId(env); + if (!id) { + return LEPUS_ThrowInternalError(env->ctx->ctx, + "failed to create External Class"); + } + LEPUSValue object = LEPUS_NewObjectClass(env->ctx->ctx, id); + if (!LEPUS_IsException(object)) { + NativeInfo *info = new NativeInfo(env, NativeType::External); + LEPUS_SetOpaque(object, info); + *result = info; + } + return object; + } + }; + + class Wrapper { + public: + static LEPUSValue Create(napi_env env, LEPUSValue proto) { + LEPUSClassID id = NativeInfo::ClassId(env); + if (!id) { + return LEPUS_ThrowInternalError(env->ctx->ctx, + "failed to create Wrapper Class"); + } + LEPUSValue object = LEPUS_NewObjectProtoClass(env->ctx->ctx, proto, id); + if (!LEPUS_IsException(object)) { + LEPUS_SetOpaque(object, new NativeInfo(env, NativeType::Wrapper)); + } + return object; + } + }; + +// Wrapper around v8impl::Persistent that implements reference counting. + class RefBase : protected Finalizer, RefTracker { + protected: + RefBase(napi_env env, uint32_t initial_refcount, bool delete_self, + napi_finalize finalize_callback, void *finalize_data, + void *finalize_hint) + : Finalizer(env, finalize_callback, finalize_data, finalize_hint), + _refcount(initial_refcount), + _delete_self(delete_self), + _is_self_destroying(false) { + Link(finalize_callback == nullptr ? &env->ctx->reflist + : &env->ctx->finalizing_reflist); + } + + public: + static RefBase *New(napi_env env, uint32_t initial_refcount, bool delete_self, + napi_finalize finalize_callback, void *finalize_data, + void *finalize_hint) { + return new RefBase(env, initial_refcount, delete_self, finalize_callback, + finalize_data, finalize_hint); + } + + virtual ~RefBase() { Unlink(); } + + inline void *Data() { return _finalize_data; } + + // Delete is called in 2 ways. Either from the finalizer or + // from one of Unwrap or napi_delete_reference. + // + // When it is called from Unwrap or napi_delete_reference we only + // want to do the delete if the finalizer has already run or + // cannot have been queued to run (ie the reference count is > 0), + // otherwise we may crash when the finalizer does run. + // If the finalizer may have been queued and has not already run + // delay the delete until the finalizer runs by not doing the delete + // and setting _delete_self to true so that the finalizer will + // delete it when it runs. + // + // The second way this is called is from + // the finalizer and _delete_self is set. In this case we + // know we need to do the deletion so just do it. + static inline void Delete(RefBase *reference) { + if ((reference->RefCount() != 0) || (reference->_delete_self) || + (reference->_finalize_ran)) { + delete reference; + } else { + // defer until finalizer runs as + // it may alread be queued + reference->_delete_self = true; + } + } + + inline uint32_t Ref() { return ++_refcount; } + + inline uint32_t Unref() { + if (_refcount == 0) { + return 0; + } + return --_refcount; + } + + inline uint32_t RefCount() { return _refcount; } + + protected: + inline void Finalize(bool is_env_teardown = false) override { + if (is_env_teardown && RefCount() > 0) _refcount = 0; + + // There are cases where we want to avoid the reentrance of Finalize ( + // causing double-free): + // * When a wrapped object holds its own strong reference (either directly + // or indirectly) + // * (JSCore specific) when the destruction of a strong reference triggers + // garbage collection + // If we are sure this is getting deleted soon, there is no need for the + // finalizer to proceed. + if (_is_self_destroying && !is_env_teardown) { + return; + } + if (is_env_teardown) { + _is_self_destroying = true; + } + + if (_finalize_callback != nullptr) { + // This ensures that we never call the finalizer twice. + napi_finalize fini = _finalize_callback; + _finalize_callback = nullptr; + _env->ctx->CallFinalizer(fini, _finalize_data, _finalize_hint); + } + + // this is safe because if a request to delete the reference + // is made in the finalize_callback it will defer deletion + // to this block and set _delete_self to true + if (_delete_self || is_env_teardown) { + Delete(this); + } else { + _finalize_ran = true; + } + } + + private: + uint32_t _refcount; + bool _delete_self; + bool _is_self_destroying; + }; + + class Reference : public RefBase { + protected: + template + Reference(napi_env env, LEPUSValueConst value, NativeInfo *native_info, + Args &&... args) + : RefBase(env, std::forward(args)...), + _persistent(env, value, native_info, env->ctx->ctx) { + if (RefCount() == 0) { + _persistent.SetWeak(this, FinalizeCallback); + } + } + + public: + static inline Reference *New(napi_env env, LEPUSValueConst value, + NativeInfo *native_info, + uint32_t initial_refcount, bool delete_self, + napi_finalize finalize_callback = nullptr, + void *finalize_data = nullptr, + void *finalize_hint = nullptr) { + return new Reference(env, value, native_info, initial_refcount, delete_self, + finalize_callback, finalize_data, finalize_hint); + } + + void Finalize(bool is_env_teardown = false) override { + _persistent.Reset(true); + RefBase::Finalize(is_env_teardown); + } + + static inline void Delete(RefBase *reference) { + static_cast(reference)->_persistent.Reset(true); + RefBase::Delete(reference); + } + + inline uint32_t Ref() { + uint32_t refcount = RefBase::Ref(); + if (refcount == 1) { + _persistent.ClearWeak(); + } + return refcount; + } + + virtual ~Reference() { _persistent.Reset(true); } + + inline uint32_t Unref() { + uint32_t old_refcount = RefCount(); + uint32_t refcount = RefBase::Unref(); + if (old_refcount == 1 && refcount == 0) { + _persistent.SetWeak(this, FinalizeCallback); + } + return refcount; + } + + inline napi_value Get() { + return _persistent.IsEmpty() ? nullptr + : _env->ctx->CreateHandle(_persistent.Value()); + } + + private: + static void FinalizeCallback(void *data) { + Reference *r = static_cast(data); + r->_persistent.Reset(); + r->Finalize(); + } + + NAPIPersistent _persistent; + }; + + enum WrapType { + retrievable, anonymous + }; + + template + inline napi_status Wrap(napi_env env, napi_value js_object, void *native_object, + napi_finalize finalize_cb, void *finalize_hint, + napi_ref *result) { + LEPUSValueConst obj = ToJSValue(js_object); + + NativeInfo *info = NativeInfo::Get(obj); + + if (wrap_type == retrievable) { + RETURN_STATUS_IF_FALSE(env, + info != nullptr && + info->Type() == NativeType::Wrapper && + info->Data() == nullptr, + napi_invalid_arg); + } else { + // If no finalize callback is provided, we error out. + CHECK_ARG(env, finalize_cb); + } + + Reference *reference = nullptr; + if (result != nullptr) { + // The returned reference should be deleted via napi_delete_reference() + // ONLY in response to the finalize callback invocation. (If it is deleted + // before then, then the finalize callback will never be invoked.) + // Therefore a finalize callback is required when returning a reference. + CHECK_ARG(env, finalize_cb); + reference = Reference::New(env, obj, info, 0, false, finalize_cb, + native_object, finalize_hint); + *result = reinterpret_cast(reference); + } else { + // Create a self-deleting reference. + reference = + Reference::New(env, obj, info, 0, true, finalize_cb, native_object, + finalize_cb == nullptr ? nullptr : finalize_hint); + } + + if (wrap_type == retrievable) { + info->Data(reference); + } + + return napi_clear_last_error(env); + } + + enum UnwrapAction { + KeepWrap, RemoveWrap + }; + + inline static napi_status Unwrap(napi_env env, napi_value js_object, + void **result, UnwrapAction action) { + if (action == KeepWrap) { + CHECK_ARG(env, result); + } + + LEPUSValue obj = ToJSValue(js_object); + NativeInfo *info = NativeInfo::Get(obj); + + if (!info || info->Type() != qjsimpl::NativeType::Wrapper) { + if (result) { + *result = nullptr; + } + return napi_clear_last_error(env); + } + + Reference *reference = static_cast(info->Data()); + + if (result) { + *result = reference->Data(); + } + + if (action == RemoveWrap) { + info->Data(nullptr); + Reference::Delete(reference); + } + + return napi_clear_last_error(env); + } + + inline static Atom qjsAtomFromPropertyDescriptor( + napi_env env, const napi_property_descriptor &p) { + if (p.utf8name != nullptr) { + return Atom(env, env->ctx->ctx, p.utf8name); + } else { + return Atom(env, env->ctx->ctx, ToJSValue(p.name)); + } + } + + inline static uint8_t qjsFlagFromPropertyDescriptor( + napi_property_attributes attributes) { + uint8_t flags = 0; + if (attributes & napi_writable) { + flags |= LEPUS_PROP_WRITABLE; + } + if (attributes & napi_enumerable) { + flags |= LEPUS_PROP_ENUMERABLE; + } + if (attributes & napi_configurable) { + flags |= LEPUS_PROP_CONFIGURABLE; + } + return flags; + } + + inline NAPIPersistent::NAPIPersistent(napi_env env, LEPUSValueConst value, + NativeInfo *native_info, + LEPUSContext *ctx, bool is_weak) + : PersistentBase( + PersistentBase::New(LEPUS_GetRuntime(ctx), value, is_weak)), + _env(env), + _empty(false), + _value(JS_DupValue_Comp(env->ctx->ctx, value)), + _native_info(native_info), + _ctx(ctx) {} + + inline NAPIPersistent::NAPIPersistent(napi_env env, JSAtom atom, + NativeInfo *native_info, + LEPUSContext *ctx, bool is_weak) + : PersistentBase(PersistentBase::New(LEPUS_GetRuntime(ctx), + LEPUS_MKVAL(LEPUS_TAG_Atom, (int) atom), + is_weak)), + _env(env), + _empty(false), + _value(LEPUS_MKVAL(LEPUS_TAG_Atom, (int) atom)), + _native_info(native_info), + _ctx(ctx) {} + + void NAPIPersistent::Reset(bool for_gc) { + if (_empty) { + return; + } + if (_ctx != nullptr && LEPUS_IsGCMode(_ctx)) { + PersistentBase::Reset(_ctx); + _env = nullptr; + _empty = true; + _native_info = nullptr; + } else if (!for_gc) { + if (_weak_info) { + ResetWeakInfo(); + } else { + JS_FreeValue_Comp(_env->ctx->ctx, _value); + } + _env = nullptr; + _empty = true; + _native_info = nullptr; + } + } + + void NAPIPersistent::Reset(napi_env env, LEPUSValueConst value, + NativeInfo *native_info, LEPUSContext *ctx, + bool for_gc) { + _ctx = ctx; + if (_ctx != nullptr && LEPUS_IsGCMode(_ctx)) { // gc + PersistentBase::Reset(_ctx, value, false); + _empty = false; + _env = env; + _value = value; + _native_info = native_info; + } else if (!for_gc) { + Reset(); + _empty = false; + _env = env; + _value = JS_DupValue_Comp(env->ctx->ctx, value); + _native_info = native_info; + } + } + + void NAPIPersistent::Reset(napi_env env, LEPUSContext *ctx, JSAtom atom) { + _env = env; + _ctx = ctx; + if (_ctx != nullptr && LEPUS_IsGCMode(_ctx)) { + PersistentBase::Reset(_ctx, LEPUS_MKVAL(LEPUS_TAG_Atom, (int) atom), false); + _empty = false; + } + } + + void NAPIPersistent::SetWeak(void *data, void (*cb)(void *)) { + if (_ctx != nullptr && LEPUS_IsGCMode(_ctx)) { + SetGlobalWeak(LEPUS_GetRuntime(_ctx), this->val_, data, cb); + } else { + assert(!_empty); + if (_weak_info) { + _weak_info->cb_arg = data; + _weak_info->cb = cb; + } else { + _weak_info.reset( + new WeakInfo{_get_native_info()->AddWeakRef(this), cb, data}); + JS_FreeValue_Comp(_env->ctx->ctx, _value); + } + } + } + + void NAPIPersistent::ClearWeak() { + if (_ctx != nullptr && LEPUS_IsGCMode(_ctx)) { + ClearGlobalWeak(LEPUS_GetRuntime(_ctx), this->val_); + } else { + JS_DupValue_Comp(_env->ctx->ctx, _value); + ResetWeakInfo(); + } + } + + LEPUSValue NAPIPersistent::Value() const { + if (_ctx != nullptr && LEPUS_IsGCMode(_ctx)) { + return Get(); + } else { + return JS_DupValue_Comp(_env->ctx->ctx, _value); + } + } + + void NAPIPersistent::OnFinalize(NAPIPersistent *ref) { + auto cb = ref->_weak_info->cb; + auto cb_arg = ref->_weak_info->cb_arg; + ref->Reset(); + cb(cb_arg); + } + + void NAPIPersistent::ResetWeakInfo() { + assert(!_empty); + _get_native_info()->RemoveWeakRef(_weak_info->weak_iter); + _weak_info.reset(); + } + + NativeInfo *NAPIPersistent::_get_native_info() { + assert(!_empty); + if (!_native_info) { + LEPUSValue finalizer = + LEPUS_GetProperty(_env->ctx->ctx, _value, _env->ctx->PROP_FINALIZER); + assert(!LEPUS_IsException(finalizer)); + if (LEPUS_IsUndefined(finalizer)) { + NativeInfo *info; + finalizer = External::Create(_env, &info); + assert(!LEPUS_IsException(finalizer)); + int ret = LEPUS_DefinePropertyValue( + _env->ctx->ctx, _value, _env->ctx->PROP_FINALIZER, finalizer, 0); + (void) ret; + assert(ret != -1); + _native_info = info; + } else { + _native_info = qjsimpl::NativeInfo::Get(finalizer); + JS_FreeValue_Comp(_env->ctx->ctx, finalizer); + } + } + return _native_info; + } + +} // namespace qjsimpl + +namespace { + static inline LEPUSValue CallJSFunctionWithNAPI(napi_env env, napi_callback cb, + napi_callback_info cbinfo) { + napi_value result; + std::unique_ptr exception; + env->ctx->CallIntoModule([&](napi_env env) { result = cb(env, cbinfo); }, + [&](napi_env env, LEPUSValue exc) { + exception.reset(new LEPUSValue(exc)); + }); + + if (exception) { + env->ctx->CreateHandle(*exception, true); + return LEPUS_Throw(env->ctx->ctx, *exception); + } + + return result ? JS_DupValue_Comp(env->ctx->ctx, ToJSValue(result)) + : LEPUS_UNDEFINED; + } +} // namespace + +namespace qjsimpl { + typedef struct FunctionDataWrapper { + void *data; + } FunctionDataWrapper; +} + +napi_status napi_create_function(napi_env env, const char *utf8name, + size_t length, napi_callback cb, + void *callback_data, napi_value *result) { + LEPUSContext *ctx = env->ctx->ctx; + + auto dataWrapper = new qjsimpl::FunctionDataWrapper(); + dataWrapper->data = callback_data; + + LEPUSValue data[] = { + LEPUS_MKPTR(LEPUS_TAG_LEPUS_CPOINTER, env), + LEPUS_MKPTR(LEPUS_TAG_LEPUS_CPOINTER, reinterpret_cast(cb)), + LEPUS_MKPTR(LEPUS_TAG_LEPUS_CPOINTER, dataWrapper)}; + LEPUSValue fun = LEPUS_NewCFunctionData( + ctx, + [](LEPUSContext *ctx, LEPUSValueConst this_val, int argc, + LEPUSValueConst *argv, int magic, + LEPUSValue *func_data) -> LEPUSValue { + napi_env env = + reinterpret_cast(LEPUS_VALUE_GET_CPOINTER(func_data[0])); + napi_callback cb = reinterpret_cast( + LEPUS_VALUE_GET_CPOINTER(func_data[1])); + auto dataWrapper = reinterpret_cast LEPUS_VALUE_GET_CPOINTER( + func_data[2]); + + napi_clear_last_error(env); + + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + + ArgsConverter args(argc, argv); + + napi_callback_info__ cbinfo{}; + cbinfo.thisArg = ToNapi(&this_val); + cbinfo.newTarget = nullptr; + cbinfo.argc = argc; + cbinfo.argv = args; + cbinfo.data = dataWrapper->data; + + return CallJSFunctionWithNAPI(env, cb, &cbinfo); + }, + 0, 0, 3, data); + + napi_value external; + napi_create_external(env, dataWrapper, [](napi_env, void *data, void *hint) { + if (data) { + auto dataWrapper = reinterpret_cast(data); + delete dataWrapper; + } + }, dataWrapper, &external); + napi_set_named_property(env, ToNapi(&fun), "__qjs::finalizer__", external); + + CHECK_QJS(env, !LEPUS_IsException(fun)); + + *result = env->ctx->CreateHandle(fun); + + if (utf8name) { + // ignore error + LEPUSValue str = LEPUS_NewString(ctx, utf8name); + env->ctx->CreateHandle(str, true); + LEPUS_DefinePropertyValue(ctx, fun, env->ctx->PROP_NAME, str, + LEPUS_PROP_CONFIGURABLE); + } + + return napi_clear_last_error(env); +} + +static __attribute__((unused)) std::string GetExceptionMessage( + LEPUSContext *ctx, LEPUSValueConst exception_val) { + LEPUSValue val; + const char *stack; + const char *message = LEPUS_ToCString(ctx, exception_val); + std::string ret = "quickjs: "; + if (message) { + ret += message; + ret += "\n"; + JS_FreeCString_Comp(ctx, message); + } + + bool is_error = LEPUS_IsError(ctx, exception_val); + if (is_error) { + val = LEPUS_GetPropertyStr(ctx, exception_val, "stack"); + if (!LEPUS_IsUndefined(val)) { + stack = LEPUS_ToCString(ctx, val); + ret += stack; + JS_FreeCString_Comp(ctx, stack); + } + JS_FreeValue_Comp(ctx, val); + } + return ret; +} + +napi_status napi_define_class(napi_env env, const char *utf8name, size_t length, + napi_callback cb, void *data, + size_t property_count, + const napi_property_descriptor *properties, napi_value *result) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + + LEPUSContext *ctx = env->ctx->ctx; + + qjsimpl::Value proto(ctx, LEPUS_NewObject(ctx)); + + CHECK_QJS(env, !LEPUS_IsException(proto)); + + struct ClassData { + napi_callback cb; + void *data; + LEPUSValue proto; + qjsimpl::NAPIPersistent p_proto; + + ~ClassData() { p_proto.Reset(true); } + }; + + qjsimpl::NativeInfo *ctor_info; + qjsimpl::Value ctor_magic(ctx, qjsimpl::External::Create(env, &ctor_info)); + CHECK_QJS(env, !LEPUS_IsException(ctor_magic)); + + ClassData *ctor_magic_data = + new ClassData{.cb = cb, .data = data, .proto = proto.dup()}; + if (LEPUS_IsGCMode(env->ctx->ctx)) { + ctor_magic_data->p_proto.Reset(env, ctor_magic_data->proto, nullptr, + env->ctx->ctx, true); + } + ctor_info->Data(ctor_magic_data); + qjsimpl::Reference::New( + env, ctor_magic, ctor_info, 0, true, + [](napi_env env, void *data, void *hint) { + ClassData *ctor_magic_data = static_cast(data); + JS_FreeValue_Comp(env->ctx->ctx, ctor_magic_data->proto); + delete ctor_magic_data; + static_cast(hint)->Data(nullptr); + }, + ctor_magic_data, ctor_info); + LEPUSValue cfunction = LEPUS_NewCFunctionMagic( + ctx, + [](LEPUSContext *ctx, LEPUSValueConst new_target, int argc, + LEPUSValueConst *argv, int magic) -> LEPUSValue { + JSAtom prop_ctor_magic = LEPUS_NewAtom(ctx, "@#ctor@#"); + LEPUSValue ctor_magic = + LEPUS_GetProperty(ctx, new_target, prop_ctor_magic); + JS_FreeAtom_Comp(ctx, prop_ctor_magic); + if (LEPUS_IsException(ctor_magic) || LEPUS_IsUndefined(ctor_magic)) { + if (LEPUS_IsObject(new_target)) { + LOGI("new_target is an object"); + } + LOGI("new_target ptr is " + << LEPUS_VALUE_GET_PTR(new_target) << ", prop_ctor_magic is " + << prop_ctor_magic << ", function magic is " << magic + << ", exception message: " + << GetExceptionMessage(ctx, ctor_magic)); + return ctor_magic; + } + qjsimpl::NativeInfo *info = qjsimpl::NativeInfo::Get(ctor_magic); + JS_FreeValue_Comp(ctx, ctor_magic); + if (!(info != nullptr && + info->Type() == qjsimpl::NativeType::External && info->Data())) { + LOGI("ctor_magic native_info error return undefined, info is " + << info); + return LEPUS_UNDEFINED; + } + napi_env env = info->Env(); + ClassData *class_data = static_cast(info->Data()); + LEPUSValue this_val = qjsimpl::Wrapper::Create(env, class_data->proto); + + if (LEPUS_IsException(this_val)) { + LOGI("create Wrapper return exception"); + return this_val; + } + napi_clear_last_error(env); + + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + + ArgsConverter args(argc, argv); + + napi_callback_info__ cbinfo{}; + cbinfo.thisArg = env->ctx->CreateHandle(this_val); + cbinfo.newTarget = ToNapi(&new_target); + cbinfo.argc = argc; + cbinfo.argv = args; + cbinfo.data = class_data->data; + + auto result = CallJSFunctionWithNAPI(env, class_data->cb, &cbinfo); + if (LEPUS_IsUndefined(result)) { + LOGI("napi callback return undefined"); + } + return result; + }, + utf8name, 0, LEPUS_CFUNC_constructor_magic, env->ctx->PROP_CTOR_MAGIC); + + qjsimpl::Value constructor(ctx, cfunction); + + auto ctor_handle = env->ctx->CreateHandle(cfunction, true); + + if (LEPUS_IsException(constructor)) { + napi_status status = + napi_set_exception(env, LEPUS_GetException(env->ctx->ctx)); + LOGI(GetExceptionMessage(env->ctx->ctx, *env->ctx->last_exception)); + return status; + } + + if (LEPUS_DefinePropertyValue(ctx, constructor, env->ctx->PROP_CTOR_MAGIC, + ctor_magic.move(), 0) == -1) { + napi_status status = + napi_set_exception(env, LEPUS_GetException(env->ctx->ctx)); + LOGI(GetExceptionMessage(env->ctx->ctx, *env->ctx->last_exception)); + return status; + } + + + CHECK_QJS( + env, LEPUS_DefinePropertyValue(ctx, constructor, env->ctx->PROP_PROTOTYPE, + proto.dup(), 0) != -1); + CHECK_QJS(env, LEPUS_DefinePropertyValue( + ctx, proto, env->ctx->PROP_CONSTRUCTOR, constructor.dup(), + LEPUS_PROP_WRITABLE | LEPUS_PROP_CONFIGURABLE) != -1); + + int instancePropertyCount{0}; + int staticPropertyCount{0}; + for (size_t i = 0; i < property_count; i++) { + if ((properties[i].attributes & napi_static) != 0) { + staticPropertyCount++; + } else { + instancePropertyCount++; + } + } + + std::vector staticDescriptors{}; + std::vector instanceDescriptors{}; + staticDescriptors.reserve(staticPropertyCount); + instanceDescriptors.reserve(instancePropertyCount); + + for (size_t i = 0; i < property_count; i++) { + if ((properties[i].attributes & napi_static) != 0) { + staticDescriptors.push_back(properties[i]); + } else { + instanceDescriptors.push_back(properties[i]); + } + } + + if (staticPropertyCount > 0) { + LEPUSValue ctor_val = constructor; + + CHECK_NAPI(napi_define_properties(env, ToNapi(&ctor_val), + staticDescriptors.size(), + staticDescriptors.data())); + } + + if (instancePropertyCount > 0) { + LEPUSValue proto_val = proto; + + CHECK_NAPI(napi_define_properties(env, ToNapi(&proto_val), + instanceDescriptors.size(), + instanceDescriptors.data())); + } + + + *result = scope.Escape(ctor_handle); + + return napi_clear_last_error(env); +} + +napi_status napi_get_property_names_gc(napi_env env, napi_value object, + napi_value *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSPropertyEnum *props = nullptr; + HandleScope func_scope(ctx, &props, HANDLE_TYPE_HEAP_OBJ); + uint32_t props_length; + CHECK_QJS(env, LEPUS_GetOwnPropertyNames( + ctx, &props, &props_length, ToJSValue(object), + LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK | + LEPUS_GPN_ENUM_ONLY | LEPUS_PROP_THROW) != -1); + + std::vector values; + values.reserve(props_length); + for (uint32_t i = 0; i < props_length; i++) { + values.emplace_back(LEPUS_AtomToValue(ctx, props[i].atom)); + func_scope.PushHandle(&values[i], HANDLE_TYPE_LEPUS_VALUE); + } + LEPUSValue arr = LEPUS_NewArrayWithValue(ctx, props_length, values.data()); + + CHECK_QJS(env, !LEPUS_IsException(arr)); + *result = env->ctx->CreateHandle(arr); + return napi_clear_last_error(env); +} + +napi_status napi_get_property_names(napi_env env, + napi_value object, + napi_value *result) { + LEPUSContext *ctx = env->ctx->ctx; + if (LEPUS_IsGCMode(ctx)) { + return napi_get_property_names_gc(env, object, result); + } + LEPUSPropertyEnum *props = nullptr; + uint32_t props_length; + CHECK_QJS(env, LEPUS_GetOwnPropertyNames( + ctx, &props, &props_length, ToJSValue(object), + LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK | + LEPUS_GPN_ENUM_ONLY | LEPUS_PROP_THROW) != -1); + + std::vector values; + values.reserve(props_length); + for (uint32_t i = 0; i < props_length; i++) { + values.emplace_back(LEPUS_AtomToValue(ctx, props[i].atom)); + JS_FreeAtom_Comp(ctx, props[i].atom); + } + js_free_comp(ctx, props); + LEPUSValue arr = LEPUS_NewArrayWithValue(ctx, props_length, values.data()); + for (LEPUSValue v: values) { + JS_FreeValue_Comp(ctx, v); + } + + CHECK_QJS(env, !LEPUS_IsException(arr)); + *result = env->ctx->CreateHandle(arr); + return napi_clear_last_error(env); +} + +napi_status napi_get_all_property_names(napi_env env, + napi_value object, + napi_key_collection_mode key_mode, + napi_key_filter key_filter, + napi_key_conversion key_conversion, + napi_value *result) { + LEPUSContext *ctx = env->ctx->ctx; + if (LEPUS_IsGCMode(ctx)) { + return napi_get_property_names_gc(env, object, result); + } + LEPUSPropertyEnum *props = nullptr; + uint32_t props_length; + CHECK_QJS(env, LEPUS_GetOwnPropertyNames( + ctx, &props, &props_length, ToJSValue(object), + LEPUS_GPN_STRING_MASK | LEPUS_GPN_SYMBOL_MASK | + LEPUS_GPN_ENUM_ONLY | LEPUS_PROP_THROW) != -1); + + std::vector values; + values.reserve(props_length); + for (uint32_t i = 0; i < props_length; i++) { + values.emplace_back(LEPUS_AtomToValue(ctx, props[i].atom)); + JS_FreeAtom_Comp(ctx, props[i].atom); + } + js_free_comp(ctx, props); + LEPUSValue arr = LEPUS_NewArrayWithValue(ctx, props_length, values.data()); + for (LEPUSValue v: values) { + JS_FreeValue_Comp(ctx, v); + } + + CHECK_QJS(env, !LEPUS_IsException(arr)); + *result = env->ctx->CreateHandle(arr); + return napi_clear_last_error(env); +} + +napi_status napi_set_property(napi_env env, napi_value object, napi_value key, + napi_value value) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + qjsimpl::Atom prop_atom(env, ctx, ToJSValue(key)); + CHECK_QJS(env, prop_atom.IsValid()); + int result = LEPUS_SetProperty(ctx, obj, prop_atom, + JS_DupValue_Comp(ctx, ToJSValue(value))); + CHECK_QJS(env, result != -1); + return napi_clear_last_error(env); +} + +napi_status napi_has_property(napi_env env, napi_value object, napi_value key, + bool *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + qjsimpl::Atom prop_atom(env, ctx, ToJSValue(key)); + CHECK_QJS(env, prop_atom.IsValid()); + int result_has = LEPUS_HasProperty(ctx, obj, prop_atom); + CHECK_QJS(env, result_has != -1); + *result = result_has; + return napi_clear_last_error(env); +} + +napi_status napi_get_property(napi_env env, napi_value object, napi_value key, + napi_value *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + qjsimpl::Atom prop_atom(env, ctx, ToJSValue(key)); + CHECK_QJS(env, prop_atom.IsValid()); + LEPUSValue val = LEPUS_GetProperty(ctx, obj, prop_atom); + CHECK_QJS(env, !LEPUS_IsException(val)); + *result = env->ctx->CreateHandle(val); + return napi_clear_last_error(env); +} + +napi_status napi_delete_property(napi_env env, napi_value object, + napi_value key, bool *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + qjsimpl::Atom prop_atom(env, ctx, ToJSValue(key)); + CHECK_QJS(env, prop_atom.IsValid()); + int result_delete = + LEPUS_DeleteProperty(ctx, obj, prop_atom, LEPUS_PROP_THROW); + CHECK_QJS(env, result_delete != -1); + if (result) { + *result = result_delete; + } + return napi_clear_last_error(env); +} + +napi_status napi_has_own_property(napi_env env, napi_value object, + napi_value key, bool *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + qjsimpl::Atom prop_atom(env, ctx, ToJSValue(key)); + CHECK_QJS(env, prop_atom.IsValid()); + int result_has = LEPUS_GetOwnProperty(ctx, nullptr, obj, prop_atom); + CHECK_QJS(env, result_has != -1); + *result = result_has; + return napi_clear_last_error(env); +} + +napi_status napi_set_named_property(napi_env env, napi_value object, + const char *utf8name, napi_value value) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + qjsimpl::Atom prop_atom(env, ctx, LEPUS_NewAtom(ctx, utf8name)); + CHECK_QJS(env, prop_atom.IsValid()); + int result = LEPUS_SetProperty(ctx, obj, prop_atom, + JS_DupValue_Comp(ctx, ToJSValue(value))); + CHECK_QJS(env, result != -1); + return napi_clear_last_error(env); +} + +napi_status napi_has_named_property(napi_env env, napi_value object, + const char *utf8name, bool *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + qjsimpl::Atom prop_atom(env, ctx, utf8name); + CHECK_QJS(env, prop_atom.IsValid()); + int result_has = LEPUS_HasProperty(ctx, obj, prop_atom); + CHECK_QJS(env, result_has != -1); + *result = result_has; + return napi_clear_last_error(env); +} + +napi_status napi_get_named_property(napi_env env, napi_value object, + const char *utf8name, napi_value *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + qjsimpl::Atom prop_atom(env, ctx, utf8name); + CHECK_QJS(env, prop_atom.IsValid()); + LEPUSValue val = LEPUS_GetProperty(ctx, obj, prop_atom); + CHECK_QJS(env, !LEPUS_IsException(val)); + *result = env->ctx->CreateHandle(val); + return napi_clear_last_error(env); +} + +napi_status napi_set_element(napi_env env, napi_value object, uint32_t index, + napi_value value) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + int result = LEPUS_SetPropertyUint32(ctx, obj, index, + JS_DupValue_Comp(ctx, ToJSValue(value))); + CHECK_QJS(env, result != -1); + return napi_clear_last_error(env); +} + +napi_status napi_has_element(napi_env env, napi_value object, uint32_t index, + bool *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + LEPUSValue val = LEPUS_GetPropertyUint32(ctx, obj, index); + CHECK_QJS(env, !LEPUS_IsException(val)); + *result = !LEPUS_IsUndefined(val); + JS_FreeValue_Comp(ctx, val); + return napi_clear_last_error(env); +} + +napi_status napi_get_element(napi_env env, napi_value object, uint32_t index, + napi_value *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + LEPUSValue val = LEPUS_GetPropertyUint32(ctx, obj, index); + + CHECK_QJS(env, !LEPUS_IsException(val)); + *result = env->ctx->CreateHandle(val); + return napi_clear_last_error(env); +} + +napi_status napi_delete_element(napi_env env, napi_value object, uint32_t index, + bool *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + qjsimpl::Atom prop_atom(env, ctx, LEPUS_NewAtomUInt32(ctx, index)); + CHECK_QJS(env, prop_atom.IsValid()); + int result_delete = + LEPUS_DeleteProperty(ctx, obj, prop_atom, LEPUS_PROP_THROW); + CHECK_QJS(env, result_delete != -1); + *result = result_delete; + return napi_clear_last_error(env); +} + +napi_status napi_define_properties(napi_env env, napi_value object, + size_t property_count, + const napi_property_descriptor *properties) { + if (property_count > 0) { + CHECK_ARG(env, properties); + } + + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue obj = ToJSValue(object); + + for (size_t i = 0; i < property_count; i++) { + const napi_property_descriptor &p = properties[i]; + qjsimpl::Atom prop_atom = qjsimpl::qjsAtomFromPropertyDescriptor(env, p); + CHECK_QJS(env, prop_atom.IsValid()); + uint8_t flags = qjsimpl::qjsFlagFromPropertyDescriptor(p.attributes); + if (p.getter != nullptr || p.setter != nullptr) { + LEPUSValue getter = LEPUS_UNDEFINED; + char name_buf[128]; + memset(name_buf, 0, sizeof(name_buf)); + if (p.getter) { + napi_value napi_getter; + if (p.utf8name) { + snprintf(name_buf, sizeof(name_buf), "get %s", p.utf8name); + } + CHECK_NAPI(napi_create_function(env, name_buf, NAPI_AUTO_LENGTH, + p.getter, p.data, &napi_getter)); + getter = JS_DupValue_Comp(ctx, ToJSValue(napi_getter)); + } + LEPUSValue setter = LEPUS_UNDEFINED; + if (p.setter) { + napi_value napi_setter; + if (p.utf8name) { + snprintf(name_buf, sizeof(name_buf), "set %s", p.utf8name); + } + CHECK_NAPI(napi_create_function(env, name_buf, NAPI_AUTO_LENGTH, + p.setter, p.data, &napi_setter)); + setter = JS_DupValue_Comp(ctx, ToJSValue(napi_setter)); + } + CHECK_QJS(env, LEPUS_DefinePropertyGetSet(ctx, obj, prop_atom, getter, + setter, flags) != -1); + } else if (p.method != nullptr) { + napi_value method; + CHECK_NAPI(napi_create_function(env, p.utf8name, NAPI_AUTO_LENGTH, + p.method, p.data, &method)); + CHECK_QJS(env, + LEPUS_DefinePropertyValue( + ctx, obj, prop_atom, + JS_DupValue_Comp(ctx, ToJSValue(method)), flags) != -1); + } else { + LEPUSValue value = JS_DupValue_Comp(ctx, ToJSValue(p.value)); + CHECK_QJS(env, LEPUS_DefinePropertyValue(ctx, obj, prop_atom, value, + flags) != -1); + } + } + + return napi_clear_last_error(env); +} + +napi_status napi_is_array(napi_env env, napi_value value, bool *result) { + int result_is = LEPUS_IsArray(env->ctx->ctx, ToJSValue(value)); + CHECK_QJS(env, result_is != -1); + *result = result_is; + return napi_clear_last_error(env); +} + +napi_status napi_get_array_length(napi_env env, napi_value value, + uint32_t *result) { + LEPUSContext *ctx = env->ctx->ctx; + LEPUSValue v = + LEPUS_GetProperty(ctx, ToJSValue(value), env->ctx->PROP_LENGTH); + CHECK_QJS(env, !LEPUS_IsException(v)); + int result_toint = LEPUS_ToUint32(ctx, result, v); + JS_FreeValue_Comp(ctx, v); + CHECK_QJS(env, result_toint != -1); + return napi_clear_last_error(env); +} + +napi_status napi_equals(napi_env env, napi_value lhs, napi_value rhs, + bool *result) { + LEPUSValue a = ToJSValue(lhs); + LEPUSValue b = ToJSValue(rhs); + LEPUSContext *ctx = env->ctx->ctx; + *result = LEPUS_SameValue(ctx, a, b); + + return napi_clear_last_error(env); +} + +napi_status napi_strict_equals(napi_env env, napi_value lhs, napi_value rhs, + bool *result) { + LEPUSValue a = ToJSValue(lhs); + LEPUSValue b = ToJSValue(rhs); + LEPUSContext *ctx = env->ctx->ctx; + *result = + LEPUS_StrictEq(ctx, JS_DupValue_Comp(ctx, a), JS_DupValue_Comp(ctx, b)); + return napi_clear_last_error(env); +} + +napi_status napi_get_prototype(napi_env env, napi_value object, + napi_value *result) { + LEPUSValueConst prototype = + LEPUS_GetPrototype(env->ctx->ctx, ToJSValue(object)); + CHECK_QJS(env, !LEPUS_IsException(prototype)); + *result = env->ctx->CreateHandle(JS_DupValue_Comp(env->ctx->ctx, prototype)); + return napi_clear_last_error(env); +} + +napi_status napi_create_object(napi_env env, napi_value *result) { + LEPUSValue object = LEPUS_NewObject(env->ctx->ctx); + CHECK_QJS(env, !LEPUS_IsException(object)); + *result = env->ctx->CreateHandle(object); + return napi_clear_last_error(env); +} + +napi_status napi_create_array(napi_env env, napi_value *result) { + LEPUSValue array = LEPUS_NewArray(env->ctx->ctx); + CHECK_QJS(env, !LEPUS_IsException(array)); + *result = env->ctx->CreateHandle(array); + return napi_clear_last_error(env); +} + +napi_status napi_create_array_with_length(napi_env env, size_t length, + napi_value *result) { + LEPUSValue array = LEPUS_NewArray(env->ctx->ctx); + CHECK_QJS(env, !LEPUS_IsException(array)); + + *result = env->ctx->CreateHandle(array); + CHECK_QJS(env, + LEPUS_SetProperty(env->ctx->ctx, array, env->ctx->PROP_LENGTH, + LEPUS_NewInt64(env->ctx->ctx, length)) != -1); + + return napi_clear_last_error(env); +} + +napi_status napi_create_string_latin1(napi_env env, const char *str, + size_t length, napi_value *result) { + *result = env->ctx->CreateHandle( + length == NAPI_AUTO_LENGTH + ? LEPUS_NewString(env->ctx->ctx, str) + : LEPUS_NewStringLen(env->ctx->ctx, str, length)); + return napi_clear_last_error(env); +} + +napi_status napi_create_string_utf8(napi_env env, const char *str, + size_t length, napi_value *result) { + *result = env->ctx->CreateHandle( + length == NAPI_AUTO_LENGTH + ? LEPUS_NewString(env->ctx->ctx, str) + : LEPUS_NewStringLen(env->ctx->ctx, str, length)); + return napi_clear_last_error(env); +} + +napi_status napi_create_string_utf16(napi_env env, const char16_t *str, + size_t length, napi_value *result) { + *result = env->ctx->CreateHandle(LEPUS_NewWString( + env->ctx->ctx, reinterpret_cast(str), + length == NAPI_AUTO_LENGTH ? std::char_traits::length(str) + : length)); + return napi_clear_last_error(env); +} + +napi_status napi_create_double(napi_env env, double value, napi_value *result) { + *result = env->ctx->CreateHandle(LEPUS_NewFloat64(env->ctx->ctx, value)); + return napi_clear_last_error(env); +} + +napi_status napi_create_int32(napi_env env, int32_t value, napi_value *result) { + *result = env->ctx->CreateHandle(LEPUS_NewInt32(env->ctx->ctx, value)); + return napi_clear_last_error(env); +} + +napi_status napi_create_uint32(napi_env env, uint32_t value, + napi_value *result) { + *result = env->ctx->CreateHandle(LEPUS_NewInt64(env->ctx->ctx, value)); + return napi_clear_last_error(env); +} + +napi_status napi_create_int64(napi_env env, int64_t value, napi_value *result) { + *result = env->ctx->CreateHandle(LEPUS_NewInt64(env->ctx->ctx, value)); + return napi_clear_last_error(env); +} + +napi_status napi_get_boolean(napi_env env, bool value, napi_value *result) { + *result = env->ctx->CreateHandle(LEPUS_NewBool(env->ctx->ctx, value)); + return napi_clear_last_error(env); +} + +napi_status napi_create_symbol(napi_env env, napi_value description, + napi_value *result) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + napi_value global{}, symbol_func{}, symbol_value{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Symbol", &symbol_func)); + CHECK_NAPI(napi_call_function(env, global, symbol_func, 1, &description, + &symbol_value)); + *result = scope.Escape(symbol_value); + return napi_clear_last_error(env); +} + +napi_status napi_create_error(napi_env env, napi_value code, napi_value msg, + napi_value *result) { + LEPUSValue error = LEPUS_NewError(env->ctx->ctx); + *result = env->ctx->CreateHandle(error); + + CHECK_NAPI(napi_set_error_msg_code(env, ToNapi(&error), code, msg, nullptr)); + + return napi_clear_last_error(env); +} + +napi_status napi_create_type_error(napi_env env, napi_value code, + napi_value msg, napi_value *result) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + + napi_value global{}, error_ctor{}, error{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "TypeError", &error_ctor)); + CHECK_NAPI(napi_new_instance(env, error_ctor, 1, &msg, &error)); + CHECK_NAPI(napi_set_error_msg_code(env, error, code, msg, nullptr)); + + *result = scope.Escape(error); + return napi_clear_last_error(env); +} + +napi_status napi_create_range_error(napi_env env, napi_value code, + napi_value msg, napi_value *result) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + + napi_value global{}, error_ctor{}, error{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "RangeError", &error_ctor)); + CHECK_NAPI(napi_new_instance(env, error_ctor, 1, &msg, &error)); + CHECK_NAPI(napi_set_error_msg_code(env, error, code, msg, nullptr)); + + *result = scope.Escape(error); + return napi_clear_last_error(env); +} + +napi_status napi_typeof(napi_env env, napi_value value, + napi_valuetype *result) { + LEPUSValue v = ToJSValue(value); + int64_t tag = LEPUS_VALUE_GET_NORM_TAG(v); + + switch (tag) { + case LEPUS_TAG_INT: + case LEPUS_TAG_FLOAT64: + *result = napi_number; + break; + case LEPUS_TAG_BIG_INT: + *result = napi_bigint; + break; + case LEPUS_TAG_STRING: + *result = napi_string; + break; + case LEPUS_TAG_SEPARABLE_STRING: + *result = napi_string; + break; + case LEPUS_TAG_SYMBOL: + *result = napi_symbol; + break; + case LEPUS_TAG_NULL: + *result = napi_null; + break; + case LEPUS_TAG_UNDEFINED: + *result = napi_undefined; + break; + case LEPUS_TAG_BOOL: + *result = napi_boolean; + break; + case LEPUS_TAG_OBJECT: + if (LEPUS_IsFunction(env->ctx->ctx, v)) { + *result = napi_function; + } else { + qjsimpl::NativeInfo *info = qjsimpl::NativeInfo::Get(v); + if (info && info->Type() == qjsimpl::NativeType::External) { + *result = napi_external; + } else { + *result = napi_object; + } + } + break; + default: + // Should not get here unless QuickJS has added some new kind of value. + return napi_set_last_error(env, napi_invalid_arg); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_undefined(napi_env env, napi_value *result) { + *result = ToNapi(&(env->ctx->V_UNDEFINED)); + return napi_clear_last_error(env); +} + +napi_status napi_get_null(napi_env env, napi_value *result) { + *result = ToNapi(&(env->ctx->V_NULL)); + return napi_clear_last_error(env); +} + +napi_status napi_get_cb_info( + napi_env env, // [in] NAPI environment handle + napi_callback_info cbinfo, // [in] Opaque callback-info handle + size_t *argc, // [in-out] Specifies the size of the provided argv array + // and receives the actual count of args. + napi_value *argv, // [out] Array of values + napi_value *this_arg, // [out] Receives the JS 'this' arg for the call + void **data) { // [out] Receives the data pointer for the callback. + if (argv != nullptr) { + CHECK_ARG(env, argc); + + size_t i{0}; + size_t min{std::min(*argc, static_cast(cbinfo->argc))}; + + for (; i < min; i++) { + argv[i] = cbinfo->argv[i]; + } + + if (i < *argc) { + for (; i < *argc; i++) { + argv[i] = ToNapi(&(env->ctx->V_UNDEFINED)); + } + } + } + + if (argc != nullptr) { + *argc = cbinfo->argc; + } + + if (this_arg != nullptr) { + *this_arg = cbinfo->thisArg; + } + + if (data != nullptr) { + *data = cbinfo->data; + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_new_target(napi_env env, napi_callback_info cbinfo, + napi_value *result) { + *result = cbinfo->newTarget; + return napi_clear_last_error(env); +} + +namespace { + inline LEPUSValueConst ToJSValue(napi_value *v) { return ToJSValue(*v); } +} // namespace + +napi_status napi_call_function(napi_env env, napi_value recv, napi_value func, + size_t argc, const napi_value *argv, + napi_value *result) { + if (argc > 0) { + CHECK_ARG(env, argv); + } + + LEPUSContext *ctx = env->ctx->ctx; + + js_enter(env); + + ArgsConverter args( + argc, const_cast(argv)); + + LEPUSValue call_result = + LEPUS_Call(ctx, ToJSValue(func), recv ? ToJSValue(recv) : LEPUS_UNDEFINED, + argc, args); + + + CHECK_QJS(env, !LEPUS_IsException(call_result)); + + if (result) { + *result = env->ctx->CreateHandle(call_result); + } else { + if (!LEPUS_IsGCMode(ctx)) { + LEPUS_FreeValue(ctx, call_result); + } + } + + js_exit(env); + auto rt = LEPUS_GetRuntime(ctx); + if (rt && !rt->current_stack_frame) { + LEPUSContext *pctx = NULL; + int result = 0; + while ((result = LEPUS_ExecutePendingJob(rt, &pctx))) { + if (result < 0) { + return napi_set_exception(env, LEPUS_GetException(pctx)); + } + } + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_global(napi_env env, napi_value *result) { + *result = env->ctx->CreateHandle(LEPUS_GetGlobalObject(env->ctx->ctx)); + return napi_clear_last_error(env); +} + +napi_status napi_throw(napi_env env, napi_value error) { + if (env->ctx->last_exception) { + JS_FreeValue_Comp(env->ctx->ctx, *env->ctx->last_exception); + } + env->ctx->last_exception.reset( + new LEPUSValue(JS_DupValue_Comp(env->ctx->ctx, ToJSValue(error)))); + env->ctx->last_exception_pVal.Reset(nullptr, *env->ctx->last_exception, + nullptr, env->ctx->ctx, true); + return napi_clear_last_error(env); +} + +napi_status napi_throw_error(napi_env env, const char *code, const char *msg) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + + LEPUSValue code_val = LEPUS_UNDEFINED; + if (code != NULL) { + LEPUS_NewString(env->ctx->ctx, code); + env->ctx->CreateHandle(code_val, true); + } + + LEPUSValue msg_val = LEPUS_NewString(env->ctx->ctx, msg); + env->ctx->CreateHandle(msg_val, true); + + napi_value error{}; + napi_status ret = + napi_create_error(env, ToNapi(&code_val), ToNapi(&msg_val), &error); + JS_FreeValue_Comp(env->ctx->ctx, code_val); + JS_FreeValue_Comp(env->ctx->ctx, msg_val); + + CHECK_NAPI(ret); + + return napi_throw(env, error); +} + +napi_status napi_throw_type_error(napi_env env, const char *code, + const char *msg) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + + LEPUSValue code_val = LEPUS_UNDEFINED; + if (code != NULL) { + LEPUS_NewString(env->ctx->ctx, code); + env->ctx->CreateHandle(code_val, true); + } + + LEPUSValue msg_val = LEPUS_NewString(env->ctx->ctx, msg); + env->ctx->CreateHandle(msg_val, true); + + napi_value error{}; + napi_status ret = + napi_create_type_error(env, ToNapi(&code_val), ToNapi(&msg_val), &error); + JS_FreeValue_Comp(env->ctx->ctx, code_val); + JS_FreeValue_Comp(env->ctx->ctx, msg_val); + + CHECK_NAPI(ret); + + return napi_throw(env, error); +} + +napi_status napi_throw_range_error(napi_env env, const char *code, + const char *msg) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + LEPUSValue code_val = LEPUS_UNDEFINED; + if (code != NULL) { + LEPUS_NewString(env->ctx->ctx, code); + env->ctx->CreateHandle(code_val, true); + } + LEPUSValue msg_val = LEPUS_NewString(env->ctx->ctx, msg); + env->ctx->CreateHandle(msg_val, true); + + napi_value error{}; + napi_status ret = + napi_create_range_error(env, ToNapi(&code_val), ToNapi(&msg_val), &error); + + JS_FreeValue_Comp(env->ctx->ctx, code_val); + + JS_FreeValue_Comp(env->ctx->ctx, msg_val); + + CHECK_NAPI(ret); + + return napi_throw(env, error); +} + +napi_status napi_is_error(napi_env env, napi_value value, bool *result) { + *result = LEPUS_IsError(env->ctx->ctx, ToJSValue(value)); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_double(napi_env env, napi_value value, + double *result) { + int ret = LEPUS_ToFloat64(env->ctx->ctx, result, ToJSValue(value)); + + CHECK_QJS(env, ret != -1); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_int32(napi_env env, napi_value value, + int32_t *result) { + int ret = LEPUS_ToInt32(env->ctx->ctx, result, ToJSValue(value)); + + CHECK_QJS(env, ret != -1); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_uint32(napi_env env, napi_value value, + uint32_t *result) { + int ret = LEPUS_ToUint32(env->ctx->ctx, result, ToJSValue(value)); + + CHECK_QJS(env, ret != -1); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_int64(napi_env env, napi_value value, + int64_t *result) { + int ret = LEPUS_ToInt64(env->ctx->ctx, result, ToJSValue(value)); + + CHECK_QJS(env, ret != -1); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_bool(napi_env env, napi_value value, bool *result) { + *result = LEPUS_ToBool(env->ctx->ctx, ToJSValue(value)); + + return napi_clear_last_error(env); +} + +// Copies a JavaScript string into a LATIN-1 string buffer. The result is the +// number of bytes (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_latin1(napi_env env, napi_value value, + char *buf, size_t bufsize, + size_t *result) { + LEPUSValue wstring = LEPUS_ToWString(env->ctx->ctx, ToJSValue(value)); + env->ctx->CreateHandle(wstring, true); + + CHECK_QJS(env, !LEPUS_IsException(wstring)); + + size_t length = LEPUS_GetStringLength(env->ctx->ctx, wstring); + + if (buf == nullptr) { + *result = length; + } else { + const char16_t *chars = reinterpret_cast( + LEPUS_GetStringChars(env->ctx->ctx, wstring)); + size_t size{std::min(length, bufsize - 1)}; + for (size_t i = 0; i < size; ++i) { + const char16_t ch{chars[i]}; + buf[i] = (ch < 256) ? ch : '?'; + } + buf[size] = '\0'; + if (result != nullptr) { + *result = size; + } + } + + JS_FreeValue_Comp(env->ctx->ctx, wstring); + + return napi_clear_last_error(env); +} + +// Copies a JavaScript string into a UTF-8 string buffer. The result is the +// number of bytes (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_utf8(napi_env env, napi_value value, + char *buf, size_t bufsize, + size_t *result) { + size_t length; + const char *str = + LEPUS_ToCStringLen(env->ctx->ctx, &length, ToJSValue(value)); + + CHECK_QJS(env, str); + + if (buf == nullptr) { + *result = length; + } else { + size_t size{std::min(length, bufsize - 1)}; + std::copy(str, str + size, buf); + buf[size] = '\0'; + if (result != nullptr) { + *result = size; + } + } + + JS_FreeCString_Comp(env->ctx->ctx, str); + + return napi_clear_last_error(env); +} + +// Copies a JavaScript string into a UTF-16 string buffer. The result is the +// number of 2-byte code units (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in 2-byte +// code units) via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_utf16(napi_env env, napi_value value, + char16_t *buf, size_t bufsize, + size_t *result) { + LEPUSValue wstring = LEPUS_ToWString(env->ctx->ctx, ToJSValue(value)); + env->ctx->CreateHandle(wstring, true); + + CHECK_QJS(env, !LEPUS_IsException(wstring)); + + size_t length = LEPUS_GetStringLength(env->ctx->ctx, wstring); + + if (buf == nullptr) { + *result = length; + } else { + const char16_t *chars = reinterpret_cast( + LEPUS_GetStringChars(env->ctx->ctx, wstring)); + size_t size{std::min(length, bufsize - 1)}; + std::copy(chars, chars + size, buf); + buf[size] = '\0'; + if (result != nullptr) { + *result = size; + } + } + + JS_FreeValue_Comp(env->ctx->ctx, wstring); + + return napi_clear_last_error(env); +} + +napi_status napi_coerce_to_bool(napi_env env, napi_value value, + napi_value *result) { + *result = env->ctx->CreateHandle(LEPUS_NewBool( + env->ctx->ctx, LEPUS_ToBool(env->ctx->ctx, ToJSValue(value)))); + return napi_clear_last_error(env); +} + +napi_status napi_coerce_to_number(napi_env env, napi_value value, + napi_value *result) { + double number; + int ret = LEPUS_ToFloat64(env->ctx->ctx, &number, ToJSValue(value)); + + CHECK_QJS(env, ret != -1); + + *result = env->ctx->CreateHandle(LEPUS_NewFloat64(env->ctx->ctx, number)); + return napi_clear_last_error(env); +} + +napi_status napi_coerce_to_object(napi_env env, napi_value value, + napi_value *result) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + napi_value global{}, object_func{}, object_value{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Object", &object_func)); + CHECK_NAPI( + napi_call_function(env, global, object_func, 1, &value, &object_value)); + *result = scope.Escape(object_value); + + return napi_clear_last_error(env); +} + +napi_status napi_coerce_to_string(napi_env env, napi_value value, + napi_value *result) { + LEPUSValue str = LEPUS_ToString(env->ctx->ctx, ToJSValue(value)); + CHECK_QJS(env, !LEPUS_IsException(str)); + *result = env->ctx->CreateHandle(str); + return napi_clear_last_error(env); +} + +napi_status napi_wrap(napi_env env, napi_value js_object, void *native_object, + napi_finalize finalize_cb, void *finalize_hint, + napi_ref *result) { + return qjsimpl::Wrap( + env, js_object, native_object, finalize_cb, finalize_hint, result); +} + +napi_status napi_unwrap(napi_env env, napi_value obj, void **result) { + return qjsimpl::Unwrap(env, obj, result, qjsimpl::KeepWrap); +} + +napi_status napi_remove_wrap(napi_env env, napi_value obj, void **result) { + return qjsimpl::Unwrap(env, obj, result, qjsimpl::RemoveWrap); +} + +napi_status napi_create_external(napi_env env, void *data, + napi_finalize finalize_cb, void *finalize_hint, + napi_value *result) { + qjsimpl::NativeInfo *info; + LEPUSValue value = qjsimpl::External::Create(env, &info); + + CHECK_QJS(env, !LEPUS_IsException(value)); + + info->Data(data); + + qjsimpl::Reference::New(env, value, info, 0, true, finalize_cb, data, + finalize_hint); + + *result = env->ctx->CreateHandle(value); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_external(napi_env env, napi_value value, + void **result) { + qjsimpl::NativeInfo *info = qjsimpl::NativeInfo::Get(ToJSValue(value)); + *result = info != nullptr && info->Type() == qjsimpl::NativeType::External + ? info->Data() + : nullptr; + return napi_clear_last_error(env); +} + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +napi_status napi_create_reference(napi_env env, napi_value value, + uint32_t initial_refcount, napi_ref *result) { + LEPUSValueConst val = ToJSValue(value); + + if (LEPUS_VALUE_GET_NORM_TAG(val) != LEPUS_TAG_OBJECT) { + return napi_set_last_error(env, napi_object_expected); + } + + qjsimpl::Reference *reference = qjsimpl::Reference::New( + env, val, qjsimpl::NativeInfo::Get(val), initial_refcount, false); + + *result = reinterpret_cast(reference); + + return napi_clear_last_error(env); +} + +// Deletes a reference. The referenced value is released, and may be GC'd +// unless there are other references to it. +napi_status napi_delete_reference(napi_env env, napi_ref ref) { + qjsimpl::Reference::Delete(reinterpret_cast(ref)); + + return napi_clear_last_error(env); +} + +// Increments the reference count, optionally returning the resulting count. +// After this call the reference will be a strong reference because its refcount +// is >0, and the referenced object is effectively "pinned". Calling this when +// the refcount is 0 and the target is unavailable results in an error. +napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t *result) { + qjsimpl::Reference *reference = reinterpret_cast(ref); + uint32_t count = reference->Ref(); + + if (result != nullptr) { + *result = count; + } + + return napi_clear_last_error(env); +} + +// Decrements the reference count, optionally returning the resulting count. +// If the result is 0 the reference is now weak and the object may be GC'd at +// any time if there are no other references. Calling this when the refcount +// is already 0 results in an error. +napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t *result) { + qjsimpl::Reference *reference = reinterpret_cast(ref); + + if (reference->RefCount() == 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + uint32_t count = reference->Unref(); + + if (result != nullptr) { + *result = count; + } + + return napi_clear_last_error(env); +} + +// Attempts to get a referenced value. If the reference is weak, the value +// might no longer be available, in that case the call is still successful but +// the result is NULL. +napi_status napi_get_reference_value(napi_env env, napi_ref ref, + napi_value *result) { + qjsimpl::Reference *reference = reinterpret_cast(ref); + + *result = reference->Get(); + return napi_clear_last_error(env); +} + +//// Stub implementation of handle scope apis for QuickLEPUS. +//napi_status napi_open_context_scope(napi_env env, napi_context_scope* result) { +// *result = reinterpret_cast(1); +// return napi_clear_last_error(env); +//} +// +//// Stub implementation of handle scope apis for QuickJS. +//napi_status napi_close_context_scope(napi_env env, napi_context_scope scope) { +// return napi_clear_last_error(env); +//} + +napi_status napi_open_handle_scope(napi_env env, napi_handle_scope *result) { + *result = reinterpret_cast( + new napi_handle_scope__(env, env->ctx->ctx, reset_napi_env)); + env->ctx->open_handle_scopes++; + return napi_clear_last_error(env); +} + +napi_status napi_close_handle_scope(napi_env env, napi_handle_scope scope) { + if (env->ctx->open_handle_scopes == 0) { + return napi_handle_scope_mismatch; + } + env->ctx->open_handle_scopes--; + + delete reinterpret_cast(scope); + return napi_clear_last_error(env); +} + +napi_status napi_open_escapable_handle_scope( + napi_env env, napi_escapable_handle_scope *result) { + *result = reinterpret_cast( + new napi_handle_scope__(env, env->ctx->ctx, reset_napi_env)); + env->ctx->open_handle_scopes++; + return napi_clear_last_error(env); +} + +napi_status napi_close_escapable_handle_scope( + napi_env env, napi_escapable_handle_scope scope) { + if (env->ctx->open_handle_scopes == 0) { + return napi_handle_scope_mismatch; + } + env->ctx->open_handle_scopes--; + + delete reinterpret_cast(scope); + return napi_clear_last_error(env); +} + +napi_status napi_escape_handle(napi_env env, napi_escapable_handle_scope scope, + napi_value escapee, napi_value *result) { + *result = reinterpret_cast(scope)->Escape(escapee); + return napi_clear_last_error(env); +} + +napi_status napi_new_instance(napi_env env, napi_value constructor, size_t argc, + const napi_value *argv, napi_value *result) { + if (argc > 0) { + CHECK_ARG(env, argv); + } + + ArgsConverter args( + argc, const_cast(argv)); + + js_enter(env); + + LEPUSValue instance = + LEPUS_CallConstructor(env->ctx->ctx, ToJSValue(constructor), argc, args); + + js_exit(env); + + CHECK_QJS(env, !LEPUS_IsException(instance)); + + *result = env->ctx->CreateHandle(instance); + + return napi_clear_last_error(env); +} + +napi_status napi_instanceof(napi_env env, napi_value object, + napi_value constructor, bool *result) { + int ret = LEPUS_IsInstanceOf(env->ctx->ctx, ToJSValue(object), + ToJSValue(constructor)); + + CHECK_QJS(env, ret != -1); + + *result = ret; + + return napi_clear_last_error(env); +} + +napi_status napi_is_exception_pending(napi_env env, bool *result) { + *result = static_cast(env->ctx->last_exception); + return napi_clear_last_error(env); +} + +napi_status napi_get_and_clear_last_exception(napi_env env, + napi_value *result) { + if (!env->ctx->last_exception) { + return napi_get_undefined(env, result); + } else { + *result = env->ctx->CreateHandle(*env->ctx->last_exception); + env->ctx->last_exception.reset(); + env->ctx->last_exception_pVal.Reset(true); + } + + return napi_clear_last_error(env); +} + +std::string get_lepus_error_stack(LEPUSContext *ctx, LEPUSValue &value) { + std::string err; + if (LEPUS_IsError(ctx, value) || LEPUS_IsException(value)) { + LEPUSValue val = LEPUS_GetPropertyStr(ctx, value, "stack"); + if (!LEPUS_IsUndefined(val)) { + const char *stack = LEPUS_ToCString(ctx, val); + if (stack) { + err.append(stack); + } + JS_FreeCString_Comp(ctx, stack); + } + JS_FreeValue_Comp(ctx, val); + } + return err; +} + +napi_status napi_get_unhandled_rejection_exception(napi_env env, + napi_value *result) { + LEPUSContext *ctx = env->ctx->ctx; + std::string error_result; + while (LEPUS_MoveUnhandledRejectionToException(ctx)) { + LEPUSValue exception = LEPUS_GetException(ctx); + env->ctx->CreateHandle(exception, true); + const char *error_message = LEPUS_ToCString(ctx, exception); + if (error_message) { + error_result.append("message: "); + error_result.append(error_message); + } + JS_FreeCString_Comp(ctx, error_message); + + std::string error_stack = get_lepus_error_stack(ctx, exception); + error_result.append("\nstack: "); + error_result.append(error_stack); + error_result.append("\n"); + } + LEPUSValue result_lepus = LEPUS_NewString(ctx, error_result.c_str()); + *result = env->ctx->CreateHandle(result_lepus); + return napi_clear_last_error(env); +} + +napi_status napi_get_own_property_descriptor(napi_env env, napi_value obj, + napi_value prop, + napi_value *result) { + LEPUSValueConst args[2]; + args[0] = ToJSValue(obj); + args[1] = ToJSValue(prop); + LEPUSValue descriptor = lepus_object_getOwnPropertyDescriptor( + env->ctx->ctx, LEPUS_UNDEFINED, 2, args, 0); + CHECK_QJS(env, !LEPUS_IsException(descriptor)); + *result = env->ctx->CreateHandle(descriptor); + return napi_clear_last_error(env); +} + +napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool *result) { + LEPUSClassID id = LEPUS_GetClassID(env->ctx->ctx, ToJSValue(value)); + *result = id == JS_CLASS_ARRAY_BUFFER || id == JS_CLASS_SHARED_ARRAY_BUFFER; + return napi_clear_last_error(env); +} + +napi_status napi_create_arraybuffer(napi_env env, size_t byte_length, + void **data, napi_value *result) { + void *bytes = std::malloc(byte_length); + // v8 use zero initialized + std::memset(bytes, 0, byte_length); + LEPUSValue buffer = LEPUS_NewArrayBuffer( + env->ctx->ctx, static_cast(bytes), byte_length, + [](LEPUSRuntime *rt, void *opaque, void *ptr) { std::free(ptr); }, + nullptr, false); + + if (LEPUS_IsException(buffer)) { + std::free(bytes); + CHECK_QJS(env, false); + } + + *data = bytes; + *result = env->ctx->CreateHandle(buffer); + + return napi_clear_last_error(env); +} + +napi_status napi_create_external_arraybuffer(napi_env env, void *external_data, + size_t byte_length, + napi_finalize finalize_cb, + void *finalize_hint, + napi_value *result) { + LEPUSValue buffer = LEPUS_NewArrayBuffer( + env->ctx->ctx, static_cast(external_data), byte_length, + [](LEPUSRuntime *rt, void *opaque, void *ptr) {}, nullptr, false); + + CHECK_QJS(env, !LEPUS_IsException(buffer)); + + if (finalize_cb != nullptr) { + qjsimpl::Reference::New(env, buffer, nullptr, 0, true, finalize_cb, + external_data, finalize_hint); + } + + *result = env->ctx->CreateHandle(buffer); + + return napi_clear_last_error(env); +} + +napi_status napi_get_arraybuffer_info(napi_env env, napi_value arraybuffer, + void **data, size_t *byte_length) { + size_t size; + uint8_t *bytes = + LEPUS_GetArrayBuffer(env->ctx->ctx, &size, ToJSValue(arraybuffer)); + + CHECK_QJS(env, bytes); + + if (data) { + *data = static_cast(bytes); + } + if (byte_length) { + *byte_length = size; + } + return napi_clear_last_error(env); +} + +napi_status napi_is_typedarray(napi_env env, napi_value value, bool *result) { + LEPUSClassID class_id = LEPUS_GetClassID(env->ctx->ctx, ToJSValue(value)); + *result = + class_id >= JS_CLASS_UINT8C_ARRAY && class_id <= JS_CLASS_FLOAT64_ARRAY; + return napi_clear_last_error(env); +} + +#define FOR_EACH_TYPEDARRAY(V) \ + V(napi_uint8_clamped_array, JS_CLASS_UINT8C_ARRAY) \ + V(napi_uint8_array, JS_CLASS_UINT8_ARRAY) \ + V(napi_int8_array, JS_CLASS_INT8_ARRAY) \ + V(napi_int16_array, JS_CLASS_INT16_ARRAY) \ + V(napi_uint16_array, JS_CLASS_UINT16_ARRAY) \ + V(napi_int32_array, JS_CLASS_INT32_ARRAY) \ + V(napi_uint32_array, JS_CLASS_UINT32_ARRAY) \ + V(napi_float32_array, JS_CLASS_FLOAT32_ARRAY) \ + V(napi_float64_array, JS_CLASS_FLOAT64_ARRAY) + +napi_status napi_create_typedarray(napi_env env, napi_typedarray_type type, + size_t length, napi_value arraybuffer, + size_t byte_offset, napi_value *result) { + LEPUSClassID class_id; + + switch (type) { +#define CASE_TYPE(TYPE, CLASS_ID) \ + case TYPE: \ + class_id = CLASS_ID; \ + break; + + FOR_EACH_TYPEDARRAY(CASE_TYPE) + +#undef CASE_TYPE + case napi_bigint64_array: + case napi_biguint64_array: + return napi_set_last_error(env, napi_invalid_arg); + } + + LEPUSValue array = LEPUS_NewTypedArrayWithBuffer( + env->ctx->ctx, ToJSValue(arraybuffer), byte_offset, length, class_id); + + CHECK_QJS(env, !LEPUS_IsException(array)); + + *result = env->ctx->CreateHandle(array); + + return napi_clear_last_error(env); +} + +napi_status napi_is_typedarray_of(napi_env env, napi_value typedarray, + napi_typedarray_type type, bool *result) { + LEPUSClassID class_id = + LEPUS_GetClassID(env->ctx->ctx, ToJSValue(typedarray)); + + switch (type) { +#define CASE_TYPE(TYPE, CLASS_ID) \ + case TYPE: \ + *result = class_id == CLASS_ID; \ + break; + + FOR_EACH_TYPEDARRAY(CASE_TYPE) + +#undef CASE_TYPE + case napi_bigint64_array: + case napi_biguint64_array: + return napi_set_last_error(env, napi_invalid_arg); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_typedarray_info(napi_env env, napi_value typedarray, + napi_typedarray_type *type, size_t *length, + void **data, napi_value *arraybuffer, + size_t *byte_offset) { + LEPUSValueConst typedarray_val = ToJSValue(typedarray); + LEPUSClassID class_id = LEPUS_GetClassID(env->ctx->ctx, typedarray_val); + + switch (class_id) { +#define CASE_TYPE(TYPE, CLASS_ID) \ + case CLASS_ID: \ + if (type) *type = TYPE; \ + break; + + FOR_EACH_TYPEDARRAY(CASE_TYPE) + +#undef CASE_TYPE + case napi_bigint64_array: + case napi_biguint64_array: + return napi_set_last_error(env, napi_invalid_arg); + } + + uint32_t byte_offset_num; + + { + LEPUSValue val = LEPUS_GetProperty(env->ctx->ctx, typedarray_val, + env->ctx->PROP_BYTEOFFSET); + CHECK_QJS(env, !LEPUS_IsException(val)); + CHECK_QJS(env, LEPUS_ToUint32(env->ctx->ctx, &byte_offset_num, val) != -1); + if (byte_offset) { + *byte_offset = byte_offset_num; + } + } + + if (length) { + LEPUSValue val = + LEPUS_GetProperty(env->ctx->ctx, typedarray_val, env->ctx->PROP_LENGTH); + CHECK_QJS(env, !LEPUS_IsException(val)); + uint32_t length_num; + CHECK_QJS(env, LEPUS_ToUint32(env->ctx->ctx, &length_num, val) != -1); + *length = length_num; + } + + if (data || arraybuffer) { + LEPUSValue val = + LEPUS_GetProperty(env->ctx->ctx, typedarray_val, env->ctx->PROP_BUFFER); + CHECK_QJS(env, !LEPUS_IsException(val)); + if (arraybuffer) { + *arraybuffer = env->ctx->CreateHandle(val); + } + if (data) { + size_t unused; + uint8_t *buffer_start = LEPUS_GetArrayBuffer(env->ctx->ctx, &unused, val); + CHECK_QJS(env, buffer_start); + *data = buffer_start + byte_offset_num; + } + } + + return napi_clear_last_error(env); +} + +napi_status napi_create_dataview(napi_env env, size_t byte_length, + napi_value arraybuffer, size_t byte_offset, + napi_value *result) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + napi_value global{}, dataview_ctor{}, data_view{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "DataView", &dataview_ctor)); + + napi_value byte_offset_value{}, byte_length_value{}; + napi_create_double(env, static_cast(byte_offset), &byte_offset_value); + napi_create_double(env, static_cast(byte_length), &byte_length_value); + napi_value args[] = {arraybuffer, byte_offset_value, byte_length_value}; + CHECK_NAPI(napi_new_instance(env, dataview_ctor, 3, args, &data_view)); + + *result = scope.Escape(data_view); + + return napi_clear_last_error(env); +} + +napi_status napi_is_dataview(napi_env env, napi_value value, bool *result) { + LEPUSClassID class_id = LEPUS_GetClassID(env->ctx->ctx, ToJSValue(value)); + *result = class_id == JS_CLASS_DATAVIEW; + return napi_clear_last_error(env); +} + +napi_status napi_is_date(napi_env env, napi_value value, bool *result) { + LEPUSClassID class_id = LEPUS_GetClassID(env->ctx->ctx, ToJSValue(value)); + *result = class_id == JS_CLASS_DATE; + return napi_clear_last_error(env); +} + +napi_status napi_get_dataview_info(napi_env env, napi_value dataview, + size_t *byte_length, void **data, + napi_value *arraybuffer, + size_t *byte_offset) { + LEPUSValueConst dataview_val = ToJSValue(dataview); + LEPUSClassID class_id = LEPUS_GetClassID(env->ctx->ctx, dataview_val); + + if (class_id != JS_CLASS_DATAVIEW) { + return napi_set_last_error(env, napi_invalid_arg); + } + + uint32_t byte_offset_num; + + { + LEPUSValue val = LEPUS_GetProperty(env->ctx->ctx, dataview_val, + env->ctx->PROP_BYTEOFFSET); + CHECK_QJS(env, !LEPUS_IsException(val)); + CHECK_QJS(env, LEPUS_ToUint32(env->ctx->ctx, &byte_offset_num, val) != -1); + if (byte_offset) { + *byte_offset = byte_offset_num; + } + } + + if (byte_length) { + LEPUSValue val = LEPUS_GetProperty(env->ctx->ctx, dataview_val, + env->ctx->PROP_BYTELENGTH); + CHECK_QJS(env, !LEPUS_IsException(val)); + uint32_t byte_length_num; + CHECK_QJS(env, LEPUS_ToUint32(env->ctx->ctx, &byte_length_num, val) != -1); + *byte_length = byte_length_num; + } + + if (data || arraybuffer) { + LEPUSValue val = + LEPUS_GetProperty(env->ctx->ctx, dataview_val, env->ctx->PROP_BUFFER); + CHECK_QJS(env, !LEPUS_IsException(val)); + if (arraybuffer) { + *arraybuffer = env->ctx->CreateHandle(val); + } + if (data) { + size_t unused; + uint8_t *buffer_start = LEPUS_GetArrayBuffer(env->ctx->ctx, &unused, val); + CHECK_QJS(env, buffer_start); + *data = buffer_start + byte_offset_num; + } + } + + return napi_clear_last_error(env); +} + +struct napi_deferred__ { + qjsimpl::NAPIPersistent resolve; + qjsimpl::NAPIPersistent reject; + + bool has_init = false; + + static napi_value Callback(napi_env env, napi_callback_info cbinfo) { + napi_deferred__ *deferred = + static_cast(cbinfo->data); + deferred->has_init = true; + deferred->resolve.Reset(env, ToJSValue(cbinfo->argv[0]), nullptr, + env->ctx->ctx, false); + deferred->reject.Reset(env, ToJSValue(cbinfo->argv[1]), nullptr, + env->ctx->ctx, false); + return nullptr; + } + + ~napi_deferred__() { + resolve.Reset(true); + reject.Reset(true); + } +}; + +napi_status napi_create_promise(napi_env env, napi_deferred *deferred, + napi_value *promise) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + napi_value global{}, promise_ctor{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Promise", &promise_ctor)); + + std::unique_ptr deferred_val = + std::make_unique(); + napi_value executor{}, promise_val{}; + CHECK_NAPI(napi_create_function(env, "executor", NAPI_AUTO_LENGTH, + napi_deferred__::Callback, + deferred_val.get(), &executor)); + CHECK_NAPI(napi_new_instance(env, promise_ctor, 1, &executor, &promise_val)); + + if (!deferred_val->has_init) { + return napi_set_last_error(env, napi_generic_failure); + } + + *promise = scope.Escape(promise_val); + *deferred = deferred_val.release(); + + return napi_clear_last_error(env); +} + +napi_status napi_is_promise(napi_env env, napi_value promise, + bool *is_promise) { + napi_handle_scope__ scope(env, env->ctx->ctx, reset_napi_env); + + napi_value global{}, promise_ctor{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Promise", &promise_ctor)); + CHECK_NAPI(napi_instanceof(env, promise, promise_ctor, is_promise)); + + return napi_clear_last_error(env); +} + +napi_status napi_run_script(napi_env env, napi_value script, + napi_value *result) { + return napi_run_script_source(env, script, "ctx->ctx, &size, ToJSValue(script)); + js_enter(env); + result_val = LEPUS_Eval(env->ctx->ctx, src, size, + source_url, LEPUS_EVAL_TYPE_GLOBAL); + JS_FreeCString_Comp(env->ctx->ctx, src); + js_exit(env); + + CHECK_QJS(env, !LEPUS_IsException(result_val)); + + *result = env->ctx->CreateHandle(result_val); + return napi_clear_last_error(env); +} + +#ifdef ENABLE_CODECACHE + +napi_status napi_run_code_cache(napi_env env, const uint8_t* data, int length, + napi_value* result) { + LEPUSValue result_val = LEPUS_UNDEFINED; + LEPUSValue top_func = + LEPUS_EvalBinary(env->ctx->ctx, data, static_cast(length), + LEPUS_EVAL_BINARY_LOAD_ONLY); + if (!LEPUS_IsException(top_func) && !LEPUS_IsUndefined(top_func)) { + LEPUSValue global = LEPUS_GetGlobalObject(env->ctx->ctx); + env->ctx->CreateHandle(top_func, true); + result_val = LEPUS_EvalFunction(env->ctx->ctx, top_func, global); + } + CHECK_QJS(env, !LEPUS_IsException(result_val)); + + *result = env->ctx->CreateHandle(result_val); + return napi_clear_last_error(env); +} + +napi_status napi_run_script_cache(napi_env env, const char* script, + size_t length, const char* filename, + napi_value* result) { + if (length == NAPI_AUTO_LENGTH) { + length = std::strlen(script); + } + + LEPUSValue result_val = LEPUS_UNINITIALIZED; + { + int len = -1; + const uint8_t* data = nullptr; + env->napi_get_code_cache(env, filename, &data, &len); + if (data) { + // TODO(yang): check whether the script is obsolate + LOG_TIME_START(); + LEPUSValue top_func = + LEPUS_EvalBinary(env->ctx->ctx, data, static_cast(len), + LEPUS_EVAL_BINARY_LOAD_ONLY); + if (!LEPUS_IsException(top_func) && !LEPUS_IsUndefined(top_func)) { + LEPUSValue global = LEPUS_GetGlobalObject(env->ctx->ctx); + env->ctx->CreateHandle(top_func, true); + result_val = LEPUS_EvalFunction(env->ctx->ctx, top_func, global); + } + LOG_TIME_END("----- script eval with cache -----"); + } else if (len == 0) { + // if len is 0, we need to make cache. + // if len is -1, someone is modifying the cache, we do not make cache. + LOG_TIME_START(); + LEPUSValue top_func = + LEPUS_Eval(env->ctx->ctx, script, length, filename, + LEPUS_EVAL_FLAG_COMPILE_ONLY | LEPUS_EVAL_TYPE_GLOBAL); + CHECK_QJS(env, + !LEPUS_IsException(top_func) && !LEPUS_IsUndefined(top_func)); + LEPUSValue global = LEPUS_GetGlobalObject(env->ctx->ctx); + env->ctx->CreateHandle(top_func, true); + + size_t obj_len; + data = LEPUS_WriteObject(env->ctx->ctx, &obj_len, top_func, + LEPUS_WRITE_OBJ_BYTECODE); + env->napi_store_code_cache(env, filename, data, + static_cast(obj_len)); + js_free_comp(env->ctx->ctx, + reinterpret_cast(const_cast(data))); + result_val = LEPUS_EvalFunction(env->ctx->ctx, top_func, global); + LOG_TIME_END( + "---- evaluating %s and making code cache for it lengthed %d -----", + filename, (int)obj_len); + } + } + if (LEPUS_IsUninitialized(result_val)) { + LOG_TIME_START(); + result_val = LEPUS_Eval(env->ctx->ctx, script, length, + filename ? filename : "", LEPUS_EVAL_TYPE_GLOBAL); + LOG_TIME_END("----- script eval without cache -----"); + } + CHECK_QJS(env, !LEPUS_IsException(result_val)); + + *result = env->ctx->CreateHandle(result_val); + return napi_clear_last_error(env); +} + +// `data` memory need to be freed from outside +napi_status napi_gen_code_cache(napi_env env, const char* script, + size_t script_len, const uint8_t** data, + int* length) { + if (script_len == NAPI_AUTO_LENGTH) { + script_len = std::strlen(script); + } + LEPUSValue top_func = + LEPUS_Eval(env->ctx->ctx, script, script_len, "", + LEPUS_EVAL_FLAG_COMPILE_ONLY | LEPUS_EVAL_TYPE_GLOBAL); + CHECK_QJS(env, !LEPUS_IsException(top_func) && !LEPUS_IsUndefined(top_func)); + env->ctx->CreateHandle(top_func, true); + + size_t obj_len; + uint8_t* cache = LEPUS_WriteObject(env->ctx->ctx, &obj_len, top_func, + LEPUS_WRITE_OBJ_BYTECODE); + *data = static_cast(std::malloc(obj_len)); + *length = static_cast(obj_len); + memcpy(static_cast(const_cast(*data)), + static_cast(cache), obj_len); + js_free_comp(env->ctx->ctx, reinterpret_cast(cache)); + + return napi_clear_last_error(env); +} + +#endif // ENABLE_CODECACHE + +napi_status napi_add_finalizer(napi_env env, napi_value js_object, + void *native_object, napi_finalize finalize_cb, + void *finalize_hint, napi_ref *result) { + return qjsimpl::Wrap(env, js_object, native_object, + finalize_cb, finalize_hint, result); +} + +napi_status napi_adjust_external_memory(napi_env env, int64_t change_in_bytes, + int64_t *adjusted_value) { + // TODO: Determine if QJS needs or is able to do anything here + // For now, we can lie and say that we always adjusted more memory + *adjusted_value = change_in_bytes; + + return napi_clear_last_error(env); +} + +napi_status napi_set_instance_data(napi_env env, uint64_t key, void *data, + napi_finalize finalize_cb, + void *finalize_hint) { + auto it = env->ctx->instance_data_registry.find(key); + if (it != env->ctx->instance_data_registry.end()) { + return napi_invalid_arg; + } + + env->ctx->instance_data_registry[key] = + qjsimpl::RefBase::New(env, 0, true, finalize_cb, data, finalize_hint); + + return napi_clear_last_error(env); +} + +napi_status napi_get_instance_data(napi_env env, uint64_t key, void **data) { + auto it = env->ctx->instance_data_registry.find(key); + if (it == env->ctx->instance_data_registry.end()) { + *data = nullptr; + } else { + qjsimpl::RefBase *idata = static_cast(it->second); + + *data = idata->Data(); + } + + return napi_clear_last_error(env); +} + +void napi_attach_quickjs(napi_env env, LEPUSContext *context) { + env->ctx = new napi_context__(env, context); + + InitNapiScope(context); +} + +void napi_detach_quickjs(napi_env env) { + LEPUSContext *ctx = env->ctx->ctx; + delete env->ctx; + env->ctx = nullptr; + FreeNapiScope(ctx); +} + +LEPUSContext *napi_get_env_context_quickjs(napi_env env) { + return env->ctx->ctx; +} + +LEPUSValue napi_js_value_to_quickjs_value(napi_env env, napi_value value) { + return JS_DupValue_Comp(env->ctx->ctx, ToJSValue(value)); +} + +napi_value napi_quickjs_value_to_js_value(napi_env env, LEPUSValue value) { + return env->ctx->CreateHandle(value); +} + +inline napi_handle_scope__::napi_handle_scope__(napi_env env, LEPUSContext *ctx, + napi_func *func) + : env_(env), ctx_(ctx), handle_tail_(nullptr), reset_napi_env(func) { + is_gc = env_->ctx->gc_enable; + if (is_gc) { + prev_ = reinterpret_cast(GetNapiScope(ctx_)); + SetNapiScope(ctx_, this); + } else { + prev_ = env_->ctx->handle_scope; + env_->ctx->handle_scope = this; + } +} + +inline napi_value napi_handle_scope__::CreateHandle(LEPUSValue v) { + Handle *h = new Handle{.value = v, .prev = handle_tail_}; + handle_tail_ = h; + return ToNapi(&(h->value)); +} + +inline napi_value napi_handle_scope__::Escape(napi_value v) { + return prev_->CreateHandle(JS_DupValue_Comp(env_->ctx->ctx, ToJSValue(v))); +} + +napi_status primjs_execute_pending_jobs(napi_env env) { + int error; + do { + LEPUSContext *context; + error = LEPUS_ExecutePendingJob(LEPUS_GetRuntime(env->ctx->ctx), &context); + if (error == -1) { + return napi_set_last_error(env, napi_pending_exception); + } + } while (error != 0); + + return napi_clear_last_error(env); +} + + +typedef struct NapiHostObjectInfo { + void *data; + napi_ref ref; + napi_finalize finalize_cb; + bool is_array; + napi_ref getter; + napi_ref setter; +} NapiHostObjectInfo; + +void host_object_finalizer(LEPUSRuntime *rt, LEPUSValue value) { + napi_env env = (napi_env) napi_context__::GetEnv(rt); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(value, + env->ctx->napiHostObjectClassId); + if (info->finalize_cb) { + info->finalize_cb(env, info->data, NULL); + } + if (info->is_array) { + napi_delete_reference(env, info->getter); + napi_delete_reference(env, info->setter); + } + + napi_delete_reference(env, info->ref); + delete info; +} + +int host_object_set(LEPUSContext *ctx, LEPUSValue obj, JSAtom atom, + LEPUSValue value, LEPUSValue receiver, int flags) { + napi_env env = (napi_env) napi_context__::GetEnv(LEPUS_GetRuntime(ctx)); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(obj, + env->ctx->napiHostObjectClassId); + if (info != NULL) { + auto *target = reinterpret_cast(info->ref); + if (info->is_array) { + LEPUSValue atom_val = LEPUS_AtomToValue(ctx, atom); + + auto *setter = reinterpret_cast(info->setter); + + LEPUSValue argv[4] = { + ToJSValue(target->Get()), + atom_val, + value, + obj + }; + + LEPUSValue result = LEPUS_Call(ctx, ToJSValue(setter->Get()), LEPUS_UNDEFINED, 4, argv); + + JS_FreeValue_Comp(ctx, atom_val); + + if (LEPUS_IsException(result)) return -1; + + return true; + } + return LEPUS_SetProperty(ctx, ToJSValue(target->Get()), atom, JS_DupValue_Comp(ctx, value)); + } + return true; +} + +LEPUSValue host_object_get(LEPUSContext *ctx, LEPUSValue obj, JSAtom atom, LEPUSValue receiver) { + napi_env env = (napi_env) napi_context__::GetEnv(LEPUS_GetRuntime(ctx)); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(obj, + env->ctx->napiHostObjectClassId); + if (info != NULL) { + auto *target = reinterpret_cast(info->ref); + if (info->is_array) { + LEPUSValue atom_val = LEPUS_AtomToValue(ctx, atom); + auto *getter = reinterpret_cast(info->getter); + LEPUSValue argv[3] = { + ToJSValue(target->Get()), + atom_val, + obj + }; + LEPUSValue value = LEPUS_Call(ctx, ToJSValue(getter->Get()), LEPUS_UNDEFINED, 3, argv); + JS_FreeValue_Comp(ctx, atom_val); + return value; + } + return LEPUS_GetProperty(ctx, ToJSValue(target->Get()), atom); + } + return LEPUS_UNDEFINED; +} + +int host_object_has(LEPUSContext *ctx, LEPUSValue obj, JSAtom atom) { + napi_env env = (napi_env) napi_context__::GetEnv(LEPUS_GetRuntime(ctx)); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(obj, + env->ctx->napiHostObjectClassId); + if (info != NULL) { + auto *target = reinterpret_cast(info->ref); + return LEPUS_HasProperty(ctx, ToJSValue(target->Get()), atom); + } + return false; +} + +static int host_object_delete(LEPUSContext *ctx, LEPUSValue obj, JSAtom atom) { + napi_env env = (napi_env) napi_context__::GetEnv(LEPUS_GetRuntime(ctx)); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(obj, + env->ctx->napiHostObjectClassId); + if (info != NULL) { + auto *target = reinterpret_cast(info->ref); + return LEPUS_DeleteProperty(ctx, ToJSValue(target->Get()), atom, 0); + } + return true; +} + +LEPUSClassExoticMethods NapiHostObjectExoticMethods = { + .get_own_property = nullptr, + .get_own_property_names = nullptr, + .delete_property = host_object_delete, + .define_own_property = nullptr, + .has_property = host_object_has, + .get_property = host_object_get, + .set_property = host_object_set, +}; + +napi_status +napi_create_host_object(napi_env env, napi_value value, napi_finalize finalize, void *data, + bool is_array, napi_value getter, napi_value setter, napi_value *result) { + CHECK_ARG(env, result); + + if (env->ctx->napi_host_object_class_init == 0) { + LEPUSClassDef NapiHostObjectClassDef = {"NapiHostObject", host_object_finalizer, NULL, NULL, + &NapiHostObjectExoticMethods}; + LEPUS_NewClass(env->ctx->rt, env->ctx->napiHostObjectClassId, &NapiHostObjectClassDef); + env->ctx->napi_host_object_class_init = 1; + } + + napi_value constructor; + napi_get_named_property(env, value, "constructor", &constructor); + + napi_value prototype; + napi_get_named_property(env, constructor, "prototype", &prototype); + + LEPUSValue jsValue = LEPUS_NewObjectClass(env->ctx->ctx, env->ctx->napiHostObjectClassId); + LEPUS_SetPrototype(env->ctx->ctx, jsValue, ToJSValue(prototype)); + + NapiHostObjectInfo *info = new NapiHostObjectInfo; + info->data = data; + if (finalize) { + info->finalize_cb = finalize; + } else { + info->finalize_cb = NULL; + } + info->is_array = is_array; + + if (is_array) { + if (getter) napi_create_reference(env, getter, 1, &info->getter); + if (setter) napi_create_reference(env, setter, 1, &info->setter); + } + + napi_create_reference(env, value, 1, &info->ref); + + LEPUS_SetOpaque(jsValue, info); + + *result = env->ctx->CreateHandle(jsValue); + return napi_ok; +} + +napi_status napi_get_host_object_data(napi_env env, napi_value object, void **data) { + CHECK_ARG(env, object); + CHECK_ARG(env, data); + + LEPUSValue jsValue = ToJSValue(object); + + + if (!LEPUS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected); + } + + NapiHostObjectInfo *info = (NapiHostObjectInfo *) LEPUS_GetOpaque(jsValue, + env->ctx->napiHostObjectClassId); + if (info) { + *data = info->data; + } else { + *data = NULL; + } + + return napi_clear_last_error(env); +} + +napi_status napi_is_host_object(napi_env env, napi_value object, bool *result) { + CHECK_ARG(env, object); + + LEPUSValue jsValue = ToJSValue(object); + + if (!LEPUS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected); + } + + void *data = LEPUS_GetOpaque(jsValue, + env->ctx->napiHostObjectClassId); + if (data != NULL) { + *result = true; + } else { + *result = false; + } + + return napi_clear_last_error(env); +} \ No newline at end of file diff --git a/NativeScript/napi/android/primjs/primjs-api.h b/NativeScript/napi/android/primjs/primjs-api.h new file mode 100644 index 000000000..c7953eef9 --- /dev/null +++ b/NativeScript/napi/android/primjs/primjs-api.h @@ -0,0 +1,572 @@ +/** + * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved. + * + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file in the root of the source tree. + */ +// Copyright 2024 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. +#ifndef SRC_NAPI_QUICKJS_JS_NATIVE_API_QUICKJS_H_ +#define SRC_NAPI_QUICKJS_JS_NATIVE_API_QUICKJS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus +#include "quickjs/include/quickjs.h" +#ifdef __cplusplus +} +#endif // __cplusplus + +#include "gc/persistent-handle.h" +#include "gc/trace-gc.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "napi_env_quickjs.h" + + +typedef struct napi_env_data__ *napi_env_data; +typedef struct napi_state__ { + napi_extended_error_info last_error; + napi_env_data env_data; +} napi_state; + +typedef struct napi_context__ *napi_context; + +struct napi_env__ { + napi_state state; + napi_runtime rt; + napi_context ctx; + int js_enter_state; +}; + +inline napi_status napi_clear_last_error(napi_env env) { + env->state.last_error.error_code = napi_ok; + env->state.last_error.engine_error_code = 0; + return napi_ok; +} + +inline napi_status napi_set_last_error(napi_env env, napi_status error_code) { + env->state.last_error.error_code = error_code; + return error_code; +} + +inline napi_value ToNapi(LEPUSValueConst *v) { + return reinterpret_cast(v); +} + +inline LEPUSValueConst ToJSValue(napi_value v) { + return *reinterpret_cast(v); +} + +inline LEPUSValue JS_DupValue_Comp(LEPUSContext *ctx, LEPUSValueConst v) { + if (!LEPUS_IsGCMode(ctx)) { + return LEPUS_DupValue(ctx, v); + } + return v; +} + +inline void JS_FreeValue_Comp(LEPUSContext *ctx, LEPUSValue v) { + if (!LEPUS_IsGCMode(ctx)) { + LEPUS_FreeValue(ctx, v); + } +} + +inline void JS_FreeAtom_Comp(LEPUSContext *ctx, JSAtom v) { + if (!LEPUS_IsGCMode(ctx)) { + LEPUS_FreeAtom(ctx, v); + } +} + +inline void JS_FreeCString_Comp(LEPUSContext *ctx, const char *ptr) { + if (!LEPUS_IsGCMode(ctx)) { + LEPUS_FreeCString(ctx, ptr); + } +} + +inline void js_free_comp(LEPUSContext *ctx, void *ptr) { + if (!LEPUS_IsGCMode(ctx)) { + lepus_free(ctx, ptr); + } +} + +namespace qjsimpl { + + class NAPIPersistent; + + class NativeInfo; + + struct WeakInfo { + std::list::const_iterator weak_iter; + std::function cb; + void *cb_arg; + }; + + class NAPIPersistent : public PersistentBase { + public: + /** + * A Persistent with no storage cell. + */ + inline NAPIPersistent() + : PersistentBase(nullptr), + _env(nullptr), + _empty(true), + _native_info(nullptr), + _ctx(nullptr) {} + + /** + * Construct a NAPIPersistent from a Local. + * When the Local is non-empty, a new storage cell is created + * pointing to the same object, and no flags are set. + */ + inline NAPIPersistent(napi_env env, LEPUSValueConst value, + NativeInfo *native_info, LEPUSContext *ctx, + bool is_weak = false); + + inline NAPIPersistent(napi_env env, JSAtom atom, NativeInfo *native_info, + LEPUSContext *ctx, bool is_weak = false); + + NAPIPersistent(const NAPIPersistent &that) = delete; + + NAPIPersistent &operator=(const NAPIPersistent &that) = delete; + + void Reset(bool for_gc = false); + + void Reset(napi_env env, LEPUSValueConst value, NativeInfo *native_info, + LEPUSContext *ctx, bool for_gc = false); + + void Reset(napi_env env, LEPUSContext *ctx, JSAtom atom); + + void SetWeak(void *data, void (*cb)(void *)); + + void ClearWeak(); + + inline ~NAPIPersistent() { Reset(); } + + LEPUSValue Value() const; + + bool IsEmpty() { + if (_ctx != nullptr && LEPUS_IsGCMode(_ctx)) { + return val_ == nullptr; + } else { + return _empty; + } + } + + static void OnFinalize(NAPIPersistent *ref); + + private: + inline LEPUSValue *operator*() const { return this->val_; } + + void ResetWeakInfo(); + + NativeInfo *_get_native_info(); + + napi_env _env; + bool _empty; + LEPUSValue _value; + NativeInfo *_native_info; + std::unique_ptr _weak_info; + LEPUSContext *_ctx; + }; + + class Atom { + public: + Atom() : _env(nullptr), _ctx(nullptr), _atom(0) {} + + Atom(napi_env env, LEPUSContext *ctx, LEPUSValueConst value) + : _env(env), _ctx(ctx), _atom(LEPUS_ValueToAtom(ctx, value)) { + _atom_persist.Reset(_env, _ctx, _atom); + } + + Atom(napi_env env, LEPUSContext *ctx, JSAtom atom) + : _env(env), _ctx(ctx), _atom(atom) { + _atom_persist.Reset(_env, _ctx, _atom); + } + + Atom(napi_env env, LEPUSContext *ctx, const char *str, + size_t length = NAPI_AUTO_LENGTH) + : _env(env), + _ctx(ctx), + _atom(length == NAPI_AUTO_LENGTH ? LEPUS_NewAtom(ctx, str) + : LEPUS_NewAtomLen(ctx, str, length)) { + _atom_persist.Reset(_env, _ctx, _atom); + } + + Atom(Atom &other) + : _env(other._env), + _ctx(other._ctx), + _atom(LEPUS_DupAtom(_ctx, other._atom)) { + _atom_persist.Reset(_env, _ctx, _atom); + } + + Atom(Atom &&other) : _env(other._env), _ctx(other._ctx), _atom(other._atom) { + other._env = nullptr; + other._ctx = nullptr; + other._atom = 0; + other._atom_persist.Reset(true); + _atom_persist.Reset(_env, _ctx, _atom); + } + + ~Atom() { + if (_atom) { + JS_FreeAtom_Comp(_ctx, _atom); + } + _atom_persist.Reset(true); + } + + bool IsValid() { return _atom > 0; } + + operator JSAtom() const { return _atom; } + + private: + napi_env _env; + LEPUSContext *_ctx; + JSAtom _atom; + NAPIPersistent _atom_persist; + }; + + class Value { + public: + Value() : _ctx(nullptr), _val(), pVal(), is_gc(false) {} + + Value(LEPUSContext *ctx, LEPUSValue val) : _ctx(ctx), _val(val), pVal() { + if (ctx) { + is_gc = LEPUS_IsGCMode(ctx); + } + if (is_gc) { + pVal.Reset(nullptr, val, nullptr, ctx, true); + } + } + + ~Value() { + if (is_gc) { + pVal.Reset(true); + } else if (_ctx) { + JS_FreeValue_Comp(_ctx, _val); + } + } + + operator LEPUSValueConst() { + if (is_gc) { + return pVal.Value(); + } else { + return _val; + } + } + + LEPUSValue dup() { + if (is_gc) { + return pVal.Value(); + } else { + return JS_DupValue_Comp(_ctx, _val); + } + } + + LEPUSValue move() { + if (!is_gc) { + _ctx = nullptr; + return _val; + } else { + return pVal.Value(); + } + } + + private: + LEPUSContext *_ctx; + LEPUSValue _val; + NAPIPersistent pVal; + bool is_gc; + }; + + class RefTracker { + public: + RefTracker() {} + + virtual ~RefTracker() {} + + virtual void Finalize(bool isEnvTeardown) {} + + typedef RefTracker RefList; + + inline void Link(RefList *list) { + prev_ = list; + next_ = list->next_; + if (next_ != nullptr) { + next_->prev_ = this; + } + list->next_ = this; + } + + inline void Unlink() { + if (prev_ != nullptr) { + prev_->next_ = next_; + } + if (next_ != nullptr) { + next_->prev_ = prev_; + } + prev_ = nullptr; + next_ = nullptr; + } + + static void FinalizeAll(RefList *list) { + while (list->next_ != nullptr) { + list->next_->Finalize(true); + } + } + + private: + RefList *next_ = nullptr; + RefList *prev_ = nullptr; + }; + + class Reference; + +} // namespace qjsimpl + +inline void reset_napi_env(napi_env env, napi_handle_scope__ *scope); + + + +struct napi_context__ { + napi_env env; + LEPUSRuntime *rt{}; + LEPUSContext *ctx{}; + + LEPUSValue V_NULL{LEPUS_NULL}; + LEPUSValue V_UNDEFINED{LEPUS_UNDEFINED}; + + napi_context__(napi_env env, LEPUSContext *ctx) + : env(env), + rt{LEPUS_GetRuntime(ctx)}, + ctx{ctx}, + PROP_NAME(env, ctx, "name"), + PROP_LENGTH(env, ctx, "length"), + PROP_PROTOTYPE(env, ctx, "prototype"), + PROP_CONSTRUCTOR(env, ctx, "constructor"), + PROP_FINALIZER(env, ctx, "@#fin@#"), + PROP_MESSAGE(env, ctx, "message"), + PROP_CODE(env, ctx, "code"), + PROP_BUFFER(env, ctx, "buffer"), + PROP_BYTELENGTH(env, ctx, "byteLength"), + PROP_BYTEOFFSET(env, ctx, "byteOffset"), + PROP_CTOR_MAGIC(env, ctx, "@#ctor@#"), + gc_enable(LEPUS_IsGCModeRT(rt)) { + env->ctx = this; + napi_context__::rt_to_env_cache.emplace(rt, env); + handle_scope = new napi_handle_scope__(env, ctx, reset_napi_env); + + LEPUSValue gc = LEPUS_NewCFunction(ctx, [](LEPUSContext *ctx, LEPUSValueConst this_val, + int argc, LEPUSValueConst *argv) -> LEPUSValue { + LEPUS_RunGC(LEPUS_GetRuntime(ctx)); + return LEPUS_UNDEFINED; + }, "gc", 0); + auto globalValue = LEPUS_GetGlobalObject(ctx); + LEPUS_SetPropertyStr(ctx, globalValue, "gc", gc); + JS_FreeValue_Comp(ctx, globalValue); + LEPUS_NewClassID(&napiHostObjectClassId); + + // TODO primjs doesn't expose DupContext + } + + ~napi_context__() { + qjsimpl::RefTracker::FinalizeAll(&finalizing_reflist); + qjsimpl::RefTracker::FinalizeAll(&reflist); + + // root handle scope may be used during FinalizeAll + // must delete at last + delete handle_scope; + rt_to_env_cache.erase(rt); + } + + inline void Ref() { refs++; } + + inline void Unref() { + if (--refs == 0) delete this; + } + + template> + inline void CallIntoModule(T &&call, U &&handle_exception) { + int open_handle_scopes_before = open_handle_scopes; + (void) open_handle_scopes_before; + napi_clear_last_error(this->env); + call(this->env); + assert(open_handle_scopes == open_handle_scopes_before); + if (last_exception) { + handle_exception(this->env, *last_exception); + last_exception.reset(); + last_exception_pVal.Reset(true); + } + } + + void CallFinalizer(napi_finalize cb, void *data, void *hint) { + cb(this->env, data, hint); + } + + qjsimpl::RefTracker::RefList reflist; + qjsimpl::RefTracker::RefList finalizing_reflist; + + std::unique_ptr last_exception; + qjsimpl::NAPIPersistent last_exception_pVal; + + std::unordered_map instance_data_registry; + + int napi_host_object_class_init = 0; + LEPUSClassID napiHostObjectClassId = 0; + static std::unordered_map rt_to_env_cache; + + static napi_env GetEnv(LEPUSRuntime* rt) { + auto it = rt_to_env_cache.find(rt); + if (it == rt_to_env_cache.end()) return nullptr; + return it->second; + } + + int open_handle_scopes = 0; + + const qjsimpl::Atom PROP_NAME; + const qjsimpl::Atom PROP_LENGTH; + const qjsimpl::Atom PROP_PROTOTYPE; + const qjsimpl::Atom PROP_CONSTRUCTOR; + const qjsimpl::Atom PROP_FINALIZER; + const qjsimpl::Atom PROP_MESSAGE; + const qjsimpl::Atom PROP_CODE; + const qjsimpl::Atom PROP_BUFFER; + const qjsimpl::Atom PROP_BYTELENGTH; + const qjsimpl::Atom PROP_BYTEOFFSET; + const qjsimpl::Atom PROP_CTOR_MAGIC; + + napi_value CreateHandle(LEPUSValue v, bool only_gc = false) { + if (LEPUS_IsGCMode(ctx)) { + return static_cast(GetNapiScope(ctx))->CreateHandle(v); + } else if (!only_gc) { + return handle_scope->CreateHandle(v); + } + return {}; + } + + void set_handle_scope(napi_handle_scope__ *scope) { handle_scope = scope; } + +private: + int refs = 1; + napi_handle_scope__ *handle_scope{}; + bool gc_enable; + + friend class napi_handle_scope__; +}; + +inline void reset_napi_env(napi_env env, napi_handle_scope__ *scope) { + env->ctx->set_handle_scope(scope); +} + +struct napi_class__qjs { + napi_class__qjs(LEPUSContext *ctx, LEPUSValue proto, LEPUSValue constructor) + : ctx(ctx), proto(proto), constructor(constructor) { + if (LEPUS_IsGCMode(ctx)) { + proto_persist.Reset(nullptr, proto, nullptr, ctx, true); + constructor_persist.Reset(nullptr, constructor, nullptr, ctx, true); + } + } + + ~napi_class__qjs() { + if (LEPUS_IsGCMode(ctx)) { + proto_persist.Reset(true); + constructor_persist.Reset(true); + } else { + LEPUS_FreeValue(ctx, proto); + LEPUS_FreeValue(ctx, constructor); + } + } + + LEPUSValue GetFunction() { return JS_DupValue_Comp(ctx, constructor); } + + LEPUSContext *ctx; + + LEPUSValue proto; + qjsimpl::NAPIPersistent proto_persist; + LEPUSValue constructor; + qjsimpl::NAPIPersistent constructor_persist; +}; + +#define RETURN_STATUS_IF_FALSE(env, condition, status) \ + do { \ + if (!(condition)) { \ + return napi_set_last_error((env), (status)); \ + } \ + } while (0) + +#define CHECK_ARG(env, arg) \ + RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), napi_invalid_arg) + +#define CHECK_QJS(env, condition) \ + do { \ + if (!(condition)) { \ + return napi_set_exception(env, LEPUS_GetException(env->ctx->ctx)); \ + } \ + } while (0) + +// This does not call napi_set_last_error because the expression +// is assumed to be a NAPI function call that already did. +#define CHECK_NAPI(expr) \ + do { \ + napi_status status = (expr); \ + if (status != napi_ok) return status; \ + } while (0) + +namespace qjsimpl { +// Adapter for napi_finalize callbacks. + class Finalizer { + public: + // Some Finalizers are run during shutdown when the napi_env is destroyed, + // and some need to keep an explicit reference to the napi_env because they + // are run independently. + enum EnvReferenceMode { + kNoEnvReference, kKeepEnvReference + }; + + protected: + Finalizer(napi_env env, napi_finalize finalize_callback, void *finalize_data, + void *finalize_hint, EnvReferenceMode refmode = kNoEnvReference) + : _env(env), + _finalize_callback(finalize_callback), + _finalize_data(finalize_data), + _finalize_hint(finalize_hint), + _has_env_reference(refmode == kKeepEnvReference) { + if (_has_env_reference) _env->ctx->Ref(); + } + + ~Finalizer() { + if (_has_env_reference) _env->ctx->Unref(); + } + + public: + static Finalizer *New(napi_env env, napi_finalize finalize_callback = nullptr, + void *finalize_data = nullptr, + void *finalize_hint = nullptr, + EnvReferenceMode refmode = kNoEnvReference) { + return new Finalizer(env, finalize_callback, finalize_data, finalize_hint, + refmode); + } + + static void Delete(Finalizer *finalizer) { delete finalizer; } + + protected: + napi_env _env; + napi_finalize _finalize_callback; + void *_finalize_data; + void *_finalize_hint; + bool _finalize_ran = false; + bool _has_env_reference = false; + }; + +} // namespace qjsimpl + +#endif // SRC_NAPI_QUICKJS_JS_NATIVE_API_QUICKJS_H_ diff --git a/NativeScript/napi/android/quickjs/jsr.cpp b/NativeScript/napi/android/quickjs/jsr.cpp new file mode 100644 index 000000000..c05830713 --- /dev/null +++ b/NativeScript/napi/android/quickjs/jsr.cpp @@ -0,0 +1,75 @@ +#include "jsr.h" +#include "quicks-runtime.h" + +JSR::JSR() = default; +tns::ConcurrentMap JSR::env_to_jsr_cache; + +napi_status js_create_runtime(napi_runtime *runtime) { + return qjs_create_runtime(runtime); +} +napi_status js_create_napi_env(napi_env *env, napi_runtime runtime) { + napi_status status = qjs_create_napi_env(env, runtime); + JSR::env_to_jsr_cache.Insert((*env), new JSR()); + return status; +} + +napi_status js_set_runtime_flags(const char *flags) { + return napi_ok; +} + +napi_status js_lock_env(napi_env env) { + auto jsr = JSR::env_to_jsr_cache.Get(env); + if (jsr) jsr->lock(); + return napi_ok; +} + +napi_status js_unlock_env(napi_env env) { + auto jsr = JSR::env_to_jsr_cache.Get(env); + if (jsr) jsr->unlock(); + + return napi_ok; +} + +napi_status js_free_napi_env(napi_env env) { + JSR* jsr = JSR::env_to_jsr_cache.Get(env); + delete jsr; + JSR::env_to_jsr_cache.Remove(env); + return qjs_free_napi_env(env); +} + +napi_status js_free_runtime(napi_runtime runtime) { + return qjs_free_runtime(runtime); +} + +napi_status js_execute_script(napi_env env, + napi_value script, + const char *file, + napi_value *result) { + return qjs_execute_script(env, script, file, result); +} + +napi_status js_execute_pending_jobs(napi_env env) { + return qjs_execute_pending_jobs(env); +} + +napi_status +js_adjust_external_memory(napi_env env, int64_t changeInBytes, int64_t *externalMemory) { + napi_adjust_external_memory(env, changeInBytes, externalMemory); + return napi_ok; +} + +napi_status js_cache_script(napi_env env, const char *source, const char *file) { + return napi_ok; +} + +napi_status js_run_cached_script(napi_env env, const char *file, napi_value script, void *cache, + napi_value *result) { + return napi_ok; +} + + +napi_status js_get_runtime_version(napi_env env, napi_value *version) { + napi_create_string_utf8(env, "QuickJS", NAPI_AUTO_LENGTH, version); + + return napi_ok; +} diff --git a/NativeScript/napi/android/quickjs/quickjs-api.c b/NativeScript/napi/android/quickjs/quickjs-api.c new file mode 100644 index 000000000..b7102a056 --- /dev/null +++ b/NativeScript/napi/android/quickjs/quickjs-api.c @@ -0,0 +1,4573 @@ +#include +#include +#include +#include +#include "js_native_api.h" +#include "quicks-runtime.h" + +#ifndef __QJS_NG__ +#include "cutils.h" +#endif + +#ifdef __ANDROID__ + +#include + +#endif + +#ifdef USE_MIMALLOC + +#include "mimalloc.h" + +#else + +#include + +#define mi_malloc(size) \ + malloc(size) + +#define mi_calloc(count, size) \ + calloc(count,size) + +#define mi_free(ptr) \ + free(ptr) + +#define mi_realloc(ptr, size) \ + realloc(ptr, size) + +#endif + +#ifdef USE_MIMALLOC + + +#ifdef __QJS_NG__ +static void *js_mi_calloc(void *opaque, size_t count, size_t size) { + return mi_heap_calloc((mi_heap_t *) opaque, count, size); +} + +static void *js_mi_malloc(void *opaque, size_t size) { + return mi_heap_malloc((mi_heap_t *) opaque, size); +} + +static void js_mi_free(void *opaque, void *ptr) { + if (!ptr) + return; + mi_free(ptr); +} + +static void *js_mi_realloc(void *opaque, void *ptr, size_t size) { + return mi_heap_realloc((mi_heap_t *) opaque, ptr, size); +} + +static const JSMallocFunctions mi_mf = { + js_mi_calloc, + js_mi_malloc, + js_mi_free, + js_mi_realloc, + mi_malloc_usable_size +}; +#endif + +#ifndef __QJS_NG__ + +#if defined(__APPLE__) +#define MALLOC_OVERHEAD 0 +#else +#define MALLOC_OVERHEAD 8 +#endif + +static void *js_def_malloc(JSMallocState *s, size_t size) +{ + void *ptr; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(size != 0); + + if (unlikely(s->malloc_size + size > s->malloc_limit)) + return NULL; + + ptr = mi_heap_malloc((mi_heap_t *) s->opaque, size); + if (!ptr) + return NULL; + + s->malloc_count++; + s->malloc_size += mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + return ptr; +} + +static void js_def_free(JSMallocState *s, void *ptr) +{ + if (!ptr) + return; + + s->malloc_count--; + s->malloc_size -= mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + mi_free(ptr); +} + +static void *js_def_realloc(JSMallocState *s, void *ptr, size_t size) +{ + size_t old_size; + + if (!ptr) { + if (size == 0) + return NULL; + return js_def_malloc(s, size); + } + old_size = mi_malloc_usable_size(ptr); + if (size == 0) { + s->malloc_count--; + s->malloc_size -= old_size + MALLOC_OVERHEAD; + mi_free(ptr); + return NULL; + } + if (s->malloc_size + size - old_size > s->malloc_limit) + return NULL; + + ptr = mi_heap_realloc((mi_heap_t *) s->opaque, ptr, size); + if (!ptr) + return NULL; + + s->malloc_size += mi_malloc_usable_size(ptr) - old_size; + return ptr; +} + +static const JSMallocFunctions mi_mf = { + js_def_malloc, + js_def_free, + js_def_realloc, + mi_malloc_usable_size, +}; + +#endif + + + + + + +#endif + +/** + * -------------------------------------- + * SAFE LIST TRANSVERSAL MACROS + * -------------------------------------- + */ + +#ifndef SLIST_FOREACH_SAFE +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST((head)); (var) && ((tvar) = SLIST_NEXT((var), field), 1); (var) = (tvar)) +#endif + +#ifndef LIST_FOREACH_SAFE +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); (var) && ((tvar) = LIST_NEXT((var), field), 1); (var) = (tvar)) +#endif + +/** + * -------------------------------------- + * NAPI UNDEFINED AND NULL + * -------------------------------------- + */ +static JSValueConst JSUndefined = JS_UNDEFINED; +static JSValueConst JSNull = JS_NULL; + +/** + * -------------------------------------- + * NAPI DATA STRUCTURES + * -------------------------------------- + */ + +typedef enum { + HANDLE_STACK_ALLOCATED, + HANDLE_HEAP_ALLOCATED +} HandleType; + +typedef struct Handle { + JSValue value; // size_t * 2 + SLIST_ENTRY(Handle) + node; // size_t + + HandleType type; +} Handle; + +typedef struct napi_handle_scope__ { + LIST_ENTRY(napi_handle_scope__) + node; // size_t + SLIST_HEAD(, Handle) + handleList; // size_t + bool escapeCalled; + Handle stackHandles[8]; + int handleCount; + HandleType type; +} napi_handle_scope__; + +typedef struct napi_ref__ { + JSValue value; // size_t * 2 + LIST_ENTRY(napi_ref__) + node; // size_t * 2 + uint8_t referenceCount; // 8 +} napi_ref__; + +typedef struct napi_deferred__ { + napi_value resolve; + napi_value reject; +} napi_deferred__; + +typedef struct ExternalInfo { + void *data; // size_t + void *finalizeHint; // size_t + napi_finalize finalizeCallback; // size_t +} ExternalInfo; + +typedef struct NapiHostObjectInfo { + void *data; + napi_ref ref; + napi_finalize finalize_cb; + bool is_array; + napi_ref getter; + napi_ref setter; +} NapiHostObjectInfo; + + +typedef struct JsAtoms { + JSAtom napi_external; + JSAtom registerFinalizer; + JSAtom constructor; + JSAtom prototype; + JSAtom napi_buffer; + JSAtom NAPISymbolFor; + JSAtom object; + JSAtom freeze; + JSAtom seal; + JSAtom Symbol; + JSAtom length; + JSAtom is; + JSAtom byteLength; + JSAtom buffer; + JSAtom byteOffset; + JSAtom name; + JSAtom napi_typetag; + JSAtom weakref; +} JsAtoms; + +typedef struct napi_env__ { + JSValue referenceSymbolValue; // size_t * 2 + napi_runtime runtime; // size_t + JSContext *context; // size_t + LIST_HEAD(, napi_handle_scope__) + handleScopeList; // size_t + LIST_HEAD(, napi_ref__) + referencesList; // size_t + bool isThrowNull; + ExternalInfo *instanceData; + JSValue finalizationRegistry; + napi_extended_error_info last_error; + JsAtoms atoms; + ExternalInfo *gcBefore; + ExternalInfo *gcAfter; + int js_enter_state; + int64_t usedMemory; +} napi_env__; + +typedef struct napi_runtime__ { + JSRuntime *runtime; // size_t +#ifdef USE_MIMALLOC + mi_heap_t *heap; +#endif + JSClassID constructorClassId; // uint32_t + JSClassID functionClassId; // uint32_t + JSClassID externalClassId; // uint32_t + JSClassID napiHostObjectClassId; +} napi_runtime__; + +typedef struct napi_callback_info__ { + JSValueConst newTarget; // size_t * 2 + JSValueConst thisArg; // size_t * 2 + JSValueConst *argv; // size_t * 2 + void *data; // size_t + int argc; +} napi_callback_info__; + +typedef struct FunctionInfo { + void *data; // size_t + napi_callback callback; // size_t +} FunctionInfo; + +typedef struct ExternalBufferInfo { + void *hint; + napi_finalize finalize_cb; +} ExternalBufferInfo; + +/** + * ------------------------------------- + * MICROTASK HANDLING + * ------------------------------------- + */ + +static inline void js_enter(napi_env env) { + env->js_enter_state++; +} + +static inline void js_exit(napi_env env) { + if (--env->js_enter_state <= 0) { + JS_ClearWeakRefKeepAlives(JS_GetRuntime(env->context)); + qjs_execute_pending_jobs(env); + } +} + +/** + * -------------------------------------- + * NAPI DATA FINALIZERS + * -------------------------------------- + */ + +static void function_finalizer(JSRuntime *rt, JSValue val) { + napi_env env = (napi_env) JS_GetRuntimeOpaque(rt); + FunctionInfo *functionInfo = (FunctionInfo *) JS_GetOpaque(val, env->runtime->functionClassId); + mi_free(functionInfo); +} + +static void external_finalizer(JSRuntime *rt, JSValue val) { + napi_env env = (napi_env) JS_GetRuntimeOpaque(rt); + ExternalInfo *externalInfo = JS_GetOpaque(val, env->runtime->externalClassId); + if (externalInfo == NULL) { + return; + } + if (externalInfo->finalizeCallback != NULL) { + externalInfo->finalizeCallback(env, externalInfo->data, externalInfo->finalizeHint); + } + mi_free(externalInfo); +} + +static void buffer_finalizer(JSRuntime *rt, void *opaque, void *data) { + napi_env env = (napi_env) JS_GetRuntimeOpaque(rt); + ExternalBufferInfo *external_buffer_info = opaque; + if (external_buffer_info->finalize_cb) { + external_buffer_info->finalize_cb(env, data, external_buffer_info->hint); + } + mi_free(external_buffer_info); +} + +/** + * ------------------------------------- + * NAPI ERROR MANAGEMENT + * ------------------------------------- + */ + +static inline napi_status napi_set_last_error(napi_env env, + napi_status error_code, + const char *error_message, + uint32_t engine_error_code, + void *engine_reserved) { + if (error_code == napi_ok && env->last_error.error_code == napi_ok) + return napi_ok; + + env->last_error.error_code = error_code; + env->last_error.engine_error_code = engine_error_code || 0; + env->last_error.engine_reserved = engine_reserved; + env->last_error.error_message = error_message; + + return error_code; +} + +static inline napi_status napi_clear_last_error(napi_env env) { + if (env->last_error.error_code == napi_ok) + return napi_ok; + return napi_set_last_error(env, napi_ok, NULL, 0, NULL); +} + +/** + * -------------------------------------- + * NAPI MACROS + * -------------------------------------- + */ + +#define TRUTHY(expr) \ + __builtin_expect(expr, false) + + +#define RETURN_STATUS_IF_FALSE(condition, status) \ + if (__builtin_expect(!(condition), false)) \ + { \ + return napi_set_last_error(env, status, NULL, 0, NULL); \ + } + + +#define CHECK_NAPI(expr) \ + { \ + napi_status status = expr; \ + if (__builtin_expect(status != napi_ok, false)) \ + { \ + return napi_set_last_error(env, status, NULL, 0, NULL); \ + } \ + } + + +#define CHECK_ARG(arg) \ + if (__builtin_expect(!(arg), false)) \ + { \ + return napi_set_last_error(env, napi_invalid_arg, NULL, 0, NULL); \ + } + +#define NAPI_PREAMBLE(env) \ + { \ + CHECK_ARG(env) \ +JSValue exceptionValue = JS_GetException((env)->context); \ +if (__builtin_expect(JS_IsException(exceptionValue), false)) { \ + print_exception(env, exceptionValue); \ +return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); \ +} \ +RETURN_STATUS_IF_FALSE(!(env)->isThrowNull, napi_pending_exception) \ + } + +#ifndef NDEBUG +static const char *const FUNCTION_CLASS_ID_ZERO = "functionClassId must not be 0."; +#endif + +/** + * -------------------------------------- + * NAPI HANDLE SCOPES + * -------------------------------------- + */ + +static inline napi_status CreateJSValueHandle(napi_env env, JSValue value, struct Handle **result) { + CHECK_ARG(env) + CHECK_ARG(result) + + RETURN_STATUS_IF_FALSE(!LIST_EMPTY(&env->handleScopeList), napi_handle_scope_empty) + + napi_handle_scope handleScope = LIST_FIRST(&env->handleScopeList); + + if (handleScope->handleCount < 8) { + Handle *handle = &handleScope->stackHandles[handleScope->handleCount]; + handle->type = HANDLE_STACK_ALLOCATED; + *result = handle; + handleScope->handleCount++; + } else { + *result = (Handle *) mi_malloc(sizeof(Handle)); + (*result)->type = HANDLE_HEAP_ALLOCATED; + } + + (*result)->value = value; + SLIST_INSERT_HEAD(&handleScope->handleList, *result, node); + + return napi_clear_last_error(env); +} + +static inline napi_status CreateScopedResult(napi_env env, JSValue value, napi_value *result) { + struct Handle *jsValueHandle; + napi_status status = CreateJSValueHandle(env, value, &jsValueHandle); + if (status != napi_ok) { + JS_FreeValue(env->context, value); + return napi_set_last_error(env, status, NULL, 0, NULL); + } + *result = (napi_value) &jsValueHandle->value; + return napi_clear_last_error(env); +} + +#define NAPI_OPEN_HANDLE_SCOPE \ + napi_handle_scope__ handleScope; \ + handleScope.type = HANDLE_STACK_ALLOCATED; \ + handleScope.handleCount = 0;\ + handleScope.escapeCalled = false; \ + SLIST_INIT(&handleScope.handleList); \ + LIST_INSERT_HEAD(&env->handleScopeList, &handleScope, node); + +#define NAPI_CLOSE_HANDLE_SCOPE \ + Handle *handle, *tempHandle; \ + SLIST_FOREACH_SAFE(handle, &handleScope.handleList, node, tempHandle) { \ + JS_FreeValue(env->context, handle->value); \ + handle->value = JSUndefined; \ + SLIST_REMOVE(&handleScope.handleList, handle, Handle, node); \ + mi_free(handle); \ + } \ + LIST_REMOVE(&handleScope, node); + +napi_status napi_open_handle_scope(napi_env env, napi_handle_scope *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + napi_handle_scope__ *handleScope = (napi_handle_scope__ *) mi_malloc( + sizeof(napi_handle_scope__)); + + RETURN_STATUS_IF_FALSE(handleScope, napi_memory_error) + handleScope->type = HANDLE_HEAP_ALLOCATED; + handleScope->handleCount = 0; + handleScope->escapeCalled = false; + *result = handleScope; + SLIST_INIT(&(*result)->handleList); + + LIST_INSERT_HEAD(&env->handleScopeList, *result, node); + + return napi_clear_last_error(env); +} + +napi_status napi_close_handle_scope(napi_env env, napi_handle_scope scope) { + CHECK_ARG(env) + CHECK_ARG(scope) + + assert(LIST_FIRST(&env->handleScopeList) == scope && + "napi_close_handle_scope() or napi_close_escapable_handle_scope() should follow FILO rule."); + + Handle *handle, *tempHandle; + SLIST_FOREACH_SAFE(handle, &scope->handleList, node, tempHandle) { + JS_FreeValue(env->context, handle->value); + handle->value = JSUndefined; + + // Instead of freeing, return the handle to the pool for reuse + SLIST_REMOVE(&scope->handleList, handle, Handle, node); + if (handle->type == HANDLE_HEAP_ALLOCATED) { + mi_free(handle); + } + } + + LIST_REMOVE(scope, node); + mi_free(scope); + + return napi_clear_last_error(env); +} + +napi_status napi_open_escapable_handle_scope(napi_env env, napi_escapable_handle_scope *result) { + + CHECK_ARG(env) + CHECK_ARG(result) + + napi_handle_scope__ *handleScope = (napi_handle_scope__ *) mi_malloc( + sizeof(napi_handle_scope__)); + handleScope->type = HANDLE_HEAP_ALLOCATED; + handleScope->handleCount = 0; + RETURN_STATUS_IF_FALSE(handleScope, napi_memory_error) + SLIST_INIT(&handleScope->handleList); + handleScope->escapeCalled = false; + LIST_INSERT_HEAD(&env->handleScopeList, handleScope, node); + + *result = handleScope; + + return napi_clear_last_error(env); +} + +napi_status +napi_close_escapable_handle_scope(napi_env env, napi_escapable_handle_scope escapableScope) { + CHECK_ARG(env) + CHECK_ARG(escapableScope) + + assert(LIST_FIRST(&env->handleScopeList) == escapableScope && + "napi_close_handle_scope() or napi_close_escapable_handle_scope() should follow FILO rule."); + + Handle *handle, *tempHandle; + SLIST_FOREACH_SAFE(handle, &escapableScope->handleList, node, tempHandle) { + JS_FreeValue(env->context, handle->value); + handle->value = JSUndefined; + + // Instead of freeing, return the handle to the pool for reuse + SLIST_REMOVE(&escapableScope->handleList, handle, Handle, node); + if (handle->type == HANDLE_HEAP_ALLOCATED) { + mi_free(handle); + } + } + + LIST_REMOVE(escapableScope, node); + mi_free(escapableScope); + + return napi_clear_last_error(env); +} + + +napi_status napi_escape_handle(napi_env env, napi_escapable_handle_scope scope, napi_value escapee, + napi_value *result) { + + CHECK_ARG(env) + CHECK_ARG(scope) + CHECK_ARG(escapee) + + RETURN_STATUS_IF_FALSE(!scope->escapeCalled, napi_escape_called_twice) + // Get the outer handle scope + napi_handle_scope handleScope = LIST_NEXT(scope, node); + RETURN_STATUS_IF_FALSE(handleScope, napi_handle_scope_empty) + + Handle *handle = (Handle *) mi_malloc(sizeof(Handle)); + + RETURN_STATUS_IF_FALSE(handle, napi_memory_error) + + scope->escapeCalled = true; + handle->value = JS_DupValue(env->context, *((JSValue *) escapee)); + SLIST_INSERT_HEAD(&handleScope->handleList, handle, node); + + if (result != NULL) { + *result = (napi_value) &handle->value; + } + + return napi_clear_last_error(env); +} + +/** + * -------------------------------------- + * EXCEPTIONS + * -------------------------------------- + */ + +napi_status napi_throw(napi_env env, napi_value error) { + CHECK_ARG(env) + CHECK_ARG(error) + + JS_Throw(env->context, JS_IsNull((*(JSValue *) error)) ? JSNull : JS_DupValue(env->context, + *((JSValue *) error))); + + return napi_clear_last_error(env); +} + +napi_status napi_throw_error(napi_env env, const char *code, const char *msg) { + CHECK_ARG(env) + CHECK_ARG(msg) + if (JS_HasException(env->context)) { + return napi_pending_exception; + } + + JSValue error = JS_NewError(env->context); + JS_SetPropertyStr(env->context, error, "message", JS_NewString(env->context, msg)); + + if (code != NULL) { + JS_SetPropertyStr(env->context, error, "code", JS_NewString(env->context, code)); + } + + JS_Throw(env->context, error); + + return napi_clear_last_error(env); +} + +napi_status napi_throw_range_error(napi_env env, const char *code, const char *msg) { + CHECK_ARG(env) + CHECK_ARG(msg) + + if (JS_HasException(env->context)) { + return napi_pending_exception; + } + + JSValue error = JS_NewError(env->context); + JS_SetPropertyStr(env->context, error, "message", JS_NewString(env->context, msg)); + + if (code != NULL) { + JS_SetPropertyStr(env->context, error, "code", JS_NewString(env->context, code)); + } + + JS_SetPropertyStr(env->context, error, "name", JS_NewString(env->context, "RangeError")); + + JS_Throw(env->context, error); + + + return napi_clear_last_error(env); +} + +napi_status napi_throw_type_error(napi_env env, const char *code, const char *msg) { + CHECK_ARG(env) + CHECK_ARG(msg) + + if (JS_HasException(env->context)) { + return napi_pending_exception; + } + + JSValue error = JS_NewError(env->context); + JS_SetPropertyStr(env->context, error, "message", JS_NewString(env->context, msg)); + + if (code != NULL) { + JS_SetPropertyStr(env->context, error, "code", JS_NewString(env->context, code)); + } + + JS_SetPropertyStr(env->context, error, "name", JS_NewString(env->context, "TypeError")); + + JS_Throw(env->context, error); + + return napi_clear_last_error(env); +} + + +napi_status napi_is_exception_pending(napi_env env, bool *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + *result = JS_HasException(env->context); + + return napi_clear_last_error(env); +} + +napi_status napi_get_and_clear_last_exception(napi_env env, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + CHECK_ARG(env->context) + + JSValue exceptionValue = JS_GetException(env->context); + + if (JS_IsUninitialized(exceptionValue) || JS_IsNull(exceptionValue)) { + *result = (napi_value) &JSUndefined; + return napi_clear_last_error(env); + } + + return CreateScopedResult(env, exceptionValue, result); +} + +napi_status napi_get_last_error_info(napi_env env, const napi_extended_error_info **result) { + CHECK_ARG(env) + CHECK_ARG(result) + + *result = env->last_error.error_code == napi_ok ? NULL : &env->last_error; + + return napi_clear_last_error(env); +} + +napi_status napi_create_error(napi_env env, napi_value code, napi_value msg, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(msg); + CHECK_ARG(result) + + JSValue error = JS_NewError(env->context); + JS_SetPropertyStr(env->context, error, "message", + JS_DupValue(env->context, *((JSValue *) msg))); + + if (code != NULL) { + JS_SetPropertyStr(env->context, error, "code", + JS_DupValue(env->context, *((JSValue *) code))); + } + + return CreateScopedResult(env, error, result); +} + +napi_status +napi_create_type_error(napi_env env, napi_value code, napi_value msg, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue error = JS_NewError(env->context); + JS_SetPropertyStr(env->context, error, "message", + JS_DupValue(env->context, *((JSValue *) msg))); + + if (code != NULL) { + JS_SetPropertyStr(env->context, error, "code", + JS_DupValue(env->context, *((JSValue *) code))); + } + + JS_SetPropertyStr(env->context, error, "name", JS_NewString(env->context, "TypeError")); + + return CreateScopedResult(env, error, result); +} + +napi_status +napi_create_range_error(napi_env env, napi_value code, napi_value msg, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue error = JS_NewError(env->context); + JS_SetPropertyStr(env->context, error, "message", + JS_DupValue(env->context, *((JSValue *) msg))); + + if (code != NULL) { + JS_SetPropertyStr(env->context, error, "code", + JS_DupValue(env->context, *((JSValue *) code))); + } + + JS_SetPropertyStr(env->context, error, "name", JS_NewString(env->context, "RangeError")); + + return CreateScopedResult(env, error, result); +} + +napi_status +napi_create_syntax_error(napi_env env, napi_value code, napi_value msg, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue error = JS_NewError(env->context); + JS_SetPropertyStr(env->context, error, "message", + JS_DupValue(env->context, *((JSValue *) msg))); + + if (code != NULL) { + JS_SetPropertyStr(env->context, error, "code", + JS_DupValue(env->context, *((JSValue *) code))); + } + + JS_SetPropertyStr(env->context, error, "name", JS_NewString(env->context, "SyntaxError")); + + return CreateScopedResult(env, error, result); +} + + +/** + * -------------------------------------- + * REFERENCE MANAGEMENT + * -------------------------------------- + */ + +napi_status +napi_create_reference(napi_env env, napi_value value, uint32_t initialRefCount, napi_ref *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + *result = (napi_ref__ *) mi_malloc(sizeof(napi_ref__)); + RETURN_STATUS_IF_FALSE(*result, napi_memory_error) + + JSValue jsValue = *((JSValue *) value); + + if (JS_IsUndefined(jsValue)) { + (*result)->value = JS_UNDEFINED; + (*result)->referenceCount = 0; + LIST_INSERT_HEAD(&env->referencesList, *result, node); + return napi_clear_last_error(env); + } + + if (initialRefCount == 0) { + JSValue global = JS_GetGlobalObject(env->context); + JSValue JS_WeakRef_Ctor = JS_GetProperty(env->context, global, env->atoms.weakref); + JSValue args[1] = { + jsValue + }; + JSValue weak_ref = JS_CallConstructor(env->context, JS_WeakRef_Ctor, 1, args); + + JS_FreeValue(env->context, global); + JS_FreeValue(env->context, JS_WeakRef_Ctor); + (*result)->referenceCount = 0; + (*result)->value = weak_ref; + } else { + (*result)->referenceCount = initialRefCount; + (*result)->value = JS_DupValue(env->context, jsValue); + } + + LIST_INSERT_HEAD(&env->referencesList, *result, node); + + return napi_clear_last_error(env); +} + +napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t *result) { + CHECK_ARG(env) + CHECK_ARG(ref) + + if (!ref->referenceCount) { + JSValue value = JS_WeakRef_Deref(env->context, ref->value); + JS_FreeValue(env->context, ref->value); + ref->value = value; + } + + uint8_t count = ++ref->referenceCount; + if (result) { + *result = count; + } + + return napi_clear_last_error(env); +} + +napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t *result) { + CHECK_ARG(env) + CHECK_ARG(ref) + + RETURN_STATUS_IF_FALSE(ref->referenceCount, napi_generic_failure) + + if (ref->referenceCount == 1) { + JSValue global = JS_GetGlobalObject(env->context); + JSValue JS_WeakRef_Ctor = JS_GetProperty(env->context, global, env->atoms.weakref); + + JSValue args[1] = { + ref->value + }; + JSValue weak_ref = JS_CallConstructor(env->context, JS_WeakRef_Ctor, 1, args); + JS_FreeValue(env->context, global); + JS_FreeValue(env->context, JS_WeakRef_Ctor); + JS_FreeValue(env->context, ref->value); + ref->value = weak_ref; + } + + uint8_t count = --ref->referenceCount; + if (result) { + *result = count; + } + + return napi_clear_last_error(env); +} + + +napi_status napi_get_reference_value(napi_env env, napi_ref ref, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(ref) + CHECK_ARG(result) + + if (!ref->referenceCount && JS_IsUndefined(ref->value)) { + CreateScopedResult(env, JS_UNDEFINED, result); + } + + JSValue value; + if (ref->referenceCount > 0) { + value = JS_DupValue(env->context, ref->value); + } else { + value = JS_WeakRef_Deref(env->context, ref->value); + } + + return CreateScopedResult(env, value, result); +} + +napi_status napi_delete_reference(napi_env env, napi_ref ref) { + CHECK_ARG(env) + CHECK_ARG(ref) + + if (!JS_IsUndefined(ref->value)) { + JS_FreeValue(env->context, ref->value); + ref->value = JSUndefined; + } + + LIST_REMOVE(ref, node); + + mi_free(ref); + + return napi_clear_last_error(env); +} + + +/** + * -------------------------------------- + * NATIVE TO JS VALUE CONVERSION + * -------------------------------------- + */ + +napi_status napi_create_string_latin1(napi_env env, + const char *str, + size_t length, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + CHECK_ARG(str) + + JSValue value = JS_NewStringLen(env->context, str, + (length == NAPI_AUTO_LENGTH) ? strlen(str) : length); + return CreateScopedResult(env, value, result); +} + +size_t char16_t_length(const char16_t *str) { + size_t length = 0; + + while (str[length] != 0) { + ++length; + } + + return length; +} + +napi_status napi_create_string_utf16(napi_env env, + const char16_t *str, + size_t length, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + CHECK_ARG(str) + + JSValue value = JS_NewString16(env->context, (uint16_t *) str, + length == NAPI_AUTO_LENGTH ? (int) char16_t_length(str) + : (int) length); + + return CreateScopedResult(env, value, result); +} + +napi_status napi_create_string_utf8(napi_env env, + const char *str, + size_t length, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + CHECK_ARG(str) + + + JSValue value = JS_NewStringLen(env->context, str, + (length == NAPI_AUTO_LENGTH) ? strlen(str) : length); + + return CreateScopedResult(env, value, result); +} + +napi_status napi_create_int32(napi_env env, int32_t value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue jsValue = JS_NewInt32(env->context, value); + + return CreateScopedResult(env, jsValue, result); +} + +napi_status napi_create_uint32(napi_env env, uint32_t value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue jsValue = JS_NewInt32(env->context, value); + return CreateScopedResult(env, jsValue, result); +} + +napi_status napi_create_int64(napi_env env, int64_t value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue jsValue = JS_NewInt64(env->context, value); + return CreateScopedResult(env, jsValue, result); +} + +napi_status napi_create_uint64(napi_env env, uint64_t value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue jsValue = JS_NewInt64(env->context, value); + return CreateScopedResult(env, jsValue, result); +} + +napi_status napi_create_double(napi_env env, double value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue jsValue = JS_NewFloat64(env->context, value); + return CreateScopedResult(env, jsValue, result); +} + +JSValue +JS_CreateBigIntWords(JSContext *context, int signBit, size_t wordCount, const uint64_t *words) { + JSValue thisVar = JS_UNDEFINED; + const size_t Count = 20; + const size_t Two = 2; + if (wordCount <= 0 || wordCount > Count || words == NULL) { + return JS_EXCEPTION; + } + + JSValue signValue = JS_NewInt32(context, (signBit % Two)); + if (JS_IsException(signValue)) { + return JS_EXCEPTION; + } + + JSValue wordsValue = JS_NewArray(context); + if (JS_IsException(wordsValue)) { + return JS_EXCEPTION; + } + + for (size_t i = 0; i < wordCount; i++) { + // shift 32 bits right to get high bit + JSValue idxValueHigh = JS_NewUint32(context, (uint32_t) (words[i] >> 32)); + // gets lower 32 bits + JSValue idxValueLow = JS_NewUint32(context, (uint32_t) (words[i] & 0xFFFFFFFF)); + if (!(JS_IsException(idxValueHigh)) && !(JS_IsException(idxValueLow))) { + JS_SetPropertyUint32(context, wordsValue, (i * Two), idxValueHigh); + JS_SetPropertyUint32(context, wordsValue, (i * Two + 1), idxValueLow); + } + } + + JSValue argv[2] = {signValue, wordsValue}; + JSValue global = JS_GetGlobalObject(context); + JSValue CreateBigIntWords = JS_GetPropertyStr(context, global, "CreateBigIntWords"); + + JSValue ret = JS_Call(context, CreateBigIntWords, thisVar, 2, (JSValue *) &argv); + JS_FreeValue(context, global); + JS_FreeValue(context, CreateBigIntWords); + JS_FreeValue(context, signValue); + JS_FreeValue(context, wordsValue); + return ret; +} + +bool ParseBigIntWordsInternal(JSContext *context, JSValue value, int *signBit, size_t *wordCount, + uint64_t *words) { + int cntValue = 0; + if (wordCount == NULL) { + return false; + } + + JSValue jsValue = JS_GetPropertyStr(context, value, "count"); + if (!JS_IsException(jsValue)) { + JS_ToInt32(context, &cntValue, jsValue); + JS_FreeValue(context, jsValue); + } else { + return false; + } + + if (signBit == NULL && words == NULL) { + *wordCount = cntValue; + return true; + } else if (signBit != NULL && words != NULL) { + cntValue = (cntValue > *wordCount) ? *wordCount : cntValue; + jsValue = JS_GetPropertyStr(context, value, "sign"); + if (!JS_IsException(jsValue)) { + int sigValue = 0; + JS_ToInt32(context, &sigValue, jsValue); + *signBit = sigValue; + JS_FreeValue(context, jsValue); + } + + jsValue = JS_GetPropertyStr(context, value, "words"); + if (!JS_IsException(jsValue)) { + JSValue element; + int64_t cValue = 0; + for (uint32_t i = 0; i < (uint32_t) cntValue; i++) { + element = JS_GetPropertyUint32(context, jsValue, i); + JS_ToInt64Ext(context, &cValue, element); + JS_FreeValue(context, element); + words[i] = (uint64_t) cValue; + } + JS_FreeValue(context, jsValue); + *wordCount = cntValue; + return true; + } + } + return false; +} + +bool JS_GetBigIntWords(JSContext *context, JSValue value, int *signBit, size_t *wordCount, + uint64_t *words) { + + bool rev = false; + JSValue thisVar = JS_UNDEFINED; + if (wordCount == NULL) { + return false; + } + + JSValue global = JS_GetGlobalObject(context); + JSValue GetBigIntWords = JS_GetPropertyStr(context, global, "GetBigIntWords"); + JSValue bigObj = JS_Call(context, GetBigIntWords, JS_UNDEFINED, 1, &value); + + if (!JS_IsException(bigObj)) { + if (JS_IsObject(bigObj)) { + rev = ParseBigIntWordsInternal(context, bigObj, signBit, wordCount, words); + } + } + + JS_FreeValue(context, global); + JS_FreeValue(context, GetBigIntWords); + JS_FreeValue(context, bigObj); + return rev; +} + +//typedef struct JS_BigFloatExt { +// JSRefCountHeader header; +// bf_t num; +//} JS_BigFloatExt; +// +//bool JS_ToInt64WithBigInt(JSContext *context, JSValueConst value, int64_t *pres, bool *lossless) { +// if (pres == NULL || lossless == NULL) { +// return 0; +// } +// +// bool rev = false; +// JSValue val = JS_DupValue(context, value); +// JS_BigFloatExt *p = (JS_BigFloatExt *) JS_VALUE_GET_PTR(val); +// if (p) { +// int opFlag = bf_get_int64(pres, &p->num, 0); +// if (lossless != NULL) { +// *lossless = (opFlag == 0); +// } +// rev = true; +// } +// JS_FreeValue(context, val); +// return rev; +//} +// +//bool JS_ToUInt64WithBigInt(JSContext *context, JSValueConst value, uint64_t *pres, bool *lossless) { +// if (pres == NULL || lossless == NULL) { +// return false; +// } +// +// bool rev = false; +// JSValue val = JS_DupValue(context, value); +// JS_BigFloatExt *p = (JS_BigFloatExt *) JS_VALUE_GET_PTR(val); +// if (p) { +// int opFlag = bf_get_uint64(pres, &p->num); +// if (lossless != NULL) { +// *lossless = (opFlag == 0); +// } +// rev = true; +// } +// JS_FreeValue(context, val); +// return rev; +//} + +napi_status napi_create_bigint_int64(napi_env env, int64_t value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue jsValue = JS_NewBigInt64(env->context, value); + return CreateScopedResult(env, jsValue, result); +} + +napi_status napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue jsValue = JS_NewBigUint64(env->context, value); + return CreateScopedResult(env, jsValue, result); +} + +napi_status napi_create_bigint_words(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t *words, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue value = JS_CreateBigIntWords(env->context, sign_bit, word_count, words); + return CreateScopedResult(env, value, result); +} + + +/** + * -------------------------------------- + * OBJECT CREATION + * -------------------------------------- + */ + +napi_status napi_create_object(napi_env env, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue value = JS_NewObject(env->context); + + return CreateScopedResult(env, value, result); +} + +napi_status napi_create_array(napi_env env, napi_value *result) { + + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue value = JS_NewArray(env->context); + + return CreateScopedResult(env, value, result); +} + +napi_status napi_create_array_with_length(napi_env env, + size_t length, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue value = JS_NewArray(env->context); + + // Set array length + if (length != 0) { + JSValue jsLength = JS_NewInt32(env->context, (int32_t) length); + JS_SetPropertyStr(env->context, value, "length", jsLength); + JS_FreeValue(env->context, jsLength); + } + + return CreateScopedResult(env, value, result); +} + +napi_status +napi_create_external(napi_env env, void *data, napi_finalize finalize_cb, void *finalize_hint, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + ExternalInfo *externalInfo = mi_malloc(sizeof(ExternalInfo)); + + externalInfo->data = data; + externalInfo->finalizeHint = finalize_hint; + externalInfo->finalizeCallback = NULL; + + JSValue object = JS_NewObjectClass(env->context, (int) env->runtime->externalClassId); + + if (JS_IsException(object)) { + mi_free(externalInfo); + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + JS_SetOpaque(object, externalInfo); + + napi_status status = CreateScopedResult(env, object, result); + + externalInfo->finalizeCallback = finalize_cb; + + return napi_clear_last_error(env); +} + + +napi_status napi_create_arraybuffer(napi_env env, + size_t byte_length, + void **data, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + size_t size = 0; + JSValue value = JS_NewArrayBufferCopy(env->context, NULL, byte_length); + + if (data) { + *data = JS_GetArrayBuffer(env->context, &size, value); + } + + return CreateScopedResult(env, value, result); +} + +#define MARK_AS_NAPI_BUFFER \ + JS_SetProperty(env->context, value, env->atoms.napi_buffer, JS_NewBool(env->context, true)); + +napi_status napi_create_buffer(napi_env env, + size_t size, + void **data, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + size_t buf_size = 0; + JSValue value = JS_NewArrayBufferCopy(env->context, NULL, size); + + if (data) { + *data = JS_GetArrayBuffer(env->context, &size, value); + } + + MARK_AS_NAPI_BUFFER + + return CreateScopedResult(env, value, result); +} + +static size_t napi_sizeof_typedarray_type(napi_typedarray_type type) { + + switch (type) { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + return sizeof(int8_t); + case napi_int16_array: + case napi_uint16_array: + return sizeof(int16_t); + case napi_int32_array: + case napi_uint32_array: + case napi_float32_array: + return sizeof(int32_t); + case napi_float64_array: + return sizeof(double); + case napi_bigint64_array: + case napi_biguint64_array: + return sizeof(int64_t); + default: + // Handle other cases or return an error value + return 0; + } +} + + +const char *typedArrayClassNames[] = { + "Int8Array", + "Uint8Array", + "Uint8ClampedArray", + "Int16Array", + "Uint16Array", + "Int32Array", + "Uint32Array", + "Float32Array", + "Float64Array", + "BigInt64Array", + "BigUint64Array", +}; + +napi_status napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + CHECK_ARG(arraybuffer) + + // Ensure type is within bounds + if (type < 0 || type >= sizeof(typedArrayClassNames) / sizeof(typedArrayClassNames[0])) { + return napi_set_last_error(env, napi_invalid_arg, NULL, 0, NULL); + } + + if (!JS_IsArrayBuffer2(env->context, *(JSValue *) arraybuffer)) { + return napi_set_last_error(env, napi_arraybuffer_expected, NULL, 0, NULL); + } + + size_t size_of_element = napi_sizeof_typedarray_type(type); + + size_t bufferSize = 0; + void *buffer = JS_GetArrayBuffer(env->context, &bufferSize, *((JSValue *) arraybuffer)); + + // It's required that (length * size_of_element) + byte_offset + // should be <= the size in bytes of the array passed in. + // If not, a RangeError exception is raised. + size_t total_size = (length * size_of_element) + byte_offset; + if (total_size > bufferSize) { + return napi_throw_range_error(env, "napi_generic_failure", "Invalid typed array length"); + } + + JSValue global = JS_GetGlobalObject(env->context); + JSValue typedArrayConstructor = JS_GetPropertyStr(env->context, global, + typedArrayClassNames[type]); + JS_FreeValue(env->context, global); + + if (JS_IsException(typedArrayConstructor)) { + return napi_set_last_error(env, napi_generic_failure, NULL, 0, NULL); + } + + JSValue params[] = { + *((JSValue *) arraybuffer), + JS_NewInt64(env->context, byte_offset), + JS_NewInt64(env->context, length), + }; + + JSValue value = JS_CallConstructor(env->context, typedArrayConstructor, 3, params); + JS_FreeValue(env->context, typedArrayConstructor); + + return CreateScopedResult(env, value, result); +} + +napi_status napi_create_dataview(napi_env env, + size_t byte_length, + napi_value arraybuffer, + size_t byte_offset, + napi_value *result) { + + CHECK_ARG(env) + CHECK_ARG(result) + CHECK_ARG(arraybuffer) + + if (!JS_IsArrayBuffer2(env->context, *(JSValue *) arraybuffer)) { + return napi_set_last_error(env, napi_invalid_arg, NULL, 0, NULL); + } + + size_t bufferSize = 0; + void *buffer = JS_GetArrayBuffer(env->context, &bufferSize, *((JSValue *) arraybuffer)); + + // It is required that byte_length + byte_offset is less + // than or equal to the size in bytes of the array passed in. + // If not, a RangeError exception is raised. + if (byte_length + byte_offset > bufferSize) { + return napi_throw_range_error(env, "napi_generic_failure", "Invalid DataView length"); + } + + JSValue global = JS_GetGlobalObject(env->context); + JSValue dataView = JS_GetPropertyStr(env->context, global, "DataView"); + + JSValue param[] = { + *((JSValue *) arraybuffer), + JS_NewInt64(env->context, byte_offset), + JS_NewInt64(env->context, byte_length), + }; + + JSValue value = JS_CallConstructor(env->context, dataView, 3, param); + + JS_FreeValue(env->context, dataView); + JS_FreeValue(env->context, global); + + return CreateScopedResult(env, value, result); +} + +napi_status napi_create_buffer_copy(napi_env env, + size_t length, + const void *data, + void **result_data, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + CHECK_ARG(data) + + size_t size = 0; + + JSValue value = JS_NewArrayBufferCopy(env->context, (uint8_t *) data, length); + + MARK_AS_NAPI_BUFFER + + if (result_data) { + *result_data = JS_GetArrayBuffer(env->context, &size, value); + } + + return CreateScopedResult(env, value, result); +} + + +napi_status napi_create_external_arraybuffer(napi_env env, + void *external_data, + size_t byte_length, + napi_finalize finalize_cb, + void *finalize_hint, + napi_value *result) { + + CHECK_ARG(env) + CHECK_ARG(external_data) + CHECK_ARG(byte_length) + CHECK_ARG(result) + + JSValue value; + if (finalize_cb) { + ExternalBufferInfo *external_arraybuffer_info = (ExternalBufferInfo *) mi_malloc( + sizeof(ExternalBufferInfo)); + external_arraybuffer_info->finalize_cb = finalize_cb; + external_arraybuffer_info->hint = finalize_hint; + + value = JS_NewArrayBuffer(env->context, (uint8_t *) external_data, byte_length, + buffer_finalizer, external_arraybuffer_info, false); + } else { + value = JS_NewArrayBuffer(env->context, (uint8_t *) external_data, byte_length, NULL, + NULL, false); + } + + return CreateScopedResult(env, value, result); +} + +napi_status napi_create_external_buffer(napi_env env, + size_t length, + void *data, + napi_finalize finalize_cb, + void *finalize_hint, + napi_value *result) { + + CHECK_ARG(env) + CHECK_ARG(data) + CHECK_ARG(length) + CHECK_ARG(result) + + JSValue value; + if (finalize_cb) { + struct ExternalBufferInfo *external_arraybuffer_info = mi_malloc( + sizeof(struct ExternalBufferInfo)); + external_arraybuffer_info->finalize_cb = finalize_cb; + external_arraybuffer_info->hint = finalize_hint; + value = JS_NewArrayBuffer(env->context, (uint8_t *) data, length, buffer_finalizer, + external_arraybuffer_info, false); + } else { + value = JS_NewArrayBuffer(env->context, (uint8_t *) data, length, NULL, NULL, false); + } + + MARK_AS_NAPI_BUFFER; + + return CreateScopedResult(env, value, result); +} + +napi_status napi_create_date(napi_env env, + double time, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue value = JS_NewDate(env->context, time); + + return CreateScopedResult(env, value, result); +} + + +napi_status napi_create_symbol(napi_env env, + napi_value description, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue symbol = {0}; + + JSValue global = JS_GetGlobalObject(env->context); + JSValue symbolCotr = JS_GetPropertyStr(env->context, global, "Symbol"); + + JSValue jsValue = *((JSValue *) description); + + symbol = JS_Call(env->context, symbolCotr, global, 1, &jsValue); + + JS_FreeValue(env->context, symbolCotr); + JS_FreeValue(env->context, global); + + return CreateScopedResult(env, symbol, result); +} + + +napi_status node_api_symbol_for(napi_env env, + const char *utf8description, + size_t length, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + CHECK_ARG(utf8description); + + JSValue global = JS_GetGlobalObject(env->context); + JSValue description = JS_NewString(env->context, utf8description); + JSValue symbol = JS_Invoke(env->context, global, env->atoms.NAPISymbolFor, 1, &description); + JS_FreeValue(env->context, global); + JS_FreeValue(env->context, description); + + return CreateScopedResult(env, symbol, result); +} + +/** + * -------------------------------------- + * NODE-API TO C TYPES CONVERSION + * -------------------------------------- + */ + + +napi_status napi_get_array_length(napi_env env, + napi_value value, + uint32_t *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + +#ifdef __QJS_NG__ + if (!JS_IsArray(jsValue)) +#else + if (!JS_IsArray(env->context,jsValue)) +#endif + return napi_set_last_error(env, napi_array_expected, NULL, 0, NULL); + + int64_t length = 0; + JS_GetLength(env->context, jsValue, &length); + + *result = (uint32_t) length; + + return napi_clear_last_error(env); +} + +napi_status napi_get_arraybuffer_info(napi_env env, + napi_value arraybuffer, + void **data, + size_t *byte_length) { + CHECK_ARG(env) + CHECK_ARG(arraybuffer) + CHECK_ARG(byte_length) + + size_t size = 0; + JSValue value = *((JSValue *) arraybuffer); + + if (!JS_IsArrayBuffer2(env->context, value)) + return napi_set_last_error(env, napi_arraybuffer_expected, NULL, 0, NULL); + + if (JS_HasProperty(env->context, value, env->atoms.napi_buffer)) + return napi_set_last_error(env, napi_invalid_arg, NULL, 0, NULL);; + + if (data) { + *data = JS_GetArrayBuffer(env->context, &size, value); + if (byte_length) *byte_length = size; + } else { + JS_GetArrayBuffer(env->context, &size, value); + if (byte_length) *byte_length = size; + } + + + return napi_clear_last_error(env); +} + +napi_status napi_get_buffer_info(napi_env env, + napi_value buffer, + void **data, + size_t *length) { + CHECK_ARG(env) + CHECK_ARG(buffer) + CHECK_ARG(data) + CHECK_ARG(length) + + size_t size = 0; + JSValue value = *((JSValue *) buffer); + + if (!JS_IsArrayBuffer2(env->context, value)) + return napi_set_last_error(env, napi_arraybuffer_expected, NULL, 0, NULL); + + if (!JS_HasProperty(env->context, value, env->atoms.napi_buffer)) + return napi_set_last_error(env, napi_invalid_arg, NULL, 0, NULL); + + if (data) *data = JS_GetArrayBuffer(env->context, &size, value); + + if (length) *length = size; + + return napi_clear_last_error(env); +} + +napi_status napi_get_prototype(napi_env env, + napi_value object, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(result) + + JSValue value = *((JSValue *) object); + + if (!JS_IsObject(value)) + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + + JSValue prototype = JS_GetPrototype(env->context, value); + + return CreateScopedResult(env, prototype, result); +} + +int findIndex(const char *array[], int size, const char *target) { + for (int i = 0; i < size; ++i) { + if (strcmp(array[i], target) == 0) { + return i; // Return the index if the target is found + } + } + + // Return -1 if the target is not found in the array + return -1; +} + +static inline int napi_get_typedarray_type(napi_env env, napi_value typedarray) { + CHECK_ARG(env) + CHECK_ARG(typedarray) + + JSValue value = *((JSValue *) typedarray); + + if (!JS_IsObject(value)) { + return -1; + } + + napi_typedarray_type typedArrayType = 0; + + int type = JS_GetTypedArrayType(value); + switch (type) { + case JS_TYPED_ARRAY_INT8: + typedArrayType = napi_int8_array; + break; + case JS_TYPED_ARRAY_UINT8: + typedArrayType = napi_uint8_array; + break; + case JS_TYPED_ARRAY_UINT8C: + typedArrayType = napi_uint8_clamped_array; + break; + case JS_TYPED_ARRAY_INT16: + typedArrayType = napi_int16_array; + break; + case JS_TYPED_ARRAY_UINT16: + typedArrayType = napi_uint16_array; + break; + case JS_TYPED_ARRAY_INT32: + typedArrayType = napi_int32_array; + break; + case JS_TYPED_ARRAY_UINT32: + typedArrayType = napi_uint32_array; + break; + case JS_TYPED_ARRAY_FLOAT32: + typedArrayType = napi_float32_array; + break; + case JS_TYPED_ARRAY_FLOAT64: + typedArrayType = napi_float64_array; + break; + case JS_TYPED_ARRAY_BIG_INT64: + typedArrayType = napi_bigint64_array; + break; + case JS_TYPED_ARRAY_BIG_UINT64: + typedArrayType = napi_biguint64_array; + break; + default: + typedArrayType = -1; + } + + return typedArrayType; +} + +napi_status napi_get_typedarray_info(napi_env env, + napi_value typedarray, + napi_typedarray_type *type, + size_t *length, + void **data, + napi_value *arraybuffer, + size_t *byte_offset) { + CHECK_ARG(env) + CHECK_ARG(typedarray) + + int typedArrayType = napi_get_typedarray_type(env, typedarray); + if (typedArrayType == -1) + return napi_set_last_error(env, napi_invalid_arg, NULL, 0, NULL); + + JSValue value = *((JSValue *) typedarray); + + if (type) { + *type = (napi_typedarray_type) typedArrayType; + } + + if (length) { + JSValue len = JS_GetPropertyStr(env->context, value, "length"); + *length = JS_VALUE_GET_INT(len); + JS_FreeValue(env->context, len); + } + + if (data || arraybuffer) { + JSValue jsArrayBuffer = JS_GetPropertyStr(env->context, value, "buffer"); + if (data) { + size_t bufferSize; + *data = JS_GetArrayBuffer(env->context, &bufferSize, jsArrayBuffer); + } + + if (arraybuffer) { + CreateScopedResult(env, jsArrayBuffer, arraybuffer); + } else { + JS_FreeValue(env->context, jsArrayBuffer); + } + } + + if (byte_offset) { + JSValue byteOffset = JS_GetPropertyStr(env->context, value, "byteOffset"); + uint32_t cValue = 0; + JS_ToUint32(env->context, &cValue, byteOffset); + JS_FreeValue(env->context, byteOffset); + *byte_offset = cValue; + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_dataview_info(napi_env env, + napi_value dataview, + size_t *byte_length, + void **data, + napi_value *arraybuffer, + size_t *byte_offset) { + CHECK_ARG(env) + CHECK_ARG(dataview) + + JSValue value = *((JSValue *) dataview); + + if (!JS_IsDataView(value)) { + return napi_set_last_error(env, napi_invalid_arg, NULL, 0, NULL); + } + + if (byte_length) { + JSValue byteLength = JS_GetPropertyStr(env->context, value, "byteLength"); + *byte_length = JS_VALUE_GET_INT(byteLength); + JS_FreeValue(env->context, byteLength); + } + + if (data || arraybuffer) { + JSValue jsArrayBuffer = JS_GetPropertyStr(env->context, value, "buffer"); + if (data) { + size_t bufferSize; + *data = JS_GetArrayBuffer(env->context, &bufferSize, jsArrayBuffer); + } + + if (arraybuffer) { + CreateScopedResult(env, jsArrayBuffer, arraybuffer); + } else { + JS_FreeValue(env->context, jsArrayBuffer); + } + } + + if (byte_offset) { + JSValue byteOffset = JS_GetPropertyStr(env->context, value, "byteOffset"); + uint32_t cValue = 0; + JS_ToUint32(env->context, &cValue, byteOffset); + JS_FreeValue(env->context, byteOffset); + *byte_offset = cValue; + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_date_value(napi_env env, + napi_value value, + double *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + + if (JS_GetClassID(jsValue) != 10) { + return napi_set_last_error(env, napi_date_expected, NULL, 0, NULL); + } + + JSValue timeValue = JS_GetPropertyStr(env->context, jsValue, "getTime"); + JSValue time = JS_Call(env->context, timeValue, jsValue, 0, NULL); + JS_ToFloat64(env->context, result, time); + + JS_FreeValue(env->context, timeValue); + JS_FreeValue(env->context, time); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_bool(napi_env env, + napi_value value, + bool *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + + if (!JS_IsBool(jsValue)) { + return napi_set_last_error(env, napi_boolean_expected, NULL, 0, NULL); + } + + *result = JS_VALUE_GET_BOOL(jsValue); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_double(napi_env env, + napi_value value, + double *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + + int tag = JS_VALUE_GET_TAG(jsValue); + + if (tag == JS_TAG_INT) { + *result = JS_VALUE_GET_INT(jsValue); + } else if (JS_TAG_IS_FLOAT64(tag)) { + *result = JS_VALUE_GET_FLOAT64(jsValue); + } else { + return napi_set_last_error(env, napi_number_expected, NULL, 0, NULL); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_bigint_int64(napi_env env, + napi_value value, + int64_t *result, + bool *lossless) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + if (!JS_IsBigInt( +#ifndef __QJS_NG__ + env->context, +#endif + *(JSValue *) value)) { + return napi_set_last_error(env, napi_bigint_expected, NULL, 0, NULL); + } + + JS_ToBigInt64(env->context, result,*(JSValue *) value); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_bigint_uint64(napi_env env, + napi_value value, + uint64_t *result, + bool *lossless) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + if (!JS_IsBigInt( +#ifndef __QJS_NG__ + env->context, +#endif + *(JSValue *) value)) { + return napi_set_last_error(env, napi_bigint_expected, NULL, 0, NULL); + } + + JS_ToBigUint64(env->context, result, *(JSValue *) value); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_bigint_words(napi_env env, + napi_value value, + int *sign_bit, + size_t *word_count, + uint64_t *words) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(sign_bit) + CHECK_ARG(word_count) + CHECK_ARG(words) + + JSValue jsValue = *(JSValue *) value; + + if (!JS_IsBigInt( +#ifndef __QJS_NG__ + env->context, +#endif + jsValue)) { + return napi_set_last_error(env, napi_bigint_expected, NULL, 0, NULL); + } + + JS_GetBigIntWords(env->context, jsValue, sign_bit, word_count, words); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_external(napi_env env, + napi_value value, + void **result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + ExternalInfo *external = (ExternalInfo *) JS_GetOpaque(jsValue, env->runtime->externalClassId); + + *result = external ? external->data : NULL; + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_int32(napi_env env, + napi_value value, + int32_t *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + + if (!JS_IsNumber(jsValue)) { + return napi_set_last_error(env, napi_number_expected, NULL, 0, NULL); + } + + JS_ToInt32(env->context, result, jsValue); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_int64(napi_env env, + napi_value value, + int64_t *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + + if (!JS_IsNumber(jsValue)) { + return napi_set_last_error(env, napi_number_expected, NULL, 0, NULL); + } + + JS_ToInt64(env->context, result, jsValue); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_string_latin1(napi_env env, napi_value value, char *str, size_t length, + size_t *result) { + CHECK_ARG(env) + CHECK_ARG(value) + + if (!JS_IsString(*((JSValue *) value))) { + return napi_set_last_error(env, napi_string_expected, NULL, 0, NULL); + } + + size_t cstr_len = 0; + const char *cstr = JS_ToCStringLen(env->context, &cstr_len, *((JSValue *) value)); + + if (str == NULL) { + CHECK_ARG(result) + *result = cstr_len; + } else if (length != 0) { + strcpy(str, cstr); + str[cstr_len] = '\0'; + } else if (result != NULL) { + *result = 0; + } + + JS_FreeCString(env->context, cstr); + return napi_clear_last_error(env); +} + +napi_status napi_get_value_string_utf8(napi_env env, napi_value value, char *str, size_t length, + size_t *result) { + CHECK_ARG(env) + CHECK_ARG(value) + + if (!JS_IsString(*((JSValue *) value))) { + return napi_set_last_error(env, napi_string_expected, NULL, 0, NULL); + } + + size_t cstr_len = 0; + const char *cstr = JS_ToCStringLen(env->context, &cstr_len, *((JSValue *) value)); + + if (str == NULL) { + CHECK_ARG(result) + *result = cstr_len; + } else if (length != 0) { + strcpy(str, cstr); + str[cstr_len] = '\0'; + } else if (result != NULL) { + *result = 0; + } + + JS_FreeCString(env->context, cstr); + return napi_clear_last_error(env); +} + +size_t Utf8CodePointLen(uint8_t ch) { + const uint8_t offset = 3; + return ((0xe5000000 >> ((ch >> offset) & 0x1e)) & offset) + 1; +} + +void Utf8ShiftAndMask(uint32_t *codePoint, const uint8_t byte) { + *codePoint <<= 6; + *codePoint |= 0x3F & byte; +} + +uint32_t Utf8ToUtf32CodePoint(const char *src, size_t length) { + uint32_t unicode = 0; + const + size_t lengthSizeOne = 1; + const + size_t lengthSizeTwo = 2; + const + size_t lengthSizeThree = 3; + const + size_t lengthSizeFour = 4; + const + size_t offsetZero = 0; + const + size_t offsetOne = 1; + const + size_t offsetTwo = 2; + const + size_t offsetThree = 3; + switch (length) { + case lengthSizeOne: + return src[offsetZero]; + case lengthSizeTwo: + unicode = src[offsetZero] & 0x1f; + Utf8ShiftAndMask(&unicode, src[offsetOne]); + return unicode; + case lengthSizeThree: + unicode = src[offsetZero] & 0x0f; + Utf8ShiftAndMask(&unicode, src[offsetOne]); + Utf8ShiftAndMask(&unicode, src[offsetTwo]); + return unicode; + case lengthSizeFour: + unicode = src[offsetZero] & 0x07; + Utf8ShiftAndMask(&unicode, src[offsetOne]); + Utf8ShiftAndMask(&unicode, src[offsetTwo]); + Utf8ShiftAndMask(&unicode, src[offsetThree]); + return unicode; + default: + return 0xffff; + } +} + +char16_t *Utf8ToUtf16(const char *utf8Str, size_t u8len, char16_t *u16str, size_t u16len) { + if (u16len == 0) { + return u16str; + } + const char *u8end = utf8Str + u8len; + const char *u8cur = utf8Str; + const char16_t *u16end = u16str + u16len - 1; + const uint8_t offset = 10; + char16_t *u16cur = u16str; + + while ((u8cur < u8end) && (u16cur < u16end)) { + size_t len = Utf8CodePointLen(*u8cur); + uint32_t codepoint = Utf8ToUtf32CodePoint(u8cur, len); + // Convert the UTF32 codepoint to one or more UTF16 codepoints + if (codepoint <= 0xFFFF) { + // Single UTF16 character + *u16cur++ = (char16_t) codepoint; + } else { + // Multiple UTF16 characters with surrogates + codepoint = codepoint - 0x10000; + *u16cur++ = (char16_t) ((codepoint >> offset) + 0xD800); + if (u16cur >= u16end) { + // Ooops... not enough room for this surrogate pair. + return u16cur - 1; + } + *u16cur++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00); + } + + u8cur += len; + } + return u16cur; +} + +int Utf8ToUtf16Length(const char *str8, size_t str8Len) { + const char *str8end = str8 + str8Len; + int utf16len = 0; + while (str8 < str8end) { + utf16len++; + size_t u8charlen = Utf8CodePointLen(*str8); + if (str8 + u8charlen - 1 >= str8end) { + return -1; + } + uint32_t codepoint = Utf8ToUtf32CodePoint(str8, u8charlen); + if (codepoint > 0xFFFF) { + utf16len++; // this will be a surrogate pair in utf16 + } + str8 += u8charlen; + } + if (str8 != str8end) { + return -1; + } + return utf16len; +} + +napi_status napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t *buf, + size_t bufsize, + size_t *result) { + CHECK_ARG(env) + CHECK_ARG(value) + + size_t l = 0; + const char *str = JS_ToCStringLen(env->context, &l, *((JSValue *) value)); + + if (str == NULL) { + return napi_set_last_error(env, napi_string_expected, NULL, 0, NULL); + } + + int ret = Utf8ToUtf16Length(str, strlen(str)); + if (ret == -1) { + JS_FreeCString(env->context, str); + return napi_set_last_error(env, napi_generic_failure, NULL, 0, NULL); + } + + if (result) { + *result = ret; + } + + if (buf != NULL) { + memset(buf, 0, sizeof(char16_t) * bufsize); + Utf8ToUtf16(str, strlen(str), buf, bufsize); + } + + JS_FreeCString(env->context, str); + return napi_clear_last_error(env); +} + +napi_status napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + + if (!JS_IsNumber(jsValue)) { + return napi_set_last_error(env, napi_number_expected, NULL, 0, NULL); + } + + JS_ToUint32(env->context, result, jsValue); + + return napi_clear_last_error(env); +} + +/** + * -------------------------------------- + * GLOBAL INSTANCES + * -------------------------------------- + */ + +/** + * Functions to get global instances + * https://nodejs.org/api/n-api.html#functions-to-get-global-instances + */ + +static JSValue JSTrueValue = JS_TRUE; +static JSValue JSFalseValue = JS_FALSE; + +napi_status napi_get_boolean(napi_env env, bool value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + JSValue val = value ? JSTrueValue : JSFalseValue; + return CreateScopedResult(env, val, result); +} + +napi_status napi_get_global(napi_env env, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + JSValue globalValue = JS_GetGlobalObject(env->context); + return CreateScopedResult(env, globalValue, result); +} + +napi_status napi_get_null(napi_env env, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + return CreateScopedResult(env, JS_NULL, result); +} + +napi_status napi_get_undefined(napi_env env, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(result) + + return CreateScopedResult(env, JS_UNDEFINED, result); +} + + +/** + * -------------------------------------- + * WORKING WITH JS VALUES + * -------------------------------------- + */ + +napi_status napi_coerce_to_bool(napi_env env, napi_value value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + int boolValue = JS_ToBool(env->context, jsValue); + RETURN_STATUS_IF_FALSE(boolValue != -1, napi_pending_exception) + + return CreateScopedResult(env, JS_NewBool(env->context, boolValue), result); +} + +napi_status napi_coerce_to_number(napi_env env, napi_value value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + + double number; + JS_ToFloat64(env->context, &number, jsValue); + + return CreateScopedResult(env, JS_NewFloat64(env->context, number), result); +} + +napi_status napi_coerce_to_object(napi_env env, napi_value value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + return napi_clear_last_error(env); +} + +napi_status napi_coerce_to_string(napi_env env, napi_value value, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + JSValue jsResult; + if (JS_IsSymbol(jsValue)) { + jsResult = JS_GetPropertyStr(env->context, jsValue, "description"); + } else { + jsResult = JS_ToString(env->context, jsValue); + } + + if (JS_IsException(jsResult)) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + JS_DupValue(env->context, jsResult); + + return CreateScopedResult(env, jsResult, result); +} + +napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + if (JS_IsUndefined(jsValue)) { + *result = napi_undefined; + } else if (JS_IsNull(jsValue)) { + *result = napi_null; + } else if (JS_IsNumber(jsValue)) { + *result = napi_number; + } else if (JS_IsBool(jsValue)) { + *result = napi_boolean; + } else if (JS_IsString(jsValue)) { + *result = napi_string; + } else if (JS_IsSymbol(jsValue)) { + *result = napi_symbol; + } else if (JS_IsBigInt( +#ifndef __QJS_NG__ + env->context, +#endif + jsValue)) { + *result = napi_bigint; + } else if (JS_IsFunction(env->context, jsValue)) { + *result = napi_function; + } else if (JS_GetOpaque(jsValue, env->runtime->externalClassId)) { + *result = napi_external; + } else if (JS_IsObject(jsValue)) { + *result = napi_object; + } else { + return napi_set_last_error(env, napi_invalid_arg, NULL, 0, NULL); + } + + return napi_clear_last_error(env); +} + +napi_status napi_instanceof(napi_env env, napi_value object, napi_value constructor, bool *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(constructor) + CHECK_ARG(result) + + int status = JS_IsInstanceOf(env->context, *((JSValue *) object), *((JSValue *) constructor)); + RETURN_STATUS_IF_FALSE(status != -1, napi_pending_exception); + + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_is_float(napi_env env, napi_value value, bool *result) { + CHECK_ARG(env) + CHECK_ARG(value) + + JSValue jsValue = *((JSValue *) value); + + RETURN_STATUS_IF_FALSE(JS_IsNumber(jsValue), napi_number_expected) + + *result = JS_VALUE_GET_TAG(jsValue) == JS_TAG_FLOAT64; + + return napi_clear_last_error(env); +} + +napi_status napi_is_array(napi_env env, napi_value value, bool *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + int status = JS_IsArray( +#ifndef __QJS_NG__ + env->context, +#endif + jsValue); + RETURN_STATUS_IF_FALSE(status != -1, napi_pending_exception); + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + int status = JS_IsArrayBuffer2(env->context, jsValue); + RETURN_STATUS_IF_FALSE(status != -1, napi_pending_exception); + + if (status && JS_HasProperty(env->context, jsValue, env->atoms.napi_buffer)) { + *result = false; + return napi_clear_last_error(env); + } + + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_is_buffer(napi_env env, napi_value value, bool *result) { + + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + int status = JS_IsArrayBuffer2(env->context, jsValue); + RETURN_STATUS_IF_FALSE(status != -1, napi_pending_exception); + + if (status && !JS_HasProperty(env->context, jsValue, env->atoms.napi_buffer)) { + *result = false; + return napi_clear_last_error(env); + } + + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_is_date(napi_env env, napi_value value, bool *result) { + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) value); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + bool status = JS_GetClassID(jsValue) == 10; + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_is_error(napi_env env, napi_value value, bool *result) { + + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + int status = JS_IsError( +#ifndef __QJS_NG__ + env->context, +#endif + *((JSValue *) value)); + *result = status; + return napi_clear_last_error(env); +} + +napi_status napi_is_typedarray(napi_env env, napi_value value, bool *result) { + + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + int status = napi_get_typedarray_type(env, value); + *result = status == -1 ? 0 : 1; + + return napi_clear_last_error(env); +} + +napi_status napi_is_dataview(napi_env env, napi_value value, bool *result) { + + CHECK_ARG(env) + CHECK_ARG(value) + CHECK_ARG(result) + + int status = JS_IsDataView(*((JSValue *) value)); + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_strict_equals(napi_env env, napi_value lhs, napi_value rhs, bool *result) { + CHECK_ARG(env); + CHECK_ARG(lhs); + CHECK_ARG(rhs); + CHECK_ARG(result); + + *result = JS_IsStrictEqual(env->context, *(JSValue *) lhs, *(JSValue *) rhs); + + return napi_clear_last_error(env); +} + +napi_status napi_detach_arraybuffer(napi_env env, napi_value arraybuffer) { + + CHECK_ARG(env) + CHECK_ARG(arraybuffer) + + JSValue jsValue = *((JSValue *) arraybuffer); + + if (!JS_IsArrayBuffer2(env->context, jsValue)) { + return napi_set_last_error(env, napi_arraybuffer_expected, NULL, 0, NULL); + } + + JS_DetachArrayBuffer(env->context, jsValue); + + return napi_clear_last_error(env); +} + +napi_status napi_is_detached_arraybuffer(napi_env env, napi_value arraybuffer, bool *result) { + + CHECK_ARG(env) + CHECK_ARG(arraybuffer) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) arraybuffer); + + if (!JS_IsArrayBuffer2(env->context, jsValue)) { + return napi_set_last_error(env, napi_arraybuffer_expected, NULL, 0, NULL); + } + + void *buffer = NULL; + size_t bufferSize = 0; + buffer = JS_GetArrayBuffer(env->context, &bufferSize, jsValue); + *result = buffer == NULL; + + return napi_clear_last_error(env); +} + +/** + * -------------------------------------- + * OBJECT PROPERTIES + * -------------------------------------- + */ +napi_status napi_get_all_property_names(napi_env env, + napi_value object, + napi_key_collection_mode key_mode, + napi_key_filter key_filter, + napi_key_conversion key_conversion, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + int get_filter = JS_GPN_STRING_MASK; + if (key_filter == napi_key_all_properties) { + get_filter = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY; + } else { + if (key_filter & napi_key_skip_strings) { + get_filter &= ~JS_GPN_STRING_MASK; + } + + if (key_filter & napi_key_enumerable) { + get_filter |= JS_GPN_ENUM_ONLY; + } + + if (!(key_filter & napi_key_skip_symbols)) { + get_filter |= JS_GPN_SYMBOL_MASK; + } + } + + JSValue array = JS_NewArray(env->context); + JSValue proto = JS_DupValue(env->context, jsValue); + + while (!JS_IsNull(proto)) { + JSPropertyEnum *tab = NULL; + uint32_t len = 0; + + JS_GetOwnPropertyNames(env->context, &tab, &len, proto, get_filter); + + for (uint32_t i = 0; i < len; i++) { + JSValue name = JS_AtomToValue(env->context, tab[i].atom); + JS_SetPropertyInt64(env->context, array, i, name); + } + + JS_FreePropertyEnum(env->context, tab, len); + + // Free the prototype. + JS_FreeValue(env->context, proto); + + if (key_mode == napi_key_include_prototypes) { + proto = JS_GetPrototype(env->context, proto); + } else { + proto = JS_NULL; + } + } + + return CreateScopedResult(env, array, result); +} + +napi_status napi_get_property_names(napi_env env, napi_value object, napi_value *result) { + return napi_get_all_property_names( + env, + object, + napi_key_include_prototypes, + (napi_key_filter) (napi_key_enumerable | napi_key_skip_symbols), + napi_key_numbers_to_strings, + result); +} + +napi_status napi_set_property(napi_env env, napi_value object, napi_value key, napi_value value) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(key) + CHECK_ARG(value) + + JSValue jsObject = *((JSValue *) object); + JSValue jsKey = *((JSValue *) key); + JSValue jsValue = *((JSValue *) value); + + if (!JS_IsObject(jsObject)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSAtom keyAtom = JS_ValueToAtom(env->context, jsKey); + int result = JS_SetProperty(env->context, jsObject, keyAtom, + JS_DupValue(env->context, jsValue)); + JS_FreeAtom(env->context, keyAtom); + + if (!result) { + return napi_set_last_error(env, napi_generic_failure, NULL, 0, NULL); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_property(napi_env env, napi_value object, napi_value key, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(key) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + JSValue jsKey = *((JSValue *) key); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSAtom keyAtom = JS_ValueToAtom(env->context, jsKey); + JSValue jsResult = JS_GetProperty(env->context, jsValue, keyAtom); + JS_FreeAtom(env->context, keyAtom); + + if (JS_IsException(jsResult)) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + return CreateScopedResult(env, jsResult, result); +} + +napi_status napi_has_property(napi_env env, napi_value object, napi_value key, bool *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(key) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + JSValue jsKey = *((JSValue *) key); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSAtom keyAtom = JS_ValueToAtom(env->context, jsKey); + int status = JS_HasProperty(env->context, jsValue, keyAtom); + JS_FreeAtom(env->context, keyAtom); + + if (status == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_delete_property(napi_env env, napi_value object, napi_value key, bool *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(key) + + JSValue jsValue = *((JSValue *) object); + JSValue jsKey = *((JSValue *) key); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSAtom keyAtom = JS_ValueToAtom(env->context, jsKey); + int status = JS_DeleteProperty(env->context, jsValue, keyAtom, JS_PROP_THROW); + JS_FreeAtom(env->context, keyAtom); + + if (status == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + if (result != NULL) { + *result = status; + } + + return napi_clear_last_error(env); +} + +napi_status +napi_has_own_named_property(napi_env env, napi_value object, const char *utf8name, bool *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(utf8name) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSValue jsKey = JS_NewString(env->context, utf8name); + JSAtom keyAtom = JS_ValueToAtom(env->context, jsKey); + int status = JS_GetOwnProperty(env->context, NULL, jsValue, keyAtom); + JS_FreeAtom(env->context, keyAtom); + + if (status == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_has_own_property(napi_env env, napi_value object, napi_value key, bool *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(key) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + JSValue jsKey = *((JSValue *) key); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + + JSAtom keyAtom = JS_ValueToAtom(env->context, jsKey); + int status = JS_GetOwnProperty(env->context, NULL, jsValue, keyAtom); + JS_FreeAtom(env->context, keyAtom); + + if (status == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + *result = status; + + return napi_clear_last_error(env); +} + +napi_status +napi_set_named_property(napi_env env, napi_value object, const char *utf8Name, napi_value value) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(utf8Name) + CHECK_ARG(value) + + JSValue jsObject = *((JSValue *) object); + JSValue jsValue = *((JSValue *) value); + + if (!JS_IsObject(jsObject)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + int status = JS_SetPropertyStr(env->context, jsObject, utf8Name, + JS_DupValue(env->context, jsValue)); + + if (status == -1) { + return napi_set_last_error(env, napi_generic_failure, NULL, 0, NULL); + } + + return napi_clear_last_error(env); +} + +napi_status +napi_get_named_property(napi_env env, napi_value object, const char *utf8Name, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(utf8Name) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSValue jsResult = JS_GetPropertyStr(env->context, jsValue, utf8Name); + + if (JS_IsException(jsResult)) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + return CreateScopedResult(env, jsResult, result); +} + +napi_status +napi_has_named_property(napi_env env, napi_value object, const char *utf8Name, bool *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(utf8Name) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSAtom keyAtom = JS_NewAtom(env->context, utf8Name); + int status = JS_HasProperty(env->context, jsValue, keyAtom); + JS_FreeAtom(env->context, keyAtom); + + if (status == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_set_element(napi_env env, napi_value object, uint32_t index, napi_value value) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(value) + + JSValue jsObject = *((JSValue *) object); + JSValue jsValue = *((JSValue *) value); + + if (!JS_IsObject(jsObject)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + int status = JS_SetPropertyUint32(env->context, jsObject, index, + JS_DupValue(env->context, jsValue)); + + if (!status) { + return napi_set_last_error(env, napi_generic_failure, NULL, 0, NULL); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_element(napi_env env, napi_value object, uint32_t index, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSValue jsResult = JS_GetPropertyUint32(env->context, jsValue, index); + + if (JS_IsException(jsResult)) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + return CreateScopedResult(env, jsResult, result); +} + +napi_status napi_has_element(napi_env env, napi_value object, uint32_t index, bool *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSAtom key = JS_NewAtomUInt32(env->context, index); + int status = JS_HasProperty(env->context, jsValue, key); + JS_FreeAtom(env->context, key); + + if (status == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + *result = status; + + return napi_clear_last_error(env); +} + +napi_status napi_delete_element(napi_env env, napi_value object, uint32_t index, bool *result) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(result) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSAtom key = JS_NewAtomUInt32(env->context, index); + int status = JS_DeleteProperty(env->context, jsValue, key, JS_PROP_THROW); + JS_FreeAtom(env->context, key); + + if (status == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + *result = status; + + return napi_clear_last_error(env); +} + +static inline void +napi_set_property_descriptor(napi_env env, napi_value object, napi_property_descriptor descriptor) { + JSAtom key; + + if (descriptor.name) { + JSValue symbol = *((JSValue *) descriptor.name); + key = JS_ValueToAtom(env->context, symbol); + } else { + key = JS_NewAtom(env->context, descriptor.utf8name); + } + + JSValue jsObject = *((JSValue *) object); + + int flags = JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE | JS_PROP_HAS_CONFIGURABLE; + + if ((descriptor.attributes & napi_writable) != 0 || descriptor.getter || descriptor.setter) { + flags |= JS_PROP_WRITABLE; + } + + if ((descriptor.attributes & napi_enumerable) != 0) { + flags |= JS_PROP_ENUMERABLE; + } + + if ((descriptor.attributes & napi_configurable) != 0) { + flags |= JS_PROP_CONFIGURABLE; + } + + JSValue value = JS_UNDEFINED, getterValue = JS_UNDEFINED, setterValue = JS_UNDEFINED; + + if (descriptor.value) { + flags |= JS_PROP_HAS_VALUE; + value = *((JSValue *) descriptor.value); + } else if (descriptor.method) { + flags |= JS_PROP_HAS_VALUE; + napi_value function = NULL; + napi_create_function(env, descriptor.utf8name, NAPI_AUTO_LENGTH, descriptor.method, descriptor.data, &function); + if (function) { + value = *((JSValue *) function); + } + } else if (descriptor.getter || descriptor.setter) { + if (descriptor.getter) { + napi_value getter = NULL; + flags |= JS_PROP_HAS_GET; + napi_create_function(env, descriptor.utf8name, NAPI_AUTO_LENGTH, descriptor.getter, descriptor.data, &getter); + if (getter) { + getterValue = *((JSValue *) getter); + } + } + + if (descriptor.setter) { + napi_value setter = NULL; + flags |= JS_PROP_HAS_SET; + napi_create_function(env, descriptor.utf8name, NAPI_AUTO_LENGTH, descriptor.setter, descriptor.data, &setter); + if (setter) { + setterValue = *((JSValue *) setter); + } + } + } + + JS_DefineProperty(env->context, jsObject, key, value, getterValue, setterValue, flags); + JS_FreeAtom(env->context, key); +} + +napi_status napi_define_properties(napi_env env, napi_value object, size_t property_count, + const napi_property_descriptor *properties) { + CHECK_ARG(env) + CHECK_ARG(object) + CHECK_ARG(properties) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + for (size_t i = 0; i < property_count; i++) { + napi_set_property_descriptor(env, object, properties[i]); + } + + return napi_clear_last_error(env); +} + +napi_status napi_object_freeze(napi_env env, napi_value object) { + CHECK_ARG(env) + CHECK_ARG(object) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JS_FreezeObject(env->context, *(JSValue *) object); + + return napi_clear_last_error(env); +} + +napi_status napi_object_seal(napi_env env, napi_value object) { + CHECK_ARG(env) + CHECK_ARG(object) + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JS_SealObject(env->context, *(JSValue *) object); + + return napi_clear_last_error(env); +} + +/** + * -------------------------------------- + * FUNCTIONS + * -------------------------------------- + */ + +napi_status napi_call_function(napi_env env, napi_value thisValue, napi_value func, size_t argc, + const napi_value *argv, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(func) + + JSValue jsThis = *((JSValue *) thisValue); + JSValue jsFunction = *(JSValue *) func; + + bool useGlobal = false; + + if (JS_IsUndefined(jsThis)) { + useGlobal = true; + jsThis = JS_GetGlobalObject(env->context); + } + + js_enter(env); + JSValue *args = NULL; + JSValue returnValue; + + if (argc > 0) { + CHECK_ARG(argv) + JSValue stack_args[8]; + if (argc <= 8) { + args = stack_args; + } else { + args = (JSValue *) mi_malloc(sizeof(JSValue) * argc); + } + + for (size_t i = 0; i < argc; ++i) { + args[i] = *((JSValue *) argv[i]); + } + + + returnValue = JS_Call(env->context, jsFunction, jsThis, (int) argc, + args); + if (argc > 8) mi_free(args); + } else { + returnValue = JS_Call(env->context, jsFunction, jsThis, 0, + NULL); + } + + js_exit(env); + + if (useGlobal) JS_FreeValue(env->context, jsThis); + + if (JS_IsException(returnValue)) { + if (result) *result = (napi_value) &JSUndefined; + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + if (result) { + return CreateScopedResult(env, returnValue, result); + } + + JS_FreeValue(env->context, returnValue); + + + return napi_clear_last_error(env); +} + +static JSValue +CallCFunction(JSContext *context, JSValueConst thisVal, int argc, JSValueConst *argv, int magic, + JSValue *funcData) { + napi_env env = (napi_env) JS_GetContextOpaque(context); + + + bool useGlobalValue = false; + if (JS_IsUndefined(thisVal)) { + useGlobalValue = true; + thisVal = JS_GetGlobalObject(context); + } + + FunctionInfo *functionInfo = (FunctionInfo *) JS_GetOpaque(funcData[0], + env->runtime->functionClassId); + + struct napi_callback_info__ callbackInfo = {JSUndefined, thisVal, argv, functionInfo->data, + argc}; + + + napi_handle_scope__ handleScope; + handleScope.type = HANDLE_STACK_ALLOCATED; + handleScope.handleCount = 0; + handleScope.escapeCalled = false; + SLIST_INIT(&handleScope.handleList); + LIST_INSERT_HEAD(&env->handleScopeList, &handleScope, node); + + + + napi_value result = functionInfo->callback(env, &callbackInfo); + + if (useGlobalValue) { + JS_FreeValue(context, thisVal); + } + + JSValue returnValue = JSUndefined; + if (result) { + returnValue = JS_DupValue(context, *((JSValue *) result)); + } + + assert(LIST_FIRST(&env->handleScopeList) == &handleScope && + "napi_close_handle_scope() or napi_close_escapable_handle_scope() should follow FILO rule."); + + Handle *handle, *tempHandle; + SLIST_FOREACH_SAFE(handle, &handleScope.handleList, node, tempHandle) { + JS_FreeValue(env->context, handle->value); + handle->value = JSUndefined; + SLIST_REMOVE(&handleScope.handleList, handle, Handle, node); + if (handle->type == HANDLE_HEAP_ALLOCATED) { + mi_free(handle); + } + } + LIST_REMOVE(&handleScope, node); + + + if (JS_HasException(context)) { + JS_FreeValue(context, returnValue); + return JS_Throw(context, JS_GetException(context)); + } + + + return returnValue; +} + +napi_status +napi_create_function(napi_env env, const char *utf8name, size_t length, napi_callback cb, + void *data, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(cb) + CHECK_ARG(result) + + FunctionInfo *functionInfo = (FunctionInfo *) mi_malloc(sizeof(FunctionInfo)); + RETURN_STATUS_IF_FALSE(functionInfo, napi_memory_error) + functionInfo->data = data; + functionInfo->callback = cb; + + if (TRUTHY(!env->runtime->functionClassId)) { + assert(false && FUNCTION_CLASS_ID_ZERO); + mi_free(functionInfo); + + return napi_set_last_error(env, napi_generic_failure, NULL, 0, NULL); + } + + JSValue dataValue = JS_NewObjectClass(env->context, (int) env->runtime->functionClassId); + if (TRUTHY(JS_IsException(dataValue))) { + mi_free(functionInfo); + + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + JS_SetOpaque(dataValue, functionInfo); + + JSValue functionValue = JS_NewCFunctionData(env->context, CallCFunction, 0, 0, 1, &dataValue); + + JS_FreeValue(env->context, dataValue); + + RETURN_STATUS_IF_FALSE(!JS_IsException(functionValue), napi_pending_exception) + + if (utf8name && strcmp(utf8name, "") != 0) { + JS_DefinePropertyValue(env->context, functionValue, env->atoms.name, + JS_NewString(env->context, utf8name), + JS_PROP_CONFIGURABLE); + } + + return CreateScopedResult(env, functionValue, result); +} + +napi_status +napi_get_cb_info(napi_env env, napi_callback_info callbackInfo, size_t *argc, napi_value *argv, + napi_value *thisArg, void **data) { + CHECK_ARG(env) + CHECK_ARG(callbackInfo) + + if (argv && argc) { + size_t i = 0; + size_t min = callbackInfo->argc<0 || *argc>(size_t) + callbackInfo->argc ? callbackInfo->argc : *argc; + + for (; i < min; ++i) { + argv[i] = (napi_value) &callbackInfo->argv[i]; +// CreateScopedResult(env, JS_DupValue(env->context, callbackInfo->argv[i]), &argv[i]); + } + + if (i < *argc) { + for (; i < *argc; ++i) { + argv[i] = (napi_value) &JSUndefined; + } + } + } + + if (argc) { + *argc = callbackInfo->argc; + } + + if (thisArg) { + *thisArg = (napi_value) &callbackInfo->thisArg; + //CreateScopedResult(env, JS_DupValue(env->context, callbackInfo->thisArg), thisArg); + } + + if (data) { + *data = callbackInfo->data; + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_new_target(napi_env env, napi_callback_info callbackInfo, napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(callbackInfo) + CHECK_ARG(result) + + return CreateScopedResult(env, JS_DupValue(env->context, callbackInfo->newTarget), result); +} + +napi_status +napi_new_instance(napi_env env, napi_value constructor, size_t argc, const napi_value *argv, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(constructor) + CHECK_ARG(result) + + js_enter(env); + JSValue *args = NULL; + JSValue returnValue; + + if (argc > 0) { + CHECK_ARG(argv) + JSValue stack_args[8]; + if (argc <= 8) { + args = stack_args; + } else { + args = (JSValue *) mi_malloc(sizeof(JSValue) * argc); + } + + for (size_t i = 0; i < argc; ++i) { + args[i] = *((JSValue *) argv[i]); + } + returnValue = JS_CallConstructor(env->context, *((JSValue *) constructor), (int) argc, + args); + + if (argc > 8) mi_free(args); + } else { + returnValue = JS_CallConstructor(env->context, *((JSValue *) constructor), (int) argc, + args); + } + + js_exit(env); + + + if (JS_IsException(returnValue)) { + JS_Throw(env->context, returnValue); + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + return CreateScopedResult(env, returnValue, result); +} + +/** + * -------------------------------------- + * OBJECT WRAP + * -------------------------------------- + */ + +static JSValue +CallConstructor(JSContext *context, JSValueConst newTarget, int argc, JSValueConst *argv, int magic, + JSValue *data) { + + napi_env env = (napi_env) JS_GetContextOpaque(context); + + FunctionInfo *constructorInfo = (FunctionInfo *) JS_GetOpaque(*data, + env->runtime->constructorClassId); + + JSValue prototype = JS_GetPropertyStr(context, newTarget, "prototype"); + JSValue thisValue = JS_NewObjectProto(context, prototype); + JS_FreeValue(context, prototype); + + struct napi_callback_info__ callbackInfo = {newTarget, + thisValue, + argv, + constructorInfo->data, + argc}; + + napi_handle_scope__ handleScope; + handleScope.type = HANDLE_STACK_ALLOCATED; + handleScope.handleCount = 0; + handleScope.escapeCalled = false; + SLIST_INIT(&handleScope.handleList); + LIST_INSERT_HEAD(&env->handleScopeList, &handleScope, node); + + napi_value result = constructorInfo->callback(env, &callbackInfo); + + JSValue returnValue = JS_UNDEFINED; + + if (result) { + returnValue = *((JSValue *) result); + JS_DupValue(env->context, returnValue); + JS_FreeValue(env->context, thisValue); + } + + assert(LIST_FIRST(&env->handleScopeList) == &handleScope && + "napi_close_handle_scope() or napi_close_escapable_handle_scope() should follow FILO rule."); + Handle *handle, *tempHandle; + SLIST_FOREACH_SAFE(handle, &handleScope.handleList, node, tempHandle) { + JS_FreeValue(env->context, handle->value); + handle->value = JSUndefined; + SLIST_REMOVE(&handleScope.handleList, handle, Handle, node); + if (handle->type == HANDLE_HEAP_ALLOCATED) { + mi_free(handle); + } + } + LIST_REMOVE(&handleScope, node); + + if (JS_HasException(context)) { + JS_FreeValue(context, returnValue); + + return JS_Throw(context, JS_GetException(context)); + } + + + return returnValue; +} + +napi_status napi_define_class(napi_env env, + const char *utf8name, + size_t length, + napi_callback constructor, + void *data, + size_t property_count, + const napi_property_descriptor *properties, + napi_value *result) { + + CHECK_ARG(env) + CHECK_ARG(constructor) + CHECK_ARG(result) + + FunctionInfo *constructorInfo = (FunctionInfo *) mi_malloc(sizeof(FunctionInfo)); + + constructorInfo->data = data; + constructorInfo->callback = constructor; + + JSValue external = JS_NewObjectClass(env->context, (int) env->runtime->constructorClassId); + JS_SetOpaque(external, constructorInfo); + + JSValue cls = JS_NewCFunctionData(env->context, CallConstructor, 0, 0, 1, &external); + JS_SetConstructorBit(env->context, cls, true); + + if (utf8name && strcmp(utf8name, "") != 0) { + JS_DefinePropertyValue(env->context, cls, env->atoms.name, + JS_NewString(env->context, utf8name), JS_PROP_CONFIGURABLE); + } + + JSValue prototype = JS_NewObject(env->context); + + JS_SetConstructor(env->context, cls, prototype); + + for (size_t i = 0; i < property_count; i++) { + if (properties[i].attributes & napi_static) { + napi_set_property_descriptor(env, (napi_value) &cls, properties[i]); + } else { + napi_set_property_descriptor(env, (napi_value) &prototype, properties[i]); + } + } + + JS_FreeValue(env->context, external); + JS_FreeValue(env->context, prototype); + + return CreateScopedResult(env, cls, result); +} + +napi_status +napi_wrap(napi_env env, napi_value jsObject, void *nativeObject, napi_finalize finalize_cb, + void *finalize_hint, + napi_ref *result) { + + CHECK_ARG(env) + CHECK_ARG(jsObject) + CHECK_ARG(nativeObject) + + JSValue jsValue = *((JSValue *) jsObject); + + RETURN_STATUS_IF_FALSE(JS_IsObject(jsValue), napi_object_expected) + + int isWrapped = JS_GetOwnProperty(env->context, NULL, jsValue, env->atoms.napi_external); + + RETURN_STATUS_IF_FALSE(isWrapped != -1, napi_pending_exception) + + RETURN_STATUS_IF_FALSE(isWrapped == 0, napi_invalid_arg) + + ExternalInfo *externalInfo = (ExternalInfo *) mi_malloc(sizeof(ExternalInfo)); + + externalInfo->data = nativeObject; + externalInfo->finalizeHint = finalize_hint; + externalInfo->finalizeCallback = NULL; + + JSValue external = JS_NewObjectClass(env->context, (int) env->runtime->externalClassId); + + if (JS_IsException(external)) { + mi_free(externalInfo); + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + JS_SetOpaque(external, externalInfo); + + JS_SetProperty(env->context, jsValue, env->atoms.napi_external, external); + + if (result) { + napi_ref ref; + napi_create_reference(env, jsObject, 0, &ref); + *result = ref; + } + + return napi_clear_last_error(env); +} + +napi_status napi_unwrap(napi_env env, napi_value jsObject, void **result) { + CHECK_ARG(env) + CHECK_ARG(jsObject) + CHECK_ARG(result) + *result = NULL; + + JSValue jsValue = *((JSValue *) jsObject); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSPropertyDescriptor descriptor; + + int isWrapped = JS_GetOwnProperty(env->context, &descriptor, jsValue, env->atoms.napi_external); + + if (isWrapped == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + if (!isWrapped) { + *result = NULL; + return napi_set_last_error(env, napi_generic_failure, NULL, 0, NULL); + } + + JSValue external = descriptor.value; + + ExternalInfo *externalInfo = (ExternalInfo *) JS_GetOpaque(external, + env->runtime->externalClassId); + if (externalInfo == NULL) { + JS_FreeValue(env->context, descriptor.value); + return napi_set_last_error(env, napi_generic_failure, NULL, 0, NULL); + } + *result = externalInfo->data; + + JS_FreeValue(env->context, descriptor.value); + + return napi_clear_last_error(env); +} + +napi_status napi_remove_wrap(napi_env env, napi_value jsObject, void **result) { + CHECK_ARG(env) + CHECK_ARG(jsObject) + CHECK_ARG(result) + *result = NULL; + + JSValue jsValue = *((JSValue *) jsObject); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSPropertyDescriptor descriptor; + int isWrapped = JS_GetOwnProperty(env->context, &descriptor, jsValue, env->atoms.napi_external); + + if (isWrapped == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + if (!isWrapped) { + return napi_clear_last_error(env); + } + + JSValue external = descriptor.value; + + if (JS_IsObject(external)) { + if (result) { + ExternalInfo *externalInfo = (ExternalInfo *) JS_GetOpaque(external, + env->runtime->externalClassId); + if (externalInfo) { + *result = externalInfo->data; + } + mi_free(externalInfo); + JS_SetOpaque(external, NULL); + JS_SetOpaque(jsValue, NULL); + } + + int status = JS_DeleteProperty(env->context, jsValue, env->atoms.napi_external, 0); + if (status == -1) { + JS_FreeValue(env->context, descriptor.value); + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + JS_FreeValue(env->context, descriptor.value); + } + + return napi_clear_last_error(env); +} + +napi_status +napi_add_finalizer(napi_env env, napi_value jsObject, void *nativeObject, napi_finalize finalize_cb, + void *finalize_hint, napi_ref *result) { + CHECK_ARG(env) + CHECK_ARG(jsObject) + CHECK_ARG(nativeObject) + + JSValue jsValue = *((JSValue *) jsObject); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSValue heldValue = JS_NewObjectClass(env->context, env->runtime->externalClassId); + if (JS_IsException(heldValue)) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + ExternalInfo *info = (ExternalInfo *) mi_malloc(sizeof(ExternalInfo)); + if (info == NULL) { + JS_FreeValue(env->context, heldValue); + return napi_set_last_error(env, napi_memory_error, NULL, 0, NULL); + } + + info->data = nativeObject; + info->finalizeCallback = finalize_cb; + info->finalizeHint = finalize_hint; + JS_SetOpaque(heldValue, info); + + JSValue params[] = { + jsValue, + heldValue}; + + JSValue res = JS_Invoke(env->context, env->finalizationRegistry, env->atoms.registerFinalizer, + 2, params); + bool failed = JS_IsException(res); + + if (failed) { + JS_SetOpaque(heldValue, NULL); + mi_free(info); + JS_FreeValue(env->context, heldValue); + JS_FreeValue(env->context, res); + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + JS_FreeValue(env->context, res); + JS_FreeValue(env->context, heldValue); + + if (result) { + napi_ref ref; + napi_create_reference(env, jsObject, 0, &ref); + *result = ref; + } + + return napi_clear_last_error(env); +} + +/** + * -------------------------------------- + * ENV INSTANCE DATA + * -------------------------------------- + */ + +napi_status +napi_set_instance_data(napi_env env, void *data, napi_finalize finalize_cb, void *finalize_hint) { + + CHECK_ARG(env) + + // Match the Node-API contract used by the V8 backend: replacing instance + // data releases the old holder without invoking its finalizer. + ExternalInfo *instanceData = (ExternalInfo *) mi_malloc(sizeof(ExternalInfo)); + if (instanceData == NULL) { + return napi_set_last_error(env, napi_memory_error, NULL, 0, NULL); + } + + instanceData->data = data; + instanceData->finalizeCallback = finalize_cb; + instanceData->finalizeHint = finalize_hint; + + mi_free(env->instanceData); + env->instanceData = instanceData; + + return napi_clear_last_error(env); +} + +napi_status napi_get_instance_data(napi_env env, void **data) { + + CHECK_ARG(env) + CHECK_ARG(data) + + if (env->instanceData) { + *data = env->instanceData->data; + } else { + *data = NULL; + } + + return napi_clear_last_error(env); +} + +/** + * -------------------------------------- + * PROMISES + * -------------------------------------- + */ + +void deferred_finalize(napi_env env, void *finalizeData, void *finalizeHint) { + napi_deferred__ *deferred = (napi_deferred__ *) finalizeData; + JS_FreeValue(env->context, *(JSValue *) deferred->resolve); + JS_FreeValue(env->context, *(JSValue *) deferred->reject); + mi_free(deferred->resolve); + mi_free(deferred->reject); + mi_free(deferred); +}; + +napi_status napi_create_promise(napi_env env, napi_deferred *deferred, napi_value *result) { + CHECK_ARG(env); + CHECK_ARG(deferred); + CHECK_ARG(result); + + JSValue resolving_funcs[2]; + JSValue promise = JS_NewPromiseCapability(env->context, resolving_funcs); + if (JS_IsException(promise)) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + *deferred = (napi_deferred__ *) mi_malloc(sizeof(napi_deferred__)); + JSValue *resolve = (JSValue *) mi_malloc(sizeof(JSValue)); + JSValue *reject = (JSValue *) mi_malloc(sizeof(JSValue)); + if (*deferred == NULL || resolve == NULL || reject == NULL) { + mi_free(*deferred); + mi_free(resolve); + mi_free(reject); + JS_FreeValue(env->context, resolving_funcs[0]); + JS_FreeValue(env->context, resolving_funcs[1]); + JS_FreeValue(env->context, promise); + return napi_set_last_error(env, napi_memory_error, NULL, 0, NULL); + } + + *resolve = JS_DupValue(env->context, resolving_funcs[0]); + *reject = JS_DupValue(env->context, resolving_funcs[1]); + (*deferred)->resolve = (napi_value) resolve; + (*deferred)->reject = (napi_value) reject; + + JSValue heldValue = JS_NewObjectClass(env->context, env->runtime->externalClassId); + if (JS_IsException(heldValue)) { + deferred_finalize(env, *deferred, NULL); + *deferred = NULL; + JS_FreeValue(env->context, resolving_funcs[0]); + JS_FreeValue(env->context, resolving_funcs[1]); + JS_FreeValue(env->context, promise); + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + ExternalInfo *info = (ExternalInfo *) mi_malloc(sizeof(ExternalInfo)); + if (info == NULL) { + JS_FreeValue(env->context, heldValue); + deferred_finalize(env, *deferred, NULL); + *deferred = NULL; + JS_FreeValue(env->context, resolving_funcs[0]); + JS_FreeValue(env->context, resolving_funcs[1]); + JS_FreeValue(env->context, promise); + return napi_set_last_error(env, napi_memory_error, NULL, 0, NULL); + } + info->data = *deferred; + info->finalizeCallback = deferred_finalize; + info->finalizeHint = NULL; + JS_SetOpaque(heldValue, info); + + JSValue params[] = { + promise, + heldValue}; + + JSValue res = JS_Invoke(env->context, env->finalizationRegistry, env->atoms.registerFinalizer, + 2, params); + bool registrationFailed = JS_IsException(res); + JS_FreeValue(env->context, resolving_funcs[0]); + JS_FreeValue(env->context, resolving_funcs[1]); + + if (registrationFailed) { + JS_SetOpaque(heldValue, NULL); + mi_free(info); + JS_FreeValue(env->context, heldValue); + JS_FreeValue(env->context, res); + deferred_finalize(env, *deferred, NULL); + *deferred = NULL; + JS_FreeValue(env->context, promise); + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + JS_FreeValue(env->context, heldValue); + JS_FreeValue(env->context, res); + + return CreateScopedResult(env, promise, result); +} + +napi_status napi_resolve_deferred(napi_env env, napi_deferred deferred, napi_value resolution) { + CHECK_ARG(env); + CHECK_ARG(deferred); + + JSValue value = JS_UNDEFINED; + if (resolution != NULL) { + value = *((JSValue *) resolution); + } + js_enter(env); + JSValue jsResult = JS_Call(env->context, *((JSValue *) deferred->resolve), JS_UNDEFINED, 1, + &value); + js_exit(env); + JS_FreeValue(env->context, jsResult); + + return napi_clear_last_error(env); +} + +napi_status napi_reject_deferred(napi_env env, napi_deferred deferred, napi_value rejection) { + CHECK_ARG(env); + CHECK_ARG(deferred); + + JSValue value = JS_UNDEFINED; + if (rejection != NULL) { + value = *((JSValue *) rejection); + } + js_enter(env); + JSValue jsResult = JS_Call(env->context, *((JSValue *) deferred->reject), JS_UNDEFINED, 1, + &value); + js_exit(env); + JS_FreeValue(env->context, jsResult); + + return napi_clear_last_error(env); +} + +napi_status napi_is_promise(napi_env env, + napi_value value, + bool *is_promise) { + CHECK_ARG(env); + CHECK_ARG(value); + + JSValue constructor = JS_GetProperty(env->context, *((JSValue *) value), + env->atoms.constructor); + JSValue name = JS_GetProperty(env->context, constructor, env->atoms.name); + const char *cName = JS_ToCString(env->context, name); + *is_promise = !strcmp("Promise", cName ? cName : ""); + JS_FreeCString(env->context, cName); + JS_FreeValue(env->context, name); + JS_FreeValue(env->context, constructor); + + + return napi_clear_last_error(env); +} + +NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory( + napi_env env, int64_t change_in_bytes, int64_t *adjusted_value) { + size_t cur = JS_GetGCThreshold(env->runtime->runtime); + if (cur != env->usedMemory && change_in_bytes < 0) + return napi_ok; // don't update, changed after GC + int64_t new = cur - change_in_bytes; + if (new < 0) new = 0; + JS_SetGCThreshold(env->runtime->runtime, new); + env->usedMemory = new; + *adjusted_value = new; + return napi_ok; +} + + +/** + * -------------------------------------- + * TYPE TAG + * -------------------------------------- + */ +napi_status napi_type_tag_object(napi_env env, napi_value object, const napi_type_tag *tag) { + CHECK_ARG(env); + CHECK_ARG(object); + CHECK_ARG(tag); + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + int hasTag = JS_GetOwnProperty(env->context, NULL, jsValue, + env->atoms.napi_typetag); + if (hasTag < 0) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + RETURN_STATUS_IF_FALSE(!hasTag, napi_invalid_arg); + + const uint32_t size = 2; + uint64_t words[2] = {tag->lower, tag->upper}; + JSValue value = JS_CreateBigIntWords(env->context, 0, size, words); + if (JS_IsException(value)) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + int status = JS_SetProperty(env->context, jsValue, env->atoms.napi_typetag, value); + if (status < 0) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + + return napi_clear_last_error(env); +} + +napi_status +napi_check_object_type_tag(napi_env env, napi_value object, const napi_type_tag *tag, + bool *result) { + CHECK_ARG(env); + CHECK_ARG(object); + CHECK_ARG(tag); + CHECK_ARG(result); + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + JSPropertyDescriptor descriptor; + int hasTag = JS_GetOwnProperty(env->context, &descriptor, jsValue, + env->atoms.napi_typetag); + if (hasTag < 0) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + if (!hasTag) { + *result = false; + return napi_clear_last_error(env); + } + + JSValue value = descriptor.value; + int sign = 0; + size_t wordCount = 2; + uint64_t words[2] = {0}; + bool converted = JS_GetBigIntWords(env->context, value, &sign, &wordCount, words); + JS_FreeValue(env->context, value); + *result = converted && sign == 0 && wordCount == 2 && + words[0] == tag->lower && words[1] == tag->upper; + + return napi_clear_last_error(env); +} + + +napi_status napi_run_script(napi_env env, + napi_value script, + napi_value *result) { + return qjs_execute_script(env, script, NULL, result); +} + + +void host_object_finalizer(JSRuntime *rt, JSValue value) { + napi_env env = (napi_env) JS_GetRuntimeOpaque(rt); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) JS_GetOpaque(value, + env->runtime->napiHostObjectClassId); + if (info->finalize_cb) { + info->finalize_cb(env, info->data, NULL); + } + if (info->is_array) { + napi_delete_reference(env, info->getter); + napi_delete_reference(env, info->setter); + } + + napi_delete_reference(env, info->ref); + mi_free(info); +} + +int host_object_set(JSContext *ctx, JSValue obj, JSAtom atom, + JSValue value, JSValue receiver, int flags) { + napi_env env = (napi_env) JS_GetContextOpaque(ctx); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) JS_GetOpaque(obj, + env->runtime->napiHostObjectClassId); + if (info != NULL) { + if (info->is_array) { + JSValue atom_val = JS_AtomToValue(ctx, atom); + JSValue argv[4] = { + info->ref->value, + atom_val, + value, + obj + }; + JSValue result = JS_Call(ctx, info->setter->value, JS_UNDEFINED, 4, argv); + + JS_FreeValue(ctx, atom_val); + + if (JS_IsException(result) || JS_HasException(ctx)) return -1; + + return true; + } + return JS_SetProperty(ctx, info->ref->value, atom, JS_DupValue(ctx, value)); + } + return true; +} + +JSValue host_object_get(JSContext *ctx, JSValue obj, JSAtom atom, JSValue receiver) { + napi_env env = (napi_env) JS_GetContextOpaque(ctx); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) JS_GetOpaque(obj, + env->runtime->napiHostObjectClassId); + if (info != NULL) { + if (info->is_array) { + JSValue atom_val = JS_AtomToValue(ctx, atom); + JSValue argv[3] = { + info->ref->value, + atom_val, + obj + }; + JSValue value = JS_Call(ctx, info->getter->value, JS_UNDEFINED, 3, argv); + JS_FreeValue(ctx, atom_val); + return value; + } + return JS_GetProperty(ctx, info->ref->value, atom); + } + return JS_UNDEFINED; +} + +int host_object_has(JSContext *ctx, JSValue obj, JSAtom atom) { + napi_env env = (napi_env) JS_GetContextOpaque(ctx); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) JS_GetOpaque(obj, + env->runtime->napiHostObjectClassId); + if (info != NULL) { + return JS_HasProperty(ctx, info->ref->value, atom); + } + return false; +} + +static int host_object_delete(JSContext *ctx, JSValue obj, JSAtom atom) { + napi_env env = (napi_env) JS_GetContextOpaque(ctx); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) JS_GetOpaque(obj, + env->runtime->napiHostObjectClassId); + if (info != NULL) { + return JS_DeleteProperty(ctx, info->ref->value, atom, 0); + } + return true; +} + +static int host_object_get_own_property_names(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, + JSValue obj) { + napi_env env = (napi_env) JS_GetContextOpaque(ctx); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) JS_GetOpaque(obj, + env->runtime->napiHostObjectClassId); + if (info != NULL) { + return JS_GetOwnPropertyNames(ctx, ptab, plen, info->ref->value, + JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY); + } + return true; +} + +static int host_object_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc, + JSValue obj, JSAtom prop) { + napi_env env = (napi_env) JS_GetContextOpaque(ctx); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) JS_GetOpaque(obj, + env->runtime->napiHostObjectClassId); + if (info != NULL) { + return JS_GetOwnProperty(ctx, desc, info->ref->value, prop); + } + return true; +} + +static int host_object_define_own_property(JSContext *ctx, JSValue obj, + JSAtom prop, JSValue val, + JSValue getter, JSValue setter, + int flags) { + napi_env env = (napi_env) JS_GetContextOpaque(ctx); + NapiHostObjectInfo *info = (NapiHostObjectInfo *) JS_GetOpaque(obj, + env->runtime->napiHostObjectClassId); + if (info != NULL) { + return JS_DefineProperty(ctx, info->ref->value, prop, JS_DupValue(ctx, val), getter, setter, + flags); + } + return true; +} + +JSClassExoticMethods NapiHostObjectExoticMethods = { + .set_property = host_object_set, + .get_property = host_object_get, + .has_property = host_object_has, + .delete_property = host_object_delete, +// .get_own_property_names = host_object_get_own_property_names, +// .get_own_property = host_object_get_own_property, +// .define_own_property = host_object_define_own_property +}; + + +napi_status +napi_create_host_object(napi_env env, napi_value value, napi_finalize finalize, void *data, + bool is_array, napi_value getter, napi_value setter, napi_value *result) { + CHECK_ARG(env); + CHECK_ARG(result); + + napi_value constructor; + napi_get_named_property(env, value, "constructor", &constructor); + + napi_value prototype; + napi_get_named_property(env, constructor, "prototype", &prototype); + + JSValue jsValue = JS_NewObjectClass(env->context, env->runtime->napiHostObjectClassId); + JS_SetPrototype(env->context, jsValue, *((JSValue *) prototype)); + + NapiHostObjectInfo *info = (NapiHostObjectInfo *) mi_malloc(sizeof(NapiHostObjectInfo)); + info->data = data; + if (finalize) { + info->finalize_cb = finalize; + } else { + info->finalize_cb = NULL; + } + info->is_array = is_array; + + if (is_array) { + if (getter) napi_create_reference(env, getter, 1, &info->getter); + if (setter) napi_create_reference(env, setter, 1, &info->setter); + } + + napi_create_reference(env, value, 1, &info->ref); + + JS_SetOpaque(jsValue, info); + return CreateScopedResult(env, jsValue, result); +} + +napi_status napi_get_host_object_data(napi_env env, napi_value object, void **data) { + CHECK_ARG(env); + CHECK_ARG(object); + CHECK_ARG(data); + + JSValue jsValue = *((JSValue *) object); + + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + NapiHostObjectInfo *info = (NapiHostObjectInfo *) JS_GetOpaque(jsValue, + env->runtime->napiHostObjectClassId); + if (info) { + *data = info->data; + } else { + *data = NULL; + } + + return napi_clear_last_error(env); +} + +napi_status napi_is_host_object(napi_env env, napi_value object, bool *result) { + CHECK_ARG(env); + CHECK_ARG(object); + + JSValue jsValue = *((JSValue *) object); + + if (!JS_IsObject(jsValue)) { + return napi_set_last_error(env, napi_object_expected, NULL, 0, NULL); + } + + void *data = JS_GetOpaque(jsValue, + env->runtime->napiHostObjectClassId); + if (data != NULL) { + *result = true; + } else { + *result = false; + } + + return napi_clear_last_error(env); +} + +/** + * -------------------------------- + * JS RUNTIME + * -------------------------------- + */ + + + + +napi_status qjs_create_runtime(napi_runtime *runtime) { + assert(runtime); + + *runtime = mi_malloc(sizeof(napi_runtime__)); + if (*runtime == NULL) { + return napi_memory_error; + } + +#ifdef USE_MIMALLOC + (*runtime)->heap = mi_heap_new(); + if ((*runtime)->heap == NULL) { + mi_free(*runtime); + *runtime = NULL; + return napi_memory_error; + } + (*runtime)->runtime = JS_NewRuntime2(&mi_mf, (*runtime)->heap); +#else + (*runtime)->runtime = JS_NewRuntime(); +#endif + if ((*runtime)->runtime == NULL) { +#ifdef USE_MIMALLOC + mi_heap_destroy((*runtime)->heap); +#endif + mi_free(*runtime); + *runtime = NULL; + return napi_memory_error; + } + +#ifdef __QJS_NG__ + #ifndef NDEBUG + JS_SetDumpFlags((*runtime)->runtime, JS_DUMP_LEAKS); + #endif +#endif + + JS_SetMaxStackSize((*runtime)->runtime, 0); + + (*runtime)->constructorClassId = 0; + (*runtime)->functionClassId = 0; + (*runtime)->externalClassId = 0; + (*runtime)->napiHostObjectClassId = 0; + + JSClassDef ExternalClassDef = {"ExternalInfo", external_finalizer, NULL, NULL, NULL}; + JSClassDef FunctionClassDef = {"FunctionInfo", function_finalizer, NULL, NULL, NULL}; + JSClassDef ConstructorClassDef = {"ConstructorInfo", function_finalizer, NULL, NULL, NULL}; + JSClassDef NapiHostObjectClassDef = {"NapiHostObject", host_object_finalizer, NULL, NULL, + &NapiHostObjectExoticMethods}; +#ifndef __QJS_NG__ + JS_NewClassID( &(*runtime)->napiHostObjectClassId); + JS_NewClassID( &(*runtime)->constructorClassId); + JS_NewClassID(&(*runtime)->functionClassId); + JS_NewClassID(&(*runtime)->externalClassId); +#else + JS_NewClassID((*runtime)->runtime, &(*runtime)->napiHostObjectClassId); + JS_NewClassID((*runtime)->runtime, &(*runtime)->constructorClassId); + JS_NewClassID((*runtime)->runtime, &(*runtime)->functionClassId); + JS_NewClassID((*runtime)->runtime, &(*runtime)->externalClassId); +#endif + + JS_NewClass((*runtime)->runtime, (*runtime)->napiHostObjectClassId, &NapiHostObjectClassDef); + JS_NewClass((*runtime)->runtime, (*runtime)->externalClassId, &ExternalClassDef); + JS_NewClass((*runtime)->runtime, (*runtime)->functionClassId, &FunctionClassDef); + JS_NewClass((*runtime)->runtime, (*runtime)->constructorClassId, &ConstructorClassDef); + + return napi_ok; +} + +static JSValue JSRunGCCallback(JSContext *ctx, JSValue +this_val, + int argc, JSValue + *argv) { + JSRuntime *rt = JS_GetRuntime(ctx); + JS_ClearWeakRefKeepAlives(rt); + JS_RunGC(rt); +#ifdef USE_MIMALLOC + napi_env env = (napi_env) JS_GetContextOpaque(ctx); + if (env != NULL && env->runtime != NULL && env->runtime->heap != NULL) { + mi_heap_collect(env->runtime->heap, true); + } +#endif + return JS_TRUE; +} + +static JSValue +JSFinalizeValueCallback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + napi_env env = (napi_env) JS_GetContextOpaque(ctx); + + JSValue heldValue = argv[0]; + if (!JS_IsUndefined(heldValue)) { + ExternalInfo *info = (ExternalInfo *) JS_GetOpaque(heldValue, + env->runtime->externalClassId); + if (info != NULL) { + JS_SetOpaque(heldValue, NULL); + napi_finalize callback = info->finalizeCallback; + void *data = info->data; + void *hint = info->finalizeHint; + mi_free(info); + if (callback != NULL) { + callback(env, data, hint); + } + } + } + return JS_UNDEFINED; +} + +static JSValue +JSEngineCallback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + return JS_UNDEFINED; +} + +#ifndef __QJS_NG__ +void JSR_PromiseRejectionTracker(JSContext *ctx, JSValueConst promise, + JSValueConst reason, + JS_BOOL is_handled, void *opaque) +#else +void JSR_PromiseRejectionTracker(JSContext *ctx, JSValue promise, + JSValue reason, + bool is_handled, void *opaque) +#endif + { + JSValue global = JS_GetGlobalObject(ctx); + JSValue onUnhandledRejection = JS_GetPropertyStr(ctx, global, "onUnhandledPromiseRejectionTracker"); + if (JS_IsFunction(ctx, onUnhandledRejection)) { + JSValue isHandled = JS_NewBool(ctx, is_handled); + JSValue argv[3] = {promise, reason, isHandled}; + JS_Call(ctx, onUnhandledRejection, global, 3, argv); + JS_FreeValue(ctx, isHandled); + } + JS_FreeValue(ctx, onUnhandledRejection); + JS_FreeValue(ctx, global); +} + +napi_status qjs_create_napi_env(napi_env *env, napi_runtime runtime) { + assert(env && runtime); + + *env = (napi_env__ *) mi_malloc(sizeof(struct napi_env__)); + if (*env == NULL) { + return napi_memory_error; + } + + (*env)->runtime = runtime; + + + JSContext *context = JS_NewContext(runtime->runtime); + if (context == NULL) { + mi_free(*env); + *env = NULL; + return napi_memory_error; + } + JS_SetContextOpaque(context, *env); + + (*env)->context = context; + + (*env)->js_enter_state = 0; + + JS_SetRuntimeOpaque(runtime->runtime, *env); + + // Create runtime atoms + (*env)->atoms.napi_external = JS_NewAtom(context, "napi_external"); + (*env)->atoms.registerFinalizer = JS_NewAtom(context, "register"); + (*env)->atoms.name = JS_NewAtom(context, "name"); + (*env)->atoms.constructor = JS_NewAtom(context, "constructor"); + (*env)->atoms.prototype = JS_NewAtom(context, "prototype"); + (*env)->atoms.buffer = JS_NewAtom(context, "buffer"); + (*env)->atoms.length = JS_NewAtom(context, "length"); + (*env)->atoms.object = JS_NewAtom(context, "Object"); + (*env)->atoms.Symbol = JS_NewAtom(context, "Symbol"); + (*env)->atoms.NAPISymbolFor = JS_NewAtom(context, "NAPISymbolFor"); + (*env)->atoms.freeze = JS_NewAtom(context, "freeze"); + (*env)->atoms.is = JS_NewAtom(context, "is"); + (*env)->atoms.byteLength = JS_NewAtom(context, "byteLength"); + (*env)->atoms.byteOffset = JS_NewAtom(context, "byteOffset"); + (*env)->atoms.seal = JS_NewAtom(context, "seal"); + (*env)->atoms.napi_buffer = JS_NewAtom(context, "napi_buffer"); + (*env)->atoms.napi_typetag = JS_NewAtom(context, "napi_typetag"); + (*env)->atoms.weakref = JS_NewAtom(context, "WeakRef"); + + JS_SetClassProto(context, runtime->externalClassId, JS_NewObject(context)); + JS_SetClassProto(context, runtime->functionClassId, JS_NewObject(context)); + JS_SetClassProto(context, runtime->constructorClassId, JS_NewObject(context)); + JS_SetClassProto(context, runtime->napiHostObjectClassId, JS_NewObject(context)); + JSValue globalValue = JS_GetGlobalObject(context); + + JSValue gc = JS_NewCFunction(context, JSRunGCCallback, "gc", 0); + JS_SetPropertyStr(context, globalValue, "gc", gc); + + + JSValue FinalizationRegistry = JS_GetPropertyStr(context, globalValue, "FinalizationRegistry"); + JSValue FinalizeCallback = JS_NewCFunction(context, JSFinalizeValueCallback, NULL, 0); + (*env)->finalizationRegistry = JS_CallConstructor(context, FinalizationRegistry, 1, + &FinalizeCallback); + + JSValue EngineCallback = JS_NewCFunction(context, JSEngineCallback, NULL, 0); + JS_SetPropertyStr(context, globalValue, "directFunction", EngineCallback); + + JS_SetHostPromiseRejectionTracker(runtime->runtime, JSR_PromiseRejectionTracker, *env); + + (*env)->instanceData = NULL; + (*env)->isThrowNull = false; + (*env)->gcBefore = NULL; + (*env)->gcAfter = NULL; + (*env)->referenceSymbolValue = JS_UNDEFINED; + + + LIST_INIT(&(*env)->handleScopeList); + LIST_INIT(&(*env)->referencesList); + + static const char script[] = "globalThis.CreateBigIntWords = (sign, word) => { " + " const max_v = BigInt(2 ** 64 - 1);" + " var bg = 0n;" + " for (var i=0; i {" + "const max_v = BigInt(2 ** 64 - 1);" + "var rev = {};" + "rev.sign = 0;" + "rev.count = 0;" + "rev.words = [];" + "if (big < 0n) {" + " rev.sign = 1;" + " big = big * (-1n);" + "}" + "while (big >= max_v) {" + " rev.words[rev.count] = big % max_v;" + " big = big / max_v;" + " rev.count++;" + "}" + "rev.words[rev.count] = big % max_v;" + "rev.count++;" + "return rev;" + "};" + "globalThis.NAPISymbolFor = (description) => {return Symbol.for(description)};"; + + JSValue result = JS_Eval((*env)->context, script, strlen(script), "", + JS_EVAL_TYPE_GLOBAL); + + JS_FreeValue((*env)->context, result); + JS_FreeValue(context, FinalizationRegistry); + JS_FreeValue(context, FinalizeCallback); + JS_FreeValue(context, globalValue); + + return napi_clear_last_error((*env)); +} + +napi_status qjs_free_napi_env(napi_env env) { + CHECK_ARG(env) + + // Instance data owns bridge state whose finalizer deletes N-API references. + // Run it while the reference and handle lists are still intact. + ExternalInfo *instanceData = env->instanceData; + env->instanceData = NULL; + if (instanceData != NULL) { + if (instanceData->finalizeCallback != NULL) { + instanceData->finalizeCallback(env, instanceData->data, + instanceData->finalizeHint); + } + mi_free(instanceData); + } + + // Free all handle scopes + napi_handle_scope handleScope, tempHandleScope; + LIST_FOREACH_SAFE(handleScope, &env->handleScopeList, node, tempHandleScope) { + struct Handle *handle, *tempHandle; + SLIST_FOREACH_SAFE(handle, &handleScope->handleList, node, tempHandle) { + JS_FreeValue(env->context, handle->value); + if (handle->type == HANDLE_HEAP_ALLOCATED) { + mi_free(handle); + } + } + LIST_REMOVE(handleScope, node); + if (handleScope->type == HANDLE_HEAP_ALLOCATED) { + mi_free(handleScope); + } + } + + // Free all references + napi_ref ref, temp; + LIST_FOREACH_SAFE(ref, &env->referencesList, node, temp) { + LIST_REMOVE(ref, node); + JS_FreeValue(env->context, ref->value); + mi_free(ref); + } + + // Free Reference Symbol + JS_FreeValue(env->context, env->referenceSymbolValue); + + // Free Finalization Registry + JS_FreeValue(env->context, env->finalizationRegistry); + + if (env->gcAfter != NULL) { + mi_free(env->gcAfter); + } + + if (env->gcBefore != NULL) { + mi_free(env->gcBefore); + } + + // Free Atoms + JS_FreeAtom(env->context, env->atoms.napi_external); + JS_FreeAtom(env->context, env->atoms.registerFinalizer); + JS_FreeAtom(env->context, env->atoms.buffer); + JS_FreeAtom(env->context, env->atoms.napi_buffer); + JS_FreeAtom(env->context, env->atoms.byteLength); + JS_FreeAtom(env->context, env->atoms.byteOffset); + JS_FreeAtom(env->context, env->atoms.constructor); + JS_FreeAtom(env->context, env->atoms.prototype); + JS_FreeAtom(env->context, env->atoms.name); + JS_FreeAtom(env->context, env->atoms.length); + JS_FreeAtom(env->context, env->atoms.is); + JS_FreeAtom(env->context, env->atoms.freeze); + JS_FreeAtom(env->context, env->atoms.seal); + JS_FreeAtom(env->context, env->atoms.Symbol); + JS_FreeAtom(env->context, env->atoms.NAPISymbolFor); + JS_FreeAtom(env->context, env->atoms.object); + JS_FreeAtom(env->context, env->atoms.napi_typetag); + JS_FreeAtom(env->context, env->atoms.weakref); + + // Free Context + JS_FreeContext(env->context); + + + return napi_clear_last_error(env); +} + +napi_status qjs_free_runtime(napi_runtime runtime) { + assert(runtime); + + napi_env env = (napi_env) JS_GetRuntimeOpaque(runtime->runtime); + + JS_FreeRuntime(runtime->runtime); + + mi_free(env); +#ifdef USE_MIMALLOC + mi_heap_destroy(runtime->heap); +#endif + mi_free(runtime); + + return napi_ok; +} + + +napi_status qjs_execute_script(napi_env env, + napi_value script, + const char *file, + napi_value *result) { + CHECK_ARG(env) + CHECK_ARG(script) + + JSValue eval_result; + const char *cScript = JS_ToCString(env->context, *((JSValue *) script)); + js_enter(env); + eval_result = JS_Eval(env->context, cScript, strlen(cScript), file, JS_EVAL_TYPE_GLOBAL); + JS_FreeCString(env->context, cScript); + js_exit(env); + if (JS_IsException(eval_result)) { + JSValue exceptionValue = JS_GetException(env->context); + const char *exceptionMessage = JS_ToCString(env->context, exceptionValue); + napi_set_last_error(env, napi_cannot_run_js, exceptionMessage, 0, NULL); + JS_FreeCString(env->context, exceptionMessage); + JS_Throw(env->context, exceptionValue); + return napi_cannot_run_js; + } + + if (result) { + CreateScopedResult(env, eval_result, result); + } else { + JS_FreeValue(env->context, eval_result); + } + + return napi_clear_last_error(env); +} + + +napi_status qjs_runtime_before_gc_callback(napi_env env, napi_finalize cb, void *data) { + CHECK_ARG(env) + CHECK_ARG(cb) + + ExternalInfo *info = mi_malloc(sizeof(ExternalInfo)); + info->data = data; + info->finalizeCallback = cb; + info->finalizeHint = NULL; + env->gcBefore = info; + + return napi_clear_last_error(env); +} + +napi_status qjs_runtime_after_gc_callback(napi_env env, napi_finalize cb, void *data) { + CHECK_ARG(env) + CHECK_ARG(cb) + + ExternalInfo *info = mi_malloc(sizeof(ExternalInfo)); + info->data = data; + info->finalizeCallback = cb; + info->finalizeHint = NULL; + + env->gcAfter = info; + + return napi_clear_last_error(env); +} + +napi_status qjs_execute_pending_jobs(napi_env env) { + CHECK_ARG(env) + int error; + do { + JSContext *context; + error = JS_ExecutePendingJob(JS_GetRuntime(env->context), &context); + if (error == -1) { + return napi_set_last_error(env, napi_pending_exception, NULL, 0, NULL); + } + } while (error != 0); + + return napi_clear_last_error(env); +} + +napi_status qjs_update_stack_top(napi_env env) { + CHECK_ARG(env) + JS_UpdateStackTop(env->runtime->runtime); + return napi_clear_last_error(env); +} diff --git a/NativeScript/napi/android/quickjs/quicks-runtime.h b/NativeScript/napi/android/quickjs/quicks-runtime.h new file mode 100644 index 000000000..e1d969ca2 --- /dev/null +++ b/NativeScript/napi/android/quickjs/quicks-runtime.h @@ -0,0 +1,35 @@ +// +// Created by Ammar Ahmed on 09/02/2025. +// + +#ifndef TEST_APP_QUICKS_RUNTIME_H +#define TEST_APP_QUICKS_RUNTIME_H +#include "js_native_api.h" + +EXTERN_C_START + +NAPI_EXTERN napi_status NAPI_CDECL qjs_create_runtime(napi_runtime *runtime); + +NAPI_EXTERN napi_status NAPI_CDECL qjs_create_napi_env(napi_env *env, napi_runtime runtime); + +NAPI_EXTERN napi_status NAPI_CDECL qjs_free_napi_env(napi_env env); + +NAPI_EXTERN napi_status NAPI_CDECL qjs_free_runtime(napi_runtime runtime); + +NAPI_EXTERN napi_status NAPI_CDECL qjs_execute_script(napi_env env, + napi_value script, + const char *file, + napi_value *result); + +NAPI_EXTERN napi_status NAPI_CDECL qjs_runtime_before_gc_callback(napi_env env, napi_finalize cb, void *data); + +NAPI_EXTERN napi_status NAPI_CDECL qjs_runtime_after_gc_callback(napi_env env, napi_finalize cb, void *data); + + +NAPI_EXTERN napi_status NAPI_CDECL qjs_execute_pending_jobs(napi_env env); + +NAPI_EXTERN napi_status NAPI_CDECL qjs_update_stack_top(napi_env env); + +EXTERN_C_END + +#endif //TEST_APP_QUICKS_RUNTIME_H diff --git a/NativeScript/napi/android/quickjs/source/.gitignore b/NativeScript/napi/android/quickjs/source/.gitignore new file mode 100644 index 000000000..fbeb98f3a --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/.gitignore @@ -0,0 +1,9 @@ +*.a +*.orig +*.so +.obj/ +build/ +unicode/ +test262_*.txt +.idea +cmake-* \ No newline at end of file diff --git a/NativeScript/napi/android/quickjs/source/.gitmodules b/NativeScript/napi/android/quickjs/source/.gitmodules new file mode 100644 index 000000000..b10f021f2 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/.gitmodules @@ -0,0 +1,4 @@ +[submodule "test262"] + path = test262 + url = https://github.com/tc39/test262 + shallow = true diff --git a/NativeScript/napi/android/quickjs/source/Changelog b/NativeScript/napi/android/quickjs/source/Changelog new file mode 100755 index 000000000..070b0a77a --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/Changelog @@ -0,0 +1,219 @@ +- micro optimizations (30% faster on bench-v8) +- added resizable array buffers +- added ArrayBuffer.prototype.transfer +- added the Iterator object and methods +- added set methods +- added Atomics.pause +- added added Map and WeakMap upsert methods +- added Math.sumPrecise() +- misc bug fixes + +2025-09-13: + +- added JSON modules and import attributes +- added JS_PrintValue() API +- qjs: pretty print objects in print() and console.log() +- qjs: better promise rejection tracker heuristics +- added RegExp v flag +- added RegExp modifiers +- added RegExp.escape +- added Float16Array +- added Promise.try +- improved JSON parser spec conformance +- qjs: improved compatibility of std.parseExtJSON() with JSON5 and + accept JSON5 modules +- added JS_FreePropertyEnum() and JS_AtomToCStringLen() API +- added Error.isError() +- misc bug fixes + +2025-04-26: + +- removed the bignum extensions and qjscalc +- new BigInt implementation optimized for small numbers +- added WeakRef, FinalizationRegistry and symbols as weakrefs +- added builtin float64 printing and parsing functions for more correctness +- faster repeated string concatenation +- qjs: promise unhandled rejections are fatal errors by default +- added column number in debug information +- removed the "use strip" extension +- qjs: added -s and --strip-source options +- qjsc: added -s and --keep-source options +- added JS_GetAnyOpaque() +- added more callbacks for exotic objects in JSClassExoticMethods +- misc bug fixes + +2024-01-13: + +- top-level-await support in modules +- allow 'await' in the REPL +- added Array.prototype.{with,toReversed,toSpliced,toSorted} and +TypedArray.prototype.{with,toReversed,toSorted} +- added String.prototype.isWellFormed and String.prototype.toWellFormed +- added Object.groupBy and Map.groupBy +- added Promise.withResolvers +- class static block +- 'in' operator support for private fields +- optional chaining fixes +- added RegExp 'd' flag +- fixed RegExp zero length match logic +- fixed RegExp case insensitive flag +- added os.sleepAsync(), os.getpid() and os.now() +- added cosmopolitan build +- misc bug fixes + +2023-12-09: + +- added Object.hasOwn, {String|Array|TypedArray}.prototype.at, + {Array|TypedArray}.prototype.findLast{Index} +- BigInt support is enabled even if CONFIG_BIGNUM disabled +- updated to Unicode 15.0.0 +- misc bug fixes + +2021-03-27: + +- faster Array.prototype.push and Array.prototype.unshift +- added JS_UpdateStackTop() +- fixed Windows console +- misc bug fixes + +2020-11-08: + +- improved function parameter initializers +- added std.setenv(), std.unsetenv() and std.getenviron() +- added JS_EvalThis() +- misc bug fixes + +2020-09-06: + +- added logical assignment operators +- added IsHTMLDDA support +- faster for-of loops +- os.Worker now takes a module filename as parameter +- qjsc: added -D option to compile dynamically loaded modules or workers +- misc bug fixes + +2020-07-05: + +- modified JS_GetPrototype() to return a live value +- REPL: support unicode characters larger than 16 bits +- added os.Worker +- improved object serialization +- added std.parseExtJSON +- misc bug fixes + +2020-04-12: + +- added cross realm support +- added AggregateError and Promise.any +- added env, uid and gid options in os.exec() +- misc bug fixes + +2020-03-16: + +- reworked error handling in std and os libraries: suppressed I/O + exceptions in std FILE functions and return a positive errno value + when it is explicit +- output exception messages to stderr +- added std.loadFile(), std.strerror(), std.FILE.prototype.tello() +- added JS_GetRuntimeOpaque(), JS_SetRuntimeOpaque(), JS_NewUint32() +- updated to Unicode 13.0.0 +- misc bug fixes + +2020-01-19: + +- keep CONFIG_BIGNUM in the makefile +- added os.chdir() +- qjs: added -I option +- more memory checks in the bignum operations +- modified operator overloading semantics to be closer to the TC39 + proposal +- suppressed "use bigint" mode. Simplified "use math" mode +- BigDecimal: changed suffix from 'd' to 'm' +- misc bug fixes + +2020-01-05: + +- always compile the bignum code. Added '--bignum' option to qjs. +- added BigDecimal +- added String.prototype.replaceAll +- misc bug fixes + +2019-12-21: + +- added nullish coalescing operator (ES2020) +- added optional chaining (ES2020) +- removed recursions in garbage collector +- test stack overflow in the parser +- improved backtrace logic +- added JS_SetHostPromiseRejectionTracker() +- allow exotic constructors +- improved c++ compatibility +- misc bug fixes + +2019-10-27: + +- added example of C class in a module (examples/test_point.js) +- added JS_GetTypedArrayBuffer() +- misc bug fixes + +2019-09-18: + +- added os.exec and other system calls +- exported JS_ValueToAtom() +- qjsc: added 'qjsc_' prefix to the generated C identifiers +- added cross-compilation support +- misc bug fixes + +2019-09-01: + +- added globalThis +- documented JS_EVAL_FLAG_COMPILE_ONLY +- added import.meta.url and import.meta.main +- added 'debugger' statement +- misc bug fixes + +2019-08-18: + +- added os.realpath, os.getcwd, os.mkdir, os.stat, os.lstat, + os.readlink, os.readdir, os.utimes, std.popen +- module autodetection +- added import.meta +- misc bug fixes + +2019-08-10: + +- added public class fields and private class fields, methods and + accessors (TC39 proposal) +- changed JS_ToCStringLen() prototype +- qjsc: handle '-' in module names and modules with the same filename +- added std.urlGet +- exported JS_GetOwnPropertyNames() and JS_GetOwnProperty() +- exported some bigint C functions +- added support for eshost in run-test262 +- misc bug fixes + +2019-07-28: + +- added dynamic import +- added Promise.allSettled +- added String.prototype.matchAll +- added Object.fromEntries +- reduced number of ticks in await +- added BigInt support in Atomics +- exported JS_NewPromiseCapability() +- misc async function and async generator fixes +- enabled hashbang support by default + +2019-07-21: + +- updated test262 tests +- updated to Unicode version 12.1.0 +- fixed missing Date object in qjsc +- fixed multi-context creation +- misc ES2020 related fixes +- simplified power and division operators in bignum extension +- fixed several crash conditions + +2019-07-09: + +- first public release diff --git a/NativeScript/napi/android/quickjs/source/LICENSE b/NativeScript/napi/android/quickjs/source/LICENSE new file mode 100755 index 000000000..2cf449dd3 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/LICENSE @@ -0,0 +1,22 @@ +QuickJS Javascript Engine + +Copyright (c) 2017-2021 Fabrice Bellard +Copyright (c) 2017-2021 Charlie Gordon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/NativeScript/napi/android/quickjs/source/Makefile b/NativeScript/napi/android/quickjs/source/Makefile new file mode 100755 index 000000000..ba64923d2 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/Makefile @@ -0,0 +1,550 @@ +# +# QuickJS Javascript Engine +# +# Copyright (c) 2017-2021 Fabrice Bellard +# Copyright (c) 2017-2021 Charlie Gordon +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +ifeq ($(shell uname -s),Darwin) +CONFIG_DARWIN=y +endif +ifeq ($(shell uname -s),FreeBSD) +CONFIG_FREEBSD=y +endif +# Windows cross compilation from Linux +# May need to have libwinpthread*.dll alongside the executable +# (On Ubuntu/Debian may be installed with mingw-w64-x86-64-dev +# to /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll) +#CONFIG_WIN32=y +# use link time optimization (smaller and faster executables but slower build) +#CONFIG_LTO=y +# consider warnings as errors (for development) +#CONFIG_WERROR=y +# force 32 bit build on x86_64 +#CONFIG_M32=y +# cosmopolitan build (see https://github.com/jart/cosmopolitan) +#CONFIG_COSMO=y + +# installation directory +PREFIX?=/usr/local + +# use the gprof profiler +#CONFIG_PROFILE=y +# use address sanitizer +#CONFIG_ASAN=y +# use memory sanitizer +#CONFIG_MSAN=y +# use UB sanitizer +#CONFIG_UBSAN=y + +# TEST262 bootstrap config: commit id and shallow "since" parameter +TEST262_COMMIT?=42303c7c2bcf1c1edb9e5375c291c6fbc8a261ab +TEST262_SINCE?=2025-09-01 + +OBJDIR=.obj + +ifdef CONFIG_ASAN +OBJDIR:=$(OBJDIR)/asan +endif +ifdef CONFIG_MSAN +OBJDIR:=$(OBJDIR)/msan +endif +ifdef CONFIG_UBSAN +OBJDIR:=$(OBJDIR)/ubsan +endif + +ifdef CONFIG_DARWIN +# use clang instead of gcc +CONFIG_CLANG=y +CONFIG_DEFAULT_AR=y +endif +ifdef CONFIG_FREEBSD +# use clang instead of gcc +CONFIG_CLANG=y +CONFIG_DEFAULT_AR=y +CONFIG_LTO= +endif + +ifdef CONFIG_WIN32 + ifdef CONFIG_M32 + CROSS_PREFIX?=i686-w64-mingw32- + else + CROSS_PREFIX?=x86_64-w64-mingw32- + endif + EXE=.exe +else ifdef MSYSTEM + CONFIG_WIN32=y + CROSS_PREFIX?= + EXE=.exe +else + CROSS_PREFIX?= + EXE= +endif + +ifdef CONFIG_CLANG + HOST_CC=clang + CC=$(CROSS_PREFIX)clang + CFLAGS+=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d + CFLAGS += -Wextra + CFLAGS += -Wno-sign-compare + CFLAGS += -Wno-missing-field-initializers + CFLAGS += -Wundef -Wuninitialized + CFLAGS += -Wunused -Wno-unused-parameter + CFLAGS += -Wwrite-strings + CFLAGS += -Wchar-subscripts -funsigned-char + CFLAGS += -MMD -MF $(OBJDIR)/$(@F).d + ifdef CONFIG_DEFAULT_AR + AR=$(CROSS_PREFIX)ar + else + ifdef CONFIG_LTO + AR=$(CROSS_PREFIX)llvm-ar + else + AR=$(CROSS_PREFIX)ar + endif + endif + LIB_FUZZING_ENGINE ?= "-fsanitize=fuzzer" +else ifdef CONFIG_COSMO + CONFIG_LTO= + HOST_CC=gcc + CC=cosmocc + # cosmocc does not correct support -MF + CFLAGS=-g -Wall #-MMD -MF $(OBJDIR)/$(@F).d + CFLAGS += -Wno-array-bounds -Wno-format-truncation + AR=cosmoar +else + HOST_CC=gcc + CC=$(CROSS_PREFIX)gcc + CFLAGS+=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d + CFLAGS += -Wno-array-bounds -Wno-format-truncation -Wno-infinite-recursion + ifdef CONFIG_LTO + AR=$(CROSS_PREFIX)gcc-ar + else + AR=$(CROSS_PREFIX)ar + endif +endif +STRIP?=$(CROSS_PREFIX)strip +ifdef CONFIG_M32 +CFLAGS+=-msse2 -mfpmath=sse # use SSE math for correct FP rounding +ifndef CONFIG_WIN32 +CFLAGS+=-m32 +LDFLAGS+=-m32 +endif +endif +CFLAGS+=-fwrapv # ensure that signed overflows behave as expected +ifdef CONFIG_WERROR +CFLAGS+=-Werror +endif +DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(shell cat VERSION)\" +ifdef CONFIG_WIN32 +DEFINES+=-D__USE_MINGW_ANSI_STDIO # for standard snprintf behavior +endif +ifndef CONFIG_WIN32 +ifeq ($(shell $(CC) -o /dev/null compat/test-closefrom.c 2>/dev/null && echo 1),1) +DEFINES+=-DHAVE_CLOSEFROM +endif +endif + +CFLAGS+=$(DEFINES) +CFLAGS_DEBUG=$(CFLAGS) -O0 +CFLAGS_SMALL=$(CFLAGS) -Os +CFLAGS_OPT=$(CFLAGS) -O2 +CFLAGS_NOLTO:=$(CFLAGS_OPT) +ifdef CONFIG_COSMO +LDFLAGS+=-s # better to strip by default +else +LDFLAGS+=-g +endif +ifdef CONFIG_LTO +CFLAGS_SMALL+=-flto +CFLAGS_OPT+=-flto +LDFLAGS+=-flto +endif +ifdef CONFIG_PROFILE +CFLAGS+=-p +LDFLAGS+=-p +endif +ifdef CONFIG_ASAN +CFLAGS+=-fsanitize=address -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer +endif +ifdef CONFIG_MSAN +CFLAGS+=-fsanitize=memory -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=memory -fno-omit-frame-pointer +endif +ifdef CONFIG_UBSAN +CFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer +endif +ifdef CONFIG_WIN32 +LDEXPORT= +else +LDEXPORT=-rdynamic +endif + +ifndef CONFIG_COSMO +ifndef CONFIG_DARWIN +ifndef CONFIG_WIN32 +CONFIG_SHARED_LIBS=y # building shared libraries is supported +endif +endif +endif + +PROGS=qjs$(EXE) qjsc$(EXE) run-test262$(EXE) + +ifneq ($(CROSS_PREFIX),) +QJSC_CC=gcc +QJSC=./host-qjsc +PROGS+=$(QJSC) +else +QJSC_CC=$(CC) +QJSC=./qjsc$(EXE) +endif +PROGS+=libquickjs.a +ifdef CONFIG_LTO +PROGS+=libquickjs.lto.a +endif + +# examples +ifeq ($(CROSS_PREFIX),) +ifndef CONFIG_ASAN +ifndef CONFIG_MSAN +ifndef CONFIG_UBSAN +PROGS+=examples/hello examples/test_fib +# no -m32 option in qjsc +ifndef CONFIG_M32 +ifndef CONFIG_WIN32 +PROGS+=examples/hello_module +endif +endif +ifdef CONFIG_SHARED_LIBS +PROGS+=examples/fib.so examples/point.so +endif +endif +endif +endif +endif + +all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS) + +QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/dtoa.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o + +QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS) + +HOST_LIBS=-lm -ldl -lpthread +LIBS=-lm -lpthread +ifndef CONFIG_WIN32 +LIBS+=-ldl +endif +LIBS+=$(EXTRA_LIBS) + +$(OBJDIR): + mkdir -p $(OBJDIR) $(OBJDIR)/examples $(OBJDIR)/tests + +qjs$(EXE): $(QJS_OBJS) + $(CC) $(LDFLAGS) $(LDEXPORT) -o $@ $^ $(LIBS) + +qjs-debug$(EXE): $(patsubst %.o, %.debug.o, $(QJS_OBJS)) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +qjsc$(EXE): $(OBJDIR)/qjsc.o $(QJS_LIB_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +fuzz_eval: $(OBJDIR)/fuzz_eval.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a + $(CC) $(CFLAGS_OPT) $^ -o fuzz_eval $(LIB_FUZZING_ENGINE) + +fuzz_compile: $(OBJDIR)/fuzz_compile.o $(OBJDIR)/fuzz_common.o libquickjs.fuzz.a + $(CC) $(CFLAGS_OPT) $^ -o fuzz_compile $(LIB_FUZZING_ENGINE) + +fuzz_regexp: $(OBJDIR)/fuzz_regexp.o $(OBJDIR)/libregexp.fuzz.o $(OBJDIR)/cutils.fuzz.o $(OBJDIR)/libunicode.fuzz.o + $(CC) $(CFLAGS_OPT) $^ -o fuzz_regexp $(LIB_FUZZING_ENGINE) + +libfuzzer: fuzz_eval fuzz_compile fuzz_regexp + +ifneq ($(CROSS_PREFIX),) + +$(QJSC): $(OBJDIR)/qjsc.host.o \ + $(patsubst %.o, %.host.o, $(QJS_LIB_OBJS)) + $(HOST_CC) $(LDFLAGS) -o $@ $^ $(HOST_LIBS) + +endif #CROSS_PREFIX + +QJSC_DEFINES:=-DCONFIG_CC=\"$(QJSC_CC)\" -DCONFIG_PREFIX=\"$(PREFIX)\" +ifdef CONFIG_LTO +QJSC_DEFINES+=-DCONFIG_LTO +endif +QJSC_HOST_DEFINES:=-DCONFIG_CC=\"$(HOST_CC)\" -DCONFIG_PREFIX=\"$(PREFIX)\" + +$(OBJDIR)/qjsc.o: CFLAGS+=$(QJSC_DEFINES) +$(OBJDIR)/qjsc.host.o: CFLAGS+=$(QJSC_HOST_DEFINES) + +ifdef CONFIG_LTO +LTOEXT=.lto +else +LTOEXT= +endif + +libquickjs$(LTOEXT).a: $(QJS_LIB_OBJS) + $(AR) rcs $@ $^ + +ifdef CONFIG_LTO +libquickjs.a: $(patsubst %.o, %.nolto.o, $(QJS_LIB_OBJS)) + $(AR) rcs $@ $^ +endif # CONFIG_LTO + +libquickjs.fuzz.a: $(patsubst %.o, %.fuzz.o, $(QJS_LIB_OBJS)) + $(AR) rcs $@ $^ + +repl.c: $(QJSC) repl.js + $(QJSC) -s -c -o $@ -m repl.js + +ifneq ($(wildcard unicode/UnicodeData.txt),) +$(OBJDIR)/libunicode.o $(OBJDIR)/libunicode.nolto.o: libunicode-table.h + +libunicode-table.h: unicode_gen + ./unicode_gen unicode $@ +endif + +run-test262$(EXE): $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +run-test262-debug: $(patsubst %.o, %.debug.o, $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS)) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +# object suffix order: nolto + +$(OBJDIR)/%.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_OPT) -c -o $@ $< + +$(OBJDIR)/fuzz_%.o: fuzz/fuzz_%.c | $(OBJDIR) + $(CC) $(CFLAGS_OPT) -c -I. -o $@ $< + +$(OBJDIR)/%.host.o: %.c | $(OBJDIR) + $(HOST_CC) $(CFLAGS_OPT) -c -o $@ $< + +$(OBJDIR)/%.pic.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_OPT) -fPIC -DJS_SHARED_LIBRARY -c -o $@ $< + +$(OBJDIR)/%.nolto.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_NOLTO) -c -o $@ $< + +$(OBJDIR)/%.debug.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_DEBUG) -c -o $@ $< + +$(OBJDIR)/%.fuzz.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_OPT) -fsanitize=fuzzer-no-link -c -o $@ $< + +$(OBJDIR)/%.check.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS) -DCONFIG_CHECK_JSVALUE -c -o $@ $< + +regexp_test: libregexp.c libunicode.c cutils.c + $(CC) $(LDFLAGS) $(CFLAGS) -DTEST -o $@ libregexp.c libunicode.c cutils.c $(LIBS) + +unicode_gen: $(OBJDIR)/unicode_gen.host.o $(OBJDIR)/cutils.host.o libunicode.c unicode_gen_def.h + $(HOST_CC) $(LDFLAGS) $(CFLAGS) -o $@ $(OBJDIR)/unicode_gen.host.o $(OBJDIR)/cutils.host.o + +clean: + rm -f repl.c out.c + rm -f *.a *.o *.d *~ unicode_gen regexp_test fuzz_eval fuzz_compile fuzz_regexp $(PROGS) + rm -f hello.c test_fib.c + rm -f examples/*.so tests/*.so + rm -rf $(OBJDIR)/ *.dSYM/ qjs-debug$(EXE) + rm -rf run-test262-debug$(EXE) + rm -f run_octane run_sunspider_like + +install: all + mkdir -p "$(DESTDIR)$(PREFIX)/bin" + $(STRIP) qjs$(EXE) qjsc$(EXE) + install -m755 qjs$(EXE) qjsc$(EXE) "$(DESTDIR)$(PREFIX)/bin" + mkdir -p "$(DESTDIR)$(PREFIX)/lib/quickjs" + install -m644 libquickjs.a "$(DESTDIR)$(PREFIX)/lib/quickjs" +ifdef CONFIG_LTO + install -m644 libquickjs.lto.a "$(DESTDIR)$(PREFIX)/lib/quickjs" +endif + mkdir -p "$(DESTDIR)$(PREFIX)/include/quickjs" + install -m644 quickjs.h quickjs-libc.h "$(DESTDIR)$(PREFIX)/include/quickjs" + +############################################################################### +# examples + +# example of static JS compilation +HELLO_SRCS=examples/hello.js +HELLO_OPTS=-fno-string-normalize -fno-map -fno-promise -fno-typedarray \ + -fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \ + -fno-date -fno-module-loader + +hello.c: $(QJSC) $(HELLO_SRCS) + $(QJSC) -e $(HELLO_OPTS) -o $@ $(HELLO_SRCS) + +examples/hello: $(OBJDIR)/hello.o $(QJS_LIB_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +# example of static JS compilation with modules +HELLO_MODULE_SRCS=examples/hello_module.js +HELLO_MODULE_OPTS=-fno-string-normalize -fno-map -fno-typedarray \ + -fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \ + -fno-date -m +examples/hello_module: $(QJSC) libquickjs$(LTOEXT).a $(HELLO_MODULE_SRCS) + $(QJSC) $(HELLO_MODULE_OPTS) -o $@ $(HELLO_MODULE_SRCS) + +# use of an external C module (static compilation) + +test_fib.c: $(QJSC) examples/test_fib.js + $(QJSC) -e -M examples/fib.so,fib -m -o $@ examples/test_fib.js + +examples/test_fib: $(OBJDIR)/test_fib.o $(OBJDIR)/examples/fib.o libquickjs$(LTOEXT).a + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +examples/fib.so: $(OBJDIR)/examples/fib.pic.o + $(CC) $(LDFLAGS) -shared -o $@ $^ + +examples/point.so: $(OBJDIR)/examples/point.pic.o + $(CC) $(LDFLAGS) -shared -o $@ $^ + +############################################################################### +# documentation + +DOCS=doc/quickjs.pdf doc/quickjs.html + +build_doc: $(DOCS) + +clean_doc: + rm -f $(DOCS) + +doc/version.texi: VERSION + @echo "@set VERSION `cat $<`" > $@ + +doc/%.pdf: doc/%.texi doc/version.texi + texi2pdf --clean -o $@ -q $< + +doc/%.html.pre: doc/%.texi doc/version.texi + makeinfo --html --no-headers --no-split --number-sections -o $@ $< + +doc/%.html: doc/%.html.pre + sed -e 's||\n|' < $< > $@ + +############################################################################### +# tests + +ifdef CONFIG_SHARED_LIBS +test: tests/bjson.so examples/point.so +endif + +test: qjs$(EXE) + $(WINE) ./qjs$(EXE) tests/test_closure.js + $(WINE) ./qjs$(EXE) tests/test_language.js + $(WINE) ./qjs$(EXE) --std tests/test_builtin.js + $(WINE) ./qjs$(EXE) tests/test_loop.js + $(WINE) ./qjs$(EXE) tests/test_bigint.js + $(WINE) ./qjs$(EXE) tests/test_cyclic_import.js + $(WINE) ./qjs$(EXE) tests/test_worker.js +ifndef CONFIG_WIN32 + $(WINE) ./qjs$(EXE) tests/test_std.js +endif +ifdef CONFIG_SHARED_LIBS + $(WINE) ./qjs$(EXE) tests/test_bjson.js + $(WINE) ./qjs$(EXE) examples/test_point.js +endif + +stats: qjs$(EXE) + $(WINE) ./qjs$(EXE) -qd + +microbench: qjs$(EXE) + $(WINE) ./qjs$(EXE) --std tests/microbench.js + +ifeq ($(wildcard test262/features.txt),) +test2-bootstrap: + git clone --single-branch --shallow-since=$(TEST262_SINCE) https://github.com/tc39/test262.git + (cd test262 && git checkout -q $(TEST262_COMMIT) && patch -p1 < ../tests/test262.patch && cd ..) +else +test2-bootstrap: + (cd test262 && git fetch && git reset --hard $(TEST262_COMMIT) && patch -p1 < ../tests/test262.patch && cd ..) +endif + +ifeq ($(wildcard test262o/tests.txt),) +test2o test2o-update: + @echo test262o tests not installed +else +# ES5 tests (obsolete) +test2o: run-test262 + time ./run-test262 -t -m -c test262o.conf + +test2o-update: run-test262 + ./run-test262 -t -u -c test262o.conf +endif + +ifeq ($(wildcard test262/features.txt),) +test2 test2-update test2-default test2-check: + @echo test262 tests not installed +else +# Test262 tests +test2-default: run-test262 + time ./run-test262 -t -m -c test262.conf + +test2: run-test262 + time ./run-test262 -t -m -c test262.conf -a + +test2-update: run-test262 + ./run-test262 -t -u -c test262.conf -a + +test2-check: run-test262 + time ./run-test262 -t -m -c test262.conf -E -a +endif + +testall: all test microbench test2o test2 + +testall-complete: testall + +node-test: + node tests/test_closure.js + node tests/test_language.js + node tests/test_builtin.js + node tests/test_loop.js + node tests/test_bigint.js + +node-microbench: + node tests/microbench.js -s microbench-node.txt + node --jitless tests/microbench.js -s microbench-node-jitless.txt + +bench-v8: qjs + make -C tests/bench-v8 + ./qjs -d tests/bench-v8/combined.js + +node-bench-v8: + make -C tests/bench-v8 + node --jitless tests/bench-v8/combined.js + +tests/bjson.so: $(OBJDIR)/tests/bjson.pic.o + $(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS) + +BENCHMARKDIR=../quickjs-benchmarks + +run_sunspider_like: $(BENCHMARKDIR)/run_sunspider_like.c + $(CC) $(CFLAGS) $(LDFLAGS) -DNO_INCLUDE_DIR -I. -o $@ $< libquickjs$(LTOEXT).a $(LIBS) + +run_octane: $(BENCHMARKDIR)/run_octane.c + $(CC) $(CFLAGS) $(LDFLAGS) -DNO_INCLUDE_DIR -I. -o $@ $< libquickjs$(LTOEXT).a $(LIBS) + +benchmarks: run_sunspider_like run_octane + ./run_sunspider_like $(BENCHMARKDIR)/kraken-1.0/ + ./run_sunspider_like $(BENCHMARKDIR)/kraken-1.1/ + ./run_sunspider_like $(BENCHMARKDIR)/sunspider-1.0/ + ./run_octane $(BENCHMARKDIR)/ + +-include $(wildcard $(OBJDIR)/*.d) diff --git a/NativeScript/napi/android/quickjs/source/TODO b/NativeScript/napi/android/quickjs/source/TODO new file mode 100755 index 000000000..e52ccd750 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/TODO @@ -0,0 +1,66 @@ +Misc ideas: +- use custom printf to avoid compatibility issues with floating point numbers +- consistent naming for preprocessor defines +- unify coding style and naming conventions +- use names from the ECMA spec in library implementation +- use byte code emitters with typed arguments (for clarity) +- use 2 bytecode DynBufs in JSFunctionDef, one for reading, one for writing + and use the same wrappers in all phases +- use more generic method for line numbers in resolve_variables and resolve_labels +- use custom timezone support to avoid C library compatibility issues + +Memory: +- use memory pools for objects, etc? +- test border cases for max number of atoms, object properties, string length +- add emergency malloc mode for out of memory exceptions. +- test all DynBuf memory errors +- test all js_realloc memory errors +- improve JS_ComputeMemoryUsage() with more info + +Built-in standard library: +- BSD sockets +- modules: use realpath in module name normalizer and put it in quickjs-libc +- modules: if no ".", use a well known module loading path ? +- get rid of __loadScript, use more common name + +REPL: +- debugger +- readline: support MS Windows terminal +- readline: handle dynamic terminal resizing +- readline: handle double width unicode characters +- multiline editing +- runtime object and function inspectors +- interactive object browser +- use more generic approach to display evaluation results +- improve directive handling: dispatch, colorize, completion... +- save history +- close all predefined methods in repl.js and jscalc.js + +Optimization ideas: +- use 64 bit JSValue in 64 bit mode +- use JSValue as atoms and use a specific constant pool in functions to + reference atoms from the bytecode +- reuse stack slots for disjoint scopes, if strip +- add heuristic to avoid some cycles in closures +- small String (1 codepoint) with immediate storage +- perform static string concatenation at compile time +- add implicit numeric strings for Uint32 numbers? +- optimize `s += a + b`, `s += a.b` and similar simple expressions +- ensure string canonical representation and optimise comparisons and hashes? +- property access optimization on the global object, functions, + prototypes and special non extensible objects. +- create object literals with the correct length by backpatching length argument +- remove redundant set_loc_uninitialized/check_uninitialized opcodes +- peephole optim: push_atom_value, to_propkey -> push_atom_value +- peephole optim: put_loc x, get_loc_check x -> set_loc x +- convert slow array to fast array when all properties != length are numeric +- optimize destructuring assignments for global and local variables +- implement some form of tail-call-optimization +- optimize OP_apply +- optimize f(...b) + +Test262o: 0/11262 errors, 463 excluded +Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) + +Test262: +Result: 66/83147 errors, 1646 excluded, 5538 skipped diff --git a/NativeScript/napi/android/quickjs/source/compat/test-closefrom.c b/NativeScript/napi/android/quickjs/source/compat/test-closefrom.c new file mode 100755 index 000000000..1324e97bf --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/compat/test-closefrom.c @@ -0,0 +1,6 @@ +#include + +int main(void) { + closefrom(3); + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source/cutils.c b/NativeScript/napi/android/quickjs/source/cutils.c new file mode 100755 index 000000000..52ff1649b --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/cutils.c @@ -0,0 +1,641 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include + +#include "cutils.h" + +void pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for(;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +/* strcat and truncate. */ +char *pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +int strstart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (*p != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +int has_suffix(const char *str, const char *suffix) +{ + size_t len = strlen(str); + size_t slen = strlen(suffix); + return (len >= slen && !memcmp(str + len - slen, suffix, slen)); +} + +/* Dynamic buffer package */ + +static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) +{ + memset(s, 0, sizeof(*s)); + if (!realloc_func) + realloc_func = dbuf_default_realloc; + s->opaque = opaque; + s->realloc_func = realloc_func; +} + +void dbuf_init(DynBuf *s) +{ + dbuf_init2(s, NULL, NULL); +} + +/* Try to allocate 'len' more bytes. return < 0 if error */ +int dbuf_claim(DynBuf *s, size_t len) +{ + size_t new_size, size; + uint8_t *new_buf; + new_size = s->size + len; + if (new_size < len) + return -1; /* overflow case */ + if (new_size > s->allocated_size) { + if (s->error) + return -1; + size = s->allocated_size + (s->allocated_size / 2); + if (size < s->allocated_size) + return -1; /* overflow case */ + if (size > new_size) + new_size = size; + new_buf = s->realloc_func(s->opaque, s->buf, new_size); + if (!new_buf) { + s->error = TRUE; + return -1; + } + s->buf = new_buf; + s->allocated_size = new_size; + } + return 0; +} + +int dbuf_put(DynBuf *s, const uint8_t *data, size_t len) +{ + if (unlikely((s->allocated_size - s->size) < len)) { + if (dbuf_claim(s, len)) + return -1; + } + memcpy_no_ub(s->buf + s->size, data, len); + s->size += len; + return 0; +} + +int dbuf_put_self(DynBuf *s, size_t offset, size_t len) +{ + if (unlikely((s->allocated_size - s->size) < len)) { + if (dbuf_claim(s, len)) + return -1; + } + memcpy(s->buf + s->size, s->buf + offset, len); + s->size += len; + return 0; +} + +int __dbuf_putc(DynBuf *s, uint8_t c) +{ + return dbuf_put(s, &c, 1); +} + +int __dbuf_put_u16(DynBuf *s, uint16_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 2); +} + +int __dbuf_put_u32(DynBuf *s, uint32_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 4); +} + +int __dbuf_put_u64(DynBuf *s, uint64_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 8); +} + +int dbuf_putstr(DynBuf *s, const char *str) +{ + return dbuf_put(s, (const uint8_t *)str, strlen(str)); +} + +int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s, + const char *fmt, ...) +{ + va_list ap; + char buf[128]; + int len; + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (len < 0) + return -1; + if (len < sizeof(buf)) { + /* fast case */ + return dbuf_put(s, (uint8_t *)buf, len); + } else { + if (dbuf_claim(s, len + 1)) + return -1; + va_start(ap, fmt); + vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size, + fmt, ap); + va_end(ap); + s->size += len; + } + return 0; +} + +void dbuf_free(DynBuf *s) +{ + /* we test s->buf as a fail safe to avoid crashing if dbuf_free() + is called twice */ + if (s->buf) { + s->realloc_func(s->opaque, s->buf, 0); + } + memset(s, 0, sizeof(*s)); +} + +/* Note: at most 31 bits are encoded. At most UTF8_CHAR_LEN_MAX bytes + are output. */ +int unicode_to_utf8(uint8_t *buf, unsigned int c) +{ + uint8_t *q = buf; + + if (c < 0x80) { + *q++ = c; + } else { + if (c < 0x800) { + *q++ = (c >> 6) | 0xc0; + } else { + if (c < 0x10000) { + *q++ = (c >> 12) | 0xe0; + } else { + if (c < 0x00200000) { + *q++ = (c >> 18) | 0xf0; + } else { + if (c < 0x04000000) { + *q++ = (c >> 24) | 0xf8; + } else if (c < 0x80000000) { + *q++ = (c >> 30) | 0xfc; + *q++ = ((c >> 24) & 0x3f) | 0x80; + } else { + return 0; + } + *q++ = ((c >> 18) & 0x3f) | 0x80; + } + *q++ = ((c >> 12) & 0x3f) | 0x80; + } + *q++ = ((c >> 6) & 0x3f) | 0x80; + } + *q++ = (c & 0x3f) | 0x80; + } + return q - buf; +} + +static const unsigned int utf8_min_code[5] = { + 0x80, 0x800, 0x10000, 0x00200000, 0x04000000, +}; + +static const unsigned char utf8_first_code_mask[5] = { + 0x1f, 0xf, 0x7, 0x3, 0x1, +}; + +/* return -1 if error. *pp is not updated in this case. max_len must + be >= 1. The maximum length for a UTF8 byte sequence is 6 bytes. */ +int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp) +{ + int l, c, b, i; + + c = *p++; + if (c < 0x80) { + *pp = p; + return c; + } + switch(c) { + case 0xc0: case 0xc1: case 0xc2: case 0xc3: + case 0xc4: case 0xc5: case 0xc6: case 0xc7: + case 0xc8: case 0xc9: case 0xca: case 0xcb: + case 0xcc: case 0xcd: case 0xce: case 0xcf: + case 0xd0: case 0xd1: case 0xd2: case 0xd3: + case 0xd4: case 0xd5: case 0xd6: case 0xd7: + case 0xd8: case 0xd9: case 0xda: case 0xdb: + case 0xdc: case 0xdd: case 0xde: case 0xdf: + l = 1; + break; + case 0xe0: case 0xe1: case 0xe2: case 0xe3: + case 0xe4: case 0xe5: case 0xe6: case 0xe7: + case 0xe8: case 0xe9: case 0xea: case 0xeb: + case 0xec: case 0xed: case 0xee: case 0xef: + l = 2; + break; + case 0xf0: case 0xf1: case 0xf2: case 0xf3: + case 0xf4: case 0xf5: case 0xf6: case 0xf7: + l = 3; + break; + case 0xf8: case 0xf9: case 0xfa: case 0xfb: + l = 4; + break; + case 0xfc: case 0xfd: + l = 5; + break; + default: + return -1; + } + /* check that we have enough characters */ + if (l > (max_len - 1)) + return -1; + c &= utf8_first_code_mask[l - 1]; + for(i = 0; i < l; i++) { + b = *p++; + if (b < 0x80 || b >= 0xc0) + return -1; + c = (c << 6) | (b & 0x3f); + } + if (c < utf8_min_code[l - 1]) + return -1; + *pp = p; + return c; +} + +#if 0 + +#if defined(EMSCRIPTEN) || defined(__ANDROID__) + +static void *rqsort_arg; +static int (*rqsort_cmp)(const void *, const void *, void *); + +static int rqsort_cmp2(const void *p1, const void *p2) +{ + return rqsort_cmp(p1, p2, rqsort_arg); +} + +/* not reentrant, but not needed with emscripten */ +void rqsort(void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *, void *), + void *arg) +{ + rqsort_arg = arg; + rqsort_cmp = cmp; + qsort(base, nmemb, size, rqsort_cmp2); +} + +#endif + +#else + +typedef void (*exchange_f)(void *a, void *b, size_t size); +typedef int (*cmp_f)(const void *, const void *, void *opaque); + +static void exchange_bytes(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + + while (size-- != 0) { + uint8_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_byte(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + uint8_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int16s(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + + for (size /= sizeof(uint16_t); size-- != 0;) { + uint16_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int16(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + uint16_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int32s(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + + for (size /= sizeof(uint32_t); size-- != 0;) { + uint32_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int32(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + uint32_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int64s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t); size-- != 0;) { + uint64_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int64(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int128s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t) * 2; size-- != 0; ap += 2, bp += 2) { + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; + } +} + +static void exchange_one_int128(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; +} + +static inline exchange_f exchange_func(const void *base, size_t size) { + switch (((uintptr_t)base | (uintptr_t)size) & 15) { + case 0: + if (size == sizeof(uint64_t) * 2) + return exchange_one_int128; + else + return exchange_int128s; + case 8: + if (size == sizeof(uint64_t)) + return exchange_one_int64; + else + return exchange_int64s; + case 4: + case 12: + if (size == sizeof(uint32_t)) + return exchange_one_int32; + else + return exchange_int32s; + case 2: + case 6: + case 10: + case 14: + if (size == sizeof(uint16_t)) + return exchange_one_int16; + else + return exchange_int16s; + default: + if (size == 1) + return exchange_one_byte; + else + return exchange_bytes; + } +} + +static void heapsortx(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + uint8_t *basep = (uint8_t *)base; + size_t i, n, c, r; + exchange_f swap = exchange_func(base, size); + + if (nmemb > 1) { + i = (nmemb / 2) * size; + n = nmemb * size; + + while (i > 0) { + i -= size; + for (r = i; (c = r * 2 + size) < n; r = c) { + if (c < n - size && cmp(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + for (i = n - size; i > 0; i -= size) { + swap(basep, basep + i, size); + + for (r = 0; (c = r * 2 + size) < i; r = c) { + if (c < i - size && cmp(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + } +} + +static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque) +{ + return cmp(a, b, opaque) < 0 ? + (cmp(b, c, opaque) < 0 ? b : (cmp(a, c, opaque) < 0 ? c : a )) : + (cmp(b, c, opaque) > 0 ? b : (cmp(a, c, opaque) < 0 ? a : c )); +} + +/* pointer based version with local stack and insertion sort threshhold */ +void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack; + uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m; + size_t m4, i, lt, gt, span, span2; + int c, depth; + exchange_f swap = exchange_func(base, size); + exchange_f swap_block = exchange_func(base, size | 128); + + if (nmemb < 2 || size <= 0) + return; + + sp->base = (uint8_t *)base; + sp->count = nmemb; + sp->depth = 0; + sp++; + + while (sp > stack) { + sp--; + ptr = sp->base; + nmemb = sp->count; + depth = sp->depth; + + while (nmemb > 6) { + if (++depth > 50) { + /* depth check to ensure worst case logarithmic time */ + heapsortx(ptr, nmemb, size, cmp, opaque); + nmemb = 0; + break; + } + /* select median of 3 from 1/4, 1/2, 3/4 positions */ + /* should use median of 5 or 9? */ + m4 = (nmemb >> 2) * size; + m = med3(ptr + m4, ptr + 2 * m4, ptr + 3 * m4, cmp, opaque); + swap(ptr, m, size); /* move the pivot to the start or the array */ + i = lt = 1; + pi = plt = ptr + size; + gt = nmemb; + pj = pgt = top = ptr + nmemb * size; + for (;;) { + while (pi < pj && (c = cmp(ptr, pi, opaque)) >= 0) { + if (c == 0) { + swap(plt, pi, size); + lt++; + plt += size; + } + i++; + pi += size; + } + while (pi < (pj -= size) && (c = cmp(ptr, pj, opaque)) <= 0) { + if (c == 0) { + gt--; + pgt -= size; + swap(pgt, pj, size); + } + } + if (pi >= pj) + break; + swap(pi, pj, size); + i++; + pi += size; + } + /* array has 4 parts: + * from 0 to lt excluded: elements identical to pivot + * from lt to pi excluded: elements smaller than pivot + * from pi to gt excluded: elements greater than pivot + * from gt to n excluded: elements identical to pivot + */ + /* move elements identical to pivot in the middle of the array: */ + /* swap values in ranges [0..lt[ and [i-lt..i[ + swapping the smallest span between lt and i-lt is sufficient + */ + span = plt - ptr; + span2 = pi - plt; + lt = i - lt; + if (span > span2) + span = span2; + swap_block(ptr, pi - span, span); + /* swap values in ranges [gt..top[ and [i..top-(top-gt)[ + swapping the smallest span between top-gt and gt-i is sufficient + */ + span = top - pgt; + span2 = pgt - pi; + pgt = top - span2; + gt = nmemb - (gt - i); + if (span > span2) + span = span2; + swap_block(pi, top - span, span); + + /* now array has 3 parts: + * from 0 to lt excluded: elements smaller than pivot + * from lt to gt excluded: elements identical to pivot + * from gt to n excluded: elements greater than pivot + */ + /* stack the larger segment and keep processing the smaller one + to minimize stack use for pathological distributions */ + if (lt > nmemb - gt) { + sp->base = ptr; + sp->count = lt; + sp->depth = depth; + sp++; + ptr = pgt; + nmemb -= gt; + } else { + sp->base = pgt; + sp->count = nmemb - gt; + sp->depth = depth; + sp++; + nmemb = lt; + } + } + /* Use insertion sort for small fragments */ + for (pi = ptr + size, top = ptr + nmemb * size; pi < top; pi += size) { + for (pj = pi; pj > ptr && cmp(pj - size, pj, opaque) > 0; pj -= size) + swap(pj, pj - size, size); + } + } +} + +#endif diff --git a/NativeScript/napi/android/quickjs/source/cutils.h b/NativeScript/napi/android/quickjs/source/cutils.h new file mode 100755 index 000000000..094a8f124 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/cutils.h @@ -0,0 +1,457 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef CUTILS_H +#define CUTILS_H + +#include +#include +#include + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#define force_inline inline __attribute__((always_inline)) +#define no_inline __attribute__((noinline)) +#define __maybe_unused __attribute__((unused)) + +#define xglue(x, y) x ## y +#define glue(x, y) xglue(x, y) +#define stringify(s) tostring(s) +#define tostring(s) #s + +#ifndef offsetof +#define offsetof(type, field) ((size_t) &((type *)0)->field) +#endif +#ifndef countof +#define countof(x) (sizeof(x) / sizeof((x)[0])) +#endif +#ifndef container_of +/* return the pointer of type 'type *' containing 'ptr' as field 'member' */ +#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member))) +#endif + +#if !defined(_MSC_VER) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define minimum_length(n) static n +#else +#define minimum_length(n) n +#endif + +typedef int BOOL; + +#ifndef FALSE +enum { + FALSE = 0, + TRUE = 1, +}; +#endif + +void pstrcpy(char *buf, int buf_size, const char *str); +char *pstrcat(char *buf, int buf_size, const char *s); +int strstart(const char *str, const char *val, const char **ptr); +int has_suffix(const char *str, const char *suffix); + +/* Prevent UB when n == 0 and (src == NULL or dest == NULL) */ +static inline void memcpy_no_ub(void *dest, const void *src, size_t n) { + if (n) + memcpy(dest, src, n); +} + +static inline int max_int(int a, int b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int min_int(int a, int b) +{ + if (a < b) + return a; + else + return b; +} + +static inline uint32_t max_uint32(uint32_t a, uint32_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline uint32_t min_uint32(uint32_t a, uint32_t b) +{ + if (a < b) + return a; + else + return b; +} + +static inline int64_t max_int64(int64_t a, int64_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int64_t min_int64(int64_t a, int64_t b) +{ + if (a < b) + return a; + else + return b; +} + +/* WARNING: undefined if a = 0 */ +static inline int clz32(unsigned int a) +{ + return __builtin_clz(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int clz64(uint64_t a) +{ + return __builtin_clzll(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz32(unsigned int a) +{ + return __builtin_ctz(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz64(uint64_t a) +{ + return __builtin_ctzll(a); +} + +struct __attribute__((packed)) packed_u64 { + uint64_t v; +}; + +struct __attribute__((packed)) packed_u32 { + uint32_t v; +}; + +struct __attribute__((packed)) packed_u16 { + uint16_t v; +}; + +static inline uint64_t get_u64(const uint8_t *tab) +{ + return ((const struct packed_u64 *)tab)->v; +} + +static inline int64_t get_i64(const uint8_t *tab) +{ + return (int64_t)((const struct packed_u64 *)tab)->v; +} + +static inline void put_u64(uint8_t *tab, uint64_t val) +{ + ((struct packed_u64 *)tab)->v = val; +} + +static inline uint32_t get_u32(const uint8_t *tab) +{ + return ((const struct packed_u32 *)tab)->v; +} + +static inline int32_t get_i32(const uint8_t *tab) +{ + return (int32_t)((const struct packed_u32 *)tab)->v; +} + +static inline void put_u32(uint8_t *tab, uint32_t val) +{ + ((struct packed_u32 *)tab)->v = val; +} + +static inline uint32_t get_u16(const uint8_t *tab) +{ + return ((const struct packed_u16 *)tab)->v; +} + +static inline int32_t get_i16(const uint8_t *tab) +{ + return (int16_t)((const struct packed_u16 *)tab)->v; +} + +static inline void put_u16(uint8_t *tab, uint16_t val) +{ + ((struct packed_u16 *)tab)->v = val; +} + +static inline uint32_t get_u8(const uint8_t *tab) +{ + return *tab; +} + +static inline int32_t get_i8(const uint8_t *tab) +{ + return (int8_t)*tab; +} + +static inline void put_u8(uint8_t *tab, uint8_t val) +{ + *tab = val; +} + +#ifndef bswap16 +static inline uint16_t bswap16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} +#endif + +#ifndef bswap32 +static inline uint32_t bswap32(uint32_t v) +{ + return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | + ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); +} +#endif + +#ifndef bswap64 +static inline uint64_t bswap64(uint64_t v) +{ + return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | + ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | + ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | + ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | + ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | + ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | + ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | + ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); +} +#endif + +/* XXX: should take an extra argument to pass slack information to the caller */ +typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size); + +typedef struct DynBuf { + uint8_t *buf; + size_t size; + size_t allocated_size; + BOOL error; /* true if a memory allocation error occurred */ + DynBufReallocFunc *realloc_func; + void *opaque; /* for realloc_func */ +} DynBuf; + +void dbuf_init(DynBuf *s); +void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func); +int dbuf_claim(DynBuf *s, size_t len); +int dbuf_put(DynBuf *s, const uint8_t *data, size_t len); +int dbuf_put_self(DynBuf *s, size_t offset, size_t len); +int dbuf_putstr(DynBuf *s, const char *str); +int __dbuf_putc(DynBuf *s, uint8_t c); +int __dbuf_put_u16(DynBuf *s, uint16_t val); +int __dbuf_put_u32(DynBuf *s, uint32_t val); +int __dbuf_put_u64(DynBuf *s, uint64_t val); + +static inline int dbuf_putc(DynBuf *s, uint8_t val) +{ + if (unlikely((s->allocated_size - s->size) < 1)) { + return __dbuf_putc(s, val); + } else { + s->buf[s->size++] = val; + return 0; + } +} + +static inline int dbuf_put_u16(DynBuf *s, uint16_t val) +{ + if (unlikely((s->allocated_size - s->size) < 2)) { + return __dbuf_put_u16(s, val); + } else { + put_u16(s->buf + s->size, val); + s->size += 2; + return 0; + } +} + +static inline int dbuf_put_u32(DynBuf *s, uint32_t val) +{ + if (unlikely((s->allocated_size - s->size) < 4)) { + return __dbuf_put_u32(s, val); + } else { + put_u32(s->buf + s->size, val); + s->size += 4; + return 0; + } +} + +static inline int dbuf_put_u64(DynBuf *s, uint64_t val) +{ + if (unlikely((s->allocated_size - s->size) < 8)) { + return __dbuf_put_u64(s, val); + } else { + put_u64(s->buf + s->size, val); + s->size += 8; + return 0; + } +} + +int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s, + const char *fmt, ...); +void dbuf_free(DynBuf *s); +static inline BOOL dbuf_error(DynBuf *s) { + return s->error; +} +static inline void dbuf_set_error(DynBuf *s) +{ + s->error = TRUE; +} + +#define UTF8_CHAR_LEN_MAX 6 + +int unicode_to_utf8(uint8_t *buf, unsigned int c); +int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp); + +static inline BOOL is_surrogate(uint32_t c) +{ + return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF +} + +static inline BOOL is_hi_surrogate(uint32_t c) +{ + return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF +} + +static inline BOOL is_lo_surrogate(uint32_t c) +{ + return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF +} + +static inline uint32_t get_hi_surrogate(uint32_t c) +{ + return (c >> 10) - (0x10000 >> 10) + 0xD800; +} + +static inline uint32_t get_lo_surrogate(uint32_t c) +{ + return (c & 0x3FF) | 0xDC00; +} + +static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo) +{ + return 0x10000 + 0x400 * (hi - 0xD800) + (lo - 0xDC00); +} + +static inline int from_hex(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +void rqsort(void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *, void *), + void *arg); + +static inline uint64_t float64_as_uint64(double d) +{ + union { + double d; + uint64_t u64; + } u; + u.d = d; + return u.u64; +} + +static inline double uint64_as_float64(uint64_t u64) +{ + union { + double d; + uint64_t u64; + } u; + u.u64 = u64; + return u.d; +} + +static inline double fromfp16(uint16_t v) +{ + double d; + uint32_t v1; + v1 = v & 0x7fff; + if (unlikely(v1 >= 0x7c00)) + v1 += 0x1f8000; /* NaN or infinity */ + d = uint64_as_float64(((uint64_t)(v >> 15) << 63) | ((uint64_t)v1 << (52 - 10))); + return d * 0x1p1008; +} + +static inline uint16_t tofp16(double d) +{ + uint64_t a, addend; + uint32_t v, sgn; + int shift; + + a = float64_as_uint64(d); + sgn = a >> 63; + a = a & 0x7fffffffffffffff; + if (unlikely(a > 0x7ff0000000000000)) { + /* nan */ + v = 0x7c01; + } else if (a < 0x3f10000000000000) { /* 0x1p-14 */ + /* subnormal f16 number or zero */ + if (a <= 0x3e60000000000000) { /* 0x1p-25 */ + v = 0x0000; /* zero */ + } else { + shift = 1051 - (a >> 52); + a = ((uint64_t)1 << 52) | (a & (((uint64_t)1 << 52) - 1)); + addend = ((a >> shift) & 1) + (((uint64_t)1 << (shift - 1)) - 1); + v = (a + addend) >> shift; + } + } else { + /* normal number or infinity */ + a -= 0x3f00000000000000; /* adjust the exponent */ + /* round */ + addend = ((a >> (52 - 10)) & 1) + (((uint64_t)1 << (52 - 11)) - 1); + v = (a + addend) >> (52 - 10); + /* overflow ? */ + if (unlikely(v > 0x7c00)) + v = 0x7c00; + } + return v | (sgn << 15); +} + +static inline int isfp16nan(uint16_t v) +{ + return (v & 0x7FFF) > 0x7C00; +} + +static inline int isfp16zero(uint16_t v) +{ + return (v & 0x7FFF) == 0; +} + +#endif /* CUTILS_H */ diff --git a/NativeScript/napi/android/quickjs/source/doc/quickjs.texi b/NativeScript/napi/android/quickjs/source/doc/quickjs.texi new file mode 100755 index 000000000..0b8c744ec --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/doc/quickjs.texi @@ -0,0 +1,1073 @@ +\input texinfo + +@iftex +@afourpaper +@headings double +@end iftex + +@include version.texi + +@titlepage +@afourpaper +@sp 7 +@center @titlefont{QuickJS Javascript Engine} +@sp 3 +@center Version: @value{VERSION} +@end titlepage + +@setfilename spec.info +@settitle QuickJS Javascript Engine + +@contents + +@chapter Introduction + +QuickJS (version @value{VERSION}) is a small and embeddable Javascript +engine. It supports most of the ES2024 specification +@footnote{@url{https://tc39.es/ecma262/2024 }} including modules, +asynchronous generators, proxies and BigInt. + +@section Main Features + +@itemize + +@item Small and easily embeddable: just a few C files, no external dependency, 210 KiB of x86 code for a simple ``hello world'' program. + +@item Fast interpreter with very low startup time: runs the 77000 tests of the ECMAScript Test Suite@footnote{@url{https://github.com/tc39/test262}} in less than 2 minutes on a single core of a desktop PC. The complete life cycle of a runtime instance completes in less than 300 microseconds. + +@item Almost complete ES2024 support including modules, asynchronous +generators and full Annex B support (legacy web compatibility). Some +features from the upcoming ES2024 specification +@footnote{@url{https://tc39.es/ecma262/}} are also supported. + +@item Passes nearly 100% of the ECMAScript Test Suite tests when selecting the ES2024 features. + +@item Compile Javascript sources to executables with no external dependency. + +@item Garbage collection using reference counting (to reduce memory usage and have deterministic behavior) with cycle removal. + +@item Command line interpreter with contextual colorization and completion implemented in Javascript. + +@item Small built-in standard library with C library wrappers. + +@end itemize + +@chapter Usage + +@section Installation + +A Makefile is provided to compile the engine on Linux or MacOS/X. A +preliminary Windows support is available thru cross compilation on a +Linux host with the MingGW tools. + +Edit the top of the @code{Makefile} if you wish to select specific +options then run @code{make}. + +You can type @code{make install} as root if you wish to install the binaries and support files to +@code{/usr/local} (this is not necessary to use QuickJS). + +Note: On some OSes atomic operations are not available or need a +specific library. If you get related errors, you should either add +@code{-latomics} in the Makefile @code{LIBS} variable or disable +@code{CONFIG_ATOMICS} in @file{quickjs.c}. + +@section Quick start + +@code{qjs} is the command line interpreter (Read-Eval-Print Loop). You can pass +Javascript files and/or expressions as arguments to execute them: + +@example +./qjs examples/hello.js +@end example + +@code{qjsc} is the command line compiler: + +@example +./qjsc -o hello examples/hello.js +./hello +@end example + +generates a @code{hello} executable with no external dependency. + +@section Command line options + +@subsection @code{qjs} interpreter + +@verbatim +usage: qjs [options] [file [args]] +@end verbatim + +Options are: +@table @code +@item -h +@item --help +List options. + +@item -e @code{EXPR} +@item --eval @code{EXPR} +Evaluate EXPR. + +@item -i +@item --interactive +Go to interactive mode (it is not the default when files are provided on the command line). + +@item -m +@item --module +Load as ES6 module (default=autodetect). A module is autodetected if +the filename extension is @code{.mjs} or if the first keyword of the +source is @code{import}. + +@item --script +Load as ES6 script (default=autodetect). + +@item -I file +@item --include file +Include an additional file. + +@end table + +Advanced options are: + +@table @code +@item --std +Make the @code{std} and @code{os} modules available to the loaded +script even if it is not a module. + +@item -d +@item --dump +Dump the memory usage stats. + +@item -q +@item --quit +just instantiate the interpreter and quit. + +@end table + +@subsection @code{qjsc} compiler + +@verbatim +usage: qjsc [options] [files] +@end verbatim + +Options are: +@table @code +@item -c +Only output bytecode in a C file. The default is to output an executable file. +@item -e +Output @code{main()} and bytecode in a C file. The default is to output an +executable file. +@item -o output +Set the output filename (default = @file{out.c} or @file{a.out}). + +@item -N cname +Set the C name of the generated data. + +@item -m +Compile as Javascript module (default=autodetect). + +@item -D module_name +Compile a dynamically loaded module and its dependencies. This option +is needed when your code uses the @code{import} keyword or the +@code{os.Worker} constructor because the compiler cannot statically +find the name of the dynamically loaded modules. + +@item -M module_name[,cname] +Add initialization code for an external C module. See the +@code{c_module} example. + +@item -x +Byte swapped output (only used for cross compilation). + +@item -flto +Use link time optimization. The compilation is slower but the +executable is smaller and faster. This option is automatically set +when the @code{-fno-x} options are used. + +@item -fno-[eval|string-normalize|regexp|json|proxy|map|typedarray|promise|bigint] +Disable selected language features to produce a smaller executable file. + +@end table + +@section Built-in tests + +Run @code{make test} to run the few built-in tests included in the +QuickJS archive. + +@section Test262 (ECMAScript Test Suite) + +A test262 runner is included in the QuickJS archive. The test262 tests +can be installed in the QuickJS source directory with: + +@example +git clone https://github.com/tc39/test262.git test262 +cd test262 +patch -p1 < ../tests/test262.patch +cd .. +@end example + +The patch adds the implementation specific @code{harness} functions +and optimizes the inefficient RegExp character classes and Unicode +property escapes tests (the tests themselves are not modified, only a +slow string initialization function is optimized). + +The tests can be run with +@example +make test2 +@end example + +The configuration files @code{test262.conf} +(resp. @code{test262o.conf} for the old ES5.1 tests@footnote{The old +ES5.1 tests can be extracted with @code{git clone --single-branch +--branch es5-tests https://github.com/tc39/test262.git test262o}})) +contain the options to run the various tests. Tests can be excluded +based on features or filename. + +The file @code{test262_errors.txt} contains the current list of +errors. The runner displays a message when a new error appears or when +an existing error is corrected or modified. Use the @code{-u} option +to update the current list of errors (or @code{make test2-update}). + +The file @code{test262_report.txt} contains the logs of all the +tests. It is useful to have a clearer analysis of a particular +error. In case of crash, the last line corresponds to the failing +test. + +Use the syntax @code{./run-test262 -c test262.conf -f filename.js} to +run a single test. Use the syntax @code{./run-test262 -c test262.conf +N} to start testing at test number @code{N}. + +For more information, run @code{./run-test262} to see the command line +options of the test262 runner. + +@code{run-test262} accepts the @code{-N} option to be invoked from +@code{test262-harness}@footnote{@url{https://github.com/bterlson/test262-harness}} +thru @code{eshost}. Unless you want to compare QuickJS with other +engines under the same conditions, we do not recommend to run the +tests this way as it is much slower (typically half an hour instead of +about 100 seconds). + +@chapter Specifications + +@section Language support + +@subsection ES2024 support + +The ES2024 specification is almost fully supported including the Annex +B (legacy web compatibility) and the Unicode related features. + +The following features are not supported yet: + +@itemize + +@item Tail calls@footnote{We believe the current specification of tails calls is too complicated and presents limited practical interests.} + +@item Atomics.waitAsync + +@end itemize + +@subsection ECMA402 + +ECMA402 (Internationalization API) is not supported. + +@section Modules + +ES6 modules are fully supported. The default name resolution is the +following: + +@itemize + +@item Module names with a leading @code{.} or @code{..} are relative +to the current module path. + +@item Module names without a leading @code{.} or @code{..} are system +modules, such as @code{std} or @code{os}. + +@item Module names ending with @code{.so} are native modules using the +QuickJS C API. + +@end itemize + +@section Standard library + +The standard library is included by default in the command line +interpreter. It contains the two modules @code{std} and @code{os} and +a few global objects. + +@subsection Global objects + +@table @code +@item scriptArgs +Provides the command line arguments. The first argument is the script name. +@item print(...args) +Print the arguments separated by spaces and a trailing newline. +@item console.log(...args) +Same as print(). + +@end table + +@subsection @code{std} module + +The @code{std} module provides wrappers to the libc @file{stdlib.h} +and @file{stdio.h} and a few other utilities. + +Available exports: + +@table @code + +@item exit(n) +Exit the process. + +@item evalScript(str, options = undefined) +Evaluate the string @code{str} as a script (global +eval). @code{options} is an optional object containing the following +optional properties: + + @table @code + @item backtrace_barrier + Boolean (default = false). If true, error backtraces do not list the + stack frames below the evalScript. + @item async + Boolean (default = false). If true, @code{await} is accepted in the + script and a promise is returned. The promise is resolved with an + object whose @code{value} property holds the value returned by the + script. + @end table + +@item loadScript(filename) +Evaluate the file @code{filename} as a script (global eval). + +@item loadFile(filename) +Load the file @code{filename} and return it as a string assuming UTF-8 +encoding. Return @code{null} in case of I/O error. + +@item open(filename, flags, errorObj = undefined) +Open a file (wrapper to the libc @code{fopen()}). Return the FILE +object or @code{null} in case of I/O error. If @code{errorObj} is not +undefined, set its @code{errno} property to the error code or to 0 if +no error occured. + +@item popen(command, flags, errorObj = undefined) +Open a process by creating a pipe (wrapper to the libc +@code{popen()}). Return the FILE +object or @code{null} in case of I/O error. If @code{errorObj} is not +undefined, set its @code{errno} property to the error code or to 0 if +no error occured. + +@item fdopen(fd, flags, errorObj = undefined) +Open a file from a file handle (wrapper to the libc +@code{fdopen()}). Return the FILE +object or @code{null} in case of I/O error. If @code{errorObj} is not +undefined, set its @code{errno} property to the error code or to 0 if +no error occured. + +@item tmpfile(errorObj = undefined) +Open a temporary file. Return the FILE +object or @code{null} in case of I/O error. If @code{errorObj} is not +undefined, set its @code{errno} property to the error code or to 0 if +no error occured. + +@item puts(str) +Equivalent to @code{std.out.puts(str)}. + +@item printf(fmt, ...args) +Equivalent to @code{std.out.printf(fmt, ...args)}. + +@item sprintf(fmt, ...args) +Equivalent to the libc sprintf(). + +@item in +@item out +@item err +Wrappers to the libc file @code{stdin}, @code{stdout}, @code{stderr}. + +@item SEEK_SET +@item SEEK_CUR +@item SEEK_END +Constants for seek(). + +@item Error + +Enumeration object containing the integer value of common errors +(additional error codes may be defined): + + @table @code + @item EINVAL + @item EIO + @item EACCES + @item EEXIST + @item ENOSPC + @item ENOSYS + @item EBUSY + @item ENOENT + @item EPERM + @item EPIPE + @end table + +@item strerror(errno) +Return a string that describes the error @code{errno}. + +@item gc() +Manually invoke the cycle removal algorithm. The cycle removal +algorithm is automatically started when needed, so this function is +useful in case of specific memory constraints or for testing. + +@item getenv(name) +Return the value of the environment variable @code{name} or +@code{undefined} if it is not defined. + +@item setenv(name, value) +Set the value of the environment variable @code{name} to the string +@code{value}. + +@item unsetenv(name) +Delete the environment variable @code{name}. + +@item getenviron() +Return an object containing the environment variables as key-value pairs. + +@item urlGet(url, options = undefined) + +Download @code{url} using the @file{curl} command line +utility. @code{options} is an optional object containing the following +optional properties: + + @table @code + @item binary + Boolean (default = false). If true, the response is an ArrayBuffer + instead of a string. When a string is returned, the data is assumed + to be UTF-8 encoded. + + @item full + + Boolean (default = false). If true, return the an object contains + the properties @code{response} (response content), + @code{responseHeaders} (headers separated by CRLF), @code{status} + (status code). @code{response} is @code{null} is case of protocol or + network error. If @code{full} is false, only the response is + returned if the status is between 200 and 299. Otherwise @code{null} + is returned. + + @end table + +@item parseExtJSON(str) + + Parse @code{str} using a superset of @code{JSON.parse}. The superset + is very close to the JSON5 specification. The following extensions + are accepted: + + @itemize + @item Single line and multiline comments + @item unquoted properties (ASCII-only Javascript identifiers) + @item trailing comma in array and object definitions + @item single quoted strings + @item @code{\v} escape and multi-line strings with trailing @code{\} + @item @code{\f} and @code{\v} are accepted as space characters + @item leading plus or decimal point in numbers + @item hexadecimal (@code{0x} prefix), octal (@code{0o} prefix) and binary (@code{0b} prefix) integers + @item @code{NaN} and @code{Infinity} are accepted as numbers + @end itemize +@end table + +FILE prototype: + +@table @code +@item close() +Close the file. Return 0 if OK or @code{-errno} in case of I/O error. +@item puts(str) +Outputs the string with the UTF-8 encoding. +@item printf(fmt, ...args) +Formatted printf. + +The same formats as the standard C library @code{printf} are +supported. Integer format types (e.g. @code{%d}) truncate the Numbers +or BigInts to 32 bits. Use the @code{l} modifier (e.g. @code{%ld}) to +truncate to 64 bits. + +@item flush() +Flush the buffered file. +@item seek(offset, whence) +Seek to a give file position (whence is +@code{std.SEEK_*}). @code{offset} can be a number or a bigint. Return +0 if OK or @code{-errno} in case of I/O error. +@item tell() +Return the current file position. +@item tello() +Return the current file position as a bigint. +@item eof() +Return true if end of file. +@item fileno() +Return the associated OS handle. +@item error() +Return true if there was an error. +@item clearerr() +Clear the error indication. + +@item read(buffer, position, length) +Read @code{length} bytes from the file to the ArrayBuffer @code{buffer} at byte +position @code{position} (wrapper to the libc @code{fread}). + +@item write(buffer, position, length) +Write @code{length} bytes to the file from the ArrayBuffer @code{buffer} at byte +position @code{position} (wrapper to the libc @code{fwrite}). + +@item getline() +Return the next line from the file, assuming UTF-8 encoding, excluding +the trailing line feed. + +@item readAsString(max_size = undefined) +Read @code{max_size} bytes from the file and return them as a string +assuming UTF-8 encoding. If @code{max_size} is not present, the file +is read up its end. + +@item getByte() +Return the next byte from the file. Return -1 if the end of file is reached. + +@item putByte(c) +Write one byte to the file. +@end table + +@subsection @code{os} module + +The @code{os} module provides Operating System specific functions: + +@itemize +@item low level file access +@item signals +@item timers +@item asynchronous I/O +@item workers (threads) +@end itemize + +The OS functions usually return 0 if OK or an OS specific negative +error code. + +Available exports: + +@table @code +@item open(filename, flags, mode = 0o666) +Open a file. Return a handle or < 0 if error. + +@item O_RDONLY +@item O_WRONLY +@item O_RDWR +@item O_APPEND +@item O_CREAT +@item O_EXCL +@item O_TRUNC +POSIX open flags. + +@item O_TEXT +(Windows specific). Open the file in text mode. The default is binary mode. + +@item close(fd) +Close the file handle @code{fd}. + +@item seek(fd, offset, whence) +Seek in the file. Use @code{std.SEEK_*} for +@code{whence}. @code{offset} is either a number or a bigint. If +@code{offset} is a bigint, a bigint is returned too. + +@item read(fd, buffer, offset, length) +Read @code{length} bytes from the file handle @code{fd} to the +ArrayBuffer @code{buffer} at byte position @code{offset}. +Return the number of read bytes or < 0 if error. + +@item write(fd, buffer, offset, length) +Write @code{length} bytes to the file handle @code{fd} from the +ArrayBuffer @code{buffer} at byte position @code{offset}. +Return the number of written bytes or < 0 if error. + +@item isatty(fd) +Return @code{true} is @code{fd} is a TTY (terminal) handle. + +@item ttyGetWinSize(fd) +Return the TTY size as @code{[width, height]} or @code{null} if not available. + +@item ttySetRaw(fd) +Set the TTY in raw mode. + +@item remove(filename) +Remove a file. Return 0 if OK or @code{-errno}. + +@item rename(oldname, newname) +Rename a file. Return 0 if OK or @code{-errno}. + +@item realpath(path) +Return @code{[str, err]} where @code{str} is the canonicalized absolute +pathname of @code{path} and @code{err} the error code. + +@item getcwd() +Return @code{[str, err]} where @code{str} is the current working directory +and @code{err} the error code. + +@item chdir(path) +Change the current directory. Return 0 if OK or @code{-errno}. + +@item mkdir(path, mode = 0o777) +Create a directory at @code{path}. Return 0 if OK or @code{-errno}. + +@item stat(path) +@item lstat(path) + +Return @code{[obj, err]} where @code{obj} is an object containing the +file status of @code{path}. @code{err} is the error code. The +following fields are defined in @code{obj}: dev, ino, mode, nlink, +uid, gid, rdev, size, blocks, atime, mtime, ctime. The times are +specified in milliseconds since 1970. @code{lstat()} is the same as +@code{stat()} excepts that it returns information about the link +itself. + +@item S_IFMT +@item S_IFIFO +@item S_IFCHR +@item S_IFDIR +@item S_IFBLK +@item S_IFREG +@item S_IFSOCK +@item S_IFLNK +@item S_ISGID +@item S_ISUID +Constants to interpret the @code{mode} property returned by +@code{stat()}. They have the same value as in the C system header +@file{sys/stat.h}. + +@item utimes(path, atime, mtime) +Change the access and modification times of the file @code{path}. The +times are specified in milliseconds since 1970. Return 0 if OK or @code{-errno}. + +@item symlink(target, linkpath) +Create a link at @code{linkpath} containing the string @code{target}. Return 0 if OK or @code{-errno}. + +@item readlink(path) +Return @code{[str, err]} where @code{str} is the link target and @code{err} +the error code. + +@item readdir(path) +Return @code{[array, err]} where @code{array} is an array of strings +containing the filenames of the directory @code{path}. @code{err} is +the error code. + +@item setReadHandler(fd, func) +Add a read handler to the file handle @code{fd}. @code{func} is called +each time there is data pending for @code{fd}. A single read handler +per file handle is supported. Use @code{func = null} to remove the +handler. + +@item setWriteHandler(fd, func) +Add a write handler to the file handle @code{fd}. @code{func} is +called each time data can be written to @code{fd}. A single write +handler per file handle is supported. Use @code{func = null} to remove +the handler. + +@item signal(signal, func) +Call the function @code{func} when the signal @code{signal} +happens. Only a single handler per signal number is supported. Use +@code{null} to set the default handler or @code{undefined} to ignore +the signal. Signal handlers can only be defined in the main thread. + +@item SIGINT +@item SIGABRT +@item SIGFPE +@item SIGILL +@item SIGSEGV +@item SIGTERM +POSIX signal numbers. + +@item kill(pid, sig) +Send the signal @code{sig} to the process @code{pid}. + +@item exec(args[, options]) +Execute a process with the arguments @code{args}. @code{options} is an +object containing optional parameters: + + @table @code + @item block + Boolean (default = true). If true, wait until the process is + terminated. In this case, @code{exec} return the exit code if positive + or the negated signal number if the process was interrupted by a + signal. If false, do not block and return the process id of the child. + + @item usePath + Boolean (default = true). If true, the file is searched in the + @code{PATH} environment variable. + + @item file + String (default = @code{args[0]}). Set the file to be executed. + + @item cwd + String. If present, set the working directory of the new process. + + @item stdin + @item stdout + @item stderr + If present, set the handle in the child for stdin, stdout or stderr. + + @item env + Object. If present, set the process environment from the object + key-value pairs. Otherwise use the same environment as the current + process. + + @item uid + Integer. If present, the process uid with @code{setuid}. + + @item gid + Integer. If present, the process gid with @code{setgid}. + + @end table + +@item getpid() +Return the current process ID. + +@item waitpid(pid, options) +@code{waitpid} Unix system call. Return the array @code{[ret, +status]}. @code{ret} contains @code{-errno} in case of error. + +@item WNOHANG +Constant for the @code{options} argument of @code{waitpid}. + +@item dup(fd) +@code{dup} Unix system call. + +@item dup2(oldfd, newfd) +@code{dup2} Unix system call. + +@item pipe() +@code{pipe} Unix system call. Return two handles as @code{[read_fd, +write_fd]} or null in case of error. + +@item sleep(delay_ms) +Sleep during @code{delay_ms} milliseconds. + +@item sleepAsync(delay_ms) +Asynchronouse sleep during @code{delay_ms} milliseconds. Returns a promise. Example: +@example +await os.sleepAsync(500); +@end example + +@item now() +Return a timestamp in milliseconds with more precision than +@code{Date.now()}. The time origin is unspecified and is normally not +impacted by system clock adjustments. + +@item setTimeout(func, delay) +Call the function @code{func} after @code{delay} ms. Return a handle +to the timer. + +@item clearTimeout(handle) +Cancel a timer. + +@item platform +Return a string representing the platform: @code{"linux"}, @code{"darwin"}, +@code{"win32"} or @code{"js"}. + +@item Worker(module_filename) +Constructor to create a new thread (worker) with an API close to the +@code{WebWorkers}. @code{module_filename} is a string specifying the +module filename which is executed in the newly created thread. As for +dynamically imported module, it is relative to the current script or +module path. Threads normally don't share any data and communicate +between each other with messages. Nested workers are not supported. An +example is available in @file{tests/test_worker.js}. + +The worker class has the following static properties: + + @table @code + @item parent + In the created worker, @code{Worker.parent} represents the parent + worker and is used to send or receive messages. + @end table + +The worker instances have the following properties: + + @table @code + @item postMessage(msg) + + Send a message to the corresponding worker. @code{msg} is cloned in + the destination worker using an algorithm similar to the @code{HTML} + structured clone algorithm. @code{SharedArrayBuffer} are shared + between workers. + + Current limitations: @code{Map} and @code{Set} are not supported + yet. + + @item onmessage + + Getter and setter. Set a function which is called each time a + message is received. The function is called with a single + argument. It is an object with a @code{data} property containing the + received message. The thread is not terminated if there is at least + one non @code{null} @code{onmessage} handler. + + @end table + +@end table + +@section QuickJS C API + +The C API was designed to be simple and efficient. The C API is +defined in the header @code{quickjs.h}. + +@subsection Runtime and contexts + +@code{JSRuntime} represents a Javascript runtime corresponding to an +object heap. Several runtimes can exist at the same time but they +cannot exchange objects. Inside a given runtime, no multi-threading is +supported. + +@code{JSContext} represents a Javascript context (or Realm). Each +JSContext has its own global objects and system objects. There can be +several JSContexts per JSRuntime and they can share objects, similar +to frames of the same origin sharing Javascript objects in a +web browser. + +@subsection JSValue + +@code{JSValue} represents a Javascript value which can be a primitive +type or an object. Reference counting is used, so it is important to +explicitly duplicate (@code{JS_DupValue()}, increment the reference +count) or free (@code{JS_FreeValue()}, decrement the reference count) +JSValues. + +@subsection C functions + +C functions can be created with +@code{JS_NewCFunction()}. @code{JS_SetPropertyFunctionList()} is a +shortcut to easily add functions, setters and getters properties to a +given object. + +Unlike other embedded Javascript engines, there is no implicit stack, +so C functions get their parameters as normal C parameters. As a +general rule, C functions take constant @code{JSValue}s as parameters +(so they don't need to free them) and return a newly allocated (=live) +@code{JSValue}. + +@subsection Exceptions + +Exceptions: most C functions can return a Javascript exception. It +must be explicitly tested and handled by the C code. The specific +@code{JSValue} @code{JS_EXCEPTION} indicates that an exception +occurred. The actual exception object is stored in the +@code{JSContext} and can be retrieved with @code{JS_GetException()}. + +@subsection Script evaluation + +Use @code{JS_Eval()} to evaluate a script or module source. + +If the script or module was compiled to bytecode with @code{qjsc}, it +can be evaluated by calling @code{js_std_eval_binary()}. The advantage +is that no compilation is needed so it is faster and smaller because +the compiler can be removed from the executable if no @code{eval} is +required. + +Note: the bytecode format is linked to a given QuickJS +version. Moreover, no security check is done before its +execution. Hence the bytecode should not be loaded from untrusted +sources. That's why there is no option to output the bytecode to a +binary file in @code{qjsc}. + +@subsection JS Classes + +C opaque data can be attached to a Javascript object. The type of the +C opaque data is determined with the class ID (@code{JSClassID}) of +the object. Hence the first step is to register a new class ID and JS +class (@code{JS_NewClassID()}, @code{JS_NewClass()}). Then you can +create objects of this class with @code{JS_NewObjectClass()} and get or +set the C opaque point with +@code{JS_GetOpaque()}/@code{JS_SetOpaque()}. + +When defining a new JS class, it is possible to declare a finalizer +which is called when the object is destroyed. The finalizer should be +used to release C resources. It is invalid to execute JS code from +it. A @code{gc_mark} method can be provided so that the cycle removal +algorithm can find the other objects referenced by this object. Other +methods are available to define exotic object behaviors. + +The Class ID are globally allocated (i.e. for all runtimes). The +JSClass are allocated per @code{JSRuntime}. @code{JS_SetClassProto()} +is used to define a prototype for a given class in a given +JSContext. @code{JS_NewObjectClass()} sets this prototype in the +created object. + +Examples are available in @file{quickjs-libc.c}. + +@subsection C Modules + +Native ES6 modules are supported and can be dynamically or statically +linked. Look at the @file{test_bjson} and @file{bjson.so} +examples. The standard library @file{quickjs-libc.c} is also a good example +of a native module. + +@subsection Memory handling + +Use @code{JS_SetMemoryLimit()} to set a global memory allocation limit +to a given JSRuntime. + +Custom memory allocation functions can be provided with +@code{JS_NewRuntime2()}. + +The maximum system stack size can be set with @code{JS_SetMaxStackSize()}. + +@subsection Execution timeout and interrupts + +Use @code{JS_SetInterruptHandler()} to set a callback which is +regularly called by the engine when it is executing code. This +callback can be used to implement an execution timeout. + +It is used by the command line interpreter to implement a +@code{Ctrl-C} handler. + +@chapter Internals + +@section Bytecode + +The compiler generates bytecode directly with no intermediate +representation such as a parse tree, hence it is very fast. Several +optimizations passes are done over the generated bytecode. + +A stack-based bytecode was chosen because it is simple and generates +compact code. + +For each function, the maximum stack size is computed at compile time so that +no runtime stack overflow tests are needed. + +A separate compressed line number table is maintained for the debug +information. + +Access to closure variables is optimized and is almost as fast as local +variables. + +Direct @code{eval} in strict mode is optimized. + +@section Executable generation + +@subsection @code{qjsc} compiler + +The @code{qjsc} compiler generates C sources from Javascript files. By +default the C sources are compiled with the system compiler +(@code{gcc} or @code{clang}). + +The generated C source contains the bytecode of the compiled functions +or modules. If a full complete executable is needed, it also +contains a @code{main()} function with the necessary C code to initialize the +Javascript engine and to load and execute the compiled functions and +modules. + +Javascript code can be mixed with C modules. + +In order to have smaller executables, specific Javascript features can +be disabled, in particular @code{eval} or the regular expressions. The +code removal relies on the Link Time Optimization of the system +compiler. + +@subsection Binary JSON + +@code{qjsc} works by compiling scripts or modules and then serializing +them to a binary format. A subset of this format (without functions or +modules) can be used as binary JSON. The example @file{test_bjson.js} +shows how to use it. + +Warning: the binary JSON format may change without notice, so it +should not be used to store persistent data. The @file{test_bjson.js} +example is only used to test the binary object format functions. + +@section Runtime + +@subsection Strings + +Strings are stored either as an 8 bit or a 16 bit array of +characters. Hence random access to characters is always fast. + +The C API provides functions to convert Javascript Strings to C UTF-8 encoded +strings. The most common case where the Javascript string contains +only ASCII characters involves no copying. + +@subsection Objects + +The object shapes (object prototype, property names and flags) are shared +between objects to save memory. + +Arrays with no holes (except at the end of the array) are optimized. + +TypedArray accesses are optimized. + +@subsection Atoms + +Object property names and some strings are stored as Atoms (unique +strings) to save memory and allow fast comparison. Atoms are +represented as a 32 bit integer. Half of the atom range is reserved for +immediate integer literals from @math{0} to @math{2^{31}-1}. + +@subsection Numbers + +Numbers are represented either as 32-bit signed integers or 64-bit IEEE-754 +floating point values. Most operations have fast paths for the 32-bit +integer case. + +@subsection Garbage collection + +Reference counting is used to free objects automatically and +deterministically. A separate cycle removal pass is done when the allocated +memory becomes too large. The cycle removal algorithm only uses the +reference counts and the object content, so no explicit garbage +collection roots need to be manipulated in the C code. + +@subsection JSValue + +It is a Javascript value which can be a primitive type (such as +Number, String, ...) or an Object. NaN boxing is used in the 32-bit version +to store 64-bit floating point numbers. The representation is +optimized so that 32-bit integers and reference counted values can be +efficiently tested. + +In 64-bit code, JSValue are 128-bit large and no NaN boxing is used. The +rationale is that in 64-bit code memory usage is less critical. + +In both cases (32 or 64 bits), JSValue exactly fits two CPU registers, +so it can be efficiently returned by C functions. + +@subsection Function call + +The engine is optimized so that function calls are fast. The system +stack holds the Javascript parameters and local variables. + +@section RegExp + +A specific regular expression engine was developed. It is both small +and efficient and supports all the ES2024 features including the +Unicode properties. As the Javascript compiler, it directly generates +bytecode without a parse tree. + +Backtracking with an explicit stack is used so that there is no +recursion on the system stack. Simple quantifiers are specifically +optimized to avoid recursions. + +The full regexp library weights about 15 KiB (x86 code), excluding the +Unicode library. + +@section Unicode + +A specific Unicode library was developed so that there is no +dependency on an external large Unicode library such as ICU. All the +Unicode tables are compressed while keeping a reasonable access +speed. + +The library supports case conversion, Unicode normalization, Unicode +script queries, Unicode general category queries and all Unicode +binary properties. + +The full Unicode library weights about 45 KiB (x86 code). + +@section BigInt + +BigInts are represented using binary two's complement notation. An +additional short bigint value is used to optimize the performance on +small BigInt values. + +@chapter License + +QuickJS is released under the MIT license. + +Unless otherwise specified, the QuickJS sources are copyright Fabrice +Bellard and Charlie Gordon. + +@bye diff --git a/NativeScript/napi/android/quickjs/source/dtoa.c b/NativeScript/napi/android/quickjs/source/dtoa.c new file mode 100755 index 000000000..ac1be8d8e --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/dtoa.c @@ -0,0 +1,1620 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "dtoa.h" + +/* + TODO: + - test n_digits=101 instead of 100 + - simplify subnormal handling + - reduce max memory usage + - free format: could add shortcut if exact result + - use 64 bit limb_t when possible + - use another algorithm for free format dtoa in base 10 (ryu ?) +*/ + +#define USE_POW5_TABLE +/* use fast path to print small integers in free format */ +#define USE_FAST_INT + +#define LIMB_LOG2_BITS 5 + +#define LIMB_BITS (1 << LIMB_LOG2_BITS) + +typedef int32_t slimb_t; +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; + +#define LIMB_DIGITS 9 + +#define JS_RADIX_MAX 36 + +#define DBIGNUM_LEN_MAX 52 /* ~ 2^(1072+53)*36^100 (dtoa) */ +#define MANT_LEN_MAX 18 /* < 36^100 */ + +typedef intptr_t mp_size_t; + +/* the represented number is sum(i, tab[i]*2^(LIMB_BITS * i)) */ +typedef struct { + int len; /* >= 1 */ + limb_t tab[]; +} mpb_t; + +static limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n) +{ + size_t i; + limb_t k, a; + + k=b; + for(i=0;i> LIMB_BITS; + } + return l; +} + +/* WARNING: d must be >= 2^(LIMB_BITS-1) */ +static inline limb_t udiv1norm_init(limb_t d) +{ + limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((dlimb_t)a1 << LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0, + limb_t d, limb_t d_inv) +{ + limb_t n1m, n_adj, q, r, ah; + dlimb_t a; + n1m = ((slimb_t)a0 >> (LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is between + 0 and d - 1 */ + a = ((dlimb_t)a1 << LIMB_BITS) | a0; + a = a - (dlimb_t)q * d - d; + ah = a >> LIMB_BITS; + q += 1 + ah; + r = (limb_t)a + (ah & d); + *pr = r; + return q; +} + +static limb_t mp_div1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r) +{ + slimb_t i; + dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((dlimb_t)r << LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + return r; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t high) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + tab_r[i] = (a >> shift) | (l << (LIMB_BITS - shift)); + l = a; + } + return l & (((limb_t)1 << shift) - 1); +} + +/* r = (a << shift) + low. 1 <= shift <= LIMB_BITS - 1, 0 <= low < + 2^shift. */ +static limb_t mp_shl(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t low) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = low; + for(i = 0; i < n; i++) { + a = tab[i]; + tab_r[i] = (a << shift) | l; + l = (a >> (LIMB_BITS - shift)); + } + return l; +} + +static no_inline limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r, limb_t b_inv, int shift) +{ + slimb_t i; + + if (shift != 0) { + r = (r << shift) | mp_shl(tabr, taba, n, shift, 0); + } + for(i = n - 1; i >= 0; i--) { + tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); + } + r >>= shift; + return r; +} + +static __maybe_unused void mpb_dump(const char *str, const mpb_t *a) +{ + int i; + + printf("%s= 0x", str); + for(i = a->len - 1; i >= 0; i--) { + printf("%08x", a->tab[i]); + if (i != 0) + printf("_"); + } + printf("\n"); +} + +static void mpb_renorm(mpb_t *r) +{ + while (r->len > 1 && r->tab[r->len - 1] == 0) + r->len--; +} + +#ifdef USE_POW5_TABLE +static const uint32_t pow5_table[17] = { + 0x00000005, 0x00000019, 0x0000007d, 0x00000271, + 0x00000c35, 0x00003d09, 0x0001312d, 0x0005f5e1, + 0x001dcd65, 0x009502f9, 0x02e90edd, 0x0e8d4a51, + 0x48c27395, 0x6bcc41e9, 0x1afd498d, 0x86f26fc1, + 0xa2bc2ec5, +}; + +static const uint8_t pow5h_table[4] = { + 0x00000001, 0x00000007, 0x00000023, 0x000000b1, +}; + +static const uint32_t pow5_inv_table[13] = { + 0x99999999, 0x47ae147a, 0x0624dd2f, 0xa36e2eb1, + 0x4f8b588e, 0x0c6f7a0b, 0xad7f29ab, 0x5798ee23, + 0x12e0be82, 0xb7cdfd9d, 0x5fd7fe17, 0x19799812, + 0xc25c2684, +}; +#endif + +/* return a^b */ +static uint64_t pow_ui(uint32_t a, uint32_t b) +{ + int i, n_bits; + uint64_t r; + if (b == 0) + return 1; + if (b == 1) + return a; +#ifdef USE_POW5_TABLE + if ((a == 5 || a == 10) && b <= 17) { + r = pow5_table[b - 1]; + if (b >= 14) { + r |= (uint64_t)pow5h_table[b - 14] << 32; + } + if (a == 10) + r <<= b; + return r; + } +#endif + r = a; + n_bits = 32 - clz32(b); + for(i = n_bits - 2; i >= 0; i--) { + r *= r; + if ((b >> i) & 1) + r *= a; + } + return r; +} + +static uint32_t pow_ui_inv(uint32_t *pr_inv, int *pshift, uint32_t a, uint32_t b) +{ + uint32_t r_inv, r; + int shift; +#ifdef USE_POW5_TABLE + if (a == 5 && b >= 1 && b <= 13) { + r = pow5_table[b - 1]; + shift = clz32(r); + r <<= shift; + r_inv = pow5_inv_table[b - 1]; + } else +#endif + { + r = pow_ui(a, b); + shift = clz32(r); + r <<= shift; + r_inv = udiv1norm_init(r); + } + *pshift = shift; + *pr_inv = r_inv; + return r; +} + +enum { + JS_RNDN, /* round to nearest, ties to even */ + JS_RNDNA, /* round to nearest, ties away from zero */ + JS_RNDZ, +}; + +static int mpb_get_bit(const mpb_t *r, int k) +{ + int l; + + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + if (l >= r->len) + return 0; + else + return (r->tab[l] >> k) & 1; +} + +/* compute round(r / 2^shift). 'shift' can be negative */ +static void mpb_shr_round(mpb_t *r, int shift, int rnd_mode) +{ + int l, i; + + if (shift == 0) + return; + if (shift < 0) { + shift = -shift; + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (shift != 0) { + r->tab[r->len] = mp_shl(r->tab, r->tab, r->len, shift, 0); + r->len++; + mpb_renorm(r); + } + if (l > 0) { + for(i = r->len - 1; i >= 0; i--) + r->tab[i + l] = r->tab[i]; + for(i = 0; i < l; i++) + r->tab[i] = 0; + r->len += l; + } + } else { + limb_t bit1, bit2; + int k, add_one; + + switch(rnd_mode) { + default: + case JS_RNDZ: + add_one = 0; + break; + case JS_RNDN: + case JS_RNDNA: + bit1 = mpb_get_bit(r, shift - 1); + if (bit1) { + if (rnd_mode == JS_RNDNA) { + bit2 = 1; + } else { + /* bit2 = oring of all the bits after bit1 */ + bit2 = 0; + if (shift >= 2) { + k = shift - 1; + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + for(i = 0; i < min_int(l, r->len); i++) + bit2 |= r->tab[i]; + if (l < r->len) + bit2 |= r->tab[l] & (((limb_t)1 << k) - 1); + } + } + if (bit2) { + add_one = 1; + } else { + /* round to even */ + add_one = mpb_get_bit(r, shift); + } + } else { + add_one = 0; + } + break; + } + + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (l >= r->len) { + r->len = 1; + r->tab[0] = add_one; + } else { + if (l > 0) { + r->len -= l; + for(i = 0; i < r->len; i++) + r->tab[i] = r->tab[i + l]; + } + if (shift != 0) { + mp_shr(r->tab, r->tab, r->len, shift, 0); + mpb_renorm(r); + } + if (add_one) { + limb_t a; + a = mp_add_ui(r->tab, 1, r->len); + if (a) + r->tab[r->len++] = a; + } + } + } +} + +/* return -1, 0 or 1 */ +static int mpb_cmp(const mpb_t *a, const mpb_t *b) +{ + mp_size_t i; + if (a->len < b->len) + return -1; + else if (a->len > b->len) + return 1; + for(i = a->len - 1; i >= 0; i--) { + if (a->tab[i] != b->tab[i]) { + if (a->tab[i] < b->tab[i]) + return -1; + else + return 1; + } + } + return 0; +} + +static void mpb_set_u64(mpb_t *r, uint64_t m) +{ +#if LIMB_BITS == 64 + r->tab[0] = m; + r->len = 1; +#else + r->tab[0] = m; + r->tab[1] = m >> LIMB_BITS; + if (r->tab[1] == 0) + r->len = 1; + else + r->len = 2; +#endif +} + +static uint64_t mpb_get_u64(mpb_t *r) +{ +#if LIMB_BITS == 64 + return r->tab[0]; +#else + if (r->len == 1) { + return r->tab[0]; + } else { + return r->tab[0] | ((uint64_t)r->tab[1] << LIMB_BITS); + } +#endif +} + +/* floor_log2() = position of the first non zero bit or -1 if zero. */ +static int mpb_floor_log2(mpb_t *a) +{ + limb_t v; + v = a->tab[a->len - 1]; + if (v == 0) + return -1; + else + return a->len * LIMB_BITS - 1 - clz32(v); +} + +#define MUL_LOG2_RADIX_BASE_LOG2 24 + +/* round((1 << MUL_LOG2_RADIX_BASE_LOG2)/log2(i + 2)) */ +static const uint32_t mul_log2_radix_table[JS_RADIX_MAX - 1] = { + 0x000000, 0xa1849d, 0x000000, 0x6e40d2, + 0x6308c9, 0x5b3065, 0x000000, 0x50c24e, + 0x4d104d, 0x4a0027, 0x4768ce, 0x452e54, + 0x433d00, 0x418677, 0x000000, 0x3ea16b, + 0x3d645a, 0x3c43c2, 0x3b3b9a, 0x3a4899, + 0x39680b, 0x3897b3, 0x37d5af, 0x372069, + 0x367686, 0x35d6df, 0x354072, 0x34b261, + 0x342bea, 0x33ac62, 0x000000, 0x32bfd9, + 0x3251dd, 0x31e8d6, 0x318465, +}; + +/* return floor(a / log2(radix)) for -2048 <= a <= 2047 */ +static int mul_log2_radix(int a, int radix) +{ + int radix_bits, mult; + + if ((radix & (radix - 1)) == 0) { + /* if the radix is a power of two better to do it exactly */ + radix_bits = 31 - clz32(radix); + if (a < 0) + a -= radix_bits - 1; + return a / radix_bits; + } else { + mult = mul_log2_radix_table[radix - 2]; + return ((int64_t)a * mult) >> MUL_LOG2_RADIX_BASE_LOG2; + } +} + +#if 0 +static void build_mul_log2_radix_table(void) +{ + int base, radix, mult, col, base_log2; + + base_log2 = 24; + base = 1 << base_log2; + col = 0; + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) + mult = 0; + else + mult = lrint((double)base / log2(radix)); + printf("0x%06x, ", mult); + if (++col == 4) { + printf("\n"); + col = 0; + } + } + printf("\n"); +} + +static void mul_log2_radix_test(void) +{ + int radix, i, ref, r; + + for(radix = 2; radix <= 36; radix++) { + for(i = -2048; i <= 2047; i++) { + ref = (int)floor((double)i / log2(radix)); + r = mul_log2_radix(i, radix); + if (ref != r) { + printf("ERROR: radix=%d i=%d r=%d ref=%d\n", + radix, i, r, ref); + exit(1); + } + } + } + if (0) + build_mul_log2_radix_table(); +} +#endif + +static void u32toa_len(char *buf, uint32_t n, size_t len) +{ + int digit, i; + for(i = len - 1; i >= 0; i--) { + digit = n % 10; + n = n / 10; + buf[i] = digit + '0'; + } +} + +/* for power of 2 radixes. len >= 1 */ +static void u64toa_bin_len(char *buf, uint64_t n, unsigned int radix_bits, int len) +{ + int digit, i; + unsigned int mask; + + mask = (1 << radix_bits) - 1; + for(i = len - 1; i >= 0; i--) { + digit = n & mask; + n >>= radix_bits; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } +} + +/* len >= 1. 2 <= radix <= 36 */ +static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ +#if LIMB_BITS == 32 + u32toa_len(buf, n, len); +#else + /* XXX: optimize */ + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % 10; + n = (limb_t)n / 10; + buf[i] = digit + '0'; + } +#endif + } else { + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % radix; + n = (limb_t)n / radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } + } +} + +size_t u32toa(char *buf, uint32_t n) +{ + char buf1[10], *q; + size_t len; + + q = buf1 + sizeof(buf1); + do { + *--q = n % 10 + '0'; + n /= 10; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; +} + +size_t i32toa(char *buf, int32_t n) +{ + if (n >= 0) { + return u32toa(buf, n); + } else { + buf[0] = '-'; + return u32toa(buf + 1, -(uint32_t)n) + 1; + } +} + +#ifdef USE_FAST_INT +size_t u64toa(char *buf, uint64_t n) +{ + if (n < 0x100000000) { + return u32toa(buf, n); + } else { + uint64_t n1; + char *q = buf; + uint32_t n2; + + n1 = n / 1000000000; + n %= 1000000000; + if (n1 >= 0x100000000) { + n2 = n1 / 1000000000; + n1 = n1 % 1000000000; + /* at most two digits */ + if (n2 >= 10) { + *q++ = n2 / 10 + '0'; + n2 %= 10; + } + *q++ = n2 + '0'; + u32toa_len(q, n1, 9); + q += 9; + } else { + q += u32toa(q, n1); + } + u32toa_len(q, n, 9); + q += 9; + return q - buf; + } +} + +size_t i64toa(char *buf, int64_t n) +{ + if (n >= 0) { + return u64toa(buf, n); + } else { + buf[0] = '-'; + return u64toa(buf + 1, -(uint64_t)n) + 1; + } +} + +/* XXX: only tested for 1 <= n < 2^53 */ +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix) +{ + int radix_bits, l; + if (likely(radix == 10)) + return u64toa(buf, n); + if ((radix & (radix - 1)) == 0) { + radix_bits = 31 - clz32(radix); + if (n == 0) + l = 1; + else + l = (64 - clz64(n) + radix_bits - 1) / radix_bits; + u64toa_bin_len(buf, n, radix_bits, l); + return l; + } else { + char buf1[41], *q; /* maximum length for radix = 3 */ + size_t len; + int digit; + q = buf1 + sizeof(buf1); + do { + digit = n % radix; + n /= radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + *--q = digit; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; + } +} + +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix) +{ + if (n >= 0) { + return u64toa_radix(buf, n, radix); + } else { + buf[0] = '-'; + return u64toa_radix(buf + 1, -(uint64_t)n, radix) + 1; + } +} +#endif /* USE_FAST_INT */ + +static const uint8_t digits_per_limb_table[JS_RADIX_MAX - 1] = { +#if LIMB_BITS == 32 +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +#else +64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, +#endif +}; + +static const uint32_t radix_base_table[JS_RADIX_MAX - 1] = { + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +}; + +/* XXX: remove the table ? */ +static uint8_t dtoa_max_digits_table[JS_RADIX_MAX - 1] = { + 54, 35, 28, 24, 22, 20, 19, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12, +}; + +/* we limit the maximum number of significant digits for atod to about + 128 bits of precision for non power of two bases. The only + requirement for Javascript is at least 20 digits in base 10. For + power of two bases, we do an exact rounding in all the cases. */ +static uint8_t atod_max_digits_table[JS_RADIX_MAX - 1] = { + 64, 80, 32, 55, 49, 45, 21, 40, 38, 37, 35, 34, 33, 32, 16, 31, 30, 30, 29, 29, 28, 28, 27, 27, 27, 26, 26, 26, 26, 25, 12, 25, 25, 24, 24, +}; + +/* if abs(d) >= B^max_exponent, it is an overflow */ +static const int16_t max_exponent[JS_RADIX_MAX - 1] = { + 1024, 647, 512, 442, 397, 365, 342, 324, + 309, 297, 286, 277, 269, 263, 256, 251, + 246, 242, 237, 234, 230, 227, 224, 221, + 218, 216, 214, 211, 209, 207, 205, 203, + 202, 200, 199, +}; + +/* if abs(d) <= B^min_exponent, it is an underflow */ +static const int16_t min_exponent[JS_RADIX_MAX - 1] = { +-1075, -679, -538, -463, -416, -383, -359, -340, + -324, -311, -300, -291, -283, -276, -269, -263, + -258, -254, -249, -245, -242, -238, -235, -232, + -229, -227, -224, -222, -220, -217, -215, -214, + -212, -210, -208, +}; + +#if 0 +void build_tables(void) +{ + int r, j, radix, n, col, i; + + /* radix_base_table */ + for(radix = 2; radix <= 36; radix++) { + r = 1; + for(j = 0; j < digits_per_limb_table[radix - 2]; j++) { + r *= radix; + } + printf(" 0x%08x,", r); + if ((radix % 4) == 1) + printf("\n"); + } + printf("\n"); + + /* dtoa_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + /* Note: over estimated when the radix is a power of two */ + printf(" %d,", 1 + (int)ceil(53.0 / log2(radix))); + } + printf("\n"); + + /* atod_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) { + /* 64 bits is more than enough */ + n = (int)floor(64.0 / log2(radix)); + } else { + n = (int)floor(128.0 / log2(radix)); + } + printf(" %d,", n); + } + printf("\n"); + + printf("static const int16_t max_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)ceil(1024 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const int16_t min_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)floor(-1075 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const uint32_t pow5_table[16] = {\n"); + col = 0; + for(i = 2; i <= 17; i++) { + r = 1; + for(j = 0; j < i; j++) { + r *= 5; + } + printf("0x%08x, ", r); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + /* high part */ + printf("static const uint8_t pow5h_table[4] = {\n"); + col = 0; + for(i = 14; i <= 17; i++) { + uint64_t r1; + r1 = 1; + for(j = 0; j < i; j++) { + r1 *= 5; + } + printf("0x%08x, ", (uint32_t)(r1 >> 32)); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); +} +#endif + +/* n_digits >= 1. 0 <= dot_pos <= n_digits. If dot_pos == n_digits, + the dot is not displayed. 'a' is modified. */ +static int output_digits(char *buf, + mpb_t *a, int radix, int n_digits1, + int dot_pos) +{ + int n_digits, digits_per_limb, radix_bits, n, len; + + n_digits = n_digits1; + if ((radix & (radix - 1)) == 0) { + /* radix = 2^radix_bits */ + radix_bits = 31 - clz32(radix); + } else { + radix_bits = 0; + } + digits_per_limb = digits_per_limb_table[radix - 2]; + if (radix_bits != 0) { + for(;;) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + u64toa_bin_len(buf + n_digits, a->tab[0], radix_bits, n); + if (n_digits == 0) + break; + mpb_shr_round(a, digits_per_limb * radix_bits, JS_RNDZ); + } + } else { + limb_t r; + while (n_digits != 0) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + r = mp_div1(a->tab, a->tab, a->len, radix_base_table[radix - 2], 0); + mpb_renorm(a); + limb_to_a(buf + n_digits, r, radix, n); + } + } + + /* add the dot */ + len = n_digits1; + if (dot_pos != n_digits1) { + memmove(buf + dot_pos + 1, buf + dot_pos, n_digits1 - dot_pos); + buf[dot_pos] = '.'; + len++; + } + return len; +} + +/* return (a, e_offset) such that a = a * (radix1*2^radix_shift)^f * + 2^-e_offset. 'f' can be negative. */ +static int mul_pow(mpb_t *a, int radix1, int radix_shift, int f, BOOL is_int, int e) +{ + int e_offset, d, n, n0; + + e_offset = -f * radix_shift; + if (radix1 != 1) { + d = digits_per_limb_table[radix1 - 2]; + if (f >= 0) { + limb_t h, b; + + b = 0; + n0 = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui(radix1, n); + n0 = n; + } + h = mp_mul1(a->tab, a->tab, a->len, b, 0); + if (h != 0) { + a->tab[a->len++] = h; + } + f -= n; + } + } else { + int extra_bits, l, shift; + limb_t r, rem, b, b_inv; + + f = -f; + l = (f + d - 1) / d; /* high bound for the number of limbs (XXX: make it better) */ + e_offset += l * LIMB_BITS; + if (!is_int) { + /* at least 'e' bits are needed in the final result for rounding */ + extra_bits = max_int(e - mpb_floor_log2(a), 0); + } else { + /* at least two extra bits are needed in the final result + for rounding */ + extra_bits = max_int(2 + e - e_offset, 0); + } + e_offset += extra_bits; + mpb_shr_round(a, -(l * LIMB_BITS + extra_bits), JS_RNDZ); + + b = 0; + b_inv = 0; + shift = 0; + n0 = 0; + rem = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui_inv(&b_inv, &shift, radix1, n); + n0 = n; + } + r = mp_div1norm(a->tab, a->tab, a->len, b, 0, b_inv, shift); + rem |= r; + mpb_renorm(a); + f -= n; + } + /* if the remainder is non zero, use it for rounding */ + a->tab[0] |= (rem != 0); + } + } + return e_offset; +} + +/* tmp1 = round(m*2^e*radix^f). 'tmp0' is a temporary storage */ +static void mul_pow_round(mpb_t *tmp1, uint64_t m, int e, int radix1, int radix_shift, int f, + int rnd_mode) +{ + int e_offset; + + mpb_set_u64(tmp1, m); + e_offset = mul_pow(tmp1, radix1, radix_shift, f, TRUE, e); + mpb_shr_round(tmp1, -e + e_offset, rnd_mode); +} + +/* return round(a*2^e_offset) rounded as a float64. 'a' is modified */ +static uint64_t round_to_d(int *pe, mpb_t *a, int e_offset, int rnd_mode) +{ + int e; + uint64_t m; + + if (a->tab[0] == 0 && a->len == 1) { + /* zero result */ + m = 0; + e = 0; /* don't care */ + } else { + int prec, prec1, e_min; + e = mpb_floor_log2(a) + 1 - e_offset; + prec1 = 53; + e_min = -1021; + if (e < e_min) { + /* subnormal result or zero */ + prec = prec1 - (e_min - e); + } else { + prec = prec1; + } + mpb_shr_round(a, e + e_offset - prec, rnd_mode); + m = mpb_get_u64(a); + m <<= (53 - prec); + /* mantissa overflow due to rounding */ + if (m >= (uint64_t)1 << 53) { + m >>= 1; + e++; + } + } + *pe = e; + return m; +} + +/* return (m, e) such that m*2^(e-53) = round(a * radix^f) with 2^52 + <= m < 2^53 or m = 0. + 'a' is modified. */ +static uint64_t mul_pow_round_to_d(int *pe, mpb_t *a, + int radix1, int radix_shift, int f, int rnd_mode) +{ + int e_offset; + + e_offset = mul_pow(a, radix1, radix_shift, f, FALSE, 55); + return round_to_d(pe, a, e_offset, rnd_mode); +} + +#ifdef JS_DTOA_DUMP_STATS +static int out_len_count[17]; + +void js_dtoa_dump_stats(void) +{ + int i, sum; + sum = 0; + for(i = 0; i < 17; i++) + sum += out_len_count[i]; + for(i = 0; i < 17; i++) { + printf("%2d %8d %5.2f%%\n", + i + 1, out_len_count[i], (double)out_len_count[i] / sum * 100); + } +} +#endif + +/* return a maximum bound of the string length. The bound depends on + 'd' only if format = JS_DTOA_FORMAT_FRAC or if JS_DTOA_EXP_DISABLED + is enabled. */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags) +{ + int fmt = flags & JS_DTOA_FORMAT_MASK; + int n, e; + uint64_t a; + + if (fmt != JS_DTOA_FORMAT_FRAC) { + if (fmt == JS_DTOA_FORMAT_FREE) { + n = dtoa_max_digits_table[radix - 2]; + } else { + n = n_digits; + } + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_DISABLED) { + /* no exponential */ + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + e -= 1023; + /* XXX: adjust */ + n += 10 + abs(mul_log2_radix(e - 1, radix)); + } + } else { + /* extra: sign, 1 dot and exponent "e-1000" */ + n += 1 + 1 + 6; + } + } else { + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + /* high bound for the integer part */ + e -= 1023; + /* x < 2^(e + 1) */ + if (e < 0) { + n = 1; + } else { + n = 2 + mul_log2_radix(e - 1, radix); + } + /* sign, extra digit, 1 dot */ + n += 1 + 1 + 1 + n_digits; + } + } + return max_int(n, 9); /* also include NaN and [-]Infinity */ +} + +#if defined(__SANITIZE_ADDRESS__) && 0 +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + return malloc(size); +} +static void dtoa_free(void *ptr) +{ + free(ptr); +} +#else +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + void *ret; + ret = *pptr; + *pptr += (size + 7) / 8; + return ret; +} + +static void dtoa_free(void *ptr) +{ +} +#endif + +/* return the length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem) +{ + uint64_t a, m, *mptr = tmp_mem->mem; + int e, sgn, l, E, P, i, E_max, radix1, radix_shift; + char *q; + mpb_t *tmp1, *mant_max; + int fmt = flags & JS_DTOA_FORMAT_MASK; + + tmp1 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + mant_max = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * MANT_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSDTOATempMem) / sizeof(mptr[0])); + + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + a = float64_as_uint64(d); + sgn = a >> 63; + e = (a >> 52) & 0x7ff; + m = a & (((uint64_t)1 << 52) - 1); + q = buf; + if (e == 0x7ff) { + if (m == 0) { + if (sgn) + *q++ = '-'; + memcpy(q, "Infinity", 8); + q += 8; + } else { + memcpy(q, "NaN", 3); + q += 3; + } + goto done; + } else if (e == 0) { + if (m == 0) { + tmp1->len = 1; + tmp1->tab[0] = 0; + E = 1; + if (fmt == JS_DTOA_FORMAT_FREE) + P = 1; + else if (fmt == JS_DTOA_FORMAT_FRAC) + P = n_digits + 1; + else + P = n_digits; + /* "-0" is displayed as "0" if JS_DTOA_MINUS_ZERO is not present */ + if (sgn && (flags & JS_DTOA_MINUS_ZERO)) + *q++ = '-'; + goto output; + } + /* denormal number: convert to a normal number */ + l = clz64(m) - 11; + e -= l - 1; + m <<= l; + } else { + m |= (uint64_t)1 << 52; + } + if (sgn) + *q++ = '-'; + /* remove the bias */ + e -= 1022; + /* d = 2^(e-53)*m */ + // printf("m=0x%016" PRIx64 " e=%d\n", m, e); +#ifdef USE_FAST_INT + if (fmt == JS_DTOA_FORMAT_FREE && + e >= 1 && e <= 53 && + (m & (((uint64_t)1 << (53 - e)) - 1)) == 0 && + (flags & JS_DTOA_EXP_MASK) != JS_DTOA_EXP_ENABLED) { + m >>= 53 - e; + /* 'm' is never zero */ + q += u64toa_radix(q, m, radix); + goto done; + } +#endif + + /* this choice of E implies F=round(x*B^(P-E) is such as: + B^(P-1) <= F < 2.B^P. */ + E = 1 + mul_log2_radix(e - 1, radix); + + if (fmt == JS_DTOA_FORMAT_FREE) { + int P_max, E0, e1, E_found, P_found; + uint64_t m1, mant_found, mant, mant_max1; + /* P_max is guarranteed to work by construction */ + P_max = dtoa_max_digits_table[radix - 2]; + E0 = E; + E_found = 0; + P_found = 0; + mant_found = 0; + /* find the minimum number of digits by successive tries */ + P = P_max; /* P_max is guarateed to work */ + for(;;) { + /* mant_max always fits on 64 bits */ + mant_max1 = pow_ui(radix, P); + /* compute the mantissa in base B */ + E = E0; + for(;;) { + /* XXX: add inexact flag */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDN); + mant = mpb_get_u64(tmp1); + if (mant < mant_max1) + break; + E++; /* at most one iteration is possible */ + } + /* remove useless trailing zero digits */ + while ((mant % radix) == 0) { + mant /= radix; + P--; + } + /* garanteed to work for P = P_max */ + if (P_found == 0) + goto prec_found; + /* convert back to base 2 */ + mpb_set_u64(tmp1, mant); + m1 = mul_pow_round_to_d(&e1, tmp1, radix1, radix_shift, E - P, JS_RNDN); + // printf("P=%2d: m=0x%016" PRIx64 " e=%d m1=0x%016" PRIx64 " e1=%d\n", P, m, e, m1, e1); + /* Note: (m, e) is never zero here, so the exponent for m1 + = 0 does not matter */ + if (m1 == m && e1 == e) { + prec_found: + P_found = P; + E_found = E; + mant_found = mant; + if (P == 1) + break; + P--; /* try lower exponent */ + } else { + break; + } + } + P = P_found; + E = E_found; + mpb_set_u64(tmp1, mant_found); +#ifdef JS_DTOA_DUMP_STATS + if (radix == 10) { + out_len_count[P - 1]++; + } +#endif + } else if (fmt == JS_DTOA_FORMAT_FRAC) { + int len; + + assert(n_digits >= 0 && n_digits <= JS_DTOA_MAX_DIGITS); + /* P = max_int(E, 1) + n_digits; */ + /* frac is rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, n_digits, JS_RNDNA); + + /* we add one extra digit on the left and remove it if needed + to avoid testing if the result is < radix^P */ + len = output_digits(q, tmp1, radix, max_int(E + 1, 1) + n_digits, + max_int(E + 1, 1)); + if (q[0] == '0' && len >= 2 && q[1] != '.') { + len--; + memmove(q, q + 1, len); + } + q += len; + goto done; + } else { + int pow_shift; + assert(n_digits >= 1 && n_digits <= JS_DTOA_MAX_DIGITS); + P = n_digits; + /* mant_max = radix^P */ + mant_max->len = 1; + mant_max->tab[0] = 1; + pow_shift = mul_pow(mant_max, radix1, radix_shift, P, FALSE, 0); + mpb_shr_round(mant_max, pow_shift, JS_RNDZ); + + for(;;) { + /* fixed and frac are rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDNA); + if (mpb_cmp(tmp1, mant_max) < 0) + break; + E++; /* at most one iteration is possible */ + } + } + output: + if (fmt == JS_DTOA_FORMAT_FIXED) + E_max = n_digits; + else + E_max = dtoa_max_digits_table[radix - 2] + 4; + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_ENABLED || + ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_AUTO && (E <= -6 || E > E_max))) { + q += output_digits(q, tmp1, radix, P, 1); + E--; + if (radix == 10) { + *q++ = 'e'; + } else if (radix1 == 1 && radix_shift <= 4) { + E *= radix_shift; + *q++ = 'p'; + } else { + *q++ = '@'; + } + if (E < 0) { + *q++ = '-'; + E = -E; + } else { + *q++ = '+'; + } + q += u32toa(q, E); + } else if (E <= 0) { + *q++ = '0'; + *q++ = '.'; + for(i = 0; i < -E; i++) + *q++ = '0'; + q += output_digits(q, tmp1, radix, P, P); + } else { + q += output_digits(q, tmp1, radix, P, min_int(P, E)); + for(i = 0; i < E - P; i++) + *q++ = '0'; + } + done: + *q = '\0'; + dtoa_free(mant_max); + dtoa_free(tmp1); + return q - buf; +} + +static inline int to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* r = r * radix_base + a. radix_base = 0 means radix_base = 2^32 */ +static void mpb_mul1_base(mpb_t *r, limb_t radix_base, limb_t a) +{ + int i; + if (r->tab[0] == 0 && r->len == 1) { + r->tab[0] = a; + } else { + if (radix_base == 0) { + for(i = r->len; i >= 0; i--) { + r->tab[i + 1] = r->tab[i]; + } + r->tab[0] = a; + } else { + r->tab[r->len] = mp_mul1(r->tab, r->tab, r->len, + radix_base, a); + } + r->len++; + mpb_renorm(r); + } +} + +/* XXX: add fast path for small integers */ +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem) +{ + uint64_t *mptr = tmp_mem->mem; + const char *p, *p_start; + limb_t cur_limb, radix_base, extra_digits; + int is_neg, digit_count, limb_digit_count, digits_per_limb, sep, radix1, radix_shift; + int radix_bits, expn, e, max_digits, expn_offset, dot_pos, sig_pos, pos; + mpb_t *tmp0; + double dval; + BOOL is_bin_exp, is_zero, expn_overflow; + uint64_t m, a; + + tmp0 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSATODTempMem) / sizeof(mptr[0])); + /* optional separator between digits */ + sep = (flags & JS_ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + + p = str; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start = p; + } else if (p[0] == '-') { + is_neg = 1; + p++; + p_start = p; + } else { + p_start = p; + } + + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & JS_ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + if (!(flags & JS_ATOD_INT_ONLY) && strstart(p, "Infinity", &p)) + goto overflow; + } + if (radix == 0) + radix = 10; + + cur_limb = 0; + expn_offset = 0; + digit_count = 0; + limb_digit_count = 0; + max_digits = atod_max_digits_table[radix - 2]; + digits_per_limb = digits_per_limb_table[radix - 2]; + radix_base = radix_base_table[radix - 2]; + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + if (radix1 == 1) { + /* radix = 2^radix_bits */ + radix_bits = radix_shift; + } else { + radix_bits = 0; + } + tmp0->len = 1; + tmp0->tab[0] = 0; + extra_digits = 0; + pos = 0; + dot_pos = -1; + /* skip leading zeros */ + for(;;) { + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && p[1] == '0') + p++; + if (*p != '0') + break; + p++; + pos++; + } + + sig_pos = pos; + for(;;) { + limb_t c; + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && to_digit(p[1]) < radix) + p++; + c = to_digit(*p); + if (c >= radix) + break; + p++; + pos++; + if (digit_count < max_digits) { + /* XXX: could be faster when radix_bits != 0 */ + cur_limb = cur_limb * radix + c; + limb_digit_count++; + if (limb_digit_count == digits_per_limb) { + mpb_mul1_base(tmp0, radix_base, cur_limb); + cur_limb = 0; + limb_digit_count = 0; + } + digit_count++; + } else { + extra_digits |= c; + } + } + if (limb_digit_count != 0) { + mpb_mul1_base(tmp0, pow_ui(radix, limb_digit_count), cur_limb); + } + if (digit_count == 0) { + is_zero = TRUE; + expn_offset = 0; + } else { + is_zero = FALSE; + if (dot_pos < 0) + dot_pos = pos; + expn_offset = sig_pos + digit_count - dot_pos; + } + + /* Use the extra digits for rounding if the base is a power of + two. Otherwise they are just truncated. */ + if (radix_bits != 0 && extra_digits != 0) { + tmp0->tab[0] |= 1; + } + + /* parse the exponent, if any */ + expn = 0; + expn_overflow = FALSE; + is_bin_exp = FALSE; + if (!(flags & JS_ATOD_INT_ONLY) && + ((radix == 10 && (*p == 'e' || *p == 'E')) || + (radix != 10 && (*p == '@' || + (radix_bits >= 1 && radix_bits <= 4 && (*p == 'p' || *p == 'P'))))) && + p > p_start) { + BOOL exp_is_neg; + int c; + is_bin_exp = (*p == 'p' || *p == 'P'); + p++; + exp_is_neg = 0; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_is_neg = 1; + p++; + } + c = to_digit(*p); + if (c >= 10) + goto fail; /* XXX: could stop before the exponent part */ + expn = c; + p++; + for(;;) { + if (*p == sep && to_digit(p[1]) < 10) + p++; + c = to_digit(*p); + if (c >= 10) + break; + if (!expn_overflow) { + if (unlikely(expn > ((INT32_MAX - 2 - 9) / 10))) { + expn_overflow = TRUE; + } else { + expn = expn * 10 + c; + } + } + p++; + } + if (exp_is_neg) + expn = -expn; + /* if zero result, the exponent can be arbitrarily large */ + if (!is_zero && expn_overflow) { + if (exp_is_neg) + a = 0; + else + a = (uint64_t)0x7ff << 52; /* infinity */ + goto done; + } + } + + if (p == p_start) + goto fail; + + if (is_zero) { + a = 0; + } else { + int expn1; + if (radix_bits != 0) { + if (!is_bin_exp) + expn *= radix_bits; + expn -= expn_offset * radix_bits; + expn1 = expn + digit_count * radix_bits; + if (expn1 >= 1024 + radix_bits) + goto overflow; + else if (expn1 <= -1075) + goto underflow; + m = round_to_d(&e, tmp0, -expn, JS_RNDN); + } else { + expn -= expn_offset; + expn1 = expn + digit_count; + if (expn1 >= max_exponent[radix - 2] + 1) + goto overflow; + else if (expn1 <= min_exponent[radix - 2]) + goto underflow; + m = mul_pow_round_to_d(&e, tmp0, radix1, radix_shift, expn, JS_RNDN); + } + if (m == 0) { + underflow: + a = 0; + } else if (e > 1024) { + overflow: + /* overflow */ + a = (uint64_t)0x7ff << 52; + } else if (e < -1073) { + /* underflow */ + /* XXX: check rounding */ + a = 0; + } else if (e < -1021) { + /* subnormal */ + a = m >> (-e - 1021); + } else { + a = ((uint64_t)(e + 1022) << 52) | (m & (((uint64_t)1 << 52) - 1)); + } + } + done: + a |= (uint64_t)is_neg << 63; + dval = uint64_as_float64(a); + done1: + if (pnext) + *pnext = p; + dtoa_free(tmp0); + return dval; + fail: + dval = NAN; + goto done1; +} diff --git a/NativeScript/napi/android/quickjs/source/dtoa.h b/NativeScript/napi/android/quickjs/source/dtoa.h new file mode 100755 index 000000000..de76f1a3f --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/dtoa.h @@ -0,0 +1,83 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +//#define JS_DTOA_DUMP_STATS + +/* maximum number of digits for fixed and frac formats */ +#define JS_DTOA_MAX_DIGITS 101 + +/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */ +/* use as many digits as necessary */ +#define JS_DTOA_FORMAT_FREE (0 << 0) +/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */ +#define JS_DTOA_FORMAT_FIXED (1 << 0) +/* force fractional format: [-]dd.dd with n_digits fractional digits. + 0 <= n_digits <= JS_DTOA_MAX_DIGITS */ +#define JS_DTOA_FORMAT_FRAC (2 << 0) +#define JS_DTOA_FORMAT_MASK (3 << 0) + +/* select exponential notation either in fixed or free format */ +#define JS_DTOA_EXP_AUTO (0 << 2) +#define JS_DTOA_EXP_ENABLED (1 << 2) +#define JS_DTOA_EXP_DISABLED (2 << 2) +#define JS_DTOA_EXP_MASK (3 << 2) + +#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */ + +/* only accepts integers (no dot, no exponent) */ +#define JS_ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2) +/* accept _ between digits as a digit separator */ +#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3) + +typedef struct { + uint64_t mem[37]; +} JSDTOATempMem; + +typedef struct { + uint64_t mem[27]; +} JSATODTempMem; + +/* return a maximum bound of the string length */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags); +/* return the string length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem); +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem); + +#ifdef JS_DTOA_DUMP_STATS +void js_dtoa_dump_stats(void); +#endif + +/* additional exported functions */ +size_t u32toa(char *buf, uint32_t n); +size_t i32toa(char *buf, int32_t n); +size_t u64toa(char *buf, uint64_t n); +size_t i64toa(char *buf, int64_t n); +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix); +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix); diff --git a/NativeScript/napi/android/quickjs/source/examples/fib.c b/NativeScript/napi/android/quickjs/source/examples/fib.c new file mode 100755 index 000000000..be90af576 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/examples/fib.c @@ -0,0 +1,72 @@ +/* + * QuickJS: Example of C module + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "../quickjs.h" + +#define countof(x) (sizeof(x) / sizeof((x)[0])) + +static int fib(int n) +{ + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib(n - 1) + fib(n - 2); +} + +static JSValue js_fib(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int n, res; + if (JS_ToInt32(ctx, &n, argv[0])) + return JS_EXCEPTION; + res = fib(n); + return JS_NewInt32(ctx, res); +} + +static const JSCFunctionListEntry js_fib_funcs[] = { + JS_CFUNC_DEF("fib", 1, js_fib ), +}; + +static int js_fib_init(JSContext *ctx, JSModuleDef *m) +{ + return JS_SetModuleExportList(ctx, m, js_fib_funcs, + countof(js_fib_funcs)); +} + +#ifdef JS_SHARED_LIBRARY +#define JS_INIT_MODULE js_init_module +#else +#define JS_INIT_MODULE js_init_module_fib +#endif + +JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_fib_init); + if (!m) + return NULL; + JS_AddModuleExportList(ctx, m, js_fib_funcs, countof(js_fib_funcs)); + return m; +} diff --git a/NativeScript/napi/android/quickjs/source/examples/fib_module.js b/NativeScript/napi/android/quickjs/source/examples/fib_module.js new file mode 100755 index 000000000..6a8107163 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/examples/fib_module.js @@ -0,0 +1,10 @@ +/* fib module */ +export function fib(n) +{ + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib(n - 1) + fib(n - 2); +} diff --git a/NativeScript/napi/android/quickjs/source/examples/hello.js b/NativeScript/napi/android/quickjs/source/examples/hello.js new file mode 100755 index 000000000..accefceba --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/examples/hello.js @@ -0,0 +1 @@ +console.log("Hello World"); diff --git a/NativeScript/napi/android/quickjs/source/examples/hello_module.js b/NativeScript/napi/android/quickjs/source/examples/hello_module.js new file mode 100755 index 000000000..5d4c78ef2 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/examples/hello_module.js @@ -0,0 +1,8 @@ +/* example of JS and JSON modules */ + +import { fib } from "./fib_module.js"; +import msg from "./message.json"; + +console.log("Hello World"); +console.log("fib(10)=", fib(10)); +console.log("msg=", msg); diff --git a/NativeScript/napi/android/quickjs/source/examples/message.json b/NativeScript/napi/android/quickjs/source/examples/message.json new file mode 100755 index 000000000..3b7fe4807 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/examples/message.json @@ -0,0 +1,2 @@ +{ "x" : 1, "tab": [ 1, 2, 3 ] } + diff --git a/NativeScript/napi/android/quickjs/source/examples/pi_bigint.js b/NativeScript/napi/android/quickjs/source/examples/pi_bigint.js new file mode 100755 index 000000000..e15abd177 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/examples/pi_bigint.js @@ -0,0 +1,118 @@ +/* + * PI computation in Javascript using the BigInt type + */ +"use strict"; + +/* return floor(log2(a)) for a > 0 and 0 for a = 0 */ +function floor_log2(a) +{ + var k_max, a1, k, i; + k_max = 0n; + while ((a >> (2n ** k_max)) != 0n) { + k_max++; + } + k = 0n; + a1 = a; + for(i = k_max - 1n; i >= 0n; i--) { + a1 = a >> (2n ** i); + if (a1 != 0n) { + a = a1; + k |= (1n << i); + } + } + return k; +} + +/* return ceil(log2(a)) for a > 0 */ +function ceil_log2(a) +{ + return floor_log2(a - 1n) + 1n; +} + +/* return floor(sqrt(a)) (not efficient but simple) */ +function int_sqrt(a) +{ + var l, u, s; + if (a == 0n) + return a; + l = ceil_log2(a); + u = 1n << ((l + 1n) / 2n); + /* u >= floor(sqrt(a)) */ + for(;;) { + s = u; + u = ((a / s) + s) / 2n; + if (u >= s) + break; + } + return s; +} + +/* return pi * 2**prec */ +function calc_pi(prec) { + const CHUD_A = 13591409n; + const CHUD_B = 545140134n; + const CHUD_C = 640320n; + const CHUD_C3 = 10939058860032000n; /* C^3/24 */ + const CHUD_BITS_PER_TERM = 47.11041313821584202247; /* log2(C/12)*3 */ + + /* return [P, Q, G] */ + function chud_bs(a, b, need_G) { + var c, P, Q, G, P1, Q1, G1, P2, Q2, G2; + if (a == (b - 1n)) { + G = (2n * b - 1n) * (6n * b - 1n) * (6n * b - 5n); + P = G * (CHUD_B * b + CHUD_A); + if (b & 1n) + P = -P; + Q = b * b * b * CHUD_C3; + } else { + c = (a + b) >> 1n; + [P1, Q1, G1] = chud_bs(a, c, true); + [P2, Q2, G2] = chud_bs(c, b, need_G); + P = P1 * Q2 + P2 * G1; + Q = Q1 * Q2; + if (need_G) + G = G1 * G2; + else + G = 0n; + } + return [P, Q, G]; + } + + var n, P, Q, G; + /* number of serie terms */ + n = BigInt(Math.ceil(Number(prec) / CHUD_BITS_PER_TERM)) + 10n; + [P, Q, G] = chud_bs(0n, n, false); + Q = (CHUD_C / 12n) * (Q << prec) / (P + Q * CHUD_A); + G = int_sqrt(CHUD_C << (2n * prec)); + return (Q * G) >> prec; +} + +function main(args) { + var r, n_digits, n_bits, out; + if (args.length < 1) { + print("usage: pi n_digits"); + return; + } + n_digits = args[0] | 0; + + /* we add more bits to reduce the probability of bad rounding for + the last digits */ + n_bits = BigInt(Math.ceil(n_digits * Math.log2(10))) + 32n; + r = calc_pi(n_bits); + r = ((10n ** BigInt(n_digits)) * r) >> n_bits; + out = r.toString(); + print(out[0] + "." + out.slice(1)); +} + +var args; +if (typeof scriptArgs != "undefined") { + args = scriptArgs; + args.shift(); +} else if (typeof arguments != "undefined") { + args = arguments; +} else { + /* default: 1000 digits */ + args=[1000]; +} + +main(args); diff --git a/NativeScript/napi/android/quickjs/source/examples/point.c b/NativeScript/napi/android/quickjs/source/examples/point.c new file mode 100755 index 000000000..0386230e7 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/examples/point.c @@ -0,0 +1,151 @@ +/* + * QuickJS: Example of C module with a class + * + * Copyright (c) 2019 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "../quickjs.h" +#include + +#define countof(x) (sizeof(x) / sizeof((x)[0])) + +/* Point Class */ + +typedef struct { + int x; + int y; +} JSPointData; + +static JSClassID js_point_class_id; + +static void js_point_finalizer(JSRuntime *rt, JSValue val) +{ + JSPointData *s = JS_GetOpaque(val, js_point_class_id); + /* Note: 's' can be NULL in case JS_SetOpaque() was not called */ + js_free_rt(rt, s); +} + +static JSValue js_point_ctor(JSContext *ctx, + JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSPointData *s; + JSValue obj = JS_UNDEFINED; + JSValue proto; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &s->x, argv[0])) + goto fail; + if (JS_ToInt32(ctx, &s->y, argv[1])) + goto fail; + /* using new_target to get the prototype is necessary when the + class is extended. */ + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) + goto fail; + obj = JS_NewObjectProtoClass(ctx, proto, js_point_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) + goto fail; + JS_SetOpaque(obj, s); + return obj; + fail: + js_free(ctx, s); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_point_get_xy(JSContext *ctx, JSValueConst this_val, int magic) +{ + JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id); + if (!s) + return JS_EXCEPTION; + if (magic == 0) + return JS_NewInt32(ctx, s->x); + else + return JS_NewInt32(ctx, s->y); +} + +static JSValue js_point_set_xy(JSContext *ctx, JSValueConst this_val, JSValue val, int magic) +{ + JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id); + int v; + if (!s) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &v, val)) + return JS_EXCEPTION; + if (magic == 0) + s->x = v; + else + s->y = v; + return JS_UNDEFINED; +} + +static JSValue js_point_norm(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id); + if (!s) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, sqrt((double)s->x * s->x + (double)s->y * s->y)); +} + +static JSClassDef js_point_class = { + "Point", + .finalizer = js_point_finalizer, +}; + +static const JSCFunctionListEntry js_point_proto_funcs[] = { + JS_CGETSET_MAGIC_DEF("x", js_point_get_xy, js_point_set_xy, 0), + JS_CGETSET_MAGIC_DEF("y", js_point_get_xy, js_point_set_xy, 1), + JS_CFUNC_DEF("norm", 0, js_point_norm), +}; + +static int js_point_init(JSContext *ctx, JSModuleDef *m) +{ + JSValue point_proto, point_class; + + /* create the Point class */ + JS_NewClassID(&js_point_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_point_class_id, &js_point_class); + + point_proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, point_proto, js_point_proto_funcs, countof(js_point_proto_funcs)); + + point_class = JS_NewCFunction2(ctx, js_point_ctor, "Point", 2, JS_CFUNC_constructor, 0); + /* set proto.constructor and ctor.prototype */ + JS_SetConstructor(ctx, point_class, point_proto); + JS_SetClassProto(ctx, js_point_class_id, point_proto); + + JS_SetModuleExport(ctx, m, "Point", point_class); + return 0; +} + +JSModuleDef *js_init_module(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_point_init); + if (!m) + return NULL; + JS_AddModuleExport(ctx, m, "Point"); + return m; +} diff --git a/NativeScript/napi/android/quickjs/source/examples/test_fib.js b/NativeScript/napi/android/quickjs/source/examples/test_fib.js new file mode 100755 index 000000000..70d26bd8c --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/examples/test_fib.js @@ -0,0 +1,6 @@ +/* example of JS module importing a C module */ + +import { fib } from "./fib.so"; + +console.log("Hello World"); +console.log("fib(10)=", fib(10)); diff --git a/NativeScript/napi/android/quickjs/source/examples/test_point.js b/NativeScript/napi/android/quickjs/source/examples/test_point.js new file mode 100755 index 000000000..0659bc350 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/examples/test_point.js @@ -0,0 +1,40 @@ +/* example of JS module importing a C module */ +import { Point } from "./point.so"; + +function assert(b, str) +{ + if (b) { + return; + } else { + throw Error("assertion failed: " + str); + } +} + +class ColorPoint extends Point { + constructor(x, y, color) { + super(x, y); + this.color = color; + } + get_color() { + return this.color; + } +}; + +function main() +{ + var pt, pt2; + + pt = new Point(2, 3); + assert(pt.x === 2); + assert(pt.y === 3); + pt.x = 4; + assert(pt.x === 4); + assert(pt.norm() == 5); + + pt2 = new ColorPoint(2, 3, 0xffffff); + assert(pt2.x === 2); + assert(pt2.color === 0xffffff); + assert(pt2.get_color() === 0xffffff); +} + +main(); diff --git a/NativeScript/napi/android/quickjs/source/fuzz/README b/NativeScript/napi/android/quickjs/source/fuzz/README new file mode 100755 index 000000000..18c71cd38 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/fuzz/README @@ -0,0 +1,27 @@ +libFuzzer support for QuickJS +============================= + +Build QuickJS with libFuzzer support as follows: + + CONFIG_CLANG=y make libfuzzer + +This can be extended with sanitizer support to improve efficacy: + + CONFIG_CLANG=y CONFIG_ASAN=y make libfuzzer + + +Currently, there are three fuzzing targets defined: fuzz_eval, fuzz_compile and fuzz_regexp. +The above build command will produce an executable binary for each of them, which can be +simply executed as: + + ./fuzz_eval + +or with an initial corpus: + + ./fuzz_compile corpus_dir/ + +or with a predefined dictionary to improve its efficacy: + + ./fuzz_eval -dict fuzz/fuzz.dict + +or with arbitrary CLI arguments provided by libFuzzer (https://llvm.org/docs/LibFuzzer.html). diff --git a/NativeScript/napi/android/quickjs/source/fuzz/fuzz.dict b/NativeScript/napi/android/quickjs/source/fuzz/fuzz.dict new file mode 100755 index 000000000..f31eb9f48 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/fuzz/fuzz.dict @@ -0,0 +1,254 @@ +"__loadScript" +"abs" +"acos" +"acosh" +"add" +"AggregateError" +"and" +"apply" +"Array" +"ArrayBuffer" +"asin" +"asinh" +"atan" +"atan2" +"atanh" +"Atomics" +"BigInt" +"BigInt64Array" +"BigUint64Array" +"Boolean" +"cbrt" +"ceil" +"chdir" +"clearTimeout" +"close" +"clz32" +"compareExchange" +"console" +"construct" +"cos" +"cosh" +"DataView" +"Date" +"decodeURI" +"decodeURIComponent" +"defineProperty" +"deleteProperty" +"dup" +"dup2" +"E" +"encodeURI" +"encodeURIComponent" +"err" +"Error" +"escape" +"eval" +"EvalError" +"evalScript" +"exchange" +"exec" +"exit" +"exp" +"expm1" +"fdopen" +"Float32Array" +"Float64Array" +"floor" +"fround" +"Function" +"gc" +"get" +"getcwd" +"getenv" +"getenviron" +"getOwnPropertyDescriptor" +"getpid" +"getPrototypeOf" +"globalThis" +"has" +"hypot" +"imul" +"in" +"Infinity" +"Int16Array" +"Int32Array" +"Int8Array" +"InternalError" +"isatty" +"isExtensible" +"isFinite" +"isLockFree" +"isNaN" +"iterateBuiltIns" +"JSON" +"kill" +"length" +"LN10" +"LN2" +"load" +"loadFile" +"loadScript" +"log" +"log10" +"LOG10E" +"log1p" +"log2" +"LOG2E" +"lstat" +"Map" +"Math" +"max" +"min" +"mkdir" +"NaN" +"notify" +"now" +"Number" +"O_APPEND" +"O_CREAT" +"O_EXCL" +"O_RDONLY" +"O_RDWR" +"O_TRUNC" +"O_WRONLY" +"Object" +"open" +"Operators" +"or" +"os" +"out" +"ownKeys" +"parse" +"parseExtJSON" +"parseFloat" +"parseInt" +"PI" +"pipe" +"platform" +"popen" +"pow" +"preventExtensions" +"print" +"printf" +"Promise" +"Proxy" +"puts" +"random" +"RangeError" +"read" +"readdir" +"readlink" +"realpath" +"ReferenceError" +"Reflect" +"RegExp" +"remove" +"rename" +"round" +"S_IFBLK" +"S_IFCHR" +"S_IFDIR" +"S_IFIFO" +"S_IFLNK" +"S_IFMT" +"S_IFREG" +"S_IFSOCK" +"S_ISGID" +"S_ISUID" +"scriptArgs" +"seek" +"SEEK_CUR" +"SEEK_END" +"SEEK_SET" +"set" +"Set" +"setenv" +"setPrototypeOf" +"setReadHandler" +"setTimeout" +"setWriteHandler" +"SharedArrayBuffer" +"SIGABRT" +"SIGALRM" +"SIGCHLD" +"SIGCONT" +"SIGFPE" +"SIGILL" +"SIGINT" +"sign" +"signal" +"SIGPIPE" +"SIGQUIT" +"SIGSEGV" +"SIGSTOP" +"SIGTERM" +"SIGTSTP" +"SIGTTIN" +"SIGTTOU" +"SIGUSR1" +"SIGUSR2" +"sin" +"sinh" +"sleep" +"sleepAsync" +"sprintf" +"sqrt" +"SQRT1_2" +"SQRT2" +"stat" +"std" +"store" +"strerror" +"String" +"stringify" +"sub" +"Symbol" +"symlink" +"SyntaxError" +"tan" +"tanh" +"tmpfile" +"trunc" +"ttyGetWinSize" +"ttySetRaw" +"TypeError" +"Uint16Array" +"Uint32Array" +"Uint8Array" +"Uint8ClampedArray" +"undefined" +"unescape" +"unsetenv" +"URIError" +"urlGet" +"utimes" +"wait" +"waitpid" +"WeakMap" +"WeakSet" +"WNOHANG" +"Worker" +"write" +"xor" +"v0" +"v1" +"v2" +"v3" +"v4" +"v5" +"v6" +"v7" +"v8" +"v9" +"v10" +"v11" +"v12" +"v13" +"v14" +"v15" +"v16" +"v17" +"v18" +"v19" +"v20" diff --git a/NativeScript/napi/android/quickjs/source/fuzz/fuzz_common.c b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_common.c new file mode 100755 index 000000000..92548d203 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_common.c @@ -0,0 +1,58 @@ +/* Copyright 2020 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include + +#include "fuzz/fuzz_common.h" + +// handle timeouts from infinite loops +static int interrupt_handler(JSRuntime *rt, void *opaque) +{ + nbinterrupts++; + return (nbinterrupts > 100); +} + +void reset_nbinterrupts() { + nbinterrupts = 0; +} + +void test_one_input_init(JSRuntime *rt, JSContext *ctx) { + // 64 Mo + JS_SetMemoryLimit(rt, 0x4000000); + // 64 Kb + JS_SetMaxStackSize(rt, 0x10000); + + JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); + JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); + js_std_add_helpers(ctx, 0, NULL); + + // Load os and std + js_std_init_handlers(rt); + js_init_module_std(ctx, "std"); + js_init_module_os(ctx, "os"); + const char *str = "import * as std from 'std';\n" + "import * as os from 'os';\n" + "globalThis.std = std;\n" + "globalThis.os = os;\n"; + JSValue std_val = JS_Eval(ctx, str, strlen(str), "", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(std_val)) { + js_module_set_import_meta(ctx, std_val, 1, 1); + std_val = JS_EvalFunction(ctx, std_val); + } else { + js_std_dump_error(ctx); + } + std_val = js_std_await(ctx, std_val); + JS_FreeValue(ctx, std_val); +} diff --git a/NativeScript/napi/android/quickjs/source/fuzz/fuzz_common.h b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_common.h new file mode 100755 index 000000000..10cb49764 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_common.h @@ -0,0 +1,22 @@ +/* Copyright 2020 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "quickjs.h" +#include "quickjs-libc.h" + +static int nbinterrupts = 0; + +void reset_nbinterrupts(); +void test_one_input_init(JSRuntime *rt, JSContext *ctx); diff --git a/NativeScript/napi/android/quickjs/source/fuzz/fuzz_compile.c b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_compile.c new file mode 100755 index 000000000..0ab1b0331 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_compile.c @@ -0,0 +1,93 @@ +/* Copyright 2020 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "quickjs.h" +#include "quickjs-libc.h" +#include "cutils.h" +#include "fuzz/fuzz_common.h" + +#include +#include + + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size == 0) + return 0; + + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + test_one_input_init(rt, ctx); + + uint8_t *null_terminated_data = malloc(size + 1); + memcpy(null_terminated_data, data, size); + null_terminated_data[size] = 0; + + JSValue obj = JS_Eval(ctx, (const char *)null_terminated_data, size, "", JS_EVAL_FLAG_COMPILE_ONLY | JS_EVAL_TYPE_MODULE); + free(null_terminated_data); + //TODO target with JS_ParseJSON + if (JS_IsException(obj)) { + js_std_free_handlers(rt); + JS_FreeValue(ctx, obj); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + obj = js_std_await(ctx, obj); + size_t bytecode_size; + uint8_t* bytecode = JS_WriteObject(ctx, &bytecode_size, obj, JS_WRITE_OBJ_BYTECODE); + JS_FreeValue(ctx, obj); + if (!bytecode) { + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + obj = JS_ReadObject(ctx, bytecode, bytecode_size, JS_READ_OBJ_BYTECODE); + js_free(ctx, bytecode); + if (JS_IsException(obj)) { + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + reset_nbinterrupts(); + /* this is based on + * js_std_eval_binary(ctx, bytecode, bytecode_size, 0); + * modified so as not to exit on JS exception + */ + JSValue val; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { + if (JS_ResolveModule(ctx, obj) < 0) { + JS_FreeValue(ctx, obj); + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + js_module_set_import_meta(ctx, obj, FALSE, TRUE); + } + val = JS_EvalFunction(ctx, obj); + if (JS_IsException(val)) { + js_std_dump_error(ctx); + } else { + js_std_loop(ctx); + } + JS_FreeValue(ctx, val); + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source/fuzz/fuzz_eval.c b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_eval.c new file mode 100755 index 000000000..aa26f1efc --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_eval.c @@ -0,0 +1,49 @@ +/* Copyright 2020 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "quickjs.h" +#include "quickjs-libc.h" +#include "fuzz/fuzz_common.h" + +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size == 0) + return 0; + + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + test_one_input_init(rt, ctx); + + uint8_t *null_terminated_data = malloc(size + 1); + memcpy(null_terminated_data, data, size); + null_terminated_data[size] = 0; + + reset_nbinterrupts(); + //the final 0 does not count (as in strlen) + JSValue val = JS_Eval(ctx, (const char *)null_terminated_data, size, "", JS_EVAL_TYPE_GLOBAL); + free(null_terminated_data); + //TODO targets with JS_ParseJSON, JS_ReadObject + if (!JS_IsException(val)) { + js_std_loop(ctx); + JS_FreeValue(ctx, val); + } + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source/fuzz/fuzz_regexp.c b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_regexp.c new file mode 100755 index 000000000..ae929e840 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/fuzz/fuzz_regexp.c @@ -0,0 +1,66 @@ +/* Copyright 2020 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "libregexp.h" +#include "quickjs-libc.h" + +static int nbinterrupts = 0; + +int lre_check_stack_overflow(void *opaque, size_t alloca_size) { return 0; } + +void *lre_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +int lre_check_timeout(void *opaque) + { + nbinterrupts++; + return (nbinterrupts > 100); + } + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + int len, ret, i; + uint8_t *bc; + char error_msg[64]; + const uint8_t *input; + uint8_t *capture[255 * 2]; + size_t size1 = size; + + //Splits buffer into 2 sub buffers delimited by null character + for (i = 0; i < size; i++) { + if (data[i] == 0) { + size1 = i; + break; + } + } + if (size1 == size) { + //missing delimiter + return 0; + } + bc = lre_compile(&len, error_msg, sizeof(error_msg), (const char *) data, + size1, 0, NULL); + if (!bc) { + return 0; + } + input = data + size1 + 1; + ret = lre_exec(capture, bc, input, 0, size - (size1 + 1), 0, NULL); + if (ret == 1) { + lre_get_capture_count(bc); + } + free(bc); + + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source/fuzz/generate_dict.js b/NativeScript/napi/android/quickjs/source/fuzz/generate_dict.js new file mode 100755 index 000000000..1366fea17 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/fuzz/generate_dict.js @@ -0,0 +1,24 @@ +// Function to recursively iterate through built-in names. +function collectBuiltinNames(obj, visited = new Set(), result = new Set()) { + // Check if the object has already been visited to avoid infinite recursion. + if (visited.has(obj)) + return; + + // Add the current object to the set of visited objects + visited.add(obj); + // Get the property names of the current object + const properties = Object.getOwnPropertyNames(obj); + // Iterate through each property + for (var i=0; i < properties.length; i++) { + var property = properties[i]; + if (property != "collectBuiltinNames" && typeof property != "number") + result.add(property); + // Check if the property is an object and if so, recursively iterate through its properties. + if (typeof obj[property] === 'object' && obj[property] !== null) + collectBuiltinNames(obj[property], visited, result); + } + return result; +} + +// Start the recursive iteration with the global object. +console.log(Array.from(collectBuiltinNames(this)).join('\n')); diff --git a/NativeScript/napi/android/quickjs/source/libregexp-opcode.h b/NativeScript/napi/android/quickjs/source/libregexp-opcode.h new file mode 100755 index 000000000..9908cf373 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/libregexp-opcode.h @@ -0,0 +1,67 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef DEF + +DEF(invalid, 1) /* never used */ +DEF(char, 3) +DEF(char_i, 3) +DEF(char32, 5) +DEF(char32_i, 5) +DEF(dot, 1) +DEF(any, 1) /* same as dot but match any character including line terminator */ +DEF(line_start, 1) +DEF(line_start_m, 1) +DEF(line_end, 1) +DEF(line_end_m, 1) +DEF(goto, 5) +DEF(split_goto_first, 5) +DEF(split_next_first, 5) +DEF(match, 1) +DEF(lookahead_match, 1) +DEF(negative_lookahead_match, 1) /* must come after */ +DEF(save_start, 2) /* save start position */ +DEF(save_end, 2) /* save end position, must come after saved_start */ +DEF(save_reset, 3) /* reset save positions */ +DEF(loop, 6) /* decrement the top the stack and goto if != 0 */ +DEF(push_i32, 6) /* push integer on the stack */ +DEF(word_boundary, 1) +DEF(word_boundary_i, 1) +DEF(not_word_boundary, 1) +DEF(not_word_boundary_i, 1) +DEF(back_reference, 2) +DEF(back_reference_i, 2) /* must come after */ +DEF(backward_back_reference, 2) /* must come after */ +DEF(backward_back_reference_i, 2) /* must come after */ +DEF(range, 3) /* variable length */ +DEF(range_i, 3) /* variable length */ +DEF(range32, 3) /* variable length */ +DEF(range32_i, 3) /* variable length */ +DEF(lookahead, 5) +DEF(negative_lookahead, 5) /* must come after */ +DEF(push_char_pos, 2) /* push the character position on the stack */ +DEF(check_advance, 2) /* pop one stack element and check that it is different from the character position */ +DEF(prev, 1) /* go to the previous char */ + +#endif /* DEF */ diff --git a/NativeScript/napi/android/quickjs/source/libregexp.c b/NativeScript/napi/android/quickjs/source/libregexp.c new file mode 100755 index 000000000..28f407b72 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/libregexp.c @@ -0,0 +1,3261 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "libregexp.h" +#include "libunicode.h" + +/* + TODO: + + - Add a lock step execution mode (=linear time execution guaranteed) + when the regular expression is "simple" i.e. no backreference nor + complicated lookahead. The opcodes are designed for this execution + model. +*/ + +#if defined(TEST) +#define DUMP_REOP +#endif +//#define DUMP_REOP +//#define DUMP_EXEC + +typedef enum { +#define DEF(id, size) REOP_ ## id, +#include "libregexp-opcode.h" +#undef DEF + REOP_COUNT, +} REOPCodeEnum; + +#define CAPTURE_COUNT_MAX 255 +#define STACK_SIZE_MAX 255 +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define INTERRUPT_COUNTER_INIT 10000 + +/* unicode code points */ +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +#define TMP_BUF_SIZE 128 + +typedef struct { + DynBuf byte_code; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + const uint8_t *buf_start; + int re_flags; + BOOL is_unicode; + BOOL unicode_sets; /* if set, is_unicode is also set */ + BOOL ignore_case; + BOOL multi_line; + BOOL dotall; + int capture_count; + int total_capture_count; /* -1 = not computed yet */ + int has_named_captures; /* -1 = don't know, 0 = no, 1 = yes */ + void *opaque; + DynBuf group_names; + union { + char error_msg[TMP_BUF_SIZE]; + char tmp_buf[TMP_BUF_SIZE]; + } u; +} REParseState; + +typedef struct { +#ifdef DUMP_REOP + const char *name; +#endif + uint8_t size; +} REOpCode; + +static const REOpCode reopcode_info[REOP_COUNT] = { +#ifdef DUMP_REOP +#define DEF(id, size) { #id, size }, +#else +#define DEF(id, size) { size }, +#endif +#include "libregexp-opcode.h" +#undef DEF +}; + +#define RE_HEADER_FLAGS 0 +#define RE_HEADER_CAPTURE_COUNT 2 +#define RE_HEADER_STACK_SIZE 3 +#define RE_HEADER_BYTECODE_LEN 4 + +#define RE_HEADER_LEN 8 + +static inline int is_digit(int c) { + return c >= '0' && c <= '9'; +} + +/* insert 'len' bytes at position 'pos'. Return < 0 if error. */ +static int dbuf_insert(DynBuf *s, int pos, int len) +{ + if (dbuf_claim(s, len)) + return -1; + memmove(s->buf + pos + len, s->buf + pos, s->size - pos); + s->size += len; + return 0; +} + +typedef struct REString { + struct REString *next; + uint32_t hash; + uint32_t len; + uint32_t buf[]; +} REString; + +typedef struct { + /* the string list is the union of 'char_range' and of the strings + in hash_table[]. The strings in hash_table[] have a length != + 1. */ + CharRange cr; + uint32_t n_strings; + uint32_t hash_size; + int hash_bits; + REString **hash_table; +} REStringList; + +static uint32_t re_string_hash(int len, const uint32_t *buf) +{ + int i; + uint32_t h; + h = 1; + for(i = 0; i < len; i++) + h = h * 263 + buf[i]; + return h * 0x61C88647; +} + +static void re_string_list_init(REParseState *s1, REStringList *s) +{ + cr_init(&s->cr, s1->opaque, lre_realloc); + s->n_strings = 0; + s->hash_size = 0; + s->hash_bits = 0; + s->hash_table = NULL; +} + +static void re_string_list_free(REStringList *s) +{ + REString *p, *p_next; + int i; + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_table[i]; p != NULL; p = p_next) { + p_next = p->next; + lre_realloc(s->cr.mem_opaque, p, 0); + } + } + lre_realloc(s->cr.mem_opaque, s->hash_table, 0); + + cr_free(&s->cr); +} + +static void lre_print_char(int c, BOOL is_range) +{ + if (c == '\'' || c == '\\' || + (is_range && (c == '-' || c == ']'))) { + printf("\\%c", c); + } else if (c >= ' ' && c <= 126) { + printf("%c", c); + } else { + printf("\\u{%04x}", c); + } +} + +static __maybe_unused void re_string_list_dump(const char *str, const REStringList *s) +{ + REString *p; + const CharRange *cr; + int i, j, k; + + printf("%s:\n", str); + printf(" ranges: ["); + cr = &s->cr; + for(i = 0; i < cr->len; i += 2) { + lre_print_char(cr->points[i], TRUE); + if (cr->points[i] != cr->points[i + 1] - 1) { + printf("-"); + lre_print_char(cr->points[i + 1] - 1, TRUE); + } + } + printf("]\n"); + + j = 0; + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_table[i]; p != NULL; p = p->next) { + printf(" %d/%d: '", j, s->n_strings); + for(k = 0; k < p->len; k++) { + lre_print_char(p->buf[k], FALSE); + } + printf("'\n"); + j++; + } + } +} + +static int re_string_find2(REStringList *s, int len, const uint32_t *buf, + uint32_t h0, BOOL add_flag) +{ + uint32_t h = 0; /* avoid warning */ + REString *p; + if (s->n_strings != 0) { + h = h0 >> (32 - s->hash_bits); + for(p = s->hash_table[h]; p != NULL; p = p->next) { + if (p->hash == h0 && p->len == len && + !memcmp(p->buf, buf, len * sizeof(buf[0]))) { + return 1; + } + } + } + /* not found */ + if (!add_flag) + return 0; + /* increase the size of the hash table if needed */ + if (unlikely((s->n_strings + 1) > s->hash_size)) { + REString **new_hash_table, *p_next; + int new_hash_bits, i; + uint32_t new_hash_size; + new_hash_bits = max_int(s->hash_bits + 1, 4); + new_hash_size = 1 << new_hash_bits; + new_hash_table = lre_realloc(s->cr.mem_opaque, NULL, + sizeof(new_hash_table[0]) * new_hash_size); + if (!new_hash_table) + return -1; + memset(new_hash_table, 0, sizeof(new_hash_table[0]) * new_hash_size); + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_table[i]; p != NULL; p = p_next) { + p_next = p->next; + h = p->hash >> (32 - new_hash_bits); + p->next = new_hash_table[h]; + new_hash_table[h] = p; + } + } + lre_realloc(s->cr.mem_opaque, s->hash_table, 0); + s->hash_bits = new_hash_bits; + s->hash_size = new_hash_size; + s->hash_table = new_hash_table; + h = h0 >> (32 - s->hash_bits); + } + + p = lre_realloc(s->cr.mem_opaque, NULL, sizeof(REString) + len * sizeof(buf[0])); + if (!p) + return -1; + p->next = s->hash_table[h]; + s->hash_table[h] = p; + s->n_strings++; + p->hash = h0; + p->len = len; + memcpy(p->buf, buf, sizeof(buf[0]) * len); + return 1; +} + +static int re_string_find(REStringList *s, int len, const uint32_t *buf, + BOOL add_flag) +{ + uint32_t h0; + h0 = re_string_hash(len, buf); + return re_string_find2(s, len, buf, h0, add_flag); +} + +/* return -1 if memory error, 0 if OK */ +static int re_string_add(REStringList *s, int len, const uint32_t *buf) +{ + if (len == 1) { + return cr_union_interval(&s->cr, buf[0], buf[0]); + } + if (re_string_find(s, len, buf, TRUE) < 0) + return -1; + return 0; +} + +/* a = a op b */ +static int re_string_list_op(REStringList *a, REStringList *b, int op) +{ + int i, ret; + REString *p, **pp; + + if (cr_op1(&a->cr, b->cr.points, b->cr.len, op)) + return -1; + + switch(op) { + case CR_OP_UNION: + if (b->n_strings != 0) { + for(i = 0; i < b->hash_size; i++) { + for(p = b->hash_table[i]; p != NULL; p = p->next) { + if (re_string_find2(a, p->len, p->buf, p->hash, TRUE) < 0) + return -1; + } + } + } + break; + case CR_OP_INTER: + case CR_OP_SUB: + for(i = 0; i < a->hash_size; i++) { + pp = &a->hash_table[i]; + for(;;) { + p = *pp; + if (p == NULL) + break; + ret = re_string_find2(b, p->len, p->buf, p->hash, FALSE); + if (op == CR_OP_SUB) + ret = !ret; + if (!ret) { + /* remove it */ + *pp = p->next; + a->n_strings--; + lre_realloc(a->cr.mem_opaque, p, 0); + } else { + /* keep it */ + pp = &p->next; + } + } + } + break; + default: + abort(); + } + return 0; +} + +static int re_string_list_canonicalize(REParseState *s1, + REStringList *s, BOOL is_unicode) +{ + if (cr_regexp_canonicalize(&s->cr, is_unicode)) + return -1; + if (s->n_strings != 0) { + REStringList a_s, *a = &a_s; + int i, j; + REString *p; + + /* XXX: simplify */ + re_string_list_init(s1, a); + + a->n_strings = s->n_strings; + a->hash_size = s->hash_size; + a->hash_bits = s->hash_bits; + a->hash_table = s->hash_table; + + s->n_strings = 0; + s->hash_size = 0; + s->hash_bits = 0; + s->hash_table = NULL; + + for(i = 0; i < a->hash_size; i++) { + for(p = a->hash_table[i]; p != NULL; p = p->next) { + for(j = 0; j < p->len; j++) { + p->buf[j] = lre_canonicalize(p->buf[j], is_unicode); + } + if (re_string_add(s, p->len, p->buf)) { + re_string_list_free(a); + return -1; + } + } + } + re_string_list_free(a); + } + return 0; +} + +static const uint16_t char_range_d[] = { + 1, + 0x0030, 0x0039 + 1, +}; + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_s[] = { + 10, + 0x0009, 0x000D + 1, + 0x0020, 0x0020 + 1, + 0x00A0, 0x00A0 + 1, + 0x1680, 0x1680 + 1, + 0x2000, 0x200A + 1, + /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ + /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ + 0x2028, 0x2029 + 1, + 0x202F, 0x202F + 1, + 0x205F, 0x205F + 1, + 0x3000, 0x3000 + 1, + /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ + 0xFEFF, 0xFEFF + 1, +}; + +static const uint16_t char_range_w[] = { + 4, + 0x0030, 0x0039 + 1, + 0x0041, 0x005A + 1, + 0x005F, 0x005F + 1, + 0x0061, 0x007A + 1, +}; + +#define CLASS_RANGE_BASE 0x40000000 + +typedef enum { + CHAR_RANGE_d, + CHAR_RANGE_D, + CHAR_RANGE_s, + CHAR_RANGE_S, + CHAR_RANGE_w, + CHAR_RANGE_W, +} CharRangeEnum; + +static const uint16_t * const char_range_table[] = { + char_range_d, + char_range_s, + char_range_w, +}; + +static int cr_init_char_range(REParseState *s, REStringList *cr, uint32_t c) +{ + BOOL invert; + const uint16_t *c_pt; + int len, i; + + invert = c & 1; + c_pt = char_range_table[c >> 1]; + len = *c_pt++; + re_string_list_init(s, cr); + for(i = 0; i < len * 2; i++) { + if (cr_add_point(&cr->cr, c_pt[i])) + goto fail; + } + if (invert) { + if (cr_invert(&cr->cr)) + goto fail; + } + return 0; + fail: + re_string_list_free(cr); + return -1; +} + +#ifdef DUMP_REOP +static __maybe_unused void lre_dump_bytecode(const uint8_t *buf, + int buf_len) +{ + int pos, len, opcode, bc_len, re_flags, i; + uint32_t val, val2; + + assert(buf_len >= RE_HEADER_LEN); + + re_flags = lre_get_flags(buf); + bc_len = get_u32(buf + RE_HEADER_BYTECODE_LEN); + assert(bc_len + RE_HEADER_LEN <= buf_len); + printf("flags: 0x%x capture_count=%d aux_stack_size=%d\n", + re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_STACK_SIZE]); + if (re_flags & LRE_FLAG_NAMED_GROUPS) { + const char *p; + p = (char *)buf + RE_HEADER_LEN + bc_len; + printf("named groups: "); + for(i = 1; i < buf[RE_HEADER_CAPTURE_COUNT]; i++) { + if (i != 1) + printf(","); + printf("<%s>", p); + p += strlen(p) + 1; + } + printf("\n"); + assert(p == (char *)(buf + buf_len)); + } + printf("bytecode_len=%d\n", bc_len); + + buf += RE_HEADER_LEN; + pos = 0; + while (pos < bc_len) { + printf("%5u: ", pos); + opcode = buf[pos]; + len = reopcode_info[opcode].size; + if (opcode >= REOP_COUNT) { + printf(" invalid opcode=0x%02x\n", opcode); + break; + } + if ((pos + len) > bc_len) { + printf(" buffer overflow (opcode=0x%02x)\n", opcode); + break; + } + printf("%s", reopcode_info[opcode].name); + switch(opcode) { + case REOP_char: + case REOP_char_i: + val = get_u16(buf + pos + 1); + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%04x", val); + break; + case REOP_char32: + case REOP_char32_i: + val = get_u32(buf + pos + 1); + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%08x", val); + break; + case REOP_goto: + case REOP_split_goto_first: + case REOP_split_next_first: + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(buf + pos + 1); + val += (pos + 5); + printf(" %u", val); + break; + case REOP_loop: + val2 = buf[pos + 1]; + val = get_u32(buf + pos + 2); + val += (pos + 6); + printf(" %u, %u", val2, val); + break; + case REOP_save_start: + case REOP_save_end: + case REOP_back_reference: + case REOP_back_reference_i: + case REOP_backward_back_reference: + case REOP_backward_back_reference_i: + printf(" %u", buf[pos + 1]); + break; + case REOP_save_reset: + printf(" %u %u", buf[pos + 1], buf[pos + 2]); + break; + case REOP_push_i32: + val = buf[pos + 1]; + val2 = get_u32(buf + pos + 2); + printf(" %u, %d", val, val2); + break; + case REOP_push_char_pos: + case REOP_check_advance: + val = buf[pos + 1]; + printf(" %u", val); + break; + case REOP_range: + case REOP_range_i: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 4; + for(i = 0; i < n * 2; i++) { + val = get_u16(buf + pos + 3 + i * 2); + printf(" 0x%04x", val); + } + } + break; + case REOP_range32: + case REOP_range32_i: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 8; + for(i = 0; i < n * 2; i++) { + val = get_u32(buf + pos + 3 + i * 4); + printf(" 0x%08x", val); + } + } + break; + default: + break; + } + printf("\n"); + pos += len; + } +} +#endif + +static void re_emit_op(REParseState *s, int op) +{ + dbuf_putc(&s->byte_code, op); +} + +/* return the offset of the u32 value */ +static int re_emit_op_u32(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val); + return pos; +} + +static int re_emit_goto(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val - (pos + 4)); + return pos; +} + +static int re_emit_goto_u8(REParseState *s, int op, uint32_t arg, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + dbuf_putc(&s->byte_code, arg); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val - (pos + 4)); + return pos; +} + +static void re_emit_op_u8(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_putc(&s->byte_code, val); +} + +static void re_emit_op_u16(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_put_u16(&s->byte_code, val); +} + +static int __attribute__((format(printf, 2, 3))) re_parse_error(REParseState *s, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(s->u.error_msg, sizeof(s->u.error_msg), fmt, ap); + va_end(ap); + return -1; +} + +static int re_parse_out_of_memory(REParseState *s) +{ + return re_parse_error(s, "out of memory"); +} + +/* If allow_overflow is false, return -1 in case of + overflow. Otherwise return INT32_MAX. */ +static int parse_digits(const uint8_t **pp, BOOL allow_overflow) +{ + const uint8_t *p; + uint64_t v; + int c; + + p = *pp; + v = 0; + for(;;) { + c = *p; + if (c < '0' || c > '9') + break; + v = v * 10 + c - '0'; + if (v >= INT32_MAX) { + if (allow_overflow) + v = INT32_MAX; + else + return -1; + } + p++; + } + *pp = p; + return v; +} + +static int re_parse_expect(REParseState *s, const uint8_t **pp, int c) +{ + const uint8_t *p; + p = *pp; + if (*p != c) + return re_parse_error(s, "expecting '%c'", c); + p++; + *pp = p; + return 0; +} + +/* Parse an escape sequence, *pp points after the '\': + allow_utf16 value: + 0 : no UTF-16 escapes allowed + 1 : UTF-16 escapes allowed + 2 : UTF-16 escapes allowed and escapes of surrogate pairs are + converted to a unicode character (unicode regexp case). + + Return the unicode char and update *pp if recognized, + return -1 if malformed escape, + return -2 otherwise. */ +int lre_parse_escape(const uint8_t **pp, int allow_utf16) +{ + const uint8_t *p; + uint32_t c; + + p = *pp; + c = *p++; + switch(c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case 'x': + case 'u': + { + int h, n, i; + uint32_t c1; + + if (*p == '{' && allow_utf16) { + p++; + c = 0; + for(;;) { + h = from_hex(*p++); + if (h < 0) + return -1; + c = (c << 4) | h; + if (c > 0x10FFFF) + return -1; + if (*p == '}') + break; + } + p++; + } else { + if (c == 'x') { + n = 2; + } else { + n = 4; + } + + c = 0; + for(i = 0; i < n; i++) { + h = from_hex(*p++); + if (h < 0) { + return -1; + } + c = (c << 4) | h; + } + if (is_hi_surrogate(c) && + allow_utf16 == 2 && p[0] == '\\' && p[1] == 'u') { + /* convert an escaped surrogate pair into a + unicode char */ + c1 = 0; + for(i = 0; i < 4; i++) { + h = from_hex(p[2 + i]); + if (h < 0) + break; + c1 = (c1 << 4) | h; + } + if (i == 4 && is_lo_surrogate(c1)) { + p += 6; + c = from_surrogate(c, c1); + } + } + } + } + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c -= '0'; + if (allow_utf16 == 2) { + /* only accept \0 not followed by digit */ + if (c != 0 || is_digit(*p)) + return -1; + } else { + /* parse a legacy octal sequence */ + uint32_t v; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + if (c >= 32) + break; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + } + break; + default: + return -2; + } + *pp = p; + return c; +} + +#ifdef CONFIG_ALL_UNICODE +/* XXX: we use the same chars for name and value */ +static BOOL is_unicode_char(int c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_')); +} + +/* XXX: memory error test */ +static void seq_prop_cb(void *opaque, const uint32_t *seq, int seq_len) +{ + REStringList *sl = opaque; + re_string_add(sl, seq_len, seq); +} + +static int parse_unicode_property(REParseState *s, REStringList *cr, + const uint8_t **pp, BOOL is_inv, + BOOL allow_sequence_prop) +{ + const uint8_t *p; + char name[64], value[64]; + char *q; + BOOL script_ext; + int ret; + + p = *pp; + if (*p != '{') + return re_parse_error(s, "expecting '{' after \\p"); + p++; + q = name; + while (is_unicode_char(*p)) { + if ((q - name) >= sizeof(name) - 1) + goto unknown_property_name; + *q++ = *p++; + } + *q = '\0'; + q = value; + if (*p == '=') { + p++; + while (is_unicode_char(*p)) { + if ((q - value) >= sizeof(value) - 1) + return re_parse_error(s, "unknown unicode property value"); + *q++ = *p++; + } + } + *q = '\0'; + if (*p != '}') + return re_parse_error(s, "expecting '}'"); + p++; + // printf("name=%s value=%s\n", name, value); + + if (!strcmp(name, "Script") || !strcmp(name, "sc")) { + script_ext = FALSE; + goto do_script; + } else if (!strcmp(name, "Script_Extensions") || !strcmp(name, "scx")) { + script_ext = TRUE; + do_script: + re_string_list_init(s, cr); + ret = unicode_script(&cr->cr, value, script_ext); + if (ret) { + re_string_list_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode script"); + else + goto out_of_memory; + } + } else if (!strcmp(name, "General_Category") || !strcmp(name, "gc")) { + re_string_list_init(s, cr); + ret = unicode_general_category(&cr->cr, value); + if (ret) { + re_string_list_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode general category"); + else + goto out_of_memory; + } + } else if (value[0] == '\0') { + re_string_list_init(s, cr); + ret = unicode_general_category(&cr->cr, name); + if (ret == -1) { + re_string_list_free(cr); + goto out_of_memory; + } + if (ret < 0) { + ret = unicode_prop(&cr->cr, name); + if (ret == -1) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (ret < 0 && !is_inv && allow_sequence_prop) { + CharRange cr_tmp; + cr_init(&cr_tmp, s->opaque, lre_realloc); + ret = unicode_sequence_prop(name, seq_prop_cb, cr, &cr_tmp); + cr_free(&cr_tmp); + if (ret == -1) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (ret < 0) + goto unknown_property_name; + } else { + unknown_property_name: + return re_parse_error(s, "unknown unicode property name"); + } + + /* the ordering of case folding and inversion differs with + unicode_sets. 'unicode_sets' ordering is more consistent */ + /* XXX: the spec seems incorrect, we do it as the other engines + seem to do it. */ + if (s->ignore_case && s->unicode_sets) { + if (re_string_list_canonicalize(s, cr, s->is_unicode)) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (is_inv) { + if (cr_invert(&cr->cr)) { + re_string_list_free(cr); + goto out_of_memory; + } + } + if (s->ignore_case && !s->unicode_sets) { + if (re_string_list_canonicalize(s, cr, s->is_unicode)) { + re_string_list_free(cr); + goto out_of_memory; + } + } + *pp = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} +#endif /* CONFIG_ALL_UNICODE */ + +static int get_class_atom(REParseState *s, REStringList *cr, + const uint8_t **pp, BOOL inclass); + +static int parse_class_string_disjunction(REParseState *s, REStringList *cr, + const uint8_t **pp) +{ + const uint8_t *p; + DynBuf str; + int c; + + p = *pp; + if (*p != '{') + return re_parse_error(s, "expecting '{' after \\q"); + + dbuf_init2(&str, s->opaque, lre_realloc); + re_string_list_init(s, cr); + + p++; + for(;;) { + str.size = 0; + while (*p != '}' && *p != '|') { + c = get_class_atom(s, NULL, &p, FALSE); + if (c < 0) + goto fail; + if (dbuf_put_u32(&str, c)) { + re_parse_out_of_memory(s); + goto fail; + } + } + if (re_string_add(cr, str.size / 4, (uint32_t *)str.buf)) { + re_parse_out_of_memory(s); + goto fail; + } + if (*p == '}') + break; + p++; + } + if (s->ignore_case) { + if (re_string_list_canonicalize(s, cr, TRUE)) + goto fail; + } + p++; /* skip the '}' */ + dbuf_free(&str); + *pp = p; + return 0; + fail: + dbuf_free(&str); + re_string_list_free(cr); + return -1; +} + +/* return -1 if error otherwise the character or a class range + (CLASS_RANGE_BASE) if cr != NULL. In case of class range, 'cr' is + initialized. Otherwise, it is ignored. */ +static int get_class_atom(REParseState *s, REStringList *cr, + const uint8_t **pp, BOOL inclass) +{ + const uint8_t *p; + uint32_t c; + int ret; + + p = *pp; + + c = *p; + switch(c) { + case '\\': + p++; + if (p >= s->buf_end) + goto unexpected_end; + c = *p++; + switch(c) { + case 'd': + c = CHAR_RANGE_d; + goto class_range; + case 'D': + c = CHAR_RANGE_D; + goto class_range; + case 's': + c = CHAR_RANGE_s; + goto class_range; + case 'S': + c = CHAR_RANGE_S; + goto class_range; + case 'w': + c = CHAR_RANGE_w; + goto class_range; + case 'W': + c = CHAR_RANGE_W; + class_range: + if (!cr) + goto default_escape; + if (cr_init_char_range(s, cr, c)) + return -1; + c = CLASS_RANGE_BASE; + break; + case 'c': + c = *p; + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (((c >= '0' && c <= '9') || c == '_') && + inclass && !s->is_unicode)) { /* Annex B.1.4 */ + c &= 0x1f; + p++; + } else if (s->is_unicode) { + goto invalid_escape; + } else { + /* otherwise return '\' and 'c' */ + p--; + c = '\\'; + } + break; + case '-': + if (!inclass && s->is_unicode) + goto invalid_escape; + break; + case '^': + case '$': + case '\\': + case '.': + case '*': + case '+': + case '?': + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '|': + case '/': + /* always valid to escape these characters */ + break; +#ifdef CONFIG_ALL_UNICODE + case 'p': + case 'P': + if (s->is_unicode && cr) { + if (parse_unicode_property(s, cr, &p, (c == 'P'), s->unicode_sets)) + return -1; + c = CLASS_RANGE_BASE; + break; + } + goto default_escape; +#endif + case 'q': + if (s->unicode_sets && cr && inclass) { + if (parse_class_string_disjunction(s, cr, &p)) + return -1; + c = CLASS_RANGE_BASE; + break; + } + goto default_escape; + default: + default_escape: + p--; + ret = lre_parse_escape(&p, s->is_unicode * 2); + if (ret >= 0) { + c = ret; + } else { + if (s->is_unicode) { + invalid_escape: + return re_parse_error(s, "invalid escape sequence in regular expression"); + } else { + /* just ignore the '\' */ + goto normal_char; + } + } + break; + } + break; + case '\0': + if (p >= s->buf_end) { + unexpected_end: + return re_parse_error(s, "unexpected end"); + } + /* fall thru */ + goto normal_char; + + case '&': + case '!': + case '#': + case '$': + case '%': + case '*': + case '+': + case ',': + case '.': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '^': + case '`': + case '~': + if (s->unicode_sets && p[1] == c) { + /* forbidden double characters */ + return re_parse_error(s, "invalid class set operation in regular expression"); + } + goto normal_char; + + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '/': + case '-': + case '|': + if (s->unicode_sets) { + /* invalid characters in unicode sets */ + return re_parse_error(s, "invalid character in class in regular expression"); + } + goto normal_char; + + default: + normal_char: + /* normal char */ + if (c >= 128) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + if ((unsigned)c > 0xffff && !s->is_unicode) { + /* XXX: should handle non BMP-1 code points */ + return re_parse_error(s, "malformed unicode char"); + } + } else { + p++; + } + break; + } + *pp = p; + return c; +} + +static int re_emit_range(REParseState *s, const CharRange *cr) +{ + int len, i; + uint32_t high; + + len = (unsigned)cr->len / 2; + if (len >= 65535) + return re_parse_error(s, "too many ranges"); + if (len == 0) { + re_emit_op_u32(s, REOP_char32, -1); + } else { + high = cr->points[cr->len - 1]; + if (high == UINT32_MAX) + high = cr->points[cr->len - 2]; + if (high <= 0xffff) { + /* can use 16 bit ranges with the conversion that 0xffff = + infinity */ + re_emit_op_u16(s, s->ignore_case ? REOP_range_i : REOP_range, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u16(&s->byte_code, cr->points[i]); + high = cr->points[i + 1] - 1; + if (high == UINT32_MAX - 1) + high = 0xffff; + dbuf_put_u16(&s->byte_code, high); + } + } else { + re_emit_op_u16(s, s->ignore_case ? REOP_range32_i : REOP_range32, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u32(&s->byte_code, cr->points[i]); + dbuf_put_u32(&s->byte_code, cr->points[i + 1] - 1); + } + } + } + return 0; +} + +static int re_string_cmp_len(const void *a, const void *b, void *arg) +{ + REString *p1 = *(REString **)a; + REString *p2 = *(REString **)b; + return (p1->len < p2->len) - (p1->len > p2->len); +} + +static void re_emit_char(REParseState *s, int c) +{ + if (c <= 0xffff) + re_emit_op_u16(s, s->ignore_case ? REOP_char_i : REOP_char, c); + else + re_emit_op_u32(s, s->ignore_case ? REOP_char32_i : REOP_char32, c); +} + +static int re_emit_string_list(REParseState *s, const REStringList *sl) +{ + REString **tab, *p; + int i, j, split_pos, last_match_pos, n; + BOOL has_empty_string, is_last; + + // re_string_list_dump("sl", sl); + if (sl->n_strings == 0) { + /* simple case: only characters */ + if (re_emit_range(s, &sl->cr)) + return -1; + } else { + /* at least one string list is present : match the longest ones first */ + /* XXX: add a new op_switch opcode to compile as a trie */ + tab = lre_realloc(s->opaque, NULL, sizeof(tab[0]) * sl->n_strings); + if (!tab) { + re_parse_out_of_memory(s); + return -1; + } + has_empty_string = FALSE; + n = 0; + for(i = 0; i < sl->hash_size; i++) { + for(p = sl->hash_table[i]; p != NULL; p = p->next) { + if (p->len == 0) { + has_empty_string = TRUE; + } else { + tab[n++] = p; + } + } + } + assert(n <= sl->n_strings); + + rqsort(tab, n, sizeof(tab[0]), re_string_cmp_len, NULL); + + last_match_pos = -1; + for(i = 0; i < n; i++) { + p = tab[i]; + is_last = !has_empty_string && sl->cr.len == 0 && i == (n - 1); + if (!is_last) + split_pos = re_emit_op_u32(s, REOP_split_next_first, 0); + else + split_pos = 0; + for(j = 0; j < p->len; j++) { + re_emit_char(s, p->buf[j]); + } + if (!is_last) { + last_match_pos = re_emit_op_u32(s, REOP_goto, last_match_pos); + put_u32(s->byte_code.buf + split_pos, s->byte_code.size - (split_pos + 4)); + } + } + + if (sl->cr.len != 0) { + /* char range */ + is_last = !has_empty_string; + if (!is_last) + split_pos = re_emit_op_u32(s, REOP_split_next_first, 0); + else + split_pos = 0; /* not used */ + if (re_emit_range(s, &sl->cr)) { + lre_realloc(s->opaque, tab, 0); + return -1; + } + if (!is_last) + put_u32(s->byte_code.buf + split_pos, s->byte_code.size - (split_pos + 4)); + } + + /* patch the 'goto match' */ + while (last_match_pos != -1) { + int next_pos = get_u32(s->byte_code.buf + last_match_pos); + put_u32(s->byte_code.buf + last_match_pos, s->byte_code.size - (last_match_pos + 4)); + last_match_pos = next_pos; + } + + lre_realloc(s->opaque, tab, 0); + } + return 0; +} + +static int re_parse_nested_class(REParseState *s, REStringList *cr, const uint8_t **pp); + +static int re_parse_class_set_operand(REParseState *s, REStringList *cr, const uint8_t **pp) +{ + int c1; + const uint8_t *p = *pp; + + if (*p == '[') { + if (re_parse_nested_class(s, cr, pp)) + return -1; + } else { + c1 = get_class_atom(s, cr, pp, TRUE); + if (c1 < 0) + return -1; + if (c1 < CLASS_RANGE_BASE) { + /* create a range with a single character */ + re_string_list_init(s, cr); + if (s->ignore_case) + c1 = lre_canonicalize(c1, s->is_unicode); + if (cr_union_interval(&cr->cr, c1, c1)) { + re_string_list_free(cr); + return -1; + } + } + } + return 0; +} + +static int re_parse_nested_class(REParseState *s, REStringList *cr, const uint8_t **pp) +{ + const uint8_t *p; + uint32_t c1, c2; + int ret; + REStringList cr1_s, *cr1 = &cr1_s; + BOOL invert, is_first; + + if (lre_check_stack_overflow(s->opaque, 0)) + return re_parse_error(s, "stack overflow"); + + re_string_list_init(s, cr); + p = *pp; + p++; /* skip '[' */ + + invert = FALSE; + if (*p == '^') { + p++; + invert = TRUE; + } + + /* handle unions */ + is_first = TRUE; + for(;;) { + if (*p == ']') + break; + if (*p == '[' && s->unicode_sets) { + if (re_parse_nested_class(s, cr1, &p)) + goto fail; + goto class_union; + } else { + c1 = get_class_atom(s, cr1, &p, TRUE); + if ((int)c1 < 0) + goto fail; + if (*p == '-' && p[1] != ']') { + const uint8_t *p0 = p + 1; + if (p[1] == '-' && s->unicode_sets && is_first) + goto class_atom; /* first character class followed by '--' */ + if (c1 >= CLASS_RANGE_BASE) { + if (s->is_unicode) { + re_string_list_free(cr1); + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + c2 = get_class_atom(s, cr1, &p0, TRUE); + if ((int)c2 < 0) + goto fail; + if (c2 >= CLASS_RANGE_BASE) { + re_string_list_free(cr1); + if (s->is_unicode) { + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + p = p0; + if (c2 < c1) { + invalid_class_range: + re_parse_error(s, "invalid class range"); + goto fail; + } + if (s->ignore_case) { + CharRange cr2_s, *cr2 = &cr2_s; + cr_init(cr2, s->opaque, lre_realloc); + if (cr_add_interval(cr2, c1, c2 + 1) || + cr_regexp_canonicalize(cr2, s->is_unicode) || + cr_op1(&cr->cr, cr2->points, cr2->len, CR_OP_UNION)) { + cr_free(cr2); + goto memory_error; + } + cr_free(cr2); + } else { + if (cr_union_interval(&cr->cr, c1, c2)) + goto memory_error; + } + is_first = FALSE; /* union operation */ + } else { + class_atom: + if (c1 >= CLASS_RANGE_BASE) { + class_union: + ret = re_string_list_op(cr, cr1, CR_OP_UNION); + re_string_list_free(cr1); + if (ret) + goto memory_error; + } else { + if (s->ignore_case) + c1 = lre_canonicalize(c1, s->is_unicode); + if (cr_union_interval(&cr->cr, c1, c1)) + goto memory_error; + } + } + } + if (s->unicode_sets && is_first) { + if (*p == '&' && p[1] == '&' && p[2] != '&') { + /* handle '&&' */ + for(;;) { + if (*p == ']') { + break; + } else if (*p == '&' && p[1] == '&' && p[2] != '&') { + p += 2; + } else { + goto invalid_operation; + } + if (re_parse_class_set_operand(s, cr1, &p)) + goto fail; + ret = re_string_list_op(cr, cr1, CR_OP_INTER); + re_string_list_free(cr1); + if (ret) + goto memory_error; + } + } else if (*p == '-' && p[1] == '-') { + /* handle '--' */ + for(;;) { + if (*p == ']') { + break; + } else if (*p == '-' && p[1] == '-') { + p += 2; + } else { + invalid_operation: + re_parse_error(s, "invalid operation in regular expression"); + goto fail; + } + if (re_parse_class_set_operand(s, cr1, &p)) + goto fail; + ret = re_string_list_op(cr, cr1, CR_OP_SUB); + re_string_list_free(cr1); + if (ret) + goto memory_error; + } + } + } + is_first = FALSE; + } + + p++; /* skip ']' */ + *pp = p; + if (invert) { + /* XXX: add may_contain_string syntax check to be fully + compliant. The test here accepts more input than the + spec. */ + if (cr->n_strings != 0) { + re_parse_error(s, "negated character class with strings in regular expression debugger eval code"); + goto fail; + } + if (cr_invert(&cr->cr)) + goto memory_error; + } + return 0; + memory_error: + re_parse_out_of_memory(s); + fail: + re_string_list_free(cr); + return -1; +} + +static int re_parse_char_class(REParseState *s, const uint8_t **pp) +{ + REStringList cr_s, *cr = &cr_s; + + if (re_parse_nested_class(s, cr, pp)) + return -1; + if (re_emit_string_list(s, cr)) + goto fail; + re_string_list_free(cr); + return 0; + fail: + re_string_list_free(cr); + return -1; +} + +/* Return: + - true if the opcodes may not advance the char pointer + - false if the opcodes always advance the char pointer +*/ +static BOOL re_need_check_advance(const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len; + uint32_t val; + BOOL ret; + + ret = TRUE; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range: + case REOP_range_i: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + goto simple_char; + case REOP_range32: + case REOP_range32_i: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + goto simple_char; + case REOP_char: + case REOP_char_i: + case REOP_char32: + case REOP_char32_i: + case REOP_dot: + case REOP_any: + simple_char: + ret = FALSE; + break; + case REOP_line_start: + case REOP_line_start_m: + case REOP_line_end: + case REOP_line_end_m: + case REOP_push_i32: + case REOP_push_char_pos: + case REOP_word_boundary: + case REOP_word_boundary_i: + case REOP_not_word_boundary: + case REOP_not_word_boundary_i: + case REOP_prev: + /* no effect */ + break; + case REOP_save_start: + case REOP_save_end: + case REOP_save_reset: + case REOP_back_reference: + case REOP_back_reference_i: + case REOP_backward_back_reference: + case REOP_backward_back_reference_i: + break; + default: + /* safe behavior: we cannot predict the outcome */ + return TRUE; + } + pos += len; + } + return ret; +} + +/* '*pp' is the first char after '<' */ +static int re_parse_group_name(char *buf, int buf_size, const uint8_t **pp) +{ + const uint8_t *p, *p1; + uint32_t c, d; + char *q; + + p = *pp; + q = buf; + for(;;) { + c = *p; + if (c == '\\') { + p++; + if (*p != 'u') + return -1; + c = lre_parse_escape(&p, 2); // accept surrogate pairs + } else if (c == '>') { + break; + } else if (c >= 128) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + if (is_hi_surrogate(c)) { + d = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); + if (is_lo_surrogate(d)) { + c = from_surrogate(c, d); + p = p1; + } + } + } else { + p++; + } + if (c > 0x10FFFF) + return -1; + if (q == buf) { + if (!lre_js_is_ident_first(c)) + return -1; + } else { + if (!lre_js_is_ident_next(c)) + return -1; + } + if ((q - buf + UTF8_CHAR_LEN_MAX + 1) > buf_size) + return -1; + if (c < 128) { + *q++ = c; + } else { + q += unicode_to_utf8((uint8_t*)q, c); + } + } + if (q == buf) + return -1; + *q = '\0'; + p++; + *pp = p; + return 0; +} + +/* if capture_name = NULL: return the number of captures + 1. + Otherwise, return the capture index corresponding to capture_name + or -1 if none */ +static int re_parse_captures(REParseState *s, int *phas_named_captures, + const char *capture_name) +{ + const uint8_t *p; + int capture_index; + char name[TMP_BUF_SIZE]; + + capture_index = 1; + *phas_named_captures = 0; + for (p = s->buf_start; p < s->buf_end; p++) { + switch (*p) { + case '(': + if (p[1] == '?') { + if (p[2] == '<' && p[3] != '=' && p[3] != '!') { + *phas_named_captures = 1; + /* potential named capture */ + if (capture_name) { + p += 3; + if (re_parse_group_name(name, sizeof(name), &p) == 0) { + if (!strcmp(name, capture_name)) + return capture_index; + } + } + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + } else { + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + break; + case '\\': + p++; + break; + case '[': + for (p += 1 + (*p == ']'); p < s->buf_end && *p != ']'; p++) { + if (*p == '\\') + p++; + } + break; + } + } + done: + if (capture_name) + return -1; + else + return capture_index; +} + +static int re_count_captures(REParseState *s) +{ + if (s->total_capture_count < 0) { + s->total_capture_count = re_parse_captures(s, &s->has_named_captures, + NULL); + } + return s->total_capture_count; +} + +static BOOL re_has_named_captures(REParseState *s) +{ + if (s->has_named_captures < 0) + re_count_captures(s); + return s->has_named_captures; +} + +static int find_group_name(REParseState *s, const char *name) +{ + const char *p, *buf_end; + size_t len, name_len; + int capture_index; + + p = (char *)s->group_names.buf; + if (!p) return -1; + buf_end = (char *)s->group_names.buf + s->group_names.size; + name_len = strlen(name); + capture_index = 1; + while (p < buf_end) { + len = strlen(p); + if (len == name_len && memcmp(name, p, name_len) == 0) + return capture_index; + p += len + 1; + capture_index++; + } + return -1; +} + +static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir); + +static int re_parse_modifiers(REParseState *s, const uint8_t **pp) +{ + const uint8_t *p = *pp; + int mask = 0; + int val; + + for(;;) { + if (*p == 'i') { + val = LRE_FLAG_IGNORECASE; + } else if (*p == 'm') { + val = LRE_FLAG_MULTILINE; + } else if (*p == 's') { + val = LRE_FLAG_DOTALL; + } else { + break; + } + if (mask & val) + return re_parse_error(s, "duplicate modifier: '%c'", *p); + mask |= val; + p++; + } + *pp = p; + return mask; +} + +static BOOL update_modifier(BOOL val, int add_mask, int remove_mask, + int mask) +{ + if (add_mask & mask) + val = TRUE; + if (remove_mask & mask) + val = FALSE; + return val; +} + +static int re_parse_term(REParseState *s, BOOL is_backward_dir) +{ + const uint8_t *p; + int c, last_atom_start, quant_min, quant_max, last_capture_count; + BOOL greedy, add_zero_advance_check, is_neg, is_backward_lookahead; + REStringList cr_s, *cr = &cr_s; + + last_atom_start = -1; + last_capture_count = 0; + p = s->buf_ptr; + c = *p; + switch(c) { + case '^': + p++; + re_emit_op(s, s->multi_line ? REOP_line_start_m : REOP_line_start); + break; + case '$': + p++; + re_emit_op(s, s->multi_line ? REOP_line_end_m : REOP_line_end); + break; + case '.': + p++; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + re_emit_op(s, s->dotall ? REOP_any : REOP_dot); + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case '{': + if (s->is_unicode) { + return re_parse_error(s, "syntax error"); + } else if (!is_digit(p[1])) { + /* Annex B: we accept '{' not followed by digits as a + normal atom */ + goto parse_class_atom; + } else { + const uint8_t *p1 = p + 1; + /* Annex B: error if it is like a repetition count */ + parse_digits(&p1, TRUE); + if (*p1 == ',') { + p1++; + if (is_digit(*p1)) { + parse_digits(&p1, TRUE); + } + } + if (*p1 != '}') { + goto parse_class_atom; + } + } + /* fall thru */ + case '*': + case '+': + case '?': + return re_parse_error(s, "nothing to repeat"); + case '(': + if (p[1] == '?') { + if (p[2] == ':') { + p += 3; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + } else if (p[2] == 'i' || p[2] == 'm' || p[2] == 's' || p[2] == '-') { + BOOL saved_ignore_case, saved_multi_line, saved_dotall; + int add_mask, remove_mask; + p += 2; + remove_mask = 0; + add_mask = re_parse_modifiers(s, &p); + if (add_mask < 0) + return -1; + if (*p == '-') { + p++; + remove_mask = re_parse_modifiers(s, &p); + if (remove_mask < 0) + return -1; + } + if ((add_mask == 0 && remove_mask == 0) || + (add_mask & remove_mask) != 0) { + return re_parse_error(s, "invalid modifiers"); + } + if (re_parse_expect(s, &p, ':')) + return -1; + saved_ignore_case = s->ignore_case; + saved_multi_line = s->multi_line; + saved_dotall = s->dotall; + s->ignore_case = update_modifier(s->ignore_case, add_mask, remove_mask, LRE_FLAG_IGNORECASE); + s->multi_line = update_modifier(s->multi_line, add_mask, remove_mask, LRE_FLAG_MULTILINE); + s->dotall = update_modifier(s->dotall, add_mask, remove_mask, LRE_FLAG_DOTALL); + + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + s->ignore_case = saved_ignore_case; + s->multi_line = saved_multi_line; + s->dotall = saved_dotall; + } else if ((p[2] == '=' || p[2] == '!')) { + is_neg = (p[2] == '!'); + is_backward_lookahead = FALSE; + p += 3; + goto lookahead; + } else if (p[2] == '<' && + (p[3] == '=' || p[3] == '!')) { + int pos; + is_neg = (p[3] == '!'); + is_backward_lookahead = TRUE; + p += 4; + /* lookahead */ + lookahead: + /* Annex B allows lookahead to be used as an atom for + the quantifiers */ + if (!s->is_unicode && !is_backward_lookahead) { + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + } + pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0); + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_lookahead)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + re_emit_op(s, REOP_lookahead_match + is_neg); + /* jump after the 'match' after the lookahead is successful */ + if (dbuf_error(&s->byte_code)) + return -1; + put_u32(s->byte_code.buf + pos, s->byte_code.size - (pos + 4)); + } else if (p[2] == '<') { + p += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p)) { + return re_parse_error(s, "invalid group name"); + } + if (find_group_name(s, s->u.tmp_buf) > 0) { + return re_parse_error(s, "duplicate group name"); + } + /* group name with a trailing zero */ + dbuf_put(&s->group_names, (uint8_t *)s->u.tmp_buf, + strlen(s->u.tmp_buf) + 1); + s->has_named_captures = 1; + goto parse_capture; + } else { + return re_parse_error(s, "invalid group"); + } + } else { + int capture_index; + p++; + /* capture without group name */ + dbuf_putc(&s->group_names, 0); + parse_capture: + if (s->capture_count >= CAPTURE_COUNT_MAX) + return re_parse_error(s, "too many captures"); + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + capture_index = s->capture_count++; + re_emit_op_u8(s, REOP_save_start + is_backward_dir, + capture_index); + + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + + re_emit_op_u8(s, REOP_save_start + 1 - is_backward_dir, + capture_index); + + if (re_parse_expect(s, &p, ')')) + return -1; + } + break; + case '\\': + switch(p[1]) { + case 'b': + case 'B': + if (p[1] != 'b') { + re_emit_op(s, s->ignore_case ? REOP_not_word_boundary_i : REOP_not_word_boundary); + } else { + re_emit_op(s, s->ignore_case ? REOP_word_boundary_i : REOP_word_boundary); + } + p += 2; + break; + case 'k': + { + const uint8_t *p1; + int dummy_res; + + p1 = p; + if (p1[2] != '<') { + /* annex B: we tolerate invalid group names in non + unicode mode if there is no named capture + definition */ + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "expecting group name"); + else + goto parse_class_atom; + } + p1 += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p1)) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "invalid group name"); + else + goto parse_class_atom; + } + c = find_group_name(s, s->u.tmp_buf); + if (c < 0) { + /* no capture name parsed before, try to look + after (inefficient, but hopefully not common */ + c = re_parse_captures(s, &dummy_res, s->u.tmp_buf); + if (c < 0) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "group name not defined"); + else + goto parse_class_atom; + } + } + p = p1; + } + goto emit_back_reference; + case '0': + p += 2; + c = 0; + if (s->is_unicode) { + if (is_digit(*p)) { + return re_parse_error(s, "invalid decimal escape in regular expression"); + } + } else { + /* Annex B.1.4: accept legacy octal */ + if (*p >= '0' && *p <= '7') { + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } + goto normal_char; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + { + const uint8_t *q = ++p; + + c = parse_digits(&p, FALSE); + if (c < 0 || (c >= s->capture_count && c >= re_count_captures(s))) { + if (!s->is_unicode) { + /* Annex B.1.4: accept legacy octal */ + p = q; + if (*p <= '7') { + c = 0; + if (*p <= '3') + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } else { + c = *p++; + } + goto normal_char; + } + return re_parse_error(s, "back reference out of range in regular expression"); + } + emit_back_reference: + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + + re_emit_op_u8(s, REOP_back_reference + 2 * is_backward_dir + s->ignore_case, c); + } + break; + default: + goto parse_class_atom; + } + break; + case '[': + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (re_parse_char_class(s, &p)) + return -1; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case ']': + case '}': + if (s->is_unicode) + return re_parse_error(s, "syntax error"); + goto parse_class_atom; + default: + parse_class_atom: + c = get_class_atom(s, cr, &p, FALSE); + if ((int)c < 0) + return -1; + normal_char: + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (c >= CLASS_RANGE_BASE) { + int ret; + ret = re_emit_string_list(s, cr); + re_string_list_free(cr); + if (ret) + return -1; + } else { + if (s->ignore_case) + c = lre_canonicalize(c, s->is_unicode); + re_emit_char(s, c); + } + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + } + + /* quantifier */ + if (last_atom_start >= 0) { + c = *p; + switch(c) { + case '*': + p++; + quant_min = 0; + quant_max = INT32_MAX; + goto quantifier; + case '+': + p++; + quant_min = 1; + quant_max = INT32_MAX; + goto quantifier; + case '?': + p++; + quant_min = 0; + quant_max = 1; + goto quantifier; + case '{': + { + const uint8_t *p1 = p; + /* As an extension (see ES6 annex B), we accept '{' not + followed by digits as a normal atom */ + if (!is_digit(p[1])) { + if (s->is_unicode) + goto invalid_quant_count; + break; + } + p++; + quant_min = parse_digits(&p, TRUE); + quant_max = quant_min; + if (*p == ',') { + p++; + if (is_digit(*p)) { + quant_max = parse_digits(&p, TRUE); + if (quant_max < quant_min) { + invalid_quant_count: + return re_parse_error(s, "invalid repetition count"); + } + } else { + quant_max = INT32_MAX; /* infinity */ + } + } + if (*p != '}' && !s->is_unicode) { + /* Annex B: normal atom if invalid '{' syntax */ + p = p1; + break; + } + if (re_parse_expect(s, &p, '}')) + return -1; + } + quantifier: + greedy = TRUE; + if (*p == '?') { + p++; + greedy = FALSE; + } + if (last_atom_start < 0) { + return re_parse_error(s, "nothing to repeat"); + } + /* the spec tells that if there is no advance when + running the atom after the first quant_min times, + then there is no match. We remove this test when we + are sure the atom always advances the position. */ + add_zero_advance_check = re_need_check_advance(s->byte_code.buf + last_atom_start, + s->byte_code.size - last_atom_start); + + { + int len, pos; + len = s->byte_code.size - last_atom_start; + if (quant_min == 0) { + /* need to reset the capture in case the atom is + not executed */ + if (last_capture_count != s->capture_count) { + if (dbuf_insert(&s->byte_code, last_atom_start, 3)) + goto out_of_memory; + s->byte_code.buf[last_atom_start++] = REOP_save_reset; + s->byte_code.buf[last_atom_start++] = last_capture_count; + s->byte_code.buf[last_atom_start++] = s->capture_count - 1; + } + if (quant_max == 0) { + s->byte_code.size = last_atom_start; + } else if (quant_max == 1 || quant_max == INT32_MAX) { + BOOL has_goto = (quant_max == INT32_MAX); + if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check * 2)) + goto out_of_memory; + s->byte_code.buf[last_atom_start] = REOP_split_goto_first + + greedy; + put_u32(s->byte_code.buf + last_atom_start + 1, + len + 5 * has_goto + add_zero_advance_check * 2 * 2); + if (add_zero_advance_check) { + s->byte_code.buf[last_atom_start + 1 + 4] = REOP_push_char_pos; + s->byte_code.buf[last_atom_start + 1 + 4 + 1] = 0; + re_emit_op_u8(s, REOP_check_advance, 0); + } + if (has_goto) + re_emit_goto(s, REOP_goto, last_atom_start); + } else { + if (dbuf_insert(&s->byte_code, last_atom_start, 11 + add_zero_advance_check * 2)) + goto out_of_memory; + pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_push_i32; + s->byte_code.buf[pos++] = 0; + put_u32(s->byte_code.buf + pos, quant_max); + pos += 4; + + s->byte_code.buf[pos++] = REOP_split_goto_first + greedy; + put_u32(s->byte_code.buf + pos, len + 6 + add_zero_advance_check * 2 * 2); + pos += 4; + if (add_zero_advance_check) { + s->byte_code.buf[pos++] = REOP_push_char_pos; + s->byte_code.buf[pos++] = 0; + re_emit_op_u8(s, REOP_check_advance, 0); + } + re_emit_goto_u8(s, REOP_loop, 0, last_atom_start + 6); + } + } else if (quant_min == 1 && quant_max == INT32_MAX && + !add_zero_advance_check) { + re_emit_goto(s, REOP_split_next_first - greedy, + last_atom_start); + } else { + if (quant_min == 1) { + /* nothing to add */ + } else { + if (dbuf_insert(&s->byte_code, last_atom_start, 6)) + goto out_of_memory; + s->byte_code.buf[last_atom_start++] = REOP_push_i32; + s->byte_code.buf[last_atom_start++] = 0; + put_u32(s->byte_code.buf + last_atom_start, quant_min); + last_atom_start += 4; + re_emit_goto_u8(s, REOP_loop, 0, last_atom_start); + } + if (quant_max == INT32_MAX) { + pos = s->byte_code.size; + re_emit_op_u32(s, REOP_split_goto_first + greedy, + len + 5 + add_zero_advance_check * 2 * 2); + if (add_zero_advance_check) + re_emit_op_u8(s, REOP_push_char_pos, 0); + /* copy the atom */ + dbuf_put_self(&s->byte_code, last_atom_start, len); + if (add_zero_advance_check) + re_emit_op_u8(s, REOP_check_advance, 0); + re_emit_goto(s, REOP_goto, pos); + } else if (quant_max > quant_min) { + re_emit_op_u8(s, REOP_push_i32, 0); + dbuf_put_u32(&s->byte_code, quant_max - quant_min); + + pos = s->byte_code.size; + re_emit_op_u32(s, REOP_split_goto_first + greedy, + len + 6 + add_zero_advance_check * 2 * 2); + if (add_zero_advance_check) + re_emit_op_u8(s, REOP_push_char_pos, 0); + /* copy the atom */ + dbuf_put_self(&s->byte_code, last_atom_start, len); + if (add_zero_advance_check) + re_emit_op_u8(s, REOP_check_advance, 0); + re_emit_goto_u8(s, REOP_loop, 0, pos); + } + } + last_atom_start = -1; + } + break; + default: + break; + } + } + s->buf_ptr = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} + +static int re_parse_alternative(REParseState *s, BOOL is_backward_dir) +{ + const uint8_t *p; + int ret; + size_t start, term_start, end, term_size; + + start = s->byte_code.size; + for(;;) { + p = s->buf_ptr; + if (p >= s->buf_end) + break; + if (*p == '|' || *p == ')') + break; + term_start = s->byte_code.size; + ret = re_parse_term(s, is_backward_dir); + if (ret) + return ret; + if (is_backward_dir) { + /* reverse the order of the terms (XXX: inefficient, but + speed is not really critical here) */ + end = s->byte_code.size; + term_size = end - term_start; + if (dbuf_claim(&s->byte_code, term_size)) + return -1; + memmove(s->byte_code.buf + start + term_size, + s->byte_code.buf + start, + end - start); + memcpy(s->byte_code.buf + start, s->byte_code.buf + end, + term_size); + } + } + return 0; +} + +static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir) +{ + int start, len, pos; + + if (lre_check_stack_overflow(s->opaque, 0)) + return re_parse_error(s, "stack overflow"); + + start = s->byte_code.size; + if (re_parse_alternative(s, is_backward_dir)) + return -1; + while (*s->buf_ptr == '|') { + s->buf_ptr++; + + len = s->byte_code.size - start; + + /* insert a split before the first alternative */ + if (dbuf_insert(&s->byte_code, start, 5)) { + return re_parse_out_of_memory(s); + } + s->byte_code.buf[start] = REOP_split_next_first; + put_u32(s->byte_code.buf + start + 1, len + 5); + + pos = re_emit_op_u32(s, REOP_goto, 0); + + if (re_parse_alternative(s, is_backward_dir)) + return -1; + + /* patch the goto */ + len = s->byte_code.size - (pos + 4); + put_u32(s->byte_code.buf + pos, len); + } + return 0; +} + +/* the control flow is recursive so the analysis can be linear. As a + side effect, the auxiliary stack addresses are computed. */ +static int compute_stack_size(uint8_t *bc_buf, int bc_buf_len) +{ + int stack_size, stack_size_max, pos, opcode, len; + uint32_t val; + + stack_size = 0; + stack_size_max = 0; + bc_buf += RE_HEADER_LEN; + bc_buf_len -= RE_HEADER_LEN; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + assert(opcode < REOP_COUNT); + assert((pos + len) <= bc_buf_len); + switch(opcode) { + case REOP_push_i32: + case REOP_push_char_pos: + bc_buf[pos + 1] = stack_size; + stack_size++; + if (stack_size > stack_size_max) { + if (stack_size > STACK_SIZE_MAX) + return -1; + stack_size_max = stack_size; + } + break; + case REOP_check_advance: + case REOP_loop: + assert(stack_size > 0); + stack_size--; + bc_buf[pos + 1] = stack_size; + break; + case REOP_range: + case REOP_range_i: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + break; + case REOP_range32: + case REOP_range32_i: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + break; + } + pos += len; + } + return stack_size_max; +} + +static void *lre_bytecode_realloc(void *opaque, void *ptr, size_t size) +{ + if (size > (INT32_MAX / 2)) { + /* the bytecode cannot be larger than 2G. Leave some slack to + avoid some overflows. */ + return NULL; + } else { + return lre_realloc(opaque, ptr, size); + } +} + +/* 'buf' must be a zero terminated UTF-8 string of length buf_len. + Return NULL if error and allocate an error message in *perror_msg, + otherwise the compiled bytecode and its length in plen. +*/ +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque) +{ + REParseState s_s, *s = &s_s; + int stack_size; + BOOL is_sticky; + + memset(s, 0, sizeof(*s)); + s->opaque = opaque; + s->buf_ptr = (const uint8_t *)buf; + s->buf_end = s->buf_ptr + buf_len; + s->buf_start = s->buf_ptr; + s->re_flags = re_flags; + s->is_unicode = ((re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0); + is_sticky = ((re_flags & LRE_FLAG_STICKY) != 0); + s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0); + s->multi_line = ((re_flags & LRE_FLAG_MULTILINE) != 0); + s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0); + s->unicode_sets = ((re_flags & LRE_FLAG_UNICODE_SETS) != 0); + s->capture_count = 1; + s->total_capture_count = -1; + s->has_named_captures = -1; + + dbuf_init2(&s->byte_code, opaque, lre_bytecode_realloc); + dbuf_init2(&s->group_names, opaque, lre_realloc); + + dbuf_put_u16(&s->byte_code, re_flags); /* first element is the flags */ + dbuf_putc(&s->byte_code, 0); /* second element is the number of captures */ + dbuf_putc(&s->byte_code, 0); /* stack size */ + dbuf_put_u32(&s->byte_code, 0); /* bytecode length */ + + if (!is_sticky) { + /* iterate thru all positions (about the same as .*?( ... ) ) + . We do it without an explicit loop so that lock step + thread execution will be possible in an optimized + implementation */ + re_emit_op_u32(s, REOP_split_goto_first, 1 + 5); + re_emit_op(s, REOP_any); + re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5)); + } + re_emit_op_u8(s, REOP_save_start, 0); + + if (re_parse_disjunction(s, FALSE)) { + error: + dbuf_free(&s->byte_code); + dbuf_free(&s->group_names); + pstrcpy(error_msg, error_msg_size, s->u.error_msg); + *plen = 0; + return NULL; + } + + re_emit_op_u8(s, REOP_save_end, 0); + + re_emit_op(s, REOP_match); + + if (*s->buf_ptr != '\0') { + re_parse_error(s, "extraneous characters at the end"); + goto error; + } + + if (dbuf_error(&s->byte_code)) { + re_parse_out_of_memory(s); + goto error; + } + + stack_size = compute_stack_size(s->byte_code.buf, s->byte_code.size); + if (stack_size < 0) { + re_parse_error(s, "too many imbricated quantifiers"); + goto error; + } + + s->byte_code.buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count; + s->byte_code.buf[RE_HEADER_STACK_SIZE] = stack_size; + put_u32(s->byte_code.buf + RE_HEADER_BYTECODE_LEN, + s->byte_code.size - RE_HEADER_LEN); + + /* add the named groups if needed */ + if (s->group_names.size > (s->capture_count - 1)) { + dbuf_put(&s->byte_code, s->group_names.buf, s->group_names.size); + put_u16(s->byte_code.buf + RE_HEADER_FLAGS, + lre_get_flags(s->byte_code.buf) | LRE_FLAG_NAMED_GROUPS); + } + dbuf_free(&s->group_names); + +#ifdef DUMP_REOP + lre_dump_bytecode(s->byte_code.buf, s->byte_code.size); +#endif + + error_msg[0] = '\0'; + *plen = s->byte_code.size; + return s->byte_code.buf; +} + +static BOOL is_line_terminator(uint32_t c) +{ + return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS); +} + +static BOOL is_word_char(uint32_t c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c == '_')); +} + +#define GET_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = *cptr++; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c) && cbuf_type == 2) { \ + if (_p < _end && is_lo_surrogate(*_p)) { \ + c = from_surrogate(c, *_p++); \ + } \ + } \ + cptr = (const void *)_p; \ + } \ + } while (0) + +#define PEEK_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[0]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c) && cbuf_type == 2) { \ + if (_p < _end && is_lo_surrogate(*_p)) { \ + c = from_surrogate(c, *_p); \ + } \ + } \ + } \ + } while (0) + +#define PEEK_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[-1]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p[-1])) { \ + c = from_surrogate(*--_p, c); \ + } \ + } \ + } \ + } while (0) + +#define GET_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + c = cptr[0]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p[-1])) { \ + c = from_surrogate(*--_p, c); \ + } \ + } \ + cptr = (const void *)_p; \ + } \ + } while (0) + +#define PREV_CHAR(cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + if (is_lo_surrogate(*_p) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p[-1])) { \ + --_p; \ + } \ + } \ + cptr = (const void *)_p; \ + } \ + } while (0) + +typedef enum { + RE_EXEC_STATE_SPLIT, + RE_EXEC_STATE_LOOKAHEAD, + RE_EXEC_STATE_NEGATIVE_LOOKAHEAD, +} REExecStateEnum; + +#if INTPTR_MAX >= INT64_MAX +#define BP_TYPE_BITS 3 +#else +#define BP_TYPE_BITS 2 +#endif + +typedef union { + uint8_t *ptr; + intptr_t val; /* for bp, the low BP_SHIFT bits store REExecStateEnum */ + struct { + uintptr_t val : sizeof(uintptr_t) * 8 - BP_TYPE_BITS; + uintptr_t type : BP_TYPE_BITS; + } bp; +} StackElem; + +typedef struct { + const uint8_t *cbuf; + const uint8_t *cbuf_end; + /* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16 */ + int cbuf_type; + int capture_count; + int stack_size_max; + BOOL is_unicode; + int interrupt_counter; + void *opaque; /* used for stack overflow check */ + + StackElem *stack_buf; + size_t stack_size; + StackElem static_stack_buf[32]; /* static stack to avoid allocation in most cases */ +} REExecContext; + +static int lre_poll_timeout(REExecContext *s) +{ + if (unlikely(--s->interrupt_counter <= 0)) { + s->interrupt_counter = INTERRUPT_COUNTER_INIT; + if (lre_check_timeout(s->opaque)) + return LRE_RET_TIMEOUT; + } + return 0; +} + +static no_inline int stack_realloc(REExecContext *s, size_t n) +{ + StackElem *new_stack; + size_t new_size; + new_size = s->stack_size * 3 / 2; + if (new_size < n) + new_size = n; + if (s->stack_buf == s->static_stack_buf) { + new_stack = lre_realloc(s->opaque, NULL, new_size * sizeof(StackElem)); + if (!new_stack) + return -1; + /* XXX: could use correct size */ + memcpy(new_stack, s->stack_buf, s->stack_size * sizeof(StackElem)); + } else { + new_stack = lre_realloc(s->opaque, s->stack_buf, new_size * sizeof(StackElem)); + if (!new_stack) + return -1; + } + s->stack_size = new_size; + s->stack_buf = new_stack; + return 0; +} + +/* return 1 if match, 0 if not match or < 0 if error. */ +static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture, + uint8_t **aux_stack, const uint8_t *pc, const uint8_t *cptr) +{ + int opcode; + int cbuf_type; + uint32_t val, c, idx; + const uint8_t *cbuf_end; + StackElem *sp, *bp, *stack_end; +#ifdef DUMP_EXEC + const uint8_t *pc_start = pc; /* TEST */ +#endif + cbuf_type = s->cbuf_type; + cbuf_end = s->cbuf_end; + + sp = s->stack_buf; + bp = s->stack_buf; + stack_end = s->stack_buf + s->stack_size; + +#define CHECK_STACK_SPACE(n) \ + if (unlikely((stack_end - sp) < (n))) { \ + size_t saved_sp = sp - s->stack_buf; \ + size_t saved_bp = bp - s->stack_buf; \ + if (stack_realloc(s, sp - s->stack_buf + (n))) \ + return LRE_RET_MEMORY_ERROR; \ + stack_end = s->stack_buf + s->stack_size; \ + sp = s->stack_buf + saved_sp; \ + bp = s->stack_buf + saved_bp; \ + } + + /* XXX: could test if the value was saved to reduce the stack size + but slower */ +#define SAVE_CAPTURE(idx, value) \ + { \ + CHECK_STACK_SPACE(2); \ + sp[0].val = idx; \ + sp[1].ptr = capture[idx]; \ + sp += 2; \ + capture[idx] = (value); \ + } + + /* avoid saving the previous value if already saved */ +#define SAVE_AUX_STACK(idx, value) \ + { \ + StackElem *sp1; \ + sp1 = sp; \ + for(;;) { \ + if (sp1 > bp) { \ + if (sp1[-2].val == -(int)(idx + 1)) \ + break; \ + sp1 -= 2; \ + } else { \ + CHECK_STACK_SPACE(2); \ + sp[0].val = -(int)(idx + 1); \ + sp[1].ptr = aux_stack[idx]; \ + sp += 2; \ + break; \ + } \ + } \ + aux_stack[idx] = (value); \ + } + + +#ifdef DUMP_EXEC + printf("%5s %5s %5s %5s %s\n", "PC", "CP", "BP", "SP", "OPCODE"); +#endif + for(;;) { + opcode = *pc++; +#ifdef DUMP_EXEC + printf("%5ld %5ld %5ld %5ld %s\n", + pc - 1 - pc_start, + cbuf_type == 0 ? cptr - s->cbuf : (cptr - s->cbuf) / 2, + bp - s->stack_buf, + sp - s->stack_buf, + reopcode_info[opcode].name); +#endif + switch(opcode) { + case REOP_match: + return 1; + no_match: + for(;;) { + REExecStateEnum type; + if (bp == s->stack_buf) + return 0; + /* undo the modifications to capture[] and aux_stack[] */ + while (sp > bp) { + intptr_t idx2 = sp[-2].val; + if (idx2 >= 0) + capture[idx2] = sp[-1].ptr; + else + aux_stack[-idx2 - 1] = sp[-1].ptr; + sp -= 2; + } + + pc = sp[-3].ptr; + cptr = sp[-2].ptr; + type = sp[-1].bp.type; + bp = s->stack_buf + sp[-1].bp.val; + sp -= 3; + if (type != RE_EXEC_STATE_LOOKAHEAD) + break; + } + break; + case REOP_lookahead_match: + /* pop all the saved states until reaching the start of + the lookahead and keep the updated captures and + variables and the corresponding undo info. */ + { + StackElem *sp1, *sp_top, *next_sp; + REExecStateEnum type; + + sp_top = sp; + for(;;) { + sp1 = sp; + sp = bp; + pc = sp[-3].ptr; + cptr = sp[-2].ptr; + type = sp[-1].bp.type; + bp = s->stack_buf + sp[-1].bp.val; + sp[-1].ptr = (void *)sp1; /* save the next value for the copy step */ + sp -= 3; + if (type == RE_EXEC_STATE_LOOKAHEAD) + break; + } + if (sp != s->stack_buf) { + /* keep the undo info if there is a saved state */ + sp1 = sp; + while (sp1 < sp_top) { + next_sp = (void *)sp1[2].ptr; + sp1 += 3; + while (sp1 < next_sp) + *sp++ = *sp1++; + } + } + } + break; + case REOP_negative_lookahead_match: + /* pop all the saved states until reaching start of the negative lookahead */ + for(;;) { + REExecStateEnum type; + type = bp[-1].bp.type; + /* undo the modifications to capture[] and aux_stack[] */ + while (sp > bp) { + intptr_t idx2 = sp[-2].val; + if (idx2 >= 0) + capture[idx2] = sp[-1].ptr; + else + aux_stack[-idx2 - 1] = sp[-1].ptr; + sp -= 2; + } + pc = sp[-3].ptr; + cptr = sp[-2].ptr; + type = sp[-1].bp.type; + bp = s->stack_buf + sp[-1].bp.val; + sp -= 3; + if (type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD) + break; + } + goto no_match; + case REOP_char32: + case REOP_char32_i: + val = get_u32(pc); + pc += 4; + goto test_char; + case REOP_char: + case REOP_char_i: + val = get_u16(pc); + pc += 2; + test_char: + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_char_i || opcode == REOP_char32_i) { + c = lre_canonicalize(c, s->is_unicode); + } + if (val != c) + goto no_match; + break; + case REOP_split_goto_first: + case REOP_split_next_first: + { + const uint8_t *pc1; + + val = get_u32(pc); + pc += 4; + if (opcode == REOP_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + CHECK_STACK_SPACE(3); + sp[0].ptr = (uint8_t *)pc1; + sp[1].ptr = (uint8_t *)cptr; + sp[2].bp.val = bp - s->stack_buf; + sp[2].bp.type = RE_EXEC_STATE_SPLIT; + sp += 3; + bp = sp; + } + break; + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(pc); + pc += 4; + if (opcode == REOP_lookahead && bp != s->stack_buf && 0) { + int i; + /* save all the capture state so that they can be + restored in case of failure after the lookahead + matches */ + idx = 4 * s->capture_count; + CHECK_STACK_SPACE(idx); + for(i = 0; i < 2 * s->capture_count; i++) { + sp[0].val = i; + sp[1].ptr = capture[i]; + sp += 2; + } + } + CHECK_STACK_SPACE(3); + sp[0].ptr = (uint8_t *)(pc + (int)val); + sp[1].ptr = (uint8_t *)cptr; + sp[2].bp.val = bp - s->stack_buf; + sp[2].bp.type = RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead; + sp += 3; + bp = sp; + break; + case REOP_goto: + val = get_u32(pc); + pc += 4 + (int)val; + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + break; + case REOP_line_start: + case REOP_line_start_m: + if (cptr == s->cbuf) + break; + if (opcode == REOP_line_start) + goto no_match; + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_line_end: + case REOP_line_end_m: + if (cptr == cbuf_end) + break; + if (opcode == REOP_line_end) + goto no_match; + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_dot: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (is_line_terminator(c)) + goto no_match; + break; + case REOP_any: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + break; + case REOP_save_start: + case REOP_save_end: + val = *pc++; + assert(val < s->capture_count); + idx = 2 * val + opcode - REOP_save_start; + SAVE_CAPTURE(idx, (uint8_t *)cptr); + break; + case REOP_save_reset: + { + uint32_t val2; + val = pc[0]; + val2 = pc[1]; + pc += 2; + assert(val2 < s->capture_count); + CHECK_STACK_SPACE(2 * (val2 - val + 1)); + while (val <= val2) { + idx = 2 * val; + SAVE_CAPTURE(idx, NULL); + idx = 2 * val + 1; + SAVE_CAPTURE(idx, NULL); + val++; + } + } + break; + case REOP_push_i32: + idx = pc[0]; + val = get_u32(pc + 1); + pc += 5; + SAVE_AUX_STACK(idx, (void *)(uintptr_t)val); + break; + case REOP_loop: + { + uint32_t val2; + idx = pc[0]; + val = get_u32(pc + 1); + pc += 5; + + val2 = (uintptr_t)aux_stack[idx] - 1; + SAVE_AUX_STACK(idx, (void *)(uintptr_t)val2); + if (val2 != 0) { + pc += (int)val; + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + } + } + break; + case REOP_push_char_pos: + idx = pc[0]; + pc++; + SAVE_AUX_STACK(idx, (uint8_t *)cptr); + break; + case REOP_check_advance: + idx = pc[0]; + pc++; + if (aux_stack[idx] == cptr) + goto no_match; + break; + case REOP_word_boundary: + case REOP_word_boundary_i: + case REOP_not_word_boundary: + case REOP_not_word_boundary_i: + { + BOOL v1, v2; + int ignore_case = (opcode == REOP_word_boundary_i || opcode == REOP_not_word_boundary_i); + BOOL is_boundary = (opcode == REOP_word_boundary || opcode == REOP_word_boundary_i); + /* char before */ + if (cptr == s->cbuf) { + v1 = FALSE; + } else { + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + if (ignore_case) + c = lre_canonicalize(c, s->is_unicode); + v1 = is_word_char(c); + } + /* current char */ + if (cptr >= cbuf_end) { + v2 = FALSE; + } else { + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + if (ignore_case) + c = lre_canonicalize(c, s->is_unicode); + v2 = is_word_char(c); + } + if (v1 ^ v2 ^ is_boundary) + goto no_match; + } + break; + case REOP_back_reference: + case REOP_back_reference_i: + case REOP_backward_back_reference: + case REOP_backward_back_reference_i: + { + const uint8_t *cptr1, *cptr1_end, *cptr1_start; + uint32_t c1, c2; + + val = *pc++; + if (val >= s->capture_count) + goto no_match; + cptr1_start = capture[2 * val]; + cptr1_end = capture[2 * val + 1]; + if (!cptr1_start || !cptr1_end) + break; + if (opcode == REOP_back_reference || + opcode == REOP_back_reference_i) { + cptr1 = cptr1_start; + while (cptr1 < cptr1_end) { + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c1, cptr1, cptr1_end, cbuf_type); + GET_CHAR(c2, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_back_reference_i) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } else { + cptr1 = cptr1_end; + while (cptr1 > cptr1_start) { + if (cptr == s->cbuf) + goto no_match; + GET_PREV_CHAR(c1, cptr1, cptr1_start, cbuf_type); + GET_PREV_CHAR(c2, cptr, s->cbuf, cbuf_type); + if (opcode == REOP_backward_back_reference_i) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } + } + break; + case REOP_range: + case REOP_range_i: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_range_i) { + c = lre_canonicalize(c, s->is_unicode); + } + idx_min = 0; + low = get_u16(pc + 0 * 4); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u16(pc + idx_max * 4 + 2); + /* 0xffff in for last value means +infinity */ + if (unlikely(c >= 0xffff) && high == 0xffff) + goto range_match; + if (c > high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u16(pc + idx * 4); + high = get_u16(pc + idx * 4 + 2); + if (c < low) + idx_max = idx - 1; + else if (c > high) + idx_min = idx + 1; + else + goto range_match; + } + goto no_match; + range_match: + pc += 4 * n; + } + break; + case REOP_range32: + case REOP_range32_i: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (opcode == REOP_range32_i) { + c = lre_canonicalize(c, s->is_unicode); + } + idx_min = 0; + low = get_u32(pc + 0 * 8); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u32(pc + idx_max * 8 + 4); + if (c > high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u32(pc + idx * 8); + high = get_u32(pc + idx * 8 + 4); + if (c < low) + idx_max = idx - 1; + else if (c > high) + idx_min = idx + 1; + else + goto range32_match; + } + goto no_match; + range32_match: + pc += 8 * n; + } + break; + case REOP_prev: + /* go to the previous char */ + if (cptr == s->cbuf) + goto no_match; + PREV_CHAR(cptr, s->cbuf, cbuf_type); + break; + default: +#ifdef DUMP_EXEC + printf("unknown opcode pc=%ld\n", pc - 1 - pc_start); +#endif + abort(); + } + } +} + +/* Return 1 if match, 0 if not match or < 0 if error (see LRE_RET_x). cindex is the + starting position of the match and must be such as 0 <= cindex <= + clen. */ +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque) +{ + REExecContext s_s, *s = &s_s; + int re_flags, i, ret; + uint8_t **aux_stack; + const uint8_t *cptr; + + re_flags = lre_get_flags(bc_buf); + s->is_unicode = (re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0; + s->capture_count = bc_buf[RE_HEADER_CAPTURE_COUNT]; + s->stack_size_max = bc_buf[RE_HEADER_STACK_SIZE]; + s->cbuf = cbuf; + s->cbuf_end = cbuf + (clen << cbuf_type); + s->cbuf_type = cbuf_type; + if (s->cbuf_type == 1 && s->is_unicode) + s->cbuf_type = 2; + s->interrupt_counter = INTERRUPT_COUNTER_INIT; + s->opaque = opaque; + + s->stack_buf = s->static_stack_buf; + s->stack_size = countof(s->static_stack_buf); + + for(i = 0; i < s->capture_count * 2; i++) + capture[i] = NULL; + aux_stack = alloca(s->stack_size_max * sizeof(aux_stack[0])); + + cptr = cbuf + (cindex << cbuf_type); + if (0 < cindex && cindex < clen && s->cbuf_type == 2) { + const uint16_t *p = (const uint16_t *)cptr; + if (is_lo_surrogate(*p) && is_hi_surrogate(p[-1])) { + cptr = (const uint8_t *)(p - 1); + } + } + + ret = lre_exec_backtrack(s, capture, aux_stack, bc_buf + RE_HEADER_LEN, + cptr); + if (s->stack_buf != s->static_stack_buf) + lre_realloc(s->opaque, s->stack_buf, 0); + return ret; +} + +int lre_get_capture_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT]; +} + +int lre_get_flags(const uint8_t *bc_buf) +{ + return get_u16(bc_buf + RE_HEADER_FLAGS); +} + +/* Return NULL if no group names. Otherwise, return a pointer to + 'capture_count - 1' zero terminated UTF-8 strings. */ +const char *lre_get_groupnames(const uint8_t *bc_buf) +{ + uint32_t re_bytecode_len; + if ((lre_get_flags(bc_buf) & LRE_FLAG_NAMED_GROUPS) == 0) + return NULL; + re_bytecode_len = get_u32(bc_buf + RE_HEADER_BYTECODE_LEN); + return (const char *)(bc_buf + RE_HEADER_LEN + re_bytecode_len); +} + +#ifdef TEST + +BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size) +{ + return FALSE; +} + +void *lre_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +int main(int argc, char **argv) +{ + int len, flags, ret, i; + uint8_t *bc; + char error_msg[64]; + uint8_t *capture[CAPTURE_COUNT_MAX * 2]; + const char *input; + int input_len, capture_count; + + if (argc < 4) { + printf("usage: %s regexp flags input\n", argv[0]); + return 1; + } + flags = atoi(argv[2]); + bc = lre_compile(&len, error_msg, sizeof(error_msg), argv[1], + strlen(argv[1]), flags, NULL); + if (!bc) { + fprintf(stderr, "error: %s\n", error_msg); + exit(1); + } + + input = argv[3]; + input_len = strlen(input); + + ret = lre_exec(capture, bc, (uint8_t *)input, 0, input_len, 0, NULL); + printf("ret=%d\n", ret); + if (ret == 1) { + capture_count = lre_get_capture_count(bc); + for(i = 0; i < 2 * capture_count; i++) { + uint8_t *ptr; + ptr = capture[i]; + printf("%d: ", i); + if (!ptr) + printf(""); + else + printf("%u", (int)(ptr - (uint8_t *)input)); + printf("\n"); + } + } + return 0; +} +#endif diff --git a/NativeScript/napi/android/quickjs/source/libregexp.h b/NativeScript/napi/android/quickjs/source/libregexp.h new file mode 100755 index 000000000..da76e4cef --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/libregexp.h @@ -0,0 +1,61 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LIBREGEXP_H +#define LIBREGEXP_H + +#include +#include + +#define LRE_FLAG_GLOBAL (1 << 0) +#define LRE_FLAG_IGNORECASE (1 << 1) +#define LRE_FLAG_MULTILINE (1 << 2) +#define LRE_FLAG_DOTALL (1 << 3) +#define LRE_FLAG_UNICODE (1 << 4) +#define LRE_FLAG_STICKY (1 << 5) +#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */ +#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ +#define LRE_FLAG_UNICODE_SETS (1 << 8) + +#define LRE_RET_MEMORY_ERROR (-1) +#define LRE_RET_TIMEOUT (-2) + +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque); +int lre_get_capture_count(const uint8_t *bc_buf); +int lre_get_flags(const uint8_t *bc_buf); +const char *lre_get_groupnames(const uint8_t *bc_buf); +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque); + +int lre_parse_escape(const uint8_t **pp, int allow_utf16); + +/* must be provided by the user, return non zero if overflow */ +int lre_check_stack_overflow(void *opaque, size_t alloca_size); +/* must be provided by the user, return non zero if time out */ +int lre_check_timeout(void *opaque); +void *lre_realloc(void *opaque, void *ptr, size_t size); + +#endif /* LIBREGEXP_H */ diff --git a/NativeScript/napi/android/quickjs/source/libunicode-table.h b/NativeScript/napi/android/quickjs/source/libunicode-table.h new file mode 100755 index 000000000..67df6b3a3 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/libunicode-table.h @@ -0,0 +1,5149 @@ +/* Compressed unicode tables */ +/* Automatically generated file - do not edit */ + +#include + +static const uint32_t case_conv_table1[378] = { + 0x00209a30, 0x00309a00, 0x005a8173, 0x00601730, + 0x006c0730, 0x006f81b3, 0x00701700, 0x007c0700, + 0x007f8100, 0x00803040, 0x009801c3, 0x00988190, + 0x00990640, 0x009c9040, 0x00a481b4, 0x00a52e40, + 0x00bc0130, 0x00bc8640, 0x00bf8170, 0x00c00100, + 0x00c08130, 0x00c10440, 0x00c30130, 0x00c38240, + 0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130, + 0x00c80130, 0x00c88240, 0x00c98130, 0x00ca0130, + 0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240, + 0x00cd0100, 0x00cd8101, 0x00ce0130, 0x00ce8130, + 0x00cf0100, 0x00cf8130, 0x00d00640, 0x00d30130, + 0x00d38240, 0x00d48130, 0x00d60240, 0x00d70130, + 0x00d78240, 0x00d88230, 0x00d98440, 0x00db8130, + 0x00dc0240, 0x00de0240, 0x00df8100, 0x00e20350, + 0x00e38350, 0x00e50350, 0x00e69040, 0x00ee8100, + 0x00ef1240, 0x00f801b4, 0x00f88350, 0x00fa0240, + 0x00fb0130, 0x00fb8130, 0x00fc2840, 0x01100130, + 0x01111240, 0x011d0131, 0x011d8240, 0x011e8130, + 0x011f0131, 0x011f8201, 0x01208240, 0x01218130, + 0x01220130, 0x01228130, 0x01230a40, 0x01280101, + 0x01288101, 0x01290101, 0x01298100, 0x012a0100, + 0x012b0200, 0x012c8100, 0x012d8100, 0x012e0101, + 0x01300100, 0x01308101, 0x01318100, 0x01320101, + 0x01328101, 0x01330101, 0x01340100, 0x01348100, + 0x01350101, 0x01358101, 0x01360101, 0x01378100, + 0x01388101, 0x01390100, 0x013a8100, 0x013e8101, + 0x01400100, 0x01410101, 0x01418100, 0x01438101, + 0x01440100, 0x01448100, 0x01450200, 0x01460100, + 0x01490100, 0x014e8101, 0x014f0101, 0x01a28173, + 0x01b80440, 0x01bb0240, 0x01bd8300, 0x01bf8130, + 0x01c30130, 0x01c40330, 0x01c60130, 0x01c70230, + 0x01c801d0, 0x01c89130, 0x01d18930, 0x01d60100, + 0x01d68300, 0x01d801d3, 0x01d89100, 0x01e10173, + 0x01e18900, 0x01e60100, 0x01e68200, 0x01e78130, + 0x01e80173, 0x01e88173, 0x01ea8173, 0x01eb0173, + 0x01eb8100, 0x01ec1840, 0x01f80173, 0x01f88173, + 0x01f90100, 0x01f98100, 0x01fa01a0, 0x01fa8173, + 0x01fb8240, 0x01fc8130, 0x01fd0240, 0x01fe8330, + 0x02001030, 0x02082030, 0x02182000, 0x02281000, + 0x02302240, 0x02453640, 0x02600130, 0x02608e40, + 0x02678100, 0x02686040, 0x0298a630, 0x02b0a600, + 0x02c381b5, 0x08502631, 0x08638131, 0x08668131, + 0x08682b00, 0x087e8300, 0x09d05011, 0x09f80610, + 0x09fc0620, 0x0e400174, 0x0e408174, 0x0e410174, + 0x0e418174, 0x0e420174, 0x0e428174, 0x0e430174, + 0x0e438180, 0x0e440180, 0x0e448240, 0x0e482b30, + 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, 0x0ec70101, + 0x0f007e40, 0x0f3f1840, 0x0f4b01b5, 0x0f4b81b6, + 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, 0x0f4d8180, + 0x0f4f0130, 0x0f506040, 0x0f800800, 0x0f840830, + 0x0f880600, 0x0f8c0630, 0x0f900800, 0x0f940830, + 0x0f980800, 0x0f9c0830, 0x0fa00600, 0x0fa40630, + 0x0fa801b0, 0x0fa88100, 0x0fa901d3, 0x0fa98100, + 0x0faa01d3, 0x0faa8100, 0x0fab01d3, 0x0fab8100, + 0x0fac8130, 0x0fad8130, 0x0fae8130, 0x0faf8130, + 0x0fb00800, 0x0fb40830, 0x0fb80200, 0x0fb90400, + 0x0fbb0201, 0x0fbc0201, 0x0fbd0201, 0x0fbe0201, + 0x0fc008b7, 0x0fc40867, 0x0fc808b8, 0x0fcc0868, + 0x0fd008b8, 0x0fd40868, 0x0fd80200, 0x0fd901b9, + 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1, 0x0fdb81d7, + 0x0fdc0230, 0x0fdd0230, 0x0fde0161, 0x0fdf0173, + 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, 0x0fe301b2, + 0x0fe381d8, 0x0fe40430, 0x0fe60162, 0x0fe80201, + 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, 0x0feb81d0, + 0x0fec0230, 0x0fed0230, 0x0ff00201, 0x0ff101d3, + 0x0ff181d3, 0x0ff201ba, 0x0ff28101, 0x0ff301b0, + 0x0ff381d3, 0x0ff40231, 0x0ff50230, 0x0ff60131, + 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, 0x0ffb01b2, + 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230, 0x0ffe0162, + 0x109301a0, 0x109501a0, 0x109581a0, 0x10990131, + 0x10a70101, 0x10b01031, 0x10b81001, 0x10c18240, + 0x125b1a31, 0x12681a01, 0x16003031, 0x16183001, + 0x16300240, 0x16310130, 0x16318130, 0x16320130, + 0x16328100, 0x16330100, 0x16338640, 0x16368130, + 0x16370130, 0x16378130, 0x16380130, 0x16390240, + 0x163a8240, 0x163f0230, 0x16406440, 0x16758440, + 0x16790240, 0x16802600, 0x16938100, 0x16968100, + 0x53202e40, 0x53401c40, 0x53910e40, 0x53993e40, + 0x53bc8440, 0x53be8130, 0x53bf0a40, 0x53c58240, + 0x53c68130, 0x53c80440, 0x53ca0101, 0x53cb1440, + 0x53d50130, 0x53d58130, 0x53d60130, 0x53d68130, + 0x53d70130, 0x53d80130, 0x53d88130, 0x53d90130, + 0x53d98131, 0x53da1040, 0x53e20131, 0x53e28130, + 0x53e30130, 0x53e38440, 0x53e58130, 0x53e60240, + 0x53e80240, 0x53eb0640, 0x53ee0130, 0x53fa8240, + 0x55a98101, 0x55b85020, 0x7d8001b2, 0x7d8081b2, + 0x7d8101b2, 0x7d8181da, 0x7d8201da, 0x7d8281b3, + 0x7d8301b3, 0x7d8981bb, 0x7d8a01bb, 0x7d8a81bb, + 0x7d8b01bc, 0x7d8b81bb, 0x7f909a31, 0x7fa09a01, + 0x82002831, 0x82142801, 0x82582431, 0x826c2401, + 0x82b80b31, 0x82be0f31, 0x82c60731, 0x82ca0231, + 0x82cb8b01, 0x82d18f01, 0x82d98701, 0x82dd8201, + 0x86403331, 0x86603301, 0x86a81631, 0x86b81601, + 0x8c502031, 0x8c602001, 0xb7202031, 0xb7302001, + 0xf4802231, 0xf4912201, +}; + +static const uint8_t case_conv_table2[378] = { + 0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04, + 0x10, 0x00, 0x8f, 0x0b, 0x00, 0x00, 0x11, 0x00, + 0x08, 0x00, 0x53, 0x4b, 0x52, 0x00, 0x53, 0x00, + 0x54, 0x00, 0x3b, 0x55, 0x56, 0x00, 0x58, 0x5a, + 0x40, 0x5f, 0x5e, 0x00, 0x47, 0x52, 0x63, 0x65, + 0x43, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, 0x6c, + 0x00, 0x6e, 0x00, 0x70, 0x00, 0x00, 0x41, 0x00, + 0x00, 0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, + 0x20, 0x36, 0x00, 0x28, 0x00, 0x24, 0x00, 0x24, + 0x25, 0x2d, 0x00, 0x13, 0x6d, 0x6f, 0x00, 0x29, + 0x27, 0x2a, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x41, + 0x1e, 0x42, 0x1f, 0x4e, 0x3c, 0x40, 0x22, 0x21, + 0x44, 0x21, 0x43, 0x26, 0x28, 0x27, 0x29, 0x23, + 0x2b, 0x4b, 0x2d, 0x46, 0x2f, 0x4c, 0x31, 0x4d, + 0x33, 0x47, 0x45, 0x99, 0x00, 0x00, 0x97, 0x91, + 0x7f, 0x80, 0x85, 0x86, 0x12, 0x82, 0x84, 0x78, + 0x79, 0x12, 0x7d, 0xa3, 0x7e, 0x7a, 0x7b, 0x8c, + 0x92, 0x98, 0xa6, 0xa0, 0x87, 0x00, 0x9a, 0xa1, + 0x95, 0x77, 0x33, 0x95, 0x00, 0x90, 0x00, 0x76, + 0x9b, 0x9a, 0x99, 0x98, 0x00, 0x00, 0xa0, 0x00, + 0x9e, 0x00, 0xa3, 0xa2, 0x15, 0x31, 0x32, 0x33, + 0xb7, 0xb8, 0x55, 0xac, 0xab, 0x12, 0x14, 0x1e, + 0x21, 0x22, 0x22, 0x2a, 0x34, 0x35, 0x00, 0xa8, + 0xa9, 0x39, 0x22, 0x4c, 0x00, 0x00, 0x97, 0x01, + 0x5a, 0xda, 0x1d, 0x36, 0x05, 0x00, 0xc7, 0xc6, + 0xc9, 0xc8, 0xcb, 0xca, 0xcd, 0xcc, 0xcf, 0xce, + 0xc4, 0xd8, 0x45, 0xd9, 0x42, 0xda, 0x46, 0xdb, + 0xd1, 0xd3, 0xd5, 0xd7, 0xdd, 0xdc, 0xf1, 0xf9, + 0x01, 0x11, 0x0a, 0x12, 0x80, 0x9f, 0x00, 0x21, + 0x80, 0xa3, 0xf0, 0x00, 0xc0, 0x40, 0xc6, 0x60, + 0xea, 0xde, 0xe6, 0x99, 0xc0, 0x00, 0x00, 0x06, + 0x60, 0xdf, 0x29, 0x00, 0x15, 0x12, 0x06, 0x16, + 0xfb, 0xe0, 0x09, 0x15, 0x12, 0x84, 0x0b, 0xc6, + 0x16, 0x02, 0xe2, 0x06, 0xc0, 0x40, 0x00, 0x46, + 0x60, 0xe1, 0xe3, 0x6d, 0x37, 0x38, 0x39, 0x18, + 0x17, 0x1a, 0x19, 0x00, 0x1d, 0x1c, 0x1f, 0x1e, + 0x00, 0x61, 0xba, 0x67, 0x45, 0x48, 0x00, 0x50, + 0x64, 0x4f, 0x51, 0x00, 0x00, 0x49, 0x00, 0x00, + 0x00, 0xa5, 0xa6, 0xa7, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x00, 0x00, 0x5c, 0x00, 0x4a, 0x00, + 0x5d, 0x57, 0x59, 0x62, 0x60, 0x72, 0x6b, 0x71, + 0x54, 0x00, 0x3e, 0x69, 0xbb, 0x00, 0x5b, 0x00, + 0x00, 0x00, 0x25, 0x00, 0x48, 0xaa, 0x8a, 0x8b, + 0x8c, 0xab, 0xac, 0x58, 0x58, 0xaf, 0x94, 0xb0, + 0x6f, 0xb2, 0x63, 0x62, 0x65, 0x64, 0x67, 0x66, + 0x6c, 0x6d, 0x6e, 0x6f, 0x68, 0x69, 0x6a, 0x6b, + 0x71, 0x70, 0x73, 0x72, 0x75, 0x74, 0x77, 0x76, + 0x79, 0x78, +}; + +static const uint16_t case_conv_ext[58] = { + 0x0399, 0x0308, 0x0301, 0x03a5, 0x0313, 0x0300, 0x0342, 0x0391, + 0x0397, 0x03a9, 0x0046, 0x0049, 0x004c, 0x0053, 0x0069, 0x0307, + 0x02bc, 0x004e, 0x004a, 0x030c, 0x0535, 0x0552, 0x0048, 0x0331, + 0x0054, 0x0057, 0x030a, 0x0059, 0x0041, 0x02be, 0x1f08, 0x1f80, + 0x1f28, 0x1f90, 0x1f68, 0x1fa0, 0x1fba, 0x0386, 0x1fb3, 0x1fca, + 0x0389, 0x1fc3, 0x03a1, 0x1ffa, 0x038f, 0x1ff3, 0x0544, 0x0546, + 0x053b, 0x054e, 0x053d, 0x03b8, 0x0462, 0xa64a, 0x1e60, 0x03c9, + 0x006b, 0x00e5, +}; + +static const uint8_t unicode_prop_Cased1_table[193] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3, + 0x80, 0x9b, 0x81, 0x8d, 0x02, 0x80, 0xe1, 0x80, + 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, 0x11, 0x03, + 0x04, 0x08, 0x01, 0x08, 0x30, 0x08, 0x01, 0x15, + 0x20, 0x00, 0x39, 0x99, 0x31, 0x9d, 0x84, 0x40, + 0x94, 0x80, 0xd6, 0x82, 0xa6, 0x80, 0x41, 0x62, + 0x80, 0xa6, 0x80, 0x4b, 0x72, 0x80, 0x4c, 0x02, + 0xf8, 0x02, 0x80, 0x8f, 0x80, 0xb0, 0x40, 0xdb, + 0x08, 0x80, 0x41, 0xd0, 0x80, 0x8c, 0x80, 0x8f, + 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28, + 0x10, 0x11, 0x02, 0x01, 0x18, 0x0b, 0x24, 0x4b, + 0x26, 0x01, 0x01, 0x86, 0xe5, 0x80, 0x60, 0x79, + 0xb6, 0x81, 0x40, 0x91, 0x81, 0xbd, 0x88, 0x94, + 0x05, 0x80, 0x98, 0x80, 0xa2, 0x00, 0x80, 0x9b, + 0x12, 0x82, 0x43, 0x34, 0xa2, 0x06, 0x80, 0x8d, + 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, 0x88, + 0x60, 0xcc, 0x44, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x07, 0x47, 0x33, 0x89, 0x80, 0x93, 0x2d, 0x41, + 0x04, 0xbd, 0x50, 0xc1, 0x99, 0x85, 0x99, 0x85, + 0x99, +}; + +static const uint8_t unicode_prop_Cased1_index[18] = { + 0xb9, 0x02, 0x80, // 002B9 at 36 + 0xa0, 0x1e, 0x40, // 01EA0 at 66 + 0x9e, 0xa6, 0x40, // 0A69E at 98 + 0xbb, 0x07, 0x01, // 107BB at 128 + 0xdb, 0xd6, 0x01, // 1D6DB at 160 + 0x8a, 0xf1, 0x01, // 1F18A at 192 (upper bound) +}; + +static const uint8_t unicode_prop_Case_Ignorable_table[764] = { + 0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, + 0xc6, 0x03, 0x00, 0x03, 0x01, 0x81, 0x41, 0xf6, + 0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40, + 0xfa, 0x86, 0x40, 0xce, 0x04, 0x80, 0xb0, 0xac, + 0x00, 0x01, 0x01, 0x00, 0xab, 0x80, 0x8a, 0x85, + 0x89, 0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, + 0x80, 0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, + 0x9d, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0x18, 0x08, + 0x97, 0x97, 0xaa, 0x82, 0xab, 0x06, 0x0c, 0x88, + 0xa8, 0xb9, 0xb6, 0x00, 0x03, 0x3b, 0x02, 0x86, + 0x89, 0x81, 0x8c, 0x80, 0x8e, 0x80, 0xb9, 0x03, + 0x1f, 0x80, 0x93, 0x81, 0x99, 0x01, 0x81, 0xb8, + 0x03, 0x0b, 0x09, 0x12, 0x80, 0x9d, 0x0a, 0x80, + 0x8a, 0x81, 0xb8, 0x03, 0x20, 0x0b, 0x80, 0x93, + 0x81, 0x95, 0x28, 0x80, 0xb9, 0x01, 0x00, 0x1f, + 0x06, 0x81, 0x8a, 0x81, 0x9d, 0x80, 0xbc, 0x80, + 0x8b, 0x80, 0xb1, 0x02, 0x80, 0xb6, 0x00, 0x14, + 0x10, 0x1e, 0x81, 0x8a, 0x81, 0x9c, 0x80, 0xb9, + 0x01, 0x05, 0x04, 0x81, 0x93, 0x81, 0x9b, 0x81, + 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81, 0x9c, 0x80, + 0xc7, 0x06, 0x10, 0x80, 0xd9, 0x01, 0x86, 0x8a, + 0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x86, 0xc8, + 0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04, + 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe5, + 0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f, + 0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd, + 0x80, 0x42, 0x5f, 0x82, 0x43, 0xb1, 0x82, 0x9c, + 0x81, 0x9d, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37, + 0x01, 0x8a, 0x10, 0x20, 0xac, 0x84, 0xb2, 0x80, + 0xc0, 0x81, 0xa1, 0x80, 0xf5, 0x13, 0x81, 0x88, + 0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00, + 0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07, + 0x9e, 0xb0, 0x83, 0xaf, 0x00, 0x20, 0x04, 0x80, + 0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, 0x08, 0x82, + 0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, 0x39, 0x81, + 0xbf, 0x85, 0xd1, 0x10, 0x8c, 0x06, 0x18, 0x28, + 0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, 0xe4, 0x41, + 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, + 0x82, 0x8c, 0x81, 0x8b, 0x27, 0x81, 0x89, 0x01, + 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, 0x8c, 0x80, + 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, 0x81, 0xf0, + 0x82, 0xfc, 0x80, 0x8e, 0x80, 0xdf, 0x9f, 0xae, + 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, 0x24, 0x80, + 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, + 0x44, 0xe1, 0x85, 0x41, 0x0d, 0x80, 0xe1, 0x18, + 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, 0x8d, 0xa1, + 0xcd, 0x80, 0x96, 0x82, 0xe6, 0x12, 0x0f, 0x02, + 0x03, 0x80, 0x98, 0x0c, 0x80, 0x40, 0x96, 0x81, + 0x99, 0x91, 0x8c, 0x80, 0xa5, 0x87, 0x98, 0x8a, + 0xad, 0x82, 0xaf, 0x01, 0x19, 0x81, 0x90, 0x80, + 0x94, 0x81, 0xc1, 0x29, 0x09, 0x81, 0x8b, 0x07, + 0x80, 0xa2, 0x80, 0x8a, 0x80, 0xb2, 0x00, 0x11, + 0x0c, 0x08, 0x80, 0x9a, 0x80, 0x8d, 0x0c, 0x08, + 0x80, 0xe3, 0x84, 0x88, 0x82, 0xf8, 0x01, 0x03, + 0x80, 0x60, 0x4f, 0x2f, 0x80, 0x40, 0x92, 0x90, + 0x42, 0x3c, 0x8f, 0x10, 0x8b, 0x8f, 0xa1, 0x01, + 0x80, 0x40, 0xa8, 0x06, 0x05, 0x80, 0x8a, 0x80, + 0xa2, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, + 0x80, 0x94, 0x82, 0x42, 0x00, 0x80, 0x40, 0xe1, + 0x80, 0x40, 0x94, 0x84, 0x44, 0x04, 0x28, 0xa9, + 0x80, 0x88, 0x42, 0x45, 0x10, 0x0c, 0x83, 0xa7, + 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x83, + 0xa5, 0x80, 0x99, 0x20, 0x80, 0x41, 0x3a, 0x81, + 0xce, 0x83, 0xc5, 0x8a, 0xb0, 0x83, 0xfa, 0x80, + 0xb5, 0x8e, 0xa8, 0x01, 0x81, 0x89, 0x82, 0xb0, + 0x19, 0x09, 0x03, 0x80, 0x89, 0x80, 0xb1, 0x82, + 0xa3, 0x20, 0x87, 0xbd, 0x80, 0x8b, 0x81, 0xb3, + 0x88, 0x89, 0x19, 0x80, 0xde, 0x11, 0x00, 0x0d, + 0x01, 0x80, 0x40, 0x9c, 0x02, 0x87, 0x94, 0x81, + 0xb8, 0x0a, 0x80, 0xa4, 0x32, 0x84, 0xc5, 0x85, + 0x8c, 0x00, 0x00, 0x80, 0x8d, 0x81, 0xd4, 0x39, + 0x10, 0x80, 0x96, 0x80, 0xd3, 0x28, 0x03, 0x08, + 0x81, 0x40, 0xed, 0x1d, 0x08, 0x81, 0x9a, 0x81, + 0xd4, 0x39, 0x00, 0x81, 0xe9, 0x00, 0x01, 0x28, + 0x80, 0xe4, 0x00, 0x01, 0x18, 0x84, 0x41, 0x02, + 0x88, 0x01, 0x40, 0xff, 0x08, 0x03, 0x80, 0x40, + 0x8f, 0x19, 0x0b, 0x80, 0x9f, 0x89, 0xa7, 0x29, + 0x1f, 0x80, 0x88, 0x29, 0x82, 0xad, 0x8c, 0x01, + 0x41, 0x95, 0x30, 0x28, 0x80, 0xd1, 0x95, 0x0e, + 0x01, 0x01, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x80, + 0xc7, 0x0a, 0x00, 0x80, 0x41, 0x5a, 0x81, 0x8a, + 0x81, 0xb3, 0x24, 0x00, 0x80, 0x96, 0x80, 0x54, + 0xd4, 0x90, 0x85, 0x8e, 0x60, 0x2c, 0xc7, 0x8b, + 0x12, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x88, 0x83, + 0x41, 0xfb, 0x82, 0xa7, 0x81, 0x41, 0xe1, 0x80, + 0xbe, 0x90, 0xbf, 0x08, 0x81, 0x60, 0x40, 0x0a, + 0x18, 0x30, 0x81, 0x4c, 0x9d, 0x08, 0x83, 0x52, + 0x5b, 0xad, 0x81, 0x96, 0x42, 0x1f, 0x82, 0x88, + 0x8f, 0x0e, 0x9d, 0x83, 0x40, 0x93, 0x82, 0x47, + 0xba, 0xb6, 0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, + 0x20, 0x8e, 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, + 0x04, 0x84, 0xbd, 0xa0, 0x80, 0x40, 0x9f, 0x8d, + 0x41, 0x6f, 0x80, 0xbc, 0x83, 0x41, 0xfa, 0x84, + 0x40, 0xfd, 0x81, 0x42, 0xdf, 0x86, 0xec, 0x87, + 0x4a, 0xae, 0x84, 0x6c, 0x0c, 0x00, 0x80, 0x9d, + 0xdf, 0xff, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_Case_Ignorable_index[72] = { + 0xbe, 0x05, 0x00, // 005BE at 32 + 0xfe, 0x07, 0x00, // 007FE at 64 + 0x52, 0x0a, 0xa0, // 00A52 at 101 + 0xc1, 0x0b, 0x00, // 00BC1 at 128 + 0x82, 0x0d, 0x00, // 00D82 at 160 + 0x3f, 0x10, 0x80, // 0103F at 196 + 0xd4, 0x17, 0x40, // 017D4 at 226 + 0xcf, 0x1a, 0x20, // 01ACF at 257 + 0xf5, 0x1c, 0x00, // 01CF5 at 288 + 0x80, 0x20, 0x00, // 02080 at 320 + 0x16, 0xa0, 0x00, // 0A016 at 352 + 0xc6, 0xa8, 0x00, // 0A8C6 at 384 + 0xc2, 0xaa, 0x60, // 0AAC2 at 419 + 0x56, 0xfe, 0x20, // 0FE56 at 449 + 0xb1, 0x07, 0x01, // 107B1 at 480 + 0x02, 0x10, 0x01, // 11002 at 512 + 0x42, 0x12, 0x41, // 11242 at 546 + 0xc4, 0x14, 0x21, // 114C4 at 577 + 0xe1, 0x19, 0x81, // 119E1 at 612 + 0x48, 0x1d, 0x01, // 11D48 at 640 + 0x44, 0x6b, 0x01, // 16B44 at 672 + 0x83, 0xd1, 0x21, // 1D183 at 705 + 0x3e, 0xe1, 0x01, // 1E13E at 736 + 0xf0, 0x01, 0x0e, // E01F0 at 768 (upper bound) +}; + +static const uint8_t unicode_prop_ID_Start_table[1133] = { + 0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03, + 0x04, 0x96, 0x80, 0x9e, 0x80, 0x41, 0xc9, 0x83, + 0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20, + 0x09, 0x18, 0x05, 0x00, 0x10, 0x00, 0x93, 0x80, + 0xd2, 0x80, 0x40, 0x8a, 0x87, 0x40, 0xa5, 0x80, + 0xa5, 0x08, 0x85, 0xa8, 0xc6, 0x9a, 0x1b, 0xac, + 0xaa, 0xa2, 0x08, 0xe2, 0x00, 0x8e, 0x0e, 0x81, + 0x89, 0x11, 0x80, 0x8f, 0x00, 0x9d, 0x9c, 0xd8, + 0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95, + 0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a, + 0x84, 0x97, 0x05, 0x90, 0xa9, 0xb9, 0xb5, 0x10, + 0x91, 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, + 0x95, 0x06, 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, + 0x08, 0x82, 0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, + 0x95, 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, + 0x92, 0x82, 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, + 0x01, 0x04, 0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, + 0x80, 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, + 0x10, 0x9d, 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, + 0x2a, 0x10, 0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, + 0x8b, 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, + 0x8f, 0x10, 0x99, 0x11, 0x01, 0x81, 0x9d, 0x03, + 0x38, 0x10, 0x96, 0x80, 0x89, 0x04, 0x10, 0x9e, + 0x08, 0x81, 0x8e, 0x81, 0x90, 0x88, 0x02, 0x80, + 0xa8, 0x08, 0x8f, 0x04, 0x17, 0x82, 0x97, 0x2c, + 0x91, 0x82, 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, + 0xaf, 0x01, 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, + 0x97, 0x00, 0x80, 0x89, 0x01, 0x88, 0x01, 0x20, + 0x80, 0x94, 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, + 0x9a, 0x84, 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, + 0x1a, 0x02, 0x0e, 0x13, 0x8c, 0x8b, 0x80, 0x90, + 0xa5, 0x00, 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, + 0x03, 0x0e, 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, + 0xa0, 0x03, 0x0e, 0x00, 0x03, 0x81, 0x8e, 0x80, + 0xb8, 0x03, 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, + 0x0d, 0x82, 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, + 0x84, 0xca, 0x82, 0x8a, 0x86, 0x91, 0x8c, 0x92, + 0x8d, 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, 0xa2, + 0x03, 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, + 0xc5, 0x89, 0x9e, 0xb0, 0x9d, 0x0c, 0x8a, 0xab, + 0x83, 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, 0x80, + 0xdc, 0xae, 0x90, 0x87, 0xb5, 0x9d, 0x8c, 0x81, + 0x89, 0xab, 0x99, 0xa3, 0xa8, 0x82, 0x89, 0xa3, + 0x81, 0x8a, 0x84, 0xaa, 0x0a, 0xa8, 0x18, 0x28, + 0x0a, 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, + 0x81, 0xa5, 0x0d, 0x0f, 0x00, 0x00, 0x00, 0x80, + 0x9e, 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, 0x13, + 0x0d, 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, + 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, + 0x0d, 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, 0x24, + 0x18, 0x90, 0xa8, 0x4a, 0x76, 0x40, 0xe4, 0x2b, + 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7, 0x30, + 0x8f, 0x96, 0x88, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98, 0x88, + 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9, 0x03, + 0x84, 0xaa, 0x80, 0xdd, 0x90, 0x9f, 0xaf, 0x8f, + 0x41, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x56, 0x8c, + 0xc2, 0xad, 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, + 0x81, 0x93, 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, + 0x88, 0x81, 0xe6, 0x81, 0xc2, 0x09, 0x00, 0x07, + 0x94, 0x8f, 0x02, 0x03, 0x80, 0x96, 0x9c, 0xb3, + 0x8d, 0xb1, 0xbd, 0x2a, 0x00, 0x81, 0x8a, 0x9b, + 0x89, 0x96, 0x98, 0x9c, 0x86, 0xae, 0x9b, 0x80, + 0x8f, 0x20, 0x89, 0x89, 0x20, 0xa8, 0x96, 0x10, + 0x87, 0x93, 0x96, 0x10, 0x82, 0xb1, 0x00, 0x11, + 0x0c, 0x08, 0x00, 0x97, 0x11, 0x8a, 0x32, 0x8b, + 0x29, 0x29, 0x85, 0x88, 0x30, 0x30, 0xaa, 0x80, + 0x8d, 0x85, 0xf2, 0x9c, 0x60, 0x2b, 0xa3, 0x8b, + 0x96, 0x83, 0xb0, 0x60, 0x21, 0x03, 0x41, 0x6d, + 0x81, 0xe9, 0xa5, 0x86, 0x8b, 0x24, 0x00, 0x89, + 0x80, 0x8c, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8b, 0xf3, 0x20, 0x40, 0x86, 0xa3, 0x99, 0x85, + 0x99, 0x8a, 0xd8, 0x15, 0x0d, 0x0d, 0x0a, 0xa2, + 0x8b, 0x80, 0x99, 0x80, 0x92, 0x01, 0x80, 0x8e, + 0x81, 0x8d, 0xa1, 0xfa, 0xc4, 0xb4, 0x41, 0x0a, + 0x9c, 0x82, 0xb0, 0xae, 0x9f, 0x8c, 0x9d, 0x84, + 0xa5, 0x89, 0x9d, 0x81, 0xa3, 0x1f, 0x04, 0xa9, + 0x40, 0x9d, 0x91, 0xa3, 0x83, 0xa3, 0x83, 0xa7, + 0x87, 0xb3, 0x8b, 0x8a, 0x80, 0x8e, 0x06, 0x01, + 0x80, 0x8a, 0x80, 0x8e, 0x06, 0x01, 0x82, 0xb3, + 0x8b, 0x41, 0x36, 0x88, 0x95, 0x89, 0x87, 0x97, + 0x28, 0xa9, 0x80, 0x88, 0xc4, 0x29, 0x00, 0xab, + 0x01, 0x10, 0x81, 0x96, 0x89, 0x96, 0x88, 0x9e, + 0xc0, 0x92, 0x01, 0x89, 0x95, 0x89, 0x99, 0xc5, + 0xb7, 0x29, 0xbf, 0x80, 0x8e, 0x18, 0x10, 0x9c, + 0xa9, 0x9c, 0x82, 0x9c, 0xa2, 0x38, 0x9b, 0x9a, + 0xb5, 0x89, 0x95, 0x89, 0x92, 0x8c, 0x91, 0xed, + 0xc8, 0xb6, 0xb2, 0x8c, 0xb2, 0x8c, 0xa3, 0xa5, + 0x9b, 0x88, 0x96, 0x40, 0xf9, 0xa9, 0x29, 0x8f, + 0x82, 0xba, 0x9c, 0x89, 0x07, 0x95, 0xa9, 0x91, + 0xad, 0x94, 0x9a, 0x96, 0x8b, 0xb4, 0xb8, 0x09, + 0x80, 0x8c, 0xac, 0x9f, 0x98, 0x99, 0xa3, 0x9c, + 0x01, 0x07, 0xa2, 0x10, 0x8b, 0xaf, 0x8d, 0x83, + 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, 0x92, + 0x81, 0xbe, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89, + 0x86, 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, + 0x04, 0x10, 0x91, 0x80, 0x8b, 0x84, 0x9d, 0x89, + 0x00, 0x08, 0x80, 0xa5, 0x00, 0x98, 0x00, 0x80, + 0xab, 0xb4, 0x91, 0x83, 0x93, 0x82, 0x9d, 0xaf, + 0x93, 0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, + 0xa3, 0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, + 0xc6, 0x9a, 0xa4, 0x86, 0x40, 0xb8, 0xab, 0xf3, + 0xbf, 0x9e, 0x39, 0x01, 0x38, 0x08, 0x97, 0x8e, + 0x00, 0x80, 0xdd, 0x39, 0xa6, 0x8f, 0x00, 0x80, + 0x9b, 0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, + 0xad, 0x92, 0x80, 0x91, 0xc8, 0x40, 0xc6, 0xa0, + 0x9e, 0x88, 0x80, 0xa4, 0x90, 0x80, 0xb0, 0x9d, + 0xef, 0x30, 0x08, 0xa5, 0x94, 0x80, 0x98, 0x28, + 0x08, 0x9f, 0x8d, 0x80, 0x41, 0x46, 0x92, 0x8e, + 0x00, 0x8c, 0x80, 0xa1, 0xfb, 0x80, 0xce, 0x43, + 0x99, 0xe5, 0xee, 0x90, 0x40, 0xc3, 0x4a, 0x4b, + 0xe0, 0x8e, 0x44, 0x2f, 0x90, 0x85, 0x98, 0x4f, + 0x9a, 0x84, 0x42, 0x46, 0x5a, 0xb8, 0x9d, 0x46, + 0xe1, 0x42, 0x38, 0x86, 0x9e, 0x90, 0xce, 0x90, + 0x9d, 0x91, 0xaf, 0x8f, 0x83, 0x9e, 0x94, 0x84, + 0x92, 0x41, 0xaf, 0xac, 0x40, 0xd2, 0xbf, 0xff, + 0xca, 0x20, 0xc1, 0x8c, 0xbf, 0x08, 0x80, 0x9b, + 0x57, 0xf7, 0x87, 0x44, 0xd5, 0xa8, 0x89, 0x60, + 0x22, 0xe6, 0x18, 0x30, 0x08, 0x41, 0x22, 0x8e, + 0x80, 0x9c, 0x11, 0x80, 0x8d, 0x1f, 0x41, 0x8b, + 0x49, 0x03, 0xea, 0x84, 0x8c, 0x82, 0x88, 0x86, + 0x89, 0x57, 0x65, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x07, 0x47, 0x33, 0x9e, 0x2d, 0x41, 0x04, 0xbd, + 0x40, 0x91, 0xac, 0x89, 0x86, 0x8f, 0x80, 0x41, + 0x40, 0x9d, 0x91, 0xab, 0x41, 0xe3, 0x9b, 0x40, + 0xe3, 0x9d, 0x08, 0x41, 0xee, 0x30, 0x18, 0x08, + 0x8e, 0x80, 0x40, 0xc4, 0xba, 0xc3, 0x30, 0x44, + 0xb3, 0x18, 0x9a, 0x01, 0x00, 0x08, 0x80, 0x89, + 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, + 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, 0x80, 0x89, + 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, 0x51, 0x43, + 0x60, 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, + 0xdd, 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, + 0x42, 0x6d, 0x49, 0xa1, 0x42, 0x1d, 0x45, 0xe1, + 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_ID_Start_index[108] = { + 0xf6, 0x03, 0x20, // 003F6 at 33 + 0xa6, 0x07, 0x00, // 007A6 at 64 + 0xa9, 0x09, 0x20, // 009A9 at 97 + 0xb1, 0x0a, 0x00, // 00AB1 at 128 + 0xba, 0x0b, 0x20, // 00BBA at 161 + 0x3b, 0x0d, 0x20, // 00D3B at 193 + 0xc7, 0x0e, 0x20, // 00EC7 at 225 + 0x49, 0x12, 0x00, // 01249 at 256 + 0x9b, 0x16, 0x00, // 0169B at 288 + 0xac, 0x19, 0x00, // 019AC at 320 + 0xc0, 0x1d, 0x80, // 01DC0 at 356 + 0x80, 0x20, 0x20, // 02080 at 385 + 0x70, 0x2d, 0x00, // 02D70 at 416 + 0x00, 0x32, 0x00, // 03200 at 448 + 0xdd, 0xa7, 0x00, // 0A7DD at 480 + 0x4c, 0xaa, 0x20, // 0AA4C at 513 + 0xc7, 0xd7, 0x20, // 0D7C7 at 545 + 0xfc, 0xfd, 0x20, // 0FDFC at 577 + 0x9d, 0x02, 0x21, // 1029D at 609 + 0x96, 0x05, 0x01, // 10596 at 640 + 0x9f, 0x08, 0x01, // 1089F at 672 + 0x49, 0x0c, 0x21, // 10C49 at 705 + 0x76, 0x10, 0x21, // 11076 at 737 + 0xa9, 0x12, 0x01, // 112A9 at 768 + 0xb0, 0x14, 0x01, // 114B0 at 800 + 0x42, 0x19, 0x41, // 11942 at 834 + 0x90, 0x1c, 0x01, // 11C90 at 864 + 0xf1, 0x2f, 0x21, // 12FF1 at 897 + 0x90, 0x6b, 0x21, // 16B90 at 929 + 0x33, 0xb1, 0x21, // 1B133 at 961 + 0x06, 0xd5, 0x01, // 1D506 at 992 + 0xc3, 0xd7, 0x01, // 1D7C3 at 1024 + 0xff, 0xe7, 0x21, // 1E7FF at 1057 + 0x63, 0xee, 0x01, // 1EE63 at 1088 + 0x5e, 0xee, 0x42, // 2EE5E at 1122 + 0xb0, 0x23, 0x03, // 323B0 at 1152 (upper bound) +}; + +static const uint8_t unicode_prop_ID_Continue1_table[695] = { + 0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47, + 0xef, 0x96, 0x80, 0x40, 0xfa, 0x84, 0x41, 0x08, + 0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, + 0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89, + 0x96, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0x8e, 0x89, + 0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02, + 0x04, 0xaa, 0x82, 0xba, 0x88, 0xa9, 0x97, 0x80, + 0xa0, 0xb5, 0x10, 0x91, 0x06, 0x89, 0x09, 0x89, + 0x90, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, + 0x80, 0x89, 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, + 0x00, 0x23, 0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, + 0x8a, 0x82, 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, + 0x09, 0x89, 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, + 0x09, 0x16, 0x82, 0x89, 0x09, 0x89, 0x91, 0x80, + 0xba, 0x22, 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, + 0x8f, 0x84, 0xb6, 0x00, 0x30, 0x10, 0x1e, 0x81, + 0x8a, 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, 0x30, + 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x10, 0x8b, + 0x83, 0xb6, 0x08, 0x30, 0x10, 0x83, 0x88, 0x80, + 0x89, 0x09, 0x89, 0x90, 0x82, 0xc5, 0x03, 0x28, + 0x00, 0x3d, 0x89, 0x09, 0xbc, 0x01, 0x86, 0x8b, + 0x38, 0x89, 0xd6, 0x01, 0x88, 0x8a, 0x30, 0x89, + 0xbd, 0x0d, 0x89, 0x8a, 0x00, 0x00, 0x03, 0x81, + 0xb0, 0x93, 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, + 0x80, 0xe3, 0x93, 0x80, 0x89, 0x8b, 0x1b, 0x10, + 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x80, 0x8e, 0x42, + 0xbe, 0x82, 0x88, 0x88, 0x43, 0x9f, 0x83, 0x9b, + 0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x9f, 0x88, + 0x01, 0x89, 0xa0, 0x10, 0x8a, 0x40, 0x8e, 0x80, + 0xf5, 0x8b, 0x83, 0x8b, 0x89, 0x89, 0xff, 0x8a, + 0xbb, 0x84, 0xb8, 0x89, 0x80, 0x9c, 0x81, 0x8a, + 0x85, 0x89, 0x95, 0x8d, 0x80, 0x8f, 0xb0, 0x84, + 0xae, 0x90, 0x8a, 0x89, 0x90, 0x88, 0x8b, 0x82, + 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d, 0xaf, 0x93, + 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10, 0x94, 0x18, + 0x28, 0x0a, 0x40, 0xc5, 0xbf, 0x42, 0x0b, 0x81, + 0xb0, 0x81, 0x92, 0x80, 0xfa, 0x8c, 0x18, 0x82, + 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, 0x80, 0xdf, + 0x9f, 0x42, 0x29, 0x85, 0xe8, 0x81, 0xdf, 0x80, + 0x60, 0x75, 0x23, 0x89, 0xc4, 0x03, 0x89, 0x9f, + 0x81, 0xcf, 0x81, 0x41, 0x0f, 0x02, 0x03, 0x80, + 0x96, 0x23, 0x80, 0xd2, 0x81, 0xb1, 0x91, 0x89, + 0x89, 0x85, 0x91, 0x8c, 0x8a, 0x9b, 0x87, 0x98, + 0x8c, 0xab, 0x83, 0xae, 0x8d, 0x8e, 0x89, 0x8a, + 0x80, 0x89, 0x89, 0xae, 0x8d, 0x8b, 0x07, 0x09, + 0x89, 0xa0, 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, + 0x80, 0xa8, 0x24, 0x81, 0x40, 0xeb, 0x38, 0x09, + 0x89, 0x60, 0x4f, 0x23, 0x80, 0x42, 0xe0, 0x8f, + 0x8f, 0x8f, 0x11, 0x97, 0x82, 0x40, 0xbf, 0x89, + 0xa4, 0x80, 0xa4, 0x80, 0x42, 0x96, 0x80, 0x40, + 0xe1, 0x80, 0x40, 0x94, 0x84, 0x41, 0x24, 0x89, + 0x45, 0x56, 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, + 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x1f, 0x89, 0x85, + 0x89, 0x9e, 0x84, 0x41, 0x3c, 0x81, 0xce, 0x83, + 0xc5, 0x8a, 0xb0, 0x83, 0xf9, 0x82, 0xb4, 0x8e, + 0x9e, 0x8a, 0x09, 0x89, 0x83, 0xac, 0x8a, 0x30, + 0xac, 0x89, 0x2a, 0xa3, 0x8d, 0x80, 0x89, 0x21, + 0xab, 0x80, 0x8b, 0x82, 0xaf, 0x8d, 0x3b, 0x80, + 0x8b, 0xd1, 0x8b, 0x28, 0x08, 0x40, 0x9c, 0x8b, + 0x84, 0x89, 0x2b, 0xb6, 0x08, 0x31, 0x09, 0x82, + 0x88, 0x80, 0x89, 0x09, 0x32, 0x84, 0xc2, 0x88, + 0x00, 0x08, 0x03, 0x04, 0x00, 0x8d, 0x81, 0xd1, + 0x91, 0x88, 0x89, 0x18, 0xd0, 0x93, 0x8b, 0x89, + 0x40, 0xd4, 0x31, 0x88, 0x9a, 0x81, 0xd1, 0x90, + 0x8e, 0x89, 0xd0, 0x8c, 0x87, 0x89, 0x85, 0x93, + 0xb8, 0x8e, 0x83, 0x89, 0x40, 0xf1, 0x8e, 0x40, + 0xa4, 0x89, 0xc5, 0x28, 0x09, 0x18, 0x00, 0x81, + 0x8b, 0x89, 0xf6, 0x31, 0x32, 0x80, 0x9b, 0x89, + 0xa7, 0x30, 0x1f, 0x80, 0x88, 0x8a, 0xad, 0x8f, + 0x41, 0x55, 0x89, 0xb4, 0x38, 0x87, 0x8f, 0x89, + 0xb7, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, + 0x30, 0x07, 0x89, 0xaf, 0x20, 0x08, 0x27, 0x89, + 0x41, 0x48, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, + 0x84, 0x8c, 0x8a, 0x54, 0xe4, 0x05, 0x8e, 0x60, + 0x2c, 0xc7, 0x9b, 0x49, 0x25, 0x89, 0xd5, 0x89, + 0xa5, 0x84, 0xba, 0x86, 0x98, 0x89, 0x42, 0x15, + 0x89, 0x41, 0xd4, 0x00, 0xb6, 0x33, 0xd0, 0x80, + 0x8a, 0x81, 0x60, 0x4c, 0xaa, 0x81, 0x50, 0x50, + 0x89, 0x42, 0x05, 0xad, 0x81, 0x96, 0x42, 0x1d, + 0x22, 0x2f, 0x39, 0x86, 0x9d, 0x83, 0x40, 0x93, + 0x82, 0x45, 0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, + 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, + 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, 0x80, + 0x40, 0x9f, 0x86, 0x88, 0x89, 0x41, 0x63, 0x80, + 0xbc, 0x8d, 0x41, 0xf1, 0x8d, 0x40, 0xf3, 0x08, + 0x89, 0x42, 0xd4, 0x86, 0xec, 0x34, 0x89, 0x52, + 0x95, 0x89, 0x6c, 0x05, 0x05, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_ID_Continue1_index[66] = { + 0xfa, 0x06, 0x00, // 006FA at 32 + 0x70, 0x09, 0x00, // 00970 at 64 + 0xf0, 0x0a, 0x40, // 00AF0 at 98 + 0x57, 0x0c, 0x00, // 00C57 at 128 + 0xf0, 0x0d, 0x60, // 00DF0 at 163 + 0xc7, 0x0f, 0x20, // 00FC7 at 193 + 0xea, 0x17, 0x40, // 017EA at 226 + 0x05, 0x1b, 0x00, // 01B05 at 256 + 0x0e, 0x20, 0x00, // 0200E at 288 + 0xa0, 0xa6, 0x20, // 0A6A0 at 321 + 0xe6, 0xa9, 0x20, // 0A9E6 at 353 + 0x10, 0xfe, 0x00, // 0FE10 at 384 + 0x40, 0x0a, 0x01, // 10A40 at 416 + 0xc3, 0x10, 0x01, // 110C3 at 448 + 0x4e, 0x13, 0x01, // 1134E at 480 + 0x41, 0x16, 0x01, // 11641 at 512 + 0x0b, 0x1a, 0x01, // 11A0B at 544 + 0xaa, 0x1d, 0x01, // 11DAA at 576 + 0x7a, 0x6d, 0x21, // 16D7A at 609 + 0x45, 0xd2, 0x21, // 1D245 at 641 + 0xaf, 0xe2, 0x01, // 1E2AF at 672 + 0xf0, 0x01, 0x0e, // E01F0 at 704 (upper bound) +}; + +#ifdef CONFIG_ALL_UNICODE + +static const uint8_t unicode_cc_table[916] = { + 0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00, + 0xe8, 0x00, 0xd8, 0x04, 0xdc, 0x01, 0xca, 0x03, + 0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03, + 0xdc, 0xc7, 0x00, 0xf0, 0xc0, 0x02, 0xdc, 0xc2, + 0x01, 0xdc, 0x80, 0xc2, 0x03, 0xdc, 0xc0, 0x00, + 0xe8, 0x01, 0xdc, 0xc0, 0x41, 0xe9, 0x00, 0xea, + 0x41, 0xe9, 0x00, 0xea, 0x00, 0xe9, 0xcc, 0xb0, + 0xe2, 0xc4, 0xb0, 0xd8, 0x00, 0xdc, 0xc3, 0x00, + 0xdc, 0xc2, 0x00, 0xde, 0x00, 0xdc, 0xc5, 0x05, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xde, 0x00, + 0xe4, 0xc0, 0x49, 0x0a, 0x43, 0x13, 0x80, 0x00, + 0x17, 0x80, 0x41, 0x18, 0x80, 0xc0, 0x00, 0xdc, + 0x80, 0x00, 0x12, 0xb0, 0x17, 0xc7, 0x42, 0x1e, + 0xaf, 0x47, 0x1b, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0x8f, 0x00, 0x23, 0xb0, + 0x34, 0xc6, 0x81, 0xc3, 0x00, 0xdc, 0xc0, 0x81, + 0xc1, 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xa2, + 0x00, 0x24, 0x9d, 0xc0, 0x00, 0xdc, 0xc1, 0x00, + 0xdc, 0xc1, 0x02, 0xdc, 0xc0, 0x01, 0xdc, 0xc0, + 0x00, 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x00, 0xdc, + 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc, + 0x97, 0xc3, 0x80, 0xc8, 0x80, 0xc2, 0x80, 0xc4, + 0xaa, 0x02, 0xdc, 0xb0, 0x0a, 0xc1, 0x02, 0xdc, + 0xc3, 0xa9, 0xc4, 0x04, 0xdc, 0xcd, 0x80, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc2, + 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00, 0xdc, 0xc1, + 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, 0x07, 0x8f, + 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xaf, 0xc0, + 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, + 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3d, + 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x4e, 0x00, + 0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, + 0x86, 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, + 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, + 0x8f, 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, + 0x3c, 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, + 0xb0, 0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, + 0x7a, 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, + 0x80, 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, + 0x41, 0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, + 0x81, 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, + 0xc1, 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, + 0x07, 0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, + 0xb2, 0x9e, 0xc2, 0xb3, 0x83, 0x01, 0x09, 0x9d, + 0x00, 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, + 0xb0, 0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, + 0xc0, 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, + 0xb0, 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, + 0xdc, 0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, + 0x80, 0x01, 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc3, 0xb0, 0x34, 0x00, 0x07, 0x8e, 0x00, + 0x09, 0xa5, 0xc0, 0x00, 0xdc, 0xc6, 0xb0, 0x05, + 0x01, 0x09, 0xb0, 0x09, 0x00, 0x07, 0x8a, 0x01, + 0x09, 0xb0, 0x12, 0x00, 0x07, 0xb0, 0x67, 0xc2, + 0x41, 0x00, 0x04, 0xdc, 0xc1, 0x03, 0xdc, 0xc0, + 0x41, 0x00, 0x05, 0x01, 0x83, 0x00, 0xdc, 0x85, + 0xc0, 0x82, 0xc1, 0xb0, 0x95, 0xc1, 0x00, 0xdc, + 0xc6, 0x00, 0xdc, 0xc1, 0x00, 0xea, 0x00, 0xd6, + 0x00, 0xdc, 0x00, 0xca, 0xe4, 0x00, 0xe8, 0x01, + 0xe4, 0x00, 0xdc, 0x00, 0xda, 0xc0, 0x00, 0xe9, + 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xb2, 0x9f, 0xc1, + 0x01, 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, 0xc0, + 0x82, 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, 0x01, + 0x01, 0x03, 0xdc, 0xc0, 0xb8, 0x03, 0xcd, 0xc2, + 0xb0, 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, 0xb1, + 0xf9, 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, 0x00, + 0xde, 0x01, 0xe0, 0xb0, 0x38, 0x01, 0x08, 0xb8, + 0x6d, 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, 0xb0, + 0x1f, 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xa4, 0x00, + 0x09, 0xb0, 0x66, 0x00, 0x09, 0x9a, 0xd1, 0xb0, + 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, 0x2e, + 0x00, 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe, 0xc0, + 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, 0xc1, + 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, 0xc5, + 0x00, 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a, 0xb2, + 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, 0x00, + 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, 0xc4, + 0xb6, 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7, 0xc0, + 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, 0xb0, + 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, 0xb0, + 0x10, 0xc4, 0xb1, 0x0c, 0xc1, 0xb0, 0x1f, 0x02, + 0xdc, 0xb0, 0x15, 0x01, 0xdc, 0xc2, 0x00, 0xdc, + 0xc0, 0x03, 0xdc, 0xb0, 0x00, 0xc0, 0x00, 0xdc, + 0xc0, 0x00, 0xdc, 0xb0, 0x8f, 0x00, 0x09, 0xa8, + 0x00, 0x09, 0x8d, 0x00, 0x09, 0xb0, 0x08, 0x00, + 0x09, 0x00, 0x07, 0xb0, 0x14, 0xc2, 0xaf, 0x01, + 0x09, 0xb0, 0x0d, 0x00, 0x07, 0xb0, 0x1b, 0x00, + 0x09, 0x88, 0x00, 0x07, 0xb0, 0x39, 0x00, 0x09, + 0x00, 0x07, 0xb0, 0x81, 0x00, 0x07, 0x00, 0x09, + 0xb0, 0x1f, 0x01, 0x07, 0x8f, 0x00, 0x09, 0x97, + 0xc6, 0x82, 0xc4, 0xb0, 0x28, 0x02, 0x09, 0xb0, + 0x40, 0x00, 0x09, 0x82, 0x00, 0x07, 0x96, 0xc0, + 0xb0, 0x32, 0x00, 0x09, 0x00, 0x07, 0xb0, 0xca, + 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, 0x00, 0x09, + 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x42, + 0x00, 0x09, 0xb0, 0xdc, 0x00, 0x09, 0x00, 0x07, + 0xb0, 0xd1, 0x01, 0x09, 0x83, 0x00, 0x07, 0xb0, + 0x6b, 0x00, 0x09, 0xb0, 0x22, 0x00, 0x09, 0x91, + 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x74, + 0x00, 0x09, 0xb0, 0xd1, 0x00, 0x07, 0x80, 0x01, + 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x78, 0x01, + 0x09, 0xb8, 0x39, 0xbb, 0x00, 0x09, 0xb8, 0x01, + 0x8f, 0x04, 0x01, 0xb0, 0x0a, 0xc6, 0xb4, 0x88, + 0x01, 0x06, 0xb8, 0x44, 0x7b, 0x00, 0x01, 0xb8, + 0x0c, 0x95, 0x01, 0xd8, 0x02, 0x01, 0x82, 0x00, + 0xe2, 0x04, 0xd8, 0x87, 0x07, 0xdc, 0x81, 0xc4, + 0x01, 0xdc, 0x9d, 0xc3, 0xb0, 0x63, 0xc2, 0xb8, + 0x05, 0x8a, 0xc6, 0x80, 0xd0, 0x81, 0xc6, 0x80, + 0xc1, 0x80, 0xc4, 0xb0, 0x33, 0xc0, 0xb0, 0x6f, + 0xc6, 0xb1, 0x46, 0xc0, 0xb0, 0x0c, 0xc3, 0xb1, + 0xcb, 0x01, 0xe8, 0x00, 0xdc, 0xc0, 0xb0, 0xcd, + 0xc0, 0x00, 0xdc, 0xb2, 0xaf, 0x06, 0xdc, 0xb0, + 0x3c, 0xc5, 0x00, 0x07, +}; + +static const uint8_t unicode_cc_index[87] = { + 0x4d, 0x03, 0x00, // 0034D at 32 + 0x97, 0x05, 0x20, // 00597 at 65 + 0xc6, 0x05, 0x00, // 005C6 at 96 + 0xe7, 0x06, 0x00, // 006E7 at 128 + 0x45, 0x07, 0x00, // 00745 at 160 + 0x9c, 0x08, 0x00, // 0089C at 192 + 0x4d, 0x09, 0x00, // 0094D at 224 + 0x3c, 0x0b, 0x00, // 00B3C at 256 + 0x3d, 0x0d, 0x00, // 00D3D at 288 + 0x36, 0x0f, 0x00, // 00F36 at 320 + 0x38, 0x10, 0x20, // 01038 at 353 + 0x3a, 0x19, 0x00, // 0193A at 384 + 0xcb, 0x1a, 0x20, // 01ACB at 417 + 0xd3, 0x1c, 0x00, // 01CD3 at 448 + 0xcf, 0x1d, 0x00, // 01DCF at 480 + 0xe2, 0x20, 0x00, // 020E2 at 512 + 0x2e, 0x30, 0x20, // 0302E at 545 + 0x2b, 0xa9, 0x20, // 0A92B at 577 + 0xed, 0xab, 0x00, // 0ABED at 608 + 0x39, 0x0a, 0x01, // 10A39 at 640 + 0x4c, 0x0f, 0x01, // 10F4C at 672 + 0x35, 0x11, 0x21, // 11135 at 705 + 0x66, 0x13, 0x01, // 11366 at 736 + 0x40, 0x16, 0x01, // 11640 at 768 + 0x47, 0x1a, 0x01, // 11A47 at 800 + 0xf0, 0x6a, 0x21, // 16AF0 at 833 + 0x8a, 0xd1, 0x01, // 1D18A at 864 + 0xec, 0xe4, 0x21, // 1E4EC at 897 + 0x4b, 0xe9, 0x01, // 1E94B at 928 (upper bound) +}; + +static const uint32_t unicode_decomp_table1[709] = { + 0x00280081, 0x002a0097, 0x002a8081, 0x002bc097, + 0x002c8115, 0x002d0097, 0x002d4081, 0x002e0097, + 0x002e4115, 0x002f0199, 0x00302016, 0x00400842, + 0x00448a42, 0x004a0442, 0x004c0096, 0x004c8117, + 0x004d0242, 0x004e4342, 0x004fc12f, 0x0050c342, + 0x005240bf, 0x00530342, 0x00550942, 0x005a0842, + 0x005e0096, 0x005e4342, 0x005fc081, 0x00680142, + 0x006bc142, 0x00710185, 0x0071c317, 0x00734844, + 0x00778344, 0x00798342, 0x007b02be, 0x007c4197, + 0x007d0142, 0x007e0444, 0x00800e42, 0x00878142, + 0x00898744, 0x00ac0483, 0x00b60317, 0x00b80283, + 0x00d00214, 0x00d10096, 0x00dd0080, 0x00de8097, + 0x00df8080, 0x00e10097, 0x00e1413e, 0x00e1c080, + 0x00e204be, 0x00ea83ae, 0x00f282ae, 0x00f401ad, + 0x00f4c12e, 0x00f54103, 0x00fc0303, 0x00fe4081, + 0x0100023e, 0x0101c0be, 0x010301be, 0x010640be, + 0x010e40be, 0x0114023e, 0x0115c0be, 0x011701be, + 0x011d8144, 0x01304144, 0x01340244, 0x01358144, + 0x01368344, 0x01388344, 0x013a8644, 0x013e0144, + 0x0161c085, 0x018882ae, 0x019d422f, 0x01b00184, + 0x01b4c084, 0x024a4084, 0x024c4084, 0x024d0084, + 0x0256042e, 0x0272c12e, 0x02770120, 0x0277c084, + 0x028cc084, 0x028d8084, 0x029641ae, 0x02978084, + 0x02d20084, 0x02d2c12e, 0x02d70120, 0x02e50084, + 0x02f281ae, 0x03120084, 0x03300084, 0x0331c122, + 0x0332812e, 0x035281ae, 0x03768084, 0x037701ae, + 0x038cc085, 0x03acc085, 0x03b7012f, 0x03c30081, + 0x03d0c084, 0x03d34084, 0x03d48084, 0x03d5c084, + 0x03d70084, 0x03da4084, 0x03dcc084, 0x03dd412e, + 0x03ddc085, 0x03de0084, 0x03de4085, 0x03e04084, + 0x03e4c084, 0x03e74084, 0x03e88084, 0x03e9c084, + 0x03eb0084, 0x03ee4084, 0x04098084, 0x043f0081, + 0x06c18484, 0x06c48084, 0x06cec184, 0x06d00120, + 0x06d0c084, 0x074b0383, 0x074cc41f, 0x074f1783, + 0x075e0081, 0x0766d283, 0x07801d44, 0x078e8942, + 0x07931844, 0x079f0d42, 0x07a58216, 0x07a68085, + 0x07a6c0be, 0x07a80d44, 0x07aea044, 0x07c00122, + 0x07c08344, 0x07c20122, 0x07c28344, 0x07c40122, + 0x07c48244, 0x07c60122, 0x07c68244, 0x07c8113e, + 0x07d08244, 0x07d20122, 0x07d28244, 0x07d40122, + 0x07d48344, 0x07d64c3e, 0x07dc4080, 0x07dc80be, + 0x07dcc080, 0x07dd00be, 0x07dd4080, 0x07dd80be, + 0x07ddc080, 0x07de00be, 0x07de4080, 0x07de80be, + 0x07dec080, 0x07df00be, 0x07df4080, 0x07e00820, + 0x07e40820, 0x07e80820, 0x07ec05be, 0x07eec080, + 0x07ef00be, 0x07ef4097, 0x07ef8080, 0x07efc117, + 0x07f0443e, 0x07f24080, 0x07f280be, 0x07f2c080, + 0x07f303be, 0x07f4c080, 0x07f582ae, 0x07f6c080, + 0x07f7433e, 0x07f8c080, 0x07f903ae, 0x07fac080, + 0x07fb013e, 0x07fb8102, 0x07fc83be, 0x07fe4080, + 0x07fe80be, 0x07fec080, 0x07ff00be, 0x07ff4080, + 0x07ff8097, 0x0800011e, 0x08008495, 0x08044081, + 0x0805c097, 0x08090081, 0x08094097, 0x08098099, + 0x080bc081, 0x080cc085, 0x080d00b1, 0x080d8085, + 0x080dc0b1, 0x080f0197, 0x0811c197, 0x0815c0b3, + 0x0817c081, 0x081c0595, 0x081ec081, 0x081f0215, + 0x0820051f, 0x08228583, 0x08254415, 0x082a0097, + 0x08400119, 0x08408081, 0x0840c0bf, 0x08414119, + 0x0841c081, 0x084240bf, 0x0842852d, 0x08454081, + 0x08458097, 0x08464295, 0x08480097, 0x08484099, + 0x08488097, 0x08490081, 0x08498080, 0x084a0081, + 0x084a8102, 0x084b0495, 0x084d421f, 0x084e4081, + 0x084ec099, 0x084f0283, 0x08514295, 0x08540119, + 0x0854809b, 0x0854c619, 0x0857c097, 0x08580081, + 0x08584097, 0x08588099, 0x0858c097, 0x08590081, + 0x08594097, 0x08598099, 0x0859c09b, 0x085a0097, + 0x085a4081, 0x085a8097, 0x085ac099, 0x085b0295, + 0x085c4097, 0x085c8099, 0x085cc097, 0x085d0081, + 0x085d4097, 0x085d8099, 0x085dc09b, 0x085e0097, + 0x085e4081, 0x085e8097, 0x085ec099, 0x085f0215, + 0x08624099, 0x0866813e, 0x086b80be, 0x087341be, + 0x088100be, 0x088240be, 0x088300be, 0x088901be, + 0x088b0085, 0x088b40b1, 0x088bc085, 0x088c00b1, + 0x089040be, 0x089100be, 0x0891c1be, 0x089801be, + 0x089b42be, 0x089d0144, 0x089e0144, 0x08a00144, + 0x08a10144, 0x08a20144, 0x08ab023e, 0x08b80244, + 0x08ba8220, 0x08ca411e, 0x0918049f, 0x091a4523, + 0x091cc097, 0x091d04a5, 0x091f452b, 0x0921c09b, + 0x092204a1, 0x09244525, 0x0926c099, 0x09270d25, + 0x092d8d1f, 0x09340d1f, 0x093a8081, 0x0a8300b3, + 0x0a9d0099, 0x0a9d4097, 0x0a9d8099, 0x0ab700be, + 0x0b1f0115, 0x0b5bc081, 0x0ba7c081, 0x0bbcc081, + 0x0bc004ad, 0x0bc244ad, 0x0bc484ad, 0x0bc6f383, + 0x0be0852d, 0x0be31d03, 0x0bf1882d, 0x0c000081, + 0x0c0d8283, 0x0c130b84, 0x0c194284, 0x0c1c0122, + 0x0c1cc122, 0x0c1d8122, 0x0c1e4122, 0x0c1f0122, + 0x0c250084, 0x0c26c123, 0x0c278084, 0x0c27c085, + 0x0c2b0b84, 0x0c314284, 0x0c340122, 0x0c34c122, + 0x0c358122, 0x0c364122, 0x0c370122, 0x0c3d0084, + 0x0c3dc220, 0x0c3f8084, 0x0c3fc085, 0x0c4c4a2d, + 0x0c51451f, 0x0c53ca9f, 0x0c5915ad, 0x0c648703, + 0x0c800741, 0x0c838089, 0x0c83c129, 0x0c8441a9, + 0x0c850089, 0x0c854129, 0x0c85c2a9, 0x0c870089, + 0x0c87408f, 0x0c87808d, 0x0c881241, 0x0c910203, + 0x0c940099, 0x0c9444a3, 0x0c968323, 0x0c98072d, + 0x0c9b84af, 0x0c9dc2a1, 0x0c9f00b5, 0x0c9f40b3, + 0x0c9f8085, 0x0ca01883, 0x0cac4223, 0x0cad4523, + 0x0cafc097, 0x0cb004a1, 0x0cb241a5, 0x0cb30097, + 0x0cb34099, 0x0cb38097, 0x0cb3c099, 0x0cb417ad, + 0x0cbfc085, 0x0cc001b3, 0x0cc0c0b1, 0x0cc100b3, + 0x0cc14131, 0x0cc1c0b5, 0x0cc200b3, 0x0cc241b1, + 0x0cc30133, 0x0cc38131, 0x0cc40085, 0x0cc440b1, + 0x0cc48133, 0x0cc50085, 0x0cc540b5, 0x0cc580b7, + 0x0cc5c0b5, 0x0cc600b1, 0x0cc64135, 0x0cc6c0b3, + 0x0cc701b1, 0x0cc7c0b3, 0x0cc800b5, 0x0cc840b3, + 0x0cc881b1, 0x0cc9422f, 0x0cca4131, 0x0ccac0b5, + 0x0ccb00b1, 0x0ccb40b3, 0x0ccb80b5, 0x0ccbc0b1, + 0x0ccc012f, 0x0ccc80b5, 0x0cccc0b3, 0x0ccd00b5, + 0x0ccd40b1, 0x0ccd80b5, 0x0ccdc085, 0x0cce02b1, + 0x0ccf40b3, 0x0ccf80b1, 0x0ccfc085, 0x0cd001b1, + 0x0cd0c0b3, 0x0cd101b1, 0x0cd1c0b5, 0x0cd200b3, + 0x0cd24085, 0x0cd280b5, 0x0cd2c085, 0x0cd30133, + 0x0cd381b1, 0x0cd440b3, 0x0cd48085, 0x0cd4c0b1, + 0x0cd500b3, 0x0cd54085, 0x0cd580b5, 0x0cd5c0b1, + 0x0cd60521, 0x0cd88525, 0x0cdb02a5, 0x0cdc4099, + 0x0cdc8117, 0x0cdd0099, 0x0cdd4197, 0x0cde0127, + 0x0cde8285, 0x0cdfc089, 0x0ce0043f, 0x0ce20099, + 0x0ce2409b, 0x0ce283bf, 0x0ce44219, 0x0ce54205, + 0x0ce6433f, 0x0ce7c131, 0x0ce84085, 0x0ce881b1, + 0x0ce94085, 0x0ce98107, 0x0cea0089, 0x0cea4097, + 0x0cea8219, 0x0ceb809d, 0x0cebc08d, 0x0cec083f, + 0x0cf00105, 0x0cf0809b, 0x0cf0c197, 0x0cf1809b, + 0x0cf1c099, 0x0cf20517, 0x0cf48099, 0x0cf4c117, + 0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099, + 0x0cf68217, 0x0cf78119, 0x0cf804a1, 0x0cfa4525, + 0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103, + 0x29dc0081, 0x29fc8195, 0x29fe0103, 0x2ad70203, + 0x2ada4081, 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, + 0x3e8aa102, 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, + 0x3ec00197, 0x3ec0c119, 0x3ec1413f, 0x3ec4c2af, + 0x3ec74184, 0x3ec804ad, 0x3eca4081, 0x3eca8304, + 0x3ecc03a0, 0x3ece02a0, 0x3ecf8084, 0x3ed00120, + 0x3ed0c120, 0x3ed184ae, 0x3ed3c085, 0x3ed4312d, + 0x3ef4cbad, 0x3efa892f, 0x3eff022d, 0x3f002f2f, + 0x3f1782a5, 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, + 0x3f3c81a5, 0x3f3d64af, 0x3f542031, 0x3f649b31, + 0x3f7c0131, 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, + 0x3f7ec0bb, 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, + 0x3f8cc315, 0x3f8e462d, 0x3f91cc03, 0x3f97c695, + 0x3f9c01af, 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, + 0x3fbd442f, 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, + 0x3fe80081, 0x3fe84f1f, 0x3ff0831f, 0x3ff2831f, + 0x3ff4831f, 0x3ff6819f, 0x3ff80783, 0x41724092, + 0x41790092, 0x41e04d83, 0x41e70f91, 0x44268192, + 0x442ac092, 0x444b8112, 0x44d2c112, 0x44e0c192, + 0x44e38092, 0x44e44092, 0x44f14212, 0x452ec212, + 0x456e8112, 0x464e0092, 0x58484412, 0x5b5a0192, + 0x73358d1f, 0x733c051f, 0x74578392, 0x746ec312, + 0x75000d1f, 0x75068d1f, 0x750d0d1f, 0x7513839f, + 0x7515891f, 0x751a0d1f, 0x75208d1f, 0x75271015, + 0x752f439f, 0x7531459f, 0x75340d1f, 0x753a8d1f, + 0x75410395, 0x7543441f, 0x7545839f, 0x75478d1f, + 0x754e0795, 0x7552839f, 0x75548d1f, 0x755b0d1f, + 0x75618d1f, 0x75680d1f, 0x756e8d1f, 0x75750d1f, + 0x757b8d1f, 0x75820d1f, 0x75888d1f, 0x758f0d1f, + 0x75958d1f, 0x759c0d1f, 0x75a28d1f, 0x75a90103, + 0x75aa089f, 0x75ae4081, 0x75ae839f, 0x75b04081, + 0x75b08c9f, 0x75b6c081, 0x75b7032d, 0x75b8889f, + 0x75bcc081, 0x75bd039f, 0x75bec081, 0x75bf0c9f, + 0x75c54081, 0x75c5832d, 0x75c7089f, 0x75cb4081, + 0x75cb839f, 0x75cd4081, 0x75cd8c9f, 0x75d3c081, + 0x75d4032d, 0x75d5889f, 0x75d9c081, 0x75da039f, + 0x75dbc081, 0x75dc0c9f, 0x75e24081, 0x75e2832d, + 0x75e4089f, 0x75e84081, 0x75e8839f, 0x75ea4081, + 0x75ea8c9f, 0x75f0c081, 0x75f1042d, 0x75f3851f, + 0x75f6051f, 0x75f8851f, 0x75fb051f, 0x75fd851f, + 0x780c049f, 0x780e419f, 0x780f059f, 0x7811c203, + 0x7812d0ad, 0x781b0103, 0x7b80022d, 0x7b814dad, + 0x7b884203, 0x7b89c081, 0x7b8a452d, 0x7b8d0403, + 0x7b908081, 0x7b91dc03, 0x7ba0052d, 0x7ba2c8ad, + 0x7ba84483, 0x7baac8ad, 0x7c400097, 0x7c404521, + 0x7c440d25, 0x7c4a8087, 0x7c4ac115, 0x7c4b4117, + 0x7c4c0d1f, 0x7c528217, 0x7c538099, 0x7c53c097, + 0x7c5a8197, 0x7c640097, 0x7c80012f, 0x7c808081, + 0x7c841603, 0x7c9004c1, 0x7c940103, 0x7efc051f, + 0xbe0001ac, 0xbe00d110, 0xbe0947ac, 0xbe0d3910, + 0xbe29872c, 0xbe2d022c, 0xbe2e3790, 0xbe49ff90, + 0xbe69bc10, +}; + +static const uint16_t unicode_decomp_table2[709] = { + 0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008, + 0x000a, 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3, + 0x00c7, 0x00cb, 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8, + 0x0108, 0x010a, 0x0073, 0x0110, 0x0112, 0x0114, 0x0120, 0x012c, + 0x0144, 0x014d, 0x0153, 0x0162, 0x0168, 0x016a, 0x0176, 0x0192, + 0x0194, 0x01a9, 0x01bb, 0x01c7, 0x01d1, 0x01d5, 0x02b9, 0x01d7, + 0x003b, 0x01d9, 0x01db, 0x00b7, 0x01e1, 0x01fc, 0x020c, 0x0218, + 0x021d, 0x0223, 0x0227, 0x03a3, 0x0233, 0x023f, 0x0242, 0x024b, + 0x024e, 0x0251, 0x025d, 0x0260, 0x0269, 0x026c, 0x026f, 0x0275, + 0x0278, 0x0281, 0x028a, 0x029c, 0x029f, 0x02a3, 0x02af, 0x02b9, + 0x02c5, 0x02c9, 0x02cd, 0x02d1, 0x02d5, 0x02e7, 0x02ed, 0x02f1, + 0x02f5, 0x02f9, 0x02fd, 0x0305, 0x0309, 0x030d, 0x0313, 0x0317, + 0x031b, 0x0323, 0x0327, 0x032b, 0x032f, 0x0335, 0x033d, 0x0341, + 0x0349, 0x034d, 0x0351, 0x0f0b, 0x0357, 0x035b, 0x035f, 0x0363, + 0x0367, 0x036b, 0x036f, 0x0373, 0x0379, 0x037d, 0x0381, 0x0385, + 0x0389, 0x038d, 0x0391, 0x0395, 0x0399, 0x039d, 0x03a1, 0x10dc, + 0x03a5, 0x03c9, 0x03cd, 0x03d9, 0x03dd, 0x03e1, 0x03ef, 0x03f1, + 0x043d, 0x044f, 0x0499, 0x04f0, 0x0502, 0x054a, 0x0564, 0x056c, + 0x0570, 0x0573, 0x059a, 0x05fa, 0x05fe, 0x0607, 0x060b, 0x0614, + 0x0618, 0x061e, 0x0622, 0x0628, 0x068e, 0x0694, 0x0698, 0x069e, + 0x06a2, 0x06ab, 0x03ac, 0x06f3, 0x03ad, 0x06f6, 0x03ae, 0x06f9, + 0x03af, 0x06fc, 0x03cc, 0x06ff, 0x03cd, 0x0702, 0x03ce, 0x0705, + 0x0709, 0x070d, 0x0711, 0x0386, 0x0732, 0x0735, 0x03b9, 0x0737, + 0x073b, 0x0388, 0x0753, 0x0389, 0x0756, 0x0390, 0x076b, 0x038a, + 0x0777, 0x03b0, 0x0789, 0x038e, 0x0799, 0x079f, 0x07a3, 0x038c, + 0x07b8, 0x038f, 0x07bb, 0x00b4, 0x07be, 0x07c0, 0x07c2, 0x2010, + 0x07cb, 0x002e, 0x07cd, 0x07cf, 0x0020, 0x07d2, 0x07d6, 0x07db, + 0x07df, 0x07e4, 0x07ea, 0x07f0, 0x0020, 0x07f6, 0x2212, 0x0801, + 0x0805, 0x0807, 0x081d, 0x0825, 0x0827, 0x0043, 0x082d, 0x0830, + 0x0190, 0x0836, 0x0839, 0x004e, 0x0845, 0x0847, 0x084c, 0x084e, + 0x0851, 0x005a, 0x03a9, 0x005a, 0x0853, 0x0857, 0x0860, 0x0069, + 0x0862, 0x0865, 0x086f, 0x0874, 0x087a, 0x087e, 0x08a2, 0x0049, + 0x08a4, 0x08a6, 0x08a9, 0x0056, 0x08ab, 0x08ad, 0x08b0, 0x08b4, + 0x0058, 0x08b6, 0x08b8, 0x08bb, 0x08c0, 0x08c2, 0x08c5, 0x0076, + 0x08c7, 0x08c9, 0x08cc, 0x08d0, 0x0078, 0x08d2, 0x08d4, 0x08d7, + 0x08db, 0x08de, 0x08e4, 0x08e7, 0x08f0, 0x08f3, 0x08f6, 0x08f9, + 0x0902, 0x0906, 0x090b, 0x090f, 0x0914, 0x0917, 0x091a, 0x0923, + 0x092c, 0x093b, 0x093e, 0x0941, 0x0944, 0x0947, 0x094a, 0x0956, + 0x095c, 0x0960, 0x0962, 0x0964, 0x0968, 0x096a, 0x0970, 0x0978, + 0x097c, 0x0980, 0x0986, 0x0989, 0x098f, 0x0991, 0x0030, 0x0993, + 0x0999, 0x099c, 0x099e, 0x09a1, 0x09a4, 0x2d61, 0x6bcd, 0x9f9f, + 0x09a6, 0x09b1, 0x09bc, 0x09c7, 0x0a95, 0x0aa1, 0x0b15, 0x0020, + 0x0b27, 0x0b31, 0x0b8d, 0x0ba1, 0x0ba5, 0x0ba9, 0x0bad, 0x0bb1, + 0x0bb5, 0x0bb9, 0x0bbd, 0x0bc1, 0x0bc5, 0x0c21, 0x0c35, 0x0c39, + 0x0c3d, 0x0c41, 0x0c45, 0x0c49, 0x0c4d, 0x0c51, 0x0c55, 0x0c59, + 0x0c6f, 0x0c71, 0x0c73, 0x0ca0, 0x0cbc, 0x0cdc, 0x0ce4, 0x0cec, + 0x0cf4, 0x0cfc, 0x0d04, 0x0d0c, 0x0d14, 0x0d22, 0x0d2e, 0x0d7a, + 0x0d82, 0x0d85, 0x0d89, 0x0d8d, 0x0d9d, 0x0db1, 0x0db5, 0x0dbc, + 0x0dc2, 0x0dc6, 0x0e28, 0x0e2c, 0x0e30, 0x0e32, 0x0e36, 0x0e3c, + 0x0e3e, 0x0e41, 0x0e43, 0x0e46, 0x0e77, 0x0e7b, 0x0e89, 0x0e8e, + 0x0e94, 0x0e9c, 0x0ea3, 0x0ea9, 0x0eb4, 0x0ebe, 0x0ec6, 0x0eca, + 0x0ecf, 0x0ed9, 0x0edd, 0x0ee4, 0x0eec, 0x0ef3, 0x0ef8, 0x0f04, + 0x0f0a, 0x0f15, 0x0f1b, 0x0f22, 0x0f28, 0x0f33, 0x0f3d, 0x0f45, + 0x0f4c, 0x0f51, 0x0f57, 0x0f5e, 0x0f63, 0x0f69, 0x0f70, 0x0f76, + 0x0f7d, 0x0f82, 0x0f89, 0x0f8d, 0x0f9e, 0x0fa4, 0x0fa9, 0x0fad, + 0x0fb8, 0x0fbe, 0x0fc9, 0x0fd0, 0x0fd6, 0x0fda, 0x0fe1, 0x0fe5, + 0x0fef, 0x0ffa, 0x1000, 0x1004, 0x1009, 0x100f, 0x1013, 0x101a, + 0x101f, 0x1023, 0x1029, 0x102f, 0x1032, 0x1036, 0x1039, 0x103f, + 0x1045, 0x1059, 0x1061, 0x1079, 0x107c, 0x1080, 0x1095, 0x10a1, + 0x10b1, 0x10c3, 0x10cb, 0x10cf, 0x10da, 0x10de, 0x10ea, 0x10f2, + 0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, 0x114d, 0x1153, + 0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, 0x1181, + 0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab, + 0xa76f, 0x11af, 0x11b2, 0x11b6, 0x028d, 0x11be, 0x1210, 0x130e, + 0x140c, 0x1490, 0x1495, 0x1553, 0x156c, 0x1572, 0x1578, 0x157e, + 0x158a, 0x1596, 0x002b, 0x15a1, 0x15b9, 0x15bd, 0x15c1, 0x15c5, + 0x15c9, 0x15cd, 0x15e1, 0x15e5, 0x1649, 0x1662, 0x1688, 0x168e, + 0x174c, 0x1752, 0x1757, 0x1777, 0x1877, 0x187d, 0x1911, 0x19d3, + 0x1a77, 0x1a7f, 0x1a9d, 0x1aa2, 0x1ab6, 0x1ac0, 0x1ac6, 0x1ada, + 0x1adf, 0x1ae5, 0x1af3, 0x1b23, 0x1b30, 0x1b38, 0x1b3c, 0x1b52, + 0x1bc9, 0x1bdb, 0x1bdd, 0x1bdf, 0x3164, 0x1c20, 0x1c22, 0x1c24, + 0x1c26, 0x1c28, 0x1c2a, 0x1c48, 0x1c4d, 0x1c52, 0x1c88, 0x1cce, + 0x1cdc, 0x1ce1, 0x1cea, 0x1cf3, 0x1d01, 0x1d06, 0x1d0b, 0x1d1d, + 0x1d2f, 0x1d38, 0x1d3d, 0x1d61, 0x1d6f, 0x1d71, 0x1d73, 0x1d93, + 0x1dae, 0x1db0, 0x1db2, 0x1db4, 0x1db6, 0x1db8, 0x1dba, 0x1dbc, + 0x1ddc, 0x1dde, 0x1de0, 0x1de2, 0x1de4, 0x1deb, 0x1ded, 0x1def, + 0x1df1, 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, 0x1e0c, + 0x1e0e, 0x1e10, 0x1e12, 0x1e14, 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, + 0x1e20, 0x03f4, 0x1e22, 0x2207, 0x1e24, 0x2202, 0x1e26, 0x1e2e, + 0x03f4, 0x1e30, 0x2207, 0x1e32, 0x2202, 0x1e34, 0x1e3c, 0x03f4, + 0x1e3e, 0x2207, 0x1e40, 0x2202, 0x1e42, 0x1e4a, 0x03f4, 0x1e4c, + 0x2207, 0x1e4e, 0x2202, 0x1e50, 0x1e58, 0x03f4, 0x1e5a, 0x2207, + 0x1e5c, 0x2202, 0x1e5e, 0x1e68, 0x1e6a, 0x1e6c, 0x1e6e, 0x1e70, + 0x1e72, 0x1e74, 0x1e76, 0x1e78, 0x1e80, 0x1ea3, 0x1ea7, 0x1ead, + 0x1eca, 0x062d, 0x1ed2, 0x1ede, 0x062c, 0x1eee, 0x1f5e, 0x1f6a, + 0x1f7d, 0x1f8f, 0x1fa2, 0x1fa4, 0x1fa8, 0x1fae, 0x1fb4, 0x1fb6, + 0x1fba, 0x1fbc, 0x1fc4, 0x1fc7, 0x1fc9, 0x1fcf, 0x1fd1, 0x30b5, + 0x1fd7, 0x202f, 0x2045, 0x2049, 0x204b, 0x2050, 0x209d, 0x20ae, + 0x21af, 0x21bf, 0x21c5, 0x22bf, 0x23dd, +}; + +static const uint8_t unicode_decomp_data[9451] = { + 0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81, + 0x20, 0xa7, 0x31, 0x6f, 0x31, 0xd0, 0x34, 0x31, + 0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41, + 0x81, 0x41, 0x82, 0x41, 0x83, 0x41, 0x88, 0x41, + 0x8a, 0x00, 0x00, 0x43, 0xa7, 0x45, 0x80, 0x45, + 0x81, 0x45, 0x82, 0x45, 0x88, 0x49, 0x80, 0x49, + 0x81, 0x49, 0x82, 0x49, 0x88, 0x00, 0x00, 0x4e, + 0x83, 0x4f, 0x80, 0x4f, 0x81, 0x4f, 0x82, 0x4f, + 0x83, 0x4f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x55, + 0x80, 0x55, 0x81, 0x55, 0x82, 0x55, 0x88, 0x59, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x61, + 0x81, 0x61, 0x82, 0x61, 0x83, 0x61, 0x88, 0x61, + 0x8a, 0x00, 0x00, 0x63, 0xa7, 0x65, 0x80, 0x65, + 0x81, 0x65, 0x82, 0x65, 0x88, 0x69, 0x80, 0x69, + 0x81, 0x69, 0x82, 0x69, 0x88, 0x00, 0x00, 0x6e, + 0x83, 0x6f, 0x80, 0x6f, 0x81, 0x6f, 0x82, 0x6f, + 0x83, 0x6f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x75, + 0x80, 0x75, 0x81, 0x75, 0x82, 0x75, 0x88, 0x79, + 0x81, 0x00, 0x00, 0x79, 0x88, 0x41, 0x84, 0x41, + 0x86, 0x41, 0xa8, 0x43, 0x81, 0x43, 0x82, 0x43, + 0x87, 0x43, 0x8c, 0x44, 0x8c, 0x45, 0x84, 0x45, + 0x86, 0x45, 0x87, 0x45, 0xa8, 0x45, 0x8c, 0x47, + 0x82, 0x47, 0x86, 0x47, 0x87, 0x47, 0xa7, 0x48, + 0x82, 0x49, 0x83, 0x49, 0x84, 0x49, 0x86, 0x49, + 0xa8, 0x49, 0x87, 0x49, 0x4a, 0x69, 0x6a, 0x4a, + 0x82, 0x4b, 0xa7, 0x4c, 0x81, 0x4c, 0xa7, 0x4c, + 0x8c, 0x4c, 0x00, 0x00, 0x6b, 0x20, 0x6b, 0x4e, + 0x81, 0x4e, 0xa7, 0x4e, 0x8c, 0xbc, 0x02, 0x6e, + 0x4f, 0x84, 0x4f, 0x86, 0x4f, 0x8b, 0x52, 0x81, + 0x52, 0xa7, 0x52, 0x8c, 0x53, 0x81, 0x53, 0x82, + 0x53, 0xa7, 0x53, 0x8c, 0x54, 0xa7, 0x54, 0x8c, + 0x55, 0x83, 0x55, 0x84, 0x55, 0x86, 0x55, 0x8a, + 0x55, 0x8b, 0x55, 0xa8, 0x57, 0x82, 0x59, 0x82, + 0x59, 0x88, 0x5a, 0x81, 0x5a, 0x87, 0x5a, 0x8c, + 0x4f, 0x9b, 0x55, 0x9b, 0x44, 0x00, 0x7d, 0x01, + 0x44, 0x00, 0x7e, 0x01, 0x64, 0x00, 0x7e, 0x01, + 0x4c, 0x4a, 0x4c, 0x6a, 0x6c, 0x6a, 0x4e, 0x4a, + 0x4e, 0x6a, 0x6e, 0x6a, 0x41, 0x00, 0x8c, 0x49, + 0x00, 0x8c, 0x4f, 0x00, 0x8c, 0x55, 0x00, 0x8c, + 0xdc, 0x00, 0x84, 0xdc, 0x00, 0x81, 0xdc, 0x00, + 0x8c, 0xdc, 0x00, 0x80, 0xc4, 0x00, 0x84, 0x26, + 0x02, 0x84, 0xc6, 0x00, 0x84, 0x47, 0x8c, 0x4b, + 0x8c, 0x4f, 0xa8, 0xea, 0x01, 0x84, 0xeb, 0x01, + 0x84, 0xb7, 0x01, 0x8c, 0x92, 0x02, 0x8c, 0x6a, + 0x00, 0x8c, 0x44, 0x5a, 0x44, 0x7a, 0x64, 0x7a, + 0x47, 0x81, 0x4e, 0x00, 0x80, 0xc5, 0x00, 0x81, + 0xc6, 0x00, 0x81, 0xd8, 0x00, 0x81, 0x41, 0x8f, + 0x41, 0x91, 0x45, 0x8f, 0x45, 0x91, 0x49, 0x8f, + 0x49, 0x91, 0x4f, 0x8f, 0x4f, 0x91, 0x52, 0x8f, + 0x52, 0x91, 0x55, 0x8f, 0x55, 0x91, 0x53, 0xa6, + 0x54, 0xa6, 0x48, 0x8c, 0x41, 0x00, 0x87, 0x45, + 0x00, 0xa7, 0xd6, 0x00, 0x84, 0xd5, 0x00, 0x84, + 0x4f, 0x00, 0x87, 0x2e, 0x02, 0x84, 0x59, 0x00, + 0x84, 0x68, 0x00, 0x66, 0x02, 0x6a, 0x00, 0x72, + 0x00, 0x79, 0x02, 0x7b, 0x02, 0x81, 0x02, 0x77, + 0x00, 0x79, 0x00, 0x20, 0x86, 0x20, 0x87, 0x20, + 0x8a, 0x20, 0xa8, 0x20, 0x83, 0x20, 0x8b, 0x63, + 0x02, 0x6c, 0x00, 0x73, 0x00, 0x78, 0x00, 0x95, + 0x02, 0x80, 0x81, 0x00, 0x93, 0x88, 0x81, 0x20, + 0xc5, 0x20, 0x81, 0xa8, 0x00, 0x81, 0x91, 0x03, + 0x81, 0x95, 0x03, 0x81, 0x97, 0x03, 0x81, 0x99, + 0x03, 0x81, 0x00, 0x00, 0x00, 0x9f, 0x03, 0x81, + 0x00, 0x00, 0x00, 0xa5, 0x03, 0x81, 0xa9, 0x03, + 0x81, 0xca, 0x03, 0x81, 0x01, 0x03, 0x98, 0x07, + 0xa4, 0x07, 0xb0, 0x00, 0xb4, 0x00, 0xb6, 0x00, + 0xb8, 0x00, 0xca, 0x00, 0x01, 0x03, 0xb8, 0x07, + 0xc4, 0x07, 0xbe, 0x00, 0xc4, 0x00, 0xc8, 0x00, + 0xa5, 0x03, 0x0d, 0x13, 0x00, 0x01, 0x03, 0xd1, + 0x00, 0xd1, 0x07, 0xc6, 0x03, 0xc0, 0x03, 0xba, + 0x03, 0xc1, 0x03, 0xc2, 0x03, 0x00, 0x00, 0x98, + 0x03, 0xb5, 0x03, 0x15, 0x04, 0x80, 0x15, 0x04, + 0x88, 0x00, 0x00, 0x00, 0x13, 0x04, 0x81, 0x06, + 0x04, 0x88, 0x1a, 0x04, 0x81, 0x18, 0x04, 0x80, + 0x23, 0x04, 0x86, 0x18, 0x04, 0x86, 0x38, 0x04, + 0x86, 0x35, 0x04, 0x80, 0x35, 0x04, 0x88, 0x00, + 0x00, 0x00, 0x33, 0x04, 0x81, 0x56, 0x04, 0x88, + 0x3a, 0x04, 0x81, 0x38, 0x04, 0x80, 0x43, 0x04, + 0x86, 0x74, 0x04, 0x8f, 0x16, 0x04, 0x86, 0x10, + 0x04, 0x86, 0x10, 0x04, 0x88, 0x15, 0x04, 0x86, + 0xd8, 0x04, 0x88, 0x16, 0x04, 0x88, 0x17, 0x04, + 0x88, 0x18, 0x04, 0x84, 0x18, 0x04, 0x88, 0x1e, + 0x04, 0x88, 0xe8, 0x04, 0x88, 0x2d, 0x04, 0x88, + 0x23, 0x04, 0x84, 0x23, 0x04, 0x88, 0x23, 0x04, + 0x8b, 0x27, 0x04, 0x88, 0x2b, 0x04, 0x88, 0x65, + 0x05, 0x82, 0x05, 0x27, 0x06, 0x00, 0x2c, 0x00, + 0x2d, 0x21, 0x2d, 0x00, 0x2e, 0x23, 0x2d, 0x27, + 0x06, 0x00, 0x4d, 0x21, 0x4d, 0xa0, 0x4d, 0x23, + 0x4d, 0xd5, 0x06, 0x54, 0x06, 0x00, 0x00, 0x00, + 0x00, 0xc1, 0x06, 0x54, 0x06, 0xd2, 0x06, 0x54, + 0x06, 0x28, 0x09, 0x3c, 0x09, 0x30, 0x09, 0x3c, + 0x09, 0x33, 0x09, 0x3c, 0x09, 0x15, 0x09, 0x00, + 0x27, 0x01, 0x27, 0x02, 0x27, 0x07, 0x27, 0x0c, + 0x27, 0x0d, 0x27, 0x16, 0x27, 0x1a, 0x27, 0xbe, + 0x09, 0x09, 0x00, 0x09, 0x19, 0xa1, 0x09, 0xbc, + 0x09, 0xaf, 0x09, 0xbc, 0x09, 0x32, 0x0a, 0x3c, + 0x0a, 0x38, 0x0a, 0x3c, 0x0a, 0x16, 0x0a, 0x00, + 0x26, 0x01, 0x26, 0x06, 0x26, 0x2b, 0x0a, 0x3c, + 0x0a, 0x47, 0x0b, 0x56, 0x0b, 0x3e, 0x0b, 0x09, + 0x00, 0x09, 0x19, 0x21, 0x0b, 0x3c, 0x0b, 0x92, + 0x0b, 0xd7, 0x0b, 0xbe, 0x0b, 0x08, 0x00, 0x09, + 0x00, 0x08, 0x19, 0x46, 0x0c, 0x56, 0x0c, 0xbf, + 0x0c, 0xd5, 0x0c, 0xc6, 0x0c, 0xd5, 0x0c, 0xc2, + 0x0c, 0x04, 0x00, 0x08, 0x13, 0x3e, 0x0d, 0x08, + 0x00, 0x09, 0x00, 0x08, 0x19, 0xd9, 0x0d, 0xca, + 0x0d, 0xca, 0x0d, 0x0f, 0x05, 0x12, 0x00, 0x0f, + 0x15, 0x4d, 0x0e, 0x32, 0x0e, 0xcd, 0x0e, 0xb2, + 0x0e, 0x99, 0x0e, 0x12, 0x00, 0x12, 0x08, 0x42, + 0x0f, 0xb7, 0x0f, 0x4c, 0x0f, 0xb7, 0x0f, 0x51, + 0x0f, 0xb7, 0x0f, 0x56, 0x0f, 0xb7, 0x0f, 0x5b, + 0x0f, 0xb7, 0x0f, 0x40, 0x0f, 0xb5, 0x0f, 0x71, + 0x0f, 0x72, 0x0f, 0x71, 0x0f, 0x00, 0x03, 0x41, + 0x0f, 0xb2, 0x0f, 0x81, 0x0f, 0xb3, 0x0f, 0x80, + 0x0f, 0xb3, 0x0f, 0x81, 0x0f, 0x71, 0x0f, 0x80, + 0x0f, 0x92, 0x0f, 0xb7, 0x0f, 0x9c, 0x0f, 0xb7, + 0x0f, 0xa1, 0x0f, 0xb7, 0x0f, 0xa6, 0x0f, 0xb7, + 0x0f, 0xab, 0x0f, 0xb7, 0x0f, 0x90, 0x0f, 0xb5, + 0x0f, 0x25, 0x10, 0x2e, 0x10, 0x05, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x09, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1b, 0x35, + 0x1b, 0x11, 0x1b, 0x35, 0x1b, 0x3a, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1b, 0x35, + 0x1b, 0x3e, 0x1b, 0x35, 0x1b, 0x42, 0x1b, 0x35, + 0x1b, 0x41, 0x00, 0xc6, 0x00, 0x42, 0x00, 0x00, + 0x00, 0x44, 0x00, 0x45, 0x00, 0x8e, 0x01, 0x47, + 0x00, 0x4f, 0x00, 0x22, 0x02, 0x50, 0x00, 0x52, + 0x00, 0x54, 0x00, 0x55, 0x00, 0x57, 0x00, 0x61, + 0x00, 0x50, 0x02, 0x51, 0x02, 0x02, 0x1d, 0x62, + 0x00, 0x64, 0x00, 0x65, 0x00, 0x59, 0x02, 0x5b, + 0x02, 0x5c, 0x02, 0x67, 0x00, 0x00, 0x00, 0x6b, + 0x00, 0x6d, 0x00, 0x4b, 0x01, 0x6f, 0x00, 0x54, + 0x02, 0x16, 0x1d, 0x17, 0x1d, 0x70, 0x00, 0x74, + 0x00, 0x75, 0x00, 0x1d, 0x1d, 0x6f, 0x02, 0x76, + 0x00, 0x25, 0x1d, 0xb2, 0x03, 0xb3, 0x03, 0xb4, + 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x69, 0x00, 0x72, + 0x00, 0x75, 0x00, 0x76, 0x00, 0xb2, 0x03, 0xb3, + 0x03, 0xc1, 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x52, + 0x02, 0x63, 0x00, 0x55, 0x02, 0xf0, 0x00, 0x5c, + 0x02, 0x66, 0x00, 0x5f, 0x02, 0x61, 0x02, 0x65, + 0x02, 0x68, 0x02, 0x69, 0x02, 0x6a, 0x02, 0x7b, + 0x1d, 0x9d, 0x02, 0x6d, 0x02, 0x85, 0x1d, 0x9f, + 0x02, 0x71, 0x02, 0x70, 0x02, 0x72, 0x02, 0x73, + 0x02, 0x74, 0x02, 0x75, 0x02, 0x78, 0x02, 0x82, + 0x02, 0x83, 0x02, 0xab, 0x01, 0x89, 0x02, 0x8a, + 0x02, 0x1c, 0x1d, 0x8b, 0x02, 0x8c, 0x02, 0x7a, + 0x00, 0x90, 0x02, 0x91, 0x02, 0x92, 0x02, 0xb8, + 0x03, 0x41, 0x00, 0xa5, 0x42, 0x00, 0x87, 0x42, + 0x00, 0xa3, 0x42, 0x00, 0xb1, 0xc7, 0x00, 0x81, + 0x44, 0x00, 0x87, 0x44, 0x00, 0xa3, 0x44, 0x00, + 0xb1, 0x44, 0x00, 0xa7, 0x44, 0x00, 0xad, 0x12, + 0x01, 0x80, 0x12, 0x01, 0x81, 0x45, 0x00, 0xad, + 0x45, 0x00, 0xb0, 0x28, 0x02, 0x86, 0x46, 0x00, + 0x87, 0x47, 0x00, 0x84, 0x48, 0x00, 0x87, 0x48, + 0x00, 0xa3, 0x48, 0x00, 0x88, 0x48, 0x00, 0xa7, + 0x48, 0x00, 0xae, 0x49, 0x00, 0xb0, 0xcf, 0x00, + 0x81, 0x4b, 0x00, 0x81, 0x4b, 0x00, 0xa3, 0x4b, + 0x00, 0xb1, 0x4c, 0x00, 0xa3, 0x36, 0x1e, 0x84, + 0x4c, 0xb1, 0x4c, 0xad, 0x4d, 0x81, 0x4d, 0x87, + 0x4d, 0xa3, 0x4e, 0x87, 0x4e, 0xa3, 0x4e, 0xb1, + 0x4e, 0xad, 0xd5, 0x00, 0x81, 0xd5, 0x00, 0x88, + 0x4c, 0x01, 0x80, 0x4c, 0x01, 0x81, 0x50, 0x00, + 0x81, 0x50, 0x00, 0x87, 0x52, 0x00, 0x87, 0x52, + 0x00, 0xa3, 0x5a, 0x1e, 0x84, 0x52, 0x00, 0xb1, + 0x53, 0x00, 0x87, 0x53, 0x00, 0xa3, 0x5a, 0x01, + 0x87, 0x60, 0x01, 0x87, 0x62, 0x1e, 0x87, 0x54, + 0x00, 0x87, 0x54, 0x00, 0xa3, 0x54, 0x00, 0xb1, + 0x54, 0x00, 0xad, 0x55, 0x00, 0xa4, 0x55, 0x00, + 0xb0, 0x55, 0x00, 0xad, 0x68, 0x01, 0x81, 0x6a, + 0x01, 0x88, 0x56, 0x83, 0x56, 0xa3, 0x57, 0x80, + 0x57, 0x81, 0x57, 0x88, 0x57, 0x87, 0x57, 0xa3, + 0x58, 0x87, 0x58, 0x88, 0x59, 0x87, 0x5a, 0x82, + 0x5a, 0xa3, 0x5a, 0xb1, 0x68, 0xb1, 0x74, 0x88, + 0x77, 0x8a, 0x79, 0x8a, 0x61, 0x00, 0xbe, 0x02, + 0x7f, 0x01, 0x87, 0x41, 0x00, 0xa3, 0x41, 0x00, + 0x89, 0xc2, 0x00, 0x81, 0xc2, 0x00, 0x80, 0xc2, + 0x00, 0x89, 0xc2, 0x00, 0x83, 0xa0, 0x1e, 0x82, + 0x02, 0x01, 0x81, 0x02, 0x01, 0x80, 0x02, 0x01, + 0x89, 0x02, 0x01, 0x83, 0xa0, 0x1e, 0x86, 0x45, + 0x00, 0xa3, 0x45, 0x00, 0x89, 0x45, 0x00, 0x83, + 0xca, 0x00, 0x81, 0xca, 0x00, 0x80, 0xca, 0x00, + 0x89, 0xca, 0x00, 0x83, 0xb8, 0x1e, 0x82, 0x49, + 0x00, 0x89, 0x49, 0x00, 0xa3, 0x4f, 0x00, 0xa3, + 0x4f, 0x00, 0x89, 0xd4, 0x00, 0x81, 0xd4, 0x00, + 0x80, 0xd4, 0x00, 0x89, 0xd4, 0x00, 0x83, 0xcc, + 0x1e, 0x82, 0xa0, 0x01, 0x81, 0xa0, 0x01, 0x80, + 0xa0, 0x01, 0x89, 0xa0, 0x01, 0x83, 0xa0, 0x01, + 0xa3, 0x55, 0x00, 0xa3, 0x55, 0x00, 0x89, 0xaf, + 0x01, 0x81, 0xaf, 0x01, 0x80, 0xaf, 0x01, 0x89, + 0xaf, 0x01, 0x83, 0xaf, 0x01, 0xa3, 0x59, 0x00, + 0x80, 0x59, 0x00, 0xa3, 0x59, 0x00, 0x89, 0x59, + 0x00, 0x83, 0xb1, 0x03, 0x13, 0x03, 0x00, 0x1f, + 0x80, 0x00, 0x1f, 0x81, 0x00, 0x1f, 0xc2, 0x91, + 0x03, 0x13, 0x03, 0x08, 0x1f, 0x80, 0x08, 0x1f, + 0x81, 0x08, 0x1f, 0xc2, 0xb5, 0x03, 0x13, 0x03, + 0x10, 0x1f, 0x80, 0x10, 0x1f, 0x81, 0x95, 0x03, + 0x13, 0x03, 0x18, 0x1f, 0x80, 0x18, 0x1f, 0x81, + 0xb7, 0x03, 0x93, 0xb7, 0x03, 0x94, 0x20, 0x1f, + 0x80, 0x21, 0x1f, 0x80, 0x20, 0x1f, 0x81, 0x21, + 0x1f, 0x81, 0x20, 0x1f, 0xc2, 0x21, 0x1f, 0xc2, + 0x97, 0x03, 0x93, 0x97, 0x03, 0x94, 0x28, 0x1f, + 0x80, 0x29, 0x1f, 0x80, 0x28, 0x1f, 0x81, 0x29, + 0x1f, 0x81, 0x28, 0x1f, 0xc2, 0x29, 0x1f, 0xc2, + 0xb9, 0x03, 0x93, 0xb9, 0x03, 0x94, 0x30, 0x1f, + 0x80, 0x31, 0x1f, 0x80, 0x30, 0x1f, 0x81, 0x31, + 0x1f, 0x81, 0x30, 0x1f, 0xc2, 0x31, 0x1f, 0xc2, + 0x99, 0x03, 0x93, 0x99, 0x03, 0x94, 0x38, 0x1f, + 0x80, 0x39, 0x1f, 0x80, 0x38, 0x1f, 0x81, 0x39, + 0x1f, 0x81, 0x38, 0x1f, 0xc2, 0x39, 0x1f, 0xc2, + 0xbf, 0x03, 0x93, 0xbf, 0x03, 0x94, 0x40, 0x1f, + 0x80, 0x40, 0x1f, 0x81, 0x9f, 0x03, 0x13, 0x03, + 0x48, 0x1f, 0x80, 0x48, 0x1f, 0x81, 0xc5, 0x03, + 0x13, 0x03, 0x50, 0x1f, 0x80, 0x50, 0x1f, 0x81, + 0x50, 0x1f, 0xc2, 0xa5, 0x03, 0x94, 0x00, 0x00, + 0x00, 0x59, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x59, + 0x1f, 0x81, 0x00, 0x00, 0x00, 0x59, 0x1f, 0xc2, + 0xc9, 0x03, 0x93, 0xc9, 0x03, 0x94, 0x60, 0x1f, + 0x80, 0x61, 0x1f, 0x80, 0x60, 0x1f, 0x81, 0x61, + 0x1f, 0x81, 0x60, 0x1f, 0xc2, 0x61, 0x1f, 0xc2, + 0xa9, 0x03, 0x93, 0xa9, 0x03, 0x94, 0x68, 0x1f, + 0x80, 0x69, 0x1f, 0x80, 0x68, 0x1f, 0x81, 0x69, + 0x1f, 0x81, 0x68, 0x1f, 0xc2, 0x69, 0x1f, 0xc2, + 0xb1, 0x03, 0x80, 0xb5, 0x03, 0x80, 0xb7, 0x03, + 0x80, 0xb9, 0x03, 0x80, 0xbf, 0x03, 0x80, 0xc5, + 0x03, 0x80, 0xc9, 0x03, 0x80, 0x00, 0x1f, 0x45, + 0x03, 0x20, 0x1f, 0x45, 0x03, 0x60, 0x1f, 0x45, + 0x03, 0xb1, 0x03, 0x86, 0xb1, 0x03, 0x84, 0x70, + 0x1f, 0xc5, 0xb1, 0x03, 0xc5, 0xac, 0x03, 0xc5, + 0x00, 0x00, 0x00, 0xb1, 0x03, 0xc2, 0xb6, 0x1f, + 0xc5, 0x91, 0x03, 0x86, 0x91, 0x03, 0x84, 0x91, + 0x03, 0x80, 0x91, 0x03, 0xc5, 0x20, 0x93, 0x20, + 0x93, 0x20, 0xc2, 0xa8, 0x00, 0xc2, 0x74, 0x1f, + 0xc5, 0xb7, 0x03, 0xc5, 0xae, 0x03, 0xc5, 0x00, + 0x00, 0x00, 0xb7, 0x03, 0xc2, 0xc6, 0x1f, 0xc5, + 0x95, 0x03, 0x80, 0x97, 0x03, 0x80, 0x97, 0x03, + 0xc5, 0xbf, 0x1f, 0x80, 0xbf, 0x1f, 0x81, 0xbf, + 0x1f, 0xc2, 0xb9, 0x03, 0x86, 0xb9, 0x03, 0x84, + 0xca, 0x03, 0x80, 0x00, 0x03, 0xb9, 0x42, 0xca, + 0x42, 0x99, 0x06, 0x99, 0x04, 0x99, 0x00, 0xfe, + 0x1f, 0x80, 0xfe, 0x1f, 0x81, 0xfe, 0x1f, 0xc2, + 0xc5, 0x03, 0x86, 0xc5, 0x03, 0x84, 0xcb, 0x03, + 0x80, 0x00, 0x03, 0xc1, 0x13, 0xc1, 0x14, 0xc5, + 0x42, 0xcb, 0x42, 0xa5, 0x06, 0xa5, 0x04, 0xa5, + 0x00, 0xa1, 0x03, 0x94, 0xa8, 0x00, 0x80, 0x85, + 0x03, 0x60, 0x00, 0x7c, 0x1f, 0xc5, 0xc9, 0x03, + 0xc5, 0xce, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xc9, + 0x03, 0xc2, 0xf6, 0x1f, 0xc5, 0x9f, 0x03, 0x80, + 0xa9, 0x03, 0x80, 0xa9, 0x03, 0xc5, 0x20, 0x94, + 0x02, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0xb3, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, + 0x00, 0x00, 0x00, 0x35, 0x20, 0x35, 0x20, 0x35, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x21, 0x00, 0x00, + 0x20, 0x85, 0x3f, 0x3f, 0x3f, 0x21, 0x21, 0x3f, + 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30, 0x69, + 0x00, 0x00, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x2b, 0x3d, 0x28, 0x29, 0x6e, 0x30, 0x00, 0x2b, + 0x00, 0x12, 0x22, 0x3d, 0x00, 0x28, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x61, 0x00, 0x65, 0x00, 0x6f, + 0x00, 0x78, 0x00, 0x59, 0x02, 0x68, 0x6b, 0x6c, + 0x6d, 0x6e, 0x70, 0x73, 0x74, 0x52, 0x73, 0x61, + 0x2f, 0x63, 0x61, 0x2f, 0x73, 0xb0, 0x00, 0x43, + 0x63, 0x2f, 0x6f, 0x63, 0x2f, 0x75, 0xb0, 0x00, + 0x46, 0x48, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, + 0xdf, 0x01, 0x01, 0x04, 0x24, 0x4e, 0x6f, 0x50, + 0x51, 0x52, 0x52, 0x52, 0x53, 0x4d, 0x54, 0x45, + 0x4c, 0x54, 0x4d, 0x4b, 0x00, 0xc5, 0x00, 0x42, + 0x43, 0x00, 0x65, 0x45, 0x46, 0x00, 0x4d, 0x6f, + 0xd0, 0x05, 0x46, 0x41, 0x58, 0xc0, 0x03, 0xb3, + 0x03, 0x93, 0x03, 0xa0, 0x03, 0x11, 0x22, 0x44, + 0x64, 0x65, 0x69, 0x6a, 0x31, 0xd0, 0x37, 0x31, + 0xd0, 0x39, 0x31, 0xd0, 0x31, 0x30, 0x31, 0xd0, + 0x33, 0x32, 0xd0, 0x33, 0x31, 0xd0, 0x35, 0x32, + 0xd0, 0x35, 0x33, 0xd0, 0x35, 0x34, 0xd0, 0x35, + 0x31, 0xd0, 0x36, 0x35, 0xd0, 0x36, 0x31, 0xd0, + 0x38, 0x33, 0xd0, 0x38, 0x35, 0xd0, 0x38, 0x37, + 0xd0, 0x38, 0x31, 0xd0, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x56, 0x56, 0x49, 0x56, 0x49, 0x49, + 0x56, 0x49, 0x49, 0x49, 0x49, 0x58, 0x58, 0x49, + 0x58, 0x49, 0x49, 0x4c, 0x43, 0x44, 0x4d, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x76, 0x76, + 0x69, 0x76, 0x69, 0x69, 0x76, 0x69, 0x69, 0x69, + 0x69, 0x78, 0x78, 0x69, 0x78, 0x69, 0x69, 0x6c, + 0x63, 0x64, 0x6d, 0x30, 0xd0, 0x33, 0x90, 0x21, + 0xb8, 0x92, 0x21, 0xb8, 0x94, 0x21, 0xb8, 0xd0, + 0x21, 0xb8, 0xd4, 0x21, 0xb8, 0xd2, 0x21, 0xb8, + 0x03, 0x22, 0xb8, 0x08, 0x22, 0xb8, 0x0b, 0x22, + 0xb8, 0x23, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x25, + 0x22, 0xb8, 0x2b, 0x22, 0x2b, 0x22, 0x2b, 0x22, + 0x00, 0x00, 0x00, 0x2e, 0x22, 0x2e, 0x22, 0x2e, + 0x22, 0x00, 0x00, 0x00, 0x3c, 0x22, 0xb8, 0x43, + 0x22, 0xb8, 0x45, 0x22, 0xb8, 0x00, 0x00, 0x00, + 0x48, 0x22, 0xb8, 0x3d, 0x00, 0xb8, 0x00, 0x00, + 0x00, 0x61, 0x22, 0xb8, 0x4d, 0x22, 0xb8, 0x3c, + 0x00, 0xb8, 0x3e, 0x00, 0xb8, 0x64, 0x22, 0xb8, + 0x65, 0x22, 0xb8, 0x72, 0x22, 0xb8, 0x76, 0x22, + 0xb8, 0x7a, 0x22, 0xb8, 0x82, 0x22, 0xb8, 0x86, + 0x22, 0xb8, 0xa2, 0x22, 0xb8, 0xa8, 0x22, 0xb8, + 0xa9, 0x22, 0xb8, 0xab, 0x22, 0xb8, 0x7c, 0x22, + 0xb8, 0x91, 0x22, 0xb8, 0xb2, 0x22, 0x38, 0x03, + 0x08, 0x30, 0x31, 0x00, 0x31, 0x00, 0x30, 0x00, + 0x32, 0x30, 0x28, 0x00, 0x31, 0x00, 0x29, 0x00, + 0x28, 0x00, 0x31, 0x00, 0x30, 0x00, 0x29, 0x00, + 0x28, 0x32, 0x30, 0x29, 0x31, 0x00, 0x2e, 0x00, + 0x31, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x32, 0x30, + 0x2e, 0x28, 0x00, 0x61, 0x00, 0x29, 0x00, 0x41, + 0x00, 0x61, 0x00, 0x2b, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0xdd, 0x2a, 0xb8, 0x6a, 0x56, 0x00, 0x4e, + 0x00, 0x28, 0x36, 0x3f, 0x59, 0x85, 0x8c, 0xa0, + 0xba, 0x3f, 0x51, 0x00, 0x26, 0x2c, 0x43, 0x57, + 0x6c, 0xa1, 0xb6, 0xc1, 0x9b, 0x52, 0x00, 0x5e, + 0x7a, 0x7f, 0x9d, 0xa6, 0xc1, 0xce, 0xe7, 0xb6, + 0x53, 0xc8, 0x53, 0xe3, 0x53, 0xd7, 0x56, 0x1f, + 0x57, 0xeb, 0x58, 0x02, 0x59, 0x0a, 0x59, 0x15, + 0x59, 0x27, 0x59, 0x73, 0x59, 0x50, 0x5b, 0x80, + 0x5b, 0xf8, 0x5b, 0x0f, 0x5c, 0x22, 0x5c, 0x38, + 0x5c, 0x6e, 0x5c, 0x71, 0x5c, 0xdb, 0x5d, 0xe5, + 0x5d, 0xf1, 0x5d, 0xfe, 0x5d, 0x72, 0x5e, 0x7a, + 0x5e, 0x7f, 0x5e, 0xf4, 0x5e, 0xfe, 0x5e, 0x0b, + 0x5f, 0x13, 0x5f, 0x50, 0x5f, 0x61, 0x5f, 0x73, + 0x5f, 0xc3, 0x5f, 0x08, 0x62, 0x36, 0x62, 0x4b, + 0x62, 0x2f, 0x65, 0x34, 0x65, 0x87, 0x65, 0x97, + 0x65, 0xa4, 0x65, 0xb9, 0x65, 0xe0, 0x65, 0xe5, + 0x65, 0xf0, 0x66, 0x08, 0x67, 0x28, 0x67, 0x20, + 0x6b, 0x62, 0x6b, 0x79, 0x6b, 0xb3, 0x6b, 0xcb, + 0x6b, 0xd4, 0x6b, 0xdb, 0x6b, 0x0f, 0x6c, 0x14, + 0x6c, 0x34, 0x6c, 0x6b, 0x70, 0x2a, 0x72, 0x36, + 0x72, 0x3b, 0x72, 0x3f, 0x72, 0x47, 0x72, 0x59, + 0x72, 0x5b, 0x72, 0xac, 0x72, 0x84, 0x73, 0x89, + 0x73, 0xdc, 0x74, 0xe6, 0x74, 0x18, 0x75, 0x1f, + 0x75, 0x28, 0x75, 0x30, 0x75, 0x8b, 0x75, 0x92, + 0x75, 0x76, 0x76, 0x7d, 0x76, 0xae, 0x76, 0xbf, + 0x76, 0xee, 0x76, 0xdb, 0x77, 0xe2, 0x77, 0xf3, + 0x77, 0x3a, 0x79, 0xb8, 0x79, 0xbe, 0x79, 0x74, + 0x7a, 0xcb, 0x7a, 0xf9, 0x7a, 0x73, 0x7c, 0xf8, + 0x7c, 0x36, 0x7f, 0x51, 0x7f, 0x8a, 0x7f, 0xbd, + 0x7f, 0x01, 0x80, 0x0c, 0x80, 0x12, 0x80, 0x33, + 0x80, 0x7f, 0x80, 0x89, 0x80, 0xe3, 0x81, 0x00, + 0x07, 0x10, 0x19, 0x29, 0x38, 0x3c, 0x8b, 0x8f, + 0x95, 0x4d, 0x86, 0x6b, 0x86, 0x40, 0x88, 0x4c, + 0x88, 0x63, 0x88, 0x7e, 0x89, 0x8b, 0x89, 0xd2, + 0x89, 0x00, 0x8a, 0x37, 0x8c, 0x46, 0x8c, 0x55, + 0x8c, 0x78, 0x8c, 0x9d, 0x8c, 0x64, 0x8d, 0x70, + 0x8d, 0xb3, 0x8d, 0xab, 0x8e, 0xca, 0x8e, 0x9b, + 0x8f, 0xb0, 0x8f, 0xb5, 0x8f, 0x91, 0x90, 0x49, + 0x91, 0xc6, 0x91, 0xcc, 0x91, 0xd1, 0x91, 0x77, + 0x95, 0x80, 0x95, 0x1c, 0x96, 0xb6, 0x96, 0xb9, + 0x96, 0xe8, 0x96, 0x51, 0x97, 0x5e, 0x97, 0x62, + 0x97, 0x69, 0x97, 0xcb, 0x97, 0xed, 0x97, 0xf3, + 0x97, 0x01, 0x98, 0xa8, 0x98, 0xdb, 0x98, 0xdf, + 0x98, 0x96, 0x99, 0x99, 0x99, 0xac, 0x99, 0xa8, + 0x9a, 0xd8, 0x9a, 0xdf, 0x9a, 0x25, 0x9b, 0x2f, + 0x9b, 0x32, 0x9b, 0x3c, 0x9b, 0x5a, 0x9b, 0xe5, + 0x9c, 0x75, 0x9e, 0x7f, 0x9e, 0xa5, 0x9e, 0x00, + 0x16, 0x1e, 0x28, 0x2c, 0x54, 0x58, 0x69, 0x6e, + 0x7b, 0x96, 0xa5, 0xad, 0xe8, 0xf7, 0xfb, 0x12, + 0x30, 0x00, 0x00, 0x41, 0x53, 0x44, 0x53, 0x45, + 0x53, 0x4b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x51, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x53, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x55, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x59, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x61, 0x30, 0x99, 0x30, 0x64, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x66, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0x99, + 0x30, 0x6f, 0x30, 0x99, 0x30, 0x72, 0x30, 0x99, + 0x30, 0x75, 0x30, 0x99, 0x30, 0x78, 0x30, 0x99, + 0x30, 0x7b, 0x30, 0x99, 0x30, 0x46, 0x30, 0x99, + 0x30, 0x20, 0x00, 0x99, 0x30, 0x9d, 0x30, 0x99, + 0x30, 0x88, 0x30, 0x8a, 0x30, 0xab, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xad, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x30, 0x99, + 0x30, 0xc4, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc6, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc8, 0x30, 0x99, 0x30, 0xcf, 0x30, 0x99, + 0x30, 0xd2, 0x30, 0x99, 0x30, 0xd5, 0x30, 0x99, + 0x30, 0xd8, 0x30, 0x99, 0x30, 0xdb, 0x30, 0x99, + 0x30, 0xa6, 0x30, 0x99, 0x30, 0xef, 0x30, 0x99, + 0x30, 0xfd, 0x30, 0x99, 0x30, 0xb3, 0x30, 0xc8, + 0x30, 0x00, 0x11, 0x00, 0x01, 0xaa, 0x02, 0xac, + 0xad, 0x03, 0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0x1a, 0x06, 0x07, 0x08, 0x21, 0x09, + 0x11, 0x61, 0x11, 0x14, 0x11, 0x4c, 0x00, 0x01, + 0xb3, 0xb4, 0xb8, 0xba, 0xbf, 0xc3, 0xc5, 0x08, + 0xc9, 0xcb, 0x09, 0x0a, 0x0c, 0x0e, 0x0f, 0x13, + 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x22, + 0x2c, 0x33, 0x38, 0xdd, 0xde, 0x43, 0x44, 0x45, + 0x70, 0x71, 0x74, 0x7d, 0x7e, 0x80, 0x8a, 0x8d, + 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, + 0x0a, 0x4e, 0x2d, 0x4e, 0x0b, 0x4e, 0x32, 0x75, + 0x59, 0x4e, 0x19, 0x4e, 0x01, 0x4e, 0x29, 0x59, + 0x30, 0x57, 0xba, 0x4e, 0x28, 0x00, 0x29, 0x00, + 0x00, 0x11, 0x02, 0x11, 0x03, 0x11, 0x05, 0x11, + 0x06, 0x11, 0x07, 0x11, 0x09, 0x11, 0x0b, 0x11, + 0x0c, 0x11, 0x0e, 0x11, 0x0f, 0x11, 0x10, 0x11, + 0x11, 0x11, 0x12, 0x11, 0x28, 0x00, 0x00, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x02, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x05, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x09, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0e, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0c, 0x11, + 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x69, 0x11, 0x0c, 0x11, 0x65, 0x11, 0xab, 0x11, + 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11, + 0x12, 0x11, 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, + 0x29, 0x00, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, + 0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e, + 0x6b, 0x51, 0x5d, 0x4e, 0x41, 0x53, 0x08, 0x67, + 0x6b, 0x70, 0x34, 0x6c, 0x28, 0x67, 0xd1, 0x91, + 0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67, + 0x3e, 0x79, 0x0d, 0x54, 0x79, 0x72, 0xa1, 0x8c, + 0x5d, 0x79, 0xb4, 0x52, 0xe3, 0x4e, 0x7c, 0x54, + 0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c, + 0x54, 0x53, 0x6d, 0x79, 0x11, 0x4f, 0xea, 0x81, + 0xf3, 0x81, 0x4f, 0x55, 0x7c, 0x5e, 0x87, 0x65, + 0x8f, 0x7b, 0x50, 0x54, 0x45, 0x32, 0x00, 0x31, + 0x00, 0x33, 0x00, 0x30, 0x00, 0x00, 0x11, 0x00, + 0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x0c, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x00, 0x11, 0x00, + 0x61, 0x02, 0x61, 0x03, 0x61, 0x05, 0x61, 0x06, + 0x61, 0x07, 0x61, 0x09, 0x61, 0x0b, 0x61, 0x0c, + 0x61, 0x0e, 0x11, 0x61, 0x11, 0x00, 0x11, 0x0e, + 0x61, 0xb7, 0x00, 0x69, 0x0b, 0x11, 0x01, 0x63, + 0x00, 0x69, 0x0b, 0x11, 0x6e, 0x11, 0x00, 0x4e, + 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, 0x94, 0x4e, + 0x6d, 0x51, 0x03, 0x4e, 0x6b, 0x51, 0x5d, 0x4e, + 0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c, + 0x28, 0x67, 0xd1, 0x91, 0x1f, 0x57, 0xe5, 0x65, + 0x2a, 0x68, 0x09, 0x67, 0x3e, 0x79, 0x0d, 0x54, + 0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52, + 0xd8, 0x79, 0x37, 0x75, 0x73, 0x59, 0x69, 0x90, + 0x2a, 0x51, 0x70, 0x53, 0xe8, 0x6c, 0x05, 0x98, + 0x11, 0x4f, 0x99, 0x51, 0x63, 0x6b, 0x0a, 0x4e, + 0x2d, 0x4e, 0x0b, 0x4e, 0xe6, 0x5d, 0xf3, 0x53, + 0x3b, 0x53, 0x97, 0x5b, 0x66, 0x5b, 0xe3, 0x76, + 0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x1c, 0x59, + 0x33, 0x00, 0x36, 0x00, 0x34, 0x00, 0x30, 0x00, + 0x35, 0x30, 0x31, 0x00, 0x08, 0x67, 0x31, 0x00, + 0x30, 0x00, 0x08, 0x67, 0x48, 0x67, 0x65, 0x72, + 0x67, 0x65, 0x56, 0x4c, 0x54, 0x44, 0xa2, 0x30, + 0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0b, 0x0d, + 0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, + 0x1f, 0x22, 0x24, 0x26, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x30, 0x33, 0x36, 0x39, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x42, 0x44, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0xe4, + 0x4e, 0x8c, 0x54, 0xa1, 0x30, 0x01, 0x30, 0x5b, + 0x27, 0x01, 0x4a, 0x34, 0x00, 0x01, 0x52, 0x39, + 0x01, 0xa2, 0x30, 0x00, 0x5a, 0x49, 0xa4, 0x30, + 0x00, 0x27, 0x4f, 0x0c, 0xa4, 0x30, 0x00, 0x4f, + 0x1d, 0x02, 0x05, 0x4f, 0xa8, 0x30, 0x00, 0x11, + 0x07, 0x54, 0x21, 0xa8, 0x30, 0x00, 0x54, 0x03, + 0x54, 0xa4, 0x30, 0x06, 0x4f, 0x15, 0x06, 0x58, + 0x3c, 0x07, 0x00, 0x46, 0xab, 0x30, 0x00, 0x3e, + 0x18, 0x1d, 0x00, 0x42, 0x3f, 0x51, 0xac, 0x30, + 0x00, 0x41, 0x47, 0x00, 0x47, 0x32, 0xae, 0x30, + 0xac, 0x30, 0xae, 0x30, 0x00, 0x1d, 0x4e, 0xad, + 0x30, 0x00, 0x38, 0x3d, 0x4f, 0x01, 0x3e, 0x13, + 0x4f, 0xad, 0x30, 0xed, 0x30, 0xad, 0x30, 0x00, + 0x40, 0x03, 0x3c, 0x33, 0xad, 0x30, 0x00, 0x40, + 0x34, 0x4f, 0x1b, 0x3e, 0xad, 0x30, 0x00, 0x40, + 0x42, 0x16, 0x1b, 0xb0, 0x30, 0x00, 0x39, 0x30, + 0xa4, 0x30, 0x0c, 0x45, 0x3c, 0x24, 0x4f, 0x0b, + 0x47, 0x18, 0x00, 0x49, 0xaf, 0x30, 0x00, 0x3e, + 0x4d, 0x1e, 0xb1, 0x30, 0x00, 0x4b, 0x08, 0x02, + 0x3a, 0x19, 0x02, 0x4b, 0x2c, 0xa4, 0x30, 0x11, + 0x00, 0x0b, 0x47, 0xb5, 0x30, 0x00, 0x3e, 0x0c, + 0x47, 0x2b, 0xb0, 0x30, 0x07, 0x3a, 0x43, 0x00, + 0xb9, 0x30, 0x02, 0x3a, 0x08, 0x02, 0x3a, 0x0f, + 0x07, 0x43, 0x00, 0xb7, 0x30, 0x10, 0x00, 0x12, + 0x34, 0x11, 0x3c, 0x13, 0x17, 0xa4, 0x30, 0x2a, + 0x1f, 0x24, 0x2b, 0x00, 0x20, 0xbb, 0x30, 0x16, + 0x41, 0x00, 0x38, 0x0d, 0xc4, 0x30, 0x0d, 0x38, + 0x00, 0xd0, 0x30, 0x00, 0x2c, 0x1c, 0x1b, 0xa2, + 0x30, 0x32, 0x00, 0x17, 0x26, 0x49, 0xaf, 0x30, + 0x25, 0x00, 0x3c, 0xb3, 0x30, 0x21, 0x00, 0x20, + 0x38, 0xa1, 0x30, 0x34, 0x00, 0x48, 0x22, 0x28, + 0xa3, 0x30, 0x32, 0x00, 0x59, 0x25, 0xa7, 0x30, + 0x2f, 0x1c, 0x10, 0x00, 0x44, 0xd5, 0x30, 0x00, + 0x14, 0x1e, 0xaf, 0x30, 0x29, 0x00, 0x10, 0x4d, + 0x3c, 0xda, 0x30, 0xbd, 0x30, 0xb8, 0x30, 0x22, + 0x13, 0x1a, 0x20, 0x33, 0x0c, 0x22, 0x3b, 0x01, + 0x22, 0x44, 0x00, 0x21, 0x44, 0x07, 0xa4, 0x30, + 0x39, 0x00, 0x4f, 0x24, 0xc8, 0x30, 0x14, 0x23, + 0x00, 0xdb, 0x30, 0xf3, 0x30, 0xc9, 0x30, 0x14, + 0x2a, 0x00, 0x12, 0x33, 0x22, 0x12, 0x33, 0x2a, + 0xa4, 0x30, 0x3a, 0x00, 0x0b, 0x49, 0xa4, 0x30, + 0x3a, 0x00, 0x47, 0x3a, 0x1f, 0x2b, 0x3a, 0x47, + 0x0b, 0xb7, 0x30, 0x27, 0x3c, 0x00, 0x30, 0x3c, + 0xaf, 0x30, 0x30, 0x00, 0x3e, 0x44, 0xdf, 0x30, + 0xea, 0x30, 0xd0, 0x30, 0x0f, 0x1a, 0x00, 0x2c, + 0x1b, 0xe1, 0x30, 0xac, 0x30, 0xac, 0x30, 0x35, + 0x00, 0x1c, 0x47, 0x35, 0x50, 0x1c, 0x3f, 0xa2, + 0x30, 0x42, 0x5a, 0x27, 0x42, 0x5a, 0x49, 0x44, + 0x00, 0x51, 0xc3, 0x30, 0x27, 0x00, 0x05, 0x28, + 0xea, 0x30, 0xe9, 0x30, 0xd4, 0x30, 0x17, 0x00, + 0x28, 0xd6, 0x30, 0x15, 0x26, 0x00, 0x15, 0xec, + 0x30, 0xe0, 0x30, 0xb2, 0x30, 0x3a, 0x41, 0x16, + 0x00, 0x41, 0xc3, 0x30, 0x2c, 0x00, 0x05, 0x30, + 0x00, 0xb9, 0x70, 0x31, 0x00, 0x30, 0x00, 0xb9, + 0x70, 0x32, 0x00, 0x30, 0x00, 0xb9, 0x70, 0x68, + 0x50, 0x61, 0x64, 0x61, 0x41, 0x55, 0x62, 0x61, + 0x72, 0x6f, 0x56, 0x70, 0x63, 0x64, 0x6d, 0x64, + 0x00, 0x6d, 0x00, 0xb2, 0x00, 0x49, 0x00, 0x55, + 0x00, 0x73, 0x5e, 0x10, 0x62, 0x2d, 0x66, 0x8c, + 0x54, 0x27, 0x59, 0x63, 0x6b, 0x0e, 0x66, 0xbb, + 0x6c, 0x2a, 0x68, 0x0f, 0x5f, 0x1a, 0x4f, 0x3e, + 0x79, 0x70, 0x00, 0x41, 0x6e, 0x00, 0x41, 0xbc, + 0x03, 0x41, 0x6d, 0x00, 0x41, 0x6b, 0x00, 0x41, + 0x4b, 0x00, 0x42, 0x4d, 0x00, 0x42, 0x47, 0x00, + 0x42, 0x63, 0x61, 0x6c, 0x6b, 0x63, 0x61, 0x6c, + 0x70, 0x00, 0x46, 0x6e, 0x00, 0x46, 0xbc, 0x03, + 0x46, 0xbc, 0x03, 0x67, 0x6d, 0x00, 0x67, 0x6b, + 0x00, 0x67, 0x48, 0x00, 0x7a, 0x6b, 0x48, 0x7a, + 0x4d, 0x48, 0x7a, 0x47, 0x48, 0x7a, 0x54, 0x48, + 0x7a, 0xbc, 0x03, 0x13, 0x21, 0x6d, 0x00, 0x13, + 0x21, 0x64, 0x00, 0x13, 0x21, 0x6b, 0x00, 0x13, + 0x21, 0x66, 0x00, 0x6d, 0x6e, 0x00, 0x6d, 0xbc, + 0x03, 0x6d, 0x6d, 0x00, 0x6d, 0x63, 0x00, 0x6d, + 0x6b, 0x00, 0x6d, 0x63, 0x00, 0x0a, 0x0a, 0x4f, + 0x00, 0x0a, 0x4f, 0x6d, 0x00, 0xb2, 0x00, 0x63, + 0x00, 0x08, 0x0a, 0x4f, 0x0a, 0x0a, 0x50, 0x00, + 0x0a, 0x50, 0x6d, 0x00, 0xb3, 0x00, 0x6b, 0x00, + 0x6d, 0x00, 0xb3, 0x00, 0x6d, 0x00, 0x15, 0x22, + 0x73, 0x00, 0x6d, 0x00, 0x15, 0x22, 0x73, 0x00, + 0xb2, 0x00, 0x50, 0x61, 0x6b, 0x50, 0x61, 0x4d, + 0x50, 0x61, 0x47, 0x50, 0x61, 0x72, 0x61, 0x64, + 0x72, 0x61, 0x64, 0xd1, 0x73, 0x72, 0x00, 0x61, + 0x00, 0x64, 0x00, 0x15, 0x22, 0x73, 0x00, 0xb2, + 0x00, 0x70, 0x00, 0x73, 0x6e, 0x00, 0x73, 0xbc, + 0x03, 0x73, 0x6d, 0x00, 0x73, 0x70, 0x00, 0x56, + 0x6e, 0x00, 0x56, 0xbc, 0x03, 0x56, 0x6d, 0x00, + 0x56, 0x6b, 0x00, 0x56, 0x4d, 0x00, 0x56, 0x70, + 0x00, 0x57, 0x6e, 0x00, 0x57, 0xbc, 0x03, 0x57, + 0x6d, 0x00, 0x57, 0x6b, 0x00, 0x57, 0x4d, 0x00, + 0x57, 0x6b, 0x00, 0xa9, 0x03, 0x4d, 0x00, 0xa9, + 0x03, 0x61, 0x2e, 0x6d, 0x2e, 0x42, 0x71, 0x63, + 0x63, 0x63, 0x64, 0x43, 0xd1, 0x6b, 0x67, 0x43, + 0x6f, 0x2e, 0x64, 0x42, 0x47, 0x79, 0x68, 0x61, + 0x48, 0x50, 0x69, 0x6e, 0x4b, 0x4b, 0x4b, 0x4d, + 0x6b, 0x74, 0x6c, 0x6d, 0x6c, 0x6e, 0x6c, 0x6f, + 0x67, 0x6c, 0x78, 0x6d, 0x62, 0x6d, 0x69, 0x6c, + 0x6d, 0x6f, 0x6c, 0x50, 0x48, 0x70, 0x2e, 0x6d, + 0x2e, 0x50, 0x50, 0x4d, 0x50, 0x52, 0x73, 0x72, + 0x53, 0x76, 0x57, 0x62, 0x56, 0xd1, 0x6d, 0x41, + 0xd1, 0x6d, 0x31, 0x00, 0xe5, 0x65, 0x31, 0x00, + 0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00, + 0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65, + 0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x43, + 0x46, 0x51, 0x26, 0x01, 0x53, 0x01, 0x27, 0xa7, + 0x37, 0xab, 0x6b, 0x02, 0x52, 0xab, 0x48, 0x8c, + 0xf4, 0x66, 0xca, 0x8e, 0xc8, 0x8c, 0xd1, 0x6e, + 0x32, 0x4e, 0xe5, 0x53, 0x9c, 0x9f, 0x9c, 0x9f, + 0x51, 0x59, 0xd1, 0x91, 0x87, 0x55, 0x48, 0x59, + 0xf6, 0x61, 0x69, 0x76, 0x85, 0x7f, 0x3f, 0x86, + 0xba, 0x87, 0xf8, 0x88, 0x8f, 0x90, 0x02, 0x6a, + 0x1b, 0x6d, 0xd9, 0x70, 0xde, 0x73, 0x3d, 0x84, + 0x6a, 0x91, 0xf1, 0x99, 0x82, 0x4e, 0x75, 0x53, + 0x04, 0x6b, 0x1b, 0x72, 0x2d, 0x86, 0x1e, 0x9e, + 0x50, 0x5d, 0xeb, 0x6f, 0xcd, 0x85, 0x64, 0x89, + 0xc9, 0x62, 0xd8, 0x81, 0x1f, 0x88, 0xca, 0x5e, + 0x17, 0x67, 0x6a, 0x6d, 0xfc, 0x72, 0xce, 0x90, + 0x86, 0x4f, 0xb7, 0x51, 0xde, 0x52, 0xc4, 0x64, + 0xd3, 0x6a, 0x10, 0x72, 0xe7, 0x76, 0x01, 0x80, + 0x06, 0x86, 0x5c, 0x86, 0xef, 0x8d, 0x32, 0x97, + 0x6f, 0x9b, 0xfa, 0x9d, 0x8c, 0x78, 0x7f, 0x79, + 0xa0, 0x7d, 0xc9, 0x83, 0x04, 0x93, 0x7f, 0x9e, + 0xd6, 0x8a, 0xdf, 0x58, 0x04, 0x5f, 0x60, 0x7c, + 0x7e, 0x80, 0x62, 0x72, 0xca, 0x78, 0xc2, 0x8c, + 0xf7, 0x96, 0xd8, 0x58, 0x62, 0x5c, 0x13, 0x6a, + 0xda, 0x6d, 0x0f, 0x6f, 0x2f, 0x7d, 0x37, 0x7e, + 0x4b, 0x96, 0xd2, 0x52, 0x8b, 0x80, 0xdc, 0x51, + 0xcc, 0x51, 0x1c, 0x7a, 0xbe, 0x7d, 0xf1, 0x83, + 0x75, 0x96, 0x80, 0x8b, 0xcf, 0x62, 0x02, 0x6a, + 0xfe, 0x8a, 0x39, 0x4e, 0xe7, 0x5b, 0x12, 0x60, + 0x87, 0x73, 0x70, 0x75, 0x17, 0x53, 0xfb, 0x78, + 0xbf, 0x4f, 0xa9, 0x5f, 0x0d, 0x4e, 0xcc, 0x6c, + 0x78, 0x65, 0x22, 0x7d, 0xc3, 0x53, 0x5e, 0x58, + 0x01, 0x77, 0x49, 0x84, 0xaa, 0x8a, 0xba, 0x6b, + 0xb0, 0x8f, 0x88, 0x6c, 0xfe, 0x62, 0xe5, 0x82, + 0xa0, 0x63, 0x65, 0x75, 0xae, 0x4e, 0x69, 0x51, + 0xc9, 0x51, 0x81, 0x68, 0xe7, 0x7c, 0x6f, 0x82, + 0xd2, 0x8a, 0xcf, 0x91, 0xf5, 0x52, 0x42, 0x54, + 0x73, 0x59, 0xec, 0x5e, 0xc5, 0x65, 0xfe, 0x6f, + 0x2a, 0x79, 0xad, 0x95, 0x6a, 0x9a, 0x97, 0x9e, + 0xce, 0x9e, 0x9b, 0x52, 0xc6, 0x66, 0x77, 0x6b, + 0x62, 0x8f, 0x74, 0x5e, 0x90, 0x61, 0x00, 0x62, + 0x9a, 0x64, 0x23, 0x6f, 0x49, 0x71, 0x89, 0x74, + 0xca, 0x79, 0xf4, 0x7d, 0x6f, 0x80, 0x26, 0x8f, + 0xee, 0x84, 0x23, 0x90, 0x4a, 0x93, 0x17, 0x52, + 0xa3, 0x52, 0xbd, 0x54, 0xc8, 0x70, 0xc2, 0x88, + 0xaa, 0x8a, 0xc9, 0x5e, 0xf5, 0x5f, 0x7b, 0x63, + 0xae, 0x6b, 0x3e, 0x7c, 0x75, 0x73, 0xe4, 0x4e, + 0xf9, 0x56, 0xe7, 0x5b, 0xba, 0x5d, 0x1c, 0x60, + 0xb2, 0x73, 0x69, 0x74, 0x9a, 0x7f, 0x46, 0x80, + 0x34, 0x92, 0xf6, 0x96, 0x48, 0x97, 0x18, 0x98, + 0x8b, 0x4f, 0xae, 0x79, 0xb4, 0x91, 0xb8, 0x96, + 0xe1, 0x60, 0x86, 0x4e, 0xda, 0x50, 0xee, 0x5b, + 0x3f, 0x5c, 0x99, 0x65, 0x02, 0x6a, 0xce, 0x71, + 0x42, 0x76, 0xfc, 0x84, 0x7c, 0x90, 0x8d, 0x9f, + 0x88, 0x66, 0x2e, 0x96, 0x89, 0x52, 0x7b, 0x67, + 0xf3, 0x67, 0x41, 0x6d, 0x9c, 0x6e, 0x09, 0x74, + 0x59, 0x75, 0x6b, 0x78, 0x10, 0x7d, 0x5e, 0x98, + 0x6d, 0x51, 0x2e, 0x62, 0x78, 0x96, 0x2b, 0x50, + 0x19, 0x5d, 0xea, 0x6d, 0x2a, 0x8f, 0x8b, 0x5f, + 0x44, 0x61, 0x17, 0x68, 0x87, 0x73, 0x86, 0x96, + 0x29, 0x52, 0x0f, 0x54, 0x65, 0x5c, 0x13, 0x66, + 0x4e, 0x67, 0xa8, 0x68, 0xe5, 0x6c, 0x06, 0x74, + 0xe2, 0x75, 0x79, 0x7f, 0xcf, 0x88, 0xe1, 0x88, + 0xcc, 0x91, 0xe2, 0x96, 0x3f, 0x53, 0xba, 0x6e, + 0x1d, 0x54, 0xd0, 0x71, 0x98, 0x74, 0xfa, 0x85, + 0xa3, 0x96, 0x57, 0x9c, 0x9f, 0x9e, 0x97, 0x67, + 0xcb, 0x6d, 0xe8, 0x81, 0xcb, 0x7a, 0x20, 0x7b, + 0x92, 0x7c, 0xc0, 0x72, 0x99, 0x70, 0x58, 0x8b, + 0xc0, 0x4e, 0x36, 0x83, 0x3a, 0x52, 0x07, 0x52, + 0xa6, 0x5e, 0xd3, 0x62, 0xd6, 0x7c, 0x85, 0x5b, + 0x1e, 0x6d, 0xb4, 0x66, 0x3b, 0x8f, 0x4c, 0x88, + 0x4d, 0x96, 0x8b, 0x89, 0xd3, 0x5e, 0x40, 0x51, + 0xc0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x58, + 0x00, 0x00, 0x74, 0x66, 0x00, 0x00, 0x00, 0x00, + 0xde, 0x51, 0x2a, 0x73, 0xca, 0x76, 0x3c, 0x79, + 0x5e, 0x79, 0x65, 0x79, 0x8f, 0x79, 0x56, 0x97, + 0xbe, 0x7c, 0xbd, 0x7f, 0x00, 0x00, 0x12, 0x86, + 0x00, 0x00, 0xf8, 0x8a, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x90, 0xfd, 0x90, 0xef, 0x98, 0xfc, 0x98, + 0x28, 0x99, 0xb4, 0x9d, 0xde, 0x90, 0xb7, 0x96, + 0xae, 0x4f, 0xe7, 0x50, 0x4d, 0x51, 0xc9, 0x52, + 0xe4, 0x52, 0x51, 0x53, 0x9d, 0x55, 0x06, 0x56, + 0x68, 0x56, 0x40, 0x58, 0xa8, 0x58, 0x64, 0x5c, + 0x6e, 0x5c, 0x94, 0x60, 0x68, 0x61, 0x8e, 0x61, + 0xf2, 0x61, 0x4f, 0x65, 0xe2, 0x65, 0x91, 0x66, + 0x85, 0x68, 0x77, 0x6d, 0x1a, 0x6e, 0x22, 0x6f, + 0x6e, 0x71, 0x2b, 0x72, 0x22, 0x74, 0x91, 0x78, + 0x3e, 0x79, 0x49, 0x79, 0x48, 0x79, 0x50, 0x79, + 0x56, 0x79, 0x5d, 0x79, 0x8d, 0x79, 0x8e, 0x79, + 0x40, 0x7a, 0x81, 0x7a, 0xc0, 0x7b, 0xf4, 0x7d, + 0x09, 0x7e, 0x41, 0x7e, 0x72, 0x7f, 0x05, 0x80, + 0xed, 0x81, 0x79, 0x82, 0x79, 0x82, 0x57, 0x84, + 0x10, 0x89, 0x96, 0x89, 0x01, 0x8b, 0x39, 0x8b, + 0xd3, 0x8c, 0x08, 0x8d, 0xb6, 0x8f, 0x38, 0x90, + 0xe3, 0x96, 0xff, 0x97, 0x3b, 0x98, 0x75, 0x60, + 0xee, 0x42, 0x18, 0x82, 0x02, 0x26, 0x4e, 0xb5, + 0x51, 0x68, 0x51, 0x80, 0x4f, 0x45, 0x51, 0x80, + 0x51, 0xc7, 0x52, 0xfa, 0x52, 0x9d, 0x55, 0x55, + 0x55, 0x99, 0x55, 0xe2, 0x55, 0x5a, 0x58, 0xb3, + 0x58, 0x44, 0x59, 0x54, 0x59, 0x62, 0x5a, 0x28, + 0x5b, 0xd2, 0x5e, 0xd9, 0x5e, 0x69, 0x5f, 0xad, + 0x5f, 0xd8, 0x60, 0x4e, 0x61, 0x08, 0x61, 0x8e, + 0x61, 0x60, 0x61, 0xf2, 0x61, 0x34, 0x62, 0xc4, + 0x63, 0x1c, 0x64, 0x52, 0x64, 0x56, 0x65, 0x74, + 0x66, 0x17, 0x67, 0x1b, 0x67, 0x56, 0x67, 0x79, + 0x6b, 0xba, 0x6b, 0x41, 0x6d, 0xdb, 0x6e, 0xcb, + 0x6e, 0x22, 0x6f, 0x1e, 0x70, 0x6e, 0x71, 0xa7, + 0x77, 0x35, 0x72, 0xaf, 0x72, 0x2a, 0x73, 0x71, + 0x74, 0x06, 0x75, 0x3b, 0x75, 0x1d, 0x76, 0x1f, + 0x76, 0xca, 0x76, 0xdb, 0x76, 0xf4, 0x76, 0x4a, + 0x77, 0x40, 0x77, 0xcc, 0x78, 0xb1, 0x7a, 0xc0, + 0x7b, 0x7b, 0x7c, 0x5b, 0x7d, 0xf4, 0x7d, 0x3e, + 0x7f, 0x05, 0x80, 0x52, 0x83, 0xef, 0x83, 0x79, + 0x87, 0x41, 0x89, 0x86, 0x89, 0x96, 0x89, 0xbf, + 0x8a, 0xf8, 0x8a, 0xcb, 0x8a, 0x01, 0x8b, 0xfe, + 0x8a, 0xed, 0x8a, 0x39, 0x8b, 0x8a, 0x8b, 0x08, + 0x8d, 0x38, 0x8f, 0x72, 0x90, 0x99, 0x91, 0x76, + 0x92, 0x7c, 0x96, 0xe3, 0x96, 0x56, 0x97, 0xdb, + 0x97, 0xff, 0x97, 0x0b, 0x98, 0x3b, 0x98, 0x12, + 0x9b, 0x9c, 0x9f, 0x4a, 0x28, 0x44, 0x28, 0xd5, + 0x33, 0x9d, 0x3b, 0x18, 0x40, 0x39, 0x40, 0x49, + 0x52, 0xd0, 0x5c, 0xd3, 0x7e, 0x43, 0x9f, 0x8e, + 0x9f, 0x2a, 0xa0, 0x02, 0x66, 0x66, 0x66, 0x69, + 0x66, 0x6c, 0x66, 0x66, 0x69, 0x66, 0x66, 0x6c, + 0x7f, 0x01, 0x74, 0x73, 0x00, 0x74, 0x65, 0x05, + 0x0f, 0x11, 0x0f, 0x00, 0x0f, 0x06, 0x19, 0x11, + 0x0f, 0x08, 0xd9, 0x05, 0xb4, 0x05, 0x00, 0x00, + 0x00, 0x00, 0xf2, 0x05, 0xb7, 0x05, 0xd0, 0x05, + 0x12, 0x00, 0x03, 0x04, 0x0b, 0x0c, 0x0d, 0x18, + 0x1a, 0xe9, 0x05, 0xc1, 0x05, 0xe9, 0x05, 0xc2, + 0x05, 0x49, 0xfb, 0xc1, 0x05, 0x49, 0xfb, 0xc2, + 0x05, 0xd0, 0x05, 0xb7, 0x05, 0xd0, 0x05, 0xb8, + 0x05, 0xd0, 0x05, 0xbc, 0x05, 0xd8, 0x05, 0xbc, + 0x05, 0xde, 0x05, 0xbc, 0x05, 0xe0, 0x05, 0xbc, + 0x05, 0xe3, 0x05, 0xbc, 0x05, 0xb9, 0x05, 0x2d, + 0x03, 0x2e, 0x03, 0x2f, 0x03, 0x30, 0x03, 0x31, + 0x03, 0x1c, 0x00, 0x18, 0x06, 0x22, 0x06, 0x2b, + 0x06, 0xd0, 0x05, 0xdc, 0x05, 0x71, 0x06, 0x00, + 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0f, 0x0f, 0x0f, 0x0f, 0x09, 0x09, 0x09, + 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x08, 0x08, + 0x08, 0x33, 0x33, 0x33, 0x33, 0x35, 0x35, 0x35, + 0x35, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, + 0x12, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x1c, 0x1c, 0x1b, 0x1b, 0x1d, 0x1d, 0x17, + 0x17, 0x27, 0x27, 0x20, 0x20, 0x38, 0x38, 0x38, + 0x38, 0x3e, 0x3e, 0x3e, 0x3e, 0x42, 0x42, 0x42, + 0x42, 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x4a, + 0x4a, 0x4a, 0x4a, 0x4f, 0x4f, 0x50, 0x50, 0x50, + 0x50, 0x4d, 0x4d, 0x4d, 0x4d, 0x61, 0x61, 0x62, + 0x62, 0x49, 0x06, 0x64, 0x64, 0x64, 0x64, 0x7e, + 0x7e, 0x7d, 0x7d, 0x7f, 0x7f, 0x2e, 0x82, 0x82, + 0x7c, 0x7c, 0x80, 0x80, 0x87, 0x87, 0x87, 0x87, + 0x00, 0x00, 0x26, 0x06, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xaf, 0x00, 0xaf, 0x00, 0x22, 0x00, 0x22, + 0x00, 0xa1, 0x00, 0xa1, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa2, 0x00, 0xa2, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0x23, 0x00, 0x23, 0x00, 0x23, + 0xcc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x26, 0x06, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x1f, 0x00, 0x23, + 0x00, 0x24, 0x02, 0x06, 0x02, 0x07, 0x02, 0x08, + 0x02, 0x1f, 0x02, 0x23, 0x02, 0x24, 0x04, 0x06, + 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x23, + 0x04, 0x24, 0x05, 0x06, 0x05, 0x1f, 0x05, 0x23, + 0x05, 0x24, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, + 0x07, 0x1f, 0x08, 0x06, 0x08, 0x07, 0x08, 0x1f, + 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, + 0x0f, 0x07, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, + 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x11, 0x1f, + 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, + 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, 0x1b, 0x08, + 0x1b, 0x1f, 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x07, + 0x1c, 0x1f, 0x1c, 0x23, 0x1c, 0x24, 0x1d, 0x01, + 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e, + 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x06, + 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x23, + 0x1e, 0x24, 0x1f, 0x06, 0x1f, 0x07, 0x1f, 0x08, + 0x1f, 0x1f, 0x1f, 0x23, 0x1f, 0x24, 0x20, 0x06, + 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, 0x23, + 0x20, 0x24, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x23, + 0x21, 0x24, 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, + 0x24, 0x1f, 0x24, 0x23, 0x24, 0x24, 0x0a, 0x4a, + 0x0b, 0x4a, 0x23, 0x4a, 0x20, 0x00, 0x4c, 0x06, + 0x51, 0x06, 0x51, 0x06, 0xff, 0x00, 0x1f, 0x26, + 0x06, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x1f, 0x00, + 0x20, 0x00, 0x23, 0x00, 0x24, 0x02, 0x0b, 0x02, + 0x0c, 0x02, 0x1f, 0x02, 0x20, 0x02, 0x23, 0x02, + 0x24, 0x04, 0x0b, 0x04, 0x0c, 0x04, 0x1f, 0x26, + 0x06, 0x04, 0x20, 0x04, 0x23, 0x04, 0x24, 0x05, + 0x0b, 0x05, 0x0c, 0x05, 0x1f, 0x05, 0x20, 0x05, + 0x23, 0x05, 0x24, 0x1b, 0x23, 0x1b, 0x24, 0x1c, + 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x1e, 0x1d, + 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x1f, 0x1e, + 0x23, 0x1e, 0x24, 0x1f, 0x01, 0x1f, 0x1f, 0x20, + 0x0b, 0x20, 0x0c, 0x20, 0x1f, 0x20, 0x20, 0x20, + 0x23, 0x20, 0x24, 0x23, 0x4a, 0x24, 0x0b, 0x24, + 0x0c, 0x24, 0x1f, 0x24, 0x20, 0x24, 0x23, 0x24, + 0x24, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x1f, 0x00, 0x21, 0x02, 0x06, 0x02, 0x07, 0x02, + 0x08, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x06, 0x04, + 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x21, 0x05, + 0x1f, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, + 0x1f, 0x08, 0x06, 0x08, 0x1f, 0x0d, 0x06, 0x0d, + 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, 0x0f, + 0x08, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, + 0x08, 0x10, 0x1f, 0x11, 0x07, 0x12, 0x1f, 0x13, + 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, 0x1b, + 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1c, + 0x07, 0x1c, 0x1f, 0x1d, 0x06, 0x1d, 0x07, 0x1d, + 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x06, 0x1e, + 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x21, 0x1f, + 0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, 0x20, + 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, + 0x21, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x4a, 0x24, + 0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, 0x24, + 0x21, 0x00, 0x1f, 0x00, 0x21, 0x02, 0x1f, 0x02, + 0x21, 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x05, + 0x21, 0x0d, 0x1f, 0x0d, 0x21, 0x0e, 0x1f, 0x0e, + 0x21, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x1f, 0x20, + 0x1f, 0x20, 0x21, 0x24, 0x1f, 0x24, 0x21, 0x40, + 0x06, 0x4e, 0x06, 0x51, 0x06, 0x27, 0x06, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0c, + 0x20, 0x0d, 0x20, 0x10, 0x1e, 0x0c, 0x05, 0x0c, + 0x06, 0x0c, 0x07, 0x0d, 0x05, 0x0d, 0x06, 0x0d, + 0x07, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x24, 0x00, + 0x24, 0x2a, 0x06, 0x00, 0x02, 0x1b, 0x00, 0x03, + 0x02, 0x00, 0x03, 0x02, 0x00, 0x03, 0x1b, 0x00, + 0x04, 0x1b, 0x00, 0x1b, 0x02, 0x00, 0x1b, 0x03, + 0x00, 0x1b, 0x04, 0x02, 0x1b, 0x03, 0x02, 0x1b, + 0x03, 0x03, 0x1b, 0x20, 0x03, 0x1b, 0x1f, 0x09, + 0x03, 0x02, 0x09, 0x02, 0x03, 0x09, 0x02, 0x1f, + 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x03, 0x09, 0x1b, + 0x02, 0x09, 0x1b, 0x1b, 0x09, 0x1b, 0x1b, 0x0b, + 0x03, 0x03, 0x0b, 0x03, 0x03, 0x0b, 0x1b, 0x1b, + 0x0a, 0x03, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, 0x02, + 0x20, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x04, 0x0a, + 0x1b, 0x1b, 0x0a, 0x1b, 0x1b, 0x0c, 0x03, 0x1f, + 0x0c, 0x04, 0x1b, 0x0c, 0x04, 0x1b, 0x0d, 0x1b, + 0x03, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, 0x1b, 0x0d, + 0x1b, 0x20, 0x0f, 0x02, 0x1b, 0x0f, 0x1b, 0x1b, + 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1f, 0x10, 0x1b, + 0x1b, 0x10, 0x1b, 0x20, 0x10, 0x1b, 0x1f, 0x17, + 0x04, 0x1b, 0x17, 0x04, 0x1b, 0x18, 0x1b, 0x03, + 0x18, 0x1b, 0x1b, 0x1a, 0x03, 0x1b, 0x1a, 0x03, + 0x20, 0x1a, 0x03, 0x1f, 0x1a, 0x02, 0x02, 0x1a, + 0x02, 0x02, 0x1a, 0x04, 0x1b, 0x1a, 0x04, 0x1b, + 0x1a, 0x1b, 0x03, 0x1a, 0x1b, 0x03, 0x1b, 0x03, + 0x02, 0x1b, 0x03, 0x1b, 0x1b, 0x03, 0x20, 0x1b, + 0x02, 0x03, 0x1b, 0x02, 0x1b, 0x1b, 0x04, 0x02, + 0x1b, 0x04, 0x1b, 0x28, 0x06, 0x1d, 0x04, 0x06, + 0x1f, 0x1d, 0x04, 0x1f, 0x1d, 0x1d, 0x1e, 0x05, + 0x1d, 0x1e, 0x05, 0x21, 0x1e, 0x04, 0x1d, 0x1e, + 0x04, 0x1d, 0x1e, 0x04, 0x21, 0x1e, 0x1d, 0x22, + 0x1e, 0x1d, 0x21, 0x22, 0x1d, 0x1d, 0x22, 0x1d, + 0x1d, 0x00, 0x06, 0x22, 0x02, 0x04, 0x22, 0x02, + 0x04, 0x21, 0x02, 0x06, 0x22, 0x02, 0x06, 0x21, + 0x02, 0x1d, 0x22, 0x02, 0x1d, 0x21, 0x04, 0x1d, + 0x22, 0x04, 0x05, 0x21, 0x04, 0x1d, 0x21, 0x0b, + 0x06, 0x21, 0x0d, 0x05, 0x22, 0x0c, 0x05, 0x22, + 0x0e, 0x05, 0x22, 0x1c, 0x04, 0x22, 0x1c, 0x1d, + 0x22, 0x22, 0x05, 0x22, 0x22, 0x04, 0x22, 0x22, + 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1a, 0x1d, 0x22, + 0x1e, 0x05, 0x22, 0x1a, 0x1d, 0x05, 0x1c, 0x05, + 0x1d, 0x11, 0x1d, 0x22, 0x1b, 0x1d, 0x22, 0x1e, + 0x04, 0x05, 0x1d, 0x06, 0x22, 0x1c, 0x04, 0x1d, + 0x1b, 0x1d, 0x1d, 0x1c, 0x04, 0x1d, 0x1e, 0x04, + 0x05, 0x04, 0x05, 0x22, 0x05, 0x04, 0x22, 0x1d, + 0x04, 0x22, 0x19, 0x1d, 0x22, 0x00, 0x05, 0x22, + 0x1b, 0x1d, 0x1d, 0x11, 0x04, 0x1d, 0x0d, 0x1d, + 0x1d, 0x0b, 0x06, 0x22, 0x1e, 0x04, 0x22, 0x35, + 0x06, 0x00, 0x0f, 0x9d, 0x0d, 0x0f, 0x9d, 0x27, + 0x06, 0x00, 0x1d, 0x1d, 0x20, 0x00, 0x1c, 0x01, + 0x0a, 0x1e, 0x06, 0x1e, 0x08, 0x0e, 0x1d, 0x12, + 0x1e, 0x0a, 0x0c, 0x21, 0x1d, 0x12, 0x1d, 0x23, + 0x20, 0x21, 0x0c, 0x1d, 0x1e, 0x35, 0x06, 0x00, + 0x0f, 0x14, 0x27, 0x06, 0x0e, 0x1d, 0x22, 0xff, + 0x00, 0x1d, 0x1d, 0x20, 0xff, 0x12, 0x1d, 0x23, + 0x20, 0xff, 0x21, 0x0c, 0x1d, 0x1e, 0x27, 0x06, + 0x05, 0x1d, 0xff, 0x05, 0x1d, 0x00, 0x1d, 0x20, + 0x27, 0x06, 0x0a, 0xa5, 0x00, 0x1d, 0x2c, 0x00, + 0x01, 0x30, 0x02, 0x30, 0x3a, 0x00, 0x3b, 0x00, + 0x21, 0x00, 0x3f, 0x00, 0x16, 0x30, 0x17, 0x30, + 0x26, 0x20, 0x13, 0x20, 0x12, 0x01, 0x00, 0x5f, + 0x5f, 0x28, 0x29, 0x7b, 0x7d, 0x08, 0x30, 0x0c, + 0x0d, 0x08, 0x09, 0x02, 0x03, 0x00, 0x01, 0x04, + 0x05, 0x06, 0x07, 0x5b, 0x00, 0x5d, 0x00, 0x3e, + 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x5f, + 0x00, 0x5f, 0x00, 0x5f, 0x00, 0x2c, 0x00, 0x01, + 0x30, 0x2e, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x3a, + 0x00, 0x3f, 0x00, 0x21, 0x00, 0x14, 0x20, 0x28, + 0x00, 0x29, 0x00, 0x7b, 0x00, 0x7d, 0x00, 0x14, + 0x30, 0x15, 0x30, 0x23, 0x26, 0x2a, 0x2b, 0x2d, + 0x3c, 0x3e, 0x3d, 0x00, 0x5c, 0x24, 0x25, 0x40, + 0x40, 0x06, 0xff, 0x0b, 0x00, 0x0b, 0xff, 0x0c, + 0x20, 0x00, 0x4d, 0x06, 0x40, 0x06, 0xff, 0x0e, + 0x00, 0x0e, 0xff, 0x0f, 0x00, 0x0f, 0xff, 0x10, + 0x00, 0x10, 0xff, 0x11, 0x00, 0x11, 0xff, 0x12, + 0x00, 0x12, 0x21, 0x06, 0x00, 0x01, 0x01, 0x02, + 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x05, + 0x05, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, + 0x08, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, + 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x12, + 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, + 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, + 0x18, 0x19, 0x19, 0x19, 0x19, 0x20, 0x20, 0x20, + 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, + 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, + 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, + 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x29, + 0x29, 0x22, 0x06, 0x22, 0x00, 0x22, 0x00, 0x22, + 0x01, 0x22, 0x01, 0x22, 0x03, 0x22, 0x03, 0x22, + 0x05, 0x22, 0x05, 0x21, 0x00, 0x85, 0x29, 0x01, + 0x30, 0x01, 0x0b, 0x0c, 0x00, 0xfa, 0xf1, 0xa0, + 0xa2, 0xa4, 0xa6, 0xa8, 0xe2, 0xe4, 0xe6, 0xc2, + 0xfb, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, 0xac, + 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, + 0xbe, 0xc0, 0xc3, 0xc5, 0xc7, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xd1, 0xd4, 0xd7, 0xda, 0xdd, + 0xde, 0xdf, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, 0xe8, + 0xe9, 0xea, 0xeb, 0xec, 0xee, 0xf2, 0x98, 0x99, + 0x31, 0x31, 0x4f, 0x31, 0x55, 0x31, 0x5b, 0x31, + 0x61, 0x31, 0xa2, 0x00, 0xa3, 0x00, 0xac, 0x00, + 0xaf, 0x00, 0xa6, 0x00, 0xa5, 0x00, 0xa9, 0x20, + 0x00, 0x00, 0x02, 0x25, 0x90, 0x21, 0x91, 0x21, + 0x92, 0x21, 0x93, 0x21, 0xa0, 0x25, 0xcb, 0x25, + 0xd2, 0x05, 0x07, 0x03, 0x01, 0xda, 0x05, 0x07, + 0x03, 0x01, 0xd0, 0x02, 0xd1, 0x02, 0xe6, 0x00, + 0x99, 0x02, 0x53, 0x02, 0x00, 0x00, 0xa3, 0x02, + 0x66, 0xab, 0xa5, 0x02, 0xa4, 0x02, 0x56, 0x02, + 0x57, 0x02, 0x91, 0x1d, 0x58, 0x02, 0x5e, 0x02, + 0xa9, 0x02, 0x64, 0x02, 0x62, 0x02, 0x60, 0x02, + 0x9b, 0x02, 0x27, 0x01, 0x9c, 0x02, 0x67, 0x02, + 0x84, 0x02, 0xaa, 0x02, 0xab, 0x02, 0x6c, 0x02, + 0x04, 0xdf, 0x8e, 0xa7, 0x6e, 0x02, 0x05, 0xdf, + 0x8e, 0x02, 0x06, 0xdf, 0xf8, 0x00, 0x76, 0x02, + 0x77, 0x02, 0x71, 0x00, 0x7a, 0x02, 0x08, 0xdf, + 0x7d, 0x02, 0x7e, 0x02, 0x80, 0x02, 0xa8, 0x02, + 0xa6, 0x02, 0x67, 0xab, 0xa7, 0x02, 0x88, 0x02, + 0x71, 0x2c, 0x00, 0x00, 0x8f, 0x02, 0xa1, 0x02, + 0xa2, 0x02, 0x98, 0x02, 0xc0, 0x01, 0xc1, 0x01, + 0xc2, 0x01, 0x0a, 0xdf, 0x1e, 0xdf, 0x41, 0x04, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x14, 0x99, 0x10, + 0xba, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, + 0xba, 0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, + 0x05, 0x31, 0x11, 0x27, 0x11, 0x32, 0x11, 0x27, + 0x11, 0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, + 0x57, 0x13, 0x55, 0x82, 0x13, 0xc9, 0x13, 0x00, + 0x00, 0x00, 0x00, 0x84, 0x13, 0xbb, 0x13, 0x05, + 0x05, 0x8b, 0x13, 0xc2, 0x13, 0x05, 0x90, 0x13, + 0xc9, 0x13, 0x05, 0xc2, 0x13, 0xc2, 0x13, 0x00, + 0x00, 0x00, 0x00, 0xc2, 0x13, 0xb8, 0x13, 0xc2, + 0x13, 0xc9, 0x13, 0x05, 0x55, 0xb9, 0x14, 0xba, + 0x14, 0xb9, 0x14, 0xb0, 0x14, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x14, 0xbd, 0x14, 0x55, 0x50, 0xb8, + 0x15, 0xaf, 0x15, 0xb9, 0x15, 0xaf, 0x15, 0x55, + 0x35, 0x19, 0x30, 0x19, 0x05, 0x1e, 0x61, 0x1e, + 0x61, 0x1e, 0x61, 0x29, 0x61, 0x1e, 0x61, 0x1f, + 0x61, 0x29, 0x61, 0x1f, 0x61, 0x1e, 0x61, 0x20, + 0x61, 0x21, 0x61, 0x1f, 0x61, 0x22, 0x61, 0x1f, + 0x61, 0x21, 0x61, 0x20, 0x61, 0x55, 0x55, 0x55, + 0x55, 0x67, 0x6d, 0x67, 0x6d, 0x63, 0x6d, 0x67, + 0x6d, 0x69, 0x6d, 0x67, 0x6d, 0x55, 0x05, 0x41, + 0x00, 0x30, 0x00, 0x57, 0xd1, 0x65, 0xd1, 0x58, + 0xd1, 0x65, 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, 0x5f, + 0xd1, 0x6f, 0xd1, 0x5f, 0xd1, 0x70, 0xd1, 0x5f, + 0xd1, 0x71, 0xd1, 0x5f, 0xd1, 0x72, 0xd1, 0x55, + 0x55, 0x55, 0x05, 0xb9, 0xd1, 0x65, 0xd1, 0xba, + 0xd1, 0x65, 0xd1, 0xbb, 0xd1, 0x6e, 0xd1, 0xbc, + 0xd1, 0x6e, 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, 0xbc, + 0xd1, 0x6f, 0xd1, 0x55, 0x55, 0x55, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x69, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x43, 0x44, + 0x00, 0x00, 0x47, 0x00, 0x00, 0x4a, 0x4b, 0x00, + 0x00, 0x4e, 0x4f, 0x50, 0x51, 0x00, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, + 0x63, 0x64, 0x00, 0x66, 0x68, 0x00, 0x70, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, + 0x45, 0x46, 0x47, 0x4a, 0x00, 0x53, 0x00, 0x61, + 0x00, 0x41, 0x42, 0x00, 0x44, 0x45, 0x46, 0x47, + 0x00, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x00, 0x4f, + 0x53, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x31, 0x01, 0x37, 0x02, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, + 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, + 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, + 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, + 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, + 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x0b, 0x0c, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x04, 0x3a, 0x04, 0x3e, 0x04, + 0x4b, 0x04, 0x4d, 0x04, 0x4e, 0x04, 0x89, 0xa6, + 0x30, 0x04, 0xa9, 0x26, 0x28, 0xb9, 0x7f, 0x9f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x0a, 0x0b, 0x0e, 0x0f, 0x11, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x61, 0x26, + 0x25, 0x2f, 0x7b, 0x51, 0xa6, 0xb1, 0x04, 0x27, + 0x06, 0x00, 0x01, 0x05, 0x08, 0x2a, 0x06, 0x1e, + 0x08, 0x03, 0x0d, 0x20, 0x19, 0x1a, 0x1b, 0x1c, + 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, + 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x44, 0x90, + 0x77, 0x45, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, + 0x47, 0x06, 0x33, 0x06, 0x17, 0x10, 0x11, 0x12, + 0x13, 0x00, 0x06, 0x0e, 0x02, 0x0f, 0x34, 0x06, + 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, + 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, 0x2d, 0x06, + 0x00, 0x00, 0x4a, 0x06, 0x00, 0x00, 0x44, 0x06, + 0x00, 0x00, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, + 0x00, 0x00, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, + 0x34, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x06, + 0x00, 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, + 0x00, 0x00, 0xba, 0x06, 0x00, 0x00, 0x6f, 0x06, + 0x00, 0x00, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, + 0x47, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x06, + 0x37, 0x06, 0x4a, 0x06, 0x43, 0x06, 0x00, 0x00, + 0x45, 0x06, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, + 0x41, 0x06, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, + 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, + 0x00, 0x00, 0x36, 0x06, 0x38, 0x06, 0x3a, 0x06, + 0x6e, 0x06, 0x00, 0x00, 0xa1, 0x06, 0x27, 0x06, + 0x00, 0x01, 0x05, 0x08, 0x20, 0x21, 0x0b, 0x06, + 0x10, 0x23, 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, + 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, + 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x28, 0x06, 0x2c, + 0x06, 0x2f, 0x06, 0x00, 0x00, 0x48, 0x06, 0x32, + 0x06, 0x2d, 0x06, 0x37, 0x06, 0x4a, 0x06, 0x2a, + 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, + 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, 0x0c, + 0x0e, 0x10, 0x30, 0x2e, 0x30, 0x00, 0x2c, 0x00, + 0x28, 0x00, 0x41, 0x00, 0x29, 0x00, 0x14, 0x30, + 0x53, 0x00, 0x15, 0x30, 0x43, 0x52, 0x43, 0x44, + 0x57, 0x5a, 0x41, 0x00, 0x48, 0x56, 0x4d, 0x56, + 0x53, 0x44, 0x53, 0x53, 0x50, 0x50, 0x56, 0x57, + 0x43, 0x4d, 0x43, 0x4d, 0x44, 0x4d, 0x52, 0x44, + 0x4a, 0x4b, 0x30, 0x30, 0x00, 0x68, 0x68, 0x4b, + 0x62, 0x57, 0x5b, 0xcc, 0x53, 0xc7, 0x30, 0x8c, + 0x4e, 0x1a, 0x59, 0xe3, 0x89, 0x29, 0x59, 0xa4, + 0x4e, 0x20, 0x66, 0x21, 0x71, 0x99, 0x65, 0x4d, + 0x52, 0x8c, 0x5f, 0x8d, 0x51, 0xb0, 0x65, 0x1d, + 0x52, 0x42, 0x7d, 0x1f, 0x75, 0xa9, 0x8c, 0xf0, + 0x58, 0x39, 0x54, 0x14, 0x6f, 0x95, 0x62, 0x55, + 0x63, 0x00, 0x4e, 0x09, 0x4e, 0x4a, 0x90, 0xe6, + 0x5d, 0x2d, 0x4e, 0xf3, 0x53, 0x07, 0x63, 0x70, + 0x8d, 0x53, 0x62, 0x81, 0x79, 0x7a, 0x7a, 0x08, + 0x54, 0x80, 0x6e, 0x09, 0x67, 0x08, 0x67, 0x33, + 0x75, 0x72, 0x52, 0xb6, 0x55, 0x4d, 0x91, 0x14, + 0x30, 0x15, 0x30, 0x2c, 0x67, 0x09, 0x4e, 0x8c, + 0x4e, 0x89, 0x5b, 0xb9, 0x70, 0x53, 0x62, 0xd7, + 0x76, 0xdd, 0x52, 0x57, 0x65, 0x97, 0x5f, 0xef, + 0x53, 0x30, 0x00, 0x38, 0x4e, 0x05, 0x00, 0x09, + 0x22, 0x01, 0x60, 0x4f, 0xae, 0x4f, 0xbb, 0x4f, + 0x02, 0x50, 0x7a, 0x50, 0x99, 0x50, 0xe7, 0x50, + 0xcf, 0x50, 0x9e, 0x34, 0x3a, 0x06, 0x4d, 0x51, + 0x54, 0x51, 0x64, 0x51, 0x77, 0x51, 0x1c, 0x05, + 0xb9, 0x34, 0x67, 0x51, 0x8d, 0x51, 0x4b, 0x05, + 0x97, 0x51, 0xa4, 0x51, 0xcc, 0x4e, 0xac, 0x51, + 0xb5, 0x51, 0xdf, 0x91, 0xf5, 0x51, 0x03, 0x52, + 0xdf, 0x34, 0x3b, 0x52, 0x46, 0x52, 0x72, 0x52, + 0x77, 0x52, 0x15, 0x35, 0x02, 0x00, 0x20, 0x80, + 0x80, 0x00, 0x08, 0x00, 0x00, 0xc7, 0x52, 0x00, + 0x02, 0x1d, 0x33, 0x3e, 0x3f, 0x50, 0x82, 0x8a, + 0x93, 0xac, 0xb6, 0xb8, 0xb8, 0xb8, 0x2c, 0x0a, + 0x70, 0x70, 0xca, 0x53, 0xdf, 0x53, 0x63, 0x0b, + 0xeb, 0x53, 0xf1, 0x53, 0x06, 0x54, 0x9e, 0x54, + 0x38, 0x54, 0x48, 0x54, 0x68, 0x54, 0xa2, 0x54, + 0xf6, 0x54, 0x10, 0x55, 0x53, 0x55, 0x63, 0x55, + 0x84, 0x55, 0x84, 0x55, 0x99, 0x55, 0xab, 0x55, + 0xb3, 0x55, 0xc2, 0x55, 0x16, 0x57, 0x06, 0x56, + 0x17, 0x57, 0x51, 0x56, 0x74, 0x56, 0x07, 0x52, + 0xee, 0x58, 0xce, 0x57, 0xf4, 0x57, 0x0d, 0x58, + 0x8b, 0x57, 0x32, 0x58, 0x31, 0x58, 0xac, 0x58, + 0xe4, 0x14, 0xf2, 0x58, 0xf7, 0x58, 0x06, 0x59, + 0x1a, 0x59, 0x22, 0x59, 0x62, 0x59, 0xa8, 0x16, + 0xea, 0x16, 0xec, 0x59, 0x1b, 0x5a, 0x27, 0x5a, + 0xd8, 0x59, 0x66, 0x5a, 0xee, 0x36, 0xfc, 0x36, + 0x08, 0x5b, 0x3e, 0x5b, 0x3e, 0x5b, 0xc8, 0x19, + 0xc3, 0x5b, 0xd8, 0x5b, 0xe7, 0x5b, 0xf3, 0x5b, + 0x18, 0x1b, 0xff, 0x5b, 0x06, 0x5c, 0x53, 0x5f, + 0x22, 0x5c, 0x81, 0x37, 0x60, 0x5c, 0x6e, 0x5c, + 0xc0, 0x5c, 0x8d, 0x5c, 0xe4, 0x1d, 0x43, 0x5d, + 0xe6, 0x1d, 0x6e, 0x5d, 0x6b, 0x5d, 0x7c, 0x5d, + 0xe1, 0x5d, 0xe2, 0x5d, 0x2f, 0x38, 0xfd, 0x5d, + 0x28, 0x5e, 0x3d, 0x5e, 0x69, 0x5e, 0x62, 0x38, + 0x83, 0x21, 0x7c, 0x38, 0xb0, 0x5e, 0xb3, 0x5e, + 0xb6, 0x5e, 0xca, 0x5e, 0x92, 0xa3, 0xfe, 0x5e, + 0x31, 0x23, 0x31, 0x23, 0x01, 0x82, 0x22, 0x5f, + 0x22, 0x5f, 0xc7, 0x38, 0xb8, 0x32, 0xda, 0x61, + 0x62, 0x5f, 0x6b, 0x5f, 0xe3, 0x38, 0x9a, 0x5f, + 0xcd, 0x5f, 0xd7, 0x5f, 0xf9, 0x5f, 0x81, 0x60, + 0x3a, 0x39, 0x1c, 0x39, 0x94, 0x60, 0xd4, 0x26, + 0xc7, 0x60, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, + 0x02, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x08, + 0x80, 0x28, 0x80, 0x02, 0x00, 0x00, 0x02, 0x48, + 0x61, 0x00, 0x04, 0x06, 0x04, 0x32, 0x46, 0x6a, + 0x5c, 0x67, 0x96, 0xaa, 0xae, 0xc8, 0xd3, 0x5d, + 0x62, 0x00, 0x54, 0x77, 0xf3, 0x0c, 0x2b, 0x3d, + 0x63, 0xfc, 0x62, 0x68, 0x63, 0x83, 0x63, 0xe4, + 0x63, 0xf1, 0x2b, 0x22, 0x64, 0xc5, 0x63, 0xa9, + 0x63, 0x2e, 0x3a, 0x69, 0x64, 0x7e, 0x64, 0x9d, + 0x64, 0x77, 0x64, 0x6c, 0x3a, 0x4f, 0x65, 0x6c, + 0x65, 0x0a, 0x30, 0xe3, 0x65, 0xf8, 0x66, 0x49, + 0x66, 0x19, 0x3b, 0x91, 0x66, 0x08, 0x3b, 0xe4, + 0x3a, 0x92, 0x51, 0x95, 0x51, 0x00, 0x67, 0x9c, + 0x66, 0xad, 0x80, 0xd9, 0x43, 0x17, 0x67, 0x1b, + 0x67, 0x21, 0x67, 0x5e, 0x67, 0x53, 0x67, 0xc3, + 0x33, 0x49, 0x3b, 0xfa, 0x67, 0x85, 0x67, 0x52, + 0x68, 0x85, 0x68, 0x6d, 0x34, 0x8e, 0x68, 0x1f, + 0x68, 0x14, 0x69, 0x9d, 0x3b, 0x42, 0x69, 0xa3, + 0x69, 0xea, 0x69, 0xa8, 0x6a, 0xa3, 0x36, 0xdb, + 0x6a, 0x18, 0x3c, 0x21, 0x6b, 0xa7, 0x38, 0x54, + 0x6b, 0x4e, 0x3c, 0x72, 0x6b, 0x9f, 0x6b, 0xba, + 0x6b, 0xbb, 0x6b, 0x8d, 0x3a, 0x0b, 0x1d, 0xfa, + 0x3a, 0x4e, 0x6c, 0xbc, 0x3c, 0xbf, 0x6c, 0xcd, + 0x6c, 0x67, 0x6c, 0x16, 0x6d, 0x3e, 0x6d, 0x77, + 0x6d, 0x41, 0x6d, 0x69, 0x6d, 0x78, 0x6d, 0x85, + 0x6d, 0x1e, 0x3d, 0x34, 0x6d, 0x2f, 0x6e, 0x6e, + 0x6e, 0x33, 0x3d, 0xcb, 0x6e, 0xc7, 0x6e, 0xd1, + 0x3e, 0xf9, 0x6d, 0x6e, 0x6f, 0x5e, 0x3f, 0x8e, + 0x3f, 0xc6, 0x6f, 0x39, 0x70, 0x1e, 0x70, 0x1b, + 0x70, 0x96, 0x3d, 0x4a, 0x70, 0x7d, 0x70, 0x77, + 0x70, 0xad, 0x70, 0x25, 0x05, 0x45, 0x71, 0x63, + 0x42, 0x9c, 0x71, 0xab, 0x43, 0x28, 0x72, 0x35, + 0x72, 0x50, 0x72, 0x08, 0x46, 0x80, 0x72, 0x95, + 0x72, 0x35, 0x47, 0x02, 0x20, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, + 0x02, 0x02, 0x80, 0x8a, 0x00, 0x00, 0x20, 0x00, + 0x08, 0x0a, 0x00, 0x80, 0x88, 0x80, 0x20, 0x14, + 0x48, 0x7a, 0x73, 0x8b, 0x73, 0xac, 0x3e, 0xa5, + 0x73, 0xb8, 0x3e, 0xb8, 0x3e, 0x47, 0x74, 0x5c, + 0x74, 0x71, 0x74, 0x85, 0x74, 0xca, 0x74, 0x1b, + 0x3f, 0x24, 0x75, 0x36, 0x4c, 0x3e, 0x75, 0x92, + 0x4c, 0x70, 0x75, 0x9f, 0x21, 0x10, 0x76, 0xa1, + 0x4f, 0xb8, 0x4f, 0x44, 0x50, 0xfc, 0x3f, 0x08, + 0x40, 0xf4, 0x76, 0xf3, 0x50, 0xf2, 0x50, 0x19, + 0x51, 0x33, 0x51, 0x1e, 0x77, 0x1f, 0x77, 0x1f, + 0x77, 0x4a, 0x77, 0x39, 0x40, 0x8b, 0x77, 0x46, + 0x40, 0x96, 0x40, 0x1d, 0x54, 0x4e, 0x78, 0x8c, + 0x78, 0xcc, 0x78, 0xe3, 0x40, 0x26, 0x56, 0x56, + 0x79, 0x9a, 0x56, 0xc5, 0x56, 0x8f, 0x79, 0xeb, + 0x79, 0x2f, 0x41, 0x40, 0x7a, 0x4a, 0x7a, 0x4f, + 0x7a, 0x7c, 0x59, 0xa7, 0x5a, 0xa7, 0x5a, 0xee, + 0x7a, 0x02, 0x42, 0xab, 0x5b, 0xc6, 0x7b, 0xc9, + 0x7b, 0x27, 0x42, 0x80, 0x5c, 0xd2, 0x7c, 0xa0, + 0x42, 0xe8, 0x7c, 0xe3, 0x7c, 0x00, 0x7d, 0x86, + 0x5f, 0x63, 0x7d, 0x01, 0x43, 0xc7, 0x7d, 0x02, + 0x7e, 0x45, 0x7e, 0x34, 0x43, 0x28, 0x62, 0x47, + 0x62, 0x59, 0x43, 0xd9, 0x62, 0x7a, 0x7f, 0x3e, + 0x63, 0x95, 0x7f, 0xfa, 0x7f, 0x05, 0x80, 0xda, + 0x64, 0x23, 0x65, 0x60, 0x80, 0xa8, 0x65, 0x70, + 0x80, 0x5f, 0x33, 0xd5, 0x43, 0xb2, 0x80, 0x03, + 0x81, 0x0b, 0x44, 0x3e, 0x81, 0xb5, 0x5a, 0xa7, + 0x67, 0xb5, 0x67, 0x93, 0x33, 0x9c, 0x33, 0x01, + 0x82, 0x04, 0x82, 0x9e, 0x8f, 0x6b, 0x44, 0x91, + 0x82, 0x8b, 0x82, 0x9d, 0x82, 0xb3, 0x52, 0xb1, + 0x82, 0xb3, 0x82, 0xbd, 0x82, 0xe6, 0x82, 0x3c, + 0x6b, 0xe5, 0x82, 0x1d, 0x83, 0x63, 0x83, 0xad, + 0x83, 0x23, 0x83, 0xbd, 0x83, 0xe7, 0x83, 0x57, + 0x84, 0x53, 0x83, 0xca, 0x83, 0xcc, 0x83, 0xdc, + 0x83, 0x36, 0x6c, 0x6b, 0x6d, 0x02, 0x00, 0x00, + 0x20, 0x22, 0x2a, 0xa0, 0x0a, 0x00, 0x20, 0x80, + 0x28, 0x00, 0xa8, 0x20, 0x20, 0x00, 0x02, 0x80, + 0x22, 0x02, 0x8a, 0x08, 0x00, 0xaa, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x28, 0xd5, 0x6c, 0x2b, + 0x45, 0xf1, 0x84, 0xf3, 0x84, 0x16, 0x85, 0xca, + 0x73, 0x64, 0x85, 0x2c, 0x6f, 0x5d, 0x45, 0x61, + 0x45, 0xb1, 0x6f, 0xd2, 0x70, 0x6b, 0x45, 0x50, + 0x86, 0x5c, 0x86, 0x67, 0x86, 0x69, 0x86, 0xa9, + 0x86, 0x88, 0x86, 0x0e, 0x87, 0xe2, 0x86, 0x79, + 0x87, 0x28, 0x87, 0x6b, 0x87, 0x86, 0x87, 0xd7, + 0x45, 0xe1, 0x87, 0x01, 0x88, 0xf9, 0x45, 0x60, + 0x88, 0x63, 0x88, 0x67, 0x76, 0xd7, 0x88, 0xde, + 0x88, 0x35, 0x46, 0xfa, 0x88, 0xbb, 0x34, 0xae, + 0x78, 0x66, 0x79, 0xbe, 0x46, 0xc7, 0x46, 0xa0, + 0x8a, 0xed, 0x8a, 0x8a, 0x8b, 0x55, 0x8c, 0xa8, + 0x7c, 0xab, 0x8c, 0xc1, 0x8c, 0x1b, 0x8d, 0x77, + 0x8d, 0x2f, 0x7f, 0x04, 0x08, 0xcb, 0x8d, 0xbc, + 0x8d, 0xf0, 0x8d, 0xde, 0x08, 0xd4, 0x8e, 0x38, + 0x8f, 0xd2, 0x85, 0xed, 0x85, 0x94, 0x90, 0xf1, + 0x90, 0x11, 0x91, 0x2e, 0x87, 0x1b, 0x91, 0x38, + 0x92, 0xd7, 0x92, 0xd8, 0x92, 0x7c, 0x92, 0xf9, + 0x93, 0x15, 0x94, 0xfa, 0x8b, 0x8b, 0x95, 0x95, + 0x49, 0xb7, 0x95, 0x77, 0x8d, 0xe6, 0x49, 0xc3, + 0x96, 0xb2, 0x5d, 0x23, 0x97, 0x45, 0x91, 0x1a, + 0x92, 0x6e, 0x4a, 0x76, 0x4a, 0xe0, 0x97, 0x0a, + 0x94, 0xb2, 0x4a, 0x96, 0x94, 0x0b, 0x98, 0x0b, + 0x98, 0x29, 0x98, 0xb6, 0x95, 0xe2, 0x98, 0x33, + 0x4b, 0x29, 0x99, 0xa7, 0x99, 0xc2, 0x99, 0xfe, + 0x99, 0xce, 0x4b, 0x30, 0x9b, 0x12, 0x9b, 0x40, + 0x9c, 0xfd, 0x9c, 0xce, 0x4c, 0xed, 0x4c, 0x67, + 0x9d, 0xce, 0xa0, 0xf8, 0x4c, 0x05, 0xa1, 0x0e, + 0xa2, 0x91, 0xa2, 0xbb, 0x9e, 0x56, 0x4d, 0xf9, + 0x9e, 0xfe, 0x9e, 0x05, 0x9f, 0x0f, 0x9f, 0x16, + 0x9f, 0x3b, 0x9f, 0x00, 0xa6, 0x02, 0x88, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x28, 0x00, + 0x08, 0xa0, 0x80, 0xa0, 0x80, 0x00, 0x80, 0x80, + 0x00, 0x0a, 0x88, 0x80, 0x00, 0x80, 0x00, 0x20, + 0x2a, 0x00, 0x80, +}; + +static const uint16_t unicode_comp_table[965] = { + 0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0, + 0x02c2, 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982, + 0x2440, 0x2280, 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8, + 0x02ca, 0x02cc, 0x0287, 0x228a, 0x02ce, 0x228c, 0x2290, 0x2292, + 0x228e, 0x0288, 0x0289, 0x028a, 0x2482, 0x0300, 0x0302, 0x0304, + 0x028b, 0x2480, 0x0308, 0x0984, 0x0986, 0x2458, 0x0a02, 0x0306, + 0x2298, 0x229a, 0x229e, 0x0900, 0x030a, 0x22a0, 0x030c, 0x030e, + 0x0840, 0x0310, 0x0312, 0x22a2, 0x22a6, 0x09c0, 0x22a4, 0x22a8, + 0x22aa, 0x028c, 0x028d, 0x028e, 0x0340, 0x0342, 0x0344, 0x0380, + 0x028f, 0x248e, 0x07c2, 0x0988, 0x098a, 0x2490, 0x0346, 0x22ac, + 0x0400, 0x22b0, 0x0842, 0x22b2, 0x0402, 0x22b4, 0x0440, 0x0444, + 0x22b6, 0x0442, 0x22c2, 0x22c0, 0x22c4, 0x22c6, 0x22c8, 0x0940, + 0x04c0, 0x0291, 0x22ca, 0x04c4, 0x22cc, 0x04c2, 0x22d0, 0x22ce, + 0x0292, 0x0293, 0x0294, 0x0295, 0x0540, 0x0542, 0x0a08, 0x0296, + 0x2494, 0x0544, 0x07c4, 0x098c, 0x098e, 0x06c0, 0x2492, 0x0844, + 0x2308, 0x230a, 0x0580, 0x230c, 0x0584, 0x0990, 0x0992, 0x230e, + 0x0582, 0x2312, 0x0586, 0x0588, 0x2314, 0x058c, 0x2316, 0x0998, + 0x058a, 0x231e, 0x0590, 0x2320, 0x099a, 0x058e, 0x2324, 0x2322, + 0x0299, 0x029a, 0x029b, 0x05c0, 0x05c2, 0x05c4, 0x029c, 0x24ac, + 0x05c6, 0x05c8, 0x07c6, 0x0994, 0x0996, 0x0700, 0x24aa, 0x2326, + 0x05ca, 0x232a, 0x2328, 0x2340, 0x2342, 0x2344, 0x2346, 0x05cc, + 0x234a, 0x2348, 0x234c, 0x234e, 0x2350, 0x24b8, 0x029d, 0x05ce, + 0x24be, 0x0a0c, 0x2352, 0x0600, 0x24bc, 0x24ba, 0x0640, 0x2354, + 0x0642, 0x0644, 0x2356, 0x2358, 0x02a0, 0x02a1, 0x02a2, 0x02a3, + 0x02c1, 0x02c3, 0x0a01, 0x02a4, 0x2443, 0x02a5, 0x07c1, 0x0981, + 0x0983, 0x2441, 0x2281, 0x02c5, 0x2283, 0x2285, 0x2287, 0x02c7, + 0x02c9, 0x02cb, 0x02cd, 0x02a7, 0x228b, 0x02cf, 0x228d, 0x2291, + 0x2293, 0x228f, 0x02a8, 0x02a9, 0x02aa, 0x2483, 0x0301, 0x0303, + 0x0305, 0x02ab, 0x2481, 0x0309, 0x0985, 0x0987, 0x2459, 0x0a03, + 0x0307, 0x2299, 0x229b, 0x229f, 0x0901, 0x030b, 0x22a1, 0x030d, + 0x030f, 0x0841, 0x0311, 0x0313, 0x22a3, 0x22a7, 0x09c1, 0x22a5, + 0x22a9, 0x22ab, 0x2380, 0x02ac, 0x02ad, 0x02ae, 0x0341, 0x0343, + 0x0345, 0x02af, 0x248f, 0x07c3, 0x0989, 0x098b, 0x2491, 0x0347, + 0x22ad, 0x0401, 0x0884, 0x22b1, 0x0843, 0x22b3, 0x0403, 0x22b5, + 0x0441, 0x0445, 0x22b7, 0x0443, 0x22c3, 0x22c1, 0x22c5, 0x22c7, + 0x22c9, 0x0941, 0x04c1, 0x02b1, 0x22cb, 0x04c5, 0x22cd, 0x04c3, + 0x22d1, 0x22cf, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x0541, 0x0543, + 0x0a09, 0x02b6, 0x2495, 0x0545, 0x07c5, 0x098d, 0x098f, 0x06c1, + 0x2493, 0x0845, 0x2309, 0x230b, 0x0581, 0x230d, 0x0585, 0x0991, + 0x0993, 0x230f, 0x0583, 0x2313, 0x0587, 0x0589, 0x2315, 0x058d, + 0x2317, 0x0999, 0x058b, 0x231f, 0x2381, 0x0591, 0x2321, 0x099b, + 0x058f, 0x2325, 0x2323, 0x02b9, 0x02ba, 0x02bb, 0x05c1, 0x05c3, + 0x05c5, 0x02bc, 0x24ad, 0x05c7, 0x05c9, 0x07c7, 0x0995, 0x0997, + 0x0701, 0x24ab, 0x2327, 0x05cb, 0x232b, 0x2329, 0x2341, 0x2343, + 0x2345, 0x2347, 0x05cd, 0x234b, 0x2349, 0x2382, 0x234d, 0x234f, + 0x2351, 0x24b9, 0x02bd, 0x05cf, 0x24bf, 0x0a0d, 0x2353, 0x02bf, + 0x24bd, 0x2383, 0x24bb, 0x0641, 0x2355, 0x0643, 0x0645, 0x2357, + 0x2359, 0x3101, 0x0c80, 0x2e00, 0x2446, 0x2444, 0x244a, 0x2448, + 0x0800, 0x0942, 0x0944, 0x0804, 0x2288, 0x2486, 0x2484, 0x248a, + 0x2488, 0x22ae, 0x2498, 0x2496, 0x249c, 0x249a, 0x2300, 0x0a06, + 0x2302, 0x0a04, 0x0946, 0x07ce, 0x07ca, 0x07c8, 0x07cc, 0x2447, + 0x2445, 0x244b, 0x2449, 0x0801, 0x0943, 0x0945, 0x0805, 0x2289, + 0x2487, 0x2485, 0x248b, 0x2489, 0x22af, 0x2499, 0x2497, 0x249d, + 0x249b, 0x2301, 0x0a07, 0x2303, 0x0a05, 0x0947, 0x07cf, 0x07cb, + 0x07c9, 0x07cd, 0x2450, 0x244e, 0x2454, 0x2452, 0x2451, 0x244f, + 0x2455, 0x2453, 0x2294, 0x2296, 0x2295, 0x2297, 0x2304, 0x2306, + 0x2305, 0x2307, 0x2318, 0x2319, 0x231a, 0x231b, 0x232c, 0x232d, + 0x232e, 0x232f, 0x2400, 0x24a2, 0x24a0, 0x24a6, 0x24a4, 0x24a8, + 0x24a3, 0x24a1, 0x24a7, 0x24a5, 0x24a9, 0x24b0, 0x24ae, 0x24b4, + 0x24b2, 0x24b6, 0x24b1, 0x24af, 0x24b5, 0x24b3, 0x24b7, 0x0882, + 0x0880, 0x0881, 0x0802, 0x0803, 0x229c, 0x229d, 0x0a0a, 0x0a0b, + 0x0883, 0x0b40, 0x2c8a, 0x0c81, 0x2c89, 0x2c88, 0x2540, 0x2541, + 0x2d00, 0x2e07, 0x0d00, 0x2640, 0x2641, 0x2e80, 0x0d01, 0x26c8, + 0x26c9, 0x2f00, 0x2f84, 0x0d02, 0x2f83, 0x2f82, 0x0d40, 0x26d8, + 0x26d9, 0x3186, 0x0d04, 0x2740, 0x2741, 0x3100, 0x3086, 0x0d06, + 0x3085, 0x3084, 0x0d41, 0x2840, 0x3200, 0x0d07, 0x284f, 0x2850, + 0x3280, 0x2c84, 0x2e03, 0x2857, 0x0d42, 0x2c81, 0x2c80, 0x24c0, + 0x24c1, 0x2c86, 0x2c83, 0x28c0, 0x0d43, 0x25c0, 0x25c1, 0x2940, + 0x0d44, 0x26c0, 0x26c1, 0x2e05, 0x2e02, 0x29c0, 0x0d45, 0x2f05, + 0x2f04, 0x0d80, 0x26d0, 0x26d1, 0x2f80, 0x2a40, 0x0d82, 0x26e0, + 0x26e1, 0x3080, 0x3081, 0x2ac0, 0x0d83, 0x3004, 0x3003, 0x0d81, + 0x27c0, 0x27c1, 0x3082, 0x2b40, 0x0d84, 0x2847, 0x2848, 0x3184, + 0x3181, 0x2f06, 0x0d08, 0x2f81, 0x3005, 0x0d46, 0x3083, 0x3182, + 0x0e00, 0x0e01, 0x0f40, 0x1180, 0x1182, 0x0f03, 0x0f00, 0x11c0, + 0x0f01, 0x1140, 0x1202, 0x1204, 0x0f81, 0x1240, 0x0fc0, 0x1242, + 0x0f80, 0x1244, 0x1284, 0x0f82, 0x1286, 0x1288, 0x128a, 0x12c0, + 0x1282, 0x1181, 0x1183, 0x1043, 0x1040, 0x11c1, 0x1041, 0x1141, + 0x1203, 0x1205, 0x10c1, 0x1241, 0x1000, 0x1243, 0x10c0, 0x1245, + 0x1285, 0x10c2, 0x1287, 0x1289, 0x128b, 0x12c1, 0x1283, 0x1080, + 0x1100, 0x1101, 0x1200, 0x1201, 0x1280, 0x1281, 0x1340, 0x1341, + 0x1343, 0x1342, 0x1344, 0x13c2, 0x1400, 0x13c0, 0x1440, 0x1480, + 0x14c0, 0x1540, 0x1541, 0x1740, 0x1700, 0x1741, 0x17c0, 0x1800, + 0x1802, 0x1801, 0x1840, 0x1880, 0x1900, 0x18c0, 0x18c1, 0x1901, + 0x1940, 0x1942, 0x1941, 0x1980, 0x19c0, 0x19c2, 0x19c1, 0x1c80, + 0x1cc0, 0x1dc0, 0x1f80, 0x2000, 0x2002, 0x2004, 0x2006, 0x2008, + 0x2040, 0x2080, 0x2082, 0x20c0, 0x20c1, 0x2100, 0x22b8, 0x22b9, + 0x2310, 0x2311, 0x231c, 0x231d, 0x244c, 0x2456, 0x244d, 0x2457, + 0x248c, 0x248d, 0x249e, 0x249f, 0x2500, 0x2502, 0x2504, 0x2bc0, + 0x2501, 0x2503, 0x2505, 0x2bc1, 0x2bc2, 0x2bc3, 0x2bc4, 0x2bc5, + 0x2bc6, 0x2bc7, 0x2580, 0x2582, 0x2584, 0x2bc8, 0x2581, 0x2583, + 0x2585, 0x2bc9, 0x2bca, 0x2bcb, 0x2bcc, 0x2bcd, 0x2bce, 0x2bcf, + 0x2600, 0x2602, 0x2601, 0x2603, 0x2680, 0x2682, 0x2681, 0x2683, + 0x26c2, 0x26c4, 0x26c6, 0x2c00, 0x26c3, 0x26c5, 0x26c7, 0x2c01, + 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x26ca, 0x26cc, + 0x26ce, 0x2c08, 0x26cb, 0x26cd, 0x26cf, 0x2c09, 0x2c0a, 0x2c0b, + 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x26d2, 0x26d4, 0x26d6, 0x26d3, + 0x26d5, 0x26d7, 0x26da, 0x26dc, 0x26de, 0x26db, 0x26dd, 0x26df, + 0x2700, 0x2702, 0x2701, 0x2703, 0x2780, 0x2782, 0x2781, 0x2783, + 0x2800, 0x2802, 0x2804, 0x2801, 0x2803, 0x2805, 0x2842, 0x2844, + 0x2846, 0x2849, 0x284b, 0x284d, 0x2c40, 0x284a, 0x284c, 0x284e, + 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2851, + 0x2853, 0x2855, 0x2c48, 0x2852, 0x2854, 0x2856, 0x2c49, 0x2c4a, + 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c82, 0x2e01, 0x3180, + 0x2c87, 0x2f01, 0x2f02, 0x2f03, 0x2e06, 0x3185, 0x3000, 0x3001, + 0x3002, 0x4640, 0x4641, 0x4680, 0x46c0, 0x46c2, 0x46c1, 0x4700, + 0x4740, 0x4780, 0x47c0, 0x47c2, 0x4900, 0x4940, 0x4980, 0x4982, + 0x4a00, 0x49c2, 0x4a03, 0x4a04, 0x4a40, 0x4a41, 0x4a80, 0x4a81, + 0x4ac0, 0x4ac1, 0x4bc0, 0x4bc1, 0x4b00, 0x4b01, 0x4b40, 0x4b41, + 0x4bc2, 0x4bc3, 0x4b80, 0x4b81, 0x4b82, 0x4b83, 0x4c00, 0x4c01, + 0x4c02, 0x4c03, 0x5600, 0x5440, 0x5442, 0x5444, 0x5446, 0x5448, + 0x544a, 0x544c, 0x544e, 0x5450, 0x5452, 0x5454, 0x5456, 0x5480, + 0x5482, 0x5484, 0x54c0, 0x54c1, 0x5500, 0x5501, 0x5540, 0x5541, + 0x5580, 0x5581, 0x55c0, 0x55c1, 0x5680, 0x58c0, 0x5700, 0x5702, + 0x5704, 0x5706, 0x5708, 0x570a, 0x570c, 0x570e, 0x5710, 0x5712, + 0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, 0x5781, 0x57c0, + 0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, 0x5900, + 0x5901, 0x5902, 0x5903, 0x5940, 0x8ec0, 0x8f00, 0x8fc0, 0x8fc2, + 0x9000, 0x9040, 0x9041, 0x9080, 0x9081, 0x90c0, 0x90c2, 0x9100, + 0x9140, 0x9182, 0x9180, 0x9183, 0x91c1, 0x91c0, 0x91c3, 0x9200, + 0x9201, 0x9240, 0x9280, 0x9282, 0x9284, 0x9281, 0x9285, 0x9287, + 0x9286, 0x9283, 0x92c1, 0x92c0, 0x92c2, +}; + +typedef enum { + UNICODE_GC_Cn, + UNICODE_GC_Lu, + UNICODE_GC_Ll, + UNICODE_GC_Lt, + UNICODE_GC_Lm, + UNICODE_GC_Lo, + UNICODE_GC_Mn, + UNICODE_GC_Mc, + UNICODE_GC_Me, + UNICODE_GC_Nd, + UNICODE_GC_Nl, + UNICODE_GC_No, + UNICODE_GC_Sm, + UNICODE_GC_Sc, + UNICODE_GC_Sk, + UNICODE_GC_So, + UNICODE_GC_Pc, + UNICODE_GC_Pd, + UNICODE_GC_Ps, + UNICODE_GC_Pe, + UNICODE_GC_Pi, + UNICODE_GC_Pf, + UNICODE_GC_Po, + UNICODE_GC_Zs, + UNICODE_GC_Zl, + UNICODE_GC_Zp, + UNICODE_GC_Cc, + UNICODE_GC_Cf, + UNICODE_GC_Cs, + UNICODE_GC_Co, + UNICODE_GC_LC, + UNICODE_GC_L, + UNICODE_GC_M, + UNICODE_GC_N, + UNICODE_GC_S, + UNICODE_GC_P, + UNICODE_GC_Z, + UNICODE_GC_C, + UNICODE_GC_COUNT, +} UnicodeGCEnum; + +static const char unicode_gc_name_table[] = + "Cn,Unassigned" "\0" + "Lu,Uppercase_Letter" "\0" + "Ll,Lowercase_Letter" "\0" + "Lt,Titlecase_Letter" "\0" + "Lm,Modifier_Letter" "\0" + "Lo,Other_Letter" "\0" + "Mn,Nonspacing_Mark" "\0" + "Mc,Spacing_Mark" "\0" + "Me,Enclosing_Mark" "\0" + "Nd,Decimal_Number,digit" "\0" + "Nl,Letter_Number" "\0" + "No,Other_Number" "\0" + "Sm,Math_Symbol" "\0" + "Sc,Currency_Symbol" "\0" + "Sk,Modifier_Symbol" "\0" + "So,Other_Symbol" "\0" + "Pc,Connector_Punctuation" "\0" + "Pd,Dash_Punctuation" "\0" + "Ps,Open_Punctuation" "\0" + "Pe,Close_Punctuation" "\0" + "Pi,Initial_Punctuation" "\0" + "Pf,Final_Punctuation" "\0" + "Po,Other_Punctuation" "\0" + "Zs,Space_Separator" "\0" + "Zl,Line_Separator" "\0" + "Zp,Paragraph_Separator" "\0" + "Cc,Control,cntrl" "\0" + "Cf,Format" "\0" + "Cs,Surrogate" "\0" + "Co,Private_Use" "\0" + "LC,Cased_Letter" "\0" + "L,Letter" "\0" + "M,Mark,Combining_Mark" "\0" + "N,Number" "\0" + "S,Symbol" "\0" + "P,Punctuation,punct" "\0" + "Z,Separator" "\0" + "C,Other" "\0" +; + +static const uint8_t unicode_gc_table[4070] = { + 0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13, + 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, + 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, + 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, + 0xfa, 0x19, 0x17, 0x16, 0x6d, 0x0f, 0x16, 0x0e, + 0x0f, 0x05, 0x14, 0x0c, 0x1b, 0x0f, 0x0e, 0x0f, + 0x0c, 0x2b, 0x0e, 0x02, 0x36, 0x0e, 0x0b, 0x05, + 0x15, 0x4b, 0x16, 0xe1, 0x0f, 0x0c, 0xc1, 0xe2, + 0x10, 0x0c, 0xe2, 0x00, 0xff, 0x30, 0x02, 0xff, + 0x08, 0x02, 0xff, 0x27, 0xbf, 0x22, 0x21, 0x02, + 0x5f, 0x5f, 0x21, 0x22, 0x61, 0x02, 0x21, 0x02, + 0x41, 0x42, 0x21, 0x02, 0x21, 0x02, 0x9f, 0x7f, + 0x02, 0x5f, 0x5f, 0x21, 0x02, 0x5f, 0x3f, 0x02, + 0x05, 0x3f, 0x22, 0x65, 0x01, 0x03, 0x02, 0x01, + 0x03, 0x02, 0x01, 0x03, 0x02, 0xff, 0x08, 0x02, + 0xff, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x5f, 0x21, + 0x02, 0xff, 0x32, 0xa2, 0x21, 0x02, 0x21, 0x22, + 0x5f, 0x41, 0x02, 0xff, 0x00, 0xe2, 0x3c, 0x05, + 0xe2, 0x13, 0xe4, 0x0a, 0x6e, 0xe4, 0x04, 0xee, + 0x06, 0x84, 0xce, 0x04, 0x0e, 0x04, 0xee, 0x09, + 0xe6, 0x68, 0x7f, 0x04, 0x0e, 0x3f, 0x20, 0x04, + 0x42, 0x16, 0x01, 0x60, 0x2e, 0x01, 0x16, 0x41, + 0x00, 0x01, 0x00, 0x21, 0x02, 0xe1, 0x09, 0x00, + 0xe1, 0x01, 0xe2, 0x1b, 0x3f, 0x02, 0x41, 0x42, + 0xff, 0x10, 0x62, 0x3f, 0x0c, 0x5f, 0x3f, 0x02, + 0xe1, 0x2b, 0xe2, 0x28, 0xff, 0x1a, 0x0f, 0x86, + 0x28, 0xff, 0x2f, 0xff, 0x06, 0x02, 0xff, 0x58, + 0x00, 0xe1, 0x1e, 0x20, 0x04, 0xb6, 0xe2, 0x21, + 0x16, 0x11, 0x20, 0x2f, 0x0d, 0x00, 0xe6, 0x25, + 0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06, + 0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0, + 0x03, 0xbb, 0x4c, 0x36, 0x0d, 0x36, 0x2f, 0xe6, + 0x03, 0x16, 0x1b, 0x56, 0xe5, 0x18, 0x04, 0xe5, + 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, 0x06, + 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f, 0xa6, + 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, 0x45, + 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, 0x06, + 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51, 0xe6, + 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, 0x19, + 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, 0x06, + 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01, 0x04, + 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, 0xe5, + 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, 0x80, + 0xe5, 0x10, 0x0e, 0xa5, 0x00, 0x3b, 0x80, 0xe6, + 0x01, 0xe5, 0x21, 0x04, 0xe6, 0x10, 0x1b, 0xe6, + 0x18, 0x07, 0xe5, 0x2e, 0x06, 0x07, 0x06, 0x05, + 0x47, 0xe6, 0x00, 0x67, 0x06, 0x27, 0x05, 0xc6, + 0xe5, 0x02, 0x26, 0x36, 0xe9, 0x02, 0x16, 0x04, + 0xe5, 0x07, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, + 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x05, + 0x40, 0x65, 0x20, 0x06, 0x05, 0x47, 0x66, 0x20, + 0x27, 0x20, 0x27, 0x06, 0x05, 0xe0, 0x00, 0x07, + 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, 0x02, + 0x25, 0x2d, 0xab, 0x0f, 0x0d, 0x05, 0x16, 0x06, + 0x20, 0x26, 0x07, 0x00, 0xa5, 0x60, 0x25, 0x20, + 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x25, + 0x00, 0x25, 0x20, 0x06, 0x00, 0x47, 0x26, 0x60, + 0x26, 0x20, 0x46, 0x40, 0x06, 0xc0, 0x65, 0x00, + 0x05, 0xc0, 0xe9, 0x02, 0x26, 0x45, 0x06, 0x16, + 0xe0, 0x02, 0x26, 0x07, 0x00, 0xe5, 0x01, 0x00, + 0x45, 0x00, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, + 0x00, 0x85, 0x20, 0x06, 0x05, 0x47, 0x86, 0x00, + 0x26, 0x07, 0x00, 0x27, 0x06, 0x20, 0x05, 0xe0, + 0x07, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x16, 0x0d, + 0xc0, 0x05, 0xa6, 0x00, 0x06, 0x27, 0x00, 0xe5, + 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, + 0x00, 0x25, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, + 0x06, 0x07, 0x66, 0x20, 0x27, 0x20, 0x27, 0x06, + 0xc0, 0x26, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, + 0x20, 0xe9, 0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, + 0x06, 0x05, 0x00, 0xa5, 0x40, 0x45, 0x00, 0x65, + 0x40, 0x25, 0x00, 0x05, 0x00, 0x25, 0x40, 0x25, + 0x40, 0x45, 0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, + 0x27, 0x40, 0x47, 0x00, 0x47, 0x06, 0x20, 0x05, + 0xa0, 0x07, 0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, + 0x0d, 0x0f, 0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, + 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x08, + 0x20, 0x06, 0x05, 0x46, 0x67, 0x00, 0x46, 0x00, + 0x66, 0xc0, 0x26, 0x00, 0x45, 0x20, 0x05, 0x20, + 0x25, 0x26, 0x20, 0xe9, 0x02, 0xc0, 0x16, 0xcb, + 0x0f, 0x05, 0x06, 0x27, 0x16, 0xe5, 0x00, 0x00, + 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x02, 0x00, + 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x87, 0x00, + 0x06, 0x27, 0x00, 0x27, 0x26, 0xc0, 0x27, 0xa0, + 0x25, 0x00, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x00, + 0x25, 0x07, 0xe0, 0x04, 0x26, 0x27, 0xe5, 0x01, + 0x00, 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, 0x47, + 0x66, 0x00, 0x47, 0x00, 0x47, 0x06, 0x05, 0x0f, + 0x60, 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, 0xe9, + 0x02, 0xeb, 0x01, 0x0f, 0xa5, 0x00, 0x06, 0x27, + 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10, 0x00, 0xe5, + 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, 0x60, + 0x47, 0x46, 0x00, 0x06, 0x00, 0xe7, 0x00, 0xa0, + 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0, 0x04, 0xe5, + 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, 0x04, + 0xe6, 0x00, 0x16, 0xe9, 0x02, 0x36, 0xe0, 0x1d, + 0x25, 0x00, 0x05, 0x00, 0x85, 0x00, 0xe5, 0x10, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, 0xe6, + 0x01, 0x05, 0x20, 0x85, 0x00, 0x04, 0x00, 0xc6, + 0x00, 0xe9, 0x02, 0x20, 0x65, 0xe0, 0x18, 0x05, + 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, 0xaf, + 0xe9, 0x02, 0xeb, 0x02, 0x0f, 0x06, 0x0f, 0x06, + 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13, 0x27, 0xe5, + 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, 0x07, + 0x86, 0x16, 0x26, 0x85, 0xe6, 0x03, 0x00, 0xe6, + 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf, 0x00, 0x2f, + 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, 0x27, + 0x66, 0x07, 0xa6, 0x07, 0x26, 0x27, 0x26, 0x05, + 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26, 0x65, 0x46, + 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, 0x05, + 0x06, 0x27, 0x26, 0xa7, 0x06, 0x05, 0x07, 0xe9, + 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e, 0x00, 0x01, + 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, 0x42, + 0xe5, 0x80, 0xc1, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21, 0x00, 0x65, + 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x07, 0x00, 0xe5, + 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b, 0x20, 0x46, + 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, 0xef, + 0x02, 0xa0, 0xe1, 0x4e, 0x20, 0xa2, 0x20, 0x11, + 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5, 0x09, 0x17, + 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, 0x56, + 0x4a, 0xe5, 0x00, 0xc0, 0xe5, 0x0a, 0x46, 0x07, + 0xe0, 0x01, 0xe5, 0x0b, 0x26, 0x07, 0x36, 0xe0, + 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, 0x05, + 0x00, 0x45, 0x00, 0x26, 0xe0, 0x04, 0xe5, 0x2c, + 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06, 0x27, 0xe6, + 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, 0x20, + 0xe9, 0x02, 0xa0, 0xeb, 0x02, 0xa0, 0xb6, 0x11, + 0x76, 0x46, 0x1b, 0x06, 0xe9, 0x02, 0xa0, 0xe5, + 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, 0xe5, + 0x1a, 0x06, 0x05, 0x80, 0xe5, 0x3e, 0xe0, 0x02, + 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26, 0x47, 0x60, + 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, 0x36, + 0xe9, 0x02, 0xe5, 0x16, 0x20, 0x85, 0xe0, 0x03, + 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0, 0xe9, 0x02, + 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, 0x27, + 0x06, 0x20, 0x36, 0xe5, 0x2d, 0x07, 0x06, 0x07, + 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27, 0xe6, 0x00, + 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, 0xa0, + 0xe9, 0x02, 0xa0, 0xd6, 0x04, 0xb6, 0x20, 0xe6, + 0x06, 0x08, 0xe6, 0x08, 0xe0, 0x29, 0x66, 0x07, + 0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, 0x06, 0x87, + 0x06, 0x27, 0xe5, 0x00, 0x00, 0x36, 0xe9, 0x02, + 0xd6, 0xef, 0x02, 0xe6, 0x01, 0xef, 0x01, 0x56, + 0x26, 0x07, 0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, + 0x07, 0x46, 0x25, 0xe9, 0x02, 0xe5, 0x24, 0x06, + 0x07, 0x26, 0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, + 0x00, 0x76, 0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, + 0x27, 0x26, 0x40, 0x96, 0xe9, 0x02, 0x40, 0x45, + 0xe9, 0x02, 0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, + 0x3f, 0x80, 0xe1, 0x23, 0x20, 0x41, 0xf6, 0x00, + 0xe0, 0x00, 0x46, 0x16, 0xe6, 0x05, 0x07, 0xc6, + 0x65, 0x06, 0xa5, 0x06, 0x25, 0x07, 0x26, 0x05, + 0x80, 0xe2, 0x24, 0xe4, 0x37, 0xe2, 0x05, 0x04, + 0xe2, 0x1a, 0xe4, 0x1d, 0xe6, 0x38, 0xff, 0x80, + 0x0e, 0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, + 0x00, 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, + 0x00, 0xe2, 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, + 0x20, 0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, + 0x20, 0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, + 0x00, 0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, + 0x61, 0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, + 0x61, 0x03, 0x4e, 0x62, 0x20, 0x22, 0x61, 0x00, + 0x4e, 0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, + 0x22, 0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, + 0xb1, 0x36, 0x14, 0x15, 0x12, 0x34, 0x15, 0x12, + 0x14, 0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, + 0x01, 0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, + 0x13, 0xf6, 0x03, 0x0c, 0x16, 0x10, 0xf6, 0x02, + 0x17, 0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, + 0xab, 0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, + 0x12, 0x13, 0x00, 0xe4, 0x05, 0x40, 0xed, 0x19, + 0xe0, 0x07, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, + 0x04, 0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, + 0x02, 0x41, 0x22, 0x41, 0x02, 0x0f, 0x01, 0x2f, + 0x0c, 0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, + 0x2f, 0x22, 0x21, 0x8c, 0x3f, 0x42, 0x0f, 0x0c, + 0x2f, 0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, + 0x6a, 0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, + 0x0c, 0x2f, 0x0c, 0x2f, 0x0c, 0xcf, 0x0c, 0xef, + 0x17, 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, + 0xec, 0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, + 0x13, 0xef, 0x0c, 0x2c, 0xcf, 0x12, 0x13, 0xef, + 0x49, 0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, + 0xac, 0xef, 0x40, 0xe0, 0x0e, 0xef, 0x03, 0xe0, + 0x0d, 0xeb, 0x34, 0xef, 0x46, 0xeb, 0x0e, 0xef, + 0x80, 0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, + 0xec, 0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, + 0xef, 0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, + 0x12, 0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, + 0xec, 0x80, 0x7a, 0xef, 0x28, 0xec, 0x0d, 0x2f, + 0xac, 0xef, 0x1f, 0x20, 0xef, 0x18, 0x00, 0xef, + 0x61, 0xe1, 0x28, 0xe2, 0x28, 0x5f, 0x21, 0x22, + 0xdf, 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82, 0x24, + 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, 0x46, + 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, 0x00, + 0x02, 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0, 0x04, + 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, 0x01, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, 0x56, + 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, 0x11, + 0x36, 0x11, 0x16, 0x14, 0x15, 0x36, 0x14, 0x15, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, 0x16, + 0x12, 0xf6, 0x05, 0x2f, 0x56, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, 0xe0, 0x1a, + 0xef, 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, + 0x80, 0x4e, 0xe0, 0x12, 0xef, 0x08, 0x17, 0x56, + 0x0f, 0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, + 0x12, 0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, 0x11, + 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, + 0xe5, 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, + 0xe5, 0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, 0x23, + 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, + 0xe5, 0x18, 0xef, 0x1e, 0xe0, 0x01, 0x0f, 0xe5, + 0x08, 0xef, 0x17, 0x00, 0xeb, 0x02, 0xef, 0x16, + 0xeb, 0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, + 0x02, 0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, + 0xe5, 0x99, 0x38, 0xef, 0x38, 0xe5, 0xc0, 0x11, + 0x8d, 0x04, 0xe5, 0x83, 0xef, 0x40, 0xef, 0x2f, + 0xe0, 0x01, 0xe5, 0x20, 0xa4, 0x36, 0xe5, 0x80, + 0x84, 0x04, 0x56, 0xe5, 0x08, 0xe9, 0x02, 0x25, + 0xe0, 0x0c, 0xff, 0x26, 0x05, 0x06, 0x48, 0x16, + 0xe6, 0x02, 0x16, 0x04, 0xff, 0x14, 0x24, 0x26, + 0xe5, 0x3e, 0xea, 0x02, 0x26, 0xb6, 0xe0, 0x00, + 0xee, 0x0f, 0xe4, 0x01, 0x2e, 0xff, 0x06, 0x22, + 0xff, 0x36, 0x04, 0xe2, 0x00, 0x9f, 0xff, 0x02, + 0x04, 0x2e, 0x7f, 0x05, 0x7f, 0x22, 0xff, 0x0d, + 0x61, 0x02, 0x81, 0x02, 0xff, 0x07, 0x41, 0x02, + 0x5f, 0x3f, 0x20, 0x3f, 0x00, 0x02, 0x00, 0x02, + 0xdf, 0xe0, 0x0d, 0x44, 0x3f, 0x05, 0x24, 0x02, + 0xc5, 0x06, 0x45, 0x06, 0x65, 0x06, 0xe5, 0x0f, + 0x27, 0x26, 0x07, 0x6f, 0x06, 0x40, 0xab, 0x2f, + 0x0d, 0x0f, 0xa0, 0xe5, 0x2c, 0x76, 0xe0, 0x00, + 0x27, 0xe5, 0x2a, 0xe7, 0x08, 0x26, 0xe0, 0x00, + 0x36, 0xe9, 0x02, 0xa0, 0xe6, 0x0a, 0xa5, 0x56, + 0x05, 0x16, 0x25, 0x06, 0xe9, 0x02, 0xe5, 0x14, + 0xe6, 0x00, 0x36, 0xe5, 0x0f, 0xe6, 0x03, 0x27, + 0xe0, 0x03, 0x16, 0xe5, 0x15, 0x40, 0x46, 0x07, + 0xe5, 0x27, 0x06, 0x27, 0x66, 0x27, 0x26, 0x47, + 0xf6, 0x05, 0x00, 0x04, 0xe9, 0x02, 0x60, 0x36, + 0x85, 0x06, 0x04, 0xe5, 0x01, 0xe9, 0x02, 0x85, + 0x00, 0xe5, 0x21, 0xa6, 0x27, 0x26, 0x27, 0x26, + 0xe0, 0x01, 0x45, 0x06, 0xe5, 0x00, 0x06, 0x07, + 0x20, 0xe9, 0x02, 0x20, 0x76, 0xe5, 0x08, 0x04, + 0xa5, 0x4f, 0x05, 0x07, 0x06, 0x07, 0xe5, 0x2a, + 0x06, 0x05, 0x46, 0x25, 0x26, 0x85, 0x26, 0x05, + 0x06, 0x05, 0xe0, 0x10, 0x25, 0x04, 0x36, 0xe5, + 0x03, 0x07, 0x26, 0x27, 0x36, 0x05, 0x24, 0x07, + 0x06, 0xe0, 0x02, 0xa5, 0x20, 0xa5, 0x20, 0xa5, + 0xe0, 0x01, 0xc5, 0x00, 0xc5, 0x00, 0xe2, 0x23, + 0x0e, 0x64, 0xe2, 0x01, 0x04, 0x2e, 0x60, 0xe2, + 0x48, 0xe5, 0x1b, 0x27, 0x06, 0x27, 0x06, 0x27, + 0x16, 0x07, 0x06, 0x20, 0xe9, 0x02, 0xa0, 0xe5, + 0xab, 0x1c, 0xe0, 0x04, 0xe5, 0x0f, 0x60, 0xe5, + 0x29, 0x60, 0xfc, 0x87, 0x78, 0xfd, 0x98, 0x78, + 0xe5, 0x80, 0xe6, 0x20, 0xe5, 0x62, 0xe0, 0x1e, + 0xc2, 0xe0, 0x04, 0x82, 0x80, 0x05, 0x06, 0xe5, + 0x02, 0x0c, 0xe5, 0x05, 0x00, 0x85, 0x00, 0x05, + 0x00, 0x25, 0x00, 0x25, 0x00, 0xe5, 0x64, 0xee, + 0x09, 0xe0, 0x08, 0xe5, 0x80, 0xe3, 0x13, 0x12, + 0xef, 0x08, 0xe5, 0x38, 0x20, 0xe5, 0x2e, 0xc0, + 0x0f, 0xe0, 0x18, 0xe5, 0x04, 0x0d, 0x4f, 0xe6, + 0x08, 0xd6, 0x12, 0x13, 0x16, 0xa0, 0xe6, 0x08, + 0x16, 0x31, 0x30, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x36, 0x12, 0x13, 0x76, 0x50, + 0x56, 0x00, 0x76, 0x11, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x56, 0x0c, 0x11, 0x4c, 0x00, 0x16, + 0x0d, 0x36, 0x60, 0x85, 0x00, 0xe5, 0x7f, 0x20, + 0x1b, 0x00, 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16, + 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c, + 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, 0x10, + 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, 0x12, + 0x13, 0x16, 0x12, 0x13, 0x36, 0xe5, 0x02, 0x04, + 0xe5, 0x25, 0x24, 0xe5, 0x17, 0x40, 0xa5, 0x20, + 0xa5, 0x20, 0xa5, 0x20, 0x45, 0x40, 0x2d, 0x0c, + 0x0e, 0x0f, 0x2d, 0x00, 0x0f, 0x6c, 0x2f, 0xe0, + 0x02, 0x5b, 0x2f, 0x20, 0xe5, 0x04, 0x00, 0xe5, + 0x12, 0x00, 0xe5, 0x0b, 0x00, 0x25, 0x00, 0xe5, + 0x07, 0x20, 0xe5, 0x06, 0xe0, 0x1a, 0xe5, 0x73, + 0x80, 0x56, 0x60, 0xeb, 0x25, 0x40, 0xef, 0x01, + 0xea, 0x2d, 0x6b, 0xef, 0x09, 0x2b, 0x4f, 0x00, + 0xef, 0x05, 0x40, 0x0f, 0xe0, 0x27, 0xef, 0x25, + 0x06, 0xe0, 0x7a, 0xe5, 0x15, 0x40, 0xe5, 0x29, + 0xe0, 0x07, 0x06, 0xeb, 0x13, 0x60, 0xe5, 0x18, + 0x6b, 0xe0, 0x01, 0xe5, 0x0c, 0x0a, 0xe5, 0x00, + 0x0a, 0x80, 0xe5, 0x1e, 0x86, 0x80, 0xe5, 0x16, + 0x00, 0x16, 0xe5, 0x1c, 0x60, 0xe5, 0x00, 0x16, + 0x8a, 0xe0, 0x22, 0xe1, 0x20, 0xe2, 0x20, 0xe5, + 0x46, 0x20, 0xe9, 0x02, 0xa0, 0xe1, 0x1c, 0x60, + 0xe2, 0x1c, 0x60, 0xe5, 0x20, 0xe0, 0x00, 0xe5, + 0x2c, 0xe0, 0x03, 0x16, 0xe1, 0x03, 0x00, 0xe1, + 0x07, 0x00, 0xc1, 0x00, 0x21, 0x00, 0xe2, 0x03, + 0x00, 0xe2, 0x07, 0x00, 0xc2, 0x00, 0x22, 0x40, + 0xe5, 0x2c, 0xe0, 0x04, 0xe5, 0x80, 0xaf, 0xe0, + 0x01, 0xe5, 0x0e, 0xe0, 0x02, 0xe5, 0x00, 0xe0, + 0x10, 0xa4, 0x00, 0xe4, 0x22, 0x00, 0xe4, 0x01, + 0xe0, 0x3d, 0xa5, 0x20, 0x05, 0x00, 0xe5, 0x24, + 0x00, 0x25, 0x40, 0x05, 0x20, 0xe5, 0x0f, 0x00, + 0x16, 0xeb, 0x00, 0xe5, 0x0f, 0x2f, 0xcb, 0xe5, + 0x17, 0xe0, 0x00, 0xeb, 0x01, 0xe0, 0x28, 0xe5, + 0x0b, 0x00, 0x25, 0x80, 0x8b, 0xe5, 0x0e, 0xab, + 0x40, 0x16, 0xe5, 0x12, 0x80, 0x16, 0xe0, 0x38, + 0xe5, 0x30, 0x60, 0x2b, 0x25, 0xeb, 0x08, 0x20, + 0xeb, 0x26, 0x05, 0x46, 0x00, 0x26, 0x80, 0x66, + 0x65, 0x00, 0x45, 0x00, 0xe5, 0x15, 0x20, 0x46, + 0x60, 0x06, 0xeb, 0x01, 0xc0, 0xf6, 0x01, 0xc0, + 0xe5, 0x15, 0x2b, 0x16, 0xe5, 0x15, 0x4b, 0xe0, + 0x18, 0xe5, 0x00, 0x0f, 0xe5, 0x14, 0x26, 0x60, + 0x8b, 0xd6, 0xe0, 0x01, 0xe5, 0x2e, 0x40, 0xd6, + 0xe5, 0x0e, 0x20, 0xeb, 0x00, 0xe5, 0x0b, 0x80, + 0xeb, 0x00, 0xe5, 0x0a, 0xc0, 0x76, 0xe0, 0x04, + 0xcb, 0xe0, 0x48, 0xe5, 0x41, 0xe0, 0x2f, 0xe1, + 0x2b, 0xe0, 0x05, 0xe2, 0x2b, 0xc0, 0xab, 0xe5, + 0x1c, 0x66, 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xe9, + 0x02, 0x65, 0x04, 0x05, 0xe1, 0x0e, 0x40, 0x86, + 0x11, 0x04, 0xe2, 0x0e, 0xe0, 0x00, 0x2c, 0xe0, + 0x80, 0x48, 0xeb, 0x17, 0x00, 0xe5, 0x22, 0x00, + 0x26, 0x11, 0x20, 0x25, 0xe0, 0x08, 0x45, 0xe0, + 0x2f, 0x66, 0xe5, 0x15, 0xeb, 0x02, 0x05, 0xe0, + 0x00, 0xe5, 0x0e, 0xe6, 0x03, 0x6b, 0x96, 0xe0, + 0x0e, 0xe5, 0x0a, 0x66, 0x76, 0xe0, 0x1e, 0xe5, + 0x0d, 0xcb, 0xe0, 0x0c, 0xe5, 0x0f, 0xe0, 0x01, + 0x07, 0x06, 0x07, 0xe5, 0x2d, 0xe6, 0x07, 0xd6, + 0x60, 0xeb, 0x0c, 0xe9, 0x02, 0x06, 0x25, 0x26, + 0x05, 0xe0, 0x01, 0x46, 0x07, 0xe5, 0x25, 0x47, + 0x66, 0x27, 0x26, 0x36, 0x1b, 0x76, 0x06, 0xe0, + 0x02, 0x1b, 0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, + 0xa0, 0x46, 0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, + 0x00, 0xe9, 0x02, 0x76, 0x05, 0x27, 0x05, 0xe0, + 0x00, 0xe5, 0x1b, 0x06, 0x36, 0x05, 0xe0, 0x01, + 0x26, 0x07, 0xe5, 0x28, 0x47, 0xe6, 0x01, 0x27, + 0x65, 0x76, 0x66, 0x16, 0x07, 0x06, 0xe9, 0x02, + 0x05, 0x16, 0x05, 0x56, 0x00, 0xeb, 0x0c, 0xe0, + 0x03, 0xe5, 0x0a, 0x00, 0xe5, 0x11, 0x47, 0x46, + 0x27, 0x06, 0x07, 0x26, 0xb6, 0x06, 0x25, 0x06, + 0xe0, 0x36, 0xc5, 0x00, 0x05, 0x00, 0x65, 0x00, + 0xe5, 0x07, 0x00, 0xe5, 0x02, 0x16, 0xa0, 0xe5, + 0x27, 0x06, 0x47, 0xe6, 0x00, 0x80, 0xe9, 0x02, + 0xa0, 0x26, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, + 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, + 0x85, 0x00, 0x26, 0x05, 0x27, 0x06, 0x67, 0x20, + 0x27, 0x20, 0x47, 0x20, 0x05, 0xa0, 0x07, 0x80, + 0x85, 0x27, 0x20, 0xc6, 0x40, 0x86, 0xe0, 0x03, + 0xe5, 0x02, 0x00, 0x05, 0x20, 0x05, 0x00, 0xe5, + 0x1e, 0x00, 0x05, 0x47, 0xa6, 0x00, 0x07, 0x20, + 0x07, 0x00, 0x67, 0x00, 0x27, 0x06, 0x07, 0x06, + 0x05, 0x06, 0x05, 0x36, 0x00, 0x36, 0xe0, 0x00, + 0x26, 0xe0, 0x15, 0xe5, 0x2d, 0x47, 0xe6, 0x00, + 0x27, 0x46, 0x07, 0x06, 0x65, 0x96, 0xe9, 0x02, + 0x36, 0x00, 0x16, 0x06, 0x45, 0xe0, 0x16, 0xe5, + 0x28, 0x47, 0xa6, 0x07, 0x06, 0x67, 0x26, 0x07, + 0x26, 0x25, 0x16, 0x05, 0xe0, 0x00, 0xe9, 0x02, + 0xe0, 0x80, 0x1e, 0xe5, 0x27, 0x47, 0x66, 0x20, + 0x67, 0x26, 0x07, 0x26, 0xf6, 0x0f, 0x65, 0x26, + 0xe0, 0x1a, 0xe5, 0x28, 0x47, 0xe6, 0x00, 0x27, + 0x06, 0x07, 0x26, 0x56, 0x05, 0xe0, 0x03, 0xe9, + 0x02, 0xa0, 0xf6, 0x05, 0xe0, 0x0b, 0xe5, 0x23, + 0x06, 0x07, 0x06, 0x27, 0xa6, 0x07, 0x06, 0x05, + 0x16, 0xa0, 0xe9, 0x02, 0xa0, 0xe9, 0x0c, 0xe0, + 0x14, 0xe5, 0x13, 0x20, 0x06, 0x07, 0x06, 0x27, + 0x66, 0x07, 0x86, 0x60, 0xe9, 0x02, 0x2b, 0x56, + 0x0f, 0xc5, 0xe0, 0x80, 0x31, 0xe5, 0x24, 0x47, + 0xe6, 0x01, 0x07, 0x26, 0x16, 0xe0, 0x5c, 0xe1, + 0x18, 0xe2, 0x18, 0xe9, 0x02, 0xeb, 0x01, 0xe0, + 0x04, 0xe5, 0x00, 0x20, 0x05, 0x20, 0xe5, 0x00, + 0x00, 0x25, 0x00, 0xe5, 0x10, 0xa7, 0x00, 0x27, + 0x20, 0x26, 0x07, 0x06, 0x05, 0x07, 0x05, 0x07, + 0x06, 0x56, 0xe0, 0x01, 0xe9, 0x02, 0xe0, 0x3e, + 0xe5, 0x00, 0x20, 0xe5, 0x1f, 0x47, 0x66, 0x20, + 0x26, 0x67, 0x06, 0x05, 0x16, 0x05, 0x07, 0xe0, + 0x13, 0x05, 0xe6, 0x02, 0xe5, 0x20, 0xa6, 0x07, + 0x05, 0x66, 0xf6, 0x00, 0x06, 0xe0, 0x00, 0x05, + 0xa6, 0x27, 0x46, 0xe5, 0x26, 0xe6, 0x05, 0x07, + 0x26, 0x56, 0x05, 0x96, 0xe0, 0x05, 0xe5, 0x41, + 0xc0, 0xf6, 0x02, 0xe0, 0x80, 0x2e, 0xe5, 0x19, + 0x16, 0xe0, 0x06, 0xe9, 0x02, 0xa0, 0xe5, 0x01, + 0x00, 0xe5, 0x1d, 0x07, 0xc6, 0x00, 0xa6, 0x07, + 0x06, 0x05, 0x96, 0xe0, 0x02, 0xe9, 0x02, 0xeb, + 0x0b, 0x40, 0x36, 0xe5, 0x16, 0x20, 0xe6, 0x0e, + 0x00, 0x07, 0xc6, 0x07, 0x26, 0x07, 0x26, 0xe0, + 0x41, 0xc5, 0x00, 0x25, 0x00, 0xe5, 0x1e, 0xa6, + 0x40, 0x06, 0x00, 0x26, 0x00, 0xc6, 0x05, 0x06, + 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xa5, 0x00, 0x25, + 0x00, 0xe5, 0x18, 0x87, 0x00, 0x26, 0x00, 0x27, + 0x06, 0x07, 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, + 0x80, 0xae, 0xe5, 0x0b, 0x26, 0x27, 0x36, 0xc0, + 0x26, 0x05, 0x07, 0xe5, 0x05, 0x00, 0xe5, 0x1a, + 0x27, 0x86, 0x40, 0x27, 0x06, 0x07, 0x06, 0xf6, + 0x05, 0xe9, 0x02, 0x06, 0xe0, 0x4d, 0x05, 0xe0, + 0x07, 0xeb, 0x0d, 0xef, 0x00, 0x6d, 0xef, 0x09, + 0xe0, 0x05, 0x16, 0xe5, 0x83, 0x12, 0xe0, 0x5e, + 0xea, 0x67, 0x00, 0x96, 0xe0, 0x03, 0xe5, 0x80, + 0x3c, 0xe0, 0x89, 0xc4, 0xe5, 0x59, 0x36, 0xe0, + 0x05, 0xe5, 0x83, 0xa8, 0xfb, 0x08, 0x06, 0xa5, + 0xe6, 0x07, 0xe0, 0x02, 0xe5, 0x8f, 0x13, 0x80, + 0xe5, 0x81, 0xbf, 0xe0, 0x9a, 0x31, 0xe5, 0x16, + 0xe6, 0x04, 0x47, 0x46, 0xe9, 0x02, 0xe0, 0x86, + 0x3e, 0xe5, 0x81, 0xb1, 0xc0, 0xe5, 0x17, 0x00, + 0xe9, 0x02, 0x60, 0x36, 0xe5, 0x47, 0x00, 0xe9, + 0x02, 0xa0, 0xe5, 0x16, 0x20, 0x86, 0x16, 0xe0, + 0x02, 0xe5, 0x28, 0xc6, 0x96, 0x6f, 0x64, 0x16, + 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, 0xcb, 0x00, + 0xe5, 0x0d, 0x80, 0xe5, 0x0b, 0xe0, 0x81, 0x28, + 0x44, 0xe5, 0x20, 0x24, 0x56, 0xe9, 0x02, 0xe0, + 0x80, 0x3e, 0xe1, 0x18, 0xe2, 0x18, 0xeb, 0x0f, + 0x76, 0xe0, 0x5d, 0xe5, 0x43, 0x60, 0x06, 0x05, + 0xe7, 0x2f, 0xc0, 0x66, 0xe4, 0x05, 0xe0, 0x38, + 0x24, 0x16, 0x04, 0x06, 0xe0, 0x03, 0x27, 0xe0, + 0x06, 0xe5, 0x97, 0x70, 0xe0, 0x00, 0xe5, 0x84, + 0x4e, 0xe0, 0x21, 0xe5, 0x02, 0xe0, 0xa2, 0x5f, + 0x64, 0x00, 0xc4, 0x00, 0x24, 0x00, 0xe5, 0x80, + 0x9b, 0xe0, 0x07, 0x05, 0xe0, 0x15, 0x45, 0x20, + 0x05, 0xe0, 0x06, 0x65, 0xe0, 0x00, 0xe5, 0x81, + 0x04, 0xe0, 0x88, 0x7c, 0xe5, 0x63, 0x80, 0xe5, + 0x05, 0x40, 0xe5, 0x01, 0xc0, 0xe5, 0x02, 0x20, + 0x0f, 0x26, 0x16, 0x7b, 0xe0, 0x8e, 0xd4, 0xef, + 0x80, 0x68, 0xe9, 0x02, 0xa0, 0xef, 0x81, 0x2c, + 0xe0, 0x44, 0xe6, 0x26, 0x20, 0xe6, 0x0f, 0xe0, + 0x01, 0xef, 0x6c, 0xe0, 0x34, 0xef, 0x80, 0x6e, + 0xe0, 0x02, 0xef, 0x1f, 0x20, 0xef, 0x34, 0x27, + 0x46, 0x4f, 0xa7, 0xfb, 0x00, 0xe6, 0x00, 0x2f, + 0xc6, 0xef, 0x16, 0x66, 0xef, 0x35, 0xe0, 0x0d, + 0xef, 0x3a, 0x46, 0x0f, 0xe0, 0x72, 0xeb, 0x0c, + 0xe0, 0x04, 0xeb, 0x0c, 0xe0, 0x04, 0xef, 0x4f, + 0xe0, 0x01, 0xeb, 0x11, 0xe0, 0x7f, 0xe1, 0x12, + 0xe2, 0x12, 0xe1, 0x12, 0xc2, 0x00, 0xe2, 0x0a, + 0xe1, 0x12, 0xe2, 0x12, 0x01, 0x00, 0x21, 0x20, + 0x01, 0x20, 0x21, 0x20, 0x61, 0x00, 0xe1, 0x00, + 0x62, 0x00, 0x02, 0x00, 0xc2, 0x00, 0xe2, 0x03, + 0xe1, 0x12, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x20, + 0xe1, 0x00, 0x00, 0xc1, 0x00, 0xe2, 0x12, 0x21, + 0x00, 0x61, 0x00, 0x81, 0x00, 0x01, 0x40, 0xc1, + 0x00, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x14, 0x20, 0xe1, 0x11, 0x0c, 0xe2, + 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, + 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, + 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, + 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0x3f, + 0x20, 0xe9, 0x2a, 0xef, 0x81, 0x78, 0xe6, 0x2f, + 0x6f, 0xe6, 0x2a, 0xef, 0x00, 0x06, 0xef, 0x06, + 0x06, 0x2f, 0x96, 0xe0, 0x07, 0x86, 0x00, 0xe6, + 0x07, 0xe0, 0x83, 0xc8, 0xe2, 0x02, 0x05, 0xe2, + 0x0c, 0xa0, 0xa2, 0xe0, 0x80, 0x4d, 0xc6, 0x00, + 0xe6, 0x09, 0x20, 0xc6, 0x00, 0x26, 0x00, 0x86, + 0x80, 0xe4, 0x36, 0xe0, 0x19, 0x06, 0xe0, 0x68, + 0xe5, 0x25, 0x40, 0xc6, 0xc4, 0x20, 0xe9, 0x02, + 0x60, 0x05, 0x0f, 0xe0, 0x80, 0xb8, 0xe5, 0x16, + 0x06, 0xe0, 0x09, 0xe5, 0x24, 0x66, 0xe9, 0x02, + 0x80, 0x0d, 0xe0, 0x81, 0x48, 0xe5, 0x13, 0x04, + 0x66, 0xe9, 0x02, 0xe0, 0x80, 0x4e, 0xe5, 0x16, + 0x26, 0x05, 0xe9, 0x02, 0x60, 0x16, 0xe0, 0x81, + 0x58, 0xc5, 0x00, 0x65, 0x00, 0x25, 0x00, 0xe5, + 0x07, 0x00, 0xe5, 0x80, 0x3d, 0x20, 0xeb, 0x01, + 0xc6, 0xe0, 0x21, 0xe1, 0x1a, 0xe2, 0x1a, 0xc6, + 0x04, 0x60, 0xe9, 0x02, 0x60, 0x36, 0xe0, 0x82, + 0x89, 0xeb, 0x33, 0x0f, 0x4b, 0x0d, 0x6b, 0xe0, + 0x44, 0xeb, 0x25, 0x0f, 0xeb, 0x07, 0xe0, 0x80, + 0x3a, 0x65, 0x00, 0xe5, 0x13, 0x00, 0x25, 0x00, + 0x05, 0x20, 0x05, 0x00, 0xe5, 0x02, 0x00, 0x65, + 0x00, 0x05, 0x00, 0x05, 0xa0, 0x05, 0x60, 0x05, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x45, 0x00, 0x25, + 0x00, 0x05, 0x20, 0x05, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x25, 0x00, 0x05, + 0x20, 0x65, 0x00, 0xc5, 0x00, 0x65, 0x00, 0x65, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x00, 0xe5, 0x09, + 0x80, 0x45, 0x00, 0x85, 0x00, 0xe5, 0x09, 0xe0, + 0x2c, 0x2c, 0xe0, 0x80, 0x86, 0xef, 0x24, 0x60, + 0xef, 0x5c, 0xe0, 0x04, 0xef, 0x07, 0x20, 0xef, + 0x07, 0x00, 0xef, 0x07, 0x00, 0xef, 0x1d, 0xe0, + 0x02, 0xeb, 0x05, 0xef, 0x80, 0x19, 0xe0, 0x30, + 0xef, 0x15, 0xe0, 0x05, 0xef, 0x24, 0x60, 0xef, + 0x01, 0xc0, 0x2f, 0xe0, 0x06, 0xaf, 0xe0, 0x80, + 0x12, 0xef, 0x80, 0x73, 0x8e, 0xef, 0x82, 0x50, + 0x60, 0xef, 0x09, 0x40, 0xef, 0x05, 0x40, 0xef, + 0x6f, 0x60, 0xef, 0x57, 0xa0, 0xef, 0x04, 0x60, + 0x0f, 0xe0, 0x07, 0xef, 0x04, 0x60, 0xef, 0x30, + 0xe0, 0x00, 0xef, 0x02, 0xa0, 0xef, 0x20, 0xe0, + 0x00, 0xef, 0x16, 0x20, 0xef, 0x04, 0x60, 0x2f, + 0xe0, 0x36, 0xef, 0x80, 0xcc, 0xe0, 0x04, 0xef, + 0x06, 0x20, 0xef, 0x05, 0x40, 0xef, 0x02, 0x80, + 0xef, 0x30, 0xc0, 0xef, 0x07, 0x20, 0xef, 0x03, + 0xa0, 0xef, 0x01, 0xc0, 0xef, 0x80, 0x0b, 0x00, + 0xef, 0x54, 0xe9, 0x02, 0xe0, 0x83, 0x7e, 0xe5, + 0xc0, 0x66, 0x58, 0xe0, 0x18, 0xe5, 0x8f, 0xb2, + 0xa0, 0xe5, 0x80, 0x56, 0x20, 0xe5, 0x95, 0xfa, + 0xe0, 0x06, 0xe5, 0x9c, 0xa9, 0xe0, 0x07, 0xe5, + 0x81, 0xe6, 0xe0, 0x89, 0x1a, 0xe5, 0x81, 0x96, + 0xe0, 0x85, 0x5a, 0xe5, 0x92, 0xc3, 0x80, 0xe5, + 0x8f, 0xd8, 0xe0, 0xca, 0x9b, 0xc9, 0x1b, 0xe0, + 0x16, 0xfb, 0x58, 0xe0, 0x78, 0xe6, 0x80, 0x68, + 0xe0, 0xc0, 0xbd, 0x88, 0xfd, 0xc0, 0xbf, 0x76, + 0x20, 0xfd, 0xc0, 0xbf, 0x76, 0x20, +}; + +typedef enum { + UNICODE_SCRIPT_Unknown, + UNICODE_SCRIPT_Adlam, + UNICODE_SCRIPT_Ahom, + UNICODE_SCRIPT_Anatolian_Hieroglyphs, + UNICODE_SCRIPT_Arabic, + UNICODE_SCRIPT_Armenian, + UNICODE_SCRIPT_Avestan, + UNICODE_SCRIPT_Balinese, + UNICODE_SCRIPT_Bamum, + UNICODE_SCRIPT_Bassa_Vah, + UNICODE_SCRIPT_Batak, + UNICODE_SCRIPT_Bengali, + UNICODE_SCRIPT_Bhaiksuki, + UNICODE_SCRIPT_Bopomofo, + UNICODE_SCRIPT_Brahmi, + UNICODE_SCRIPT_Braille, + UNICODE_SCRIPT_Buginese, + UNICODE_SCRIPT_Buhid, + UNICODE_SCRIPT_Canadian_Aboriginal, + UNICODE_SCRIPT_Carian, + UNICODE_SCRIPT_Caucasian_Albanian, + UNICODE_SCRIPT_Chakma, + UNICODE_SCRIPT_Cham, + UNICODE_SCRIPT_Cherokee, + UNICODE_SCRIPT_Chorasmian, + UNICODE_SCRIPT_Common, + UNICODE_SCRIPT_Coptic, + UNICODE_SCRIPT_Cuneiform, + UNICODE_SCRIPT_Cypriot, + UNICODE_SCRIPT_Cyrillic, + UNICODE_SCRIPT_Cypro_Minoan, + UNICODE_SCRIPT_Deseret, + UNICODE_SCRIPT_Devanagari, + UNICODE_SCRIPT_Dives_Akuru, + UNICODE_SCRIPT_Dogra, + UNICODE_SCRIPT_Duployan, + UNICODE_SCRIPT_Egyptian_Hieroglyphs, + UNICODE_SCRIPT_Elbasan, + UNICODE_SCRIPT_Elymaic, + UNICODE_SCRIPT_Ethiopic, + UNICODE_SCRIPT_Garay, + UNICODE_SCRIPT_Georgian, + UNICODE_SCRIPT_Glagolitic, + UNICODE_SCRIPT_Gothic, + UNICODE_SCRIPT_Grantha, + UNICODE_SCRIPT_Greek, + UNICODE_SCRIPT_Gujarati, + UNICODE_SCRIPT_Gunjala_Gondi, + UNICODE_SCRIPT_Gurmukhi, + UNICODE_SCRIPT_Gurung_Khema, + UNICODE_SCRIPT_Han, + UNICODE_SCRIPT_Hangul, + UNICODE_SCRIPT_Hanifi_Rohingya, + UNICODE_SCRIPT_Hanunoo, + UNICODE_SCRIPT_Hatran, + UNICODE_SCRIPT_Hebrew, + UNICODE_SCRIPT_Hiragana, + UNICODE_SCRIPT_Imperial_Aramaic, + UNICODE_SCRIPT_Inherited, + UNICODE_SCRIPT_Inscriptional_Pahlavi, + UNICODE_SCRIPT_Inscriptional_Parthian, + UNICODE_SCRIPT_Javanese, + UNICODE_SCRIPT_Kaithi, + UNICODE_SCRIPT_Kannada, + UNICODE_SCRIPT_Katakana, + UNICODE_SCRIPT_Kawi, + UNICODE_SCRIPT_Kayah_Li, + UNICODE_SCRIPT_Kharoshthi, + UNICODE_SCRIPT_Khmer, + UNICODE_SCRIPT_Khojki, + UNICODE_SCRIPT_Khitan_Small_Script, + UNICODE_SCRIPT_Khudawadi, + UNICODE_SCRIPT_Kirat_Rai, + UNICODE_SCRIPT_Lao, + UNICODE_SCRIPT_Latin, + UNICODE_SCRIPT_Lepcha, + UNICODE_SCRIPT_Limbu, + UNICODE_SCRIPT_Linear_A, + UNICODE_SCRIPT_Linear_B, + UNICODE_SCRIPT_Lisu, + UNICODE_SCRIPT_Lycian, + UNICODE_SCRIPT_Lydian, + UNICODE_SCRIPT_Makasar, + UNICODE_SCRIPT_Mahajani, + UNICODE_SCRIPT_Malayalam, + UNICODE_SCRIPT_Mandaic, + UNICODE_SCRIPT_Manichaean, + UNICODE_SCRIPT_Marchen, + UNICODE_SCRIPT_Masaram_Gondi, + UNICODE_SCRIPT_Medefaidrin, + UNICODE_SCRIPT_Meetei_Mayek, + UNICODE_SCRIPT_Mende_Kikakui, + UNICODE_SCRIPT_Meroitic_Cursive, + UNICODE_SCRIPT_Meroitic_Hieroglyphs, + UNICODE_SCRIPT_Miao, + UNICODE_SCRIPT_Modi, + UNICODE_SCRIPT_Mongolian, + UNICODE_SCRIPT_Mro, + UNICODE_SCRIPT_Multani, + UNICODE_SCRIPT_Myanmar, + UNICODE_SCRIPT_Nabataean, + UNICODE_SCRIPT_Nag_Mundari, + UNICODE_SCRIPT_Nandinagari, + UNICODE_SCRIPT_New_Tai_Lue, + UNICODE_SCRIPT_Newa, + UNICODE_SCRIPT_Nko, + UNICODE_SCRIPT_Nushu, + UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong, + UNICODE_SCRIPT_Ogham, + UNICODE_SCRIPT_Ol_Chiki, + UNICODE_SCRIPT_Ol_Onal, + UNICODE_SCRIPT_Old_Hungarian, + UNICODE_SCRIPT_Old_Italic, + UNICODE_SCRIPT_Old_North_Arabian, + UNICODE_SCRIPT_Old_Permic, + UNICODE_SCRIPT_Old_Persian, + UNICODE_SCRIPT_Old_Sogdian, + UNICODE_SCRIPT_Old_South_Arabian, + UNICODE_SCRIPT_Old_Turkic, + UNICODE_SCRIPT_Old_Uyghur, + UNICODE_SCRIPT_Oriya, + UNICODE_SCRIPT_Osage, + UNICODE_SCRIPT_Osmanya, + UNICODE_SCRIPT_Pahawh_Hmong, + UNICODE_SCRIPT_Palmyrene, + UNICODE_SCRIPT_Pau_Cin_Hau, + UNICODE_SCRIPT_Phags_Pa, + UNICODE_SCRIPT_Phoenician, + UNICODE_SCRIPT_Psalter_Pahlavi, + UNICODE_SCRIPT_Rejang, + UNICODE_SCRIPT_Runic, + UNICODE_SCRIPT_Samaritan, + UNICODE_SCRIPT_Saurashtra, + UNICODE_SCRIPT_Sharada, + UNICODE_SCRIPT_Shavian, + UNICODE_SCRIPT_Siddham, + UNICODE_SCRIPT_SignWriting, + UNICODE_SCRIPT_Sinhala, + UNICODE_SCRIPT_Sogdian, + UNICODE_SCRIPT_Sora_Sompeng, + UNICODE_SCRIPT_Soyombo, + UNICODE_SCRIPT_Sundanese, + UNICODE_SCRIPT_Sunuwar, + UNICODE_SCRIPT_Syloti_Nagri, + UNICODE_SCRIPT_Syriac, + UNICODE_SCRIPT_Tagalog, + UNICODE_SCRIPT_Tagbanwa, + UNICODE_SCRIPT_Tai_Le, + UNICODE_SCRIPT_Tai_Tham, + UNICODE_SCRIPT_Tai_Viet, + UNICODE_SCRIPT_Takri, + UNICODE_SCRIPT_Tamil, + UNICODE_SCRIPT_Tangut, + UNICODE_SCRIPT_Telugu, + UNICODE_SCRIPT_Thaana, + UNICODE_SCRIPT_Thai, + UNICODE_SCRIPT_Tibetan, + UNICODE_SCRIPT_Tifinagh, + UNICODE_SCRIPT_Tirhuta, + UNICODE_SCRIPT_Tangsa, + UNICODE_SCRIPT_Todhri, + UNICODE_SCRIPT_Toto, + UNICODE_SCRIPT_Tulu_Tigalari, + UNICODE_SCRIPT_Ugaritic, + UNICODE_SCRIPT_Vai, + UNICODE_SCRIPT_Vithkuqi, + UNICODE_SCRIPT_Wancho, + UNICODE_SCRIPT_Warang_Citi, + UNICODE_SCRIPT_Yezidi, + UNICODE_SCRIPT_Yi, + UNICODE_SCRIPT_Zanabazar_Square, + UNICODE_SCRIPT_COUNT, +} UnicodeScriptEnum; + +static const char unicode_script_name_table[] = + "Unknown,Zzzz" "\0" + "Adlam,Adlm" "\0" + "Ahom,Ahom" "\0" + "Anatolian_Hieroglyphs,Hluw" "\0" + "Arabic,Arab" "\0" + "Armenian,Armn" "\0" + "Avestan,Avst" "\0" + "Balinese,Bali" "\0" + "Bamum,Bamu" "\0" + "Bassa_Vah,Bass" "\0" + "Batak,Batk" "\0" + "Bengali,Beng" "\0" + "Bhaiksuki,Bhks" "\0" + "Bopomofo,Bopo" "\0" + "Brahmi,Brah" "\0" + "Braille,Brai" "\0" + "Buginese,Bugi" "\0" + "Buhid,Buhd" "\0" + "Canadian_Aboriginal,Cans" "\0" + "Carian,Cari" "\0" + "Caucasian_Albanian,Aghb" "\0" + "Chakma,Cakm" "\0" + "Cham,Cham" "\0" + "Cherokee,Cher" "\0" + "Chorasmian,Chrs" "\0" + "Common,Zyyy" "\0" + "Coptic,Copt,Qaac" "\0" + "Cuneiform,Xsux" "\0" + "Cypriot,Cprt" "\0" + "Cyrillic,Cyrl" "\0" + "Cypro_Minoan,Cpmn" "\0" + "Deseret,Dsrt" "\0" + "Devanagari,Deva" "\0" + "Dives_Akuru,Diak" "\0" + "Dogra,Dogr" "\0" + "Duployan,Dupl" "\0" + "Egyptian_Hieroglyphs,Egyp" "\0" + "Elbasan,Elba" "\0" + "Elymaic,Elym" "\0" + "Ethiopic,Ethi" "\0" + "Garay,Gara" "\0" + "Georgian,Geor" "\0" + "Glagolitic,Glag" "\0" + "Gothic,Goth" "\0" + "Grantha,Gran" "\0" + "Greek,Grek" "\0" + "Gujarati,Gujr" "\0" + "Gunjala_Gondi,Gong" "\0" + "Gurmukhi,Guru" "\0" + "Gurung_Khema,Gukh" "\0" + "Han,Hani" "\0" + "Hangul,Hang" "\0" + "Hanifi_Rohingya,Rohg" "\0" + "Hanunoo,Hano" "\0" + "Hatran,Hatr" "\0" + "Hebrew,Hebr" "\0" + "Hiragana,Hira" "\0" + "Imperial_Aramaic,Armi" "\0" + "Inherited,Zinh,Qaai" "\0" + "Inscriptional_Pahlavi,Phli" "\0" + "Inscriptional_Parthian,Prti" "\0" + "Javanese,Java" "\0" + "Kaithi,Kthi" "\0" + "Kannada,Knda" "\0" + "Katakana,Kana" "\0" + "Kawi,Kawi" "\0" + "Kayah_Li,Kali" "\0" + "Kharoshthi,Khar" "\0" + "Khmer,Khmr" "\0" + "Khojki,Khoj" "\0" + "Khitan_Small_Script,Kits" "\0" + "Khudawadi,Sind" "\0" + "Kirat_Rai,Krai" "\0" + "Lao,Laoo" "\0" + "Latin,Latn" "\0" + "Lepcha,Lepc" "\0" + "Limbu,Limb" "\0" + "Linear_A,Lina" "\0" + "Linear_B,Linb" "\0" + "Lisu,Lisu" "\0" + "Lycian,Lyci" "\0" + "Lydian,Lydi" "\0" + "Makasar,Maka" "\0" + "Mahajani,Mahj" "\0" + "Malayalam,Mlym" "\0" + "Mandaic,Mand" "\0" + "Manichaean,Mani" "\0" + "Marchen,Marc" "\0" + "Masaram_Gondi,Gonm" "\0" + "Medefaidrin,Medf" "\0" + "Meetei_Mayek,Mtei" "\0" + "Mende_Kikakui,Mend" "\0" + "Meroitic_Cursive,Merc" "\0" + "Meroitic_Hieroglyphs,Mero" "\0" + "Miao,Plrd" "\0" + "Modi,Modi" "\0" + "Mongolian,Mong" "\0" + "Mro,Mroo" "\0" + "Multani,Mult" "\0" + "Myanmar,Mymr" "\0" + "Nabataean,Nbat" "\0" + "Nag_Mundari,Nagm" "\0" + "Nandinagari,Nand" "\0" + "New_Tai_Lue,Talu" "\0" + "Newa,Newa" "\0" + "Nko,Nkoo" "\0" + "Nushu,Nshu" "\0" + "Nyiakeng_Puachue_Hmong,Hmnp" "\0" + "Ogham,Ogam" "\0" + "Ol_Chiki,Olck" "\0" + "Ol_Onal,Onao" "\0" + "Old_Hungarian,Hung" "\0" + "Old_Italic,Ital" "\0" + "Old_North_Arabian,Narb" "\0" + "Old_Permic,Perm" "\0" + "Old_Persian,Xpeo" "\0" + "Old_Sogdian,Sogo" "\0" + "Old_South_Arabian,Sarb" "\0" + "Old_Turkic,Orkh" "\0" + "Old_Uyghur,Ougr" "\0" + "Oriya,Orya" "\0" + "Osage,Osge" "\0" + "Osmanya,Osma" "\0" + "Pahawh_Hmong,Hmng" "\0" + "Palmyrene,Palm" "\0" + "Pau_Cin_Hau,Pauc" "\0" + "Phags_Pa,Phag" "\0" + "Phoenician,Phnx" "\0" + "Psalter_Pahlavi,Phlp" "\0" + "Rejang,Rjng" "\0" + "Runic,Runr" "\0" + "Samaritan,Samr" "\0" + "Saurashtra,Saur" "\0" + "Sharada,Shrd" "\0" + "Shavian,Shaw" "\0" + "Siddham,Sidd" "\0" + "SignWriting,Sgnw" "\0" + "Sinhala,Sinh" "\0" + "Sogdian,Sogd" "\0" + "Sora_Sompeng,Sora" "\0" + "Soyombo,Soyo" "\0" + "Sundanese,Sund" "\0" + "Sunuwar,Sunu" "\0" + "Syloti_Nagri,Sylo" "\0" + "Syriac,Syrc" "\0" + "Tagalog,Tglg" "\0" + "Tagbanwa,Tagb" "\0" + "Tai_Le,Tale" "\0" + "Tai_Tham,Lana" "\0" + "Tai_Viet,Tavt" "\0" + "Takri,Takr" "\0" + "Tamil,Taml" "\0" + "Tangut,Tang" "\0" + "Telugu,Telu" "\0" + "Thaana,Thaa" "\0" + "Thai,Thai" "\0" + "Tibetan,Tibt" "\0" + "Tifinagh,Tfng" "\0" + "Tirhuta,Tirh" "\0" + "Tangsa,Tnsa" "\0" + "Todhri,Todr" "\0" + "Toto,Toto" "\0" + "Tulu_Tigalari,Tutg" "\0" + "Ugaritic,Ugar" "\0" + "Vai,Vaii" "\0" + "Vithkuqi,Vith" "\0" + "Wancho,Wcho" "\0" + "Warang_Citi,Wara" "\0" + "Yezidi,Yezi" "\0" + "Yi,Yiii" "\0" + "Zanabazar_Square,Zanb" "\0" +; + +static const uint8_t unicode_script_table[2803] = { + 0xc0, 0x19, 0x99, 0x4a, 0x85, 0x19, 0x99, 0x4a, + 0xae, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x80, 0x4a, + 0x84, 0x19, 0x96, 0x4a, 0x80, 0x19, 0x9e, 0x4a, + 0x80, 0x19, 0xe1, 0x60, 0x4a, 0xa6, 0x19, 0x84, + 0x4a, 0x84, 0x19, 0x81, 0x0d, 0x93, 0x19, 0xe0, + 0x0f, 0x3a, 0x83, 0x2d, 0x80, 0x19, 0x82, 0x2d, + 0x01, 0x83, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x03, + 0x80, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x80, 0x19, + 0x82, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x93, 0x2d, + 0x00, 0xbe, 0x2d, 0x8d, 0x1a, 0x8f, 0x2d, 0xe0, + 0x24, 0x1d, 0x81, 0x3a, 0xe0, 0x48, 0x1d, 0x00, + 0xa5, 0x05, 0x01, 0xb1, 0x05, 0x01, 0x82, 0x05, + 0x00, 0xb6, 0x37, 0x07, 0x9a, 0x37, 0x03, 0x85, + 0x37, 0x0a, 0x84, 0x04, 0x80, 0x19, 0x85, 0x04, + 0x80, 0x19, 0x8d, 0x04, 0x80, 0x19, 0x82, 0x04, + 0x80, 0x19, 0x9f, 0x04, 0x80, 0x19, 0x89, 0x04, + 0x8a, 0x3a, 0x99, 0x04, 0x80, 0x3a, 0xe0, 0x0b, + 0x04, 0x80, 0x19, 0xa1, 0x04, 0x8d, 0x90, 0x00, + 0xbb, 0x90, 0x01, 0x82, 0x90, 0xaf, 0x04, 0xb1, + 0x9a, 0x0d, 0xba, 0x69, 0x01, 0x82, 0x69, 0xad, + 0x83, 0x01, 0x8e, 0x83, 0x00, 0x9b, 0x55, 0x01, + 0x80, 0x55, 0x00, 0x8a, 0x90, 0x04, 0x9e, 0x04, + 0x00, 0x81, 0x04, 0x04, 0xca, 0x04, 0x80, 0x19, + 0x9c, 0x04, 0xd0, 0x20, 0x83, 0x3a, 0x8e, 0x20, + 0x81, 0x19, 0x99, 0x20, 0x83, 0x0b, 0x00, 0x87, + 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x95, 0x0b, 0x00, + 0x86, 0x0b, 0x00, 0x80, 0x0b, 0x02, 0x83, 0x0b, + 0x01, 0x88, 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x83, + 0x0b, 0x07, 0x80, 0x0b, 0x03, 0x81, 0x0b, 0x00, + 0x84, 0x0b, 0x01, 0x98, 0x0b, 0x01, 0x82, 0x30, + 0x00, 0x85, 0x30, 0x03, 0x81, 0x30, 0x01, 0x95, + 0x30, 0x00, 0x86, 0x30, 0x00, 0x81, 0x30, 0x00, + 0x81, 0x30, 0x00, 0x81, 0x30, 0x01, 0x80, 0x30, + 0x00, 0x84, 0x30, 0x03, 0x81, 0x30, 0x01, 0x82, + 0x30, 0x02, 0x80, 0x30, 0x06, 0x83, 0x30, 0x00, + 0x80, 0x30, 0x06, 0x90, 0x30, 0x09, 0x82, 0x2e, + 0x00, 0x88, 0x2e, 0x00, 0x82, 0x2e, 0x00, 0x95, + 0x2e, 0x00, 0x86, 0x2e, 0x00, 0x81, 0x2e, 0x00, + 0x84, 0x2e, 0x01, 0x89, 0x2e, 0x00, 0x82, 0x2e, + 0x00, 0x82, 0x2e, 0x01, 0x80, 0x2e, 0x0e, 0x83, + 0x2e, 0x01, 0x8b, 0x2e, 0x06, 0x86, 0x2e, 0x00, + 0x82, 0x78, 0x00, 0x87, 0x78, 0x01, 0x81, 0x78, + 0x01, 0x95, 0x78, 0x00, 0x86, 0x78, 0x00, 0x81, + 0x78, 0x00, 0x84, 0x78, 0x01, 0x88, 0x78, 0x01, + 0x81, 0x78, 0x01, 0x82, 0x78, 0x06, 0x82, 0x78, + 0x03, 0x81, 0x78, 0x00, 0x84, 0x78, 0x01, 0x91, + 0x78, 0x09, 0x81, 0x97, 0x00, 0x85, 0x97, 0x02, + 0x82, 0x97, 0x00, 0x83, 0x97, 0x02, 0x81, 0x97, + 0x00, 0x80, 0x97, 0x00, 0x81, 0x97, 0x02, 0x81, + 0x97, 0x02, 0x82, 0x97, 0x02, 0x8b, 0x97, 0x03, + 0x84, 0x97, 0x02, 0x82, 0x97, 0x00, 0x83, 0x97, + 0x01, 0x80, 0x97, 0x05, 0x80, 0x97, 0x0d, 0x94, + 0x97, 0x04, 0x8c, 0x99, 0x00, 0x82, 0x99, 0x00, + 0x96, 0x99, 0x00, 0x8f, 0x99, 0x01, 0x88, 0x99, + 0x00, 0x82, 0x99, 0x00, 0x83, 0x99, 0x06, 0x81, + 0x99, 0x00, 0x82, 0x99, 0x01, 0x80, 0x99, 0x01, + 0x83, 0x99, 0x01, 0x89, 0x99, 0x06, 0x88, 0x99, + 0x8c, 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x96, 0x3f, + 0x00, 0x89, 0x3f, 0x00, 0x84, 0x3f, 0x01, 0x88, + 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x83, 0x3f, 0x06, + 0x81, 0x3f, 0x05, 0x81, 0x3f, 0x00, 0x83, 0x3f, + 0x01, 0x89, 0x3f, 0x00, 0x82, 0x3f, 0x0b, 0x8c, + 0x54, 0x00, 0x82, 0x54, 0x00, 0xb2, 0x54, 0x00, + 0x82, 0x54, 0x00, 0x85, 0x54, 0x03, 0x8f, 0x54, + 0x01, 0x99, 0x54, 0x00, 0x82, 0x89, 0x00, 0x91, + 0x89, 0x02, 0x97, 0x89, 0x00, 0x88, 0x89, 0x00, + 0x80, 0x89, 0x01, 0x86, 0x89, 0x02, 0x80, 0x89, + 0x03, 0x85, 0x89, 0x00, 0x80, 0x89, 0x00, 0x87, + 0x89, 0x05, 0x89, 0x89, 0x01, 0x82, 0x89, 0x0b, + 0xb9, 0x9b, 0x03, 0x80, 0x19, 0x9b, 0x9b, 0x24, + 0x81, 0x49, 0x00, 0x80, 0x49, 0x00, 0x84, 0x49, + 0x00, 0x97, 0x49, 0x00, 0x80, 0x49, 0x00, 0x96, + 0x49, 0x01, 0x84, 0x49, 0x00, 0x80, 0x49, 0x00, + 0x86, 0x49, 0x00, 0x89, 0x49, 0x01, 0x83, 0x49, + 0x1f, 0xc7, 0x9c, 0x00, 0xa3, 0x9c, 0x03, 0xa6, + 0x9c, 0x00, 0xa3, 0x9c, 0x00, 0x8e, 0x9c, 0x00, + 0x86, 0x9c, 0x83, 0x19, 0x81, 0x9c, 0x24, 0xe0, + 0x3f, 0x63, 0xa5, 0x29, 0x00, 0x80, 0x29, 0x04, + 0x80, 0x29, 0x01, 0xaa, 0x29, 0x80, 0x19, 0x83, + 0x29, 0xe0, 0x9f, 0x33, 0xc8, 0x27, 0x00, 0x83, + 0x27, 0x01, 0x86, 0x27, 0x00, 0x80, 0x27, 0x00, + 0x83, 0x27, 0x01, 0xa8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xa0, 0x27, 0x00, 0x83, 0x27, 0x01, 0x86, + 0x27, 0x00, 0x80, 0x27, 0x00, 0x83, 0x27, 0x01, + 0x8e, 0x27, 0x00, 0xb8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xc2, 0x27, 0x01, 0x9f, 0x27, 0x02, 0x99, + 0x27, 0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, + 0xe2, 0x1f, 0x12, 0x9c, 0x6c, 0x02, 0xca, 0x82, + 0x82, 0x19, 0x8a, 0x82, 0x06, 0x95, 0x91, 0x08, + 0x80, 0x91, 0x94, 0x35, 0x81, 0x19, 0x08, 0x93, + 0x11, 0x0b, 0x8c, 0x92, 0x00, 0x82, 0x92, 0x00, + 0x81, 0x92, 0x0b, 0xdd, 0x44, 0x01, 0x89, 0x44, + 0x05, 0x89, 0x44, 0x05, 0x81, 0x60, 0x81, 0x19, + 0x80, 0x60, 0x80, 0x19, 0x93, 0x60, 0x05, 0xd8, + 0x60, 0x06, 0xaa, 0x60, 0x04, 0xc5, 0x12, 0x09, + 0x9e, 0x4c, 0x00, 0x8b, 0x4c, 0x03, 0x8b, 0x4c, + 0x03, 0x80, 0x4c, 0x02, 0x8b, 0x4c, 0x9d, 0x93, + 0x01, 0x84, 0x93, 0x0a, 0xab, 0x67, 0x03, 0x99, + 0x67, 0x05, 0x8a, 0x67, 0x02, 0x81, 0x67, 0x9f, + 0x44, 0x9b, 0x10, 0x01, 0x81, 0x10, 0xbe, 0x94, + 0x00, 0x9c, 0x94, 0x01, 0x8a, 0x94, 0x05, 0x89, + 0x94, 0x05, 0x8d, 0x94, 0x01, 0x9e, 0x3a, 0x30, + 0xcc, 0x07, 0x00, 0xb1, 0x07, 0xbf, 0x8d, 0xb3, + 0x0a, 0x07, 0x83, 0x0a, 0xb7, 0x4b, 0x02, 0x8e, + 0x4b, 0x02, 0x82, 0x4b, 0xaf, 0x6d, 0x8a, 0x1d, + 0x04, 0xaa, 0x29, 0x01, 0x82, 0x29, 0x87, 0x8d, + 0x07, 0x82, 0x3a, 0x80, 0x19, 0x8c, 0x3a, 0x80, + 0x19, 0x86, 0x3a, 0x83, 0x19, 0x80, 0x3a, 0x85, + 0x19, 0x80, 0x3a, 0x82, 0x19, 0x81, 0x3a, 0x80, + 0x19, 0x04, 0xa5, 0x4a, 0x84, 0x2d, 0x80, 0x1d, + 0xb0, 0x4a, 0x84, 0x2d, 0x83, 0x4a, 0x84, 0x2d, + 0x8c, 0x4a, 0x80, 0x1d, 0xc5, 0x4a, 0x80, 0x2d, + 0xbf, 0x3a, 0xe0, 0x9f, 0x4a, 0x95, 0x2d, 0x01, + 0x85, 0x2d, 0x01, 0xa5, 0x2d, 0x01, 0x85, 0x2d, + 0x01, 0x87, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x80, + 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x9e, 0x2d, 0x01, + 0xb4, 0x2d, 0x00, 0x8e, 0x2d, 0x00, 0x8d, 0x2d, + 0x01, 0x85, 0x2d, 0x00, 0x92, 0x2d, 0x01, 0x82, + 0x2d, 0x00, 0x88, 0x2d, 0x00, 0x8b, 0x19, 0x81, + 0x3a, 0xd6, 0x19, 0x00, 0x8a, 0x19, 0x80, 0x4a, + 0x01, 0x8a, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x00, + 0x8c, 0x4a, 0x02, 0xa0, 0x19, 0x0e, 0xa0, 0x3a, + 0x0e, 0xa5, 0x19, 0x80, 0x2d, 0x82, 0x19, 0x81, + 0x4a, 0x85, 0x19, 0x80, 0x4a, 0x9a, 0x19, 0x80, + 0x4a, 0x90, 0x19, 0xa8, 0x4a, 0x82, 0x19, 0x03, + 0xe2, 0x39, 0x19, 0x15, 0x8a, 0x19, 0x14, 0xe3, + 0x3f, 0x19, 0xe0, 0x9f, 0x0f, 0xe2, 0x13, 0x19, + 0x01, 0x9f, 0x19, 0x00, 0xe0, 0x08, 0x19, 0xdf, + 0x2a, 0x9f, 0x4a, 0xe0, 0x13, 0x1a, 0x04, 0x86, + 0x1a, 0xa5, 0x29, 0x00, 0x80, 0x29, 0x04, 0x80, + 0x29, 0x01, 0xb7, 0x9d, 0x06, 0x81, 0x9d, 0x0d, + 0x80, 0x9d, 0x96, 0x27, 0x08, 0x86, 0x27, 0x00, + 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, + 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, + 0x27, 0x00, 0x86, 0x27, 0x00, 0x9f, 0x1d, 0xdd, + 0x19, 0x21, 0x99, 0x32, 0x00, 0xd8, 0x32, 0x0b, + 0xe0, 0x75, 0x32, 0x19, 0x94, 0x19, 0x80, 0x32, + 0x80, 0x19, 0x80, 0x32, 0x98, 0x19, 0x88, 0x32, + 0x83, 0x3a, 0x81, 0x33, 0x87, 0x19, 0x83, 0x32, + 0x83, 0x19, 0x00, 0xd5, 0x38, 0x01, 0x81, 0x3a, + 0x81, 0x19, 0x82, 0x38, 0x80, 0x19, 0xd9, 0x40, + 0x81, 0x19, 0x82, 0x40, 0x04, 0xaa, 0x0d, 0x00, + 0xdd, 0x33, 0x00, 0x8f, 0x19, 0x9f, 0x0d, 0xa5, + 0x19, 0x08, 0x80, 0x19, 0x8f, 0x40, 0x9e, 0x33, + 0x00, 0xbf, 0x19, 0x9e, 0x33, 0xd0, 0x19, 0xae, + 0x40, 0x80, 0x19, 0xd7, 0x40, 0xe0, 0x47, 0x19, + 0xf0, 0x09, 0x5f, 0x32, 0xbf, 0x19, 0xf0, 0x41, + 0x9f, 0x32, 0xe4, 0x2c, 0xa9, 0x02, 0xb6, 0xa9, + 0x08, 0xaf, 0x4f, 0xe0, 0xcb, 0xa4, 0x13, 0xdf, + 0x1d, 0xd7, 0x08, 0x07, 0xa1, 0x19, 0xe0, 0x05, + 0x4a, 0x82, 0x19, 0xc2, 0x4a, 0x01, 0x81, 0x4a, + 0x00, 0x80, 0x4a, 0x00, 0x87, 0x4a, 0x14, 0x8d, + 0x4a, 0xac, 0x8f, 0x02, 0x89, 0x19, 0x05, 0xb7, + 0x7e, 0x07, 0xc5, 0x84, 0x07, 0x8b, 0x84, 0x05, + 0x9f, 0x20, 0xad, 0x42, 0x80, 0x19, 0x80, 0x42, + 0xa3, 0x81, 0x0a, 0x80, 0x81, 0x9c, 0x33, 0x02, + 0xcd, 0x3d, 0x00, 0x80, 0x19, 0x89, 0x3d, 0x03, + 0x81, 0x3d, 0x9e, 0x63, 0x00, 0xb6, 0x16, 0x08, + 0x8d, 0x16, 0x01, 0x89, 0x16, 0x01, 0x83, 0x16, + 0x9f, 0x63, 0xc2, 0x95, 0x17, 0x84, 0x95, 0x96, + 0x5a, 0x09, 0x85, 0x27, 0x01, 0x85, 0x27, 0x01, + 0x85, 0x27, 0x08, 0x86, 0x27, 0x00, 0x86, 0x27, + 0x00, 0xaa, 0x4a, 0x80, 0x19, 0x88, 0x4a, 0x80, + 0x2d, 0x83, 0x4a, 0x81, 0x19, 0x03, 0xcf, 0x17, + 0xad, 0x5a, 0x01, 0x89, 0x5a, 0x05, 0xf0, 0x1b, + 0x43, 0x33, 0x0b, 0x96, 0x33, 0x03, 0xb0, 0x33, + 0x70, 0x10, 0xa3, 0xe1, 0x0d, 0x32, 0x01, 0xe0, + 0x09, 0x32, 0x25, 0x86, 0x4a, 0x0b, 0x84, 0x05, + 0x04, 0x99, 0x37, 0x00, 0x84, 0x37, 0x00, 0x80, + 0x37, 0x00, 0x81, 0x37, 0x00, 0x81, 0x37, 0x00, + 0x89, 0x37, 0xe0, 0x12, 0x04, 0x0f, 0xe1, 0x0a, + 0x04, 0x81, 0x19, 0xcf, 0x04, 0x01, 0xb5, 0x04, + 0x06, 0x80, 0x04, 0x1f, 0x8f, 0x04, 0x8f, 0x3a, + 0x89, 0x19, 0x05, 0x8d, 0x3a, 0x81, 0x1d, 0xa2, + 0x19, 0x00, 0x92, 0x19, 0x00, 0x83, 0x19, 0x03, + 0x84, 0x04, 0x00, 0xe0, 0x26, 0x04, 0x01, 0x80, + 0x19, 0x00, 0x9f, 0x19, 0x99, 0x4a, 0x85, 0x19, + 0x99, 0x4a, 0x8a, 0x19, 0x89, 0x40, 0x80, 0x19, + 0xac, 0x40, 0x81, 0x19, 0x9e, 0x33, 0x02, 0x85, + 0x33, 0x01, 0x85, 0x33, 0x01, 0x85, 0x33, 0x01, + 0x82, 0x33, 0x02, 0x86, 0x19, 0x00, 0x86, 0x19, + 0x09, 0x84, 0x19, 0x01, 0x8b, 0x4e, 0x00, 0x99, + 0x4e, 0x00, 0x92, 0x4e, 0x00, 0x81, 0x4e, 0x00, + 0x8e, 0x4e, 0x01, 0x8d, 0x4e, 0x21, 0xe0, 0x1a, + 0x4e, 0x04, 0x82, 0x19, 0x03, 0xac, 0x19, 0x02, + 0x88, 0x19, 0xce, 0x2d, 0x00, 0x8c, 0x19, 0x02, + 0x80, 0x2d, 0x2e, 0xac, 0x19, 0x80, 0x3a, 0x60, + 0x21, 0x9c, 0x50, 0x02, 0xb0, 0x13, 0x0e, 0x80, + 0x3a, 0x9a, 0x19, 0x03, 0xa3, 0x70, 0x08, 0x82, + 0x70, 0x9a, 0x2b, 0x04, 0xaa, 0x72, 0x04, 0x9d, + 0xa3, 0x00, 0x80, 0xa3, 0xa3, 0x73, 0x03, 0x8d, + 0x73, 0x29, 0xcf, 0x1f, 0xaf, 0x86, 0x9d, 0x7a, + 0x01, 0x89, 0x7a, 0x05, 0xa3, 0x79, 0x03, 0xa3, + 0x79, 0x03, 0xa7, 0x25, 0x07, 0xb3, 0x14, 0x0a, + 0x80, 0x14, 0x8a, 0xa5, 0x00, 0x8e, 0xa5, 0x00, + 0x86, 0xa5, 0x00, 0x81, 0xa5, 0x00, 0x8a, 0xa5, + 0x00, 0x8e, 0xa5, 0x00, 0x86, 0xa5, 0x00, 0x81, + 0xa5, 0x02, 0xb3, 0xa0, 0x0b, 0xe0, 0xd6, 0x4d, + 0x08, 0x95, 0x4d, 0x09, 0x87, 0x4d, 0x17, 0x85, + 0x4a, 0x00, 0xa9, 0x4a, 0x00, 0x88, 0x4a, 0x44, + 0x85, 0x1c, 0x01, 0x80, 0x1c, 0x00, 0xab, 0x1c, + 0x00, 0x81, 0x1c, 0x02, 0x80, 0x1c, 0x01, 0x80, + 0x1c, 0x95, 0x39, 0x00, 0x88, 0x39, 0x9f, 0x7c, + 0x9e, 0x64, 0x07, 0x88, 0x64, 0x2f, 0x92, 0x36, + 0x00, 0x81, 0x36, 0x04, 0x84, 0x36, 0x9b, 0x7f, + 0x02, 0x80, 0x7f, 0x99, 0x51, 0x04, 0x80, 0x51, + 0x3f, 0x9f, 0x5d, 0x97, 0x5c, 0x03, 0x93, 0x5c, + 0x01, 0xad, 0x5c, 0x83, 0x43, 0x00, 0x81, 0x43, + 0x04, 0x87, 0x43, 0x00, 0x82, 0x43, 0x00, 0x9c, + 0x43, 0x01, 0x82, 0x43, 0x03, 0x89, 0x43, 0x06, + 0x88, 0x43, 0x06, 0x9f, 0x75, 0x9f, 0x71, 0x1f, + 0xa6, 0x56, 0x03, 0x8b, 0x56, 0x08, 0xb5, 0x06, + 0x02, 0x86, 0x06, 0x95, 0x3c, 0x01, 0x87, 0x3c, + 0x92, 0x3b, 0x04, 0x87, 0x3b, 0x91, 0x80, 0x06, + 0x83, 0x80, 0x0b, 0x86, 0x80, 0x4f, 0xc8, 0x76, + 0x36, 0xb2, 0x6f, 0x0c, 0xb2, 0x6f, 0x06, 0x85, + 0x6f, 0xa7, 0x34, 0x07, 0x89, 0x34, 0x05, 0xa5, + 0x28, 0x02, 0x9c, 0x28, 0x07, 0x81, 0x28, 0x60, + 0x6f, 0x9e, 0x04, 0x00, 0xa9, 0xa8, 0x00, 0x82, + 0xa8, 0x01, 0x81, 0xa8, 0x0f, 0x82, 0x04, 0x36, + 0x83, 0x04, 0xa7, 0x74, 0x07, 0xa9, 0x8a, 0x15, + 0x99, 0x77, 0x25, 0x9b, 0x18, 0x13, 0x96, 0x26, + 0x08, 0xcd, 0x0e, 0x03, 0xa3, 0x0e, 0x08, 0x80, + 0x0e, 0xc2, 0x3e, 0x09, 0x80, 0x3e, 0x01, 0x98, + 0x8b, 0x06, 0x89, 0x8b, 0x05, 0xb4, 0x15, 0x00, + 0x91, 0x15, 0x07, 0xa6, 0x53, 0x08, 0xdf, 0x85, + 0x00, 0x93, 0x89, 0x0a, 0x91, 0x45, 0x00, 0xae, + 0x45, 0x3d, 0x86, 0x62, 0x00, 0x80, 0x62, 0x00, + 0x83, 0x62, 0x00, 0x8e, 0x62, 0x00, 0x8a, 0x62, + 0x05, 0xba, 0x47, 0x04, 0x89, 0x47, 0x05, 0x83, + 0x2c, 0x00, 0x87, 0x2c, 0x01, 0x81, 0x2c, 0x01, + 0x95, 0x2c, 0x00, 0x86, 0x2c, 0x00, 0x81, 0x2c, + 0x00, 0x84, 0x2c, 0x00, 0x80, 0x3a, 0x88, 0x2c, + 0x01, 0x81, 0x2c, 0x01, 0x82, 0x2c, 0x01, 0x80, + 0x2c, 0x05, 0x80, 0x2c, 0x04, 0x86, 0x2c, 0x01, + 0x86, 0x2c, 0x02, 0x84, 0x2c, 0x0a, 0x89, 0xa2, + 0x00, 0x80, 0xa2, 0x01, 0x80, 0xa2, 0x00, 0xa5, + 0xa2, 0x00, 0x89, 0xa2, 0x00, 0x80, 0xa2, 0x01, + 0x80, 0xa2, 0x00, 0x83, 0xa2, 0x00, 0x89, 0xa2, + 0x00, 0x81, 0xa2, 0x07, 0x81, 0xa2, 0x1c, 0xdb, + 0x68, 0x00, 0x84, 0x68, 0x1d, 0xc7, 0x9e, 0x07, + 0x89, 0x9e, 0x60, 0x45, 0xb5, 0x87, 0x01, 0xa5, + 0x87, 0x21, 0xc4, 0x5f, 0x0a, 0x89, 0x5f, 0x05, + 0x8c, 0x60, 0x12, 0xb9, 0x96, 0x05, 0x89, 0x96, + 0x05, 0x93, 0x63, 0x1b, 0x9a, 0x02, 0x01, 0x8e, + 0x02, 0x03, 0x96, 0x02, 0x60, 0x58, 0xbb, 0x22, + 0x60, 0x03, 0xd2, 0xa7, 0x0b, 0x80, 0xa7, 0x86, + 0x21, 0x01, 0x80, 0x21, 0x01, 0x87, 0x21, 0x00, + 0x81, 0x21, 0x00, 0x9d, 0x21, 0x00, 0x81, 0x21, + 0x01, 0x8b, 0x21, 0x08, 0x89, 0x21, 0x45, 0x87, + 0x66, 0x01, 0xad, 0x66, 0x01, 0x8a, 0x66, 0x1a, + 0xc7, 0xaa, 0x07, 0xd2, 0x8c, 0x0c, 0x8f, 0x12, + 0xb8, 0x7d, 0x06, 0x89, 0x20, 0x60, 0x55, 0xa1, + 0x8e, 0x0d, 0x89, 0x8e, 0x05, 0x88, 0x0c, 0x00, + 0xac, 0x0c, 0x00, 0x8d, 0x0c, 0x09, 0x9c, 0x0c, + 0x02, 0x9f, 0x57, 0x01, 0x95, 0x57, 0x00, 0x8d, + 0x57, 0x48, 0x86, 0x58, 0x00, 0x81, 0x58, 0x00, + 0xab, 0x58, 0x02, 0x80, 0x58, 0x00, 0x81, 0x58, + 0x00, 0x88, 0x58, 0x07, 0x89, 0x58, 0x05, 0x85, + 0x2f, 0x00, 0x81, 0x2f, 0x00, 0xa4, 0x2f, 0x00, + 0x81, 0x2f, 0x00, 0x85, 0x2f, 0x06, 0x89, 0x2f, + 0x60, 0xd5, 0x98, 0x52, 0x06, 0x90, 0x41, 0x00, + 0xa8, 0x41, 0x02, 0x9c, 0x41, 0x54, 0x80, 0x4f, + 0x0e, 0xb1, 0x97, 0x0c, 0x80, 0x97, 0xe3, 0x39, + 0x1b, 0x60, 0x05, 0xe0, 0x0e, 0x1b, 0x00, 0x84, + 0x1b, 0x0a, 0xe0, 0x63, 0x1b, 0x69, 0xeb, 0xe0, + 0x02, 0x1e, 0x0c, 0xe3, 0xf5, 0x24, 0x09, 0xef, + 0x3a, 0x24, 0x04, 0xe1, 0xe6, 0x03, 0x70, 0x0a, + 0x58, 0xb9, 0x31, 0x66, 0x65, 0xe1, 0xd8, 0x08, + 0x06, 0x9e, 0x61, 0x00, 0x89, 0x61, 0x03, 0x81, + 0x61, 0xce, 0x9f, 0x00, 0x89, 0x9f, 0x05, 0x9d, + 0x09, 0x01, 0x85, 0x09, 0x09, 0xc5, 0x7b, 0x09, + 0x89, 0x7b, 0x00, 0x86, 0x7b, 0x00, 0x94, 0x7b, + 0x04, 0x92, 0x7b, 0x61, 0x4f, 0xb9, 0x48, 0x60, + 0x65, 0xda, 0x59, 0x60, 0x04, 0xca, 0x5e, 0x03, + 0xb8, 0x5e, 0x06, 0x90, 0x5e, 0x3f, 0x80, 0x98, + 0x80, 0x6a, 0x81, 0x32, 0x80, 0x46, 0x0a, 0x81, + 0x32, 0x0d, 0xf0, 0x07, 0x97, 0x98, 0x07, 0xe2, + 0x9f, 0x98, 0xe1, 0x75, 0x46, 0x28, 0x80, 0x46, + 0x88, 0x98, 0x70, 0x12, 0x86, 0x83, 0x40, 0x00, + 0x86, 0x40, 0x00, 0x81, 0x40, 0x00, 0x80, 0x40, + 0xe0, 0xbe, 0x38, 0x82, 0x40, 0x0e, 0x80, 0x38, + 0x1c, 0x82, 0x38, 0x01, 0x80, 0x40, 0x0d, 0x83, + 0x40, 0x07, 0xe1, 0x2b, 0x6a, 0x68, 0xa3, 0xe0, + 0x0a, 0x23, 0x04, 0x8c, 0x23, 0x02, 0x88, 0x23, + 0x06, 0x89, 0x23, 0x01, 0x83, 0x23, 0x83, 0x19, + 0x6e, 0xfb, 0xe0, 0x99, 0x19, 0x05, 0xe1, 0x53, + 0x19, 0x4b, 0xad, 0x3a, 0x01, 0x96, 0x3a, 0x08, + 0xe0, 0x13, 0x19, 0x3b, 0xe0, 0x95, 0x19, 0x09, + 0xa6, 0x19, 0x01, 0xbd, 0x19, 0x82, 0x3a, 0x90, + 0x19, 0x87, 0x3a, 0x81, 0x19, 0x86, 0x3a, 0x9d, + 0x19, 0x83, 0x3a, 0xbc, 0x19, 0x14, 0xc5, 0x2d, + 0x60, 0x19, 0x93, 0x19, 0x0b, 0x93, 0x19, 0x0b, + 0xd6, 0x19, 0x08, 0x98, 0x19, 0x60, 0x26, 0xd4, + 0x19, 0x00, 0xc6, 0x19, 0x00, 0x81, 0x19, 0x01, + 0x80, 0x19, 0x01, 0x81, 0x19, 0x01, 0x83, 0x19, + 0x00, 0x8b, 0x19, 0x00, 0x80, 0x19, 0x00, 0x86, + 0x19, 0x00, 0xc0, 0x19, 0x00, 0x83, 0x19, 0x01, + 0x87, 0x19, 0x00, 0x86, 0x19, 0x00, 0x9b, 0x19, + 0x00, 0x83, 0x19, 0x00, 0x84, 0x19, 0x00, 0x80, + 0x19, 0x02, 0x86, 0x19, 0x00, 0xe0, 0xf3, 0x19, + 0x01, 0xe0, 0xc3, 0x19, 0x01, 0xb1, 0x19, 0xe2, + 0x2b, 0x88, 0x0e, 0x84, 0x88, 0x00, 0x8e, 0x88, + 0x63, 0xef, 0x9e, 0x4a, 0x05, 0x85, 0x4a, 0x60, + 0x74, 0x86, 0x2a, 0x00, 0x90, 0x2a, 0x01, 0x86, + 0x2a, 0x00, 0x81, 0x2a, 0x00, 0x84, 0x2a, 0x04, + 0xbd, 0x1d, 0x20, 0x80, 0x1d, 0x60, 0x0f, 0xac, + 0x6b, 0x02, 0x8d, 0x6b, 0x01, 0x89, 0x6b, 0x03, + 0x81, 0x6b, 0x60, 0xdf, 0x9e, 0xa1, 0x10, 0xb9, + 0xa6, 0x04, 0x80, 0xa6, 0x61, 0x6f, 0xa9, 0x65, + 0x60, 0x75, 0xaa, 0x6e, 0x03, 0x80, 0x6e, 0x61, + 0x7f, 0x86, 0x27, 0x00, 0x83, 0x27, 0x00, 0x81, + 0x27, 0x00, 0x8e, 0x27, 0x00, 0xe0, 0x64, 0x5b, + 0x01, 0x8f, 0x5b, 0x28, 0xcb, 0x01, 0x03, 0x89, + 0x01, 0x03, 0x81, 0x01, 0x62, 0xb0, 0xc3, 0x19, + 0x4b, 0xbc, 0x19, 0x60, 0x61, 0x83, 0x04, 0x00, + 0x9a, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, + 0x01, 0x80, 0x04, 0x00, 0x89, 0x04, 0x00, 0x83, + 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x05, + 0x80, 0x04, 0x03, 0x80, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x80, 0x04, 0x00, 0x82, 0x04, 0x00, 0x81, + 0x04, 0x00, 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, + 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x80, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, + 0x04, 0x01, 0x83, 0x04, 0x00, 0x86, 0x04, 0x00, + 0x83, 0x04, 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x89, 0x04, 0x00, 0x90, 0x04, 0x04, 0x82, + 0x04, 0x00, 0x84, 0x04, 0x00, 0x90, 0x04, 0x33, + 0x81, 0x04, 0x60, 0xad, 0xab, 0x19, 0x03, 0xe0, + 0x03, 0x19, 0x0b, 0x8e, 0x19, 0x01, 0x8e, 0x19, + 0x00, 0x8e, 0x19, 0x00, 0xa4, 0x19, 0x09, 0xe0, + 0x4d, 0x19, 0x37, 0x99, 0x19, 0x80, 0x38, 0x81, + 0x19, 0x0c, 0xab, 0x19, 0x03, 0x88, 0x19, 0x06, + 0x81, 0x19, 0x0d, 0x85, 0x19, 0x60, 0x39, 0xe3, + 0x77, 0x19, 0x03, 0x90, 0x19, 0x02, 0x8c, 0x19, + 0x02, 0xe0, 0x16, 0x19, 0x03, 0xde, 0x19, 0x05, + 0x8b, 0x19, 0x03, 0x80, 0x19, 0x0e, 0x8b, 0x19, + 0x03, 0xb7, 0x19, 0x07, 0x89, 0x19, 0x05, 0xa7, + 0x19, 0x07, 0x9d, 0x19, 0x01, 0x8b, 0x19, 0x03, + 0x81, 0x19, 0x3d, 0xe0, 0xf3, 0x19, 0x0b, 0x8d, + 0x19, 0x01, 0x8c, 0x19, 0x02, 0x89, 0x19, 0x04, + 0xb7, 0x19, 0x06, 0x8e, 0x19, 0x01, 0x8a, 0x19, + 0x05, 0x88, 0x19, 0x06, 0xe0, 0x32, 0x19, 0x00, + 0xe0, 0x05, 0x19, 0x63, 0xa5, 0xf0, 0x96, 0x7f, + 0x32, 0x1f, 0xef, 0xd9, 0x32, 0x05, 0xe0, 0x7d, + 0x32, 0x01, 0xf0, 0x06, 0x21, 0x32, 0x0d, 0xf0, + 0x0c, 0xd0, 0x32, 0x0e, 0xe2, 0x0d, 0x32, 0x69, + 0x41, 0xe1, 0xbd, 0x32, 0x65, 0x81, 0xf0, 0x02, + 0xea, 0x32, 0x04, 0xef, 0xff, 0x32, 0x7a, 0xcb, + 0xf0, 0x80, 0x19, 0x1d, 0xdf, 0x19, 0x60, 0x1f, + 0xe0, 0x8f, 0x3a, +}; + +static const uint8_t unicode_script_ext_table[1253] = { + 0x80, 0x36, 0x00, 0x00, 0x10, 0x06, 0x13, 0x1a, + 0x23, 0x25, 0x29, 0x2a, 0x2f, 0x2b, 0x2d, 0x32, + 0x4a, 0x51, 0x53, 0x72, 0x86, 0x81, 0x83, 0x00, + 0x00, 0x07, 0x0b, 0x1d, 0x20, 0x4a, 0x4f, 0x9b, + 0xa1, 0x09, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x00, + 0x00, 0x02, 0x02, 0x0d, 0x4a, 0x00, 0x00, 0x00, + 0x02, 0x4a, 0x4f, 0x08, 0x00, 0x00, 0x02, 0x4a, + 0x9b, 0x00, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x25, + 0x00, 0x00, 0x08, 0x17, 0x1a, 0x1d, 0x2d, 0x4a, + 0x72, 0x8e, 0x93, 0x00, 0x08, 0x17, 0x1d, 0x2d, + 0x4a, 0x79, 0x8e, 0x93, 0xa0, 0x00, 0x04, 0x17, + 0x1d, 0x4a, 0x9d, 0x00, 0x05, 0x2a, 0x4a, 0x8e, + 0x90, 0x9b, 0x00, 0x0b, 0x14, 0x17, 0x1a, 0x1d, + 0x2b, 0x2d, 0x4a, 0x79, 0x90, 0x9d, 0xa0, 0x00, + 0x06, 0x1a, 0x25, 0x2a, 0x2b, 0x40, 0x4a, 0x00, + 0x04, 0x1d, 0x2d, 0x4a, 0x72, 0x00, 0x09, 0x1a, + 0x23, 0x37, 0x4a, 0x72, 0x90, 0x93, 0x9d, 0xa0, + 0x00, 0x0a, 0x05, 0x1d, 0x23, 0x2b, 0x2d, 0x37, + 0x4a, 0x72, 0x90, 0x93, 0x00, 0x02, 0x4a, 0x9d, + 0x00, 0x03, 0x23, 0x4a, 0x90, 0x00, 0x04, 0x17, + 0x1d, 0x4a, 0x79, 0x00, 0x03, 0x17, 0x4a, 0x93, + 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x02, 0x27, 0x4a, + 0x00, 0x00, 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x03, + 0x1d, 0x4a, 0xa0, 0x00, 0x00, 0x00, 0x04, 0x2d, + 0x4a, 0x72, 0xa0, 0x0b, 0x00, 0x00, 0x02, 0x4a, + 0x90, 0x01, 0x00, 0x00, 0x05, 0x17, 0x23, 0x40, + 0x4a, 0x90, 0x00, 0x04, 0x17, 0x23, 0x4a, 0x90, + 0x00, 0x02, 0x4a, 0x90, 0x06, 0x00, 0x00, 0x03, + 0x4a, 0x8e, 0x90, 0x00, 0x02, 0x4a, 0x90, 0x00, + 0x00, 0x00, 0x03, 0x17, 0x4a, 0x90, 0x00, 0x06, + 0x14, 0x17, 0x2b, 0x4a, 0x8e, 0x9b, 0x0f, 0x00, + 0x00, 0x01, 0x2d, 0x01, 0x00, 0x00, 0x01, 0x2d, + 0x11, 0x00, 0x00, 0x02, 0x4a, 0x79, 0x04, 0x00, + 0x00, 0x03, 0x14, 0x4a, 0xa0, 0x03, 0x00, 0x0c, + 0x01, 0x4a, 0x03, 0x00, 0x01, 0x02, 0x1a, 0x2d, + 0x80, 0x8c, 0x00, 0x00, 0x02, 0x1d, 0x72, 0x00, + 0x02, 0x1d, 0x2a, 0x01, 0x02, 0x1d, 0x4a, 0x00, + 0x02, 0x1d, 0x2a, 0x80, 0x80, 0x00, 0x00, 0x03, + 0x05, 0x29, 0x2a, 0x80, 0x01, 0x00, 0x00, 0x07, + 0x04, 0x28, 0x69, 0x34, 0x90, 0x9a, 0xa8, 0x0d, + 0x00, 0x00, 0x07, 0x04, 0x28, 0x69, 0x34, 0x90, + 0x9a, 0xa8, 0x00, 0x03, 0x04, 0x90, 0x9a, 0x01, + 0x00, 0x00, 0x08, 0x01, 0x04, 0x28, 0x69, 0x34, + 0x90, 0x9a, 0xa8, 0x1f, 0x00, 0x00, 0x09, 0x01, + 0x04, 0x55, 0x56, 0x77, 0x80, 0x34, 0x8a, 0x90, + 0x09, 0x00, 0x0a, 0x02, 0x04, 0x90, 0x09, 0x00, + 0x09, 0x03, 0x04, 0x9a, 0xa8, 0x05, 0x00, 0x00, + 0x02, 0x04, 0x90, 0x62, 0x00, 0x00, 0x02, 0x04, + 0x34, 0x81, 0xfb, 0x00, 0x00, 0x0d, 0x0b, 0x20, + 0x2c, 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x85, + 0x97, 0x99, 0x9e, 0x00, 0x0c, 0x0b, 0x20, 0x2c, + 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x97, 0x99, + 0x9e, 0x10, 0x00, 0x00, 0x15, 0x0b, 0x20, 0x22, + 0x2f, 0x58, 0x2c, 0x2e, 0x30, 0x3f, 0x53, 0x54, + 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, 0x97, + 0x99, 0x9e, 0x00, 0x17, 0x0b, 0x20, 0x22, 0x2f, + 0x58, 0x2c, 0x2e, 0x31, 0x30, 0x3f, 0x4c, 0x53, + 0x54, 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, + 0x97, 0x99, 0x9e, 0x09, 0x04, 0x20, 0x22, 0x3e, + 0x53, 0x75, 0x00, 0x09, 0x03, 0x0b, 0x15, 0x8f, + 0x75, 0x00, 0x09, 0x02, 0x30, 0x62, 0x75, 0x00, + 0x09, 0x02, 0x2e, 0x45, 0x80, 0x75, 0x00, 0x0d, + 0x02, 0x2c, 0x97, 0x80, 0x71, 0x00, 0x09, 0x03, + 0x3f, 0x66, 0xa2, 0x82, 0xcf, 0x00, 0x09, 0x03, + 0x15, 0x63, 0x93, 0x80, 0x30, 0x00, 0x00, 0x03, + 0x29, 0x2a, 0x4a, 0x85, 0x6e, 0x00, 0x02, 0x01, + 0x82, 0x46, 0x00, 0x01, 0x04, 0x11, 0x35, 0x92, + 0x91, 0x80, 0x4a, 0x00, 0x01, 0x02, 0x60, 0x7e, + 0x00, 0x00, 0x00, 0x02, 0x60, 0x7e, 0x84, 0x49, + 0x00, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, 0x00, + 0x01, 0x20, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, + 0x00, 0x03, 0x20, 0x2c, 0x3f, 0x00, 0x01, 0x20, + 0x01, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, + 0x00, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, + 0x00, 0x06, 0x20, 0x3f, 0x54, 0x78, 0x97, 0x99, + 0x00, 0x01, 0x20, 0x01, 0x02, 0x20, 0x85, 0x01, + 0x01, 0x20, 0x00, 0x02, 0x20, 0x85, 0x00, 0x02, + 0x0b, 0x20, 0x06, 0x01, 0x20, 0x00, 0x02, 0x20, + 0x66, 0x00, 0x02, 0x0b, 0x20, 0x01, 0x01, 0x20, + 0x00, 0x02, 0x0b, 0x20, 0x03, 0x01, 0x20, 0x00, + 0x0b, 0x0b, 0x20, 0x2c, 0x3f, 0x54, 0x66, 0x78, + 0x89, 0x99, 0x9e, 0xa2, 0x00, 0x02, 0x20, 0x2c, + 0x00, 0x04, 0x20, 0x2c, 0x3f, 0xa2, 0x01, 0x02, + 0x0b, 0x20, 0x00, 0x01, 0x0b, 0x01, 0x02, 0x20, + 0x2c, 0x00, 0x01, 0x66, 0x80, 0x44, 0x00, 0x01, + 0x01, 0x2d, 0x35, 0x00, 0x00, 0x03, 0x1d, 0x4a, + 0x90, 0x00, 0x00, 0x00, 0x01, 0x90, 0x81, 0xb3, + 0x00, 0x00, 0x03, 0x4a, 0x60, 0x7e, 0x1e, 0x00, + 0x00, 0x02, 0x01, 0x04, 0x09, 0x00, 0x00, 0x06, + 0x13, 0x29, 0x2a, 0x6f, 0x50, 0x76, 0x01, 0x00, + 0x00, 0x04, 0x13, 0x2d, 0x6f, 0x5d, 0x80, 0x11, + 0x00, 0x00, 0x03, 0x20, 0x2c, 0x4a, 0x8c, 0xa5, + 0x00, 0x00, 0x02, 0x1a, 0x4a, 0x17, 0x00, 0x00, + 0x02, 0x06, 0x76, 0x00, 0x07, 0x06, 0x13, 0x29, + 0x6f, 0x3e, 0x51, 0x83, 0x09, 0x00, 0x00, 0x01, + 0x23, 0x03, 0x00, 0x00, 0x03, 0x01, 0x04, 0x6f, + 0x00, 0x00, 0x00, 0x02, 0x1d, 0x2a, 0x81, 0x2b, + 0x00, 0x0f, 0x02, 0x32, 0x98, 0x00, 0x00, 0x00, + 0x07, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, 0xa9, + 0x00, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, + 0x7e, 0xa9, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x01, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, + 0x01, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, + 0x9c, 0xa9, 0x01, 0x09, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x4f, 0x60, 0x9c, 0xa9, 0x05, 0x06, 0x0d, + 0x33, 0x32, 0x38, 0x40, 0xa9, 0x00, 0x00, 0x00, + 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x07, 0x06, + 0x0d, 0x33, 0x32, 0x38, 0x40, 0xa9, 0x03, 0x05, + 0x0d, 0x33, 0x32, 0x38, 0x40, 0x09, 0x00, 0x03, + 0x02, 0x0d, 0x32, 0x01, 0x00, 0x00, 0x05, 0x0d, + 0x33, 0x32, 0x38, 0x40, 0x04, 0x02, 0x38, 0x40, + 0x00, 0x00, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x03, 0x00, 0x01, 0x03, 0x32, 0x38, 0x40, + 0x01, 0x01, 0x32, 0x58, 0x00, 0x03, 0x02, 0x38, + 0x40, 0x02, 0x00, 0x00, 0x02, 0x38, 0x40, 0x59, + 0x00, 0x00, 0x06, 0x0d, 0x33, 0x32, 0x38, 0x40, + 0xa9, 0x00, 0x02, 0x38, 0x40, 0x80, 0x12, 0x00, + 0x0f, 0x01, 0x32, 0x1f, 0x00, 0x25, 0x01, 0x32, + 0x08, 0x00, 0x00, 0x02, 0x32, 0x98, 0x2f, 0x00, + 0x27, 0x01, 0x32, 0x37, 0x00, 0x30, 0x01, 0x32, + 0x0e, 0x00, 0x0b, 0x01, 0x32, 0x32, 0x00, 0x00, + 0x01, 0x32, 0x57, 0x00, 0x18, 0x01, 0x32, 0x09, + 0x00, 0x04, 0x01, 0x32, 0x5f, 0x00, 0x1e, 0x01, + 0x32, 0xc0, 0x31, 0xef, 0x00, 0x00, 0x02, 0x1d, + 0x2a, 0x80, 0x0f, 0x00, 0x07, 0x02, 0x32, 0x4a, + 0x80, 0xa7, 0x00, 0x02, 0x10, 0x20, 0x22, 0x2e, + 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x54, 0x5f, 0x66, + 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x02, 0x0f, 0x20, + 0x22, 0x2e, 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x5f, + 0x66, 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x01, 0x0b, + 0x20, 0x22, 0x2e, 0x30, 0x45, 0x3e, 0x53, 0x5f, + 0x47, 0x96, 0x9e, 0x00, 0x0c, 0x20, 0x22, 0x2e, + 0x30, 0x45, 0x3e, 0x53, 0x5f, 0x85, 0x47, 0x96, + 0x9e, 0x00, 0x0b, 0x20, 0x22, 0x2e, 0x30, 0x45, + 0x3e, 0x53, 0x5f, 0x47, 0x96, 0x9e, 0x80, 0x36, + 0x00, 0x00, 0x03, 0x0b, 0x20, 0xa2, 0x00, 0x00, + 0x00, 0x02, 0x20, 0x97, 0x39, 0x00, 0x00, 0x03, + 0x42, 0x4a, 0x63, 0x80, 0x1f, 0x00, 0x00, 0x02, + 0x10, 0x3d, 0xc0, 0x12, 0xed, 0x00, 0x01, 0x02, + 0x04, 0x69, 0x80, 0x31, 0x00, 0x00, 0x02, 0x04, + 0x9a, 0x09, 0x00, 0x00, 0x02, 0x04, 0x9a, 0x46, + 0x00, 0x01, 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, + 0x80, 0x99, 0x00, 0x04, 0x06, 0x0d, 0x33, 0x32, + 0x38, 0x40, 0xa9, 0x09, 0x00, 0x00, 0x02, 0x38, + 0x40, 0x2c, 0x00, 0x01, 0x02, 0x38, 0x40, 0x80, + 0xdf, 0x00, 0x01, 0x03, 0x1e, 0x1c, 0x4e, 0x00, + 0x02, 0x1c, 0x4e, 0x03, 0x00, 0x2c, 0x03, 0x1c, + 0x4d, 0x4e, 0x02, 0x00, 0x08, 0x02, 0x1c, 0x4e, + 0x81, 0x1f, 0x00, 0x1b, 0x02, 0x04, 0x1a, 0x87, + 0x75, 0x00, 0x00, 0x02, 0x56, 0x77, 0x87, 0x8d, + 0x00, 0x00, 0x02, 0x2c, 0x97, 0x00, 0x00, 0x00, + 0x02, 0x2c, 0x97, 0x36, 0x00, 0x01, 0x02, 0x2c, + 0x97, 0x8c, 0x12, 0x00, 0x01, 0x02, 0x2c, 0x97, + 0x00, 0x00, 0x00, 0x02, 0x2c, 0x97, 0xc0, 0x5c, + 0x4b, 0x00, 0x03, 0x01, 0x23, 0x96, 0x3b, 0x00, + 0x11, 0x01, 0x32, 0x9e, 0x5d, 0x00, 0x01, 0x01, + 0x32, 0xce, 0xcd, 0x2d, 0x00, +}; + +static const uint8_t unicode_prop_Hyphen_table[28] = { + 0xac, 0x80, 0xfe, 0x80, 0x44, 0xdb, 0x80, 0x52, + 0x7a, 0x80, 0x48, 0x08, 0x81, 0x4e, 0x04, 0x80, + 0x42, 0xe2, 0x80, 0x60, 0xcd, 0x66, 0x80, 0x40, + 0xa8, 0x80, 0xd6, 0x80, +}; + +static const uint8_t unicode_prop_Other_Math_table[200] = { + 0xdd, 0x80, 0x43, 0x70, 0x11, 0x80, 0x99, 0x09, + 0x81, 0x5c, 0x1f, 0x80, 0x9a, 0x82, 0x8a, 0x80, + 0x9f, 0x83, 0x97, 0x81, 0x8d, 0x81, 0xc0, 0x8c, + 0x18, 0x11, 0x1c, 0x91, 0x03, 0x01, 0x89, 0x00, + 0x14, 0x28, 0x11, 0x09, 0x02, 0x05, 0x13, 0x24, + 0xca, 0x21, 0x18, 0x08, 0x08, 0x00, 0x21, 0x0b, + 0x0b, 0x91, 0x09, 0x00, 0x06, 0x00, 0x29, 0x41, + 0x21, 0x83, 0x40, 0xa7, 0x08, 0x80, 0x97, 0x80, + 0x90, 0x80, 0x41, 0xbc, 0x81, 0x8b, 0x88, 0x24, + 0x21, 0x09, 0x14, 0x8d, 0x00, 0x01, 0x85, 0x97, + 0x81, 0xb8, 0x00, 0x80, 0x9c, 0x83, 0x88, 0x81, + 0x41, 0x55, 0x81, 0x9e, 0x89, 0x41, 0x92, 0x95, + 0xbe, 0x83, 0x9f, 0x81, 0x60, 0xd4, 0x62, 0x00, + 0x03, 0x80, 0x40, 0xd2, 0x00, 0x80, 0x60, 0xd4, + 0xc0, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, + 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, + 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, + 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x81, + 0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08, + 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, + 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, +}; + +static const uint8_t unicode_prop_Other_Alphabetic_table[443] = { + 0x43, 0x44, 0x80, 0x9c, 0x8c, 0x42, 0x3f, 0x8d, + 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, + 0x06, 0x8f, 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, + 0xa2, 0x80, 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, + 0x88, 0x02, 0x03, 0xe9, 0x80, 0xbb, 0x8b, 0x16, + 0x85, 0x93, 0xb5, 0x09, 0x8e, 0x01, 0x22, 0x89, + 0x81, 0x9c, 0x82, 0xb9, 0x31, 0x09, 0x81, 0x89, + 0x80, 0x89, 0x81, 0x9c, 0x82, 0xb9, 0x23, 0x09, + 0x0b, 0x80, 0x9d, 0x0a, 0x80, 0x8a, 0x82, 0xb9, + 0x38, 0x10, 0x81, 0x94, 0x81, 0x95, 0x13, 0x82, + 0xb9, 0x31, 0x09, 0x81, 0x88, 0x81, 0x89, 0x81, + 0x9d, 0x80, 0xba, 0x22, 0x10, 0x82, 0x89, 0x80, + 0xa7, 0x84, 0xb8, 0x30, 0x10, 0x17, 0x81, 0x8a, + 0x81, 0x9c, 0x82, 0xb9, 0x30, 0x10, 0x17, 0x81, + 0x8a, 0x81, 0x8e, 0x80, 0x8b, 0x83, 0xb9, 0x30, + 0x10, 0x82, 0x89, 0x80, 0x89, 0x81, 0x9c, 0x82, + 0xca, 0x28, 0x00, 0x87, 0x91, 0x81, 0xbc, 0x01, + 0x86, 0x91, 0x80, 0xe2, 0x01, 0x28, 0x81, 0x8f, + 0x80, 0x40, 0xa2, 0x92, 0x88, 0x8a, 0x80, 0xa3, + 0xed, 0x8b, 0x00, 0x0b, 0x96, 0x1b, 0x10, 0x11, + 0x32, 0x83, 0x8c, 0x8b, 0x00, 0x89, 0x83, 0x46, + 0x73, 0x81, 0x9d, 0x81, 0x9d, 0x81, 0x9d, 0x81, + 0xc1, 0x92, 0x40, 0xbb, 0x81, 0xa1, 0x80, 0xf5, + 0x8b, 0x83, 0x88, 0x40, 0xdd, 0x84, 0xb8, 0x89, + 0x81, 0x93, 0xc9, 0x81, 0x8a, 0x82, 0xb0, 0x84, + 0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, 0xb8, + 0x8a, 0xb1, 0x92, 0x41, 0x9b, 0xa1, 0x46, 0xc0, + 0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, 0x87, + 0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, 0x84, + 0xd7, 0x81, 0xb1, 0x8f, 0x00, 0xb8, 0x80, 0xa5, + 0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, 0xa4, + 0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, 0x82, + 0xb1, 0x00, 0x11, 0x0c, 0x80, 0xab, 0x24, 0x80, + 0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, 0x48, + 0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0x43, + 0x13, 0x83, 0xc0, 0x80, 0x41, 0x40, 0x81, 0xce, + 0x80, 0x41, 0x02, 0x82, 0xb4, 0x8d, 0xac, 0x81, + 0x8a, 0x82, 0xac, 0x88, 0x88, 0x80, 0xbc, 0x82, + 0xa3, 0x8b, 0x91, 0x81, 0xb8, 0x82, 0xaf, 0x8c, + 0x8d, 0x81, 0xdb, 0x88, 0x08, 0x28, 0x08, 0x40, + 0x9c, 0x89, 0x96, 0x83, 0xb9, 0x31, 0x09, 0x81, + 0x89, 0x80, 0x89, 0x81, 0xd3, 0x88, 0x00, 0x08, + 0x03, 0x01, 0xe6, 0x8c, 0x02, 0xe9, 0x91, 0x40, + 0xec, 0x31, 0x86, 0x9c, 0x81, 0xd1, 0x8e, 0x00, + 0xe9, 0x8a, 0xe6, 0x8d, 0x41, 0x00, 0x8c, 0x40, + 0xf6, 0x28, 0x09, 0x0a, 0x00, 0x80, 0x40, 0x8d, + 0x31, 0x2b, 0x80, 0x9b, 0x89, 0xa9, 0x20, 0x83, + 0x91, 0x8a, 0xad, 0x8d, 0x41, 0x96, 0x38, 0x86, + 0xd2, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, + 0x10, 0x02, 0x80, 0xc1, 0x20, 0x08, 0x83, 0x41, + 0x5b, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, 0x82, + 0x60, 0x41, 0xdc, 0x90, 0x4e, 0x1f, 0x00, 0xb6, + 0x33, 0xdc, 0x81, 0x60, 0x4c, 0xab, 0x80, 0x60, + 0x23, 0x60, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, + 0x80, 0x48, 0xb6, 0x80, 0x47, 0xe7, 0x99, 0x85, + 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Lowercase_table[69] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88, + 0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x4d, + 0x80, 0x80, 0x4c, 0x2e, 0xbe, 0x8c, 0x80, 0xa1, + 0xa4, 0x42, 0xb0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, + 0x40, 0xd2, 0x8f, 0x43, 0x4f, 0x99, 0x47, 0x91, + 0x81, 0x60, 0x7a, 0x1d, 0x81, 0x40, 0xd1, 0x80, + 0x40, 0x80, 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, + 0x80, 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, + 0x88, 0x60, 0xd8, 0x74, 0xbd, +}; + +static const uint8_t unicode_prop_Other_Uppercase_table[15] = { + 0x60, 0x21, 0x5f, 0x8f, 0x43, 0x45, 0x99, 0x61, + 0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Grapheme_Extend_table[112] = { + 0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80, + 0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe7, + 0x00, 0x03, 0x08, 0x81, 0x88, 0x81, 0xe6, 0x80, + 0x97, 0x80, 0xf6, 0x80, 0x8e, 0x80, 0x49, 0x34, + 0x80, 0x9d, 0x80, 0x43, 0xff, 0x04, 0x00, 0x04, + 0x81, 0xe4, 0x80, 0xc6, 0x81, 0x44, 0x17, 0x80, + 0x50, 0x20, 0x81, 0x60, 0x79, 0x22, 0x80, 0xeb, + 0x80, 0x60, 0x55, 0xdc, 0x81, 0x52, 0x1f, 0x80, + 0xf3, 0x80, 0x41, 0x07, 0x80, 0x8d, 0x80, 0x88, + 0x80, 0xdf, 0x80, 0x88, 0x01, 0x00, 0x14, 0x80, + 0x40, 0xdf, 0x80, 0x8b, 0x80, 0x40, 0xf0, 0x80, + 0x41, 0x05, 0x80, 0x42, 0x78, 0x80, 0x8b, 0x80, + 0x46, 0x02, 0x80, 0x60, 0x50, 0xad, 0x81, 0x60, + 0x61, 0x72, 0x0d, 0x85, 0x6c, 0x2e, 0xac, 0xdf, +}; + +static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table[32] = { + 0x43, 0x4e, 0x80, 0x4e, 0x0e, 0x81, 0x46, 0x52, + 0x81, 0x48, 0xae, 0x80, 0x50, 0xfd, 0x80, 0x60, + 0xce, 0x3a, 0x80, 0xce, 0x88, 0x6d, 0x00, 0x06, + 0x00, 0x9d, 0xdf, 0xff, 0x40, 0xef, 0x4e, 0x0f, +}; + +static const uint8_t unicode_prop_Other_ID_Start_table[11] = { + 0x58, 0x84, 0x81, 0x48, 0x90, 0x80, 0x94, 0x80, + 0x4f, 0x6b, 0x81, +}; + +static const uint8_t unicode_prop_Other_ID_Continue_table[22] = { + 0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0, + 0x88, 0x46, 0x67, 0x80, 0x46, 0x30, 0x81, 0x50, + 0xec, 0x80, 0x60, 0xce, 0x68, 0x80, +}; + +static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table[19] = { + 0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80, + 0x41, 0x7f, 0x81, 0xcf, 0x80, 0x61, 0x07, 0xd9, + 0x80, 0x8e, 0x80, +}; + +static const uint8_t unicode_prop_XID_Start1_table[31] = { + 0x43, 0x79, 0x80, 0x4a, 0xb7, 0x80, 0xfe, 0x80, + 0x60, 0x21, 0xe6, 0x81, 0x60, 0xcb, 0xc0, 0x85, + 0x41, 0x95, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x41, 0x1e, 0x81, +}; + +static const uint8_t unicode_prop_XID_Continue1_table[23] = { + 0x43, 0x79, 0x80, 0x60, 0x2d, 0x1f, 0x81, 0x60, + 0xcb, 0xc0, 0x85, 0x41, 0x95, 0x81, 0xf3, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Titlecased1_table[22] = { + 0x41, 0xc3, 0x08, 0x08, 0x81, 0xa4, 0x81, 0x4e, + 0xdc, 0xaa, 0x0a, 0x4e, 0x87, 0x3f, 0x3f, 0x87, + 0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Casefolded1_table[29] = { + 0x41, 0xef, 0x80, 0x41, 0x9e, 0x80, 0x9e, 0x80, + 0x5a, 0xe4, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, + 0x80, 0xde, 0x06, 0x06, 0x80, 0x8a, 0x09, 0x81, + 0x89, 0x10, 0x81, 0x8d, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table[450] = { + 0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12, + 0x10, 0x82, 0xf3, 0x80, 0x8b, 0x80, 0x40, 0x84, + 0x01, 0x01, 0x80, 0xa2, 0x01, 0x80, 0x40, 0xbb, + 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, 0x81, 0x89, + 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08, 0x07, 0x80, + 0x9e, 0x80, 0xa0, 0x82, 0x9c, 0x80, 0x42, 0x28, + 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, 0xfb, 0x08, + 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11, 0x80, 0x40, + 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, 0x80, 0xa7, + 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, 0x03, 0x03, + 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00, 0x26, 0x80, + 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, 0x80, 0x8b, + 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, 0x46, 0x52, + 0x81, 0xd4, 0x84, 0x45, 0x1b, 0x10, 0x8a, 0x80, + 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, 0xa4, 0x40, + 0xd5, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, 0x80, + 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xb7, 0x05, 0x00, 0x13, 0x05, 0x11, 0x02, 0x0c, + 0x11, 0x00, 0x00, 0x0c, 0x15, 0x05, 0x08, 0x8f, + 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b, 0x00, + 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, 0x80, + 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, 0x01, + 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06, 0x05, + 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, 0x40, + 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, 0x34, + 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6, 0x82, + 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, 0x80, + 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, 0xd5, + 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09, 0x80, + 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, 0x9e, + 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, 0x60, + 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0x40, 0x80, + 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, 0x60, + 0x4d, 0x95, 0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, + 0x00, 0x00, 0x09, 0x82, 0xc3, 0x81, 0xe9, 0xc2, + 0x00, 0x97, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8c, 0x82, 0x99, 0x95, 0x94, 0x81, 0x8b, 0x80, + 0x92, 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86, 0x08, + 0x80, 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d, 0x0d, + 0x0a, 0x16, 0x06, 0x80, 0x88, 0x47, 0x87, 0x20, + 0xa9, 0x80, 0x88, 0x60, 0xb4, 0xe4, 0x83, 0x50, + 0x31, 0xa3, 0x44, 0x63, 0x86, 0x8d, 0x87, 0xbf, + 0x85, 0x42, 0x3e, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x41, 0x23, 0x81, + 0xb1, 0x48, 0x2f, 0xbd, 0x4d, 0x91, 0x18, 0x9a, + 0x01, 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00, + 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, + 0x03, 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22, + 0x04, 0x80, 0x90, 0x42, 0x43, 0x8a, 0x84, 0x9e, + 0x80, 0x9f, 0x99, 0x82, 0xa2, 0x80, 0xee, 0x82, + 0x8c, 0xab, 0x83, 0x88, 0x31, 0x49, 0x9d, 0x89, + 0x60, 0xfc, 0x05, 0x42, 0x1d, 0x6b, 0x05, 0xe1, + 0x4f, 0xff, +}; + +static const uint8_t unicode_prop_Basic_Emoji1_table[143] = { + 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, + 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, + 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, + 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, + 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, + 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, + 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, + 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, + 0x01, 0x89, 0xe5, 0x80, 0x97, 0x80, 0x93, 0x01, + 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, 0x8b, + 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, 0x1c, + 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, 0x40, + 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, 0x80, + 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, 0xc5, + 0x28, 0x12, 0x0a, 0x1b, 0x8a, 0x0e, 0x88, 0x40, + 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, + 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, 0x84, + 0xb7, 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, +}; + +static const uint8_t unicode_prop_Basic_Emoji2_table[183] = { + 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, + 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, + 0x8e, 0x81, 0x41, 0x7c, 0x80, 0x40, 0xa5, 0x80, + 0x9c, 0x10, 0x0c, 0x82, 0x40, 0xc6, 0x80, 0x40, + 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, 0xb9, 0x0a, + 0x84, 0x88, 0x01, 0x05, 0x03, 0x01, 0x00, 0x09, + 0x02, 0x02, 0x0f, 0x14, 0x00, 0x80, 0x9b, 0x09, + 0x00, 0x08, 0x80, 0x91, 0x01, 0x80, 0x92, 0x00, + 0x18, 0x00, 0x0a, 0x05, 0x07, 0x81, 0x95, 0x05, + 0x00, 0x00, 0x80, 0x94, 0x05, 0x09, 0x01, 0x17, + 0x04, 0x09, 0x08, 0x01, 0x00, 0x00, 0x05, 0x02, + 0x80, 0x90, 0x81, 0x8e, 0x01, 0x80, 0x9a, 0x81, + 0xbb, 0x80, 0x41, 0x91, 0x81, 0x41, 0xce, 0x82, + 0x45, 0x27, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, + 0x80, 0x61, 0xbe, 0xd5, 0x81, 0x8b, 0x81, 0x40, + 0x81, 0x80, 0xb3, 0x80, 0x40, 0xe8, 0x01, 0x88, + 0x88, 0x80, 0xc5, 0x80, 0x97, 0x08, 0x11, 0x81, + 0xaa, 0x1c, 0x8b, 0x92, 0x00, 0x00, 0x80, 0xc6, + 0x00, 0x80, 0x40, 0xba, 0x80, 0xca, 0x81, 0xa3, + 0x09, 0x86, 0x8c, 0x01, 0x19, 0x80, 0x93, 0x01, + 0x07, 0x81, 0x88, 0x04, 0x82, 0x8b, 0x17, 0x11, + 0x00, 0x03, 0x05, 0x02, 0x05, 0x80, 0x40, 0xcf, + 0x00, 0x82, 0x8f, 0x2a, 0x05, 0x01, 0x80, +}; + +static const uint8_t unicode_prop_RGI_Emoji_Modifier_Sequence_table[73] = { + 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, + 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, + 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x18, 0x18, 0x88, + 0x10, 0x1a, 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, + 0xc8, 0x0b, 0x80, 0x94, 0x03, 0x81, 0x40, 0xad, + 0x12, 0x84, 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, + 0x8a, 0x80, 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, + 0x88, 0x89, 0x11, 0xb7, 0x80, 0xbc, 0x08, 0x08, + 0x80, 0x90, 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, + 0x88, +}; + +static const uint8_t unicode_prop_RGI_Emoji_Flag_Sequence_table[128] = { + 0x0c, 0x00, 0x09, 0x00, 0x04, 0x01, 0x02, 0x06, + 0x03, 0x03, 0x01, 0x02, 0x01, 0x03, 0x07, 0x0d, + 0x18, 0x00, 0x09, 0x00, 0x00, 0x89, 0x08, 0x00, + 0x00, 0x81, 0x88, 0x83, 0x8c, 0x10, 0x00, 0x01, + 0x07, 0x08, 0x29, 0x10, 0x28, 0x00, 0x80, 0x8a, + 0x00, 0x0a, 0x00, 0x0e, 0x15, 0x18, 0x83, 0x89, + 0x06, 0x00, 0x81, 0x8d, 0x00, 0x12, 0x08, 0x00, + 0x03, 0x00, 0x24, 0x00, 0x05, 0x21, 0x00, 0x00, + 0x29, 0x90, 0x00, 0x02, 0x00, 0x08, 0x09, 0x00, + 0x08, 0x18, 0x8b, 0x80, 0x8c, 0x02, 0x19, 0x1a, + 0x11, 0x00, 0x00, 0x80, 0x9c, 0x80, 0x88, 0x02, + 0x00, 0x00, 0x02, 0x20, 0x88, 0x0a, 0x00, 0x03, + 0x01, 0x02, 0x05, 0x08, 0x00, 0x01, 0x09, 0x20, + 0x21, 0x18, 0x22, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x28, 0x89, 0x80, 0x8b, 0x80, 0x90, 0x80, 0x92, + 0x80, 0x8d, 0x05, 0x80, 0x8a, 0x80, 0x88, 0x80, +}; + +static const uint8_t unicode_prop_Emoji_Keycap_Sequence_table[4] = { + 0xa2, 0x05, 0x04, 0x89, +}; + +static const uint8_t unicode_prop_ASCII_Hex_Digit_table[5] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_Bidi_Control_table[10] = { + 0x46, 0x1b, 0x80, 0x59, 0xf0, 0x81, 0x99, 0x84, + 0xb6, 0x83, +}; + +static const uint8_t unicode_prop_Dash_table[58] = { + 0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e, + 0x40, 0x80, 0x44, 0x04, 0x80, 0x48, 0x08, 0x85, + 0xbc, 0x80, 0xa6, 0x80, 0x8e, 0x80, 0x41, 0x85, + 0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, 0x0b, 0x80, + 0x9b, 0x80, 0x41, 0xbd, 0x80, 0x92, 0x80, 0xee, + 0x80, 0x60, 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, + 0x80, 0x40, 0xa8, 0x80, 0x4e, 0x5f, 0x80, 0x41, + 0x3d, 0x80, +}; + +static const uint8_t unicode_prop_Deprecated_table[23] = { + 0x41, 0x48, 0x80, 0x45, 0x28, 0x80, 0x49, 0x02, + 0x00, 0x80, 0x48, 0x28, 0x81, 0x48, 0xc4, 0x85, + 0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80, +}; + +static const uint8_t unicode_prop_Diacritic_table[438] = { + 0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81, + 0x41, 0xf6, 0x40, 0x9e, 0x07, 0x25, 0x90, 0x0b, + 0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0, + 0x80, 0xb6, 0x90, 0x80, 0x9a, 0x00, 0x01, 0x00, + 0x40, 0x85, 0x3b, 0x81, 0x40, 0x85, 0x0b, 0x0a, + 0x82, 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1, + 0x81, 0xfd, 0x87, 0xa8, 0x89, 0x8f, 0x9b, 0xbc, + 0x80, 0x8f, 0x02, 0x83, 0x9b, 0x80, 0xc9, 0x80, + 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, + 0x8f, 0x80, 0xae, 0x82, 0xbb, 0x80, 0x8f, 0x06, + 0x80, 0xf6, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, + 0x80, 0x8f, 0x80, 0xec, 0x81, 0x8f, 0x80, 0xfb, + 0x80, 0xee, 0x80, 0x8b, 0x28, 0x80, 0xea, 0x80, + 0x8c, 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03, + 0x81, 0xc1, 0x10, 0x81, 0xbd, 0x80, 0xef, 0x00, + 0x81, 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89, + 0x81, 0x42, 0xc0, 0x82, 0x43, 0xb3, 0x81, 0x9d, + 0x80, 0x40, 0x93, 0x8a, 0x88, 0x80, 0x41, 0x5a, + 0x82, 0x41, 0x23, 0x80, 0x93, 0x39, 0x80, 0xaf, + 0x8e, 0x81, 0x8a, 0xe7, 0x80, 0x8e, 0x80, 0xa5, + 0x88, 0xb5, 0x81, 0xb9, 0x80, 0x8a, 0x81, 0xc1, + 0x81, 0xbf, 0x85, 0xd1, 0x98, 0x18, 0x28, 0x0a, + 0xb1, 0xbe, 0xd8, 0x8b, 0xa4, 0x8a, 0x41, 0xbc, + 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, 0x82, + 0x8c, 0x81, 0x4c, 0xef, 0x82, 0x41, 0x3c, 0x80, + 0x41, 0xf9, 0x85, 0xe8, 0x83, 0xde, 0x80, 0x60, + 0x75, 0x71, 0x80, 0x8b, 0x08, 0x80, 0x9b, 0x81, + 0xd1, 0x81, 0x8d, 0xa1, 0xe5, 0x82, 0xec, 0x81, + 0x8b, 0x80, 0xa4, 0x80, 0x40, 0x96, 0x80, 0x9a, + 0x91, 0xb8, 0x83, 0xa3, 0x80, 0xde, 0x80, 0x8b, + 0x80, 0xa3, 0x80, 0x40, 0x94, 0x82, 0xc0, 0x83, + 0xb2, 0x80, 0xe3, 0x84, 0x88, 0x82, 0xff, 0x81, + 0x60, 0x4f, 0x2f, 0x80, 0x43, 0x00, 0x8f, 0x41, + 0x0d, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, + 0x80, 0x42, 0xfb, 0x80, 0x44, 0x9e, 0x28, 0xa9, + 0x80, 0x88, 0x42, 0x7c, 0x13, 0x80, 0x40, 0xa4, + 0x81, 0x42, 0x3a, 0x85, 0xa5, 0x80, 0x99, 0x84, + 0x41, 0x8e, 0x82, 0xc5, 0x8a, 0xb0, 0x83, 0x40, + 0xbf, 0x80, 0xa8, 0x80, 0xc7, 0x81, 0xf7, 0x81, + 0xbd, 0x80, 0xcb, 0x80, 0x88, 0x82, 0xe7, 0x81, + 0x40, 0xb1, 0x81, 0xcf, 0x81, 0x8f, 0x80, 0x97, + 0x32, 0x84, 0xd8, 0x10, 0x81, 0x8c, 0x81, 0xde, + 0x02, 0x80, 0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, + 0x80, 0xf5, 0x81, 0xf2, 0x80, 0x41, 0x0c, 0x81, + 0x41, 0x01, 0x0b, 0x80, 0x40, 0x9b, 0x80, 0xd2, + 0x80, 0x91, 0x80, 0xd0, 0x80, 0x41, 0xa4, 0x80, + 0x41, 0x01, 0x00, 0x81, 0xd0, 0x80, 0x41, 0xa8, + 0x81, 0x96, 0x80, 0x54, 0xeb, 0x8e, 0x60, 0x2c, + 0xd8, 0x80, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x42, + 0x33, 0x81, 0x42, 0x21, 0x90, 0xcf, 0x81, 0x60, + 0x3f, 0xfd, 0x18, 0x30, 0x81, 0x5f, 0x00, 0xad, + 0x81, 0x96, 0x42, 0x1f, 0x12, 0x2f, 0x39, 0x86, + 0x9d, 0x83, 0x4e, 0x81, 0xbd, 0x40, 0xc1, 0x86, + 0x41, 0x76, 0x80, 0xbc, 0x83, 0x42, 0xfd, 0x81, + 0x42, 0xdf, 0x86, 0xec, 0x10, 0x82, +}; + +static const uint8_t unicode_prop_Extender_table[111] = { + 0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d, + 0x80, 0x41, 0xb8, 0x80, 0x42, 0x75, 0x80, 0x40, + 0x88, 0x80, 0xd8, 0x80, 0x42, 0xef, 0x80, 0xfe, + 0x80, 0x49, 0x42, 0x80, 0xb7, 0x80, 0x42, 0x62, + 0x80, 0x41, 0x8d, 0x80, 0xc3, 0x80, 0x53, 0x88, + 0x80, 0xaa, 0x84, 0xe6, 0x81, 0xdc, 0x82, 0x60, + 0x6f, 0x15, 0x80, 0x45, 0xf5, 0x80, 0x43, 0xc1, + 0x80, 0x95, 0x80, 0x40, 0x88, 0x80, 0xeb, 0x80, + 0x94, 0x81, 0x60, 0x54, 0x7a, 0x80, 0x48, 0x0f, + 0x81, 0x45, 0xca, 0x80, 0x9a, 0x03, 0x80, 0x44, + 0xc6, 0x80, 0x41, 0x24, 0x80, 0xf3, 0x81, 0x41, + 0xf1, 0x82, 0x44, 0xce, 0x80, 0x60, 0x50, 0xa8, + 0x81, 0x44, 0x9b, 0x08, 0x80, 0x60, 0x71, 0x57, + 0x81, 0x44, 0xb0, 0x80, 0x43, 0x53, 0x82, +}; + +static const uint8_t unicode_prop_Hex_Digit_table[12] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, 0x60, 0xfe, 0xa8, + 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_IDS_Unary_Operator_table[4] = { + 0x60, 0x2f, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_IDS_Binary_Operator_table[8] = { + 0x60, 0x2f, 0xef, 0x09, 0x89, 0x41, 0xf0, 0x80, +}; + +static const uint8_t unicode_prop_IDS_Trinary_Operator_table[4] = { + 0x60, 0x2f, 0xf1, 0x81, +}; + +static const uint8_t unicode_prop_Ideographic_table[72] = { + 0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82, + 0x43, 0xc4, 0x59, 0xbf, 0xbf, 0x60, 0x51, 0xff, + 0x60, 0x58, 0xff, 0x41, 0x6d, 0x81, 0xe9, 0x60, + 0x75, 0x09, 0x80, 0x9a, 0x57, 0xf7, 0x87, 0x44, + 0xd5, 0xa8, 0x89, 0x60, 0x24, 0x66, 0x41, 0x8b, + 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xdf, 0x9f, 0x50, + 0x39, 0x85, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, + 0x5d, 0x30, 0x8e, 0x42, 0x6d, 0x49, 0xa1, 0x42, + 0x1d, 0x45, 0xe1, 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_Join_Control_table[4] = { + 0x60, 0x20, 0x0b, 0x81, +}; + +static const uint8_t unicode_prop_Logical_Order_Exception_table[15] = { + 0x4e, 0x3f, 0x84, 0xfa, 0x84, 0x4a, 0xef, 0x11, + 0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81, +}; + +static const uint8_t unicode_prop_Modifier_Combining_Mark_table[16] = { + 0x46, 0x53, 0x09, 0x80, 0x40, 0x82, 0x05, 0x02, + 0x81, 0x41, 0xe0, 0x08, 0x12, 0x80, 0x9e, 0x80, +}; + +static const uint8_t unicode_prop_Noncharacter_Code_Point_table[71] = { + 0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_Syntax_table[58] = { + 0xa0, 0x8e, 0x89, 0x86, 0x99, 0x18, 0x80, 0x99, + 0x83, 0xa1, 0x30, 0x00, 0x08, 0x00, 0x0b, 0x03, + 0x02, 0x80, 0x96, 0x80, 0x9e, 0x80, 0x5f, 0x17, + 0x97, 0x87, 0x8e, 0x81, 0x92, 0x80, 0x89, 0x41, + 0x30, 0x42, 0xcf, 0x40, 0x9f, 0x42, 0x75, 0x9d, + 0x44, 0x6b, 0x41, 0xff, 0xff, 0x41, 0x80, 0x13, + 0x98, 0x8e, 0x80, 0x60, 0xcd, 0x0c, 0x81, 0x41, + 0x04, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_White_Space_table[11] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x5f, 0x87, + 0x81, 0x97, 0x81, +}; + +static const uint8_t unicode_prop_Quotation_Mark_table[31] = { + 0xa1, 0x03, 0x80, 0x40, 0x82, 0x80, 0x8e, 0x80, + 0x5f, 0x5b, 0x87, 0x98, 0x81, 0x4e, 0x06, 0x80, + 0x41, 0xc8, 0x83, 0x8c, 0x82, 0x60, 0xce, 0x20, + 0x83, 0x40, 0xbc, 0x03, 0x80, 0xd9, 0x81, +}; + +static const uint8_t unicode_prop_Radical_table[9] = { + 0x60, 0x2e, 0x7f, 0x99, 0x80, 0xd8, 0x8b, 0x40, + 0xd5, +}; + +static const uint8_t unicode_prop_Regional_Indicator_table[4] = { + 0x61, 0xf1, 0xe5, 0x99, +}; + +static const uint8_t unicode_prop_Sentence_Terminal_table[213] = { + 0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48, + 0x80, 0x40, 0x92, 0x82, 0x40, 0xb3, 0x80, 0xaa, + 0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81, + 0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15, + 0x03, 0x81, 0x43, 0x04, 0x80, 0x40, 0xc5, 0x81, + 0x40, 0x9c, 0x81, 0xac, 0x04, 0x80, 0x41, 0x39, + 0x81, 0x41, 0x61, 0x83, 0x40, 0xa1, 0x81, 0x89, + 0x09, 0x81, 0x9c, 0x82, 0x40, 0xba, 0x81, 0xc0, + 0x81, 0x43, 0xa3, 0x80, 0x96, 0x81, 0x88, 0x82, + 0x4c, 0xae, 0x82, 0x41, 0x31, 0x80, 0x8c, 0x80, + 0x95, 0x81, 0x41, 0xac, 0x80, 0x60, 0x74, 0xfb, + 0x80, 0x41, 0x0d, 0x81, 0x40, 0xe2, 0x02, 0x80, + 0x41, 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, + 0x97, 0x81, 0x40, 0x92, 0x82, 0x40, 0x8f, 0x81, + 0x40, 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, + 0xba, 0x02, 0x81, 0x40, 0xa8, 0x80, 0x8b, 0x80, + 0x8f, 0x80, 0xc0, 0x80, 0x4a, 0xf3, 0x81, 0x44, + 0xfc, 0x84, 0xab, 0x83, 0x40, 0xbc, 0x81, 0xf4, + 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, + 0x81, 0xd7, 0x08, 0x81, 0xeb, 0x80, 0x41, 0x29, + 0x81, 0xf4, 0x81, 0x41, 0x74, 0x0c, 0x8e, 0xe8, + 0x81, 0x40, 0xf8, 0x82, 0x42, 0x04, 0x00, 0x80, + 0x40, 0xfa, 0x81, 0xd6, 0x81, 0x41, 0xa3, 0x81, + 0x42, 0xb3, 0x81, 0xc9, 0x81, 0x60, 0x4b, 0x28, + 0x81, 0x40, 0x84, 0x80, 0xc0, 0x81, 0x8a, 0x80, + 0x42, 0x28, 0x81, 0x41, 0x27, 0x80, 0x60, 0x4e, + 0x05, 0x80, 0x5d, 0xe7, 0x80, +}; + +static const uint8_t unicode_prop_Soft_Dotted_table[79] = { + 0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80, + 0x9d, 0x80, 0xb3, 0x80, 0x93, 0x80, 0x41, 0x3f, + 0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2, + 0x80, 0x8c, 0x02, 0x80, 0x40, 0x83, 0x80, 0x40, + 0x9c, 0x80, 0x41, 0xa4, 0x80, 0x40, 0xd5, 0x81, + 0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0x48, + 0x85, 0x80, 0x41, 0x30, 0x81, 0x99, 0x80, +}; + +static const uint8_t unicode_prop_Terminal_Punctuation_table[264] = { + 0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, + 0x43, 0x3d, 0x07, 0x80, 0x42, 0x00, 0x80, 0xb8, + 0x80, 0xc7, 0x80, 0x8d, 0x00, 0x82, 0x40, 0xb3, + 0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5, + 0x28, 0x87, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, + 0xf3, 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, + 0x81, 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, + 0x82, 0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, + 0x19, 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, + 0x40, 0xa1, 0x81, 0x89, 0x08, 0x82, 0x9c, 0x82, + 0x40, 0xba, 0x84, 0xbd, 0x81, 0x43, 0xa3, 0x80, + 0x96, 0x81, 0x88, 0x82, 0x4c, 0xae, 0x82, 0x41, + 0x31, 0x80, 0x8c, 0x03, 0x80, 0x89, 0x00, 0x0a, + 0x81, 0x41, 0xab, 0x81, 0x60, 0x74, 0xfa, 0x81, + 0x41, 0x0c, 0x82, 0x40, 0xe2, 0x84, 0x41, 0x7d, + 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x96, 0x82, + 0x40, 0x92, 0x82, 0xfe, 0x80, 0x8f, 0x81, 0x40, + 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, 0xb8, + 0x10, 0x83, 0x40, 0xa8, 0x80, 0x89, 0x00, 0x80, + 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80, 0x44, 0x39, + 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80, 0x40, 0xc6, + 0x80, 0x41, 0x35, 0x81, 0x40, 0x97, 0x85, 0xc3, + 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84, 0xab, 0x83, + 0x40, 0xbc, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40, + 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, 0x84, 0xeb, + 0x80, 0x41, 0x29, 0x81, 0xf4, 0x82, 0x8b, 0x81, + 0x41, 0x65, 0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, + 0x82, 0x42, 0x04, 0x00, 0x80, 0x40, 0xfa, 0x81, + 0xd6, 0x0b, 0x81, 0x41, 0x9d, 0x82, 0xac, 0x80, + 0x42, 0x84, 0x81, 0xc9, 0x81, 0x45, 0x2a, 0x84, + 0x60, 0x45, 0xf8, 0x81, 0x40, 0x84, 0x80, 0xc0, + 0x82, 0x89, 0x80, 0x42, 0x28, 0x81, 0x41, 0x26, + 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, 0x83, +}; + +static const uint8_t unicode_prop_Unified_Ideograph_table[48] = { + 0x60, 0x33, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x51, + 0xff, 0x60, 0x5a, 0x0d, 0x08, 0x00, 0x81, 0x89, + 0x00, 0x00, 0x09, 0x82, 0x61, 0x05, 0xd5, 0x60, + 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, 0xdd, + 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, 0x42, + 0x6d, 0x51, 0xa1, 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_Variation_Selector_table[13] = { + 0x58, 0x0a, 0x10, 0x80, 0x60, 0xe5, 0xef, 0x8f, + 0x6d, 0x02, 0xef, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_White_Space_table[22] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x99, 0x80, + 0x55, 0xde, 0x80, 0x49, 0x7e, 0x8a, 0x9c, 0x0c, + 0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80, +}; + +static const uint8_t unicode_prop_Bidi_Mirrored_table[173] = { + 0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, + 0x9c, 0x00, 0x80, 0xac, 0x80, 0x8e, 0x80, 0x4e, + 0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81, + 0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0, + 0x80, 0x40, 0xbf, 0x1a, 0x2a, 0x02, 0x0a, 0x18, + 0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23, + 0x88, 0x08, 0x00, 0x38, 0x9f, 0x0b, 0x20, 0x88, + 0x09, 0x92, 0x21, 0x88, 0x21, 0x0b, 0x97, 0x81, + 0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d, + 0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d, + 0x41, 0x92, 0x95, 0x0d, 0x80, 0x8d, 0x38, 0x35, + 0x10, 0x1c, 0x01, 0x0c, 0x18, 0x02, 0x09, 0x89, + 0x29, 0x81, 0x8b, 0x92, 0x03, 0x08, 0x00, 0x08, + 0x03, 0x21, 0x2a, 0x97, 0x81, 0x8a, 0x0b, 0x18, + 0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00, + 0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80, + 0x42, 0x02, 0x1a, 0x08, 0x81, 0x8d, 0x09, 0x89, + 0xaa, 0x87, 0x41, 0xaa, 0x89, 0x0f, 0x60, 0xce, + 0x3c, 0x2c, 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, + 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, + 0x81, 0x60, 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, + 0x80, 0xb8, 0x80, 0xb8, 0x80, +}; + +static const uint8_t unicode_prop_Emoji_table[238] = { + 0xa2, 0x05, 0x04, 0x89, 0xee, 0x03, 0x80, 0x5f, + 0x8c, 0x80, 0x8b, 0x80, 0x40, 0xd7, 0x80, 0x95, + 0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81, + 0x8b, 0x80, 0x40, 0xa5, 0x80, 0x98, 0x8a, 0x1a, + 0x40, 0xc6, 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, + 0x88, 0x80, 0xb9, 0x18, 0x84, 0x88, 0x01, 0x01, + 0x09, 0x03, 0x01, 0x00, 0x09, 0x02, 0x02, 0x0f, + 0x14, 0x00, 0x04, 0x8b, 0x8a, 0x09, 0x00, 0x08, + 0x80, 0x91, 0x01, 0x81, 0x91, 0x28, 0x00, 0x0a, + 0x0c, 0x01, 0x0b, 0x81, 0x8a, 0x0c, 0x09, 0x04, + 0x08, 0x00, 0x81, 0x93, 0x0c, 0x28, 0x19, 0x03, + 0x01, 0x01, 0x28, 0x01, 0x00, 0x00, 0x05, 0x02, + 0x05, 0x80, 0x89, 0x81, 0x8e, 0x01, 0x03, 0x00, + 0x03, 0x10, 0x80, 0x8a, 0x81, 0xaf, 0x82, 0x88, + 0x80, 0x8d, 0x80, 0x8d, 0x80, 0x41, 0x73, 0x81, + 0x41, 0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, 0x80, + 0x44, 0xd9, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, + 0x80, 0x61, 0xbd, 0x69, 0x80, 0x40, 0xc9, 0x80, + 0x40, 0x9f, 0x81, 0x8b, 0x81, 0x8d, 0x01, 0x89, + 0xca, 0x99, 0x01, 0x96, 0x80, 0x93, 0x01, 0x88, + 0x94, 0x81, 0x40, 0xad, 0xa1, 0x81, 0xef, 0x09, + 0x02, 0x81, 0xd2, 0x0a, 0x80, 0x41, 0x06, 0x80, + 0xbe, 0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, + 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, + 0x04, 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, + 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x0a, 0x83, + 0x89, 0x10, 0x01, 0x10, 0x81, 0x89, 0x40, 0xe2, + 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, 0x80, + 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, 0x84, 0xb7, + 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, +}; + +static const uint8_t unicode_prop_Emoji_Component_table[28] = { + 0xa2, 0x05, 0x04, 0x89, 0x5f, 0xd2, 0x80, 0x40, + 0xd4, 0x80, 0x60, 0xdd, 0x2a, 0x80, 0x60, 0xf3, + 0xd5, 0x99, 0x41, 0xfa, 0x84, 0x45, 0xaf, 0x83, + 0x6c, 0x06, 0x6b, 0xdf, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_table[4] = { + 0x61, 0xf3, 0xfa, 0x84, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_Base_table[71] = { + 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, + 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, + 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, 0x10, 0x1a, + 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, 0xc8, 0x0b, + 0x80, 0x94, 0x03, 0x81, 0x40, 0xad, 0x12, 0x84, + 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, 0x8a, 0x80, + 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, 0x88, 0x89, + 0x0a, 0xb7, 0x80, 0xbc, 0x08, 0x08, 0x80, 0x90, + 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, 0x88, +}; + +static const uint8_t unicode_prop_Emoji_Presentation_table[144] = { + 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, + 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, + 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, + 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, + 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, + 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, + 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, + 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, + 0x01, 0x89, 0xca, 0x99, 0x00, 0x97, 0x80, 0x93, + 0x01, 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, + 0x8b, 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, + 0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, + 0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, + 0x80, 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, + 0xc5, 0x28, 0x12, 0x0a, 0x1b, 0x8a, 0x0e, 0x88, + 0x40, 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, + 0x89, 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, + 0x84, 0xb7, 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, +}; + +static const uint8_t unicode_prop_Extended_Pictographic_table[156] = { + 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, + 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, + 0x8e, 0x81, 0x41, 0x6e, 0x81, 0x8b, 0x80, 0xde, + 0x80, 0xc5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6, + 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, + 0xb9, 0x18, 0x28, 0x8b, 0x80, 0xf1, 0x89, 0xf5, + 0x81, 0x8a, 0x00, 0x00, 0x28, 0x10, 0x28, 0x89, + 0x81, 0x8e, 0x01, 0x03, 0x00, 0x03, 0x10, 0x80, + 0x8a, 0x84, 0xac, 0x82, 0x88, 0x80, 0x8d, 0x80, + 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, 0xce, 0x82, + 0x92, 0x81, 0xb2, 0x03, 0x80, 0x44, 0xd9, 0x80, + 0x8b, 0x80, 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd, + 0x65, 0x40, 0xff, 0x8c, 0x82, 0x9e, 0x80, 0xbb, + 0x85, 0x8b, 0x81, 0x8d, 0x01, 0x89, 0x91, 0xb8, + 0x9a, 0x8e, 0x89, 0x80, 0x93, 0x01, 0x88, 0x03, + 0x88, 0x41, 0xb1, 0x84, 0x41, 0x3d, 0x87, 0x41, + 0x09, 0xaf, 0xff, 0xf3, 0x8b, 0xd4, 0xaa, 0x8b, + 0x83, 0xb7, 0x87, 0x89, 0x85, 0xa7, 0x87, 0x9d, + 0xd1, 0x8b, 0xae, 0x80, 0x89, 0x80, 0x41, 0xb8, + 0x40, 0xff, 0x43, 0xfd, +}; + +static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table[51] = { + 0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb, + 0x80, 0x4b, 0x41, 0x81, 0x46, 0x52, 0x81, 0xd4, + 0x84, 0x47, 0xfa, 0x84, 0x99, 0x84, 0xb0, 0x8f, + 0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40, + 0xee, 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60, + 0xbc, 0xa6, 0x83, 0x54, 0xce, 0x87, 0x6c, 0x2e, + 0x84, 0x4f, 0xff, +}; + +typedef enum { + UNICODE_PROP_Hyphen, + UNICODE_PROP_Other_Math, + UNICODE_PROP_Other_Alphabetic, + UNICODE_PROP_Other_Lowercase, + UNICODE_PROP_Other_Uppercase, + UNICODE_PROP_Other_Grapheme_Extend, + UNICODE_PROP_Other_Default_Ignorable_Code_Point, + UNICODE_PROP_Other_ID_Start, + UNICODE_PROP_Other_ID_Continue, + UNICODE_PROP_Prepended_Concatenation_Mark, + UNICODE_PROP_ID_Continue1, + UNICODE_PROP_XID_Start1, + UNICODE_PROP_XID_Continue1, + UNICODE_PROP_Changes_When_Titlecased1, + UNICODE_PROP_Changes_When_Casefolded1, + UNICODE_PROP_Changes_When_NFKC_Casefolded1, + UNICODE_PROP_Basic_Emoji1, + UNICODE_PROP_Basic_Emoji2, + UNICODE_PROP_RGI_Emoji_Modifier_Sequence, + UNICODE_PROP_RGI_Emoji_Flag_Sequence, + UNICODE_PROP_Emoji_Keycap_Sequence, + UNICODE_PROP_ASCII_Hex_Digit, + UNICODE_PROP_Bidi_Control, + UNICODE_PROP_Dash, + UNICODE_PROP_Deprecated, + UNICODE_PROP_Diacritic, + UNICODE_PROP_Extender, + UNICODE_PROP_Hex_Digit, + UNICODE_PROP_IDS_Unary_Operator, + UNICODE_PROP_IDS_Binary_Operator, + UNICODE_PROP_IDS_Trinary_Operator, + UNICODE_PROP_Ideographic, + UNICODE_PROP_Join_Control, + UNICODE_PROP_Logical_Order_Exception, + UNICODE_PROP_Modifier_Combining_Mark, + UNICODE_PROP_Noncharacter_Code_Point, + UNICODE_PROP_Pattern_Syntax, + UNICODE_PROP_Pattern_White_Space, + UNICODE_PROP_Quotation_Mark, + UNICODE_PROP_Radical, + UNICODE_PROP_Regional_Indicator, + UNICODE_PROP_Sentence_Terminal, + UNICODE_PROP_Soft_Dotted, + UNICODE_PROP_Terminal_Punctuation, + UNICODE_PROP_Unified_Ideograph, + UNICODE_PROP_Variation_Selector, + UNICODE_PROP_White_Space, + UNICODE_PROP_Bidi_Mirrored, + UNICODE_PROP_Emoji, + UNICODE_PROP_Emoji_Component, + UNICODE_PROP_Emoji_Modifier, + UNICODE_PROP_Emoji_Modifier_Base, + UNICODE_PROP_Emoji_Presentation, + UNICODE_PROP_Extended_Pictographic, + UNICODE_PROP_Default_Ignorable_Code_Point, + UNICODE_PROP_ID_Start, + UNICODE_PROP_Case_Ignorable, + UNICODE_PROP_ASCII, + UNICODE_PROP_Alphabetic, + UNICODE_PROP_Any, + UNICODE_PROP_Assigned, + UNICODE_PROP_Cased, + UNICODE_PROP_Changes_When_Casefolded, + UNICODE_PROP_Changes_When_Casemapped, + UNICODE_PROP_Changes_When_Lowercased, + UNICODE_PROP_Changes_When_NFKC_Casefolded, + UNICODE_PROP_Changes_When_Titlecased, + UNICODE_PROP_Changes_When_Uppercased, + UNICODE_PROP_Grapheme_Base, + UNICODE_PROP_Grapheme_Extend, + UNICODE_PROP_ID_Continue, + UNICODE_PROP_ID_Compat_Math_Start, + UNICODE_PROP_ID_Compat_Math_Continue, + UNICODE_PROP_InCB, + UNICODE_PROP_Lowercase, + UNICODE_PROP_Math, + UNICODE_PROP_Uppercase, + UNICODE_PROP_XID_Continue, + UNICODE_PROP_XID_Start, + UNICODE_PROP_Cased1, + UNICODE_PROP_COUNT, +} UnicodePropertyEnum; + +static const char unicode_prop_name_table[] = + "ASCII_Hex_Digit,AHex" "\0" + "Bidi_Control,Bidi_C" "\0" + "Dash" "\0" + "Deprecated,Dep" "\0" + "Diacritic,Dia" "\0" + "Extender,Ext" "\0" + "Hex_Digit,Hex" "\0" + "IDS_Unary_Operator,IDSU" "\0" + "IDS_Binary_Operator,IDSB" "\0" + "IDS_Trinary_Operator,IDST" "\0" + "Ideographic,Ideo" "\0" + "Join_Control,Join_C" "\0" + "Logical_Order_Exception,LOE" "\0" + "Modifier_Combining_Mark,MCM" "\0" + "Noncharacter_Code_Point,NChar" "\0" + "Pattern_Syntax,Pat_Syn" "\0" + "Pattern_White_Space,Pat_WS" "\0" + "Quotation_Mark,QMark" "\0" + "Radical" "\0" + "Regional_Indicator,RI" "\0" + "Sentence_Terminal,STerm" "\0" + "Soft_Dotted,SD" "\0" + "Terminal_Punctuation,Term" "\0" + "Unified_Ideograph,UIdeo" "\0" + "Variation_Selector,VS" "\0" + "White_Space,space" "\0" + "Bidi_Mirrored,Bidi_M" "\0" + "Emoji" "\0" + "Emoji_Component,EComp" "\0" + "Emoji_Modifier,EMod" "\0" + "Emoji_Modifier_Base,EBase" "\0" + "Emoji_Presentation,EPres" "\0" + "Extended_Pictographic,ExtPict" "\0" + "Default_Ignorable_Code_Point,DI" "\0" + "ID_Start,IDS" "\0" + "Case_Ignorable,CI" "\0" + "ASCII" "\0" + "Alphabetic,Alpha" "\0" + "Any" "\0" + "Assigned" "\0" + "Cased" "\0" + "Changes_When_Casefolded,CWCF" "\0" + "Changes_When_Casemapped,CWCM" "\0" + "Changes_When_Lowercased,CWL" "\0" + "Changes_When_NFKC_Casefolded,CWKCF" "\0" + "Changes_When_Titlecased,CWT" "\0" + "Changes_When_Uppercased,CWU" "\0" + "Grapheme_Base,Gr_Base" "\0" + "Grapheme_Extend,Gr_Ext" "\0" + "ID_Continue,IDC" "\0" + "ID_Compat_Math_Start" "\0" + "ID_Compat_Math_Continue" "\0" + "InCB" "\0" + "Lowercase,Lower" "\0" + "Math" "\0" + "Uppercase,Upper" "\0" + "XID_Continue,XIDC" "\0" + "XID_Start,XIDS" "\0" +; + +static const uint8_t * const unicode_prop_table[] = { + unicode_prop_Hyphen_table, + unicode_prop_Other_Math_table, + unicode_prop_Other_Alphabetic_table, + unicode_prop_Other_Lowercase_table, + unicode_prop_Other_Uppercase_table, + unicode_prop_Other_Grapheme_Extend_table, + unicode_prop_Other_Default_Ignorable_Code_Point_table, + unicode_prop_Other_ID_Start_table, + unicode_prop_Other_ID_Continue_table, + unicode_prop_Prepended_Concatenation_Mark_table, + unicode_prop_ID_Continue1_table, + unicode_prop_XID_Start1_table, + unicode_prop_XID_Continue1_table, + unicode_prop_Changes_When_Titlecased1_table, + unicode_prop_Changes_When_Casefolded1_table, + unicode_prop_Changes_When_NFKC_Casefolded1_table, + unicode_prop_Basic_Emoji1_table, + unicode_prop_Basic_Emoji2_table, + unicode_prop_RGI_Emoji_Modifier_Sequence_table, + unicode_prop_RGI_Emoji_Flag_Sequence_table, + unicode_prop_Emoji_Keycap_Sequence_table, + unicode_prop_ASCII_Hex_Digit_table, + unicode_prop_Bidi_Control_table, + unicode_prop_Dash_table, + unicode_prop_Deprecated_table, + unicode_prop_Diacritic_table, + unicode_prop_Extender_table, + unicode_prop_Hex_Digit_table, + unicode_prop_IDS_Unary_Operator_table, + unicode_prop_IDS_Binary_Operator_table, + unicode_prop_IDS_Trinary_Operator_table, + unicode_prop_Ideographic_table, + unicode_prop_Join_Control_table, + unicode_prop_Logical_Order_Exception_table, + unicode_prop_Modifier_Combining_Mark_table, + unicode_prop_Noncharacter_Code_Point_table, + unicode_prop_Pattern_Syntax_table, + unicode_prop_Pattern_White_Space_table, + unicode_prop_Quotation_Mark_table, + unicode_prop_Radical_table, + unicode_prop_Regional_Indicator_table, + unicode_prop_Sentence_Terminal_table, + unicode_prop_Soft_Dotted_table, + unicode_prop_Terminal_Punctuation_table, + unicode_prop_Unified_Ideograph_table, + unicode_prop_Variation_Selector_table, + unicode_prop_White_Space_table, + unicode_prop_Bidi_Mirrored_table, + unicode_prop_Emoji_table, + unicode_prop_Emoji_Component_table, + unicode_prop_Emoji_Modifier_table, + unicode_prop_Emoji_Modifier_Base_table, + unicode_prop_Emoji_Presentation_table, + unicode_prop_Extended_Pictographic_table, + unicode_prop_Default_Ignorable_Code_Point_table, + unicode_prop_ID_Start_table, + unicode_prop_Case_Ignorable_table, +}; + +static const uint16_t unicode_prop_len_table[] = { + countof(unicode_prop_Hyphen_table), + countof(unicode_prop_Other_Math_table), + countof(unicode_prop_Other_Alphabetic_table), + countof(unicode_prop_Other_Lowercase_table), + countof(unicode_prop_Other_Uppercase_table), + countof(unicode_prop_Other_Grapheme_Extend_table), + countof(unicode_prop_Other_Default_Ignorable_Code_Point_table), + countof(unicode_prop_Other_ID_Start_table), + countof(unicode_prop_Other_ID_Continue_table), + countof(unicode_prop_Prepended_Concatenation_Mark_table), + countof(unicode_prop_ID_Continue1_table), + countof(unicode_prop_XID_Start1_table), + countof(unicode_prop_XID_Continue1_table), + countof(unicode_prop_Changes_When_Titlecased1_table), + countof(unicode_prop_Changes_When_Casefolded1_table), + countof(unicode_prop_Changes_When_NFKC_Casefolded1_table), + countof(unicode_prop_Basic_Emoji1_table), + countof(unicode_prop_Basic_Emoji2_table), + countof(unicode_prop_RGI_Emoji_Modifier_Sequence_table), + countof(unicode_prop_RGI_Emoji_Flag_Sequence_table), + countof(unicode_prop_Emoji_Keycap_Sequence_table), + countof(unicode_prop_ASCII_Hex_Digit_table), + countof(unicode_prop_Bidi_Control_table), + countof(unicode_prop_Dash_table), + countof(unicode_prop_Deprecated_table), + countof(unicode_prop_Diacritic_table), + countof(unicode_prop_Extender_table), + countof(unicode_prop_Hex_Digit_table), + countof(unicode_prop_IDS_Unary_Operator_table), + countof(unicode_prop_IDS_Binary_Operator_table), + countof(unicode_prop_IDS_Trinary_Operator_table), + countof(unicode_prop_Ideographic_table), + countof(unicode_prop_Join_Control_table), + countof(unicode_prop_Logical_Order_Exception_table), + countof(unicode_prop_Modifier_Combining_Mark_table), + countof(unicode_prop_Noncharacter_Code_Point_table), + countof(unicode_prop_Pattern_Syntax_table), + countof(unicode_prop_Pattern_White_Space_table), + countof(unicode_prop_Quotation_Mark_table), + countof(unicode_prop_Radical_table), + countof(unicode_prop_Regional_Indicator_table), + countof(unicode_prop_Sentence_Terminal_table), + countof(unicode_prop_Soft_Dotted_table), + countof(unicode_prop_Terminal_Punctuation_table), + countof(unicode_prop_Unified_Ideograph_table), + countof(unicode_prop_Variation_Selector_table), + countof(unicode_prop_White_Space_table), + countof(unicode_prop_Bidi_Mirrored_table), + countof(unicode_prop_Emoji_table), + countof(unicode_prop_Emoji_Component_table), + countof(unicode_prop_Emoji_Modifier_table), + countof(unicode_prop_Emoji_Modifier_Base_table), + countof(unicode_prop_Emoji_Presentation_table), + countof(unicode_prop_Extended_Pictographic_table), + countof(unicode_prop_Default_Ignorable_Code_Point_table), + countof(unicode_prop_ID_Start_table), + countof(unicode_prop_Case_Ignorable_table), +}; + +typedef enum { + UNICODE_SEQUENCE_PROP_Basic_Emoji, + UNICODE_SEQUENCE_PROP_Emoji_Keycap_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_Modifier_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_Flag_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_Tag_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence, + UNICODE_SEQUENCE_PROP_RGI_Emoji, + UNICODE_SEQUENCE_PROP_COUNT, +} UnicodeSequencePropertyEnum; + +static const char unicode_sequence_prop_name_table[] = + "Basic_Emoji" "\0" + "Emoji_Keycap_Sequence" "\0" + "RGI_Emoji_Modifier_Sequence" "\0" + "RGI_Emoji_Flag_Sequence" "\0" + "RGI_Emoji_Tag_Sequence" "\0" + "RGI_Emoji_ZWJ_Sequence" "\0" + "RGI_Emoji" "\0" +; + +static const uint8_t unicode_rgi_emoji_tag_sequence[18] = { + 0x67, 0x62, 0x65, 0x6e, 0x67, 0x00, 0x67, 0x62, + 0x73, 0x63, 0x74, 0x00, 0x67, 0x62, 0x77, 0x6c, + 0x73, 0x00, +}; + +static const uint8_t unicode_rgi_emoji_zwj_sequence[2320] = { + 0x02, 0xb8, 0x19, 0x40, 0x86, 0x02, 0xd1, 0x39, + 0xb0, 0x19, 0x02, 0x26, 0x39, 0x42, 0x86, 0x02, + 0xb4, 0x36, 0x42, 0x86, 0x03, 0x68, 0x54, 0x64, + 0x87, 0x68, 0x54, 0x02, 0xdc, 0x39, 0x42, 0x86, + 0x02, 0xd1, 0x39, 0x73, 0x13, 0x02, 0x39, 0x39, + 0x40, 0x86, 0x02, 0x69, 0x34, 0xbd, 0x19, 0x03, + 0xb6, 0x36, 0x40, 0x86, 0xa1, 0x87, 0x03, 0x68, + 0x74, 0x1d, 0x19, 0x68, 0x74, 0x03, 0x68, 0x34, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0xf1, 0x7a, 0xf2, + 0x7a, 0x02, 0xca, 0x33, 0x42, 0x86, 0x02, 0x69, + 0x34, 0xb0, 0x19, 0x04, 0x68, 0x14, 0x68, 0x14, + 0x67, 0x14, 0x66, 0x14, 0x02, 0xf9, 0x26, 0x42, + 0x86, 0x03, 0x69, 0x74, 0x1d, 0x19, 0x69, 0x74, + 0x03, 0xd1, 0x19, 0xbc, 0x19, 0xa1, 0x87, 0x02, + 0x3c, 0x19, 0x40, 0x86, 0x02, 0x68, 0x34, 0xeb, + 0x13, 0x02, 0xc3, 0x33, 0xa1, 0x87, 0x02, 0x70, + 0x34, 0x40, 0x86, 0x02, 0xd4, 0x39, 0x42, 0x86, + 0x02, 0xcf, 0x39, 0x42, 0x86, 0x02, 0x47, 0x36, + 0x40, 0x86, 0x02, 0x39, 0x39, 0x42, 0x86, 0x04, + 0xd1, 0x79, 0x64, 0x87, 0x8b, 0x14, 0xd1, 0x79, + 0x02, 0xd1, 0x39, 0x95, 0x86, 0x02, 0x68, 0x34, + 0x93, 0x13, 0x02, 0x69, 0x34, 0xed, 0x13, 0x02, + 0xda, 0x39, 0x40, 0x86, 0x03, 0x69, 0x34, 0xaf, + 0x19, 0xa1, 0x87, 0x02, 0xd1, 0x39, 0x93, 0x13, + 0x03, 0xce, 0x39, 0x42, 0x86, 0xa1, 0x87, 0x03, + 0xd1, 0x79, 0x64, 0x87, 0xd1, 0x79, 0x03, 0xc3, + 0x33, 0x42, 0x86, 0xa1, 0x87, 0x03, 0x69, 0x74, + 0x1d, 0x19, 0x68, 0x74, 0x02, 0x69, 0x34, 0x92, + 0x16, 0x02, 0xd1, 0x39, 0x96, 0x86, 0x04, 0x69, + 0x14, 0x64, 0x87, 0x8b, 0x14, 0x68, 0x14, 0x02, + 0x68, 0x34, 0x7c, 0x13, 0x02, 0x47, 0x36, 0x42, + 0x86, 0x02, 0x86, 0x34, 0x42, 0x86, 0x02, 0xd1, + 0x39, 0x7c, 0x13, 0x02, 0x69, 0x14, 0xa4, 0x13, + 0x02, 0xda, 0x39, 0x42, 0x86, 0x02, 0x37, 0x39, + 0x40, 0x86, 0x02, 0xd1, 0x39, 0x08, 0x87, 0x04, + 0x68, 0x54, 0x64, 0x87, 0x8b, 0x14, 0x68, 0x54, + 0x02, 0x4d, 0x36, 0x40, 0x86, 0x02, 0x68, 0x34, + 0x2c, 0x15, 0x02, 0x69, 0x34, 0xaf, 0x19, 0x02, + 0x6e, 0x34, 0x40, 0x86, 0x02, 0xcd, 0x39, 0x42, + 0x86, 0x02, 0xd1, 0x39, 0x2c, 0x15, 0x02, 0x6f, + 0x14, 0x40, 0x86, 0x03, 0xd1, 0x39, 0xbc, 0x19, + 0xa1, 0x87, 0x02, 0x68, 0x34, 0xa8, 0x13, 0x02, + 0x69, 0x34, 0x73, 0x13, 0x04, 0x69, 0x54, 0x64, + 0x87, 0x8b, 0x14, 0x68, 0x54, 0x02, 0x71, 0x34, + 0x42, 0x86, 0x02, 0xd1, 0x39, 0xa8, 0x13, 0x02, + 0x45, 0x36, 0x40, 0x86, 0x03, 0x69, 0x54, 0x64, + 0x87, 0x68, 0x54, 0x03, 0x69, 0x54, 0x64, 0x87, + 0x69, 0x54, 0x03, 0xce, 0x39, 0x40, 0x86, 0xa1, + 0x87, 0x02, 0xd8, 0x39, 0x40, 0x86, 0x03, 0xc3, + 0x33, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x4d, 0x36, + 0x42, 0x86, 0x02, 0xd1, 0x19, 0x92, 0x16, 0x02, + 0xd1, 0x39, 0xeb, 0x13, 0x02, 0x68, 0x34, 0xbc, + 0x14, 0x02, 0xd1, 0x39, 0xbc, 0x14, 0x02, 0x3d, + 0x39, 0x40, 0x86, 0x02, 0xb8, 0x39, 0x42, 0x86, + 0x02, 0xa3, 0x36, 0x40, 0x86, 0x02, 0x75, 0x35, + 0x40, 0x86, 0x02, 0xd8, 0x39, 0x42, 0x86, 0x02, + 0x69, 0x34, 0x93, 0x13, 0x02, 0x35, 0x39, 0x40, + 0x86, 0x02, 0x4b, 0x36, 0x40, 0x86, 0x02, 0x3d, + 0x39, 0x42, 0x86, 0x02, 0x38, 0x39, 0x42, 0x86, + 0x02, 0xa3, 0x36, 0x42, 0x86, 0x03, 0x69, 0x14, + 0x67, 0x14, 0x67, 0x14, 0x02, 0xb6, 0x36, 0x40, + 0x86, 0x02, 0x69, 0x34, 0x7c, 0x13, 0x02, 0x75, + 0x35, 0x42, 0x86, 0x02, 0xcc, 0x93, 0x40, 0x86, + 0x02, 0xcc, 0x33, 0x40, 0x86, 0x03, 0xd1, 0x39, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0x82, 0x34, 0x40, + 0x86, 0x02, 0x87, 0x34, 0x40, 0x86, 0x02, 0x69, + 0x14, 0x3e, 0x13, 0x02, 0xd6, 0x39, 0x40, 0x86, + 0x02, 0x68, 0x14, 0xbd, 0x19, 0x02, 0x46, 0x36, + 0x42, 0x86, 0x02, 0x4b, 0x36, 0x42, 0x86, 0x02, + 0x69, 0x34, 0x2c, 0x15, 0x03, 0xb6, 0x36, 0x42, + 0x86, 0xa1, 0x87, 0x02, 0xc4, 0x33, 0x40, 0x86, + 0x02, 0x26, 0x19, 0x40, 0x86, 0x02, 0x69, 0x14, + 0xb0, 0x19, 0x02, 0xde, 0x19, 0x42, 0x86, 0x02, + 0x69, 0x34, 0xa8, 0x13, 0x02, 0xcc, 0x33, 0x42, + 0x86, 0x02, 0x82, 0x34, 0x42, 0x86, 0x02, 0xd1, + 0x19, 0x93, 0x13, 0x02, 0x81, 0x14, 0x42, 0x86, + 0x02, 0x69, 0x34, 0x95, 0x86, 0x02, 0x68, 0x34, + 0xbb, 0x14, 0x02, 0xd1, 0x39, 0xbb, 0x14, 0x02, + 0x69, 0x34, 0xeb, 0x13, 0x02, 0xd1, 0x39, 0x84, + 0x13, 0x02, 0x69, 0x34, 0xbc, 0x14, 0x04, 0x69, + 0x54, 0x64, 0x87, 0x8b, 0x14, 0x69, 0x54, 0x02, + 0x26, 0x39, 0x40, 0x86, 0x02, 0xb4, 0x36, 0x40, + 0x86, 0x02, 0x47, 0x16, 0x42, 0x86, 0x02, 0xdc, + 0x39, 0x40, 0x86, 0x02, 0xca, 0x33, 0x40, 0x86, + 0x02, 0xf9, 0x26, 0x40, 0x86, 0x02, 0x69, 0x34, + 0x08, 0x87, 0x03, 0x69, 0x14, 0x69, 0x14, 0x66, + 0x14, 0x03, 0xd1, 0x59, 0x1d, 0x19, 0xd1, 0x59, + 0x02, 0xd4, 0x39, 0x40, 0x86, 0x02, 0xcf, 0x39, + 0x40, 0x86, 0x02, 0x68, 0x34, 0xa4, 0x13, 0x02, + 0xd1, 0x39, 0xa4, 0x13, 0x02, 0xd1, 0x19, 0xa8, + 0x13, 0x02, 0xd7, 0x39, 0x42, 0x86, 0x03, 0x69, + 0x34, 0xbc, 0x19, 0xa1, 0x87, 0x02, 0x68, 0x14, + 0xb0, 0x19, 0x02, 0x68, 0x14, 0x73, 0x13, 0x04, + 0x69, 0x14, 0x69, 0x14, 0x66, 0x14, 0x66, 0x14, + 0x03, 0x68, 0x34, 0xaf, 0x19, 0xa1, 0x87, 0x02, + 0x68, 0x34, 0x80, 0x16, 0x02, 0x73, 0x34, 0x42, + 0x86, 0x02, 0xd1, 0x39, 0x80, 0x16, 0x02, 0x68, + 0x34, 0xb0, 0x19, 0x02, 0x86, 0x34, 0x40, 0x86, + 0x02, 0x38, 0x19, 0x42, 0x86, 0x02, 0x69, 0x34, + 0xbb, 0x14, 0x02, 0xb5, 0x36, 0x42, 0x86, 0x02, + 0xcd, 0x39, 0x40, 0x86, 0x02, 0x68, 0x34, 0x95, + 0x86, 0x02, 0x68, 0x34, 0x27, 0x15, 0x03, 0x68, + 0x14, 0x68, 0x14, 0x66, 0x14, 0x02, 0x71, 0x34, + 0x40, 0x86, 0x02, 0xd1, 0x39, 0x27, 0x15, 0x02, + 0x2e, 0x16, 0xa8, 0x14, 0x02, 0xc3, 0x33, 0x42, + 0x86, 0x02, 0x69, 0x14, 0x66, 0x14, 0x02, 0x68, + 0x34, 0x96, 0x86, 0x02, 0x69, 0x34, 0xa4, 0x13, + 0x03, 0x69, 0x14, 0x64, 0x87, 0x68, 0x14, 0x02, + 0xb8, 0x39, 0x40, 0x86, 0x02, 0x68, 0x34, 0x3e, + 0x13, 0x03, 0xd1, 0x19, 0xaf, 0x19, 0xa1, 0x87, + 0x02, 0xd1, 0x39, 0x3e, 0x13, 0x02, 0x68, 0x34, + 0xbd, 0x19, 0x02, 0xd1, 0x19, 0xbb, 0x14, 0x02, + 0xd1, 0x19, 0x95, 0x86, 0x02, 0xdb, 0x39, 0x42, + 0x86, 0x02, 0x38, 0x39, 0x40, 0x86, 0x02, 0x69, + 0x34, 0x80, 0x16, 0x02, 0x69, 0x14, 0xeb, 0x13, + 0x04, 0x68, 0x14, 0x69, 0x14, 0x67, 0x14, 0x67, + 0x14, 0x02, 0x77, 0x34, 0x42, 0x86, 0x02, 0x46, + 0x36, 0x40, 0x86, 0x02, 0x68, 0x34, 0x92, 0x16, + 0x02, 0x4e, 0x36, 0x42, 0x86, 0x03, 0x69, 0x14, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0xde, 0x19, 0x40, + 0x86, 0x02, 0x69, 0x34, 0x27, 0x15, 0x03, 0xc3, + 0x13, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x81, 0x14, + 0x40, 0x86, 0x03, 0xd1, 0x39, 0xaf, 0x19, 0xa1, + 0x87, 0x02, 0x68, 0x34, 0xbc, 0x19, 0x02, 0xd1, + 0x19, 0x80, 0x16, 0x02, 0xd9, 0x39, 0x42, 0x86, + 0x02, 0xd1, 0x39, 0xbc, 0x19, 0x02, 0xdc, 0x19, + 0x42, 0x86, 0x02, 0x68, 0x34, 0x73, 0x13, 0x02, + 0x69, 0x34, 0x3e, 0x13, 0x02, 0x47, 0x16, 0x40, + 0x86, 0x02, 0xd1, 0x39, 0xbd, 0x19, 0x02, 0x3e, + 0x39, 0x42, 0x86, 0x02, 0x69, 0x14, 0x95, 0x86, + 0x02, 0x68, 0x14, 0x96, 0x86, 0x03, 0x69, 0x34, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0xd7, 0x39, 0x40, + 0x86, 0x02, 0x45, 0x16, 0x42, 0x86, 0x02, 0x68, + 0x34, 0xed, 0x13, 0x03, 0x68, 0x34, 0xbc, 0x19, + 0xa1, 0x87, 0x02, 0xd1, 0x39, 0xed, 0x13, 0x02, + 0xd1, 0x39, 0x92, 0x16, 0x02, 0x73, 0x34, 0x40, + 0x86, 0x02, 0x38, 0x19, 0x40, 0x86, 0x02, 0xb5, + 0x36, 0x40, 0x86, 0x02, 0x68, 0x34, 0xaf, 0x19, + 0x02, 0xd1, 0x39, 0xaf, 0x19, 0x02, 0x69, 0x34, + 0xbc, 0x19, 0x02, 0xb6, 0x16, 0x42, 0x86, 0x02, + 0x26, 0x14, 0x25, 0x15, 0x02, 0xc3, 0x33, 0x40, + 0x86, 0x02, 0xdd, 0x39, 0x42, 0x86, 0x02, 0xcb, + 0x93, 0x42, 0x86, 0x02, 0xcb, 0x33, 0x42, 0x86, + 0x02, 0x81, 0x34, 0x42, 0x86, 0x02, 0xce, 0x39, + 0xa1, 0x87, 0x02, 0xdb, 0x39, 0x40, 0x86, 0x02, + 0x68, 0x34, 0x08, 0x87, 0x02, 0xd1, 0x19, 0xb0, + 0x19, 0x02, 0x77, 0x34, 0x40, 0x86, 0x02, 0x4e, + 0x36, 0x40, 0x86, 0x02, 0xce, 0x39, 0x42, 0x86, + 0x02, 0x4e, 0x16, 0x42, 0x86, 0x02, 0xd9, 0x39, + 0x40, 0x86, 0x02, 0xdc, 0x19, 0x40, 0x86, 0x02, + 0x3e, 0x39, 0x40, 0x86, 0x02, 0xb9, 0x39, 0x42, + 0x86, 0x02, 0xda, 0x19, 0x42, 0x86, 0x02, 0x42, + 0x16, 0x94, 0x81, 0x02, 0x45, 0x16, 0x40, 0x86, + 0x02, 0x69, 0x14, 0xbd, 0x19, 0x02, 0x70, 0x34, + 0x42, 0x86, 0x02, 0xce, 0x19, 0xa1, 0x87, 0x02, + 0xc3, 0x13, 0x42, 0x86, 0x02, 0x68, 0x14, 0x08, + 0x87, 0x02, 0xd1, 0x19, 0x7c, 0x13, 0x02, 0x68, + 0x14, 0x92, 0x16, 0x02, 0xb6, 0x16, 0x40, 0x86, + 0x02, 0x37, 0x39, 0x42, 0x86, 0x03, 0xce, 0x19, + 0x42, 0x86, 0xa1, 0x87, 0x03, 0x68, 0x14, 0x67, + 0x14, 0x67, 0x14, 0x02, 0xdd, 0x39, 0x40, 0x86, + 0x02, 0xcf, 0x19, 0x42, 0x86, 0x02, 0xd1, 0x19, + 0x2c, 0x15, 0x02, 0x4b, 0x13, 0xe9, 0x17, 0x02, + 0x68, 0x14, 0x67, 0x14, 0x02, 0xcb, 0x93, 0x40, + 0x86, 0x02, 0x6e, 0x34, 0x42, 0x86, 0x02, 0xcb, + 0x33, 0x40, 0x86, 0x02, 0x81, 0x34, 0x40, 0x86, + 0x02, 0xb6, 0x36, 0xa1, 0x87, 0x02, 0x45, 0x36, + 0x42, 0x86, 0x02, 0xb4, 0x16, 0x42, 0x86, 0x02, + 0x69, 0x14, 0x73, 0x13, 0x04, 0x69, 0x14, 0x69, + 0x14, 0x67, 0x14, 0x66, 0x14, 0x02, 0x35, 0x39, + 0x42, 0x86, 0x02, 0x68, 0x14, 0x93, 0x13, 0x02, + 0xb6, 0x36, 0x42, 0x86, 0x03, 0x68, 0x14, 0x69, + 0x14, 0x66, 0x14, 0x02, 0xce, 0x39, 0x40, 0x86, + 0x02, 0x4e, 0x16, 0x40, 0x86, 0x02, 0x87, 0x34, + 0x42, 0x86, 0x02, 0x86, 0x14, 0x42, 0x86, 0x02, + 0xd6, 0x39, 0x42, 0x86, 0x02, 0xc4, 0x33, 0x42, + 0x86, 0x02, 0x69, 0x34, 0x96, 0x86, 0x02, 0xb9, + 0x39, 0x40, 0x86, 0x02, 0x68, 0x14, 0xa8, 0x13, + 0x02, 0xd1, 0x19, 0x84, 0x13, 0x02, 0xda, 0x19, + 0x40, 0x86, 0x02, 0xd8, 0x19, 0x42, 0x86, 0x02, + 0xc3, 0x13, 0x40, 0x86, 0x02, 0xb9, 0x19, 0x42, + 0x86, 0x02, 0x3d, 0x19, 0x42, 0x86, 0x02, 0xcf, + 0x19, 0x40, 0x86, 0x04, 0x68, 0x14, 0x68, 0x14, + 0x67, 0x14, 0x67, 0x14, 0x03, 0xd1, 0x19, 0xd1, + 0x19, 0xd2, 0x19, 0x02, 0x68, 0x14, 0xbb, 0x14, + 0x02, 0x3b, 0x14, 0x44, 0x87, 0x02, 0xd1, 0x19, + 0x27, 0x15, 0x02, 0xb4, 0x16, 0x40, 0x86, 0x02, + 0xcd, 0x19, 0x42, 0x86, 0x02, 0xd3, 0x86, 0xa5, + 0x14, 0x02, 0x70, 0x14, 0x42, 0x86, 0x03, 0xb6, + 0x16, 0x42, 0x86, 0xa1, 0x87, 0x04, 0x69, 0x14, + 0x64, 0x87, 0x8b, 0x14, 0x69, 0x14, 0x02, 0x36, + 0x16, 0x2b, 0x93, 0x02, 0x68, 0x14, 0x80, 0x16, + 0x02, 0x86, 0x14, 0x40, 0x86, 0x02, 0x08, 0x14, + 0x1b, 0x0b, 0x02, 0xd1, 0x19, 0xbc, 0x19, 0x02, + 0xca, 0x13, 0x42, 0x86, 0x02, 0x41, 0x94, 0xe8, + 0x95, 0x02, 0xd8, 0x19, 0x40, 0x86, 0x02, 0xb9, + 0x19, 0x40, 0x86, 0x02, 0xd1, 0x19, 0xed, 0x13, + 0x02, 0xf9, 0x86, 0x42, 0x86, 0x03, 0xd1, 0x19, + 0xbd, 0x19, 0xa1, 0x87, 0x02, 0x3d, 0x19, 0x40, + 0x86, 0x02, 0xd6, 0x19, 0x42, 0x86, 0x03, 0x69, + 0x14, 0x66, 0x14, 0x66, 0x14, 0x02, 0xd1, 0x19, + 0xaf, 0x19, 0x03, 0x69, 0x14, 0x69, 0x14, 0x67, + 0x14, 0x02, 0xcd, 0x19, 0x40, 0x86, 0x02, 0x70, + 0x14, 0x40, 0x86, 0x03, 0x68, 0x14, 0xbc, 0x19, + 0xa1, 0x87, 0x02, 0x6e, 0x14, 0x42, 0x86, 0x02, + 0x69, 0x14, 0x92, 0x16, 0x03, 0x68, 0x14, 0x68, + 0x14, 0x67, 0x14, 0x02, 0x69, 0x14, 0x67, 0x14, + 0x02, 0x75, 0x95, 0x42, 0x86, 0x03, 0x69, 0x14, + 0x64, 0x87, 0x69, 0x14, 0x02, 0xd1, 0x19, 0xbc, + 0x14, 0x02, 0xdf, 0x19, 0x42, 0x86, 0x02, 0xca, + 0x13, 0x40, 0x86, 0x02, 0x82, 0x14, 0x42, 0x86, + 0x02, 0x69, 0x14, 0x93, 0x13, 0x02, 0x68, 0x14, + 0x7c, 0x13, 0x02, 0xf9, 0x86, 0x40, 0x86, 0x02, + 0xd6, 0x19, 0x40, 0x86, 0x02, 0x68, 0x14, 0x2c, + 0x15, 0x02, 0x69, 0x14, 0xa8, 0x13, 0x02, 0xd4, + 0x19, 0x42, 0x86, 0x04, 0x68, 0x14, 0x69, 0x14, + 0x66, 0x14, 0x66, 0x14, 0x02, 0x77, 0x14, 0x42, + 0x86, 0x02, 0x39, 0x19, 0x42, 0x86, 0x02, 0xd1, + 0x19, 0xa4, 0x13, 0x02, 0x6e, 0x14, 0x40, 0x86, + 0x03, 0xd1, 0x19, 0xd2, 0x19, 0xd2, 0x19, 0x02, + 0x69, 0x14, 0xbb, 0x14, 0x02, 0xd1, 0x19, 0x96, + 0x86, 0x02, 0x75, 0x95, 0x40, 0x86, 0x04, 0x68, + 0x14, 0x64, 0x87, 0x8b, 0x14, 0x68, 0x14, 0x02, + 0xd1, 0x19, 0x3e, 0x13, 0x02, 0xdf, 0x19, 0x40, + 0x86, 0x02, 0x82, 0x14, 0x40, 0x86, 0x02, 0x44, + 0x13, 0xeb, 0x17, 0x02, 0xdd, 0x19, 0x42, 0x86, + 0x02, 0x69, 0x14, 0x80, 0x16, 0x03, 0x68, 0x14, + 0xaf, 0x19, 0xa1, 0x87, 0x02, 0xa3, 0x16, 0x42, + 0x86, 0x02, 0x69, 0x14, 0x96, 0x86, 0x02, 0x46, + 0x16, 0x42, 0x86, 0x02, 0xb6, 0x16, 0xa1, 0x87, + 0x02, 0x68, 0x14, 0x27, 0x15, 0x02, 0x26, 0x14, + 0x1b, 0x0b, 0x02, 0xd4, 0x19, 0x40, 0x86, 0x02, + 0x77, 0x14, 0x40, 0x86, 0x02, 0x39, 0x19, 0x40, + 0x86, 0x02, 0x37, 0x19, 0x42, 0x86, 0x03, 0x69, + 0x14, 0x67, 0x14, 0x66, 0x14, 0x03, 0xc3, 0x13, + 0x42, 0x86, 0xa1, 0x87, 0x02, 0x68, 0x14, 0xbc, + 0x19, 0x02, 0xd1, 0x19, 0xeb, 0x13, 0x04, 0x69, + 0x14, 0x69, 0x14, 0x67, 0x14, 0x67, 0x14, 0x02, + 0xd1, 0x19, 0x08, 0x87, 0x02, 0x68, 0x14, 0xed, + 0x13, 0x03, 0x69, 0x14, 0xbc, 0x19, 0xa1, 0x87, + 0x02, 0xdd, 0x19, 0x40, 0x86, 0x02, 0xc3, 0x13, + 0xa1, 0x87, 0x03, 0x68, 0x14, 0x66, 0x14, 0x66, + 0x14, 0x03, 0x68, 0x14, 0x69, 0x14, 0x67, 0x14, + 0x02, 0xa3, 0x16, 0x40, 0x86, 0x02, 0xdb, 0x19, + 0x42, 0x86, 0x02, 0x68, 0x14, 0xaf, 0x19, 0x02, + 0x46, 0x16, 0x40, 0x86, 0x02, 0x35, 0x16, 0xab, + 0x14, 0x02, 0x68, 0x14, 0x95, 0x86, 0x02, 0x42, + 0x16, 0x95, 0x81, 0x02, 0xc4, 0x13, 0x42, 0x86, + 0x02, 0x15, 0x14, 0xba, 0x19, 0x02, 0x69, 0x14, + 0x08, 0x87, 0x03, 0xd1, 0x19, 0x1d, 0x19, 0xd1, + 0x19, 0x02, 0x69, 0x14, 0x7c, 0x13, 0x02, 0x37, + 0x19, 0x40, 0x86, 0x02, 0x73, 0x14, 0x42, 0x86, + 0x02, 0x69, 0x14, 0x2c, 0x15, 0x02, 0xb5, 0x16, + 0x42, 0x86, 0x02, 0x35, 0x19, 0x42, 0x86, 0x04, + 0x68, 0x14, 0x69, 0x14, 0x67, 0x14, 0x66, 0x14, + 0x02, 0x64, 0x87, 0x25, 0x15, 0x02, 0x64, 0x87, + 0x79, 0x1a, 0x02, 0x68, 0x14, 0xbc, 0x14, 0x03, + 0xce, 0x19, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x87, + 0x14, 0x42, 0x86, 0x02, 0x4d, 0x16, 0x42, 0x86, + 0x04, 0x68, 0x14, 0x68, 0x14, 0x66, 0x14, 0x66, + 0x14, 0x02, 0xdb, 0x19, 0x40, 0x86, 0x02, 0xd9, + 0x19, 0x42, 0x86, 0x02, 0xc4, 0x13, 0x40, 0x86, + 0x02, 0xd1, 0x19, 0xbd, 0x19, 0x02, 0x68, 0x14, + 0xa4, 0x13, 0x02, 0x3e, 0x19, 0x42, 0x86, 0x02, + 0xf3, 0x93, 0xa7, 0x86, 0x03, 0x69, 0x14, 0xaf, + 0x19, 0xa1, 0x87, 0x02, 0xf3, 0x93, 0x08, 0x13, + 0x02, 0xd1, 0x19, 0xd2, 0x19, 0x02, 0x73, 0x14, + 0x40, 0x86, 0x02, 0xb5, 0x16, 0x40, 0x86, 0x02, + 0x35, 0x19, 0x40, 0x86, 0x02, 0x69, 0x14, 0x27, + 0x15, 0x02, 0xce, 0x19, 0x42, 0x86, 0x02, 0x71, + 0x14, 0x42, 0x86, 0x02, 0xd1, 0x19, 0x73, 0x13, + 0x02, 0x68, 0x14, 0x3e, 0x13, 0x02, 0xf4, 0x13, + 0x20, 0x86, 0x02, 0x87, 0x14, 0x40, 0x86, 0x03, + 0xb6, 0x16, 0x40, 0x86, 0xa1, 0x87, 0x02, 0x4d, + 0x16, 0x40, 0x86, 0x02, 0x69, 0x14, 0xbc, 0x19, + 0x02, 0x4b, 0x16, 0x42, 0x86, 0x02, 0xd9, 0x19, + 0x40, 0x86, 0x02, 0x3e, 0x19, 0x40, 0x86, 0x02, + 0x69, 0x14, 0xed, 0x13, 0x02, 0xd7, 0x19, 0x42, + 0x86, 0x02, 0xb8, 0x19, 0x42, 0x86, 0x03, 0x68, + 0x14, 0x67, 0x14, 0x66, 0x14, 0x02, 0x3c, 0x19, + 0x42, 0x86, 0x02, 0x68, 0x14, 0x66, 0x14, 0x03, + 0x68, 0x14, 0x64, 0x87, 0x68, 0x14, 0x02, 0x69, + 0x14, 0xaf, 0x19, 0x02, 0xce, 0x19, 0x40, 0x86, + 0x02, 0x71, 0x14, 0x40, 0x86, 0x02, 0x68, 0x14, + 0xeb, 0x13, 0x03, 0x68, 0x14, 0xbd, 0x19, 0xa1, + 0x87, 0x02, 0x6f, 0x14, 0x42, 0x86, 0x04, 0xd1, + 0x19, 0xd1, 0x19, 0xd2, 0x19, 0xd2, 0x19, 0x02, + 0x69, 0x14, 0xbc, 0x14, 0x02, 0xcc, 0x93, 0x42, + 0x86, 0x02, 0x4b, 0x16, 0x40, 0x86, 0x02, 0x26, + 0x19, 0x42, 0x86, 0x02, 0xd7, 0x19, 0x40, 0x86, +}; + +#endif /* CONFIG_ALL_UNICODE */ +/* 71 tables / 36311 bytes, 5 index / 351 bytes */ diff --git a/NativeScript/napi/android/quickjs/source/libunicode.c b/NativeScript/napi/android/quickjs/source/libunicode.c new file mode 100755 index 000000000..0c510ccb1 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/libunicode.c @@ -0,0 +1,2123 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "libunicode.h" +#include "libunicode-table.h" + +enum { + RUN_TYPE_U, + RUN_TYPE_L, + RUN_TYPE_UF, + RUN_TYPE_LF, + RUN_TYPE_UL, + RUN_TYPE_LSU, + RUN_TYPE_U2L_399_EXT2, + RUN_TYPE_UF_D20, + RUN_TYPE_UF_D1_EXT, + RUN_TYPE_U_EXT, + RUN_TYPE_LF_EXT, + RUN_TYPE_UF_EXT2, + RUN_TYPE_LF_EXT2, + RUN_TYPE_UF_EXT3, +}; + +static int lre_case_conv1(uint32_t c, int conv_type) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + lre_case_conv(res, c, conv_type); + return res[0]; +} + +/* case conversion using the table entry 'idx' with value 'v' */ +static int lre_case_conv_entry(uint32_t *res, uint32_t c, int conv_type, uint32_t idx, uint32_t v) +{ + uint32_t code, data, type, a, is_lower; + is_lower = (conv_type != 0); + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + data = ((v & 0xf) << 8) | case_conv_table2[idx]; + code = v >> (32 - 17); + switch(type) { + case RUN_TYPE_U: + case RUN_TYPE_L: + case RUN_TYPE_UF: + case RUN_TYPE_LF: + if (conv_type == (type & 1) || + (type >= RUN_TYPE_UF && conv_type == 2)) { + c = c - code + (case_conv_table1[data] >> (32 - 17)); + } + break; + case RUN_TYPE_UL: + a = c - code; + if ((a & 1) != (1 - is_lower)) + break; + c = (a ^ 1) + code; + break; + case RUN_TYPE_LSU: + a = c - code; + if (a == 1) { + c += 2 * is_lower - 1; + } else if (a == (1 - is_lower) * 2) { + c += (2 * is_lower - 1) * 2; + } + break; + case RUN_TYPE_U2L_399_EXT2: + if (!is_lower) { + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = 0x399; + return 2; + } else { + c = c - code + case_conv_ext[data & 0x3f]; + } + break; + case RUN_TYPE_UF_D20: + if (conv_type == 1) + break; + c = data + (conv_type == 2) * 0x20; + break; + case RUN_TYPE_UF_D1_EXT: + if (conv_type == 1) + break; + c = case_conv_ext[data] + (conv_type == 2); + break; + case RUN_TYPE_U_EXT: + case RUN_TYPE_LF_EXT: + if (is_lower != (type - RUN_TYPE_U_EXT)) + break; + c = case_conv_ext[data]; + break; + case RUN_TYPE_LF_EXT2: + if (!is_lower) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + return 2; + case RUN_TYPE_UF_EXT2: + if (conv_type == 1) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + } + return 2; + default: + case RUN_TYPE_UF_EXT3: + if (conv_type == 1) + break; + res[0] = case_conv_ext[data >> 8]; + res[1] = case_conv_ext[(data >> 4) & 0xf]; + res[2] = case_conv_ext[data & 0xf]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + res[2] = lre_case_conv1(res[2], 1); + } + return 3; + } + res[0] = c; + return 1; +} + +/* conv_type: + 0 = to upper + 1 = to lower + 2 = case folding (= to lower with modifications) +*/ +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type) +{ + if (c < 128) { + if (conv_type) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_conv_entry(res, c, conv_type, idx, v); + } + } + } + res[0] = c; + return 1; +} + +static int lre_case_folding_entry(uint32_t c, uint32_t idx, uint32_t v, BOOL is_unicode) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + int len; + + if (is_unicode) { + len = lre_case_conv_entry(res, c, 2, idx, v); + if (len == 1) { + c = res[0]; + } else { + /* handle the few specific multi-character cases (see + unicode_gen.c:dump_case_folding_special_cases()) */ + if (c == 0xfb06) { + c = 0xfb05; + } else if (c == 0x01fd3) { + c = 0x390; + } else if (c == 0x01fe3) { + c = 0x3b0; + } + } + } else { + if (likely(c < 128)) { + if (c >= 'a' && c <= 'z') + c = c - 'a' + 'A'; + } else { + /* legacy regexp: to upper case if single char >= 128 */ + len = lre_case_conv_entry(res, c, FALSE, idx, v); + if (len == 1 && res[0] >= 128) + c = res[0]; + } + } + return c; +} + +/* JS regexp specific rules for case folding */ +int lre_canonicalize(uint32_t c, BOOL is_unicode) +{ + if (c < 128) { + /* fast case */ + if (is_unicode) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_folding_entry(c, idx, v, is_unicode); + } + } + } + return c; +} + +static uint32_t get_le24(const uint8_t *ptr) +{ + return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16); +} + +#define UNICODE_INDEX_BLOCK_LEN 32 + +/* return -1 if not in table, otherwise the offset in the block */ +static int get_index_pos(uint32_t *pcode, uint32_t c, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, v; + int idx_min, idx_max, idx; + + idx_min = 0; + v = get_le24(index_table); + code = v & ((1 << 21) - 1); + if (c < code) { + *pcode = 0; + return 0; + } + idx_max = index_table_len - 1; + code = get_le24(index_table + idx_max * 3); + if (c >= code) + return -1; + /* invariant: tab[idx_min] <= c < tab2[idx_max] */ + while ((idx_max - idx_min) > 1) { + idx = (idx_max + idx_min) / 2; + v = get_le24(index_table + idx * 3); + code = v & ((1 << 21) - 1); + if (c < code) { + idx_max = idx; + } else { + idx_min = idx; + } + } + v = get_le24(index_table + idx_min * 3); + *pcode = v & ((1 << 21) - 1); + return (idx_min + 1) * UNICODE_INDEX_BLOCK_LEN + (v >> 21); +} + +static BOOL lre_is_in_table(uint32_t c, const uint8_t *table, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, b, bit; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, index_table, index_table_len); + if (pos < 0) + return FALSE; /* outside the table */ + p = table + pos; + bit = 0; + /* Compressed run length encoding: + 00..3F: 2 packed lengths: 3-bit + 3-bit + 40..5F: 5-bits plus extra byte for length + 60..7F: 5-bits plus 2 extra bytes for length + 80..FF: 7-bit length + lengths must be incremented to get character count + Ranges alternate between false and true return value. + */ + for(;;) { + b = *p++; + if (b < 64) { + code += (b >> 3) + 1; + if (c < code) + return bit; + bit ^= 1; + code += (b & 7) + 1; + } else if (b >= 0x80) { + code += b - 0x80 + 1; + } else if (b < 0x60) { + code += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + code += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (c < code) + return bit; + bit ^= 1; + } +} + +BOOL lre_is_cased(uint32_t c) +{ + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return TRUE; + } + } + return lre_is_in_table(c, unicode_prop_Cased1_table, + unicode_prop_Cased1_index, + sizeof(unicode_prop_Cased1_index) / 3); +} + +BOOL lre_is_case_ignorable(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_Case_Ignorable_table, + unicode_prop_Case_Ignorable_index, + sizeof(unicode_prop_Case_Ignorable_index) / 3); +} + +/* character range */ + +static __maybe_unused void cr_dump(CharRange *cr) +{ + int i; + for(i = 0; i < cr->len; i++) + printf("%d: 0x%04x\n", i, cr->points[i]); +} + +static void *cr_default_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void cr_init(CharRange *cr, void *mem_opaque, DynBufReallocFunc *realloc_func) +{ + cr->len = cr->size = 0; + cr->points = NULL; + cr->mem_opaque = mem_opaque; + cr->realloc_func = realloc_func ? realloc_func : cr_default_realloc; +} + +void cr_free(CharRange *cr) +{ + cr->realloc_func(cr->mem_opaque, cr->points, 0); +} + +int cr_realloc(CharRange *cr, int size) +{ + int new_size; + uint32_t *new_buf; + + if (size > cr->size) { + new_size = max_int(size, cr->size * 3 / 2); + new_buf = cr->realloc_func(cr->mem_opaque, cr->points, + new_size * sizeof(cr->points[0])); + if (!new_buf) + return -1; + cr->points = new_buf; + cr->size = new_size; + } + return 0; +} + +int cr_copy(CharRange *cr, const CharRange *cr1) +{ + if (cr_realloc(cr, cr1->len)) + return -1; + memcpy(cr->points, cr1->points, sizeof(cr->points[0]) * cr1->len); + cr->len = cr1->len; + return 0; +} + +/* merge consecutive intervals and remove empty intervals */ +static void cr_compress(CharRange *cr) +{ + int i, j, k, len; + uint32_t *pt; + + pt = cr->points; + len = cr->len; + i = 0; + j = 0; + k = 0; + while ((i + 1) < len) { + if (pt[i] == pt[i + 1]) { + /* empty interval */ + i += 2; + } else { + j = i; + while ((j + 3) < len && pt[j + 1] == pt[j + 2]) + j += 2; + /* just copy */ + pt[k] = pt[i]; + pt[k + 1] = pt[j + 1]; + k += 2; + i = j + 2; + } + } + cr->len = k; +} + +/* union or intersection */ +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op) +{ + int a_idx, b_idx, is_in; + uint32_t v; + + a_idx = 0; + b_idx = 0; + for(;;) { + /* get one more point from a or b in increasing order */ + if (a_idx < a_len && b_idx < b_len) { + if (a_pt[a_idx] < b_pt[b_idx]) { + goto a_add; + } else if (a_pt[a_idx] == b_pt[b_idx]) { + v = a_pt[a_idx]; + a_idx++; + b_idx++; + } else { + goto b_add; + } + } else if (a_idx < a_len) { + a_add: + v = a_pt[a_idx++]; + } else if (b_idx < b_len) { + b_add: + v = b_pt[b_idx++]; + } else { + break; + } + /* add the point if the in/out status changes */ + switch(op) { + case CR_OP_UNION: + is_in = (a_idx & 1) | (b_idx & 1); + break; + case CR_OP_INTER: + is_in = (a_idx & 1) & (b_idx & 1); + break; + case CR_OP_XOR: + is_in = (a_idx & 1) ^ (b_idx & 1); + break; + case CR_OP_SUB: + is_in = (a_idx & 1) & ((b_idx & 1) ^ 1); + break; + default: + abort(); + } + if (is_in != (cr->len & 1)) { + if (cr_add_point(cr, v)) + return -1; + } + } + cr_compress(cr); + return 0; +} + +int cr_op1(CharRange *cr, const uint32_t *b_pt, int b_len, int op) +{ + CharRange a = *cr; + int ret; + cr->len = 0; + cr->size = 0; + cr->points = NULL; + ret = cr_op(cr, a.points, a.len, b_pt, b_len, op); + cr_free(&a); + return ret; +} + +int cr_invert(CharRange *cr) +{ + int len; + len = cr->len; + if (cr_realloc(cr, len + 2)) + return -1; + memmove(cr->points + 1, cr->points, len * sizeof(cr->points[0])); + cr->points[0] = 0; + cr->points[len + 1] = UINT32_MAX; + cr->len = len + 2; + cr_compress(cr); + return 0; +} + +#define CASE_U (1 << 0) +#define CASE_L (1 << 1) +#define CASE_F (1 << 2) + +/* use the case conversion table to generate range of characters. + CASE_U: set char if modified by uppercasing, + CASE_L: set char if modified by lowercasing, + CASE_F: set char if modified by case folding, + */ +static int unicode_case1(CharRange *cr, int case_mask) +{ +#define MR(x) (1 << RUN_TYPE_ ## x) + const uint32_t tab_run_mask[3] = { + MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | + MR(UF_D1_EXT) | MR(U_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + + MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2), + + MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + }; +#undef MR + uint32_t mask, v, code, type, len, i, idx; + + if (case_mask == 0) + return 0; + mask = 0; + for(i = 0; i < 3; i++) { + if ((case_mask >> i) & 1) + mask |= tab_run_mask[i]; + } + for(idx = 0; idx < countof(case_conv_table1); idx++) { + v = case_conv_table1[idx]; + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if ((mask >> type) & 1) { + // printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1); + switch(type) { + case RUN_TYPE_UL: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + code += ((case_mask & CASE_U) != 0); + for(i = 0; i < len; i += 2) { + if (cr_add_interval(cr, code + i, code + i + 1)) + return -1; + } + break; + case RUN_TYPE_LSU: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + if (!(case_mask & CASE_U)) { + if (cr_add_interval(cr, code, code + 1)) + return -1; + } + if (cr_add_interval(cr, code + 1, code + 2)) + return -1; + if (case_mask & CASE_U) { + if (cr_add_interval(cr, code + 2, code + 3)) + return -1; + } + break; + default: + def_case: + if (cr_add_interval(cr, code, code + len)) + return -1; + break; + } + } + } + return 0; +} + +static int point_cmp(const void *p1, const void *p2, void *arg) +{ + uint32_t v1 = *(uint32_t *)p1; + uint32_t v2 = *(uint32_t *)p2; + return (v1 > v2) - (v1 < v2); +} + +static void cr_sort_and_remove_overlap(CharRange *cr) +{ + uint32_t start, end, start1, end1, i, j; + + /* the resulting ranges are not necessarily sorted and may overlap */ + rqsort(cr->points, cr->len / 2, sizeof(cr->points[0]) * 2, point_cmp, NULL); + j = 0; + for(i = 0; i < cr->len; ) { + start = cr->points[i]; + end = cr->points[i + 1]; + i += 2; + while (i < cr->len) { + start1 = cr->points[i]; + end1 = cr->points[i + 1]; + if (start1 > end) { + /* |------| + * |-------| */ + break; + } else if (end1 <= end) { + /* |------| + * |--| */ + i += 2; + } else { + /* |------| + * |-------| */ + end = end1; + i += 2; + } + } + cr->points[j] = start; + cr->points[j + 1] = end; + j += 2; + } + cr->len = j; +} + +/* canonicalize a character set using the JS regex case folding rules + (see lre_canonicalize()) */ +int cr_regexp_canonicalize(CharRange *cr, BOOL is_unicode) +{ + CharRange cr_inter, cr_mask, cr_result, cr_sub; + uint32_t v, code, len, i, idx, start, end, c, d_start, d_end, d; + + cr_init(&cr_mask, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_inter, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_result, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_sub, cr->mem_opaque, cr->realloc_func); + + if (unicode_case1(&cr_mask, is_unicode ? CASE_F : CASE_U)) + goto fail; + if (cr_op(&cr_inter, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + if (cr_invert(&cr_mask)) + goto fail; + if (cr_op(&cr_sub, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + /* cr_inter = cr & cr_mask */ + /* cr_sub = cr & ~cr_mask */ + + /* use the case conversion table to compute the result */ + d_start = -1; + d_end = -1; + idx = 0; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + for(i = 0; i < cr_inter.len; i += 2) { + start = cr_inter.points[i]; + end = cr_inter.points[i + 1]; + + for(c = start; c < end; c++) { + for(;;) { + if (c >= code && c < code + len) + break; + idx++; + assert(idx < countof(case_conv_table1)); + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + } + d = lre_case_folding_entry(c, idx, v, is_unicode); + /* try to merge with the current interval */ + if (d_start == -1) { + d_start = d; + d_end = d + 1; + } else if (d_end == d) { + d_end++; + } else { + cr_add_interval(&cr_result, d_start, d_end); + d_start = d; + d_end = d + 1; + } + } + } + if (d_start != -1) { + if (cr_add_interval(&cr_result, d_start, d_end)) + goto fail; + } + + /* the resulting ranges are not necessarily sorted and may overlap */ + cr_sort_and_remove_overlap(&cr_result); + + /* or with the character not affected by the case folding */ + cr->len = 0; + if (cr_op(cr, cr_result.points, cr_result.len, cr_sub.points, cr_sub.len, CR_OP_UNION)) + goto fail; + + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return 0; + fail: + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return -1; +} + +#ifdef CONFIG_ALL_UNICODE + +BOOL lre_is_id_start(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_ID_Start_table, + unicode_prop_ID_Start_index, + sizeof(unicode_prop_ID_Start_index) / 3); +} + +BOOL lre_is_id_continue(uint32_t c) +{ + return lre_is_id_start(c) || + lre_is_in_table(c, unicode_prop_ID_Continue1_table, + unicode_prop_ID_Continue1_index, + sizeof(unicode_prop_ID_Continue1_index) / 3); +} + +#define UNICODE_DECOMP_LEN_MAX 18 + +typedef enum { + DECOMP_TYPE_C1, /* 16 bit char */ + DECOMP_TYPE_L1, /* 16 bit char table */ + DECOMP_TYPE_L2, + DECOMP_TYPE_L3, + DECOMP_TYPE_L4, + DECOMP_TYPE_L5, /* XXX: not used */ + DECOMP_TYPE_L6, /* XXX: could remove */ + DECOMP_TYPE_L7, /* XXX: could remove */ + DECOMP_TYPE_LL1, /* 18 bit char table */ + DECOMP_TYPE_LL2, + DECOMP_TYPE_S1, /* 8 bit char table */ + DECOMP_TYPE_S2, + DECOMP_TYPE_S3, + DECOMP_TYPE_S4, + DECOMP_TYPE_S5, + DECOMP_TYPE_I1, /* increment 16 bit char value */ + DECOMP_TYPE_I2_0, + DECOMP_TYPE_I2_1, + DECOMP_TYPE_I3_1, + DECOMP_TYPE_I3_2, + DECOMP_TYPE_I4_1, + DECOMP_TYPE_I4_2, + DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */ + DECOMP_TYPE_B2, + DECOMP_TYPE_B3, + DECOMP_TYPE_B4, + DECOMP_TYPE_B5, + DECOMP_TYPE_B6, + DECOMP_TYPE_B7, + DECOMP_TYPE_B8, + DECOMP_TYPE_B18, + DECOMP_TYPE_LS2, + DECOMP_TYPE_PAT3, + DECOMP_TYPE_S2_UL, + DECOMP_TYPE_LS2_UL, +} DecompTypeEnum; + +static uint32_t unicode_get_short_code(uint32_t c) +{ + static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 }; + + if (c < 0x80) + return c; + else if (c < 0x80 + 0x50) + return c - 0x80 + 0x300; + else + return unicode_short_table[c - 0x80 - 0x50]; +} + +static uint32_t unicode_get_lower_simple(uint32_t c) +{ + if (c < 0x100 || (c >= 0x410 && c <= 0x42f)) + c += 0x20; + else + c++; + return c; +} + +static uint16_t unicode_get16(const uint8_t *p) +{ + return p[0] | (p[1] << 8); +} + +static int unicode_decomp_entry(uint32_t *res, uint32_t c, + int idx, uint32_t code, uint32_t len, + uint32_t type) +{ + uint32_t c1; + int l, i, p; + const uint8_t *d; + + if (type == DECOMP_TYPE_C1) { + res[0] = unicode_decomp_table2[idx]; + return 1; + } else { + d = unicode_decomp_data + unicode_decomp_table2[idx]; + switch(type) { + case DECOMP_TYPE_L1: + case DECOMP_TYPE_L2: + case DECOMP_TYPE_L3: + case DECOMP_TYPE_L4: + case DECOMP_TYPE_L5: + case DECOMP_TYPE_L6: + case DECOMP_TYPE_L7: + l = type - DECOMP_TYPE_L1 + 1; + d += (c - code) * l * 2; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get16(d + 2 * i)) == 0) + return 0; + } + return l; + case DECOMP_TYPE_LL1: + case DECOMP_TYPE_LL2: + { + uint32_t k, p; + l = type - DECOMP_TYPE_LL1 + 1; + k = (c - code) * l; + p = len * l * 2; + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * k) | + (((d[p + (k / 4)] >> ((k % 4) * 2)) & 3) << 16); + if (!c1) + return 0; + res[i] = c1; + k++; + } + } + return l; + case DECOMP_TYPE_S1: + case DECOMP_TYPE_S2: + case DECOMP_TYPE_S3: + case DECOMP_TYPE_S4: + case DECOMP_TYPE_S5: + l = type - DECOMP_TYPE_S1 + 1; + d += (c - code) * l; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get_short_code(d[i])) == 0) + return 0; + } + return l; + case DECOMP_TYPE_I1: + l = 1; + p = 0; + goto decomp_type_i; + case DECOMP_TYPE_I2_0: + case DECOMP_TYPE_I2_1: + case DECOMP_TYPE_I3_1: + case DECOMP_TYPE_I3_2: + case DECOMP_TYPE_I4_1: + case DECOMP_TYPE_I4_2: + l = 2 + ((type - DECOMP_TYPE_I2_0) >> 1); + p = ((type - DECOMP_TYPE_I2_0) & 1) + (l > 2); + decomp_type_i: + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * i); + if (i == p) + c1 += c - code; + res[i] = c1; + } + return l; + case DECOMP_TYPE_B18: + l = 18; + goto decomp_type_b; + case DECOMP_TYPE_B1: + case DECOMP_TYPE_B2: + case DECOMP_TYPE_B3: + case DECOMP_TYPE_B4: + case DECOMP_TYPE_B5: + case DECOMP_TYPE_B6: + case DECOMP_TYPE_B7: + case DECOMP_TYPE_B8: + l = type - DECOMP_TYPE_B1 + 1; + decomp_type_b: + { + uint32_t c_min; + c_min = unicode_get16(d); + d += 2 + (c - code) * l; + for(i = 0; i < l; i++) { + c1 = d[i]; + if (c1 == 0xff) + c1 = 0x20; + else + c1 += c_min; + res[i] = c1; + } + } + return l; + case DECOMP_TYPE_LS2: + d += (c - code) * 3; + if (!(res[0] = unicode_get16(d))) + return 0; + res[1] = unicode_get_short_code(d[2]); + return 2; + case DECOMP_TYPE_PAT3: + res[0] = unicode_get16(d); + res[2] = unicode_get16(d + 2); + d += 4 + (c - code) * 2; + res[1] = unicode_get16(d); + return 3; + case DECOMP_TYPE_S2_UL: + case DECOMP_TYPE_LS2_UL: + c1 = c - code; + if (type == DECOMP_TYPE_S2_UL) { + d += c1 & ~1; + c = unicode_get_short_code(*d); + d++; + } else { + d += (c1 >> 1) * 3; + c = unicode_get16(d); + d += 2; + } + if (c1 & 1) + c = unicode_get_lower_simple(c); + res[0] = c; + res[1] = unicode_get_short_code(*d); + return 2; + } + } + return 0; +} + + +/* return the length of the decomposition (length <= + UNICODE_DECOMP_LEN_MAX) or 0 if no decomposition */ +static int unicode_decomp_char(uint32_t *res, uint32_t c, BOOL is_compat1) +{ + uint32_t v, type, is_compat, code, len; + int idx_min, idx_max, idx; + + idx_min = 0; + idx_max = countof(unicode_decomp_table1) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + v = unicode_decomp_table1[idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + // printf("idx=%d code=%05x len=%d\n", idx, code, len); + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + is_compat = v & 1; + if (is_compat1 < is_compat) + break; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + return unicode_decomp_entry(res, c, idx, code, len, type); + } + } + return 0; +} + +/* return 0 if no pair found */ +static int unicode_compose_pair(uint32_t c0, uint32_t c1) +{ + uint32_t code, len, type, v, idx1, d_idx, d_offset, ch; + int idx_min, idx_max, idx, d; + uint32_t pair[2]; + + idx_min = 0; + idx_max = countof(unicode_comp_table) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + idx1 = unicode_comp_table[idx]; + + /* idx1 represent an entry of the decomposition table */ + d_idx = idx1 >> 6; + d_offset = idx1 & 0x3f; + v = unicode_decomp_table1[d_idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + ch = code + d_offset; + unicode_decomp_entry(pair, ch, d_idx, code, len, type); + d = c0 - pair[0]; + if (d == 0) + d = c1 - pair[1]; + if (d < 0) { + idx_max = idx - 1; + } else if (d > 0) { + idx_min = idx + 1; + } else { + return ch; + } + } + return 0; +} + +/* return the combining class of character c (between 0 and 255) */ +static int unicode_get_cc(uint32_t c) +{ + uint32_t code, n, type, cc, c1, b; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, + unicode_cc_index, sizeof(unicode_cc_index) / 3); + if (pos < 0) + return 0; + p = unicode_cc_table + pos; + /* Compressed run length encoding: + - 2 high order bits are combining class type + - 0:0, 1:230, 2:extra byte linear progression, 3:extra byte + - 00..2F: range length (add 1) + - 30..37: 3-bit range-length + 1 extra byte + - 38..3F: 3-bit range-length + 2 extra byte + */ + for(;;) { + b = *p++; + type = b >> 6; + n = b & 0x3f; + if (n < 48) { + } else if (n < 56) { + n = (n - 48) << 8; + n |= *p++; + n += 48; + } else { + n = (n - 56) << 8; + n |= *p++ << 8; + n |= *p++; + n += 48 + (1 << 11); + } + if (type <= 1) + p++; + c1 = code + n + 1; + if (c < c1) { + switch(type) { + case 0: + cc = p[-1]; + break; + case 1: + cc = p[-1] + c - code; + break; + case 2: + cc = 0; + break; + default: + case 3: + cc = 230; + break; + } + return cc; + } + code = c1; + } +} + +static void sort_cc(int *buf, int len) +{ + int i, j, k, cc, cc1, start, ch1; + + for(i = 0; i < len; i++) { + cc = unicode_get_cc(buf[i]); + if (cc != 0) { + start = i; + j = i + 1; + while (j < len) { + ch1 = buf[j]; + cc1 = unicode_get_cc(ch1); + if (cc1 == 0) + break; + k = j - 1; + while (k >= start) { + if (unicode_get_cc(buf[k]) <= cc1) + break; + buf[k + 1] = buf[k]; + k--; + } + buf[k + 1] = ch1; + j++; + } +#if 0 + printf("cc:"); + for(k = start; k < j; k++) { + printf(" %3d", unicode_get_cc(buf[k])); + } + printf("\n"); +#endif + i = j; + } + } +} + +static void to_nfd_rec(DynBuf *dbuf, + const int *src, int src_len, int is_compat) +{ + uint32_t c, v; + int i, l; + uint32_t res[UNICODE_DECOMP_LEN_MAX]; + + for(i = 0; i < src_len; i++) { + c = src[i]; + if (c >= 0xac00 && c < 0xd7a4) { + /* Hangul decomposition */ + c -= 0xac00; + dbuf_put_u32(dbuf, 0x1100 + c / 588); + dbuf_put_u32(dbuf, 0x1161 + (c % 588) / 28); + v = c % 28; + if (v != 0) + dbuf_put_u32(dbuf, 0x11a7 + v); + } else { + l = unicode_decomp_char(res, c, is_compat); + if (l) { + to_nfd_rec(dbuf, (int *)res, l, is_compat); + } else { + dbuf_put_u32(dbuf, c); + } + } + } +} + +/* return 0 if not found */ +static int compose_pair(uint32_t c0, uint32_t c1) +{ + /* Hangul composition */ + if (c0 >= 0x1100 && c0 < 0x1100 + 19 && + c1 >= 0x1161 && c1 < 0x1161 + 21) { + return 0xac00 + (c0 - 0x1100) * 588 + (c1 - 0x1161) * 28; + } else if (c0 >= 0xac00 && c0 < 0xac00 + 11172 && + (c0 - 0xac00) % 28 == 0 && + c1 >= 0x11a7 && c1 < 0x11a7 + 28) { + return c0 + c1 - 0x11a7; + } else { + return unicode_compose_pair(c0, c1); + } +} + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, DynBufReallocFunc *realloc_func) +{ + int *buf, buf_len, i, p, starter_pos, cc, last_cc, out_len; + BOOL is_compat; + DynBuf dbuf_s, *dbuf = &dbuf_s; + + is_compat = n_type >> 1; + + dbuf_init2(dbuf, opaque, realloc_func); + if (dbuf_claim(dbuf, sizeof(int) * src_len)) + goto fail; + + /* common case: latin1 is unaffected by NFC */ + if (n_type == UNICODE_NFC) { + for(i = 0; i < src_len; i++) { + if (src[i] >= 0x100) + goto not_latin1; + } + buf = (int *)dbuf->buf; + memcpy(buf, src, src_len * sizeof(int)); + *pdst = (uint32_t *)buf; + return src_len; + not_latin1: ; + } + + to_nfd_rec(dbuf, (const int *)src, src_len, is_compat); + if (dbuf_error(dbuf)) { + fail: + *pdst = NULL; + return -1; + } + buf = (int *)dbuf->buf; + buf_len = dbuf->size / sizeof(int); + + sort_cc(buf, buf_len); + + if (buf_len <= 1 || (n_type & 1) != 0) { + /* NFD / NFKD */ + *pdst = (uint32_t *)buf; + return buf_len; + } + + i = 1; + out_len = 1; + while (i < buf_len) { + /* find the starter character and test if it is blocked from + the character at 'i' */ + last_cc = unicode_get_cc(buf[i]); + starter_pos = out_len - 1; + while (starter_pos >= 0) { + cc = unicode_get_cc(buf[starter_pos]); + if (cc == 0) + break; + if (cc >= last_cc) + goto next; + last_cc = 256; + starter_pos--; + } + if (starter_pos >= 0 && + (p = compose_pair(buf[starter_pos], buf[i])) != 0) { + buf[starter_pos] = p; + i++; + } else { + next: + buf[out_len++] = buf[i++]; + } + } + *pdst = (uint32_t *)buf; + return out_len; +} + +/* char ranges for various unicode properties */ + +static int unicode_find_name(const char *name_table, const char *name) +{ + const char *p, *r; + int pos; + size_t name_len, len; + + p = name_table; + pos = 0; + name_len = strlen(name); + while (*p) { + for(;;) { + r = strchr(p, ','); + if (!r) + len = strlen(p); + else + len = r - p; + if (len == name_len && !memcmp(p, name, name_len)) + return pos; + p += len + 1; + if (!r) + break; + } + pos++; + } + return -1; +} + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_script(CharRange *cr, + const char *script_name, BOOL is_ext) +{ + int script_idx; + const uint8_t *p, *p_end; + uint32_t c, c1, b, n, v, v_len, i, type; + CharRange cr1_s, *cr1; + CharRange cr2_s, *cr2 = &cr2_s; + BOOL is_common; + + script_idx = unicode_find_name(unicode_script_name_table, script_name); + if (script_idx < 0) + return -2; + + is_common = (script_idx == UNICODE_SCRIPT_Common || + script_idx == UNICODE_SCRIPT_Inherited); + if (is_ext) { + cr1 = &cr1_s; + cr_init(cr1, cr->mem_opaque, cr->realloc_func); + cr_init(cr2, cr->mem_opaque, cr->realloc_func); + } else { + cr1 = cr; + } + + p = unicode_script_table; + p_end = unicode_script_table + countof(unicode_script_table); + c = 0; + while (p < p_end) { + b = *p++; + type = b >> 7; + n = b & 0x7f; + if (n < 96) { + } else if (n < 112) { + n = (n - 96) << 8; + n |= *p++; + n += 96; + } else { + n = (n - 112) << 16; + n |= *p++ << 8; + n |= *p++; + n += 96 + (1 << 12); + } + c1 = c + n + 1; + if (type != 0) { + v = *p++; + if (v == script_idx || script_idx == UNICODE_SCRIPT_Unknown) { + if (cr_add_interval(cr1, c, c1)) + goto fail; + } + } + c = c1; + } + if (script_idx == UNICODE_SCRIPT_Unknown) { + /* Unknown is all the characters outside scripts */ + if (cr_invert(cr1)) + goto fail; + } + + if (is_ext) { + /* add the script extensions */ + p = unicode_script_ext_table; + p_end = unicode_script_ext_table + countof(unicode_script_ext_table); + c = 0; + while (p < p_end) { + b = *p++; + if (b < 128) { + n = b; + } else if (b < 128 + 64) { + n = (b - 128) << 8; + n |= *p++; + n += 128; + } else { + n = (b - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 128 + (1 << 14); + } + c1 = c + n + 1; + v_len = *p++; + if (is_common) { + if (v_len != 0) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + } + } else { + for(i = 0; i < v_len; i++) { + if (p[i] == script_idx) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + break; + } + } + } + p += v_len; + c = c1; + } + if (is_common) { + /* remove all the characters with script extensions */ + if (cr_invert(cr2)) + goto fail; + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_INTER)) + goto fail; + } else { + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_UNION)) + goto fail; + } + cr_free(cr1); + cr_free(cr2); + } + return 0; + fail: + if (is_ext) { + cr_free(cr1); + cr_free(cr2); + } + goto fail; +} + +#define M(id) (1U << UNICODE_GC_ ## id) + +static int unicode_general_category1(CharRange *cr, uint32_t gc_mask) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, n, v; + + p = unicode_gc_table; + p_end = unicode_gc_table + countof(unicode_gc_table); + c = 0; + /* Compressed range encoding: + initial byte: + bits 0..4: category number (special case 31) + bits 5..7: range length (add 1) + special case bits 5..7 == 7: read an extra byte + - 00..7F: range length (add 7 + 1) + - 80..BF: 6-bits plus extra byte for range length (add 7 + 128) + - C0..FF: 6-bits plus 2 extra bytes for range length (add 7 + 128 + 16384) + */ + while (p < p_end) { + b = *p++; + n = b >> 5; + v = b & 0x1f; + if (n == 7) { + n = *p++; + if (n < 128) { + n += 7; + } else if (n < 128 + 64) { + n = (n - 128) << 8; + n |= *p++; + n += 7 + 128; + } else { + n = (n - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 7 + 128 + (1 << 14); + } + } + c0 = c; + c += n + 1; + if (v == 31) { + /* run of Lu / Ll */ + b = gc_mask & (M(Lu) | M(Ll)); + if (b != 0) { + if (b == (M(Lu) | M(Ll))) { + goto add_range; + } else { + c0 += ((gc_mask & M(Ll)) != 0); + for(; c0 < c; c0 += 2) { + if (cr_add_interval(cr, c0, c0 + 1)) + return -1; + } + } + } + } else if ((gc_mask >> v) & 1) { + add_range: + if (cr_add_interval(cr, c0, c)) + return -1; + } + } + return 0; +} + +static int unicode_prop1(CharRange *cr, int prop_idx) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, bit; + + p = unicode_prop_table[prop_idx]; + p_end = p + unicode_prop_len_table[prop_idx]; + c = 0; + bit = 0; + /* Compressed range encoding: + 00..3F: 2 packed lengths: 3-bit + 3-bit + 40..5F: 5-bits plus extra byte for length + 60..7F: 5-bits plus 2 extra bytes for length + 80..FF: 7-bit length + lengths must be incremented to get character count + Ranges alternate between false and true return value. + */ + while (p < p_end) { + c0 = c; + b = *p++; + if (b < 64) { + c += (b >> 3) + 1; + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + c0 = c; + c += (b & 7) + 1; + } else if (b >= 0x80) { + c += b - 0x80 + 1; + } else if (b < 0x60) { + c += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + c += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + } + return 0; +} + +typedef enum { + POP_GC, + POP_PROP, + POP_CASE, + POP_UNION, + POP_INTER, + POP_XOR, + POP_INVERT, + POP_END, +} PropOPEnum; + +#define POP_STACK_LEN_MAX 4 + +static int unicode_prop_ops(CharRange *cr, ...) +{ + va_list ap; + CharRange stack[POP_STACK_LEN_MAX]; + int stack_len, op, ret, i; + uint32_t a; + + va_start(ap, cr); + stack_len = 0; + for(;;) { + op = va_arg(ap, int); + switch(op) { + case POP_GC: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_general_category1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_PROP: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_prop1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_CASE: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_case1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_UNION: + case POP_INTER: + case POP_XOR: + { + CharRange *cr1, *cr2, *cr3; + assert(stack_len >= 2); + assert(stack_len < POP_STACK_LEN_MAX); + cr1 = &stack[stack_len - 2]; + cr2 = &stack[stack_len - 1]; + cr3 = &stack[stack_len++]; + cr_init(cr3, cr->mem_opaque, cr->realloc_func); + /* CR_OP_XOR may be used here */ + if (cr_op(cr3, cr1->points, cr1->len, + cr2->points, cr2->len, op - POP_UNION + CR_OP_UNION)) + goto fail; + cr_free(cr1); + cr_free(cr2); + *cr1 = *cr3; + stack_len -= 2; + } + break; + case POP_INVERT: + assert(stack_len >= 1); + if (cr_invert(&stack[stack_len - 1])) + goto fail; + break; + case POP_END: + goto done; + default: + abort(); + } + } + done: + assert(stack_len == 1); + ret = cr_copy(cr, &stack[0]); + cr_free(&stack[0]); + return ret; + fail: + for(i = 0; i < stack_len; i++) + cr_free(&stack[i]); + return -1; +} + +static const uint32_t unicode_gc_mask_table[] = { + M(Lu) | M(Ll) | M(Lt), /* LC */ + M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo), /* L */ + M(Mn) | M(Mc) | M(Me), /* M */ + M(Nd) | M(Nl) | M(No), /* N */ + M(Sm) | M(Sc) | M(Sk) | M(So), /* S */ + M(Pc) | M(Pd) | M(Ps) | M(Pe) | M(Pi) | M(Pf) | M(Po), /* P */ + M(Zs) | M(Zl) | M(Zp), /* Z */ + M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn), /* C */ +}; + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_general_category(CharRange *cr, const char *gc_name) +{ + int gc_idx; + uint32_t gc_mask; + + gc_idx = unicode_find_name(unicode_gc_name_table, gc_name); + if (gc_idx < 0) + return -2; + if (gc_idx <= UNICODE_GC_Co) { + gc_mask = (uint64_t)1 << gc_idx; + } else { + gc_mask = unicode_gc_mask_table[gc_idx - UNICODE_GC_LC]; + } + return unicode_general_category1(cr, gc_mask); +} + + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_prop(CharRange *cr, const char *prop_name) +{ + int prop_idx, ret; + + prop_idx = unicode_find_name(unicode_prop_name_table, prop_name); + if (prop_idx < 0) + return -2; + prop_idx += UNICODE_PROP_ASCII_Hex_Digit; + + ret = 0; + switch(prop_idx) { + case UNICODE_PROP_ASCII: + if (cr_add_interval(cr, 0x00, 0x7f + 1)) + return -1; + break; + case UNICODE_PROP_Any: + if (cr_add_interval(cr, 0x00000, 0x10ffff + 1)) + return -1; + break; + case UNICODE_PROP_Assigned: + ret = unicode_prop_ops(cr, + POP_GC, M(Cn), + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Math: + ret = unicode_prop_ops(cr, + POP_GC, M(Sm), + POP_PROP, UNICODE_PROP_Other_Math, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Lowercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Ll), + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Uppercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Cased: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Alphabetic: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Alphabetic, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Grapheme_Base: + ret = unicode_prop_ops(cr, + POP_GC, M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn) | M(Zl) | M(Zp) | M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Grapheme_Extend: + ret = unicode_prop_ops(cr, + POP_GC, M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_XID_Start: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Start1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_XID_Continue: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) | + M(Mn) | M(Mc) | M(Nd) | M(Pc), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_ID_Continue, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Continue1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_Changes_When_Uppercased: + ret = unicode_case1(cr, CASE_U); + break; + case UNICODE_PROP_Changes_When_Lowercased: + ret = unicode_case1(cr, CASE_L); + break; + case UNICODE_PROP_Changes_When_Casemapped: + ret = unicode_case1(cr, CASE_U | CASE_L | CASE_F); + break; + case UNICODE_PROP_Changes_When_Titlecased: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_U, + POP_PROP, UNICODE_PROP_Changes_When_Titlecased1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_Casefolded1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_NFKC_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_NFKC_Casefolded1, + POP_XOR, + POP_END); + break; +#if 0 + case UNICODE_PROP_ID_Start: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_ID_Continue: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) | + M(Mn) | M(Mc) | M(Nd) | M(Pc), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_ID_Continue, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_Case_Ignorable: + ret = unicode_prop_ops(cr, + POP_GC, M(Mn) | M(Cf) | M(Lm) | M(Sk), + POP_PROP, UNICODE_PROP_Case_Ignorable1, + POP_XOR, + POP_END); + break; +#else + /* we use the existing tables */ + case UNICODE_PROP_ID_Continue: + ret = unicode_prop_ops(cr, + POP_PROP, UNICODE_PROP_ID_Start, + POP_PROP, UNICODE_PROP_ID_Continue1, + POP_XOR, + POP_END); + break; +#endif + default: + if (prop_idx >= countof(unicode_prop_table)) + return -2; + ret = unicode_prop1(cr, prop_idx); + break; + } + return ret; +} + +#endif /* CONFIG_ALL_UNICODE */ + +/*---- lre codepoint categorizing functions ----*/ + +#define S UNICODE_C_SPACE +#define D UNICODE_C_DIGIT +#define X UNICODE_C_XDIGIT +#define U UNICODE_C_UPPER +#define L UNICODE_C_LOWER +#define _ UNICODE_C_UNDER +#define d UNICODE_C_DOLLAR + +uint8_t const lre_ctype_bits[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, S, S, S, S, S, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + S, 0, 0, 0, d, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + X|D, X|D, X|D, X|D, X|D, X|D, X|D, X|D, + X|D, X|D, 0, 0, 0, 0, 0, 0, + + 0, X|U, X|U, X|U, X|U, X|U, X|U, U, + U, U, U, U, U, U, U, U, + U, U, U, U, U, U, U, U, + U, U, U, 0, 0, 0, 0, _, + + 0, X|L, X|L, X|L, X|L, X|L, X|L, L, + L, L, L, L, L, L, L, L, + L, L, L, L, L, L, L, L, + L, L, L, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + S, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +#undef S +#undef D +#undef X +#undef U +#undef L +#undef _ +#undef d + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_s[] = { + 10, + 0x0009, 0x000D + 1, + 0x0020, 0x0020 + 1, + 0x00A0, 0x00A0 + 1, + 0x1680, 0x1680 + 1, + 0x2000, 0x200A + 1, + /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ + /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ + 0x2028, 0x2029 + 1, + 0x202F, 0x202F + 1, + 0x205F, 0x205F + 1, + 0x3000, 0x3000 + 1, + /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ + 0xFEFF, 0xFEFF + 1, +}; + +BOOL lre_is_space_non_ascii(uint32_t c) +{ + size_t i, n; + + n = countof(char_range_s); + for(i = 5; i < n; i += 2) { + uint32_t low = char_range_s[i]; + uint32_t high = char_range_s[i + 1]; + if (c < low) + return FALSE; + if (c < high) + return TRUE; + } + return FALSE; +} + +#define SEQ_MAX_LEN 16 + +static int unicode_sequence_prop1(int seq_prop_idx, UnicodeSequencePropCB *cb, void *opaque, + CharRange *cr) +{ + int i, c, j; + uint32_t seq[SEQ_MAX_LEN]; + + switch(seq_prop_idx) { + case UNICODE_SEQUENCE_PROP_Basic_Emoji: + if (unicode_prop1(cr, UNICODE_PROP_Basic_Emoji1) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + seq[0] = c; + cb(opaque, seq, 1); + } + } + + cr->len = 0; + + if (unicode_prop1(cr, UNICODE_PROP_Basic_Emoji2) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + seq[0] = c; + seq[1] = 0xfe0f; + cb(opaque, seq, 2); + } + } + + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_Modifier_Sequence: + if (unicode_prop1(cr, UNICODE_PROP_Emoji_Modifier_Base) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + for(j = 0; j < 5; j++) { + seq[0] = c; + seq[1] = 0x1f3fb + j; + cb(opaque, seq, 2); + } + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_Flag_Sequence: + if (unicode_prop1(cr, UNICODE_PROP_RGI_Emoji_Flag_Sequence) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + int c0, c1; + c0 = c / 26; + c1 = c % 26; + seq[0] = 0x1F1E6 + c0; + seq[1] = 0x1F1E6 + c1; + cb(opaque, seq, 2); + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence: + { + int len, code, pres, k, mod, mod_count, mod_pos[2], hc_pos, n_mod, n_hc, mod1; + int mod_idx, hc_idx, i0, i1; + const uint8_t *tab = unicode_rgi_emoji_zwj_sequence; + + for(i = 0; i < countof(unicode_rgi_emoji_zwj_sequence);) { + len = tab[i++]; + k = 0; + mod = 0; + mod_count = 0; + hc_pos = -1; + for(j = 0; j < len; j++) { + code = tab[i++]; + code |= tab[i++] << 8; + pres = code >> 15; + mod1 = (code >> 13) & 3; + code &= 0x1fff; + if (code < 0x1000) { + c = code + 0x2000; + } else { + c = 0x1f000 + (code - 0x1000); + } + if (c == 0x1f9b0) + hc_pos = k; + seq[k++] = c; + if (mod1 != 0) { + assert(mod_count < 2); + mod = mod1; + mod_pos[mod_count++] = k; + seq[k++] = 0; /* will be filled later */ + } + if (pres) { + seq[k++] = 0xfe0f; + } + if (j < len - 1) { + seq[k++] = 0x200d; + } + } + + /* genrate all the variants */ + switch(mod) { + case 1: + n_mod = 5; + break; + case 2: + n_mod = 25; + break; + case 3: + n_mod = 20; + break; + default: + n_mod = 1; + break; + } + if (hc_pos >= 0) + n_hc = 4; + else + n_hc = 1; + for(hc_idx = 0; hc_idx < n_hc; hc_idx++) { + for(mod_idx = 0; mod_idx < n_mod; mod_idx++) { + if (hc_pos >= 0) + seq[hc_pos] = 0x1f9b0 + hc_idx; + + switch(mod) { + case 1: + seq[mod_pos[0]] = 0x1f3fb + mod_idx; + break; + case 2: + case 3: + i0 = mod_idx / 5; + i1 = mod_idx % 5; + /* avoid identical values */ + if (mod == 3 && i0 >= i1) + i0++; + seq[mod_pos[0]] = 0x1f3fb + i0; + seq[mod_pos[1]] = 0x1f3fb + i1; + break; + default: + break; + } +#if 0 + for(j = 0; j < k; j++) + printf(" %04x", seq[j]); + printf("\n"); +#endif + cb(opaque, seq, k); + } + } + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji_Tag_Sequence: + { + for(i = 0; i < countof(unicode_rgi_emoji_tag_sequence);) { + j = 0; + seq[j++] = 0x1F3F4; + for(;;) { + c = unicode_rgi_emoji_tag_sequence[i++]; + if (c == 0x00) + break; + seq[j++] = 0xe0000 + c; + } + seq[j++] = 0xe007f; + cb(opaque, seq, j); + } + } + break; + case UNICODE_SEQUENCE_PROP_Emoji_Keycap_Sequence: + if (unicode_prop1(cr, UNICODE_PROP_Emoji_Keycap_Sequence) < 0) + return -1; + for(i = 0; i < cr->len; i += 2) { + for(c = cr->points[i]; c < cr->points[i + 1]; c++) { + seq[0] = c; + seq[1] = 0xfe0f; + seq[2] = 0x20e3; + cb(opaque, seq, 3); + } + } + break; + case UNICODE_SEQUENCE_PROP_RGI_Emoji: + /* all prevous sequences */ + for(i = UNICODE_SEQUENCE_PROP_Basic_Emoji; i <= UNICODE_SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence; i++) { + int ret; + ret = unicode_sequence_prop1(i, cb, opaque, cr); + if (ret < 0) + return ret; + cr->len = 0; + } + break; + default: + return -2; + } + return 0; +} + +/* build a unicode sequence property */ +/* return -2 if not found, -1 if other error. 'cr' is used as temporary memory. */ +int unicode_sequence_prop(const char *prop_name, UnicodeSequencePropCB *cb, void *opaque, + CharRange *cr) +{ + int seq_prop_idx; + seq_prop_idx = unicode_find_name(unicode_sequence_prop_name_table, prop_name); + if (seq_prop_idx < 0) + return -2; + return unicode_sequence_prop1(seq_prop_idx, cb, opaque, cr); +} diff --git a/NativeScript/napi/android/quickjs/source/libunicode.h b/NativeScript/napi/android/quickjs/source/libunicode.h new file mode 100755 index 000000000..5d964e40f --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/libunicode.h @@ -0,0 +1,186 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LIBUNICODE_H +#define LIBUNICODE_H + +#include + +/* define it to include all the unicode tables (40KB larger) */ +#define CONFIG_ALL_UNICODE + +#define LRE_CC_RES_LEN_MAX 3 + +/* char ranges */ + +typedef struct { + int len; /* in points, always even */ + int size; + uint32_t *points; /* points sorted by increasing value */ + void *mem_opaque; + void *(*realloc_func)(void *opaque, void *ptr, size_t size); +} CharRange; + +typedef enum { + CR_OP_UNION, + CR_OP_INTER, + CR_OP_XOR, + CR_OP_SUB, +} CharRangeOpEnum; + +void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); +void cr_free(CharRange *cr); +int cr_realloc(CharRange *cr, int size); +int cr_copy(CharRange *cr, const CharRange *cr1); + +static inline int cr_add_point(CharRange *cr, uint32_t v) +{ + if (cr->len >= cr->size) { + if (cr_realloc(cr, cr->len + 1)) + return -1; + } + cr->points[cr->len++] = v; + return 0; +} + +static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + if ((cr->len + 2) > cr->size) { + if (cr_realloc(cr, cr->len + 2)) + return -1; + } + cr->points[cr->len++] = c1; + cr->points[cr->len++] = c2; + return 0; +} + +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op); +int cr_op1(CharRange *cr, const uint32_t *b_pt, int b_len, int op); + +static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + uint32_t b_pt[2]; + b_pt[0] = c1; + b_pt[1] = c2 + 1; + return cr_op1(cr, b_pt, 2, CR_OP_UNION); +} + +int cr_invert(CharRange *cr); + +int cr_regexp_canonicalize(CharRange *cr, int is_unicode); + +typedef enum { + UNICODE_NFC, + UNICODE_NFD, + UNICODE_NFKC, + UNICODE_NFKD, +} UnicodeNormalizationEnum; + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); + +/* Unicode character range functions */ + +int unicode_script(CharRange *cr, const char *script_name, int is_ext); +int unicode_general_category(CharRange *cr, const char *gc_name); +int unicode_prop(CharRange *cr, const char *prop_name); + +typedef void UnicodeSequencePropCB(void *opaque, const uint32_t *buf, int len); +int unicode_sequence_prop(const char *prop_name, UnicodeSequencePropCB *cb, void *opaque, + CharRange *cr); + +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); +int lre_canonicalize(uint32_t c, int is_unicode); + +/* Code point type categories */ +enum { + UNICODE_C_SPACE = (1 << 0), + UNICODE_C_DIGIT = (1 << 1), + UNICODE_C_UPPER = (1 << 2), + UNICODE_C_LOWER = (1 << 3), + UNICODE_C_UNDER = (1 << 4), + UNICODE_C_DOLLAR = (1 << 5), + UNICODE_C_XDIGIT = (1 << 6), +}; +extern uint8_t const lre_ctype_bits[256]; + +/* zero or non-zero return value */ +int lre_is_cased(uint32_t c); +int lre_is_case_ignorable(uint32_t c); +int lre_is_id_start(uint32_t c); +int lre_is_id_continue(uint32_t c); + +static inline int lre_is_space_byte(uint8_t c) { + return lre_ctype_bits[c] & UNICODE_C_SPACE; +} + +static inline int lre_is_id_start_byte(uint8_t c) { + return lre_ctype_bits[c] & (UNICODE_C_UPPER | UNICODE_C_LOWER | + UNICODE_C_UNDER | UNICODE_C_DOLLAR); +} + +static inline int lre_is_id_continue_byte(uint8_t c) { + return lre_ctype_bits[c] & (UNICODE_C_UPPER | UNICODE_C_LOWER | + UNICODE_C_UNDER | UNICODE_C_DOLLAR | + UNICODE_C_DIGIT); +} + +int lre_is_space_non_ascii(uint32_t c); + +static inline int lre_is_space(uint32_t c) { + if (c < 256) + return lre_is_space_byte(c); + else + return lre_is_space_non_ascii(c); +} + +static inline int lre_js_is_ident_first(uint32_t c) { + if (c < 128) { + return lre_is_id_start_byte(c); + } else { +#ifdef CONFIG_ALL_UNICODE + return lre_is_id_start(c); +#else + return !lre_is_space_non_ascii(c); +#endif + } +} + +static inline int lre_js_is_ident_next(uint32_t c) { + if (c < 128) { + return lre_is_id_continue_byte(c); + } else { + /* ZWNJ and ZWJ are accepted in identifiers */ + if (c >= 0x200C && c <= 0x200D) + return TRUE; +#ifdef CONFIG_ALL_UNICODE + return lre_is_id_continue(c); +#else + return !lre_is_space_non_ascii(c); +#endif + } +} + +#endif /* LIBUNICODE_H */ diff --git a/NativeScript/napi/android/quickjs/source/list.h b/NativeScript/napi/android/quickjs/source/list.h new file mode 100755 index 000000000..809831115 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/list.h @@ -0,0 +1,99 @@ +/* + * Linux klist like system + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LIST_H +#define LIST_H + +#ifndef NULL +#include +#endif + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +#define LIST_HEAD_INIT(el) { &(el), &(el) } + +/* return the pointer of type 'type *' containing 'el' as field 'member' */ +#define list_entry(el, type, member) container_of(el, type, member) + +static inline void init_list_head(struct list_head *head) +{ + head->prev = head; + head->next = head; +} + +/* insert 'el' between 'prev' and 'next' */ +static inline void __list_add(struct list_head *el, + struct list_head *prev, struct list_head *next) +{ + prev->next = el; + el->prev = prev; + el->next = next; + next->prev = el; +} + +/* add 'el' at the head of the list 'head' (= after element head) */ +static inline void list_add(struct list_head *el, struct list_head *head) +{ + __list_add(el, head, head->next); +} + +/* add 'el' at the end of the list 'head' (= before element head) */ +static inline void list_add_tail(struct list_head *el, struct list_head *head) +{ + __list_add(el, head->prev, head); +} + +static inline void list_del(struct list_head *el) +{ + struct list_head *prev, *next; + prev = el->prev; + next = el->next; + prev->next = next; + next->prev = prev; + el->prev = NULL; /* fail safe */ + el->next = NULL; /* fail safe */ +} + +static inline int list_empty(struct list_head *el) +{ + return el->next == el; +} + +#define list_for_each(el, head) \ + for(el = (head)->next; el != (head); el = el->next) + +#define list_for_each_safe(el, el1, head) \ + for(el = (head)->next, el1 = el->next; el != (head); \ + el = el1, el1 = el->next) + +#define list_for_each_prev(el, head) \ + for(el = (head)->prev; el != (head); el = el->prev) + +#define list_for_each_prev_safe(el, el1, head) \ + for(el = (head)->prev, el1 = el->prev; el != (head); \ + el = el1, el1 = el->prev) + +#endif /* LIST_H */ diff --git a/NativeScript/napi/android/quickjs/source/qjs.c b/NativeScript/napi/android/quickjs/source/qjs.c new file mode 100755 index 000000000..0224f7cfa --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/qjs.c @@ -0,0 +1,568 @@ +/* + * QuickJS stand alone interpreter + * + * Copyright (c) 2017-2021 Fabrice Bellard + * Copyright (c) 2017-2021 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) +#include +#elif defined(__linux__) || defined(__GLIBC__) +#include +#elif defined(__FreeBSD__) +#include +#endif + +#include "cutils.h" +#include "quickjs-libc.h" + +extern const uint8_t qjsc_repl[]; +extern const uint32_t qjsc_repl_size; + +static int eval_buf(JSContext *ctx, const void *buf, int buf_len, + const char *filename, int eval_flags) +{ + JSValue val; + int ret; + + if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) { + /* for the modules, we compile then run to be able to set + import.meta */ + val = JS_Eval(ctx, buf, buf_len, filename, + eval_flags | JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(val)) { + js_module_set_import_meta(ctx, val, TRUE, TRUE); + val = JS_EvalFunction(ctx, val); + } + val = js_std_await(ctx, val); + } else { + val = JS_Eval(ctx, buf, buf_len, filename, eval_flags); + } + if (JS_IsException(val)) { + js_std_dump_error(ctx); + ret = -1; + } else { + ret = 0; + } + JS_FreeValue(ctx, val); + return ret; +} + +static int eval_file(JSContext *ctx, const char *filename, int module, int strict) +{ + uint8_t *buf; + int ret, eval_flags; + size_t buf_len; + + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + perror(filename); + exit(1); + } + + if (module < 0) { + module = (has_suffix(filename, ".mjs") || + JS_DetectModule((const char *)buf, buf_len)); + } + if (module) { + eval_flags = JS_EVAL_TYPE_MODULE; + } else { + eval_flags = JS_EVAL_TYPE_GLOBAL; + if (strict) + eval_flags |= JS_EVAL_FLAG_STRICT; + } + ret = eval_buf(ctx, buf, buf_len, filename, eval_flags); + js_free(ctx, buf); + return ret; +} + +/* also used to initialize the worker context */ +static JSContext *JS_NewCustomContext(JSRuntime *rt) +{ + JSContext *ctx; + ctx = JS_NewContext(rt); + if (!ctx) + return NULL; + /* system modules */ + js_init_module_std(ctx, "std"); + js_init_module_os(ctx, "os"); + return ctx; +} + +#if defined(__APPLE__) +#define MALLOC_OVERHEAD 0 +#else +#define MALLOC_OVERHEAD 8 +#endif + +struct trace_malloc_data { + uint8_t *base; +}; + +static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr, + struct trace_malloc_data *dp) +{ + return ptr - dp->base; +} + +/* default memory allocation functions with memory limitation */ +static size_t js_trace_malloc_usable_size(const void *ptr) +{ +#if defined(__APPLE__) + return malloc_size(ptr); +#elif defined(_WIN32) + return _msize((void *)ptr); +#elif defined(EMSCRIPTEN) + return 0; +#elif defined(__linux__) || defined(__GLIBC__) + return malloc_usable_size((void *)ptr); +#else + /* change this to `return 0;` if compilation fails */ + return malloc_usable_size((void *)ptr); +#endif +} + +static void +#ifdef _WIN32 +/* mingw printf is used */ +__attribute__((format(gnu_printf, 2, 3))) +#else +__attribute__((format(printf, 2, 3))) +#endif + js_trace_malloc_printf(JSMallocState *s, const char *fmt, ...) +{ + va_list ap; + int c; + + va_start(ap, fmt); + while ((c = *fmt++) != '\0') { + if (c == '%') { + /* only handle %p and %zd */ + if (*fmt == 'p') { + uint8_t *ptr = va_arg(ap, void *); + if (ptr == NULL) { + printf("NULL"); + } else { + printf("H%+06lld.%zd", + js_trace_malloc_ptr_offset(ptr, s->opaque), + js_trace_malloc_usable_size(ptr)); + } + fmt++; + continue; + } + if (fmt[0] == 'z' && fmt[1] == 'd') { + size_t sz = va_arg(ap, size_t); + printf("%zd", sz); + fmt += 2; + continue; + } + } + putc(c, stdout); + } + va_end(ap); +} + +static void js_trace_malloc_init(struct trace_malloc_data *s) +{ + free(s->base = malloc(8)); +} + +static void *js_trace_malloc(JSMallocState *s, size_t size) +{ + void *ptr; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(size != 0); + + if (unlikely(s->malloc_size + size > s->malloc_limit)) + return NULL; + ptr = malloc(size); + js_trace_malloc_printf(s, "A %zd -> %p\n", size, ptr); + if (ptr) { + s->malloc_count++; + s->malloc_size += js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + } + return ptr; +} + +static void js_trace_free(JSMallocState *s, void *ptr) +{ + if (!ptr) + return; + + js_trace_malloc_printf(s, "F %p\n", ptr); + s->malloc_count--; + s->malloc_size -= js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + free(ptr); +} + +static void *js_trace_realloc(JSMallocState *s, void *ptr, size_t size) +{ + size_t old_size; + + if (!ptr) { + if (size == 0) + return NULL; + return js_trace_malloc(s, size); + } + old_size = js_trace_malloc_usable_size(ptr); + if (size == 0) { + js_trace_malloc_printf(s, "R %zd %p\n", size, ptr); + s->malloc_count--; + s->malloc_size -= old_size + MALLOC_OVERHEAD; + free(ptr); + return NULL; + } + if (s->malloc_size + size - old_size > s->malloc_limit) + return NULL; + + js_trace_malloc_printf(s, "R %zd %p", size, ptr); + + ptr = realloc(ptr, size); + js_trace_malloc_printf(s, " -> %p\n", ptr); + if (ptr) { + s->malloc_size += js_trace_malloc_usable_size(ptr) - old_size; + } + return ptr; +} + +static const JSMallocFunctions trace_mf = { + js_trace_malloc, + js_trace_free, + js_trace_realloc, + js_trace_malloc_usable_size, +}; + +static size_t get_suffixed_size(const char *str) +{ + char *p; + size_t v; + v = (size_t)strtod(str, &p); + switch(*p) { + case 'G': + v <<= 30; + break; + case 'M': + v <<= 20; + break; + case 'k': + case 'K': + v <<= 10; + break; + default: + if (*p != '\0') { + fprintf(stderr, "qjs: invalid suffix: %s\n", p); + exit(1); + } + break; + } + return v; +} + +#define PROG_NAME "qjs" + +void help(void) +{ + printf("QuickJS version " CONFIG_VERSION "\n" + "usage: " PROG_NAME " [options] [file [args]]\n" + "-h --help list options\n" + "-e --eval EXPR evaluate EXPR\n" + "-i --interactive go to interactive mode\n" + "-m --module load as ES6 module (default=autodetect)\n" + " --script load as ES6 script (default=autodetect)\n" + " --strict force strict mode\n" + "-I --include file include an additional file\n" + " --std make 'std' and 'os' available to the loaded script\n" + "-T --trace trace memory allocation\n" + "-d --dump dump the memory usage stats\n" + " --memory-limit n limit the memory usage to 'n' bytes (SI suffixes allowed)\n" + " --stack-size n limit the stack size to 'n' bytes (SI suffixes allowed)\n" + " --no-unhandled-rejection ignore unhandled promise rejections\n" + "-s strip all the debug info\n" + " --strip-source strip the source code\n" + "-q --quit just instantiate the interpreter and quit\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + JSRuntime *rt; + JSContext *ctx; + struct trace_malloc_data trace_data = { NULL }; + int optind; + char *expr = NULL; + int interactive = 0; + int dump_memory = 0; + int trace_memory = 0; + int empty_run = 0; + int module = -1; + int strict = 0; + int load_std = 0; + int dump_unhandled_promise_rejection = 1; + size_t memory_limit = 0; + char *include_list[32]; + int i, include_count = 0; + int strip_flags = 0; + size_t stack_size = 0; + + /* cannot use getopt because we want to pass the command line to + the script */ + optind = 1; + while (optind < argc && *argv[optind] == '-') { + char *arg = argv[optind] + 1; + const char *longopt = ""; + /* a single - is not an option, it also stops argument scanning */ + if (!*arg) + break; + optind++; + if (*arg == '-') { + longopt = arg + 1; + arg += strlen(arg); + /* -- stops argument scanning */ + if (!*longopt) + break; + } + for (; *arg || *longopt; longopt = "") { + char opt = *arg; + if (opt) + arg++; + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { + help(); + continue; + } + if (opt == 'e' || !strcmp(longopt, "eval")) { + if (*arg) { + expr = arg; + break; + } + if (optind < argc) { + expr = argv[optind++]; + break; + } + fprintf(stderr, "qjs: missing expression for -e\n"); + exit(2); + } + if (opt == 'I' || !strcmp(longopt, "include")) { + if (optind >= argc) { + fprintf(stderr, "expecting filename"); + exit(1); + } + if (include_count >= countof(include_list)) { + fprintf(stderr, "too many included files"); + exit(1); + } + include_list[include_count++] = argv[optind++]; + continue; + } + if (opt == 'i' || !strcmp(longopt, "interactive")) { + interactive++; + continue; + } + if (opt == 'm' || !strcmp(longopt, "module")) { + module = 1; + continue; + } + if (!strcmp(longopt, "script")) { + module = 0; + continue; + } + if (!strcmp(longopt, "strict")) { + strict = 1; + continue; + } + if (opt == 'd' || !strcmp(longopt, "dump")) { + dump_memory++; + continue; + } + if (opt == 'T' || !strcmp(longopt, "trace")) { + trace_memory++; + continue; + } + if (!strcmp(longopt, "std")) { + load_std = 1; + continue; + } + if (!strcmp(longopt, "no-unhandled-rejection")) { + dump_unhandled_promise_rejection = 0; + continue; + } + if (opt == 'q' || !strcmp(longopt, "quit")) { + empty_run++; + continue; + } + if (!strcmp(longopt, "memory-limit")) { + if (optind >= argc) { + fprintf(stderr, "expecting memory limit"); + exit(1); + } + memory_limit = get_suffixed_size(argv[optind++]); + continue; + } + if (!strcmp(longopt, "stack-size")) { + if (optind >= argc) { + fprintf(stderr, "expecting stack size"); + exit(1); + } + stack_size = get_suffixed_size(argv[optind++]); + continue; + } + if (opt == 's') { + strip_flags = JS_STRIP_DEBUG; + continue; + } + if (!strcmp(longopt, "strip-source")) { + strip_flags = JS_STRIP_SOURCE; + continue; + } + if (opt) { + fprintf(stderr, "qjs: unknown option '-%c'\n", opt); + } else { + fprintf(stderr, "qjs: unknown option '--%s'\n", longopt); + } + help(); + } + } + + if (trace_memory) { + js_trace_malloc_init(&trace_data); + rt = JS_NewRuntime2(&trace_mf, &trace_data); + } else { + rt = JS_NewRuntime(); + } + if (!rt) { + fprintf(stderr, "qjs: cannot allocate JS runtime\n"); + exit(2); + } + if (memory_limit != 0) + JS_SetMemoryLimit(rt, memory_limit); + if (stack_size != 0) + JS_SetMaxStackSize(rt, stack_size); + JS_SetStripInfo(rt, strip_flags); + js_std_set_worker_new_context_func(JS_NewCustomContext); + js_std_init_handlers(rt); + ctx = JS_NewCustomContext(rt); + if (!ctx) { + fprintf(stderr, "qjs: cannot allocate JS context\n"); + exit(2); + } + + /* loader for ES6 modules */ + JS_SetModuleLoaderFunc2(rt, NULL, js_module_loader, js_module_check_attributes, NULL); + + if (dump_unhandled_promise_rejection) { + JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker, + NULL); + } + + if (!empty_run) { + js_std_add_helpers(ctx, argc - optind, argv + optind); + + /* make 'std' and 'os' visible to non module code */ + if (load_std) { + const char *str = "import * as std from 'std';\n" + "import * as os from 'os';\n" + "globalThis.std = std;\n" + "globalThis.os = os;\n"; + eval_buf(ctx, str, strlen(str), "", JS_EVAL_TYPE_MODULE); + } + + for(i = 0; i < include_count; i++) { + if (eval_file(ctx, include_list[i], 0, strict)) + goto fail; + } + + if (expr) { + int eval_flags; + if (module > 0) { + eval_flags = JS_EVAL_TYPE_MODULE; + } else { + eval_flags = JS_EVAL_TYPE_GLOBAL; + if (strict) + eval_flags |= JS_EVAL_FLAG_STRICT; + } + if (eval_buf(ctx, expr, strlen(expr), "", eval_flags)) + goto fail; + } else + if (optind >= argc) { + /* interactive mode */ + interactive = 1; + } else { + const char *filename; + filename = argv[optind]; + if (eval_file(ctx, filename, module, strict)) + goto fail; + } + if (interactive) { + JS_SetHostPromiseRejectionTracker(rt, NULL, NULL); + js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0); + } + js_std_loop(ctx); + } + + if (dump_memory) { + JSMemoryUsage stats; + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(stdout, &stats, rt); + } + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + if (empty_run && dump_memory) { + clock_t t[5]; + double best[5]; + int i, j; + for (i = 0; i < 100; i++) { + t[0] = clock(); + rt = JS_NewRuntime(); + t[1] = clock(); + ctx = JS_NewContext(rt); + t[2] = clock(); + JS_FreeContext(ctx); + t[3] = clock(); + JS_FreeRuntime(rt); + t[4] = clock(); + for (j = 4; j > 0; j--) { + double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC; + if (i == 0 || best[j] > ms) + best[j] = ms; + } + } + printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n", + best[1] + best[2] + best[3] + best[4], + best[1], best[2], best[3], best[4]); + } + return 0; + fail: + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 1; +} diff --git a/NativeScript/napi/android/quickjs/source/qjsc.c b/NativeScript/napi/android/quickjs/source/qjsc.c new file mode 100755 index 000000000..e55ca61ce --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/qjsc.c @@ -0,0 +1,876 @@ +/* + * QuickJS command line compiler + * + * Copyright (c) 2018-2021 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(_WIN32) +#include +#endif + +#include "cutils.h" +#include "quickjs-libc.h" + +typedef struct { + char *name; + char *short_name; + int flags; +} namelist_entry_t; + +typedef struct namelist_t { + namelist_entry_t *array; + int count; + int size; +} namelist_t; + +typedef struct { + const char *option_name; + const char *init_name; +} FeatureEntry; + +static namelist_t cname_list; +static namelist_t cmodule_list; +static namelist_t init_module_list; +static uint64_t feature_bitmap; +static FILE *outfile; +static BOOL byte_swap; +static BOOL dynamic_export; +static const char *c_ident_prefix = "qjsc_"; + +#define FE_ALL (-1) + +static const FeatureEntry feature_list[] = { + { "date", "Date" }, + { "eval", "Eval" }, + { "string-normalize", "StringNormalize" }, + { "regexp", "RegExp" }, + { "json", "JSON" }, + { "proxy", "Proxy" }, + { "map", "MapSet" }, + { "typedarray", "TypedArrays" }, + { "promise", "Promise" }, +#define FE_MODULE_LOADER 9 + { "module-loader", NULL }, + { "weakref", "WeakRef" }, +}; + +void namelist_add(namelist_t *lp, const char *name, const char *short_name, + int flags) +{ + namelist_entry_t *e; + if (lp->count == lp->size) { + size_t newsize = lp->size + (lp->size >> 1) + 4; + namelist_entry_t *a = + realloc(lp->array, sizeof(lp->array[0]) * newsize); + /* XXX: check for realloc failure */ + lp->array = a; + lp->size = newsize; + } + e = &lp->array[lp->count++]; + e->name = strdup(name); + if (short_name) + e->short_name = strdup(short_name); + else + e->short_name = NULL; + e->flags = flags; +} + +void namelist_free(namelist_t *lp) +{ + while (lp->count > 0) { + namelist_entry_t *e = &lp->array[--lp->count]; + free(e->name); + free(e->short_name); + } + free(lp->array); + lp->array = NULL; + lp->size = 0; +} + +namelist_entry_t *namelist_find(namelist_t *lp, const char *name) +{ + int i; + for(i = 0; i < lp->count; i++) { + namelist_entry_t *e = &lp->array[i]; + if (!strcmp(e->name, name)) + return e; + } + return NULL; +} + +static void get_c_name(char *buf, size_t buf_size, const char *file) +{ + const char *p, *r; + size_t len, i; + int c; + char *q; + + p = strrchr(file, '/'); + if (!p) + p = file; + else + p++; + r = strrchr(p, '.'); + if (!r) + len = strlen(p); + else + len = r - p; + pstrcpy(buf, buf_size, c_ident_prefix); + q = buf + strlen(buf); + for(i = 0; i < len; i++) { + c = p[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'))) { + c = '_'; + } + if ((q - buf) < buf_size - 1) + *q++ = c; + } + *q = '\0'; +} + +static void dump_hex(FILE *f, const uint8_t *buf, size_t len) +{ + size_t i, col; + col = 0; + for(i = 0; i < len; i++) { + fprintf(f, " 0x%02x,", buf[i]); + if (++col == 8) { + fprintf(f, "\n"); + col = 0; + } + } + if (col != 0) + fprintf(f, "\n"); +} + +typedef enum { + CNAME_TYPE_SCRIPT, + CNAME_TYPE_MODULE, + CNAME_TYPE_JSON_MODULE, +} CNameTypeEnum; + +static void output_object_code(JSContext *ctx, + FILE *fo, JSValueConst obj, const char *c_name, + CNameTypeEnum c_name_type) +{ + uint8_t *out_buf; + size_t out_buf_len; + int flags; + + if (c_name_type == CNAME_TYPE_JSON_MODULE) + flags = 0; + else + flags = JS_WRITE_OBJ_BYTECODE; + if (byte_swap) + flags |= JS_WRITE_OBJ_BSWAP; + out_buf = JS_WriteObject(ctx, &out_buf_len, obj, flags); + if (!out_buf) { + js_std_dump_error(ctx); + exit(1); + } + + namelist_add(&cname_list, c_name, NULL, c_name_type); + + fprintf(fo, "const uint32_t %s_size = %u;\n\n", + c_name, (unsigned int)out_buf_len); + fprintf(fo, "const uint8_t %s[%u] = {\n", + c_name, (unsigned int)out_buf_len); + dump_hex(fo, out_buf, out_buf_len); + fprintf(fo, "};\n\n"); + + js_free(ctx, out_buf); +} + +static int js_module_dummy_init(JSContext *ctx, JSModuleDef *m) +{ + /* should never be called when compiling JS code */ + abort(); +} + +static void find_unique_cname(char *cname, size_t cname_size) +{ + char cname1[1024]; + int suffix_num; + size_t len, max_len; + assert(cname_size >= 32); + /* find a C name not matching an existing module C name by + adding a numeric suffix */ + len = strlen(cname); + max_len = cname_size - 16; + if (len > max_len) + cname[max_len] = '\0'; + suffix_num = 1; + for(;;) { + snprintf(cname1, sizeof(cname1), "%s_%d", cname, suffix_num); + if (!namelist_find(&cname_list, cname1)) + break; + suffix_num++; + } + pstrcpy(cname, cname_size, cname1); +} + +JSModuleDef *jsc_module_loader(JSContext *ctx, + const char *module_name, void *opaque, + JSValueConst attributes) +{ + JSModuleDef *m; + namelist_entry_t *e; + + /* check if it is a declared C or system module */ + e = namelist_find(&cmodule_list, module_name); + if (e) { + /* add in the static init module list */ + namelist_add(&init_module_list, e->name, e->short_name, 0); + /* create a dummy module */ + m = JS_NewCModule(ctx, module_name, js_module_dummy_init); + } else if (has_suffix(module_name, ".so")) { + fprintf(stderr, "Warning: binary module '%s' will be dynamically loaded\n", module_name); + /* create a dummy module */ + m = JS_NewCModule(ctx, module_name, js_module_dummy_init); + /* the resulting executable will export its symbols for the + dynamic library */ + dynamic_export = TRUE; + } else { + size_t buf_len; + uint8_t *buf; + char cname[1024]; + int res; + + buf = js_load_file(ctx, &buf_len, module_name); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s'", + module_name); + return NULL; + } + + res = js_module_test_json(ctx, attributes); + if (has_suffix(module_name, ".json") || res > 0) { + /* compile as JSON or JSON5 depending on "type" */ + JSValue val; + int flags; + + if (res == 2) + flags = JS_PARSE_JSON_EXT; + else + flags = 0; + val = JS_ParseJSON2(ctx, (char *)buf, buf_len, module_name, flags); + js_free(ctx, buf); + if (JS_IsException(val)) + return NULL; + /* create a dummy module */ + m = JS_NewCModule(ctx, module_name, js_module_dummy_init); + if (!m) { + JS_FreeValue(ctx, val); + return NULL; + } + + get_c_name(cname, sizeof(cname), module_name); + if (namelist_find(&cname_list, cname)) { + find_unique_cname(cname, sizeof(cname)); + } + + /* output the module name */ + fprintf(outfile, "static const uint8_t %s_module_name[] = {\n", + cname); + dump_hex(outfile, (const uint8_t *)module_name, strlen(module_name) + 1); + fprintf(outfile, "};\n\n"); + + output_object_code(ctx, outfile, val, cname, CNAME_TYPE_JSON_MODULE); + JS_FreeValue(ctx, val); + } else { + JSValue func_val; + + /* compile the module */ + func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + js_free(ctx, buf); + if (JS_IsException(func_val)) + return NULL; + get_c_name(cname, sizeof(cname), module_name); + if (namelist_find(&cname_list, cname)) { + find_unique_cname(cname, sizeof(cname)); + } + output_object_code(ctx, outfile, func_val, cname, CNAME_TYPE_MODULE); + + /* the module is already referenced, so we must free it */ + m = JS_VALUE_GET_PTR(func_val); + JS_FreeValue(ctx, func_val); + } + } + return m; +} + +static void compile_file(JSContext *ctx, FILE *fo, + const char *filename, + const char *c_name1, + int module) +{ + uint8_t *buf; + char c_name[1024]; + int eval_flags; + JSValue obj; + size_t buf_len; + + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + fprintf(stderr, "Could not load '%s'\n", filename); + exit(1); + } + eval_flags = JS_EVAL_FLAG_COMPILE_ONLY; + if (module < 0) { + module = (has_suffix(filename, ".mjs") || + JS_DetectModule((const char *)buf, buf_len)); + } + if (module) + eval_flags |= JS_EVAL_TYPE_MODULE; + else + eval_flags |= JS_EVAL_TYPE_GLOBAL; + obj = JS_Eval(ctx, (const char *)buf, buf_len, filename, eval_flags); + if (JS_IsException(obj)) { + js_std_dump_error(ctx); + exit(1); + } + js_free(ctx, buf); + if (c_name1) { + pstrcpy(c_name, sizeof(c_name), c_name1); + } else { + get_c_name(c_name, sizeof(c_name), filename); + if (namelist_find(&cname_list, c_name)) { + find_unique_cname(c_name, sizeof(c_name)); + } + } + output_object_code(ctx, fo, obj, c_name, CNAME_TYPE_SCRIPT); + JS_FreeValue(ctx, obj); +} + +static const char main_c_template1[] = + "int main(int argc, char **argv)\n" + "{\n" + " JSRuntime *rt;\n" + " JSContext *ctx;\n" + " rt = JS_NewRuntime();\n" + " js_std_set_worker_new_context_func(JS_NewCustomContext);\n" + " js_std_init_handlers(rt);\n" + ; + +static const char main_c_template2[] = + " js_std_loop(ctx);\n" + " js_std_free_handlers(rt);\n" + " JS_FreeContext(ctx);\n" + " JS_FreeRuntime(rt);\n" + " return 0;\n" + "}\n"; + +#define PROG_NAME "qjsc" + +void help(void) +{ + printf("QuickJS Compiler version " CONFIG_VERSION "\n" + "usage: " PROG_NAME " [options] [files]\n" + "\n" + "options are:\n" + "-c only output bytecode to a C file\n" + "-e output main() and bytecode to a C file (default = executable output)\n" + "-o output set the output filename\n" + "-N cname set the C name of the generated data\n" + "-m compile as Javascript module (default=autodetect)\n" + "-D module_name compile a dynamically loaded module or worker\n" + "-M module_name[,cname] add initialization code for an external C module\n" + "-x byte swapped output\n" + "-p prefix set the prefix of the generated C names\n" + "-S n set the maximum stack size to 'n' bytes (default=%d)\n" + "-s strip all the debug info\n" + "--keep-source keep the source code\n", + JS_DEFAULT_STACK_SIZE); +#ifdef CONFIG_LTO + { + int i; + printf("-flto use link time optimization\n"); + printf("-fno-["); + for(i = 0; i < countof(feature_list); i++) { + if (i != 0) + printf("|"); + printf("%s", feature_list[i].option_name); + } + printf("]\n" + " disable selected language features (smaller code size)\n"); + } +#endif + exit(1); +} + +#if defined(CONFIG_CC) && !defined(_WIN32) + +int exec_cmd(char **argv) +{ + int pid, status, ret; + + pid = fork(); + if (pid == 0) { + execvp(argv[0], argv); + exit(1); + } + + for(;;) { + ret = waitpid(pid, &status, 0); + if (ret == pid && WIFEXITED(status)) + break; + } + return WEXITSTATUS(status); +} + +static int output_executable(const char *out_filename, const char *cfilename, + BOOL use_lto, BOOL verbose, const char *exename) +{ + const char *argv[64]; + const char **arg, *bn_suffix, *lto_suffix; + char libjsname[1024]; + char exe_dir[1024], inc_dir[1024], lib_dir[1024], buf[1024], *p; + int ret; + + /* get the directory of the executable */ + pstrcpy(exe_dir, sizeof(exe_dir), exename); + p = strrchr(exe_dir, '/'); + if (p) { + *p = '\0'; + } else { + pstrcpy(exe_dir, sizeof(exe_dir), "."); + } + + /* if 'quickjs.h' is present at the same path as the executable, we + use it as include and lib directory */ + snprintf(buf, sizeof(buf), "%s/quickjs.h", exe_dir); + if (access(buf, R_OK) == 0) { + pstrcpy(inc_dir, sizeof(inc_dir), exe_dir); + pstrcpy(lib_dir, sizeof(lib_dir), exe_dir); + } else { + snprintf(inc_dir, sizeof(inc_dir), "%s/include/quickjs", CONFIG_PREFIX); + snprintf(lib_dir, sizeof(lib_dir), "%s/lib/quickjs", CONFIG_PREFIX); + } + + lto_suffix = ""; + bn_suffix = ""; + + arg = argv; + *arg++ = CONFIG_CC; + *arg++ = "-O2"; +#ifdef CONFIG_LTO + if (use_lto) { + *arg++ = "-flto"; + lto_suffix = ".lto"; + } +#endif + /* XXX: use the executable path to find the includes files and + libraries */ + *arg++ = "-D"; + *arg++ = "_GNU_SOURCE"; + *arg++ = "-I"; + *arg++ = inc_dir; + *arg++ = "-o"; + *arg++ = out_filename; + if (dynamic_export) + *arg++ = "-rdynamic"; + *arg++ = cfilename; + snprintf(libjsname, sizeof(libjsname), "%s/libquickjs%s%s.a", + lib_dir, bn_suffix, lto_suffix); + *arg++ = libjsname; + *arg++ = "-lm"; + *arg++ = "-ldl"; + *arg++ = "-lpthread"; + *arg = NULL; + + if (verbose) { + for(arg = argv; *arg != NULL; arg++) + printf("%s ", *arg); + printf("\n"); + } + + ret = exec_cmd((char **)argv); + unlink(cfilename); + return ret; +} +#else +static int output_executable(const char *out_filename, const char *cfilename, + BOOL use_lto, BOOL verbose, const char *exename) +{ + fprintf(stderr, "Executable output is not supported for this target\n"); + exit(1); + return 0; +} +#endif + +static size_t get_suffixed_size(const char *str) +{ + char *p; + size_t v; + v = (size_t)strtod(str, &p); + switch(*p) { + case 'G': + v <<= 30; + break; + case 'M': + v <<= 20; + break; + case 'k': + case 'K': + v <<= 10; + break; + default: + if (*p != '\0') { + fprintf(stderr, "qjs: invalid suffix: %s\n", p); + exit(1); + } + break; + } + return v; +} + +typedef enum { + OUTPUT_C, + OUTPUT_C_MAIN, + OUTPUT_EXECUTABLE, +} OutputTypeEnum; + +static const char *get_short_optarg(int *poptind, int opt, + const char *arg, int argc, char **argv) +{ + const char *optarg; + if (*arg) { + optarg = arg; + } else if (*poptind < argc) { + optarg = argv[(*poptind)++]; + } else { + fprintf(stderr, "qjsc: expecting parameter for -%c\n", opt); + exit(1); + } + return optarg; +} + +int main(int argc, char **argv) +{ + int i, verbose, strip_flags; + const char *out_filename, *cname; + char cfilename[1024]; + FILE *fo; + JSRuntime *rt; + JSContext *ctx; + BOOL use_lto; + int module; + OutputTypeEnum output_type; + size_t stack_size; + namelist_t dynamic_module_list; + + out_filename = NULL; + output_type = OUTPUT_EXECUTABLE; + cname = NULL; + feature_bitmap = FE_ALL; + module = -1; + byte_swap = FALSE; + verbose = 0; + strip_flags = JS_STRIP_SOURCE; + use_lto = FALSE; + stack_size = 0; + memset(&dynamic_module_list, 0, sizeof(dynamic_module_list)); + + /* add system modules */ + namelist_add(&cmodule_list, "std", "std", 0); + namelist_add(&cmodule_list, "os", "os", 0); + + optind = 1; + while (optind < argc && *argv[optind] == '-') { + char *arg = argv[optind] + 1; + const char *longopt = ""; + const char *optarg; + /* a single - is not an option, it also stops argument scanning */ + if (!*arg) + break; + optind++; + if (*arg == '-') { + longopt = arg + 1; + arg += strlen(arg); + /* -- stops argument scanning */ + if (!*longopt) + break; + } + for (; *arg || *longopt; longopt = "") { + char opt = *arg; + if (opt) + arg++; + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { + help(); + continue; + } + if (opt == 'o') { + out_filename = get_short_optarg(&optind, opt, arg, argc, argv); + break; + } + if (opt == 'c') { + output_type = OUTPUT_C; + continue; + } + if (opt == 'e') { + output_type = OUTPUT_C_MAIN; + continue; + } + if (opt == 'N') { + cname = get_short_optarg(&optind, opt, arg, argc, argv); + break; + } + if (opt == 'f') { + const char *p; + optarg = get_short_optarg(&optind, opt, arg, argc, argv); + p = optarg; + if (!strcmp(p, "lto")) { + use_lto = TRUE; + } else if (strstart(p, "no-", &p)) { + use_lto = TRUE; + for(i = 0; i < countof(feature_list); i++) { + if (!strcmp(p, feature_list[i].option_name)) { + feature_bitmap &= ~((uint64_t)1 << i); + break; + } + } + if (i == countof(feature_list)) + goto bad_feature; + } else { + bad_feature: + fprintf(stderr, "unsupported feature: %s\n", optarg); + exit(1); + } + break; + } + if (opt == 'm') { + module = 1; + continue; + } + if (opt == 'M') { + char *p; + char path[1024]; + char cname[1024]; + + optarg = get_short_optarg(&optind, opt, arg, argc, argv); + pstrcpy(path, sizeof(path), optarg); + p = strchr(path, ','); + if (p) { + *p = '\0'; + pstrcpy(cname, sizeof(cname), p + 1); + } else { + get_c_name(cname, sizeof(cname), path); + } + namelist_add(&cmodule_list, path, cname, 0); + break; + } + if (opt == 'D') { + optarg = get_short_optarg(&optind, opt, arg, argc, argv); + namelist_add(&dynamic_module_list, optarg, NULL, 0); + break; + } + if (opt == 'x') { + byte_swap = 1; + continue; + } + if (opt == 'v') { + verbose++; + continue; + } + if (opt == 'p') { + c_ident_prefix = get_short_optarg(&optind, opt, arg, argc, argv); + break; + } + if (opt == 'S') { + optarg = get_short_optarg(&optind, opt, arg, argc, argv); + stack_size = get_suffixed_size(optarg); + break; + } + if (opt == 's') { + strip_flags = JS_STRIP_DEBUG; + continue; + } + if (!strcmp(longopt, "keep-source")) { + strip_flags = 0; + continue; + } + if (opt) { + fprintf(stderr, "qjsc: unknown option '-%c'\n", opt); + } else { + fprintf(stderr, "qjsc: unknown option '--%s'\n", longopt); + } + help(); + } + } + + if (optind >= argc) + help(); + + if (!out_filename) { + if (output_type == OUTPUT_EXECUTABLE) { + out_filename = "a.out"; + } else { + out_filename = "out.c"; + } + } + + if (output_type == OUTPUT_EXECUTABLE) { +#if defined(_WIN32) || defined(__ANDROID__) + /* XXX: find a /tmp directory ? */ + snprintf(cfilename, sizeof(cfilename), "out%d.c", getpid()); +#else + snprintf(cfilename, sizeof(cfilename), "/tmp/out%d.c", getpid()); +#endif + } else { + pstrcpy(cfilename, sizeof(cfilename), out_filename); + } + + fo = fopen(cfilename, "w"); + if (!fo) { + perror(cfilename); + exit(1); + } + outfile = fo; + + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); + + JS_SetStripInfo(rt, strip_flags); + + /* loader for ES6 modules */ + JS_SetModuleLoaderFunc2(rt, NULL, jsc_module_loader, NULL, NULL); + + fprintf(fo, "/* File generated automatically by the QuickJS compiler. */\n" + "\n" + ); + + if (output_type != OUTPUT_C) { + fprintf(fo, "#include \"quickjs-libc.h\"\n" + "\n" + ); + } else { + fprintf(fo, "#include \n" + "\n" + ); + } + + for(i = optind; i < argc; i++) { + const char *filename = argv[i]; + compile_file(ctx, fo, filename, cname, module); + cname = NULL; + } + + for(i = 0; i < dynamic_module_list.count; i++) { + if (!jsc_module_loader(ctx, dynamic_module_list.array[i].name, NULL, JS_UNDEFINED)) { + fprintf(stderr, "Could not load dynamic module '%s'\n", + dynamic_module_list.array[i].name); + exit(1); + } + } + + if (output_type != OUTPUT_C) { + fprintf(fo, + "static JSContext *JS_NewCustomContext(JSRuntime *rt)\n" + "{\n" + " JSContext *ctx = JS_NewContextRaw(rt);\n" + " if (!ctx)\n" + " return NULL;\n"); + /* add the basic objects */ + fprintf(fo, " JS_AddIntrinsicBaseObjects(ctx);\n"); + for(i = 0; i < countof(feature_list); i++) { + if ((feature_bitmap & ((uint64_t)1 << i)) && + feature_list[i].init_name) { + fprintf(fo, " JS_AddIntrinsic%s(ctx);\n", + feature_list[i].init_name); + } + } + /* add the precompiled modules (XXX: could modify the module + loader instead) */ + for(i = 0; i < init_module_list.count; i++) { + namelist_entry_t *e = &init_module_list.array[i]; + /* initialize the static C modules */ + + fprintf(fo, + " {\n" + " extern JSModuleDef *js_init_module_%s(JSContext *ctx, const char *name);\n" + " js_init_module_%s(ctx, \"%s\");\n" + " }\n", + e->short_name, e->short_name, e->name); + } + for(i = 0; i < cname_list.count; i++) { + namelist_entry_t *e = &cname_list.array[i]; + if (e->flags == CNAME_TYPE_MODULE) { + fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 1);\n", + e->name, e->name); + } else if (e->flags == CNAME_TYPE_JSON_MODULE) { + fprintf(fo, " js_std_eval_binary_json_module(ctx, %s, %s_size, (const char *)%s_module_name);\n", + e->name, e->name, e->name); + } + } + fprintf(fo, + " return ctx;\n" + "}\n\n"); + + fputs(main_c_template1, fo); + + if (stack_size != 0) { + fprintf(fo, " JS_SetMaxStackSize(rt, %u);\n", + (unsigned int)stack_size); + } + + /* add the module loader if necessary */ + if (feature_bitmap & (1 << FE_MODULE_LOADER)) { + fprintf(fo, " JS_SetModuleLoaderFunc2(rt, NULL, js_module_loader, js_module_check_attributes, NULL);\n"); + } + + fprintf(fo, + " ctx = JS_NewCustomContext(rt);\n" + " js_std_add_helpers(ctx, argc, argv);\n"); + + for(i = 0; i < cname_list.count; i++) { + namelist_entry_t *e = &cname_list.array[i]; + if (e->flags == CNAME_TYPE_SCRIPT) { + fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 0);\n", + e->name, e->name); + } + } + fputs(main_c_template2, fo); + } + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + fclose(fo); + + if (output_type == OUTPUT_EXECUTABLE) { + return output_executable(out_filename, cfilename, use_lto, verbose, + argv[0]); + } + namelist_free(&cname_list); + namelist_free(&cmodule_list); + namelist_free(&init_module_list); + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source/quickjs-atom.h b/NativeScript/napi/android/quickjs/source/quickjs-atom.h new file mode 100755 index 000000000..dac2df678 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/quickjs-atom.h @@ -0,0 +1,278 @@ +/* + * QuickJS atom definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef DEF + +/* Note: first atoms are considered as keywords in the parser */ +DEF(null, "null") /* must be first */ +DEF(false, "false") +DEF(true, "true") +DEF(if, "if") +DEF(else, "else") +DEF(return, "return") +DEF(var, "var") +DEF(this, "this") +DEF(delete, "delete") +DEF(void, "void") +DEF(typeof, "typeof") +DEF(new, "new") +DEF(in, "in") +DEF(instanceof, "instanceof") +DEF(do, "do") +DEF(while, "while") +DEF(for, "for") +DEF(break, "break") +DEF(continue, "continue") +DEF(switch, "switch") +DEF(case, "case") +DEF(default, "default") +DEF(throw, "throw") +DEF(try, "try") +DEF(catch, "catch") +DEF(finally, "finally") +DEF(function, "function") +DEF(debugger, "debugger") +DEF(with, "with") +/* FutureReservedWord */ +DEF(class, "class") +DEF(const, "const") +DEF(enum, "enum") +DEF(export, "export") +DEF(extends, "extends") +DEF(import, "import") +DEF(super, "super") +/* FutureReservedWords when parsing strict mode code */ +DEF(implements, "implements") +DEF(interface, "interface") +DEF(let, "let") +DEF(package, "package") +DEF(private, "private") +DEF(protected, "protected") +DEF(public, "public") +DEF(static, "static") +DEF(yield, "yield") +DEF(await, "await") + +/* empty string */ +DEF(empty_string, "") +/* identifiers */ +DEF(keys, "keys") +DEF(size, "size") +DEF(length, "length") +DEF(fileName, "fileName") +DEF(lineNumber, "lineNumber") +DEF(columnNumber, "columnNumber") +DEF(message, "message") +DEF(cause, "cause") +DEF(errors, "errors") +DEF(stack, "stack") +DEF(name, "name") +DEF(toString, "toString") +DEF(toLocaleString, "toLocaleString") +DEF(valueOf, "valueOf") +DEF(eval, "eval") +DEF(prototype, "prototype") +DEF(constructor, "constructor") +DEF(configurable, "configurable") +DEF(writable, "writable") +DEF(enumerable, "enumerable") +DEF(value, "value") +DEF(get, "get") +DEF(set, "set") +DEF(of, "of") +DEF(__proto__, "__proto__") +DEF(undefined, "undefined") +DEF(number, "number") +DEF(boolean, "boolean") +DEF(string, "string") +DEF(object, "object") +DEF(symbol, "symbol") +DEF(integer, "integer") +DEF(unknown, "unknown") +DEF(arguments, "arguments") +DEF(callee, "callee") +DEF(caller, "caller") +DEF(_eval_, "") +DEF(_ret_, "") +DEF(_var_, "") +DEF(_arg_var_, "") +DEF(_with_, "") +DEF(lastIndex, "lastIndex") +DEF(target, "target") +DEF(index, "index") +DEF(input, "input") +DEF(defineProperties, "defineProperties") +DEF(apply, "apply") +DEF(join, "join") +DEF(concat, "concat") +DEF(split, "split") +DEF(construct, "construct") +DEF(getPrototypeOf, "getPrototypeOf") +DEF(setPrototypeOf, "setPrototypeOf") +DEF(isExtensible, "isExtensible") +DEF(preventExtensions, "preventExtensions") +DEF(has, "has") +DEF(deleteProperty, "deleteProperty") +DEF(defineProperty, "defineProperty") +DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") +DEF(ownKeys, "ownKeys") +DEF(add, "add") +DEF(done, "done") +DEF(next, "next") +DEF(values, "values") +DEF(source, "source") +DEF(flags, "flags") +DEF(global, "global") +DEF(unicode, "unicode") +DEF(raw, "raw") +DEF(new_target, "new.target") +DEF(this_active_func, "this.active_func") +DEF(home_object, "") +DEF(computed_field, "") +DEF(static_computed_field, "") /* must come after computed_fields */ +DEF(class_fields_init, "") +DEF(brand, "") +DEF(hash_constructor, "#constructor") +DEF(as, "as") +DEF(from, "from") +DEF(meta, "meta") +DEF(_default_, "*default*") +DEF(_star_, "*") +DEF(Module, "Module") +DEF(then, "then") +DEF(resolve, "resolve") +DEF(reject, "reject") +DEF(promise, "promise") +DEF(proxy, "proxy") +DEF(revoke, "revoke") +DEF(async, "async") +DEF(exec, "exec") +DEF(groups, "groups") +DEF(indices, "indices") +DEF(status, "status") +DEF(reason, "reason") +DEF(globalThis, "globalThis") +DEF(bigint, "bigint") +DEF(minus_zero, "-0") +DEF(Infinity, "Infinity") +DEF(minus_Infinity, "-Infinity") +DEF(NaN, "NaN") +DEF(hasIndices, "hasIndices") +DEF(ignoreCase, "ignoreCase") +DEF(multiline, "multiline") +DEF(dotAll, "dotAll") +DEF(sticky, "sticky") +DEF(unicodeSets, "unicodeSets") +/* the following 3 atoms are only used with CONFIG_ATOMICS */ +DEF(not_equal, "not-equal") +DEF(timed_out, "timed-out") +DEF(ok, "ok") +/* */ +DEF(toJSON, "toJSON") +DEF(maxByteLength, "maxByteLength") +/* class names */ +DEF(Object, "Object") +DEF(Array, "Array") +DEF(Error, "Error") +DEF(Number, "Number") +DEF(String, "String") +DEF(Boolean, "Boolean") +DEF(Symbol, "Symbol") +DEF(Arguments, "Arguments") +DEF(Math, "Math") +DEF(JSON, "JSON") +DEF(Date, "Date") +DEF(Function, "Function") +DEF(GeneratorFunction, "GeneratorFunction") +DEF(ForInIterator, "ForInIterator") +DEF(RegExp, "RegExp") +DEF(ArrayBuffer, "ArrayBuffer") +DEF(SharedArrayBuffer, "SharedArrayBuffer") +/* must keep same order as class IDs for typed arrays */ +DEF(Uint8ClampedArray, "Uint8ClampedArray") +DEF(Int8Array, "Int8Array") +DEF(Uint8Array, "Uint8Array") +DEF(Int16Array, "Int16Array") +DEF(Uint16Array, "Uint16Array") +DEF(Int32Array, "Int32Array") +DEF(Uint32Array, "Uint32Array") +DEF(BigInt64Array, "BigInt64Array") +DEF(BigUint64Array, "BigUint64Array") +DEF(Float16Array, "Float16Array") +DEF(Float32Array, "Float32Array") +DEF(Float64Array, "Float64Array") +DEF(DataView, "DataView") +DEF(BigInt, "BigInt") +DEF(WeakRef, "WeakRef") +DEF(FinalizationRegistry, "FinalizationRegistry") +DEF(Map, "Map") +DEF(Set, "Set") /* Map + 1 */ +DEF(WeakMap, "WeakMap") /* Map + 2 */ +DEF(WeakSet, "WeakSet") /* Map + 3 */ +DEF(Iterator, "Iterator") +DEF(IteratorHelper, "Iterator Helper") +DEF(IteratorConcat, "Iterator Concat") +DEF(IteratorWrap, "Iterator Wrap") +DEF(Map_Iterator, "Map Iterator") +DEF(Set_Iterator, "Set Iterator") +DEF(Array_Iterator, "Array Iterator") +DEF(String_Iterator, "String Iterator") +DEF(RegExp_String_Iterator, "RegExp String Iterator") +DEF(Generator, "Generator") +DEF(Proxy, "Proxy") +DEF(Promise, "Promise") +DEF(PromiseResolveFunction, "PromiseResolveFunction") +DEF(PromiseRejectFunction, "PromiseRejectFunction") +DEF(AsyncFunction, "AsyncFunction") +DEF(AsyncFunctionResolve, "AsyncFunctionResolve") +DEF(AsyncFunctionReject, "AsyncFunctionReject") +DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") +DEF(AsyncGenerator, "AsyncGenerator") +DEF(EvalError, "EvalError") +DEF(RangeError, "RangeError") +DEF(ReferenceError, "ReferenceError") +DEF(SyntaxError, "SyntaxError") +DEF(TypeError, "TypeError") +DEF(URIError, "URIError") +DEF(InternalError, "InternalError") +DEF(AggregateError, "AggregateError") +/* private symbols */ +DEF(Private_brand, "") +/* symbols */ +DEF(Symbol_toPrimitive, "Symbol.toPrimitive") +DEF(Symbol_iterator, "Symbol.iterator") +DEF(Symbol_match, "Symbol.match") +DEF(Symbol_matchAll, "Symbol.matchAll") +DEF(Symbol_replace, "Symbol.replace") +DEF(Symbol_search, "Symbol.search") +DEF(Symbol_split, "Symbol.split") +DEF(Symbol_toStringTag, "Symbol.toStringTag") +DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") +DEF(Symbol_hasInstance, "Symbol.hasInstance") +DEF(Symbol_species, "Symbol.species") +DEF(Symbol_unscopables, "Symbol.unscopables") +DEF(Symbol_asyncIterator, "Symbol.asyncIterator") + +#endif /* DEF */ diff --git a/NativeScript/napi/android/quickjs/source/quickjs-libc.c b/NativeScript/napi/android/quickjs/source/quickjs-libc.c new file mode 100755 index 000000000..c24b6d53e --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/quickjs-libc.c @@ -0,0 +1,4362 @@ +/* + * QuickJS C library + * + * Copyright (c) 2017-2021 Fabrice Bellard + * Copyright (c) 2017-2021 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(_WIN32) +#include +#include +#include +#else +#include +#include +#include +#include + +#if defined(__FreeBSD__) +extern char **environ; +#endif + +#if defined(__APPLE__) || defined(__FreeBSD__) +typedef sig_t sighandler_t; +#endif + +#if defined(__APPLE__) +#if !defined(environ) +#include +#define environ (*_NSGetEnviron()) +#endif +#endif /* __APPLE__ */ + +#endif + +/* enable the os.Worker API. It relies on POSIX threads */ +#define USE_WORKER + +#ifdef USE_WORKER +#include +#include +#endif + +#include "cutils.h" +#include "list.h" +#include "quickjs-libc.h" + +#if !defined(PATH_MAX) +#define PATH_MAX 4096 +#endif + +/* TODO: + - add socket calls +*/ + +typedef struct { + struct list_head link; + int fd; + JSValue rw_func[2]; +} JSOSRWHandler; + +typedef struct { + struct list_head link; + int sig_num; + JSValue func; +} JSOSSignalHandler; + +typedef struct { + struct list_head link; + int timer_id; + int64_t timeout; + JSValue func; +} JSOSTimer; + +typedef struct { + struct list_head link; + uint8_t *data; + size_t data_len; + /* list of SharedArrayBuffers, necessary to free the message */ + uint8_t **sab_tab; + size_t sab_tab_len; +} JSWorkerMessage; + +typedef struct JSWaker { +#ifdef _WIN32 + HANDLE handle; +#else + int read_fd; + int write_fd; +#endif +} JSWaker; + +typedef struct { + int ref_count; +#ifdef USE_WORKER + pthread_mutex_t mutex; +#endif + struct list_head msg_queue; /* list of JSWorkerMessage.link */ + JSWaker waker; +} JSWorkerMessagePipe; + +typedef struct { + struct list_head link; + JSWorkerMessagePipe *recv_pipe; + JSValue on_message_func; +} JSWorkerMessageHandler; + +typedef struct { + struct list_head link; + JSValue promise; + JSValue reason; +} JSRejectedPromiseEntry; + +typedef struct JSThreadState { + struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */ + struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */ + struct list_head os_timers; /* list of JSOSTimer.link */ + struct list_head port_list; /* list of JSWorkerMessageHandler.link */ + struct list_head rejected_promise_list; /* list of JSRejectedPromiseEntry.link */ + int eval_script_recurse; /* only used in the main thread */ + int next_timer_id; /* for setTimeout() */ + /* not used in the main thread */ + JSWorkerMessagePipe *recv_pipe, *send_pipe; +} JSThreadState; + +static uint64_t os_pending_signals; +static int (*os_poll_func)(JSContext *ctx); + +static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt); +} + +static BOOL my_isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +/* XXX: use 'o' and 'O' for object using JS_PrintValue() ? */ +static JSValue js_printf_internal(JSContext *ctx, + int argc, JSValueConst *argv, FILE *fp) +{ + char fmtbuf[32]; + uint8_t cbuf[UTF8_CHAR_LEN_MAX+1]; + JSValue res; + DynBuf dbuf; + const char *fmt_str = NULL; + const uint8_t *fmt, *fmt_end; + const uint8_t *p; + char *q; + int i, c, len, mod; + size_t fmt_len; + int32_t int32_arg; + int64_t int64_arg; + double double_arg; + const char *string_arg; + /* Use indirect call to dbuf_printf to prevent gcc warning */ + int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void*)dbuf_printf; + + js_std_dbuf_init(ctx, &dbuf); + + if (argc > 0) { + fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]); + if (!fmt_str) + goto fail; + + i = 1; + fmt = (const uint8_t *)fmt_str; + fmt_end = fmt + fmt_len; + while (fmt < fmt_end) { + for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++) + continue; + dbuf_put(&dbuf, p, fmt - p); + if (fmt >= fmt_end) + break; + q = fmtbuf; + *q++ = *fmt++; /* copy '%' */ + + /* flags */ + for(;;) { + c = *fmt; + if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' || + c == '\'') { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = c; + fmt++; + } else { + break; + } + } + /* width */ + if (*fmt == '*') { + if (i >= argc) + goto missing; + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); + fmt++; + } else { + while (my_isdigit(*fmt)) { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + } + } + if (*fmt == '.') { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + if (*fmt == '*') { + if (i >= argc) + goto missing; + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); + fmt++; + } else { + while (my_isdigit(*fmt)) { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + } + } + } + + /* we only support the "l" modifier for 64 bit numbers */ + mod = ' '; + if (*fmt == 'l') { + mod = *fmt++; + } + + /* type */ + c = *fmt++; + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = c; + *q = '\0'; + + switch (c) { + case 'c': + if (i >= argc) + goto missing; + if (JS_IsString(argv[i])) { + string_arg = JS_ToCString(ctx, argv[i++]); + if (!string_arg) + goto fail; + int32_arg = unicode_from_utf8((const uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p); + JS_FreeCString(ctx, string_arg); + } else { + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + } + /* handle utf-8 encoding explicitly */ + if ((unsigned)int32_arg > 0x10FFFF) + int32_arg = 0xFFFD; + /* ignore conversion flags, width and precision */ + len = unicode_to_utf8(cbuf, int32_arg); + dbuf_put(&dbuf, cbuf, len); + break; + + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + if (i >= argc) + goto missing; + if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) + goto fail; + if (mod == 'l') { + /* 64 bit number */ +#if defined(_WIN32) + if (q >= fmtbuf + sizeof(fmtbuf) - 3) + goto invalid; + q[2] = q[-1]; + q[-1] = 'I'; + q[0] = '6'; + q[1] = '4'; + q[3] = '\0'; + dbuf_printf_fun(&dbuf, fmtbuf, (int64_t)int64_arg); +#else + if (q >= fmtbuf + sizeof(fmtbuf) - 2) + goto invalid; + q[1] = q[-1]; + q[-1] = q[0] = 'l'; + q[2] = '\0'; + dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg); +#endif + } else { + dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg); + } + break; + + case 's': + if (i >= argc) + goto missing; + /* XXX: handle strings containing null characters */ + string_arg = JS_ToCString(ctx, argv[i++]); + if (!string_arg) + goto fail; + dbuf_printf_fun(&dbuf, fmtbuf, string_arg); + JS_FreeCString(ctx, string_arg); + break; + + case 'e': + case 'f': + case 'g': + case 'a': + case 'E': + case 'F': + case 'G': + case 'A': + if (i >= argc) + goto missing; + if (JS_ToFloat64(ctx, &double_arg, argv[i++])) + goto fail; + dbuf_printf_fun(&dbuf, fmtbuf, double_arg); + break; + + case '%': + dbuf_putc(&dbuf, '%'); + break; + + default: + /* XXX: should support an extension mechanism */ + invalid: + JS_ThrowTypeError(ctx, "invalid conversion specifier in format string"); + goto fail; + missing: + JS_ThrowReferenceError(ctx, "missing argument for conversion specifier"); + goto fail; + } + } + JS_FreeCString(ctx, fmt_str); + } + if (dbuf.error) { + res = JS_ThrowOutOfMemory(ctx); + } else { + if (fp) { + len = fwrite(dbuf.buf, 1, dbuf.size, fp); + res = JS_NewInt32(ctx, len); + } else { + res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); + } + } + dbuf_free(&dbuf); + return res; + +fail: + JS_FreeCString(ctx, fmt_str); + dbuf_free(&dbuf); + return JS_EXCEPTION; +} + +uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename) +{ + FILE *f; + uint8_t *buf; + size_t buf_len; + long lret; + + f = fopen(filename, "rb"); + if (!f) + return NULL; + if (fseek(f, 0, SEEK_END) < 0) + goto fail; + lret = ftell(f); + if (lret < 0) + goto fail; + /* XXX: on Linux, ftell() return LONG_MAX for directories */ + if (lret == LONG_MAX) { + errno = EISDIR; + goto fail; + } + buf_len = lret; + if (fseek(f, 0, SEEK_SET) < 0) + goto fail; + if (ctx) + buf = js_malloc(ctx, buf_len + 1); + else + buf = malloc(buf_len + 1); + if (!buf) + goto fail; + if (fread(buf, 1, buf_len, f) != buf_len) { + errno = EIO; + if (ctx) + js_free(ctx, buf); + else + free(buf); + fail: + fclose(f); + return NULL; + } + buf[buf_len] = '\0'; + fclose(f); + *pbuf_len = buf_len; + return buf; +} + +/* load and evaluate a file */ +static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *buf; + const char *filename; + JSValue ret; + size_t buf_len; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load '%s'", filename); + JS_FreeCString(ctx, filename); + return JS_EXCEPTION; + } + ret = JS_Eval(ctx, (char *)buf, buf_len, filename, + JS_EVAL_TYPE_GLOBAL); + js_free(ctx, buf); + JS_FreeCString(ctx, filename); + return ret; +} + +/* load a file as a UTF-8 encoded string */ +static JSValue js_std_loadFile(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *buf; + const char *filename; + JSValue ret; + size_t buf_len; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + buf = js_load_file(ctx, &buf_len, filename); + JS_FreeCString(ctx, filename); + if (!buf) + return JS_NULL; + ret = JS_NewStringLen(ctx, (char *)buf, buf_len); + js_free(ctx, buf); + return ret; +} + +typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, + const char *module_name); + + +#if defined(_WIN32) +static JSModuleDef *js_module_loader_so(JSContext *ctx, + const char *module_name) +{ + JS_ThrowReferenceError(ctx, "shared library modules are not supported yet"); + return NULL; +} +#else +static JSModuleDef *js_module_loader_so(JSContext *ctx, + const char *module_name) +{ + JSModuleDef *m; + void *hd; + JSInitModuleFunc *init; + char *filename; + + if (!strchr(module_name, '/')) { + /* must add a '/' so that the DLL is not searched in the + system library paths */ + filename = js_malloc(ctx, strlen(module_name) + 2 + 1); + if (!filename) + return NULL; + strcpy(filename, "./"); + strcpy(filename + 2, module_name); + } else { + filename = (char *)module_name; + } + + /* C module */ + hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL); + if (filename != module_name) + js_free(ctx, filename); + if (!hd) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library", + module_name); + goto fail; + } + + init = dlsym(hd, "js_init_module"); + if (!init) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", + module_name); + goto fail; + } + + m = init(ctx, module_name); + if (!m) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", + module_name); + fail: + if (hd) + dlclose(hd); + return NULL; + } + return m; +} +#endif /* !_WIN32 */ + +int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, + JS_BOOL use_realpath, JS_BOOL is_main) +{ + JSModuleDef *m; + char buf[PATH_MAX + 16]; + JSValue meta_obj; + JSAtom module_name_atom; + const char *module_name; + + assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE); + m = JS_VALUE_GET_PTR(func_val); + + module_name_atom = JS_GetModuleName(ctx, m); + module_name = JS_AtomToCString(ctx, module_name_atom); + JS_FreeAtom(ctx, module_name_atom); + if (!module_name) + return -1; + if (!strchr(module_name, ':')) { + strcpy(buf, "file://"); +#if !defined(_WIN32) + /* realpath() cannot be used with modules compiled with qjsc + because the corresponding module source code is not + necessarily present */ + if (use_realpath) { + char *res = realpath(module_name, buf + strlen(buf)); + if (!res) { + JS_ThrowTypeError(ctx, "realpath failure"); + JS_FreeCString(ctx, module_name); + return -1; + } + } else +#endif + { + pstrcat(buf, sizeof(buf), module_name); + } + } else { + pstrcpy(buf, sizeof(buf), module_name); + } + JS_FreeCString(ctx, module_name); + + meta_obj = JS_GetImportMeta(ctx, m); + if (JS_IsException(meta_obj)) + return -1; + JS_DefinePropertyValueStr(ctx, meta_obj, "url", + JS_NewString(ctx, buf), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, meta_obj, "main", + JS_NewBool(ctx, is_main), + JS_PROP_C_W_E); + JS_FreeValue(ctx, meta_obj); + return 0; +} + +static int json_module_init(JSContext *ctx, JSModuleDef *m) +{ + JSValue val; + val = JS_GetModulePrivateValue(ctx, m); + JS_SetModuleExport(ctx, m, "default", val); + return 0; +} + +static JSModuleDef *create_json_module(JSContext *ctx, const char *module_name, JSValue val) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, json_module_init); + if (!m) { + JS_FreeValue(ctx, val); + return NULL; + } + /* only export the "default" symbol which will contain the JSON object */ + JS_AddModuleExport(ctx, m, "default"); + JS_SetModulePrivateValue(ctx, m, val); + return m; +} + +/* in order to conform with the specification, only the keys should be + tested and not the associated values. */ +int js_module_check_attributes(JSContext *ctx, void *opaque, + JSValueConst attributes) +{ + JSPropertyEnum *tab; + uint32_t i, len; + int ret; + const char *cstr; + size_t cstr_len; + + if (JS_GetOwnPropertyNames(ctx, &tab, &len, attributes, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK)) + return -1; + ret = 0; + for(i = 0; i < len; i++) { + cstr = JS_AtomToCStringLen(ctx, &cstr_len, tab[i].atom); + if (!cstr) { + ret = -1; + break; + } + if (!(cstr_len == 4 && !memcmp(cstr, "type", cstr_len))) { + JS_ThrowTypeError(ctx, "import attribute '%s' is not supported", cstr); + ret = -1; + } + JS_FreeCString(ctx, cstr); + if (ret) + break; + } + JS_FreePropertyEnum(ctx, tab, len); + return ret; +} + +/* return > 0 if the attributes indicate a JSON module */ +int js_module_test_json(JSContext *ctx, JSValueConst attributes) +{ + JSValue str; + const char *cstr; + size_t len; + BOOL res; + + if (JS_IsUndefined(attributes)) + return FALSE; + str = JS_GetPropertyStr(ctx, attributes, "type"); + if (!JS_IsString(str)) + return FALSE; + cstr = JS_ToCStringLen(ctx, &len, str); + JS_FreeValue(ctx, str); + if (!cstr) + return FALSE; + /* XXX: raise an error if unknown type ? */ + if (len == 4 && !memcmp(cstr, "json", len)) { + res = 1; + } else if (len == 5 && !memcmp(cstr, "json5", len)) { + res = 2; + } else { + res = 0; + } + JS_FreeCString(ctx, cstr); + return res; +} + +JSModuleDef *js_module_loader(JSContext *ctx, + const char *module_name, void *opaque, + JSValueConst attributes) +{ + JSModuleDef *m; + int res; + + if (has_suffix(module_name, ".so")) { + m = js_module_loader_so(ctx, module_name); + } else { + size_t buf_len; + uint8_t *buf; + + buf = js_load_file(ctx, &buf_len, module_name); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s'", + module_name); + return NULL; + } + res = js_module_test_json(ctx, attributes); + if (has_suffix(module_name, ".json") || res > 0) { + /* compile as JSON or JSON5 depending on "type" */ + JSValue val; + int flags; + if (res == 2) + flags = JS_PARSE_JSON_EXT; + else + flags = 0; + val = JS_ParseJSON2(ctx, (char *)buf, buf_len, module_name, flags); + js_free(ctx, buf); + if (JS_IsException(val)) + return NULL; + m = create_json_module(ctx, module_name, val); + if (!m) + return NULL; + } else { + JSValue func_val; + /* compile the module */ + func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + js_free(ctx, buf); + if (JS_IsException(func_val)) + return NULL; + /* XXX: could propagate the exception */ + js_module_set_import_meta(ctx, func_val, TRUE, FALSE); + /* the module is already referenced, so we must free it */ + m = JS_VALUE_GET_PTR(func_val); + JS_FreeValue(ctx, func_val); + } + } + return m; +} + +static JSValue js_std_exit(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int status; + if (JS_ToInt32(ctx, &status, argv[0])) + status = -1; + exit(status); + return JS_UNDEFINED; +} + +static JSValue js_std_getenv(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *name, *str; + name = JS_ToCString(ctx, argv[0]); + if (!name) + return JS_EXCEPTION; + str = getenv(name); + JS_FreeCString(ctx, name); + if (!str) + return JS_UNDEFINED; + else + return JS_NewString(ctx, str); +} + +#if defined(_WIN32) +static void setenv(const char *name, const char *value, int overwrite) +{ + char *str; + size_t name_len, value_len; + name_len = strlen(name); + value_len = strlen(value); + str = malloc(name_len + 1 + value_len + 1); + memcpy(str, name, name_len); + str[name_len] = '='; + memcpy(str + name_len + 1, value, value_len); + str[name_len + 1 + value_len] = '\0'; + _putenv(str); + free(str); +} + +static void unsetenv(const char *name) +{ + setenv(name, "", TRUE); +} +#endif /* _WIN32 */ + +static JSValue js_std_setenv(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *name, *value; + name = JS_ToCString(ctx, argv[0]); + if (!name) + return JS_EXCEPTION; + value = JS_ToCString(ctx, argv[1]); + if (!value) { + JS_FreeCString(ctx, name); + return JS_EXCEPTION; + } + setenv(name, value, TRUE); + JS_FreeCString(ctx, name); + JS_FreeCString(ctx, value); + return JS_UNDEFINED; +} + +static JSValue js_std_unsetenv(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *name; + name = JS_ToCString(ctx, argv[0]); + if (!name) + return JS_EXCEPTION; + unsetenv(name); + JS_FreeCString(ctx, name); + return JS_UNDEFINED; +} + +/* return an object containing the list of the available environment + variables. */ +static JSValue js_std_getenviron(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + char **envp; + const char *name, *p, *value; + JSValue obj; + uint32_t idx; + size_t name_len; + JSAtom atom; + int ret; + + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + envp = environ; + for(idx = 0; envp[idx] != NULL; idx++) { + name = envp[idx]; + p = strchr(name, '='); + name_len = p - name; + if (!p) + continue; + value = p + 1; + atom = JS_NewAtomLen(ctx, name, name_len); + if (atom == JS_ATOM_NULL) + goto fail; + ret = JS_DefinePropertyValue(ctx, obj, atom, JS_NewString(ctx, value), + JS_PROP_C_W_E); + JS_FreeAtom(ctx, atom); + if (ret < 0) + goto fail; + } + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_std_gc(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JS_RunGC(JS_GetRuntime(ctx)); + return JS_UNDEFINED; +} + +static int interrupt_handler(JSRuntime *rt, void *opaque) +{ + return (os_pending_signals >> SIGINT) & 1; +} + +static int get_bool_option(JSContext *ctx, BOOL *pbool, + JSValueConst obj, + const char *option) +{ + JSValue val; + val = JS_GetPropertyStr(ctx, obj, option); + if (JS_IsException(val)) + return -1; + if (!JS_IsUndefined(val)) { + *pbool = JS_ToBool(ctx, val); + } + JS_FreeValue(ctx, val); + return 0; +} + +static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + const char *str; + size_t len; + JSValue ret; + JSValueConst options_obj; + BOOL backtrace_barrier = FALSE; + BOOL is_async = FALSE; + int flags; + + if (argc >= 2) { + options_obj = argv[1]; + if (get_bool_option(ctx, &backtrace_barrier, options_obj, + "backtrace_barrier")) + return JS_EXCEPTION; + if (get_bool_option(ctx, &is_async, options_obj, + "async")) + return JS_EXCEPTION; + } + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) + return JS_EXCEPTION; + if (!ts->recv_pipe && ++ts->eval_script_recurse == 1) { + /* install the interrupt handler */ + JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); + } + flags = JS_EVAL_TYPE_GLOBAL; + if (backtrace_barrier) + flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; + if (is_async) + flags |= JS_EVAL_FLAG_ASYNC; + ret = JS_Eval(ctx, str, len, "", flags); + JS_FreeCString(ctx, str); + if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { + /* remove the interrupt handler */ + JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL); + os_pending_signals &= ~((uint64_t)1 << SIGINT); + /* convert the uncatchable "interrupted" error into a normal error + so that it can be caught by the REPL */ + if (JS_IsException(ret)) + JS_SetUncatchableException(ctx, FALSE); + } + return ret; +} + +static JSClassID js_std_file_class_id; + +typedef struct { + FILE *f; + BOOL close_in_finalizer; + BOOL is_popen; +} JSSTDFile; + +static void js_std_file_finalizer(JSRuntime *rt, JSValue val) +{ + JSSTDFile *s = JS_GetOpaque(val, js_std_file_class_id); + if (s) { + if (s->f && s->close_in_finalizer) { + if (s->is_popen) + pclose(s->f); + else + fclose(s->f); + } + js_free_rt(rt, s); + } +} + +static ssize_t js_get_errno(ssize_t ret) +{ + if (ret == -1) + ret = -errno; + return ret; +} + +static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int err; + if (JS_ToInt32(ctx, &err, argv[0])) + return JS_EXCEPTION; + return JS_NewString(ctx, strerror(err)); +} + +static JSValue js_std_parseExtJSON(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue obj; + const char *str; + size_t len; + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) + return JS_EXCEPTION; + obj = JS_ParseJSON2(ctx, str, len, "", JS_PARSE_JSON_EXT); + JS_FreeCString(ctx, str); + return obj; +} + +static JSValue js_new_std_file(JSContext *ctx, FILE *f, + BOOL close_in_finalizer, + BOOL is_popen) +{ + JSSTDFile *s; + JSValue obj; + obj = JS_NewObjectClass(ctx, js_std_file_class_id); + if (JS_IsException(obj)) + return obj; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + s->close_in_finalizer = close_in_finalizer; + s->is_popen = is_popen; + s->f = f; + JS_SetOpaque(obj, s); + return obj; +} + +static void js_set_error_object(JSContext *ctx, JSValue obj, int err) +{ + if (!JS_IsUndefined(obj)) { + JS_SetPropertyStr(ctx, obj, "errno", JS_NewInt32(ctx, err)); + } +} + +static JSValue js_std_open(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename, *mode = NULL; + FILE *f; + int err; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; + mode = JS_ToCString(ctx, argv[1]); + if (!mode) + goto fail; + if (mode[strspn(mode, "rwa+b")] != '\0') { + JS_ThrowTypeError(ctx, "invalid file mode"); + goto fail; + } + + f = fopen(filename, mode); + if (!f) + err = errno; + else + err = 0; + if (argc >= 3) + js_set_error_object(ctx, argv[2], err); + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, TRUE, FALSE); + fail: + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + return JS_EXCEPTION; +} + +static JSValue js_std_popen(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename, *mode = NULL; + FILE *f; + int err; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; + mode = JS_ToCString(ctx, argv[1]); + if (!mode) + goto fail; + if (mode[strspn(mode, "rw")] != '\0') { + JS_ThrowTypeError(ctx, "invalid file mode"); + goto fail; + } + + f = popen(filename, mode); + if (!f) + err = errno; + else + err = 0; + if (argc >= 3) + js_set_error_object(ctx, argv[2], err); + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, TRUE, TRUE); + fail: + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + return JS_EXCEPTION; +} + +static JSValue js_std_fdopen(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *mode; + FILE *f; + int fd, err; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + mode = JS_ToCString(ctx, argv[1]); + if (!mode) + goto fail; + if (mode[strspn(mode, "rwa+")] != '\0') { + JS_ThrowTypeError(ctx, "invalid file mode"); + goto fail; + } + + f = fdopen(fd, mode); + if (!f) + err = errno; + else + err = 0; + if (argc >= 3) + js_set_error_object(ctx, argv[2], err); + JS_FreeCString(ctx, mode); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, TRUE, FALSE); + fail: + JS_FreeCString(ctx, mode); + return JS_EXCEPTION; +} + +static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f; + f = tmpfile(); + if (argc >= 1) + js_set_error_object(ctx, argv[0], f ? 0 : errno); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, TRUE, FALSE); +} + +static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return js_printf_internal(ctx, argc, argv, NULL); +} + +static JSValue js_std_printf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return js_printf_internal(ctx, argc, argv, stdout); +} + +static FILE *js_std_file_get(JSContext *ctx, JSValueConst obj) +{ + JSSTDFile *s = JS_GetOpaque2(ctx, obj, js_std_file_class_id); + if (!s) + return NULL; + if (!s->f) { + JS_ThrowTypeError(ctx, "invalid file handle"); + return NULL; + } + return s->f; +} + +static JSValue js_std_file_puts(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + FILE *f; + int i; + const char *str; + size_t len; + + if (magic == 0) { + f = stdout; + } else { + f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + } + + for(i = 0; i < argc; i++) { + str = JS_ToCStringLen(ctx, &len, argv[i]); + if (!str) + return JS_EXCEPTION; + fwrite(str, 1, len, f); + JS_FreeCString(ctx, str); + } + return JS_UNDEFINED; +} + +static JSValue js_std_file_close(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSSTDFile *s = JS_GetOpaque2(ctx, this_val, js_std_file_class_id); + int err; + if (!s) + return JS_EXCEPTION; + if (!s->f) + return JS_ThrowTypeError(ctx, "invalid file handle"); + if (s->is_popen) + err = js_get_errno(pclose(s->f)); + else + err = js_get_errno(fclose(s->f)); + s->f = NULL; + return JS_NewInt32(ctx, err); +} + +static JSValue js_std_file_printf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return js_printf_internal(ctx, argc, argv, f); +} + +static void js_print_value_write(void *opaque, const char *buf, size_t len) +{ + FILE *fo = opaque; + fwrite(buf, 1, len, fo); +} + +static JSValue js_std_file_printObject(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JS_PrintValue(ctx, js_print_value_write, stdout, argv[0], NULL); + return JS_UNDEFINED; +} + +static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + fflush(f); + return JS_UNDEFINED; +} + +static JSValue js_std_file_tell(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_bigint) +{ + FILE *f = js_std_file_get(ctx, this_val); + int64_t pos; + if (!f) + return JS_EXCEPTION; +#if defined(__linux__) || defined(__GLIBC__) + pos = ftello(f); +#else + pos = ftell(f); +#endif + if (is_bigint) + return JS_NewBigInt64(ctx, pos); + else + return JS_NewInt64(ctx, pos); +} + +static JSValue js_std_file_seek(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int64_t pos; + int whence, ret; + if (!f) + return JS_EXCEPTION; + if (JS_ToInt64Ext(ctx, &pos, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &whence, argv[1])) + return JS_EXCEPTION; +#if defined(__linux__) || defined(__GLIBC__) + ret = fseeko(f, pos, whence); +#else + ret = fseek(f, pos, whence); +#endif + if (ret < 0) + ret = -errno; + return JS_NewInt32(ctx, ret); +} + +static JSValue js_std_file_eof(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewBool(ctx, feof(f)); +} + +static JSValue js_std_file_error(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewBool(ctx, ferror(f)); +} + +static JSValue js_std_file_clearerr(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + clearerr(f); + return JS_UNDEFINED; +} + +static JSValue js_std_file_fileno(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewInt32(ctx, fileno(f)); +} + +static JSValue js_std_file_read_write(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + FILE *f = js_std_file_get(ctx, this_val); + uint64_t pos, len; + size_t size, ret; + uint8_t *buf; + + if (!f) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &pos, argv[1])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &len, argv[2])) + return JS_EXCEPTION; + buf = JS_GetArrayBuffer(ctx, &size, argv[0]); + if (!buf) + return JS_EXCEPTION; + if (pos + len > size) + return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); + if (magic) + ret = fwrite(buf + pos, 1, len, f); + else + ret = fread(buf + pos, 1, len, f); + return JS_NewInt64(ctx, ret); +} + +/* XXX: could use less memory and go faster */ +static JSValue js_std_file_getline(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int c; + DynBuf dbuf; + JSValue obj; + + if (!f) + return JS_EXCEPTION; + + js_std_dbuf_init(ctx, &dbuf); + for(;;) { + c = fgetc(f); + if (c == EOF) { + if (dbuf.size == 0) { + /* EOF */ + dbuf_free(&dbuf); + return JS_NULL; + } else { + break; + } + } + if (c == '\n') + break; + if (dbuf_putc(&dbuf, c)) { + dbuf_free(&dbuf); + return JS_ThrowOutOfMemory(ctx); + } + } + obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); + dbuf_free(&dbuf); + return obj; +} + +/* XXX: could use less memory and go faster */ +static JSValue js_std_file_readAsString(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int c; + DynBuf dbuf; + JSValue obj; + uint64_t max_size64; + size_t max_size; + JSValueConst max_size_val; + + if (!f) + return JS_EXCEPTION; + + if (argc >= 1) + max_size_val = argv[0]; + else + max_size_val = JS_UNDEFINED; + max_size = (size_t)-1; + if (!JS_IsUndefined(max_size_val)) { + if (JS_ToIndex(ctx, &max_size64, max_size_val)) + return JS_EXCEPTION; + if (max_size64 < max_size) + max_size = max_size64; + } + + js_std_dbuf_init(ctx, &dbuf); + while (max_size != 0) { + c = fgetc(f); + if (c == EOF) + break; + if (dbuf_putc(&dbuf, c)) { + dbuf_free(&dbuf); + return JS_EXCEPTION; + } + max_size--; + } + obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); + dbuf_free(&dbuf); + return obj; +} + +static JSValue js_std_file_getByte(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewInt32(ctx, fgetc(f)); +} + +static JSValue js_std_file_putByte(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int c; + if (!f) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &c, argv[0])) + return JS_EXCEPTION; + c = fputc(c, f); + return JS_NewInt32(ctx, c); +} + +/* urlGet */ + +#define URL_GET_PROGRAM "curl -s -i --" +#define URL_GET_BUF_SIZE 4096 + +static int http_get_header_line(FILE *f, char *buf, size_t buf_size, + DynBuf *dbuf) +{ + int c; + char *p; + + p = buf; + for(;;) { + c = fgetc(f); + if (c < 0) + return -1; + if ((p - buf) < buf_size - 1) + *p++ = c; + if (dbuf) + dbuf_putc(dbuf, c); + if (c == '\n') + break; + } + *p = '\0'; + return 0; +} + +static int http_get_status(const char *buf) +{ + const char *p = buf; + while (*p != ' ' && *p != '\0') + p++; + if (*p != ' ') + return 0; + while (*p == ' ') + p++; + return atoi(p); +} + +static JSValue js_std_urlGet(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *url; + DynBuf cmd_buf; + DynBuf data_buf_s, *data_buf = &data_buf_s; + DynBuf header_buf_s, *header_buf = &header_buf_s; + char *buf; + size_t i, len; + int status; + JSValue response = JS_UNDEFINED, ret_obj; + JSValueConst options_obj; + FILE *f; + BOOL binary_flag, full_flag; + + url = JS_ToCString(ctx, argv[0]); + if (!url) + return JS_EXCEPTION; + + binary_flag = FALSE; + full_flag = FALSE; + + if (argc >= 2) { + options_obj = argv[1]; + + if (get_bool_option(ctx, &binary_flag, options_obj, "binary")) + goto fail_obj; + + if (get_bool_option(ctx, &full_flag, options_obj, "full")) { + fail_obj: + JS_FreeCString(ctx, url); + return JS_EXCEPTION; + } + } + + js_std_dbuf_init(ctx, &cmd_buf); + dbuf_printf(&cmd_buf, "%s '", URL_GET_PROGRAM); + for(i = 0; url[i] != '\0'; i++) { + unsigned char c = url[i]; + switch (c) { + case '\'': + /* shell single quoted string does not support \' */ + dbuf_putstr(&cmd_buf, "'\\''"); + break; + case '[': case ']': case '{': case '}': case '\\': + /* prevent interpretation by curl as range or set specification */ + dbuf_putc(&cmd_buf, '\\'); + /* FALLTHROUGH */ + default: + dbuf_putc(&cmd_buf, c); + break; + } + } + JS_FreeCString(ctx, url); + dbuf_putstr(&cmd_buf, "'"); + dbuf_putc(&cmd_buf, '\0'); + if (dbuf_error(&cmd_buf)) { + dbuf_free(&cmd_buf); + return JS_EXCEPTION; + } + // printf("%s\n", (char *)cmd_buf.buf); + f = popen((char *)cmd_buf.buf, "r"); + dbuf_free(&cmd_buf); + if (!f) { + return JS_ThrowTypeError(ctx, "could not start curl"); + } + + js_std_dbuf_init(ctx, data_buf); + js_std_dbuf_init(ctx, header_buf); + + buf = js_malloc(ctx, URL_GET_BUF_SIZE); + if (!buf) + goto fail; + + /* get the HTTP status */ + if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, NULL) < 0) { + status = 0; + goto bad_header; + } + status = http_get_status(buf); + if (!full_flag && !(status >= 200 && status <= 299)) { + goto bad_header; + } + + /* wait until there is an empty line */ + for(;;) { + if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, header_buf) < 0) { + bad_header: + response = JS_NULL; + goto done; + } + if (!strcmp(buf, "\r\n")) + break; + } + if (dbuf_error(header_buf)) + goto fail; + header_buf->size -= 2; /* remove the trailing CRLF */ + + /* download the data */ + for(;;) { + len = fread(buf, 1, URL_GET_BUF_SIZE, f); + if (len == 0) + break; + dbuf_put(data_buf, (uint8_t *)buf, len); + } + if (dbuf_error(data_buf)) + goto fail; + if (binary_flag) { + response = JS_NewArrayBufferCopy(ctx, + data_buf->buf, data_buf->size); + } else { + response = JS_NewStringLen(ctx, (char *)data_buf->buf, data_buf->size); + } + if (JS_IsException(response)) + goto fail; + done: + js_free(ctx, buf); + buf = NULL; + pclose(f); + f = NULL; + dbuf_free(data_buf); + data_buf = NULL; + + if (full_flag) { + ret_obj = JS_NewObject(ctx); + if (JS_IsException(ret_obj)) + goto fail; + JS_DefinePropertyValueStr(ctx, ret_obj, "response", + response, + JS_PROP_C_W_E); + if (!JS_IsNull(response)) { + JS_DefinePropertyValueStr(ctx, ret_obj, "responseHeaders", + JS_NewStringLen(ctx, (char *)header_buf->buf, + header_buf->size), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, ret_obj, "status", + JS_NewInt32(ctx, status), + JS_PROP_C_W_E); + } + } else { + ret_obj = response; + } + dbuf_free(header_buf); + return ret_obj; + fail: + if (f) + pclose(f); + js_free(ctx, buf); + if (data_buf) + dbuf_free(data_buf); + if (header_buf) + dbuf_free(header_buf); + JS_FreeValue(ctx, response); + return JS_EXCEPTION; +} + +static JSClassDef js_std_file_class = { + "FILE", + .finalizer = js_std_file_finalizer, +}; + +static const JSCFunctionListEntry js_std_error_props[] = { + /* various errno values */ +#define DEF(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) + DEF(EINVAL), + DEF(EIO), + DEF(EACCES), + DEF(EEXIST), + DEF(ENOSPC), + DEF(ENOSYS), + DEF(EBUSY), + DEF(ENOENT), + DEF(EPERM), + DEF(EPIPE), + DEF(EBADF), +#undef DEF +}; + +static const JSCFunctionListEntry js_std_funcs[] = { + JS_CFUNC_DEF("exit", 1, js_std_exit ), + JS_CFUNC_DEF("gc", 0, js_std_gc ), + JS_CFUNC_DEF("evalScript", 1, js_evalScript ), + JS_CFUNC_DEF("loadScript", 1, js_loadScript ), + JS_CFUNC_DEF("getenv", 1, js_std_getenv ), + JS_CFUNC_DEF("setenv", 1, js_std_setenv ), + JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), + JS_CFUNC_DEF("getenviron", 1, js_std_getenviron ), + JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), + JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), + JS_CFUNC_DEF("strerror", 1, js_std_strerror ), + JS_CFUNC_DEF("parseExtJSON", 1, js_std_parseExtJSON ), + + /* FILE I/O */ + JS_CFUNC_DEF("open", 2, js_std_open ), + JS_CFUNC_DEF("popen", 2, js_std_popen ), + JS_CFUNC_DEF("fdopen", 2, js_std_fdopen ), + JS_CFUNC_DEF("tmpfile", 0, js_std_tmpfile ), + JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 0 ), + JS_CFUNC_DEF("printf", 1, js_std_printf ), + JS_CFUNC_DEF("sprintf", 1, js_std_sprintf ), + JS_PROP_INT32_DEF("SEEK_SET", SEEK_SET, JS_PROP_CONFIGURABLE ), + JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ), + JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ), + JS_OBJECT_DEF("Error", js_std_error_props, countof(js_std_error_props), JS_PROP_CONFIGURABLE), + JS_CFUNC_DEF("__printObject", 1, js_std_file_printObject ), +}; + +static const JSCFunctionListEntry js_std_file_proto_funcs[] = { + JS_CFUNC_DEF("close", 0, js_std_file_close ), + JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 1 ), + JS_CFUNC_DEF("printf", 1, js_std_file_printf ), + JS_CFUNC_DEF("flush", 0, js_std_file_flush ), + JS_CFUNC_MAGIC_DEF("tell", 0, js_std_file_tell, 0 ), + JS_CFUNC_MAGIC_DEF("tello", 0, js_std_file_tell, 1 ), + JS_CFUNC_DEF("seek", 2, js_std_file_seek ), + JS_CFUNC_DEF("eof", 0, js_std_file_eof ), + JS_CFUNC_DEF("fileno", 0, js_std_file_fileno ), + JS_CFUNC_DEF("error", 0, js_std_file_error ), + JS_CFUNC_DEF("clearerr", 0, js_std_file_clearerr ), + JS_CFUNC_MAGIC_DEF("read", 3, js_std_file_read_write, 0 ), + JS_CFUNC_MAGIC_DEF("write", 3, js_std_file_read_write, 1 ), + JS_CFUNC_DEF("getline", 0, js_std_file_getline ), + JS_CFUNC_DEF("readAsString", 0, js_std_file_readAsString ), + JS_CFUNC_DEF("getByte", 0, js_std_file_getByte ), + JS_CFUNC_DEF("putByte", 1, js_std_file_putByte ), + /* setvbuf, ... */ +}; + +static int js_std_init(JSContext *ctx, JSModuleDef *m) +{ + JSValue proto; + + /* FILE class */ + /* the class ID is created once */ + JS_NewClassID(&js_std_file_class_id); + /* the class is created once per runtime */ + JS_NewClass(JS_GetRuntime(ctx), js_std_file_class_id, &js_std_file_class); + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_std_file_proto_funcs, + countof(js_std_file_proto_funcs)); + JS_SetClassProto(ctx, js_std_file_class_id, proto); + + JS_SetModuleExportList(ctx, m, js_std_funcs, + countof(js_std_funcs)); + JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, FALSE, FALSE)); + JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, FALSE, FALSE)); + JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, FALSE, FALSE)); + return 0; +} + +JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_std_init); + if (!m) + return NULL; + JS_AddModuleExportList(ctx, m, js_std_funcs, countof(js_std_funcs)); + JS_AddModuleExport(ctx, m, "in"); + JS_AddModuleExport(ctx, m, "out"); + JS_AddModuleExport(ctx, m, "err"); + return m; +} + +/**********************************************************/ +/* 'os' object */ + +static JSValue js_os_open(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename; + int flags, mode, ret; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &flags, argv[1])) + goto fail; + if (argc >= 3 && !JS_IsUndefined(argv[2])) { + if (JS_ToInt32(ctx, &mode, argv[2])) { + fail: + JS_FreeCString(ctx, filename); + return JS_EXCEPTION; + } + } else { + mode = 0666; + } +#if defined(_WIN32) + /* force binary mode by default */ + if (!(flags & O_TEXT)) + flags |= O_BINARY; +#endif + ret = js_get_errno(open(filename, flags, mode)); + JS_FreeCString(ctx, filename); + return JS_NewInt32(ctx, ret); +} + +static JSValue js_os_close(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, ret; + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + ret = js_get_errno(close(fd)); + return JS_NewInt32(ctx, ret); +} + +static JSValue js_os_seek(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, whence; + int64_t pos, ret; + BOOL is_bigint; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + is_bigint = JS_IsBigInt(ctx, argv[1]); + if (JS_ToInt64Ext(ctx, &pos, argv[1])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &whence, argv[2])) + return JS_EXCEPTION; + ret = lseek(fd, pos, whence); + if (ret == -1) + ret = -errno; + if (is_bigint) + return JS_NewBigInt64(ctx, ret); + else + return JS_NewInt64(ctx, ret); +} + +static JSValue js_os_read_write(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + int fd; + uint64_t pos, len; + size_t size; + ssize_t ret; + uint8_t *buf; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &pos, argv[2])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &len, argv[3])) + return JS_EXCEPTION; + buf = JS_GetArrayBuffer(ctx, &size, argv[1]); + if (!buf) + return JS_EXCEPTION; + if (pos + len > size) + return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); + if (magic) + ret = js_get_errno(write(fd, buf + pos, len)); + else + ret = js_get_errno(read(fd, buf + pos, len)); + return JS_NewInt64(ctx, ret); +} + +static JSValue js_os_isatty(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + return JS_NewBool(ctx, isatty(fd)); +} + +#if defined(_WIN32) +static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + HANDLE handle; + CONSOLE_SCREEN_BUFFER_INFO info; + JSValue obj; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + handle = (HANDLE)_get_osfhandle(fd); + + if (!GetConsoleScreenBufferInfo(handle, &info)) + return JS_NULL; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, info.dwSize.X), JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, info.dwSize.Y), JS_PROP_C_W_E); + return obj; +} + +/* Windows 10 built-in VT100 emulation */ +#define __ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#define __ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 + +static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + HANDLE handle; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + handle = (HANDLE)_get_osfhandle(fd); + SetConsoleMode(handle, ENABLE_WINDOW_INPUT | __ENABLE_VIRTUAL_TERMINAL_INPUT); + _setmode(fd, _O_BINARY); + if (fd == 0) { + handle = (HANDLE)_get_osfhandle(1); /* corresponding output */ + SetConsoleMode(handle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | __ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + return JS_UNDEFINED; +} +#else +static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + struct winsize ws; + JSValue obj; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + if (ioctl(fd, TIOCGWINSZ, &ws) == 0 && + ws.ws_col >= 4 && ws.ws_row >= 4) { + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ws.ws_col), JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, ws.ws_row), JS_PROP_C_W_E); + return obj; + } else { + return JS_NULL; + } +} + +static struct termios oldtty; + +static void term_exit(void) +{ + tcsetattr(0, TCSANOW, &oldtty); +} + +/* XXX: should add a way to go back to normal mode */ +static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + struct termios tty; + int fd; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + + memset(&tty, 0, sizeof(tty)); + tcgetattr(fd, &tty); + oldtty = tty; + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + + tcsetattr(fd, TCSANOW, &tty); + + atexit(term_exit); + return JS_UNDEFINED; +} + +#endif /* !_WIN32 */ + +static JSValue js_os_remove(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename; + int ret; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; +#if defined(_WIN32) + { + struct stat st; + if (stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) { + ret = rmdir(filename); + } else { + ret = unlink(filename); + } + } +#else + ret = remove(filename); +#endif + ret = js_get_errno(ret); + JS_FreeCString(ctx, filename); + return JS_NewInt32(ctx, ret); +} + +static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *oldpath, *newpath; + int ret; + + oldpath = JS_ToCString(ctx, argv[0]); + if (!oldpath) + return JS_EXCEPTION; + newpath = JS_ToCString(ctx, argv[1]); + if (!newpath) { + JS_FreeCString(ctx, oldpath); + return JS_EXCEPTION; + } + ret = js_get_errno(rename(oldpath, newpath)); + JS_FreeCString(ctx, oldpath); + JS_FreeCString(ctx, newpath); + return JS_NewInt32(ctx, ret); +} + +static BOOL is_main_thread(JSRuntime *rt) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + return !ts->recv_pipe; +} + +static JSOSRWHandler *find_rh(JSThreadState *ts, int fd) +{ + JSOSRWHandler *rh; + struct list_head *el; + + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (rh->fd == fd) + return rh; + } + return NULL; +} + +static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh) +{ + int i; + list_del(&rh->link); + for(i = 0; i < 2; i++) { + JS_FreeValueRT(rt, rh->rw_func[i]); + } + js_free_rt(rt, rh); +} + +static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSOSRWHandler *rh; + int fd; + JSValueConst func; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + func = argv[1]; + if (JS_IsNull(func)) { + rh = find_rh(ts, fd); + if (rh) { + JS_FreeValue(ctx, rh->rw_func[magic]); + rh->rw_func[magic] = JS_NULL; + if (JS_IsNull(rh->rw_func[0]) && + JS_IsNull(rh->rw_func[1])) { + /* remove the entry */ + free_rw_handler(JS_GetRuntime(ctx), rh); + } + } + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + rh = find_rh(ts, fd); + if (!rh) { + rh = js_mallocz(ctx, sizeof(*rh)); + if (!rh) + return JS_EXCEPTION; + rh->fd = fd; + rh->rw_func[0] = JS_NULL; + rh->rw_func[1] = JS_NULL; + list_add_tail(&rh->link, &ts->os_rw_handlers); + } + JS_FreeValue(ctx, rh->rw_func[magic]); + rh->rw_func[magic] = JS_DupValue(ctx, func); + } + return JS_UNDEFINED; +} + +static JSOSSignalHandler *find_sh(JSThreadState *ts, int sig_num) +{ + JSOSSignalHandler *sh; + struct list_head *el; + list_for_each(el, &ts->os_signal_handlers) { + sh = list_entry(el, JSOSSignalHandler, link); + if (sh->sig_num == sig_num) + return sh; + } + return NULL; +} + +static void free_sh(JSRuntime *rt, JSOSSignalHandler *sh) +{ + list_del(&sh->link); + JS_FreeValueRT(rt, sh->func); + js_free_rt(rt, sh); +} + +static void os_signal_handler(int sig_num) +{ + os_pending_signals |= ((uint64_t)1 << sig_num); +} + +#if defined(_WIN32) +typedef void (*sighandler_t)(int sig_num); +#endif + +static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSOSSignalHandler *sh; + uint32_t sig_num; + JSValueConst func; + sighandler_t handler; + + if (!is_main_thread(rt)) + return JS_ThrowTypeError(ctx, "signal handler can only be set in the main thread"); + + if (JS_ToUint32(ctx, &sig_num, argv[0])) + return JS_EXCEPTION; + if (sig_num >= 64) + return JS_ThrowRangeError(ctx, "invalid signal number"); + func = argv[1]; + /* func = null: SIG_DFL, func = undefined, SIG_IGN */ + if (JS_IsNull(func) || JS_IsUndefined(func)) { + sh = find_sh(ts, sig_num); + if (sh) { + free_sh(JS_GetRuntime(ctx), sh); + } + if (JS_IsNull(func)) + handler = SIG_DFL; + else + handler = SIG_IGN; + signal(sig_num, handler); + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + sh = find_sh(ts, sig_num); + if (!sh) { + sh = js_mallocz(ctx, sizeof(*sh)); + if (!sh) + return JS_EXCEPTION; + sh->sig_num = sig_num; + list_add_tail(&sh->link, &ts->os_signal_handlers); + } + JS_FreeValue(ctx, sh->func); + sh->func = JS_DupValue(ctx, func); + signal(sig_num, os_signal_handler); + } + return JS_UNDEFINED; +} + +#if defined(__linux__) || defined(__APPLE__) +static int64_t get_time_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); +} + +static int64_t get_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec; +} +#else +/* more portable, but does not work if the date is updated */ +static int64_t get_time_ms(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); +} + +static int64_t get_time_ns(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000000000 + (tv.tv_usec * 1000); +} +#endif + +static JSValue js_os_now(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_NewFloat64(ctx, (double)get_time_ns() / 1e6); +} + +static void free_timer(JSRuntime *rt, JSOSTimer *th) +{ + list_del(&th->link); + JS_FreeValueRT(rt, th->func); + js_free_rt(rt, th); +} + +static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + int64_t delay; + JSValueConst func; + JSOSTimer *th; + + func = argv[0]; + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + if (JS_ToInt64(ctx, &delay, argv[1])) + return JS_EXCEPTION; + th = js_mallocz(ctx, sizeof(*th)); + if (!th) + return JS_EXCEPTION; + th->timer_id = ts->next_timer_id; + if (ts->next_timer_id == INT32_MAX) + ts->next_timer_id = 1; + else + ts->next_timer_id++; + th->timeout = get_time_ms() + delay; + th->func = JS_DupValue(ctx, func); + list_add_tail(&th->link, &ts->os_timers); + return JS_NewInt32(ctx, th->timer_id); +} + +static JSOSTimer *find_timer_by_id(JSThreadState *ts, int timer_id) +{ + struct list_head *el; + if (timer_id <= 0) + return NULL; + list_for_each(el, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + if (th->timer_id == timer_id) + return th; + } + return NULL; +} + +static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSOSTimer *th; + int timer_id; + + if (JS_ToInt32(ctx, &timer_id, argv[0])) + return JS_EXCEPTION; + th = find_timer_by_id(ts, timer_id); + if (!th) + return JS_UNDEFINED; + free_timer(rt, th); + return JS_UNDEFINED; +} + +/* return a promise */ +static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + int64_t delay; + JSOSTimer *th; + JSValue promise, resolving_funcs[2]; + + if (JS_ToInt64(ctx, &delay, argv[0])) + return JS_EXCEPTION; + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + + th = js_mallocz(ctx, sizeof(*th)); + if (!th) { + JS_FreeValue(ctx, promise); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return JS_EXCEPTION; + } + th->timer_id = -1; + th->timeout = get_time_ms() + delay; + th->func = JS_DupValue(ctx, resolving_funcs[0]); + list_add_tail(&th->link, &ts->os_timers); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; +} + +static void call_handler(JSContext *ctx, JSValueConst func) +{ + JSValue ret, func1; + /* 'func' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + func1 = JS_DupValue(ctx, func); + ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL); + JS_FreeValue(ctx, func1); + if (JS_IsException(ret)) + js_std_dump_error(ctx); + JS_FreeValue(ctx, ret); +} + +#ifdef USE_WORKER + +#ifdef _WIN32 + +static int js_waker_init(JSWaker *w) +{ + w->handle = CreateEvent(NULL, TRUE, FALSE, NULL); + return w->handle ? 0 : -1; +} + +static void js_waker_signal(JSWaker *w) +{ + SetEvent(w->handle); +} + +static void js_waker_clear(JSWaker *w) +{ + ResetEvent(w->handle); +} + +static void js_waker_close(JSWaker *w) +{ + CloseHandle(w->handle); + w->handle = INVALID_HANDLE_VALUE; +} + +#else // !_WIN32 + +static int js_waker_init(JSWaker *w) +{ + int fds[2]; + + if (pipe(fds) < 0) + return -1; + w->read_fd = fds[0]; + w->write_fd = fds[1]; + return 0; +} + +static void js_waker_signal(JSWaker *w) +{ + int ret; + + for(;;) { + ret = write(w->write_fd, "", 1); + if (ret == 1) + break; + if (ret < 0 && (errno != EAGAIN || errno != EINTR)) + break; + } +} + +static void js_waker_clear(JSWaker *w) +{ + uint8_t buf[16]; + int ret; + + for(;;) { + ret = read(w->read_fd, buf, sizeof(buf)); + if (ret >= 0) + break; + if (errno != EAGAIN && errno != EINTR) + break; + } +} + +static void js_waker_close(JSWaker *w) +{ + close(w->read_fd); + close(w->write_fd); + w->read_fd = -1; + w->write_fd = -1; +} + +#endif // _WIN32 + +static void js_free_message(JSWorkerMessage *msg); + +/* return 1 if a message was handled, 0 if no message */ +static int handle_posted_message(JSRuntime *rt, JSContext *ctx, + JSWorkerMessageHandler *port) +{ + JSWorkerMessagePipe *ps = port->recv_pipe; + int ret; + struct list_head *el; + JSWorkerMessage *msg; + JSValue obj, data_obj, func, retval; + + pthread_mutex_lock(&ps->mutex); + if (!list_empty(&ps->msg_queue)) { + el = ps->msg_queue.next; + msg = list_entry(el, JSWorkerMessage, link); + + /* remove the message from the queue */ + list_del(&msg->link); + + if (list_empty(&ps->msg_queue)) + js_waker_clear(&ps->waker); + + pthread_mutex_unlock(&ps->mutex); + + data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, + JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); + + js_free_message(msg); + + if (JS_IsException(data_obj)) + goto fail; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, data_obj); + goto fail; + } + JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); + + /* 'func' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + func = JS_DupValue(ctx, port->on_message_func); + retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj); + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, func); + if (JS_IsException(retval)) { + fail: + js_std_dump_error(ctx); + } else { + JS_FreeValue(ctx, retval); + } + ret = 1; + } else { + pthread_mutex_unlock(&ps->mutex); + ret = 0; + } + return ret; +} +#else +static int handle_posted_message(JSRuntime *rt, JSContext *ctx, + JSWorkerMessageHandler *port) +{ + return 0; +} +#endif /* !USE_WORKER */ + +#if defined(_WIN32) + +static int js_os_poll(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + int min_delay, count; + int64_t cur_time, delay; + JSOSRWHandler *rh; + struct list_head *el; + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; // 64 + + /* XXX: handle signals if useful */ + + if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && + list_empty(&ts->port_list)) { + return -1; /* no more events */ + } + + if (!list_empty(&ts->os_timers)) { + cur_time = get_time_ms(); + min_delay = 10000; + list_for_each(el, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + delay = th->timeout - cur_time; + if (delay <= 0) { + JSValue func; + /* the timer expired */ + func = th->func; + th->func = JS_UNDEFINED; + free_timer(rt, th); + call_handler(ctx, func); + JS_FreeValue(ctx, func); + return 0; + } else if (delay < min_delay) { + min_delay = delay; + } + } + } else { + min_delay = -1; + } + + count = 0; + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { + handles[count++] = (HANDLE)_get_osfhandle(rh->fd); // stdin + if (count == (int)countof(handles)) + break; + } + } + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (JS_IsNull(port->on_message_func)) + continue; + handles[count++] = port->recv_pipe->waker.handle; + if (count == (int)countof(handles)) + break; + } + + if (count > 0) { + DWORD ret, timeout = INFINITE; + if (min_delay != -1) + timeout = min_delay; + ret = WaitForMultipleObjects(count, handles, FALSE, timeout); + + if (ret < count) { + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { + call_handler(ctx, rh->rw_func[0]); + /* must stop because the list may have been modified */ + goto done; + } + } + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + if (ps->waker.handle == handles[ret]) { + if (handle_posted_message(rt, ctx, port)) + goto done; + } + } + } + } + } else { + Sleep(min_delay); + } + done: + return 0; +} + +#else + +static int js_os_poll(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + int ret, fd_max, min_delay; + int64_t cur_time, delay; + fd_set rfds, wfds; + JSOSRWHandler *rh; + struct list_head *el; + struct timeval tv, *tvp; + + /* only check signals in the main thread */ + if (!ts->recv_pipe && + unlikely(os_pending_signals != 0)) { + JSOSSignalHandler *sh; + uint64_t mask; + + list_for_each(el, &ts->os_signal_handlers) { + sh = list_entry(el, JSOSSignalHandler, link); + mask = (uint64_t)1 << sh->sig_num; + if (os_pending_signals & mask) { + os_pending_signals &= ~mask; + call_handler(ctx, sh->func); + return 0; + } + } + } + + if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && + list_empty(&ts->port_list)) + return -1; /* no more events */ + + if (!list_empty(&ts->os_timers)) { + cur_time = get_time_ms(); + min_delay = 10000; + list_for_each(el, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + delay = th->timeout - cur_time; + if (delay <= 0) { + JSValue func; + /* the timer expired */ + func = th->func; + th->func = JS_UNDEFINED; + free_timer(rt, th); + call_handler(ctx, func); + JS_FreeValue(ctx, func); + return 0; + } else if (delay < min_delay) { + min_delay = delay; + } + } + tv.tv_sec = min_delay / 1000; + tv.tv_usec = (min_delay % 1000) * 1000; + tvp = &tv; + } else { + tvp = NULL; + } + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + fd_max = -1; + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + fd_max = max_int(fd_max, rh->fd); + if (!JS_IsNull(rh->rw_func[0])) + FD_SET(rh->fd, &rfds); + if (!JS_IsNull(rh->rw_func[1])) + FD_SET(rh->fd, &wfds); + } + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + fd_max = max_int(fd_max, ps->waker.read_fd); + FD_SET(ps->waker.read_fd, &rfds); + } + } + + ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); + if (ret > 0) { + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (!JS_IsNull(rh->rw_func[0]) && + FD_ISSET(rh->fd, &rfds)) { + call_handler(ctx, rh->rw_func[0]); + /* must stop because the list may have been modified */ + goto done; + } + if (!JS_IsNull(rh->rw_func[1]) && + FD_ISSET(rh->fd, &wfds)) { + call_handler(ctx, rh->rw_func[1]); + /* must stop because the list may have been modified */ + goto done; + } + } + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + if (FD_ISSET(ps->waker.read_fd, &rfds)) { + if (handle_posted_message(rt, ctx, port)) + goto done; + } + } + } + } + done: + return 0; +} +#endif /* !_WIN32 */ + +static JSValue make_obj_error(JSContext *ctx, + JSValue obj, + int err) +{ + JSValue arr; + if (JS_IsException(obj)) + return obj; + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + return JS_EXCEPTION; + JS_DefinePropertyValueUint32(ctx, arr, 0, obj, + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, arr, 1, JS_NewInt32(ctx, err), + JS_PROP_C_W_E); + return arr; +} + +static JSValue make_string_error(JSContext *ctx, + const char *buf, + int err) +{ + return make_obj_error(ctx, JS_NewString(ctx, buf), err); +} + +/* return [cwd, errorcode] */ +static JSValue js_os_getcwd(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + char buf[PATH_MAX]; + int err; + + if (!getcwd(buf, sizeof(buf))) { + buf[0] = '\0'; + err = errno; + } else { + err = 0; + } + return make_string_error(ctx, buf, err); +} + +static JSValue js_os_chdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *target; + int err; + + target = JS_ToCString(ctx, argv[0]); + if (!target) + return JS_EXCEPTION; + err = js_get_errno(chdir(target)); + JS_FreeCString(ctx, target); + return JS_NewInt32(ctx, err); +} + +static JSValue js_os_mkdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int mode, ret; + const char *path; + + if (argc >= 2) { + if (JS_ToInt32(ctx, &mode, argv[1])) + return JS_EXCEPTION; + } else { + mode = 0777; + } + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + (void)mode; + ret = js_get_errno(mkdir(path)); +#else + ret = js_get_errno(mkdir(path, mode)); +#endif + JS_FreeCString(ctx, path); + return JS_NewInt32(ctx, ret); +} + +/* return [array, errorcode] */ +static JSValue js_os_readdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + DIR *f; + struct dirent *d; + JSValue obj; + int err; + uint32_t len; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) { + JS_FreeCString(ctx, path); + return JS_EXCEPTION; + } + f = opendir(path); + if (!f) + err = errno; + else + err = 0; + JS_FreeCString(ctx, path); + if (!f) + goto done; + len = 0; + for(;;) { + errno = 0; + d = readdir(f); + if (!d) { + err = errno; + break; + } + JS_DefinePropertyValueUint32(ctx, obj, len++, + JS_NewString(ctx, d->d_name), + JS_PROP_C_W_E); + } + closedir(f); + done: + return make_obj_error(ctx, obj, err); +} + +#if !defined(_WIN32) +static int64_t timespec_to_ms(const struct timespec *tv) +{ + return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000); +} +#endif + +/* return [obj, errcode] */ +static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_lstat) +{ + const char *path; + int err, res; + struct stat st; + JSValue obj; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + res = stat(path, &st); +#else + if (is_lstat) + res = lstat(path, &st); + else + res = stat(path, &st); +#endif + if (res < 0) + err = errno; + else + err = 0; + JS_FreeCString(ctx, path); + if (res < 0) { + obj = JS_NULL; + } else { + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JS_DefinePropertyValueStr(ctx, obj, "dev", + JS_NewInt64(ctx, st.st_dev), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ino", + JS_NewInt64(ctx, st.st_ino), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mode", + JS_NewInt32(ctx, st.st_mode), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "nlink", + JS_NewInt64(ctx, st.st_nlink), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "uid", + JS_NewInt64(ctx, st.st_uid), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "gid", + JS_NewInt64(ctx, st.st_gid), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "rdev", + JS_NewInt64(ctx, st.st_rdev), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "size", + JS_NewInt64(ctx, st.st_size), + JS_PROP_C_W_E); +#if !defined(_WIN32) + JS_DefinePropertyValueStr(ctx, obj, "blocks", + JS_NewInt64(ctx, st.st_blocks), + JS_PROP_C_W_E); +#endif +#if defined(_WIN32) + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, (int64_t)st.st_atime * 1000), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, (int64_t)st.st_mtime * 1000), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, (int64_t)st.st_ctime * 1000), + JS_PROP_C_W_E); +#elif defined(__APPLE__) + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_atimespec)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_mtimespec)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_ctimespec)), + JS_PROP_C_W_E); +#else + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_atim)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_mtim)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_ctim)), + JS_PROP_C_W_E); +#endif + } + return make_obj_error(ctx, obj, err); +} + +#if !defined(_WIN32) +static void ms_to_timeval(struct timeval *tv, uint64_t v) +{ + tv->tv_sec = v / 1000; + tv->tv_usec = (v % 1000) * 1000; +} +#endif + +static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + int64_t atime, mtime; + int ret; + + if (JS_ToInt64(ctx, &atime, argv[1])) + return JS_EXCEPTION; + if (JS_ToInt64(ctx, &mtime, argv[2])) + return JS_EXCEPTION; + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + { + struct _utimbuf times; + times.actime = atime / 1000; + times.modtime = mtime / 1000; + ret = js_get_errno(_utime(path, ×)); + } +#else + { + struct timeval times[2]; + ms_to_timeval(×[0], atime); + ms_to_timeval(×[1], mtime); + ret = js_get_errno(utimes(path, times)); + } +#endif + JS_FreeCString(ctx, path); + return JS_NewInt32(ctx, ret); +} + +/* sleep(delay_ms) */ +static JSValue js_os_sleep(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int64_t delay; + int ret; + + if (JS_ToInt64(ctx, &delay, argv[0])) + return JS_EXCEPTION; + if (delay < 0) + delay = 0; +#if defined(_WIN32) + { + if (delay > INT32_MAX) + delay = INT32_MAX; + Sleep(delay); + ret = 0; + } +#else + { + struct timespec ts; + + ts.tv_sec = delay / 1000; + ts.tv_nsec = (delay % 1000) * 1000000; + ret = js_get_errno(nanosleep(&ts, NULL)); + } +#endif + return JS_NewInt32(ctx, ret); +} + +#if defined(_WIN32) +static char *realpath(const char *path, char *buf) +{ + if (!_fullpath(buf, path, PATH_MAX)) { + errno = ENOENT; + return NULL; + } else { + return buf; + } +} +#endif + +/* return [path, errorcode] */ +static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + char buf[PATH_MAX], *res; + int err; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + res = realpath(path, buf); + JS_FreeCString(ctx, path); + if (!res) { + buf[0] = '\0'; + err = errno; + } else { + err = 0; + } + return make_string_error(ctx, buf, err); +} + +#if !defined(_WIN32) +static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *target, *linkpath; + int err; + + target = JS_ToCString(ctx, argv[0]); + if (!target) + return JS_EXCEPTION; + linkpath = JS_ToCString(ctx, argv[1]); + if (!linkpath) { + JS_FreeCString(ctx, target); + return JS_EXCEPTION; + } + err = js_get_errno(symlink(target, linkpath)); + JS_FreeCString(ctx, target); + JS_FreeCString(ctx, linkpath); + return JS_NewInt32(ctx, err); +} + +/* return [path, errorcode] */ +static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + char buf[PATH_MAX]; + int err; + ssize_t res; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + res = readlink(path, buf, sizeof(buf) - 1); + if (res < 0) { + buf[0] = '\0'; + err = errno; + } else { + buf[res] = '\0'; + err = 0; + } + JS_FreeCString(ctx, path); + return make_string_error(ctx, buf, err); +} + +static char **build_envp(JSContext *ctx, JSValueConst obj) +{ + uint32_t len, i; + JSPropertyEnum *tab; + char **envp, *pair; + const char *key, *str; + JSValue val; + size_t key_len, str_len; + + if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) + return NULL; + envp = js_mallocz(ctx, sizeof(envp[0]) * ((size_t)len + 1)); + if (!envp) + goto fail; + for(i = 0; i < len; i++) { + val = JS_GetProperty(ctx, obj, tab[i].atom); + if (JS_IsException(val)) + goto fail; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + goto fail; + key = JS_AtomToCString(ctx, tab[i].atom); + if (!key) { + JS_FreeCString(ctx, str); + goto fail; + } + key_len = strlen(key); + str_len = strlen(str); + pair = js_malloc(ctx, key_len + str_len + 2); + if (!pair) { + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + goto fail; + } + memcpy(pair, key, key_len); + pair[key_len] = '='; + memcpy(pair + key_len + 1, str, str_len); + pair[key_len + 1 + str_len] = '\0'; + envp[i] = pair; + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + } + done: + JS_FreePropertyEnum(ctx, tab, len); + return envp; + fail: + if (envp) { + for(i = 0; i < len; i++) + js_free(ctx, envp[i]); + js_free(ctx, envp); + envp = NULL; + } + goto done; +} + +/* execvpe is not available on non GNU systems */ +static int my_execvpe(const char *filename, char **argv, char **envp) +{ + char *path, *p, *p_next, *p1; + char buf[PATH_MAX]; + size_t filename_len, path_len; + BOOL eacces_error; + + filename_len = strlen(filename); + if (filename_len == 0) { + errno = ENOENT; + return -1; + } + if (strchr(filename, '/')) + return execve(filename, argv, envp); + + path = getenv("PATH"); + if (!path) + path = (char *)"/bin:/usr/bin"; + eacces_error = FALSE; + p = path; + for(p = path; p != NULL; p = p_next) { + p1 = strchr(p, ':'); + if (!p1) { + p_next = NULL; + path_len = strlen(p); + } else { + p_next = p1 + 1; + path_len = p1 - p; + } + /* path too long */ + if ((path_len + 1 + filename_len + 1) > PATH_MAX) + continue; + memcpy(buf, p, path_len); + buf[path_len] = '/'; + memcpy(buf + path_len + 1, filename, filename_len); + buf[path_len + 1 + filename_len] = '\0'; + + execve(buf, argv, envp); + + switch(errno) { + case EACCES: + eacces_error = TRUE; + break; + case ENOENT: + case ENOTDIR: + break; + default: + return -1; + } + } + if (eacces_error) + errno = EACCES; + return -1; +} + +/* exec(args[, options]) -> exitcode */ +static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst options, args = argv[0]; + JSValue val, ret_val; + const char **exec_argv, *file = NULL, *str, *cwd = NULL; + char **envp = environ; + uint32_t exec_argc, i; + int ret, pid, status; + BOOL block_flag = TRUE, use_path = TRUE; + static const char *std_name[3] = { "stdin", "stdout", "stderr" }; + int std_fds[3]; + uint32_t uid = -1, gid = -1; + + val = JS_GetPropertyStr(ctx, args, "length"); + if (JS_IsException(val)) + return JS_EXCEPTION; + ret = JS_ToUint32(ctx, &exec_argc, val); + JS_FreeValue(ctx, val); + if (ret) + return JS_EXCEPTION; + /* arbitrary limit to avoid overflow */ + if (exec_argc < 1 || exec_argc > 65535) { + return JS_ThrowTypeError(ctx, "invalid number of arguments"); + } + exec_argv = js_mallocz(ctx, sizeof(exec_argv[0]) * (exec_argc + 1)); + if (!exec_argv) + return JS_EXCEPTION; + for(i = 0; i < exec_argc; i++) { + val = JS_GetPropertyUint32(ctx, args, i); + if (JS_IsException(val)) + goto exception; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + goto exception; + exec_argv[i] = str; + } + exec_argv[exec_argc] = NULL; + + for(i = 0; i < 3; i++) + std_fds[i] = i; + + /* get the options, if any */ + if (argc >= 2) { + options = argv[1]; + + if (get_bool_option(ctx, &block_flag, options, "block")) + goto exception; + if (get_bool_option(ctx, &use_path, options, "usePath")) + goto exception; + + val = JS_GetPropertyStr(ctx, options, "file"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + file = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!file) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "cwd"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + cwd = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!cwd) + goto exception; + } + + /* stdin/stdout/stderr handles */ + for(i = 0; i < 3; i++) { + val = JS_GetPropertyStr(ctx, options, std_name[i]); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + int fd; + ret = JS_ToInt32(ctx, &fd, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + std_fds[i] = fd; + } + } + + val = JS_GetPropertyStr(ctx, options, "env"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + envp = build_envp(ctx, val); + JS_FreeValue(ctx, val); + if (!envp) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "uid"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + ret = JS_ToUint32(ctx, &uid, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "gid"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + ret = JS_ToUint32(ctx, &gid, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + } + } + + pid = fork(); + if (pid < 0) { + JS_ThrowTypeError(ctx, "fork error"); + goto exception; + } + if (pid == 0) { + /* child */ + + /* remap the stdin/stdout/stderr handles if necessary */ + for(i = 0; i < 3; i++) { + if (std_fds[i] != i) { + if (dup2(std_fds[i], i) < 0) + _exit(127); + } + } +#if defined(HAVE_CLOSEFROM) + /* closefrom() is available on many recent unix systems: + Linux with glibc 2.34+, Solaris 9+, FreeBSD 7.3+, + NetBSD 3.0+, OpenBSD 3.5+. + Linux with the musl libc and macOS don't have it. + */ + + closefrom(3); +#else + { + /* Close the file handles manually, limit to 1024 to avoid + costly loop on linux Alpine where sysconf(_SC_OPEN_MAX) + returns a huge value 1048576. + Patch inspired by nicolas-duteil-nova. See also: + https://stackoverflow.com/questions/73229353/ + https://stackoverflow.com/questions/899038/#918469 + */ + int fd_max = min_int(sysconf(_SC_OPEN_MAX), 1024); + for(i = 3; i < fd_max; i++) + close(i); + } +#endif + if (cwd) { + if (chdir(cwd) < 0) + _exit(127); + } + if (uid != -1) { + if (setuid(uid) < 0) + _exit(127); + } + if (gid != -1) { + if (setgid(gid) < 0) + _exit(127); + } + + if (!file) + file = exec_argv[0]; + if (use_path) + ret = my_execvpe(file, (char **)exec_argv, envp); + else + ret = execve(file, (char **)exec_argv, envp); + _exit(127); + } + /* parent */ + if (block_flag) { + for(;;) { + ret = waitpid(pid, &status, 0); + if (ret == pid) { + if (WIFEXITED(status)) { + ret = WEXITSTATUS(status); + break; + } else if (WIFSIGNALED(status)) { + ret = -WTERMSIG(status); + break; + } + } + } + } else { + ret = pid; + } + ret_val = JS_NewInt32(ctx, ret); + done: + JS_FreeCString(ctx, file); + JS_FreeCString(ctx, cwd); + for(i = 0; i < exec_argc; i++) + JS_FreeCString(ctx, exec_argv[i]); + js_free(ctx, exec_argv); + if (envp != environ) { + char **p; + p = envp; + while (*p != NULL) { + js_free(ctx, *p); + p++; + } + js_free(ctx, envp); + } + return ret_val; + exception: + ret_val = JS_EXCEPTION; + goto done; +} + +/* getpid() -> pid */ +static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewInt32(ctx, getpid()); +} + +/* waitpid(pid, block) -> [pid, status] */ +static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid, status, options, ret; + JSValue obj; + + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &options, argv[1])) + return JS_EXCEPTION; + + ret = waitpid(pid, &status, options); + if (ret < 0) { + ret = -errno; + status = 0; + } + + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ret), + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, status), + JS_PROP_C_W_E); + return obj; +} + +/* pipe() -> [read_fd, write_fd] or null if error */ +static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pipe_fds[2], ret; + JSValue obj; + + ret = pipe(pipe_fds); + if (ret < 0) + return JS_NULL; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), + JS_PROP_C_W_E); + return obj; +} + +/* kill(pid, sig) */ +static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid, sig, ret; + + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &sig, argv[1])) + return JS_EXCEPTION; + ret = js_get_errno(kill(pid, sig)); + return JS_NewInt32(ctx, ret); +} + +/* dup(fd) */ +static JSValue js_os_dup(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, ret; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + ret = js_get_errno(dup(fd)); + return JS_NewInt32(ctx, ret); +} + +/* dup2(fd) */ +static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, fd2, ret; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &fd2, argv[1])) + return JS_EXCEPTION; + ret = js_get_errno(dup2(fd, fd2)); + return JS_NewInt32(ctx, ret); +} + +#endif /* !_WIN32 */ + +#ifdef USE_WORKER + +/* Worker */ + +typedef struct { + JSWorkerMessagePipe *recv_pipe; + JSWorkerMessagePipe *send_pipe; + JSWorkerMessageHandler *msg_handler; +} JSWorkerData; + +typedef struct { + char *filename; /* module filename */ + char *basename; /* module base name */ + JSWorkerMessagePipe *recv_pipe, *send_pipe; + int strip_flags; +} WorkerFuncArgs; + +typedef struct { + int ref_count; + uint64_t buf[0]; +} JSSABHeader; + +static JSClassID js_worker_class_id; +static JSContext *(*js_worker_new_context_func)(JSRuntime *rt); + +static int atomic_add_int(int *ptr, int v) +{ + return atomic_fetch_add((_Atomic(uint32_t) *)ptr, v) + v; +} + +/* shared array buffer allocator */ +static void *js_sab_alloc(void *opaque, size_t size) +{ + JSSABHeader *sab; + sab = malloc(sizeof(JSSABHeader) + size); + if (!sab) + return NULL; + sab->ref_count = 1; + return sab->buf; +} + +static void js_sab_free(void *opaque, void *ptr) +{ + JSSABHeader *sab; + int ref_count; + sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); + ref_count = atomic_add_int(&sab->ref_count, -1); + assert(ref_count >= 0); + if (ref_count == 0) { + free(sab); + } +} + +static void js_sab_dup(void *opaque, void *ptr) +{ + JSSABHeader *sab; + sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); + atomic_add_int(&sab->ref_count, 1); +} + +static JSWorkerMessagePipe *js_new_message_pipe(void) +{ + JSWorkerMessagePipe *ps; + + ps = malloc(sizeof(*ps)); + if (!ps) + return NULL; + if (js_waker_init(&ps->waker)) { + free(ps); + return NULL; + } + ps->ref_count = 1; + init_list_head(&ps->msg_queue); + pthread_mutex_init(&ps->mutex, NULL); + return ps; +} + +static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps) +{ + atomic_add_int(&ps->ref_count, 1); + return ps; +} + +static void js_free_message(JSWorkerMessage *msg) +{ + size_t i; + /* free the SAB */ + for(i = 0; i < msg->sab_tab_len; i++) { + js_sab_free(NULL, msg->sab_tab[i]); + } + free(msg->sab_tab); + free(msg->data); + free(msg); +} + +static void js_free_message_pipe(JSWorkerMessagePipe *ps) +{ + struct list_head *el, *el1; + JSWorkerMessage *msg; + int ref_count; + + if (!ps) + return; + + ref_count = atomic_add_int(&ps->ref_count, -1); + assert(ref_count >= 0); + if (ref_count == 0) { + list_for_each_safe(el, el1, &ps->msg_queue) { + msg = list_entry(el, JSWorkerMessage, link); + js_free_message(msg); + } + pthread_mutex_destroy(&ps->mutex); + js_waker_close(&ps->waker); + free(ps); + } +} + +static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port) +{ + if (port) { + js_free_message_pipe(port->recv_pipe); + JS_FreeValueRT(rt, port->on_message_func); + if (port->link.prev) + list_del(&port->link); + js_free_rt(rt, port); + } +} + +static void js_worker_finalizer(JSRuntime *rt, JSValue val) +{ + JSWorkerData *worker = JS_GetOpaque(val, js_worker_class_id); + if (worker) { + js_free_message_pipe(worker->recv_pipe); + js_free_message_pipe(worker->send_pipe); + js_free_port(rt, worker->msg_handler); + js_free_rt(rt, worker); + } +} + +static void js_worker_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSWorkerData *worker = JS_GetOpaque(val, js_worker_class_id); + if (worker) { + JSWorkerMessageHandler *port = worker->msg_handler; + if (port) { + JS_MarkValue(rt, port->on_message_func, mark_func); + } + } +} + +static JSClassDef js_worker_class = { + "Worker", + .finalizer = js_worker_finalizer, + .gc_mark = js_worker_mark, +}; + +static void *worker_func(void *opaque) +{ + WorkerFuncArgs *args = opaque; + JSRuntime *rt; + JSThreadState *ts; + JSContext *ctx; + JSValue val; + + rt = JS_NewRuntime(); + if (rt == NULL) { + fprintf(stderr, "JS_NewRuntime failure"); + exit(1); + } + JS_SetStripInfo(rt, args->strip_flags); + js_std_init_handlers(rt); + + JS_SetModuleLoaderFunc2(rt, NULL, js_module_loader, js_module_check_attributes, NULL); + + /* set the pipe to communicate with the parent */ + ts = JS_GetRuntimeOpaque(rt); + ts->recv_pipe = args->recv_pipe; + ts->send_pipe = args->send_pipe; + + /* function pointer to avoid linking the whole JS_NewContext() if + not needed */ + ctx = js_worker_new_context_func(rt); + if (ctx == NULL) { + fprintf(stderr, "JS_NewContext failure"); + } + + JS_SetCanBlock(rt, TRUE); + + js_std_add_helpers(ctx, -1, NULL); + + val = JS_LoadModule(ctx, args->basename, args->filename); + free(args->filename); + free(args->basename); + free(args); + val = js_std_await(ctx, val); + if (JS_IsException(val)) + js_std_dump_error(ctx); + JS_FreeValue(ctx, val); + + js_std_loop(ctx); + + JS_FreeContext(ctx); + js_std_free_handlers(rt); + JS_FreeRuntime(rt); + return NULL; +} + +static JSValue js_worker_ctor_internal(JSContext *ctx, JSValueConst new_target, + JSWorkerMessagePipe *recv_pipe, + JSWorkerMessagePipe *send_pipe) +{ + JSValue obj = JS_UNDEFINED, proto; + JSWorkerData *s; + + /* create the object */ + if (JS_IsUndefined(new_target)) { + proto = JS_GetClassProto(ctx, js_worker_class_id); + } else { + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) + goto fail; + } + obj = JS_NewObjectProtoClass(ctx, proto, js_worker_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) + goto fail; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + goto fail; + s->recv_pipe = js_dup_message_pipe(recv_pipe); + s->send_pipe = js_dup_message_pipe(send_pipe); + + JS_SetOpaque(obj, s); + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + WorkerFuncArgs *args = NULL; + pthread_t tid; + pthread_attr_t attr; + JSValue obj = JS_UNDEFINED; + int ret; + const char *filename = NULL, *basename; + JSAtom basename_atom; + + /* XXX: in order to avoid problems with resource liberation, we + don't support creating workers inside workers */ + if (!is_main_thread(rt)) + return JS_ThrowTypeError(ctx, "cannot create a worker inside a worker"); + + /* base name, assuming the calling function is a normal JS + function */ + basename_atom = JS_GetScriptOrModuleName(ctx, 1); + if (basename_atom == JS_ATOM_NULL) { + return JS_ThrowTypeError(ctx, "could not determine calling script or module name"); + } + basename = JS_AtomToCString(ctx, basename_atom); + JS_FreeAtom(ctx, basename_atom); + if (!basename) + goto fail; + + /* module name */ + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; + + args = malloc(sizeof(*args)); + if (!args) + goto oom_fail; + memset(args, 0, sizeof(*args)); + args->filename = strdup(filename); + args->basename = strdup(basename); + + /* ports */ + args->recv_pipe = js_new_message_pipe(); + if (!args->recv_pipe) + goto oom_fail; + args->send_pipe = js_new_message_pipe(); + if (!args->send_pipe) + goto oom_fail; + + args->strip_flags = JS_GetStripInfo(rt); + + obj = js_worker_ctor_internal(ctx, new_target, + args->send_pipe, args->recv_pipe); + if (JS_IsException(obj)) + goto fail; + + pthread_attr_init(&attr); + /* no join at the end */ + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ret = pthread_create(&tid, &attr, worker_func, args); + pthread_attr_destroy(&attr); + if (ret != 0) { + JS_ThrowTypeError(ctx, "could not create worker"); + goto fail; + } + JS_FreeCString(ctx, basename); + JS_FreeCString(ctx, filename); + return obj; + oom_fail: + JS_ThrowOutOfMemory(ctx); + fail: + JS_FreeCString(ctx, basename); + JS_FreeCString(ctx, filename); + if (args) { + free(args->filename); + free(args->basename); + js_free_message_pipe(args->recv_pipe); + js_free_message_pipe(args->send_pipe); + free(args); + } + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); + JSWorkerMessagePipe *ps; + size_t data_len, sab_tab_len, i; + uint8_t *data; + JSWorkerMessage *msg; + uint8_t **sab_tab; + + if (!worker) + return JS_EXCEPTION; + + data = JS_WriteObject2(ctx, &data_len, argv[0], + JS_WRITE_OBJ_SAB | JS_WRITE_OBJ_REFERENCE, + &sab_tab, &sab_tab_len); + if (!data) + return JS_EXCEPTION; + + msg = malloc(sizeof(*msg)); + if (!msg) + goto fail; + msg->data = NULL; + msg->sab_tab = NULL; + + /* must reallocate because the allocator may be different */ + msg->data = malloc(data_len); + if (!msg->data) + goto fail; + memcpy(msg->data, data, data_len); + msg->data_len = data_len; + + if (sab_tab_len > 0) { + msg->sab_tab = malloc(sizeof(msg->sab_tab[0]) * sab_tab_len); + if (!msg->sab_tab) + goto fail; + memcpy(msg->sab_tab, sab_tab, sizeof(msg->sab_tab[0]) * sab_tab_len); + } + msg->sab_tab_len = sab_tab_len; + + js_free(ctx, data); + js_free(ctx, sab_tab); + + /* increment the SAB reference counts */ + for(i = 0; i < msg->sab_tab_len; i++) { + js_sab_dup(NULL, msg->sab_tab[i]); + } + + ps = worker->send_pipe; + pthread_mutex_lock(&ps->mutex); + /* indicate that data is present */ + if (list_empty(&ps->msg_queue)) + js_waker_signal(&ps->waker); + list_add_tail(&msg->link, &ps->msg_queue); + pthread_mutex_unlock(&ps->mutex); + return JS_UNDEFINED; + fail: + if (msg) { + free(msg->data); + free(msg->sab_tab); + free(msg); + } + js_free(ctx, data); + js_free(ctx, sab_tab); + return JS_EXCEPTION; + +} + +static JSValue js_worker_set_onmessage(JSContext *ctx, JSValueConst this_val, + JSValueConst func) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); + JSWorkerMessageHandler *port; + + if (!worker) + return JS_EXCEPTION; + + port = worker->msg_handler; + if (JS_IsNull(func)) { + if (port) { + js_free_port(rt, port); + worker->msg_handler = NULL; + } + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + if (!port) { + port = js_mallocz(ctx, sizeof(*port)); + if (!port) + return JS_EXCEPTION; + port->recv_pipe = js_dup_message_pipe(worker->recv_pipe); + port->on_message_func = JS_NULL; + list_add_tail(&port->link, &ts->port_list); + worker->msg_handler = port; + } + JS_FreeValue(ctx, port->on_message_func); + port->on_message_func = JS_DupValue(ctx, func); + } + return JS_UNDEFINED; +} + +static JSValue js_worker_get_onmessage(JSContext *ctx, JSValueConst this_val) +{ + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); + JSWorkerMessageHandler *port; + if (!worker) + return JS_EXCEPTION; + port = worker->msg_handler; + if (port) { + return JS_DupValue(ctx, port->on_message_func); + } else { + return JS_NULL; + } +} + +static const JSCFunctionListEntry js_worker_proto_funcs[] = { + JS_CFUNC_DEF("postMessage", 1, js_worker_postMessage ), + JS_CGETSET_DEF("onmessage", js_worker_get_onmessage, js_worker_set_onmessage ), +}; + +#endif /* USE_WORKER */ + +void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) +{ +#ifdef USE_WORKER + js_worker_new_context_func = func; +#endif +} + +#if defined(_WIN32) +#define OS_PLATFORM "win32" +#elif defined(__APPLE__) +#define OS_PLATFORM "darwin" +#elif defined(EMSCRIPTEN) +#define OS_PLATFORM "js" +#else +#define OS_PLATFORM "linux" +#endif + +#define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) + +static const JSCFunctionListEntry js_os_funcs[] = { + JS_CFUNC_DEF("open", 2, js_os_open ), + OS_FLAG(O_RDONLY), + OS_FLAG(O_WRONLY), + OS_FLAG(O_RDWR), + OS_FLAG(O_APPEND), + OS_FLAG(O_CREAT), + OS_FLAG(O_EXCL), + OS_FLAG(O_TRUNC), +#if defined(_WIN32) + OS_FLAG(O_BINARY), + OS_FLAG(O_TEXT), +#endif + JS_CFUNC_DEF("close", 1, js_os_close ), + JS_CFUNC_DEF("seek", 3, js_os_seek ), + JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ), + JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ), + JS_CFUNC_DEF("isatty", 1, js_os_isatty ), + JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ), + JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ), + JS_CFUNC_DEF("remove", 1, js_os_remove ), + JS_CFUNC_DEF("rename", 2, js_os_rename ), + JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ), + JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ), + JS_CFUNC_DEF("signal", 2, js_os_signal ), + OS_FLAG(SIGINT), + OS_FLAG(SIGABRT), + OS_FLAG(SIGFPE), + OS_FLAG(SIGILL), + OS_FLAG(SIGSEGV), + OS_FLAG(SIGTERM), +#if !defined(_WIN32) + OS_FLAG(SIGQUIT), + OS_FLAG(SIGPIPE), + OS_FLAG(SIGALRM), + OS_FLAG(SIGUSR1), + OS_FLAG(SIGUSR2), + OS_FLAG(SIGCHLD), + OS_FLAG(SIGCONT), + OS_FLAG(SIGSTOP), + OS_FLAG(SIGTSTP), + OS_FLAG(SIGTTIN), + OS_FLAG(SIGTTOU), +#endif + JS_CFUNC_DEF("now", 0, js_os_now ), + JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ), + JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), + JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ), + JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), + JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), + JS_CFUNC_DEF("chdir", 0, js_os_chdir ), + JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), + JS_CFUNC_DEF("readdir", 1, js_os_readdir ), + /* st_mode constants */ + OS_FLAG(S_IFMT), + OS_FLAG(S_IFIFO), + OS_FLAG(S_IFCHR), + OS_FLAG(S_IFDIR), + OS_FLAG(S_IFBLK), + OS_FLAG(S_IFREG), +#if !defined(_WIN32) + OS_FLAG(S_IFSOCK), + OS_FLAG(S_IFLNK), + OS_FLAG(S_ISGID), + OS_FLAG(S_ISUID), +#endif + JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), + JS_CFUNC_DEF("utimes", 3, js_os_utimes ), + JS_CFUNC_DEF("sleep", 1, js_os_sleep ), + JS_CFUNC_DEF("realpath", 1, js_os_realpath ), +#if !defined(_WIN32) + JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), + JS_CFUNC_DEF("symlink", 2, js_os_symlink ), + JS_CFUNC_DEF("readlink", 1, js_os_readlink ), + JS_CFUNC_DEF("exec", 1, js_os_exec ), + JS_CFUNC_DEF("getpid", 0, js_os_getpid ), + JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), + OS_FLAG(WNOHANG), + JS_CFUNC_DEF("pipe", 0, js_os_pipe ), + JS_CFUNC_DEF("kill", 2, js_os_kill ), + JS_CFUNC_DEF("dup", 1, js_os_dup ), + JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), +#endif +}; + +static int js_os_init(JSContext *ctx, JSModuleDef *m) +{ + os_poll_func = js_os_poll; + +#ifdef USE_WORKER + { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSValue proto, obj; + /* Worker class */ + JS_NewClassID(&js_worker_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_worker_class_id, &js_worker_class); + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_worker_proto_funcs, countof(js_worker_proto_funcs)); + + obj = JS_NewCFunction2(ctx, js_worker_ctor, "Worker", 1, + JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, obj, proto); + + JS_SetClassProto(ctx, js_worker_class_id, proto); + + /* set 'Worker.parent' if necessary */ + if (ts->recv_pipe && ts->send_pipe) { + JS_DefinePropertyValueStr(ctx, obj, "parent", + js_worker_ctor_internal(ctx, JS_UNDEFINED, ts->recv_pipe, ts->send_pipe), + JS_PROP_C_W_E); + } + + JS_SetModuleExport(ctx, m, "Worker", obj); + } +#endif /* USE_WORKER */ + + return JS_SetModuleExportList(ctx, m, js_os_funcs, + countof(js_os_funcs)); +} + +JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_os_init); + if (!m) + return NULL; + JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); +#ifdef USE_WORKER + JS_AddModuleExport(ctx, m, "Worker"); +#endif + return m; +} + +/**********************************************************/ + +static JSValue js_print(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int i; + JSValueConst v; + + for(i = 0; i < argc; i++) { + if (i != 0) + putchar(' '); + v = argv[i]; + if (JS_IsString(v)) { + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v); + if (!str) + return JS_EXCEPTION; + fwrite(str, 1, len, stdout); + JS_FreeCString(ctx, str); + } else { + JS_PrintValue(ctx, js_print_value_write, stdout, v, NULL); + } + } + putchar('\n'); + return JS_UNDEFINED; +} + +static JSValue js_console_log(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue ret; + ret = js_print(ctx, this_val, argc, argv); + fflush(stdout); + return ret; +} + +void js_std_add_helpers(JSContext *ctx, int argc, char **argv) +{ + JSValue global_obj, console, args, performance; + int i; + + /* XXX: should these global definitions be enumerable? */ + global_obj = JS_GetGlobalObject(ctx); + + console = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, console, "log", + JS_NewCFunction(ctx, js_console_log, "log", 1)); + JS_SetPropertyStr(ctx, global_obj, "console", console); + + performance = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, performance, "now", + JS_NewCFunction(ctx, js_os_now, "now", 0)); + JS_SetPropertyStr(ctx, global_obj, "performance", performance); + + /* same methods as the mozilla JS shell */ + if (argc >= 0) { + args = JS_NewArray(ctx); + for(i = 0; i < argc; i++) { + JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i])); + } + JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args); + } + + JS_SetPropertyStr(ctx, global_obj, "print", + JS_NewCFunction(ctx, js_print, "print", 1)); + JS_SetPropertyStr(ctx, global_obj, "__loadScript", + JS_NewCFunction(ctx, js_loadScript, "__loadScript", 1)); + + JS_FreeValue(ctx, global_obj); +} + +void js_std_init_handlers(JSRuntime *rt) +{ + JSThreadState *ts; + + ts = malloc(sizeof(*ts)); + if (!ts) { + fprintf(stderr, "Could not allocate memory for the worker"); + exit(1); + } + memset(ts, 0, sizeof(*ts)); + init_list_head(&ts->os_rw_handlers); + init_list_head(&ts->os_signal_handlers); + init_list_head(&ts->os_timers); + init_list_head(&ts->port_list); + init_list_head(&ts->rejected_promise_list); + ts->next_timer_id = 1; + + JS_SetRuntimeOpaque(rt, ts); + +#ifdef USE_WORKER + /* set the SharedArrayBuffer memory handlers */ + { + JSSharedArrayBufferFunctions sf; + memset(&sf, 0, sizeof(sf)); + sf.sab_alloc = js_sab_alloc; + sf.sab_free = js_sab_free; + sf.sab_dup = js_sab_dup; + JS_SetSharedArrayBufferFunctions(rt, &sf); + } +#endif +} + +void js_std_free_handlers(JSRuntime *rt) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + struct list_head *el, *el1; + + list_for_each_safe(el, el1, &ts->os_rw_handlers) { + JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); + free_rw_handler(rt, rh); + } + + list_for_each_safe(el, el1, &ts->os_signal_handlers) { + JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link); + free_sh(rt, sh); + } + + list_for_each_safe(el, el1, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + free_timer(rt, th); + } + + list_for_each_safe(el, el1, &ts->rejected_promise_list) { + JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); + JS_FreeValueRT(rt, rp->promise); + JS_FreeValueRT(rt, rp->reason); + free(rp); + } + +#ifdef USE_WORKER + js_free_message_pipe(ts->recv_pipe); + js_free_message_pipe(ts->send_pipe); + + list_for_each_safe(el, el1, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + /* unlink the message ports. They are freed by the Worker object */ + port->link.prev = NULL; + port->link.next = NULL; + } +#endif + + free(ts); + JS_SetRuntimeOpaque(rt, NULL); /* fail safe */ +} + +static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) +{ + JS_PrintValue(ctx, js_print_value_write, stderr, exception_val, NULL); + fputc('\n', stderr); +} + +void js_std_dump_error(JSContext *ctx) +{ + JSValue exception_val; + + exception_val = JS_GetException(ctx); + js_std_dump_error1(ctx, exception_val); + JS_FreeValue(ctx, exception_val); +} + +static JSRejectedPromiseEntry *find_rejected_promise(JSContext *ctx, JSThreadState *ts, + JSValueConst promise) +{ + struct list_head *el; + + list_for_each(el, &ts->rejected_promise_list) { + JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); + if (JS_SameValue(ctx, rp->promise, promise)) + return rp; + } + return NULL; +} + +void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, + JSValueConst reason, + BOOL is_handled, void *opaque) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSRejectedPromiseEntry *rp; + + if (!is_handled) { + /* add a new entry if needed */ + rp = find_rejected_promise(ctx, ts, promise); + if (!rp) { + rp = malloc(sizeof(*rp)); + if (rp) { + rp->promise = JS_DupValue(ctx, promise); + rp->reason = JS_DupValue(ctx, reason); + list_add_tail(&rp->link, &ts->rejected_promise_list); + } + } + } else { + /* the rejection is handled, so the entry can be removed if present */ + rp = find_rejected_promise(ctx, ts, promise); + if (rp) { + JS_FreeValue(ctx, rp->promise); + JS_FreeValue(ctx, rp->reason); + list_del(&rp->link); + free(rp); + } + } +} + +/* check if there are pending promise rejections. It must be done + asynchrously in case a rejected promise is handled later. Currently + we do it once the application is about to sleep. It could be done + more often if needed. */ +static void js_std_promise_rejection_check(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + struct list_head *el; + + if (unlikely(!list_empty(&ts->rejected_promise_list))) { + list_for_each(el, &ts->rejected_promise_list) { + JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); + fprintf(stderr, "Possibly unhandled promise rejection: "); + js_std_dump_error1(ctx, rp->reason); + } + exit(1); + } +} + +/* main loop which calls the user JS callbacks */ +void js_std_loop(JSContext *ctx) +{ + int err; + + for(;;) { + /* execute the pending jobs */ + for(;;) { + err = JS_ExecutePendingJob(JS_GetRuntime(ctx), NULL); + if (err <= 0) { + if (err < 0) + js_std_dump_error(ctx); + break; + } + } + + js_std_promise_rejection_check(ctx); + + if (!os_poll_func || os_poll_func(ctx)) + break; + } +} + +/* Wait for a promise and execute pending jobs while waiting for + it. Return the promise result or JS_EXCEPTION in case of promise + rejection. */ +JSValue js_std_await(JSContext *ctx, JSValue obj) +{ + JSValue ret; + int state; + + for(;;) { + state = JS_PromiseState(ctx, obj); + if (state == JS_PROMISE_FULFILLED) { + ret = JS_PromiseResult(ctx, obj); + JS_FreeValue(ctx, obj); + break; + } else if (state == JS_PROMISE_REJECTED) { + ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj)); + JS_FreeValue(ctx, obj); + break; + } else if (state == JS_PROMISE_PENDING) { + int err; + err = JS_ExecutePendingJob(JS_GetRuntime(ctx), NULL); + if (err < 0) { + js_std_dump_error(ctx); + } + if (err == 0) { + js_std_promise_rejection_check(ctx); + + if (os_poll_func) + os_poll_func(ctx); + } + } else { + /* not a promise */ + ret = obj; + break; + } + } + return ret; +} + +void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, + int load_only) +{ + JSValue obj, val; + obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE); + if (JS_IsException(obj)) + goto exception; + if (load_only) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { + js_module_set_import_meta(ctx, obj, FALSE, FALSE); + } + JS_FreeValue(ctx, obj); + } else { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { + if (JS_ResolveModule(ctx, obj) < 0) { + JS_FreeValue(ctx, obj); + goto exception; + } + js_module_set_import_meta(ctx, obj, FALSE, TRUE); + val = JS_EvalFunction(ctx, obj); + val = js_std_await(ctx, val); + } else { + val = JS_EvalFunction(ctx, obj); + } + if (JS_IsException(val)) { + exception: + js_std_dump_error(ctx); + exit(1); + } + JS_FreeValue(ctx, val); + } +} + +void js_std_eval_binary_json_module(JSContext *ctx, + const uint8_t *buf, size_t buf_len, + const char *module_name) +{ + JSValue obj; + JSModuleDef *m; + + obj = JS_ReadObject(ctx, buf, buf_len, 0); + if (JS_IsException(obj)) + goto exception; + m = create_json_module(ctx, module_name, obj); + if (!m) { + exception: + js_std_dump_error(ctx); + exit(1); + } +} + diff --git a/NativeScript/napi/android/quickjs/source/quickjs-libc.h b/NativeScript/napi/android/quickjs/source/quickjs-libc.h new file mode 100755 index 000000000..5c8301b71 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/quickjs-libc.h @@ -0,0 +1,66 @@ +/* + * QuickJS C library + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef QUICKJS_LIBC_H +#define QUICKJS_LIBC_H + +#include +#include + +#include "quickjs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name); +JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); +void js_std_add_helpers(JSContext *ctx, int argc, char **argv); +void js_std_loop(JSContext *ctx); +JSValue js_std_await(JSContext *ctx, JSValue obj); +void js_std_init_handlers(JSRuntime *rt); +void js_std_free_handlers(JSRuntime *rt); +void js_std_dump_error(JSContext *ctx); +uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename); +int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, + JS_BOOL use_realpath, JS_BOOL is_main); +int js_module_test_json(JSContext *ctx, JSValueConst attributes); +int js_module_check_attributes(JSContext *ctx, void *opaque, JSValueConst attributes); +JSModuleDef *js_module_loader(JSContext *ctx, + const char *module_name, void *opaque, + JSValueConst attributes); +void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, + int flags); +void js_std_eval_binary_json_module(JSContext *ctx, + const uint8_t *buf, size_t buf_len, + const char *module_name); +void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, + JSValueConst reason, + JS_BOOL is_handled, void *opaque); +void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* QUICKJS_LIBC_H */ diff --git a/NativeScript/napi/android/quickjs/source/quickjs-opcode.h b/NativeScript/napi/android/quickjs/source/quickjs-opcode.h new file mode 100755 index 000000000..d93852133 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/quickjs-opcode.h @@ -0,0 +1,365 @@ +/* + * QuickJS opcode definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FMT +FMT(none) +FMT(none_int) +FMT(none_loc) +FMT(none_arg) +FMT(none_var_ref) +FMT(u8) +FMT(i8) +FMT(loc8) +FMT(const8) +FMT(label8) +FMT(u16) +FMT(i16) +FMT(label16) +FMT(npop) +FMT(npopx) +FMT(npop_u16) +FMT(loc) +FMT(arg) +FMT(var_ref) +FMT(u32) +FMT(i32) +FMT(const) +FMT(label) +FMT(atom) +FMT(atom_u8) +FMT(atom_u16) +FMT(atom_label_u8) +FMT(atom_label_u16) +FMT(label_u16) +#undef FMT +#endif /* FMT */ + +#ifdef DEF + +#ifndef def +#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f) +#endif + +DEF(invalid, 1, 0, 0, none) /* never emitted */ + +/* push values */ +DEF( push_i32, 5, 0, 1, i32) +DEF( push_const, 5, 0, 1, const) +DEF( fclosure, 5, 0, 1, const) /* must follow push_const */ +DEF(push_atom_value, 5, 0, 1, atom) +DEF( private_symbol, 5, 0, 1, atom) +DEF( undefined, 1, 0, 1, none) +DEF( null, 1, 0, 1, none) +DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */ +DEF( push_false, 1, 0, 1, none) +DEF( push_true, 1, 0, 1, none) +DEF( object, 1, 0, 1, none) +DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */ +DEF( rest, 3, 0, 1, u16) /* only used at the start of a function */ + +DEF( drop, 1, 1, 0, none) /* a -> */ +DEF( nip, 1, 2, 1, none) /* a b -> b */ +DEF( nip1, 1, 3, 2, none) /* a b c -> b c */ +DEF( dup, 1, 1, 2, none) /* a -> a a */ +DEF( dup1, 1, 2, 3, none) /* a b -> a a b */ +DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */ +DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */ +DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */ +DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */ +DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */ +DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */ +DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */ +DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */ +DEF( swap, 1, 2, 2, none) /* a b -> b a */ +DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */ +DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */ +DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */ +DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */ +DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */ + +DEF(call_constructor, 3, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */ +DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */ +DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */ +DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */ +DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */ +DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ +DEF( apply, 3, 3, 1, u16) +DEF( return, 1, 1, 0, none) +DEF( return_undef, 1, 0, 0, none) +DEF(check_ctor_return, 1, 1, 2, none) +DEF( check_ctor, 1, 0, 0, none) +DEF( init_ctor, 1, 0, 1, none) +DEF( check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */ +DEF( add_brand, 1, 2, 0, none) /* this_obj home_obj -> */ +DEF( return_async, 1, 1, 0, none) +DEF( throw, 1, 1, 0, none) +DEF( throw_error, 6, 0, 0, atom_u8) +DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */ +DEF( apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */ +DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a + bytecode string */ +DEF( get_super, 1, 1, 1, none) +DEF( import, 1, 2, 1, none) /* dynamic module import */ + +DEF( get_var_undef, 3, 0, 1, var_ref) /* push undefined if the variable does not exist */ +DEF( get_var, 3, 0, 1, var_ref) /* throw an exception if the variable does not exist */ +DEF( put_var, 3, 1, 0, var_ref) /* must come after get_var */ +DEF( put_var_init, 3, 1, 0, var_ref) /* must come after put_var. Used to initialize a global lexical variable */ + +DEF( get_ref_value, 1, 2, 3, none) +DEF( put_ref_value, 1, 3, 0, none) + +DEF( get_field, 5, 1, 1, atom) +DEF( get_field2, 5, 1, 2, atom) +DEF( put_field, 5, 2, 0, atom) +DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */ +DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */ +DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ +DEF( get_array_el, 1, 2, 1, none) +DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ +DEF( get_array_el3, 1, 2, 3, none) /* obj prop -> obj prop1 value */ +DEF( put_array_el, 1, 3, 0, none) +DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */ +DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */ +DEF( define_field, 5, 2, 1, atom) +DEF( set_name, 5, 1, 1, atom) +DEF(set_name_computed, 1, 2, 2, none) +DEF( set_proto, 1, 2, 1, none) +DEF(set_home_object, 1, 2, 2, none) +DEF(define_array_el, 1, 3, 2, none) +DEF( append, 1, 3, 2, none) /* append enumerated object, update length */ +DEF(copy_data_properties, 2, 3, 3, u8) +DEF( define_method, 6, 2, 1, atom_u8) +DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */ +DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */ +DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */ + +DEF( get_loc, 3, 0, 1, loc) +DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */ +DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */ +DEF( get_arg, 3, 0, 1, arg) +DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ +DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */ +DEF( get_var_ref, 3, 0, 1, var_ref) +DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ +DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ +DEF(set_loc_uninitialized, 3, 0, 0, loc) +DEF( get_loc_check, 3, 0, 1, loc) +DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ +DEF( put_loc_check_init, 3, 1, 0, loc) +DEF(get_loc_checkthis, 3, 0, 1, loc) +DEF(get_var_ref_check, 3, 0, 1, var_ref) +DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ +DEF(put_var_ref_check_init, 3, 1, 0, var_ref) +DEF( close_loc, 3, 0, 0, loc) +DEF( if_false, 5, 1, 0, label) +DEF( if_true, 5, 1, 0, label) /* must come after if_false */ +DEF( goto, 5, 0, 0, label) /* must come after if_true */ +DEF( catch, 5, 0, 1, label) +DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ +DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ +DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */ + +DEF( to_object, 1, 1, 1, none) +//DEF( to_string, 1, 1, 1, none) +DEF( to_propkey, 1, 1, 1, none) + +DEF( with_get_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_put_var, 10, 2, 1, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_delete_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_make_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_get_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ + +DEF( make_loc_ref, 7, 0, 2, atom_u16) +DEF( make_arg_ref, 7, 0, 2, atom_u16) +DEF(make_var_ref_ref, 7, 0, 2, atom_u16) +DEF( make_var_ref, 5, 0, 2, atom) + +DEF( for_in_start, 1, 1, 1, none) +DEF( for_of_start, 1, 1, 3, none) +DEF(for_await_of_start, 1, 1, 3, none) +DEF( for_in_next, 1, 1, 3, none) +DEF( for_of_next, 2, 3, 5, u8) +DEF(for_await_of_next, 1, 3, 4, none) /* iter next catch_offset -> iter next catch_offset obj */ +DEF(iterator_check_object, 1, 1, 1, none) +DEF(iterator_get_value_done, 1, 2, 3, none) /* catch_offset obj -> catch_offset value done */ +DEF( iterator_close, 1, 3, 0, none) +DEF( iterator_next, 1, 4, 4, none) +DEF( iterator_call, 2, 4, 5, u8) +DEF( initial_yield, 1, 0, 0, none) +DEF( yield, 1, 1, 2, none) +DEF( yield_star, 1, 1, 2, none) +DEF(async_yield_star, 1, 1, 2, none) +DEF( await, 1, 1, 1, none) + +/* arithmetic/logic operations */ +DEF( neg, 1, 1, 1, none) +DEF( plus, 1, 1, 1, none) +DEF( dec, 1, 1, 1, none) +DEF( inc, 1, 1, 1, none) +DEF( post_dec, 1, 1, 2, none) +DEF( post_inc, 1, 1, 2, none) +DEF( dec_loc, 2, 0, 0, loc8) +DEF( inc_loc, 2, 0, 0, loc8) +DEF( add_loc, 2, 1, 0, loc8) +DEF( not, 1, 1, 1, none) +DEF( lnot, 1, 1, 1, none) +DEF( typeof, 1, 1, 1, none) +DEF( delete, 1, 2, 1, none) +DEF( delete_var, 5, 0, 1, atom) + +DEF( mul, 1, 2, 1, none) +DEF( div, 1, 2, 1, none) +DEF( mod, 1, 2, 1, none) +DEF( add, 1, 2, 1, none) +DEF( sub, 1, 2, 1, none) +DEF( pow, 1, 2, 1, none) +DEF( shl, 1, 2, 1, none) +DEF( sar, 1, 2, 1, none) +DEF( shr, 1, 2, 1, none) +DEF( lt, 1, 2, 1, none) +DEF( lte, 1, 2, 1, none) +DEF( gt, 1, 2, 1, none) +DEF( gte, 1, 2, 1, none) +DEF( instanceof, 1, 2, 1, none) +DEF( in, 1, 2, 1, none) +DEF( eq, 1, 2, 1, none) +DEF( neq, 1, 2, 1, none) +DEF( strict_eq, 1, 2, 1, none) +DEF( strict_neq, 1, 2, 1, none) +DEF( and, 1, 2, 1, none) +DEF( xor, 1, 2, 1, none) +DEF( or, 1, 2, 1, none) +DEF(is_undefined_or_null, 1, 1, 1, none) +DEF( private_in, 1, 2, 1, none) +DEF(push_bigint_i32, 5, 0, 1, i32) +/* must be the last non short and non temporary opcode */ +DEF( nop, 1, 0, 0, none) + +/* temporary opcodes: never emitted in the final bytecode */ + +def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ +def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ + +def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ + +/* the following opcodes must be in the same order as the 'with_x' and + get_var_undef, get_var and put_var opcodes */ +def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_get_var_checkthis, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */ +def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ +def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ +def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */ +def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */ +def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */ +def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ + +def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */ + +#if SHORT_OPCODES +DEF( push_minus1, 1, 0, 1, none_int) +DEF( push_0, 1, 0, 1, none_int) +DEF( push_1, 1, 0, 1, none_int) +DEF( push_2, 1, 0, 1, none_int) +DEF( push_3, 1, 0, 1, none_int) +DEF( push_4, 1, 0, 1, none_int) +DEF( push_5, 1, 0, 1, none_int) +DEF( push_6, 1, 0, 1, none_int) +DEF( push_7, 1, 0, 1, none_int) +DEF( push_i8, 2, 0, 1, i8) +DEF( push_i16, 3, 0, 1, i16) +DEF( push_const8, 2, 0, 1, const8) +DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */ +DEF(push_empty_string, 1, 0, 1, none) + +DEF( get_loc8, 2, 0, 1, loc8) +DEF( put_loc8, 2, 1, 0, loc8) +DEF( set_loc8, 2, 1, 1, loc8) + +DEF( get_loc0, 1, 0, 1, none_loc) +DEF( get_loc1, 1, 0, 1, none_loc) +DEF( get_loc2, 1, 0, 1, none_loc) +DEF( get_loc3, 1, 0, 1, none_loc) +DEF( put_loc0, 1, 1, 0, none_loc) +DEF( put_loc1, 1, 1, 0, none_loc) +DEF( put_loc2, 1, 1, 0, none_loc) +DEF( put_loc3, 1, 1, 0, none_loc) +DEF( set_loc0, 1, 1, 1, none_loc) +DEF( set_loc1, 1, 1, 1, none_loc) +DEF( set_loc2, 1, 1, 1, none_loc) +DEF( set_loc3, 1, 1, 1, none_loc) +DEF( get_arg0, 1, 0, 1, none_arg) +DEF( get_arg1, 1, 0, 1, none_arg) +DEF( get_arg2, 1, 0, 1, none_arg) +DEF( get_arg3, 1, 0, 1, none_arg) +DEF( put_arg0, 1, 1, 0, none_arg) +DEF( put_arg1, 1, 1, 0, none_arg) +DEF( put_arg2, 1, 1, 0, none_arg) +DEF( put_arg3, 1, 1, 0, none_arg) +DEF( set_arg0, 1, 1, 1, none_arg) +DEF( set_arg1, 1, 1, 1, none_arg) +DEF( set_arg2, 1, 1, 1, none_arg) +DEF( set_arg3, 1, 1, 1, none_arg) +DEF( get_var_ref0, 1, 0, 1, none_var_ref) +DEF( get_var_ref1, 1, 0, 1, none_var_ref) +DEF( get_var_ref2, 1, 0, 1, none_var_ref) +DEF( get_var_ref3, 1, 0, 1, none_var_ref) +DEF( put_var_ref0, 1, 1, 0, none_var_ref) +DEF( put_var_ref1, 1, 1, 0, none_var_ref) +DEF( put_var_ref2, 1, 1, 0, none_var_ref) +DEF( put_var_ref3, 1, 1, 0, none_var_ref) +DEF( set_var_ref0, 1, 1, 1, none_var_ref) +DEF( set_var_ref1, 1, 1, 1, none_var_ref) +DEF( set_var_ref2, 1, 1, 1, none_var_ref) +DEF( set_var_ref3, 1, 1, 1, none_var_ref) + +DEF( get_length, 1, 1, 1, none) + +DEF( if_false8, 2, 1, 0, label8) +DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */ +DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */ +DEF( goto16, 3, 0, 0, label16) + +DEF( call0, 1, 1, 1, npopx) +DEF( call1, 1, 1, 1, npopx) +DEF( call2, 1, 1, 1, npopx) +DEF( call3, 1, 1, 1, npopx) + +DEF( is_undefined, 1, 1, 1, none) +DEF( is_null, 1, 1, 1, none) +DEF(typeof_is_undefined, 1, 1, 1, none) +DEF( typeof_is_function, 1, 1, 1, none) +#endif + +#undef DEF +#undef def +#endif /* DEF */ diff --git a/NativeScript/napi/android/quickjs/source/quickjs.c b/NativeScript/napi/android/quickjs/source/quickjs.c new file mode 100755 index 000000000..935906f4c --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/quickjs.c @@ -0,0 +1,59617 @@ +/* + * QuickJS Javascript Engine + * + * Copyright (c) 2017-2025 Fabrice Bellard + * Copyright (c) 2017-2025 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) +#include +#elif defined(__linux__) || defined(__GLIBC__) +#include +#elif defined(__FreeBSD__) +#include +#endif + +#include "cutils.h" +#include "list.h" +#include "quickjs.h" +#include "libregexp.h" +#include "libunicode.h" +#include "dtoa.h" + +#define OPTIMIZE 1 +#define SHORT_OPCODES 1 +#if defined(EMSCRIPTEN) +#define DIRECT_DISPATCH 0 +#else +#define DIRECT_DISPATCH 1 +#endif + +#if defined(__APPLE__) +#define MALLOC_OVERHEAD 0 +#else +#define MALLOC_OVERHEAD 8 +#endif + +#if !defined(_WIN32) +/* define it if printf uses the RNDN rounding mode instead of RNDNA */ +#define CONFIG_PRINTF_RNDN +#endif + +/* define to include Atomics.* operations which depend on the OS + threads */ +#if !defined(EMSCRIPTEN) +#define CONFIG_ATOMICS +#endif + +#if !defined(EMSCRIPTEN) +/* enable stack limitation */ +#define CONFIG_STACK_CHECK +#endif + + +/* dump object free */ +//#define DUMP_FREE +//#define DUMP_CLOSURE +/* dump the bytecode of the compiled functions: combination of bits + 1: dump pass 3 final byte code + 2: dump pass 2 code + 4: dump pass 1 code + 8: dump stdlib functions + 16: dump bytecode in hex + 32: dump line number table + 64: dump compute_stack_size + */ +//#define DUMP_BYTECODE (1) +/* dump the occurence of the automatic GC */ +//#define DUMP_GC +/* dump objects freed by the garbage collector */ +//#define DUMP_GC_FREE +/* dump objects leaking when freeing the runtime */ +//#define DUMP_LEAKS 1 +/* dump memory usage before running the garbage collector */ +//#define DUMP_MEM +//#define DUMP_OBJECTS /* dump objects in JS_FreeContext */ +//#define DUMP_ATOMS /* dump atoms in JS_FreeContext */ +//#define DUMP_SHAPES /* dump shapes in JS_FreeContext */ +//#define DUMP_MODULE_RESOLVE +//#define DUMP_MODULE_EXEC +//#define DUMP_PROMISE +//#define DUMP_READ_OBJECT +//#define DUMP_ROPE_REBALANCE + +/* test the GC by forcing it before each object allocation */ +//#define FORCE_GC_AT_MALLOC + +#ifdef CONFIG_ATOMICS +#include +#include +#include +#endif + +enum { + /* classid tag */ /* union usage | properties */ + JS_CLASS_OBJECT = 1, /* must be first */ + JS_CLASS_ARRAY, /* u.array | length */ + JS_CLASS_ERROR, + JS_CLASS_NUMBER, /* u.object_data */ + JS_CLASS_STRING, /* u.object_data */ + JS_CLASS_BOOLEAN, /* u.object_data */ + JS_CLASS_SYMBOL, /* u.object_data */ + JS_CLASS_ARGUMENTS, /* u.array | length */ + JS_CLASS_MAPPED_ARGUMENTS, /* u.array | length */ + JS_CLASS_DATE, /* u.object_data */ + JS_CLASS_MODULE_NS, + JS_CLASS_C_FUNCTION, /* u.cfunc */ + JS_CLASS_BYTECODE_FUNCTION, /* u.func */ + JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ + JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ + JS_CLASS_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ + JS_CLASS_REGEXP, /* u.regexp */ + JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_DATAVIEW, /* u.typed_array */ + JS_CLASS_BIG_INT, /* u.object_data */ + JS_CLASS_MAP, /* u.map_state */ + JS_CLASS_SET, /* u.map_state */ + JS_CLASS_WEAKMAP, /* u.map_state */ + JS_CLASS_WEAKSET, /* u.map_state */ + JS_CLASS_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_ITERATOR_CONCAT, /* u.iterator_concat_data */ + JS_CLASS_ITERATOR_HELPER, /* u.iterator_helper_data */ + JS_CLASS_ITERATOR_WRAP, /* u.iterator_wrap_data */ + JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ + JS_CLASS_GENERATOR, /* u.generator_data */ + JS_CLASS_GLOBAL_OBJECT, /* u.global_object */ + JS_CLASS_PROXY, /* u.proxy_data */ + JS_CLASS_PROMISE, /* u.promise_data */ + JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ + JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ + JS_CLASS_ASYNC_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ + JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ + JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ + JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ + JS_CLASS_WEAK_REF, + JS_CLASS_FINALIZATION_REGISTRY, + + JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ +}; + +/* number of typed array types */ +#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) +static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT]; +#define typed_array_size_log2(classid) (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY]) + +typedef enum JSErrorEnum { + JS_EVAL_ERROR, + JS_RANGE_ERROR, + JS_REFERENCE_ERROR, + JS_SYNTAX_ERROR, + JS_TYPE_ERROR, + JS_URI_ERROR, + JS_INTERNAL_ERROR, + JS_AGGREGATE_ERROR, + + JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ +} JSErrorEnum; + +/* the variable and scope indexes must fit on 16 bits. The (-1) and + ARG_SCOPE_END values are reserved. */ +#define JS_MAX_LOCAL_VARS 65534 +#define JS_STACK_SIZE_MAX 65534 +#define JS_STRING_LEN_MAX ((1 << 30) - 1) + +/* strings <= this length are not concatenated using ropes. if too + small, the rope memory overhead becomes high. */ +#define JS_STRING_ROPE_SHORT_LEN 512 +/* specific threshold for initial rope use */ +#define JS_STRING_ROPE_SHORT2_LEN 8192 +/* rope depth at which we rebalance */ +#define JS_STRING_ROPE_MAX_DEPTH 60 + +#define __exception __attribute__((warn_unused_result)) + +typedef struct JSShape JSShape; +typedef struct JSString JSString; +typedef struct JSString JSAtomStruct; +typedef struct JSObject JSObject; + +#define JS_VALUE_GET_OBJ(v) ((JSObject *)JS_VALUE_GET_PTR(v)) +#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v)) +#define JS_VALUE_GET_STRING_ROPE(v) ((JSStringRope *)JS_VALUE_GET_PTR(v)) + +typedef enum { + JS_GC_PHASE_NONE, + JS_GC_PHASE_DECREF, + JS_GC_PHASE_REMOVE_CYCLES, +} JSGCPhaseEnum; + +typedef enum OPCodeEnum OPCodeEnum; + +struct JSRuntime { + JSMallocFunctions mf; + JSMallocState malloc_state; + const char *rt_info; + + int atom_hash_size; /* power of two */ + int atom_count; + int atom_size; + int atom_count_resize; /* resize hash table at this count */ + uint32_t *atom_hash; + JSAtomStruct **atom_array; + int atom_free_index; /* 0 = none */ + + int class_count; /* size of class_array */ + JSClass *class_array; + + struct list_head context_list; /* list of JSContext.link */ + /* list of JSGCObjectHeader.link. List of allocated GC objects (used + by the garbage collector) */ + struct list_head gc_obj_list; + /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ + struct list_head gc_zero_ref_count_list; + struct list_head tmp_obj_list; /* used during GC */ + JSGCPhaseEnum gc_phase : 8; + size_t malloc_gc_threshold; + struct list_head weakref_list; /* list of JSWeakRefHeader.link */ + struct list_head weakref_keepalive_list; /* strong refs kept until the current host job completes */ +#ifdef DUMP_LEAKS + struct list_head string_list; /* list of JSString.link */ +#endif + /* stack limitation */ + uintptr_t stack_size; /* in bytes, 0 if no limit */ + uintptr_t stack_top; + uintptr_t stack_limit; /* lower stack limit */ + + JSValue current_exception; + /* true if the current exception cannot be catched */ + BOOL current_exception_is_uncatchable : 8; + /* true if inside an out of memory error, to avoid recursing */ + BOOL in_out_of_memory : 8; + + struct JSStackFrame *current_stack_frame; + + JSInterruptHandler *interrupt_handler; + void *interrupt_opaque; + + JSHostPromiseRejectionTracker *host_promise_rejection_tracker; + void *host_promise_rejection_tracker_opaque; + + struct list_head job_list; /* list of JSJobEntry.link */ + + JSModuleNormalizeFunc *module_normalize_func; + BOOL module_loader_has_attr; + union { + JSModuleLoaderFunc *module_loader_func; + JSModuleLoaderFunc2 *module_loader_func2; + } u; + JSModuleCheckSupportedImportAttributes *module_check_attrs; + void *module_loader_opaque; + /* timestamp for internal use in module evaluation */ + int64_t module_async_evaluation_next_timestamp; + + BOOL can_block : 8; /* TRUE if Atomics.wait can block */ + /* used to allocate, free and clone SharedArrayBuffers */ + JSSharedArrayBufferFunctions sab_funcs; + /* see JS_SetStripInfo() */ + uint8_t strip_flags; + + /* Shape hash table */ + int shape_hash_bits; + int shape_hash_size; + int shape_hash_count; /* number of hashed shapes */ + JSShape **shape_hash; + void *user_opaque; +}; + +struct JSClass { + uint32_t class_id; /* 0 means free entry */ + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; + JSClassCall *call; + /* pointers for exotic behavior, can be NULL if none are present */ + const JSClassExoticMethods *exotic; +}; + +#define JS_MODE_STRICT (1 << 0) +#define JS_MODE_ASYNC (1 << 2) /* async function */ +#define JS_MODE_BACKTRACE_BARRIER (1 << 3) /* stop backtrace before this frame */ + +typedef struct JSStackFrame { + struct JSStackFrame *prev_frame; /* NULL if first stack frame */ + JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */ + JSValue *arg_buf; /* arguments */ + JSValue *var_buf; /* variables */ + struct JSVarRef **var_refs; /* references to arguments or local variables */ + const uint8_t *cur_pc; /* only used in bytecode functions : PC of the + instruction after the call */ + int arg_count; + int js_mode; /* not supported for C functions */ + /* only used in generators. Current stack pointer value. NULL if + the function is running. */ + JSValue *cur_sp; +} JSStackFrame; + +typedef struct JSWeakRefKeepAliveEntry { + struct list_head link; + JSValue value; +} JSWeakRefKeepAliveEntry; + +typedef enum { + JS_GC_OBJ_TYPE_JS_OBJECT, + JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, + JS_GC_OBJ_TYPE_SHAPE, + JS_GC_OBJ_TYPE_VAR_REF, + JS_GC_OBJ_TYPE_ASYNC_FUNCTION, + JS_GC_OBJ_TYPE_JS_CONTEXT, + JS_GC_OBJ_TYPE_MODULE, +} JSGCObjectTypeEnum; + +/* header for GC objects. GC objects are C data structures with a + reference count that can reference other GC objects. JS Objects are + a particular type of GC object. */ +struct JSGCObjectHeader { + int ref_count; /* must come first, 32-bit */ + JSGCObjectTypeEnum gc_obj_type : 4; + uint8_t mark : 1; /* used by the GC */ + uint8_t dummy0: 3; + uint8_t dummy1; /* not used by the GC */ + uint16_t dummy2; /* not used by the GC */ + struct list_head link; +}; + +typedef enum { + JS_WEAKREF_TYPE_MAP, + JS_WEAKREF_TYPE_WEAKREF, + JS_WEAKREF_TYPE_FINREC, +} JSWeakRefHeaderTypeEnum; + +typedef struct { + struct list_head link; + JSWeakRefHeaderTypeEnum weakref_type; +} JSWeakRefHeader; + +typedef struct JSVarRef { + union { + JSGCObjectHeader header; /* must come first */ + struct { + int __gc_ref_count; /* corresponds to header.ref_count */ + uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ + uint8_t is_detached; + uint8_t is_lexical; /* only used with global variables */ + uint8_t is_const; /* only used with global variables */ + }; + }; + JSValue *pvalue; /* pointer to the value, either on the stack or + to 'value' */ + union { + JSValue value; /* used when is_detached = TRUE */ + struct { + uint16_t var_ref_idx; /* index in JSStackFrame.var_refs[] */ + JSStackFrame *stack_frame; + }; /* used when is_detached = FALSE */ + }; +} JSVarRef; + +/* bigint */ + +#if JS_LIMB_BITS == 32 + +typedef int32_t js_slimb_t; +typedef uint32_t js_limb_t; +typedef int64_t js_sdlimb_t; +typedef uint64_t js_dlimb_t; + +#define JS_LIMB_DIGITS 9 + +#else + +typedef __int128 int128_t; +typedef unsigned __int128 uint128_t; +typedef int64_t js_slimb_t; +typedef uint64_t js_limb_t; +typedef int128_t js_sdlimb_t; +typedef uint128_t js_dlimb_t; + +#define JS_LIMB_DIGITS 19 + +#endif + +typedef struct JSBigInt { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len; /* number of limbs, >= 1 */ + js_limb_t tab[]; /* two's complement representation, always + normalized so that 'len' is the minimum + possible length >= 1 */ +} JSBigInt; + +/* this bigint structure can hold a 64 bit integer */ +typedef struct { + js_limb_t big_int_buf[sizeof(JSBigInt) / sizeof(js_limb_t)]; /* for JSBigInt */ + /* must come just after */ + js_limb_t tab[(64 + JS_LIMB_BITS - 1) / JS_LIMB_BITS]; +} JSBigIntBuf; + +typedef enum { + JS_AUTOINIT_ID_PROTOTYPE, + JS_AUTOINIT_ID_MODULE_NS, + JS_AUTOINIT_ID_PROP, +} JSAutoInitIDEnum; + +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define JS_INTERRUPT_COUNTER_INIT 10000 + +struct JSContext { + JSGCObjectHeader header; /* must come first */ + JSRuntime *rt; + struct list_head link; + + uint16_t binary_object_count; + int binary_object_size; + /* TRUE if the array prototype is "normal": + - no small index properties which are get/set or non writable + - its prototype is Object.prototype + - Object.prototype has no small index properties which are get/set or non writable + - the prototype of Object.prototype is null (always true as it is immutable) + */ + uint8_t std_array_prototype; + + JSShape *array_shape; /* initial shape for Array objects */ + JSShape *arguments_shape; /* shape for arguments objects */ + JSShape *mapped_arguments_shape; /* shape for mapped arguments objects */ + JSShape *regexp_shape; /* shape for regexp objects */ + JSShape *regexp_result_shape; /* shape for regexp result objects */ + + JSValue *class_proto; + JSValue function_proto; + JSValue function_ctor; + JSValue array_ctor; + JSValue regexp_ctor; + JSValue promise_ctor; + JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; + JSValue iterator_ctor; + JSValue async_iterator_proto; + JSValue array_proto_values; + JSValue throw_type_error; + JSValue eval_obj; + + JSValue global_obj; /* global object */ + JSValue global_var_obj; /* contains the global let/const definitions */ + + uint64_t random_state; + + /* when the counter reaches zero, JSRutime.interrupt_handler is called */ + int interrupt_counter; + + struct list_head loaded_modules; /* list of JSModuleDef.link */ + + /* if NULL, RegExp compilation is not supported */ + JSValue (*compile_regexp)(JSContext *ctx, JSValueConst pattern, + JSValueConst flags); + /* if NULL, eval is not supported */ + JSValue (*eval_internal)(JSContext *ctx, JSValueConst this_obj, + const char *input, size_t input_len, + const char *filename, int flags, int scope_idx); + void *user_opaque; +}; + +typedef union JSFloat64Union { + double d; + uint64_t u64; + uint32_t u32[2]; +} JSFloat64Union; + +enum { + JS_ATOM_TYPE_STRING = 1, + JS_ATOM_TYPE_GLOBAL_SYMBOL, + JS_ATOM_TYPE_SYMBOL, + JS_ATOM_TYPE_PRIVATE, +}; + +typedef enum { + JS_ATOM_KIND_STRING, + JS_ATOM_KIND_SYMBOL, + JS_ATOM_KIND_PRIVATE, +} JSAtomKindEnum; + +#define JS_ATOM_HASH_MASK ((1 << 30) - 1) +#define JS_ATOM_HASH_PRIVATE JS_ATOM_HASH_MASK + +struct JSString { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len : 31; + uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ + /* for JS_ATOM_TYPE_SYMBOL: hash = weakref_count, atom_type = 3, + for JS_ATOM_TYPE_PRIVATE: hash = JS_ATOM_HASH_PRIVATE, atom_type = 3 + XXX: could change encoding to have one more bit in hash */ + uint32_t hash : 30; + uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ + uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ +#ifdef DUMP_LEAKS + struct list_head link; /* string list */ +#endif + union { + uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */ + uint16_t str16[0]; + } u; +}; + +typedef struct JSStringRope { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len; + uint8_t is_wide_char; /* 0 = 8 bits, 1 = 16 bits characters */ + uint8_t depth; /* max depth of the rope tree */ + /* XXX: could reduce memory usage by using a direct pointer with + bit 0 to select rope or string */ + JSValue left; + JSValue right; /* might be the empty string */ +} JSStringRope; + +typedef enum { + JS_CLOSURE_LOCAL, /* 'var_idx' is the index of a local variable in the parent function */ + JS_CLOSURE_ARG, /* 'var_idx' is the index of a argument variable in the parent function */ + JS_CLOSURE_REF, /* 'var_idx' is the index of a closure variable in the parent function */ + JS_CLOSURE_GLOBAL_REF, /* 'var_idx' in the index of a closure + variable in the parent function + referencing a global variable */ + JS_CLOSURE_GLOBAL_DECL, /* global variable declaration (eval code only) */ + JS_CLOSURE_GLOBAL, /* global variable (eval code only) */ + JS_CLOSURE_MODULE_DECL, /* definition of a module variable (eval code only) */ + JS_CLOSURE_MODULE_IMPORT, /* definition of a module import (eval code only) */ +} JSClosureTypeEnum; + +typedef struct JSClosureVar { + JSClosureTypeEnum closure_type : 3; + uint8_t is_lexical : 1; /* lexical variable */ + uint8_t is_const : 1; /* const variable (is_lexical = 1 if is_const = 1 */ + uint8_t var_kind : 4; /* see JSVarKindEnum */ + uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the + parent function. otherwise: index to a closure + variable of the parent function */ + JSAtom var_name; +} JSClosureVar; + +#define ARG_SCOPE_INDEX 1 +#define ARG_SCOPE_END (-2) + +typedef enum { + /* XXX: add more variable kinds here instead of using bit fields */ + JS_VAR_NORMAL, + JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ + JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator + function declaration */ + JS_VAR_CATCH, + JS_VAR_FUNCTION_NAME, /* function expression name */ + JS_VAR_PRIVATE_FIELD, + JS_VAR_PRIVATE_METHOD, + JS_VAR_PRIVATE_GETTER, + JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */ + JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER */ + JS_VAR_GLOBAL_FUNCTION_DECL, /* global function definition, only in JSVarDef */ +} JSVarKindEnum; + +typedef struct JSBytecodeVarDef { + JSAtom var_name; + /* index into JSFunctionBytecode.vars of the next variable in the same or + enclosing lexical scope + */ + int scope_next; /* XXX: store on 16 bits */ + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t is_captured : 1; /* XXX: could remove and use a var_ref_idx value */ + uint8_t has_scope: 1; /* true if JSVarDef.scope_level != 0 */ + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* If is_captured = TRUE, provides, the index of the corresponding + JSVarRef on stack. It would be more compact to have a separate + table with the corresponding inverted table but it requires + more modifications in the code. */ + uint16_t var_ref_idx; +} JSBytecodeVarDef; + +/* for the encoding of the pc2line table */ +#define PC2LINE_BASE (-1) +#define PC2LINE_RANGE 5 +#define PC2LINE_OP_FIRST 1 +#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) + +typedef enum JSFunctionKindEnum { + JS_FUNC_NORMAL = 0, + JS_FUNC_GENERATOR = (1 << 0), + JS_FUNC_ASYNC = (1 << 1), + JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), +} JSFunctionKindEnum; + +typedef struct JSFunctionBytecode { + JSGCObjectHeader header; /* must come first */ + uint8_t js_mode; + uint8_t has_prototype : 1; /* true if a prototype field is necessary */ + uint8_t has_simple_parameter_list : 1; + uint8_t is_derived_class_constructor : 1; + /* true if home_object needs to be initialized */ + uint8_t need_home_object : 1; + uint8_t func_kind : 2; + uint8_t new_target_allowed : 1; + uint8_t super_call_allowed : 1; + uint8_t super_allowed : 1; + uint8_t arguments_allowed : 1; + uint8_t has_debug : 1; + uint8_t read_only_bytecode : 1; + uint8_t is_direct_or_indirect_eval : 1; /* used by JS_GetScriptOrModuleName() */ + /* XXX: 10 bits available */ + uint8_t *byte_code_buf; /* (self pointer) */ + int byte_code_len; + JSAtom func_name; + JSBytecodeVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */ + JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */ + uint16_t arg_count; + uint16_t var_count; + uint16_t defined_arg_count; /* for length function property */ + uint16_t stack_size; /* maximum stack size */ + uint16_t var_ref_count; /* number of local variable references */ + JSContext *realm; /* function realm */ + JSValue *cpool; /* constant pool (self pointer) */ + int cpool_count; + int closure_var_count; + struct { + /* debug info, move to separate structure to save memory? */ + JSAtom filename; + int source_len; + int pc2line_len; + uint8_t *pc2line_buf; + char *source; + } debug; +} JSFunctionBytecode; + +typedef struct JSBoundFunction { + JSValue func_obj; + JSValue this_val; + int argc; + JSValue argv[0]; +} JSBoundFunction; + +typedef enum JSIteratorKindEnum { + JS_ITERATOR_KIND_KEY, + JS_ITERATOR_KIND_VALUE, + JS_ITERATOR_KIND_KEY_AND_VALUE, +} JSIteratorKindEnum; + +typedef struct JSForInIterator { + JSValue obj; + uint32_t idx; + uint32_t atom_count; + uint8_t in_prototype_chain; + uint8_t is_array; + JSPropertyEnum *tab_atom; /* is_array = FALSE */ +} JSForInIterator; + +typedef struct JSRegExp { + JSString *pattern; + JSString *bytecode; /* also contains the flags */ +} JSRegExp; + +typedef struct JSProxyData { + JSValue target; + JSValue handler; + uint8_t is_func; + uint8_t is_revoked; +} JSProxyData; + +typedef struct JSArrayBuffer { + int byte_length; /* 0 if detached */ + int max_byte_length; /* -1 if not resizable; >= byte_length otherwise */ + uint8_t detached; + uint8_t shared; /* if shared, the array buffer cannot be detached */ + uint8_t *data; /* NULL if detached */ + struct list_head array_list; + void *opaque; + JSFreeArrayBufferDataFunc *free_func; +} JSArrayBuffer; + +typedef struct JSTypedArray { + struct list_head link; /* link to arraybuffer */ + JSObject *obj; /* back pointer to the TypedArray/DataView object */ + JSObject *buffer; /* based array buffer */ + uint32_t offset; /* byte offset in the array buffer */ + uint32_t length; /* byte length in the array buffer */ + BOOL track_rab; /* auto-track length of backing array buffer */ +} JSTypedArray; + +typedef struct JSGlobalObject { + JSValue uninitialized_vars; /* hidden object containing the list of uninitialized variables */ +} JSGlobalObject; + +typedef struct JSAsyncFunctionState { + JSGCObjectHeader header; + JSValue this_val; /* 'this' argument */ + int argc; /* number of function arguments */ + BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */ + BOOL is_completed; /* TRUE if the function has returned. The stack + frame is no longer valid */ + JSValue resolving_funcs[2]; /* only used in JS async functions */ + JSStackFrame frame; + /* arg_buf, var_buf, stack_buf and var_refs follow */ +} JSAsyncFunctionState; + +typedef enum { + /* binary operators */ + JS_OVOP_ADD, + JS_OVOP_SUB, + JS_OVOP_MUL, + JS_OVOP_DIV, + JS_OVOP_MOD, + JS_OVOP_POW, + JS_OVOP_OR, + JS_OVOP_AND, + JS_OVOP_XOR, + JS_OVOP_SHL, + JS_OVOP_SAR, + JS_OVOP_SHR, + JS_OVOP_EQ, + JS_OVOP_LESS, + + JS_OVOP_BINARY_COUNT, + /* unary operators */ + JS_OVOP_POS = JS_OVOP_BINARY_COUNT, + JS_OVOP_NEG, + JS_OVOP_INC, + JS_OVOP_DEC, + JS_OVOP_NOT, + + JS_OVOP_COUNT, +} JSOverloadableOperatorEnum; + +typedef struct { + uint32_t operator_index; + JSObject *ops[JS_OVOP_BINARY_COUNT]; /* self operators */ +} JSBinaryOperatorDefEntry; + +typedef struct { + int count; + JSBinaryOperatorDefEntry *tab; +} JSBinaryOperatorDef; + +typedef struct { + uint32_t operator_counter; + BOOL is_primitive; /* OperatorSet for a primitive type */ + /* NULL if no operator is defined */ + JSObject *self_ops[JS_OVOP_COUNT]; /* self operators */ + JSBinaryOperatorDef left; + JSBinaryOperatorDef right; +} JSOperatorSetData; + +typedef struct JSReqModuleEntry { + JSAtom module_name; + JSModuleDef *module; /* used using resolution */ + JSValue attributes; /* JS_UNDEFINED or an object contains the attributes as key/value */ +} JSReqModuleEntry; + +typedef enum JSExportTypeEnum { + JS_EXPORT_TYPE_LOCAL, + JS_EXPORT_TYPE_INDIRECT, +} JSExportTypeEnum; + +typedef struct JSExportEntry { + union { + struct { + int var_idx; /* closure variable index */ + JSVarRef *var_ref; /* if != NULL, reference to the variable */ + } local; /* for local export */ + int req_module_idx; /* module for indirect export */ + } u; + JSExportTypeEnum export_type; + JSAtom local_name; /* '*' if export ns from. not used for local + export after compilation */ + JSAtom export_name; /* exported variable name */ +} JSExportEntry; + +typedef struct JSStarExportEntry { + int req_module_idx; /* in req_module_entries */ +} JSStarExportEntry; + +typedef struct JSImportEntry { + int var_idx; /* closure variable index */ + BOOL is_star; /* import_name = '*' is a valid import name, so need a flag */ + JSAtom import_name; + int req_module_idx; /* in req_module_entries */ +} JSImportEntry; + +typedef enum { + JS_MODULE_STATUS_UNLINKED, + JS_MODULE_STATUS_LINKING, + JS_MODULE_STATUS_LINKED, + JS_MODULE_STATUS_EVALUATING, + JS_MODULE_STATUS_EVALUATING_ASYNC, + JS_MODULE_STATUS_EVALUATED, +} JSModuleStatus; + +struct JSModuleDef { + JSGCObjectHeader header; /* must come first */ + JSAtom module_name; + struct list_head link; + + JSReqModuleEntry *req_module_entries; + int req_module_entries_count; + int req_module_entries_size; + + JSExportEntry *export_entries; + int export_entries_count; + int export_entries_size; + + JSStarExportEntry *star_export_entries; + int star_export_entries_count; + int star_export_entries_size; + + JSImportEntry *import_entries; + int import_entries_count; + int import_entries_size; + + JSValue module_ns; + JSValue func_obj; /* only used for JS modules */ + JSModuleInitFunc *init_func; /* only used for C modules */ + BOOL has_tla : 8; /* true if func_obj contains await */ + BOOL resolved : 8; + BOOL func_created : 8; + JSModuleStatus status : 8; + /* temp use during js_module_link() & js_module_evaluate() */ + int dfs_index, dfs_ancestor_index; + JSModuleDef *stack_prev; + /* temp use during js_module_evaluate() */ + JSModuleDef **async_parent_modules; + int async_parent_modules_count; + int async_parent_modules_size; + int pending_async_dependencies; + BOOL async_evaluation; /* true: async_evaluation_timestamp corresponds to [[AsyncEvaluationOrder]] + false: [[AsyncEvaluationOrder]] is UNSET or DONE */ + int64_t async_evaluation_timestamp; + JSModuleDef *cycle_root; + JSValue promise; /* corresponds to spec field: capability */ + JSValue resolving_funcs[2]; /* corresponds to spec field: capability */ + + /* true if evaluation yielded an exception. It is saved in + eval_exception */ + BOOL eval_has_exception : 8; + JSValue eval_exception; + JSValue meta_obj; /* for import.meta */ + JSValue private_value; /* private value for C modules */ +}; + +typedef struct JSJobEntry { + struct list_head link; + JSContext *realm; + JSJobFunc *job_func; + int argc; + JSValue argv[0]; +} JSJobEntry; + +typedef struct JSProperty { + union { + JSValue value; /* JS_PROP_NORMAL */ + struct { /* JS_PROP_GETSET */ + JSObject *getter; /* NULL if undefined */ + JSObject *setter; /* NULL if undefined */ + } getset; + JSVarRef *var_ref; /* JS_PROP_VARREF */ + struct { /* JS_PROP_AUTOINIT */ + /* in order to use only 2 pointers, we compress the realm + and the init function pointer */ + uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x) + in the 2 low bits */ + void *opaque; + } init; + } u; +} JSProperty; + +#define JS_PROP_INITIAL_SIZE 2 +#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ + +typedef struct JSShapeProperty { + uint32_t hash_next : 26; /* 0 if last in list */ + uint32_t flags : 6; /* JS_PROP_XXX */ + JSAtom atom; /* JS_ATOM_NULL = free property entry */ +} JSShapeProperty; + +struct JSShape { + /* hash table of size hash_mask + 1 before the start of the + structure (see prop_hash_end()). */ + JSGCObjectHeader header; + /* true if the shape is inserted in the shape hash table. If not, + JSShape.hash is not valid */ + uint8_t is_hashed; + uint32_t hash; /* current hash value */ + uint32_t prop_hash_mask; + int prop_size; /* allocated properties */ + int prop_count; /* include deleted properties */ + int deleted_prop_count; + JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ + JSObject *proto; + JSShapeProperty prop[0]; /* prop_size elements */ +}; + +struct JSObject { + union { + JSGCObjectHeader header; + struct { + int __gc_ref_count; /* corresponds to header.ref_count */ + uint8_t __gc_mark : 7; /* corresponds to header.mark/gc_obj_type */ + uint8_t is_prototype : 1; /* object may be used as prototype */ + + uint8_t extensible : 1; + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ + uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_MAPPED_ARGUMENTS and typed arrays) */ + uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ + uint8_t has_immutable_prototype : 1; /* cannot modify the prototype */ + uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ + uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ + uint16_t class_id; /* see JS_CLASS_x */ + }; + }; + /* count the number of weak references to this object. The object + structure is freed only if header.ref_count = 0 and + weakref_count = 0 */ + uint32_t weakref_count; + JSShape *shape; /* prototype and property names + flag */ + JSProperty *prop; /* array of properties */ + union { + void *opaque; + struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ + struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ + struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ + struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ + struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ + struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ + struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ + struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ + struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ + struct JSIteratorConcatData *iterator_concat_data; /* JS_CLASS_ITERATOR_CONCAT */ + struct JSIteratorHelperData *iterator_helper_data; /* JS_CLASS_ITERATOR_HELPER */ + struct JSIteratorWrapData *iterator_wrap_data; /* JS_CLASS_ITERATOR_WRAP */ + struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ + struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ + struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ + struct JSAsyncFunctionState *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ + struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ + struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ + struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ + /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ + struct JSFunctionBytecode *function_bytecode; + JSVarRef **var_refs; + JSObject *home_object; /* for 'super' access */ + } func; + struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ + JSContext *realm; + JSCFunctionType c_function; + uint8_t length; + uint8_t cproto; + int16_t magic; + } cfunc; + /* array part for fast arrays and typed arrays */ + struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_MAPPED_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + union { + uint32_t size; /* JS_CLASS_ARRAY */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + } u1; + union { + JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + JSVarRef **var_refs; /* JS_CLASS_MAPPED_ARGUMENTS */ + void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */ + uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ + int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */ + uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ + int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */ + uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ + int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ + uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ + uint16_t *fp16_ptr; /* JS_CLASS_FLOAT16_ARRAY */ + float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ + double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ + } u; + uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ + } array; /* 12/20 bytes */ + JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ + JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ + JSGlobalObject global_object; + } u; +}; + +typedef struct JSMapRecord { + int ref_count; /* used during enumeration to avoid freeing the record */ + BOOL empty : 8; /* TRUE if the record is deleted */ + struct list_head link; + struct JSMapRecord *hash_next; + JSValue key; + JSValue value; +} JSMapRecord; + +typedef struct JSMapState { + BOOL is_weak; /* TRUE if WeakSet/WeakMap */ + struct list_head records; /* list of JSMapRecord.link */ + uint32_t record_count; + JSMapRecord **hash_table; + int hash_bits; + uint32_t hash_size; /* = 2 ^ hash_bits */ + uint32_t record_count_threshold; /* count at which a hash table + resize is needed */ + JSWeakRefHeader weakref_header; /* only used if is_weak = TRUE */ +} JSMapState; + +enum { + __JS_ATOM_NULL = JS_ATOM_NULL, +#define DEF(name, str) JS_ATOM_ ## name, +#include "quickjs-atom.h" +#undef DEF + JS_ATOM_END, +}; +#define JS_ATOM_LAST_KEYWORD JS_ATOM_super +#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield + +static const char js_atom_init[] = +#define DEF(name, str) str "\0" +#include "quickjs-atom.h" +#undef DEF +; + +typedef enum OPCodeFormat { +#define FMT(f) OP_FMT_ ## f, +#define DEF(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +} OPCodeFormat; + +enum OPCodeEnum { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_COUNT, /* excluding temporary opcodes */ + /* temporary opcodes : overlap with the short opcodes */ + OP_TEMP_START = OP_nop + 1, + OP___dummy = OP_TEMP_START - 1, +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) +#define def(id, size, n_pop, n_push, f) OP_ ## id, +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_TEMP_END, +}; + +static int JS_InitAtoms(JSRuntime *rt); +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type); +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p); +static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b); +static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags); +static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags); +static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValue *argv, int flags); +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValue *argv, int flags); +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv); +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValueConst *argv); +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, BOOL is_array_ctor); +static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, + JSValueConst val, int flags, int scope_idx); +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...); +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); +static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p); +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); +static __maybe_unused void JS_DumpAtom(JSContext *ctx, const char *str, JSAtom atom); +static __maybe_unused void JS_DumpValueRT(JSRuntime *rt, const char *str, JSValueConst val); +static __maybe_unused void JS_DumpValue(JSContext *ctx, const char *str, JSValueConst val); +static __maybe_unused void JS_DumpShapes(JSRuntime *rt); +static void js_dump_value_write(void *opaque, const char *buf, size_t len); +static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static void js_array_finalizer(JSRuntime *rt, JSValue val); +static void js_array_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); +static void js_mapped_arguments_finalizer(JSRuntime *rt, JSValue val); +static void js_mapped_arguments_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); +static void js_object_data_finalizer(JSRuntime *rt, JSValue val); +static void js_object_data_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); +static void js_c_function_finalizer(JSRuntime *rt, JSValue val); +static void js_c_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val); +static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_bound_function_finalizer(JSRuntime *rt, JSValue val); +static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_regexp_finalizer(JSRuntime *rt, JSValue val); +static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val); +static void js_typed_array_finalizer(JSRuntime *rt, JSValue val); +static void js_typed_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_proxy_finalizer(JSRuntime *rt, JSValue val); +static void js_proxy_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_map_finalizer(JSRuntime *rt, JSValue val); +static void js_map_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_concat_finalizer(JSRuntime *rt, JSValue val); +static void js_iterator_concat_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_helper_finalizer(JSRuntime *rt, JSValue val); +static void js_iterator_helper_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_wrap_finalizer(JSRuntime *rt, JSValue val); +static void js_iterator_wrap_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_generator_finalizer(JSRuntime *rt, JSValue obj); +static void js_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_global_object_finalizer(JSRuntime *rt, JSValue obj); +static void js_global_object_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_promise_finalizer(JSRuntime *rt, JSValue val); +static void js_promise_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val); +static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); + +#define HINT_STRING 0 +#define HINT_NUMBER 1 +#define HINT_NONE 2 +#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint); +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); +static int JS_ToBoolFree(JSContext *ctx, JSValue val); +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); +static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val); +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val); +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len); +static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern, + JSValueConst flags); +static JSValue JS_NewRegexp(JSContext *ctx, JSValue pattern, JSValue bc); +static void gc_decref(JSRuntime *rt); +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name); + +typedef enum JSStrictEqModeEnum { + JS_EQ_STRICT, + JS_EQ_SAME_VALUE, + JS_EQ_SAME_VALUE_ZERO, +} JSStrictEqModeEnum; + +static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, + JSStrictEqModeEnum eq_mode); +static BOOL js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static JSValue JS_ToObject(JSContext *ctx, JSValueConst val); +static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val); +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags); +static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags); +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val); +JSValue JS_ThrowOutOfMemory(JSContext *ctx); +static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); + +static int js_resolve_proxy(JSContext *ctx, JSValueConst *pval, int throw_exception); +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, + int flags); +static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2, + int pos2, int len); +static JSValue js_array_buffer_constructor3(JSContext *ctx, + JSValueConst new_target, + uint64_t len, uint64_t *max_len, + JSClassID class_id, + uint8_t *buf, + JSFreeArrayBufferDataFunc *free_func, + void *opaque, BOOL alloc_flag); +static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr); +static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj); +static BOOL array_buffer_is_resizable(const JSArrayBuffer *abuf); +static JSValue js_typed_array_constructor(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int classid); +static JSValue js_typed_array_constructor_ta(JSContext *ctx, + JSValueConst new_target, + JSValueConst src_obj, + int classid, uint32_t len); +static BOOL typed_array_is_oob(JSObject *p); +static int js_typed_array_get_length_unsafe(JSContext *ctx, JSValueConst obj); +static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); +static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx); +static JSVarRef *js_create_var_ref(JSContext *ctx, BOOL is_lexical); +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, + BOOL is_arg); +static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s); +static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s); +static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags); +static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val); +static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, + const char *input, size_t input_len, + const char *filename, int flags, int scope_idx); +static void js_free_module_def(JSRuntime *rt, JSModuleDef *m); +static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m, + JS_MarkFunc *mark_func); +static JSValue js_import_meta(JSContext *ctx); +static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier, JSValueConst options); +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); +static JSValue js_new_promise_capability(JSContext *ctx, + JSValue *resolving_funcs, + JSValueConst ctor); +static __exception int perform_promise_then(JSContext *ctx, + JSValueConst promise, + JSValueConst *resolve_reject, + JSValueConst *cap_resolving_funcs); +static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static BOOL js_string_eq(JSContext *ctx, + const JSString *p1, const JSString *p2); +static int js_string_compare(JSContext *ctx, + const JSString *p1, const JSString *p2); +static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val); +static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags); +static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val); +static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val); +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val); +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop); +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc); +static int JS_AddIntrinsicBasicObjects(JSContext *ctx); +static void js_free_shape(JSRuntime *rt, JSShape *sh); +static void js_free_shape_null(JSRuntime *rt, JSShape *sh); +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs); +static int init_shape_hash(JSRuntime *rt); +static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, + JSValueConst obj); +static __exception int js_get_length64(JSContext *ctx, int64_t *pres, + JSValueConst obj); +static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len); +static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, + JSValueConst array_arg); +static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj, + JSValue **arrpp, uint32_t *countp); +static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, + JSValueConst sync_iter); +static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val); +static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static JSValue js_c_function_data_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags); +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val); +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type); +static void remove_gc_object(JSGCObjectHeader *h); +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); +static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, + void *opaque); +static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, + JSAtom atom, void *opaque); +static JSValue js_object_groupBy(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_map); +static void map_delete_weakrefs(JSRuntime *rt, JSWeakRefHeader *wh); +static void weakref_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh); +static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh); +static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects); +static JSValue js_array_from_iterator(JSContext *ctx, uint32_t *plen, + JSValueConst obj, JSValueConst method); +static int js_string_find_invalid_codepoint(JSString *p); +static JSValue js_regexp_toString(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue get_date_string(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSVarRef *js_global_object_find_uninitialized_var(JSContext *ctx, JSObject *p, + JSAtom atom, BOOL is_lexical); + +static const JSClassExoticMethods js_arguments_exotic_methods; +static const JSClassExoticMethods js_string_exotic_methods; +static const JSClassExoticMethods js_proxy_exotic_methods; +static const JSClassExoticMethods js_module_ns_exotic_methods; +static JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; + +static void js_trigger_gc(JSRuntime *rt, size_t size) +{ + BOOL force_gc; +#ifdef FORCE_GC_AT_MALLOC + force_gc = TRUE; +#else + force_gc = ((rt->malloc_state.malloc_size + size) > + rt->malloc_gc_threshold); +#endif + if (force_gc) { +#ifdef DUMP_GC + printf("GC: size=%" PRIu64 "\n", + (uint64_t)rt->malloc_state.malloc_size); +#endif + JS_RunGC(rt); + rt->malloc_gc_threshold = rt->malloc_state.malloc_size + + (rt->malloc_state.malloc_size >> 1); + } +} + +static size_t js_malloc_usable_size_unknown(const void *ptr) +{ + return 0; +} + +void *js_malloc_rt(JSRuntime *rt, size_t size) +{ + return rt->mf.js_malloc(&rt->malloc_state, size); +} + +void js_free_rt(JSRuntime *rt, void *ptr) +{ + rt->mf.js_free(&rt->malloc_state, ptr); +} + +void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) +{ + return rt->mf.js_realloc(&rt->malloc_state, ptr, size); +} + +size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) +{ + return rt->mf.js_malloc_usable_size(ptr); +} + +void *js_mallocz_rt(JSRuntime *rt, size_t size) +{ + void *ptr; + ptr = js_malloc_rt(rt, size); + if (!ptr) + return NULL; + return memset(ptr, 0, size); +} + +/* Throw out of memory in case of error */ +void *js_malloc(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_malloc_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +/* Throw out of memory in case of error */ +void *js_mallocz(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_mallocz_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +void js_free(JSContext *ctx, void *ptr) +{ + js_free_rt(ctx->rt, ptr); +} + +/* Throw out of memory in case of error */ +void *js_realloc(JSContext *ctx, void *ptr, size_t size) +{ + void *ret; + ret = js_realloc_rt(ctx->rt, ptr, size); + if (unlikely(!ret && size != 0)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ret; +} + +/* store extra allocated size in *pslack if successful */ +void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack) +{ + void *ret; + ret = js_realloc_rt(ctx->rt, ptr, size); + if (unlikely(!ret && size != 0)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + if (pslack) { + size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret); + *pslack = (new_size > size) ? new_size - size : 0; + } + return ret; +} + +size_t js_malloc_usable_size(JSContext *ctx, const void *ptr) +{ + return js_malloc_usable_size_rt(ctx->rt, ptr); +} + +/* Throw out of memory exception in case of error */ +char *js_strndup(JSContext *ctx, const char *s, size_t n) +{ + char *ptr; + ptr = js_malloc(ctx, n + 1); + if (ptr) { + memcpy(ptr, s, n); + ptr[n] = '\0'; + } + return ptr; +} + +char *js_strdup(JSContext *ctx, const char *str) +{ + return js_strndup(ctx, str, strlen(str)); +} + +static no_inline int js_realloc_array(JSContext *ctx, void **parray, + int elem_size, int *psize, int req_size) +{ + int new_size; + size_t slack; + void *new_array; + /* XXX: potential arithmetic overflow */ + new_size = max_int(req_size, *psize * 3 / 2); + new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack); + if (!new_array) + return -1; + new_size += slack / elem_size; + *psize = new_size; + *parray = new_array; + return 0; +} + +/* resize the array and update its size if req_size > *psize */ +static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size, + int *psize, int req_size) +{ + if (unlikely(req_size > *psize)) + return js_realloc_array(ctx, parray, elem_size, psize, req_size); + else + return 0; +} + +static inline void js_dbuf_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, ctx->rt, (DynBufReallocFunc *)js_realloc_rt); +} + +static void *js_realloc_bytecode_rt(void *opaque, void *ptr, size_t size) +{ + JSRuntime *rt = opaque; + if (size > (INT32_MAX / 2)) { + /* the bytecode cannot be larger than 2G. Leave some slack to + avoid some overflows. */ + return NULL; + } else { + return rt->mf.js_realloc(&rt->malloc_state, ptr, size); + } +} + +static inline void js_dbuf_bytecode_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, ctx->rt, js_realloc_bytecode_rt); +} + +static inline int is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static inline int string_get(const JSString *p, int idx) { + return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx]; +} + +typedef struct JSClassShortDef { + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; +} JSClassShortDef; + +static JSClassShortDef const js_std_class_def[] = { + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */ + { JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */ + { JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */ + { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */ + { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */ + { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */ + { JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */ + { JS_ATOM_Arguments, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARGUMENTS */ + { JS_ATOM_Arguments, js_mapped_arguments_finalizer, js_mapped_arguments_mark }, /* JS_CLASS_MAPPED_ARGUMENTS */ + { JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */ + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_MODULE_NS */ + { JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */ + { JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */ + { JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */ + { JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */ + { JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */ + { JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */ + { JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ + { JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_ARRAY_BUFFER */ + { JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_SHARED_ARRAY_BUFFER */ + { JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */ + { JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT8_ARRAY */ + { JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8_ARRAY */ + { JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT16_ARRAY */ + { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */ + { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */ + { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */ + { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */ + { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */ + { JS_ATOM_Float16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT16_ARRAY */ + { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */ + { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */ + { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */ + { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */ + { JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */ + { JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */ + { JS_ATOM_WeakMap, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKMAP */ + { JS_ATOM_WeakSet, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKSET */ + { JS_ATOM_Iterator, NULL, NULL }, /* JS_CLASS_ITERATOR */ + { JS_ATOM_IteratorConcat, js_iterator_concat_finalizer, js_iterator_concat_mark }, /* JS_CLASS_ITERATOR_CONCAT */ + { JS_ATOM_IteratorHelper, js_iterator_helper_finalizer, js_iterator_helper_mark }, /* JS_CLASS_ITERATOR_HELPER */ + { JS_ATOM_IteratorWrap, js_iterator_wrap_finalizer, js_iterator_wrap_mark }, /* JS_CLASS_ITERATOR_WRAP */ + { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */ + { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ + { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ + { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ + { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ + { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ + { JS_ATOM_Object, js_global_object_finalizer, js_global_object_mark }, /* JS_CLASS_GLOBAL_OBJECT */ +}; + +static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, + int start, int count) +{ + JSClassDef cm_s, *cm = &cm_s; + int i, class_id; + + for(i = 0; i < count; i++) { + class_id = i + start; + memset(cm, 0, sizeof(*cm)); + cm->finalizer = tab[i].finalizer; + cm->gc_mark = tab[i].gc_mark; + if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0) + return -1; + } + return 0; +} + +#if !defined(CONFIG_STACK_CHECK) +/* no stack limitation */ +static inline uintptr_t js_get_stack_pointer(void) +{ + return 0; +} + +static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) +{ + return FALSE; +} +#else +/* Note: OS and CPU dependent */ +static inline uintptr_t js_get_stack_pointer(void) +{ + return (uintptr_t)__builtin_frame_address(0); +} + +static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) +{ + uintptr_t sp; + sp = js_get_stack_pointer() - alloca_size; + return unlikely(sp < rt->stack_limit); +} +#endif + +JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) +{ + JSRuntime *rt; + JSMallocState ms; + + memset(&ms, 0, sizeof(ms)); + ms.opaque = opaque; + ms.malloc_limit = -1; + + rt = mf->js_malloc(&ms, sizeof(JSRuntime)); + if (!rt) + return NULL; + memset(rt, 0, sizeof(*rt)); + rt->mf = *mf; + if (!rt->mf.js_malloc_usable_size) { + /* use dummy function if none provided */ + rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; + } + rt->malloc_state = ms; + rt->malloc_gc_threshold = 256 * 1024; + + init_list_head(&rt->context_list); + init_list_head(&rt->gc_obj_list); + init_list_head(&rt->gc_zero_ref_count_list); + rt->gc_phase = JS_GC_PHASE_NONE; + init_list_head(&rt->weakref_list); + init_list_head(&rt->weakref_keepalive_list); + +#ifdef DUMP_LEAKS + init_list_head(&rt->string_list); +#endif + init_list_head(&rt->job_list); + + if (JS_InitAtoms(rt)) + goto fail; + + /* create the object, array and function classes */ + if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT, + countof(js_std_class_def)) < 0) + goto fail; + rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods; + rt->class_array[JS_CLASS_MAPPED_ARGUMENTS].exotic = &js_arguments_exotic_methods; + rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods; + rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods; + + rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function; + rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_c_function_data_call; + rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function; + rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_generator_function_call; + if (init_shape_hash(rt)) + goto fail; + + rt->stack_size = JS_DEFAULT_STACK_SIZE; + JS_UpdateStackTop(rt); + + rt->current_exception = JS_UNINITIALIZED; + + return rt; + fail: + JS_FreeRuntime(rt); + return NULL; +} + +void *JS_GetRuntimeOpaque(JSRuntime *rt) +{ + return rt->user_opaque; +} + +void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque) +{ + rt->user_opaque = opaque; +} + +/* default memory allocation functions with memory limitation */ +static size_t js_def_malloc_usable_size(const void *ptr) +{ +#if defined(__APPLE__) + return malloc_size(ptr); +#elif defined(_WIN32) + return _msize((void *)ptr); +#elif defined(EMSCRIPTEN) + return 0; +#elif defined(__linux__) || defined(__GLIBC__) + return malloc_usable_size((void *)ptr); +#else + /* change this to `return 0;` if compilation fails */ + return malloc_usable_size((void *)ptr); +#endif +} + +static void *js_def_malloc(JSMallocState *s, size_t size) +{ + void *ptr; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(size != 0); + + if (unlikely(s->malloc_size + size > s->malloc_limit)) + return NULL; + + ptr = malloc(size); + if (!ptr) + return NULL; + + s->malloc_count++; + s->malloc_size += js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + return ptr; +} + +static void js_def_free(JSMallocState *s, void *ptr) +{ + if (!ptr) + return; + + s->malloc_count--; + s->malloc_size -= js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + free(ptr); +} + +static void *js_def_realloc(JSMallocState *s, void *ptr, size_t size) +{ + size_t old_size; + + if (!ptr) { + if (size == 0) + return NULL; + return js_def_malloc(s, size); + } + old_size = js_def_malloc_usable_size(ptr); + if (size == 0) { + s->malloc_count--; + s->malloc_size -= old_size + MALLOC_OVERHEAD; + free(ptr); + return NULL; + } + if (s->malloc_size + size - old_size > s->malloc_limit) + return NULL; + + ptr = realloc(ptr, size); + if (!ptr) + return NULL; + + s->malloc_size += js_def_malloc_usable_size(ptr) - old_size; + return ptr; +} + +static const JSMallocFunctions def_malloc_funcs = { + js_def_malloc, + js_def_free, + js_def_realloc, + js_def_malloc_usable_size, +}; + +JSRuntime *JS_NewRuntime(void) +{ + return JS_NewRuntime2(&def_malloc_funcs, NULL); +} + +void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) +{ + rt->malloc_state.malloc_limit = limit; +} + +/* use -1 to disable automatic GC */ +void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold) +{ + rt->malloc_gc_threshold = gc_threshold; +} + +#define malloc(s) malloc_is_forbidden(s) +#define free(p) free_is_forbidden(p) +#define realloc(p,s) realloc_is_forbidden(p,s) + +void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque) +{ + rt->interrupt_handler = cb; + rt->interrupt_opaque = opaque; +} + +void JS_SetCanBlock(JSRuntime *rt, BOOL can_block) +{ + rt->can_block = can_block; +} + +void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, + const JSSharedArrayBufferFunctions *sf) +{ + rt->sab_funcs = *sf; +} + +void JS_SetStripInfo(JSRuntime *rt, int flags) +{ + rt->strip_flags = flags; +} + +int JS_GetStripInfo(JSRuntime *rt) +{ + return rt->strip_flags; +} + +/* return 0 if OK, < 0 if exception */ +int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = ctx->rt; + JSJobEntry *e; + int i; + + e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); + if (!e) + return -1; + e->realm = JS_DupContext(ctx); + e->job_func = job_func; + e->argc = argc; + for(i = 0; i < argc; i++) { + e->argv[i] = JS_DupValue(ctx, argv[i]); + } + list_add_tail(&e->link, &rt->job_list); + return 0; +} + +BOOL JS_IsJobPending(JSRuntime *rt) +{ + return !list_empty(&rt->job_list); +} + +/* return < 0 if exception, 0 if no job pending, 1 if a job was + executed successfully. The context of the job is stored in '*pctx' + if pctx != NULL. It may be NULL if the context was already + destroyed or if no job was pending. The 'pctx' parameter is now + absolete. */ +int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) +{ + JSContext *ctx; + JSJobEntry *e; + JSValue res; + int i, ret; + + if (list_empty(&rt->job_list)) { + if (pctx) + *pctx = NULL; + return 0; + } + + /* get the first pending job and execute it */ + e = list_entry(rt->job_list.next, JSJobEntry, link); + list_del(&e->link); + ctx = e->realm; + res = e->job_func(ctx, e->argc, (JSValueConst *)e->argv); + for(i = 0; i < e->argc; i++) + JS_FreeValue(ctx, e->argv[i]); + if (JS_IsException(res)) + ret = -1; + else + ret = 1; + JS_FreeValue(ctx, res); + js_free(ctx, e); + if (pctx) { + if (ctx->header.ref_count > 1) + *pctx = ctx; + else + *pctx = NULL; + } + JS_FreeContext(ctx); + return ret; +} + +static inline uint32_t atom_get_free(const JSAtomStruct *p) +{ + return (uintptr_t)p >> 1; +} + +static inline BOOL atom_is_free(const JSAtomStruct *p) +{ + return (uintptr_t)p & 1; +} + +static inline JSAtomStruct *atom_set_free(uint32_t v) +{ + return (JSAtomStruct *)(((uintptr_t)v << 1) | 1); +} + +/* Note: the string contents are uninitialized */ +static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char) +{ + JSString *str; + str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); + if (unlikely(!str)) + return NULL; + str->header.ref_count = 1; + str->is_wide_char = is_wide_char; + str->len = max_len; + str->atom_type = 0; + str->hash = 0; /* optional but costless */ + str->hash_next = 0; /* optional */ +#ifdef DUMP_LEAKS + list_add_tail(&str->link, &rt->string_list); +#endif + return str; +} + +static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) +{ + JSString *p; + p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char); + if (unlikely(!p)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return p; +} + +/* same as JS_FreeValueRT() but faster */ +static inline void js_free_string(JSRuntime *rt, JSString *str) +{ + if (--str->header.ref_count <= 0) { + if (str->atom_type) { + JS_FreeAtomStruct(rt, str); + } else { +#ifdef DUMP_LEAKS + list_del(&str->link); +#endif + js_free_rt(rt, str); + } + } +} + +void JS_SetRuntimeInfo(JSRuntime *rt, const char *s) +{ + if (rt) + rt->rt_info = s; +} + +void JS_FreeRuntime(JSRuntime *rt) +{ + struct list_head *el, *el1; + int i; + + JS_FreeValueRT(rt, rt->current_exception); + + list_for_each_safe(el, el1, &rt->job_list) { + JSJobEntry *e = list_entry(el, JSJobEntry, link); + for(i = 0; i < e->argc; i++) + JS_FreeValueRT(rt, e->argv[i]); + JS_FreeContext(e->realm); + js_free_rt(rt, e); + } + init_list_head(&rt->job_list); + JS_ClearWeakRefKeepAlives(rt); + + /* don't remove the weak objects to avoid create new jobs with + FinalizationRegistry */ + JS_RunGCInternal(rt, FALSE); + +#ifdef DUMP_LEAKS + /* leaking objects */ + { + BOOL header_done; + JSGCObjectHeader *p; + int count; + + /* remove the internal refcounts to display only the object + referenced externally */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + p->mark = 0; + } + gc_decref(rt); + + header_done = FALSE; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (p->ref_count != 0) { + if (!header_done) { + printf("Object leaks:\n"); + JS_DumpObjectHeader(rt); + header_done = TRUE; + } + JS_DumpGCObject(rt, p); + } + } + + count = 0; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (p->ref_count == 0) { + count++; + } + } + if (count != 0) + printf("Secondary object leaks: %d\n", count); + } +#endif + assert(list_empty(&rt->gc_obj_list)); + assert(list_empty(&rt->weakref_list)); + + /* free the classes */ + for(i = 0; i < rt->class_count; i++) { + JSClass *cl = &rt->class_array[i]; + if (cl->class_id != 0) { + JS_FreeAtomRT(rt, cl->class_name); + } + } + js_free_rt(rt, rt->class_array); + +#ifdef DUMP_LEAKS + /* only the atoms defined in JS_InitAtoms() should be left */ + { + BOOL header_done = FALSE; + + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p) /* && p->str*/) { + if (i >= JS_ATOM_END || p->header.ref_count != 1) { + if (!header_done) { + header_done = TRUE; + if (rt->rt_info) { + printf("%s:1: atom leakage:", rt->rt_info); + } else { + printf("Atom leaks:\n" + " %6s %6s %s\n", + "ID", "REFCNT", "NAME"); + } + } + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u %6u ", i, p->header.ref_count); + } + switch (p->atom_type) { + case JS_ATOM_TYPE_STRING: + JS_DumpString(rt, p); + break; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + printf("Symbol.for("); + JS_DumpString(rt, p); + printf(")"); + break; + case JS_ATOM_TYPE_SYMBOL: + if (p->hash != JS_ATOM_HASH_PRIVATE) { + printf("Symbol("); + JS_DumpString(rt, p); + printf(")"); + } else { + printf("Private("); + JS_DumpString(rt, p); + printf(")"); + } + break; + } + if (rt->rt_info) { + printf(":%u", p->header.ref_count); + } else { + printf("\n"); + } + } + } + } + if (rt->rt_info && header_done) + printf("\n"); + } +#endif + + /* free the atoms */ + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { +#ifdef DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + } + } + js_free_rt(rt, rt->atom_array); + js_free_rt(rt, rt->atom_hash); + js_free_rt(rt, rt->shape_hash); +#ifdef DUMP_LEAKS + if (!list_empty(&rt->string_list)) { + if (rt->rt_info) { + printf("%s:1: string leakage:", rt->rt_info); + } else { + printf("String leaks:\n" + " %6s %s\n", + "REFCNT", "VALUE"); + } + list_for_each_safe(el, el1, &rt->string_list) { + JSString *str = list_entry(el, JSString, link); + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u ", str->header.ref_count); + } + JS_DumpString(rt, str); + if (rt->rt_info) { + printf(":%u", str->header.ref_count); + } else { + printf("\n"); + } + list_del(&str->link); + js_free_rt(rt, str); + } + if (rt->rt_info) + printf("\n"); + } + { + JSMallocState *s = &rt->malloc_state; + if (s->malloc_count > 1) { + if (rt->rt_info) + printf("%s:1: ", rt->rt_info); + printf("Memory leak: %"PRIu64" bytes lost in %"PRIu64" block%s\n", + (uint64_t)(s->malloc_size - sizeof(JSRuntime)), + (uint64_t)(s->malloc_count - 1), &"s"[s->malloc_count == 2]); + } + } +#endif + + { + JSMallocState ms = rt->malloc_state; + rt->mf.js_free(&ms, rt); + } +} + +JSContext *JS_NewContextRaw(JSRuntime *rt) +{ + JSContext *ctx; + int i; + + ctx = js_mallocz_rt(rt, sizeof(JSContext)); + if (!ctx) + return NULL; + ctx->header.ref_count = 1; + add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); + + ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * + rt->class_count); + if (!ctx->class_proto) { + js_free_rt(rt, ctx); + return NULL; + } + ctx->rt = rt; + list_add_tail(&ctx->link, &rt->context_list); + for(i = 0; i < rt->class_count; i++) + ctx->class_proto[i] = JS_NULL; + ctx->array_ctor = JS_NULL; + ctx->iterator_ctor = JS_NULL; + ctx->regexp_ctor = JS_NULL; + ctx->promise_ctor = JS_NULL; + init_list_head(&ctx->loaded_modules); + + if (JS_AddIntrinsicBasicObjects(ctx)) { + JS_FreeContext(ctx); + return NULL; + } + return ctx; +} + +JSContext *JS_NewContext(JSRuntime *rt) +{ + JSContext *ctx; + + ctx = JS_NewContextRaw(rt); + if (!ctx) + return NULL; + + if (JS_AddIntrinsicBaseObjects(ctx) || + JS_AddIntrinsicDate(ctx) || + JS_AddIntrinsicEval(ctx) || + JS_AddIntrinsicStringNormalize(ctx) || + JS_AddIntrinsicRegExp(ctx) || + JS_AddIntrinsicJSON(ctx) || + JS_AddIntrinsicProxy(ctx) || + JS_AddIntrinsicMapSet(ctx) || + JS_AddIntrinsicTypedArrays(ctx) || + JS_AddIntrinsicPromise(ctx) || + JS_AddIntrinsicWeakRef(ctx)) { + JS_FreeContext(ctx); + return NULL; + } + return ctx; +} + +void *JS_GetContextOpaque(JSContext *ctx) +{ + return ctx->user_opaque; +} + +void JS_SetContextOpaque(JSContext *ctx, void *opaque) +{ + ctx->user_opaque = opaque; +} + +/* set the new value and free the old value after (freeing the value + can reallocate the object data) */ +static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val) +{ + JSValue old_val; + old_val = *pval; + *pval = new_val; + JS_FreeValue(ctx, old_val); +} + +void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj) +{ + JSRuntime *rt = ctx->rt; + assert(class_id < rt->class_count); + set_value(ctx, &ctx->class_proto[class_id], obj); +} + +JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id) +{ + JSRuntime *rt = ctx->rt; + assert(class_id < rt->class_count); + return JS_DupValue(ctx, ctx->class_proto[class_id]); +} + +typedef enum JSFreeModuleEnum { + JS_FREE_MODULE_ALL, + JS_FREE_MODULE_NOT_RESOLVED, +} JSFreeModuleEnum; + +/* XXX: would be more efficient with separate module lists */ +static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) +{ + struct list_head *el, *el1; + list_for_each_safe(el, el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + if (flag == JS_FREE_MODULE_ALL || + (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) { + /* warning: the module may be referenced elsewhere. It + could be simpler to use an array instead of a list for + 'ctx->loaded_modules' */ + list_del(&m->link); + m->link.prev = NULL; + m->link.next = NULL; + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); + } + } +} + +JSContext *JS_DupContext(JSContext *ctx) +{ + ctx->header.ref_count++; + return ctx; +} + +/* used by the GC */ +static void JS_MarkContext(JSRuntime *rt, JSContext *ctx, + JS_MarkFunc *mark_func) +{ + int i; + struct list_head *el; + + list_for_each(el, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + JS_MarkValue(rt, JS_MKPTR(JS_TAG_MODULE, m), mark_func); + } + + JS_MarkValue(rt, ctx->global_obj, mark_func); + JS_MarkValue(rt, ctx->global_var_obj, mark_func); + + JS_MarkValue(rt, ctx->throw_type_error, mark_func); + JS_MarkValue(rt, ctx->eval_obj, mark_func); + + JS_MarkValue(rt, ctx->array_proto_values, mark_func); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_MarkValue(rt, ctx->native_error_proto[i], mark_func); + } + for(i = 0; i < rt->class_count; i++) { + JS_MarkValue(rt, ctx->class_proto[i], mark_func); + } + JS_MarkValue(rt, ctx->iterator_ctor, mark_func); + JS_MarkValue(rt, ctx->async_iterator_proto, mark_func); + JS_MarkValue(rt, ctx->promise_ctor, mark_func); + JS_MarkValue(rt, ctx->array_ctor, mark_func); + JS_MarkValue(rt, ctx->regexp_ctor, mark_func); + JS_MarkValue(rt, ctx->function_ctor, mark_func); + JS_MarkValue(rt, ctx->function_proto, mark_func); + + if (ctx->array_shape) + mark_func(rt, &ctx->array_shape->header); + + if (ctx->arguments_shape) + mark_func(rt, &ctx->arguments_shape->header); + + if (ctx->mapped_arguments_shape) + mark_func(rt, &ctx->mapped_arguments_shape->header); + + if (ctx->regexp_shape) + mark_func(rt, &ctx->regexp_shape->header); + + if (ctx->regexp_result_shape) + mark_func(rt, &ctx->regexp_result_shape->header); +} + +void JS_FreeContext(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + int i; + + if (--ctx->header.ref_count > 0) + return; + assert(ctx->header.ref_count == 0); + +#ifdef DUMP_ATOMS + JS_DumpAtoms(ctx->rt); +#endif +#ifdef DUMP_SHAPES + JS_DumpShapes(ctx->rt); +#endif +#ifdef DUMP_OBJECTS + { + struct list_head *el; + JSGCObjectHeader *p; + printf("JSObjects: {\n"); + JS_DumpObjectHeader(ctx->rt); + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + JS_DumpGCObject(rt, p); + } + printf("}\n"); + } +#endif +#ifdef DUMP_MEM + { + JSMemoryUsage stats; + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(stdout, &stats, rt); + } +#endif + + js_free_modules(ctx, JS_FREE_MODULE_ALL); + + JS_FreeValue(ctx, ctx->global_obj); + JS_FreeValue(ctx, ctx->global_var_obj); + + JS_FreeValue(ctx, ctx->throw_type_error); + JS_FreeValue(ctx, ctx->eval_obj); + + JS_FreeValue(ctx, ctx->array_proto_values); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_FreeValue(ctx, ctx->native_error_proto[i]); + } + for(i = 0; i < rt->class_count; i++) { + JS_FreeValue(ctx, ctx->class_proto[i]); + } + js_free_rt(rt, ctx->class_proto); + JS_FreeValue(ctx, ctx->iterator_ctor); + JS_FreeValue(ctx, ctx->async_iterator_proto); + JS_FreeValue(ctx, ctx->promise_ctor); + JS_FreeValue(ctx, ctx->array_ctor); + JS_FreeValue(ctx, ctx->regexp_ctor); + JS_FreeValue(ctx, ctx->function_ctor); + JS_FreeValue(ctx, ctx->function_proto); + + js_free_shape_null(ctx->rt, ctx->array_shape); + js_free_shape_null(ctx->rt, ctx->arguments_shape); + js_free_shape_null(ctx->rt, ctx->mapped_arguments_shape); + js_free_shape_null(ctx->rt, ctx->regexp_shape); + js_free_shape_null(ctx->rt, ctx->regexp_result_shape); + + list_del(&ctx->link); + remove_gc_object(&ctx->header); + js_free_rt(ctx->rt, ctx); +} + +JSRuntime *JS_GetRuntime(JSContext *ctx) +{ + return ctx->rt; +} + +static void update_stack_limit(JSRuntime *rt) +{ + if (rt->stack_size == 0) { + rt->stack_limit = 0; /* no limit */ + } else { + rt->stack_limit = rt->stack_top - rt->stack_size; + } +} + +void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size) +{ + rt->stack_size = stack_size; + update_stack_limit(rt); +} + +void JS_UpdateStackTop(JSRuntime *rt) +{ + rt->stack_top = js_get_stack_pointer(); + update_stack_limit(rt); +} + +static inline BOOL is_strict_mode(JSContext *ctx) +{ + JSStackFrame *sf = ctx->rt->current_stack_frame; + return (sf && (sf->js_mode & JS_MODE_STRICT)); +} + +/* JSAtom support */ + +#define JS_ATOM_TAG_INT (1U << 31) +#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) +#define JS_ATOM_MAX ((1U << 30) - 1) + +/* return the max count from the hash size */ +#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) + +static inline BOOL __JS_AtomIsConst(JSAtom v) +{ +#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1 + return (int32_t)v <= 0; +#else + return (int32_t)v < JS_ATOM_END; +#endif +} + +static inline BOOL __JS_AtomIsTaggedInt(JSAtom v) +{ + return (v & JS_ATOM_TAG_INT) != 0; +} + +static inline JSAtom __JS_AtomFromUInt32(uint32_t v) +{ + return v | JS_ATOM_TAG_INT; +} + +static inline uint32_t __JS_AtomToUInt32(JSAtom atom) +{ + return atom & ~JS_ATOM_TAG_INT; +} + +static inline int is_num(int c) +{ + return c >= '0' && c <= '9'; +} + +/* return TRUE if the string is a number n with 0 <= n <= 2^32-1 */ +static inline BOOL is_num_string(uint32_t *pval, const JSString *p) +{ + uint32_t n; + uint64_t n64; + int c, i, len; + + len = p->len; + if (len == 0 || len > 10) + return FALSE; + c = string_get(p, 0); + if (is_num(c)) { + if (c == '0') { + if (len != 1) + return FALSE; + n = 0; + } else { + n = c - '0'; + for(i = 1; i < len; i++) { + c = string_get(p, i); + if (!is_num(c)) + return FALSE; + n64 = (uint64_t)n * 10 + (c - '0'); + if ((n64 >> 32) != 0) + return FALSE; + n = n64; + } + } + *pval = n; + return TRUE; + } else { + return FALSE; + } +} + +/* XXX: could use faster version ? */ +static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h; +} + +static inline uint32_t hash_string16(const uint16_t *str, + size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h; +} + +static uint32_t hash_string(const JSString *str, uint32_t h) +{ + if (str->is_wide_char) + h = hash_string16(str->u.str16, str->len, h); + else + h = hash_string8(str->u.str8, str->len, h); + return h; +} + +static uint32_t hash_string_rope(JSValueConst val, uint32_t h) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + return hash_string(JS_VALUE_GET_STRING(val), h); + } else { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + h = hash_string_rope(r->left, h); + return hash_string_rope(r->right, h); + } +} + +static __maybe_unused void JS_DumpChar(FILE *fo, int c, int sep) +{ + if (c == sep || c == '\\') { + fputc('\\', fo); + fputc(c, fo); + } else if (c >= ' ' && c <= 126) { + fputc(c, fo); + } else if (c == '\n') { + fputc('\\', fo); + fputc('n', fo); + } else { + fprintf(fo, "\\u%04x", c); + } +} + +static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p) +{ + int i, sep; + + if (p == NULL) { + printf(""); + return; + } + printf("%d", p->header.ref_count); + sep = (p->header.ref_count == 1) ? '\"' : '\''; + putchar(sep); + for(i = 0; i < p->len; i++) { + JS_DumpChar(stdout, string_get(p, i), sep); + } + putchar(sep); +} + +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) +{ + JSAtomStruct *p; + int h, i; + /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */ + printf("JSAtom count=%d size=%d hash_size=%d:\n", + rt->atom_count, rt->atom_size, rt->atom_hash_size); + printf("JSAtom hash table: {\n"); + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + if (h) { + printf(" %d:", i); + while (h) { + p = rt->atom_array[h]; + printf(" "); + JS_DumpString(rt, p); + h = p->hash_next; + } + printf("\n"); + } + } + printf("}\n"); + printf("JSAtom table: {\n"); + for(i = 0; i < rt->atom_size; i++) { + p = rt->atom_array[i]; + if (!atom_is_free(p)) { + printf(" %d: { %d %08x ", i, p->atom_type, p->hash); + if (!(p->len == 0 && p->is_wide_char != 0)) + JS_DumpString(rt, p); + printf(" %d }\n", p->hash_next); + } + } + printf("}\n"); +} + +static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size) +{ + JSAtomStruct *p; + uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash; + + assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */ + new_hash_mask = new_hash_size - 1; + new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size); + if (!new_hash) + return -1; + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + while (h != 0) { + p = rt->atom_array[h]; + hash_next1 = p->hash_next; + /* add in new hash table */ + j = p->hash & new_hash_mask; + p->hash_next = new_hash[j]; + new_hash[j] = h; + h = hash_next1; + } + } + js_free_rt(rt, rt->atom_hash); + rt->atom_hash = new_hash; + rt->atom_hash_size = new_hash_size; + rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size); + // JS_DumpAtoms(rt); + return 0; +} + +static int JS_InitAtoms(JSRuntime *rt) +{ + int i, len, atom_type; + const char *p; + + rt->atom_hash_size = 0; + rt->atom_hash = NULL; + rt->atom_count = 0; + rt->atom_size = 0; + rt->atom_free_index = 0; + if (JS_ResizeAtomHash(rt, 512)) /* there are at least 504 predefined atoms */ + return -1; + + p = js_atom_init; + for(i = 1; i < JS_ATOM_END; i++) { + if (i == JS_ATOM_Private_brand) + atom_type = JS_ATOM_TYPE_PRIVATE; + else if (i >= JS_ATOM_Symbol_toPrimitive) + atom_type = JS_ATOM_TYPE_SYMBOL; + else + atom_type = JS_ATOM_TYPE_STRING; + len = strlen(p); + if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL) + return -1; + p = p + len + 1; + } + return 0; +} + +static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) +{ + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + p = rt->atom_array[v]; + p->header.ref_count++; + } + return v; +} + +JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + rt = ctx->rt; + p = rt->atom_array[v]; + p->header.ref_count++; + } + return v; +} + +static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return JS_ATOM_KIND_STRING; + p = rt->atom_array[v]; + switch(p->atom_type) { + case JS_ATOM_TYPE_STRING: + return JS_ATOM_KIND_STRING; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + return JS_ATOM_KIND_SYMBOL; + case JS_ATOM_TYPE_SYMBOL: + if (p->hash == JS_ATOM_HASH_PRIVATE) + return JS_ATOM_KIND_PRIVATE; + else + return JS_ATOM_KIND_SYMBOL; + default: + abort(); + } +} + +static BOOL JS_AtomIsString(JSContext *ctx, JSAtom v) +{ + return JS_AtomGetKind(ctx, v) == JS_ATOM_KIND_STRING; +} + +static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p) +{ + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p1; + + i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)]; + p1 = rt->atom_array[i]; + while (p1 != p) { + assert(i != 0); + i = p1->hash_next; + p1 = rt->atom_array[i]; + } + } + return i; +} + +/* string case (internal). Return JS_ATOM_NULL if error. 'str' is + freed. */ +static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + int len; + +#if 0 + printf("__JS_NewAtom: "); JS_DumpString(rt, str); printf("\n"); +#endif + if (atom_type < JS_ATOM_TYPE_SYMBOL) { + /* str is not NULL */ + if (str->atom_type == atom_type) { + /* str is the atom, return its index */ + i = js_get_atom_index(rt, str); + /* reduce string refcount and increase atom's unless constant */ + if (__JS_AtomIsConst(i)) + str->header.ref_count--; + return i; + } + /* try and locate an already registered atom */ + len = str->len; + h = hash_string(str, atom_type); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == atom_type && + p->len == len && + js_string_memcmp(p, 0, str, 0, len) == 0) { + if (!__JS_AtomIsConst(i)) + p->header.ref_count++; + goto done; + } + i = p->hash_next; + } + } else { + h1 = 0; /* avoid warning */ + if (atom_type == JS_ATOM_TYPE_SYMBOL) { + h = 0; + } else { + h = JS_ATOM_HASH_PRIVATE; + atom_type = JS_ATOM_TYPE_SYMBOL; + } + } + + if (rt->atom_free_index == 0) { + /* allow new atom entries */ + uint32_t new_size, start; + JSAtomStruct **new_array; + + /* alloc new with size progression 3/2: + 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 + preallocating space for predefined atoms (at least 504). + */ + new_size = max_int(711, rt->atom_size * 3 / 2); + if (new_size > JS_ATOM_MAX) + goto fail; + /* XXX: should use realloc2 to use slack space */ + new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size); + if (!new_array) + goto fail; + /* Note: the atom 0 is not used */ + start = rt->atom_size; + if (start == 0) { + /* JS_ATOM_NULL entry */ + p = js_mallocz_rt(rt, sizeof(JSAtomStruct)); + if (!p) { + js_free_rt(rt, new_array); + goto fail; + } + p->header.ref_count = 1; /* not refcounted */ + p->atom_type = JS_ATOM_TYPE_SYMBOL; +#ifdef DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + new_array[0] = p; + rt->atom_count++; + start = 1; + } + rt->atom_size = new_size; + rt->atom_array = new_array; + rt->atom_free_index = start; + for(i = start; i < new_size; i++) { + uint32_t next; + if (i == (new_size - 1)) + next = 0; + else + next = i + 1; + rt->atom_array[i] = atom_set_free(next); + } + } + + if (str) { + if (str->atom_type == 0) { + p = str; + p->atom_type = atom_type; + } else { + p = js_malloc_rt(rt, sizeof(JSString) + + (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + if (unlikely(!p)) + goto fail; + p->header.ref_count = 1; + p->is_wide_char = str->is_wide_char; + p->len = str->len; +#ifdef DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + memcpy(p->u.str8, str->u.str8, (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + js_free_string(rt, str); + } + } else { + p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ + if (!p) + return JS_ATOM_NULL; + p->header.ref_count = 1; + p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ + p->len = 0; +#ifdef DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + } + + /* use an already free entry */ + i = rt->atom_free_index; + rt->atom_free_index = atom_get_free(rt->atom_array[i]); + rt->atom_array[i] = p; + + p->hash = h; + p->hash_next = i; /* atom_index */ + p->atom_type = atom_type; + + rt->atom_count++; + + if (atom_type != JS_ATOM_TYPE_SYMBOL) { + p->hash_next = rt->atom_hash[h1]; + rt->atom_hash[h1] = i; + if (unlikely(rt->atom_count >= rt->atom_count_resize)) + JS_ResizeAtomHash(rt, rt->atom_hash_size * 2); + } + + // JS_DumpAtoms(rt); + return i; + + fail: + i = JS_ATOM_NULL; + done: + if (str) + js_free_string(rt, str); + return i; +} + +/* only works with zero terminated 8 bit strings */ +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type) +{ + JSString *p; + p = js_alloc_string_rt(rt, len, 0); + if (!p) + return JS_ATOM_NULL; + memcpy(p->u.str8, str, len); + p->u.str8[len] = '\0'; + return __JS_NewAtom(rt, p, atom_type); +} + +/* Warning: str must be ASCII only */ +static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, + int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + + h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == JS_ATOM_TYPE_STRING && + p->len == len && + p->is_wide_char == 0 && + memcmp(p->u.str8, str, len) == 0) { + if (!__JS_AtomIsConst(i)) + p->header.ref_count++; + return i; + } + i = p->hash_next; + } + return JS_ATOM_NULL; +} + +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) +{ +#if 0 /* JS_ATOM_NULL is not refcounted: __JS_AtomIsConst() includes 0 */ + if (unlikely(i == JS_ATOM_NULL)) { + p->header.ref_count = INT32_MAX / 2; + return; + } +#endif + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p0, *p1; + uint32_t h0; + + h0 = p->hash & (rt->atom_hash_size - 1); + i = rt->atom_hash[h0]; + p1 = rt->atom_array[i]; + if (p1 == p) { + rt->atom_hash[h0] = p1->hash_next; + } else { + for(;;) { + assert(i != 0); + p0 = p1; + i = p1->hash_next; + p1 = rt->atom_array[i]; + if (p1 == p) { + p0->hash_next = p1->hash_next; + break; + } + } + } + } + /* insert in free atom list */ + rt->atom_array[i] = atom_set_free(rt->atom_free_index); + rt->atom_free_index = i; + /* free the string structure */ +#ifdef DUMP_LEAKS + list_del(&p->link); +#endif + if (p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash != JS_ATOM_HASH_PRIVATE && p->hash != 0) { + /* live weak references are still present on this object: keep + it */ + } else { + js_free_rt(rt, p); + } + rt->atom_count--; + assert(rt->atom_count >= 0); +} + +static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) +{ + JSAtomStruct *p; + + p = rt->atom_array[i]; + if (--p->header.ref_count > 0) + return; + JS_FreeAtomStruct(rt, p); +} + +/* Warning: 'p' is freed */ +static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p) +{ + JSRuntime *rt = ctx->rt; + uint32_t n; + if (is_num_string(&n, p)) { + if (n <= JS_ATOM_MAX_INT) { + js_free_string(rt, p); + return __JS_AtomFromUInt32(n); + } + } + /* XXX: should generate an exception */ + return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING); +} + +/* XXX: optimize */ +static size_t count_ascii(const uint8_t *buf, size_t len) +{ + const uint8_t *p, *p_end; + p = buf; + p_end = buf + len; + while (p < p_end && *p < 128) + p++; + return p - buf; +} + +/* str is UTF-8 encoded */ +JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) +{ + JSValue val; + + if (len == 0 || + (!is_digit(*str) && + count_ascii((const uint8_t *)str, len) == len)) { + JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING); + if (atom) + return atom; + } + val = JS_NewStringLen(ctx, str, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val)); +} + +JSAtom JS_NewAtom(JSContext *ctx, const char *str) +{ + return JS_NewAtomLen(ctx, str, strlen(str)); +} + +JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n) +{ + if (n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32(n); + } else { + char buf[11]; + JSValue val; + size_t len; + len = u32toa(buf, n); + val = js_new_string8_len(ctx, buf, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n) +{ + if ((uint64_t)n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32((uint32_t)n); + } else { + char buf[24]; + JSValue val; + size_t len; + len = i64toa(buf, n); + val = js_new_string8_len(ctx, buf, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +/* 'p' is freed */ +static JSValue JS_NewSymbol(JSContext *ctx, JSString *p, int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSAtom atom; + atom = __JS_NewAtom(rt, p, atom_type); + if (atom == JS_ATOM_NULL) + return JS_ThrowOutOfMemory(ctx); + return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]); +} + +/* descr must be a non-numeric string atom */ +static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr, + int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSString *p; + + assert(!__JS_AtomIsTaggedInt(descr)); + assert(descr < rt->atom_size); + p = rt->atom_array[descr]; + JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); + return JS_NewSymbol(ctx, p, atom_type); +} + +#define ATOM_GET_STR_BUF_SIZE 64 + +/* Should only be used for debug. */ +static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, + JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom)); + } else { + JSAtomStruct *p; + assert(atom < rt->atom_size); + if (atom == JS_ATOM_NULL) { + snprintf(buf, buf_size, ""); + } else { + int i, c; + char *q; + JSString *str; + + q = buf; + p = rt->atom_array[atom]; + assert(!atom_is_free(p)); + str = p; + if (str) { + if (!str->is_wide_char) { + /* special case ASCII strings */ + c = 0; + for(i = 0; i < str->len; i++) { + c |= str->u.str8[i]; + } + if (c < 0x80) + return (const char *)str->u.str8; + } + for(i = 0; i < str->len; i++) { + c = string_get(str, i); + if ((q - buf) >= buf_size - UTF8_CHAR_LEN_MAX) + break; + if (c < 128) { + *q++ = c; + } else { + q += unicode_to_utf8((uint8_t *)q, c); + } + } + } + *q = '\0'; + } + } + return buf; +} + +static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom) +{ + return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom); +} + +static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + + if (__JS_AtomIsTaggedInt(atom)) { + size_t len = u32toa(buf, __JS_AtomToUInt32(atom)); + return js_new_string8_len(ctx, buf, len); + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING) { + goto ret_string; + } else if (force_string) { + if (p->len == 0 && p->is_wide_char != 0) { + /* no description string */ + p = rt->atom_array[JS_ATOM_empty_string]; + } + ret_string: + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); + } else { + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_SYMBOL, p)); + } + } +} + +JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, FALSE); +} + +JSValue JS_AtomToString(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, TRUE); +} + +/* return TRUE if the atom is an array index (i.e. 0 <= index <= + 2^32-2 and return its value */ +static BOOL JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + *pval = __JS_AtomToUInt32(atom); + return TRUE; + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + uint32_t val; + + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING && + is_num_string(&val, p) && val != -1) { + *pval = val; + return TRUE; + } else { + *pval = 0; + return FALSE; + } + } +} + +/* This test must be fast if atom is not a numeric index (e.g. a + method name). Return JS_UNDEFINED if not a numeric + index. JS_EXCEPTION can also be returned. */ +static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) +{ + JSRuntime *rt = ctx->rt; + JSAtomStruct *p1; + JSString *p; + int c, ret; + JSValue num, str; + + if (__JS_AtomIsTaggedInt(atom)) + return JS_NewInt32(ctx, __JS_AtomToUInt32(atom)); + assert(atom < rt->atom_size); + p1 = rt->atom_array[atom]; + if (p1->atom_type != JS_ATOM_TYPE_STRING) + return JS_UNDEFINED; + switch(atom) { + case JS_ATOM_minus_zero: + return __JS_NewFloat64(ctx, -0.0); + case JS_ATOM_Infinity: + return __JS_NewFloat64(ctx, INFINITY); + case JS_ATOM_minus_Infinity: + return __JS_NewFloat64(ctx, -INFINITY); + case JS_ATOM_NaN: + return __JS_NewFloat64(ctx, NAN); + default: + break; + } + p = p1; + if (p->len == 0) + return JS_UNDEFINED; + c = string_get(p, 0); + if (!is_num(c) && c != '-') + return JS_UNDEFINED; + /* this is ECMA CanonicalNumericIndexString primitive */ + num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p)); + if (JS_IsException(num)) + return num; + str = JS_ToString(ctx, num); + if (JS_IsException(str)) { + JS_FreeValue(ctx, num); + return str; + } + ret = js_string_eq(ctx, p, JS_VALUE_GET_STRING(str)); + JS_FreeValue(ctx, str); + if (ret) { + return num; + } else { + JS_FreeValue(ctx, num); + return JS_UNDEFINED; + } +} + +/* return -1 if exception or TRUE/FALSE */ +static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom) +{ + JSValue num; + num = JS_AtomIsNumericIndex1(ctx, atom); + if (likely(JS_IsUndefined(num))) + return FALSE; + if (JS_IsException(num)) + return -1; + JS_FreeValue(ctx, num); + return TRUE; +} + +void JS_FreeAtom(JSContext *ctx, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(ctx->rt, v); +} + +void JS_FreeAtomRT(JSRuntime *rt, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(rt, v); +} + +/* return TRUE if 'v' is a symbol with a string description */ +static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return FALSE; + p = rt->atom_array[v]; + return (((p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash != JS_ATOM_HASH_PRIVATE) || + p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && + !(p->len == 0 && p->is_wide_char != 0)); +} + +/* free with JS_FreeCString() */ +const char *JS_AtomToCStringLen(JSContext *ctx, size_t *plen, JSAtom atom) +{ + JSValue str; + const char *cstr; + + str = JS_AtomToString(ctx, atom); + if (JS_IsException(str)) { + if (plen) + *plen = 0; + return NULL; + } + cstr = JS_ToCStringLen(ctx, plen, str); + JS_FreeValue(ctx, str); + return cstr; +} + +/* return a string atom containing name concatenated with str1 */ +static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1) +{ + JSValue str; + JSAtom atom; + const char *cstr; + char *cstr2; + size_t len, len1; + + str = JS_AtomToString(ctx, name); + if (JS_IsException(str)) + return JS_ATOM_NULL; + cstr = JS_ToCStringLen(ctx, &len, str); + if (!cstr) + goto fail; + len1 = strlen(str1); + cstr2 = js_malloc(ctx, len + len1 + 1); + if (!cstr2) + goto fail; + memcpy(cstr2, cstr, len); + memcpy(cstr2 + len, str1, len1); + cstr2[len + len1] = '\0'; + atom = JS_NewAtomLen(ctx, cstr2, len + len1); + js_free(ctx, cstr2); + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return atom; + fail: + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return JS_ATOM_NULL; +} + +static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) +{ + char buf[16]; + size_t len; + len = u32toa(buf, n); + buf[len] = '\0'; + return js_atom_concat_str(ctx, name, buf); +} + +static inline BOOL JS_IsEmptyString(JSValueConst v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0; +} + +/* JSClass support */ + +#ifdef CONFIG_ATOMICS +static pthread_mutex_t js_class_id_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +/* a new class ID is allocated if *pclass_id != 0 */ +JSClassID JS_NewClassID(JSClassID *pclass_id) +{ + JSClassID class_id; +#ifdef CONFIG_ATOMICS + pthread_mutex_lock(&js_class_id_mutex); +#endif + class_id = *pclass_id; + if (class_id == 0) { + class_id = js_class_id_alloc++; + *pclass_id = class_id; + } +#ifdef CONFIG_ATOMICS + pthread_mutex_unlock(&js_class_id_mutex); +#endif + return class_id; +} + +JSClassID JS_GetClassID(JSValue v) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) + return JS_INVALID_CLASS_ID; + p = JS_VALUE_GET_OBJ(v); + return p->class_id; +} + +BOOL JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) +{ + return (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0); +} + +/* create a new object internal class. Return -1 if error, 0 if + OK. The finalizer can be NULL if none is needed. */ +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name) +{ + int new_size, i; + JSClass *cl, *new_class_array; + struct list_head *el; + + if (class_id >= (1 << 16)) + return -1; + if (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0) + return -1; + + if (class_id >= rt->class_count) { + new_size = max_int(JS_CLASS_INIT_COUNT, + max_int(class_id + 1, rt->class_count * 3 / 2)); + + /* reallocate the context class prototype array, if any */ + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSValue *new_tab; + new_tab = js_realloc_rt(rt, ctx->class_proto, + sizeof(ctx->class_proto[0]) * new_size); + if (!new_tab) + return -1; + for(i = rt->class_count; i < new_size; i++) + new_tab[i] = JS_NULL; + ctx->class_proto = new_tab; + } + /* reallocate the class array */ + new_class_array = js_realloc_rt(rt, rt->class_array, + sizeof(JSClass) * new_size); + if (!new_class_array) + return -1; + memset(new_class_array + rt->class_count, 0, + (new_size - rt->class_count) * sizeof(JSClass)); + rt->class_array = new_class_array; + rt->class_count = new_size; + } + cl = &rt->class_array[class_id]; + cl->class_id = class_id; + cl->class_name = JS_DupAtomRT(rt, name); + cl->finalizer = class_def->finalizer; + cl->gc_mark = class_def->gc_mark; + cl->call = class_def->call; + cl->exotic = class_def->exotic; + return 0; +} + +int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) +{ + int ret, len; + JSAtom name; + + len = strlen(class_def->class_name); + name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) { + name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) + return -1; + } + ret = JS_NewClass1(rt, class_id, class_def, name); + JS_FreeAtomRT(rt, name); + return ret; +} + +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len) +{ + JSString *str; + + if (len <= 0) { + return JS_AtomToString(ctx, JS_ATOM_empty_string); + } + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + memcpy(str->u.str8, buf, len); + str->u.str8[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue js_new_string8(JSContext *ctx, const char *buf) +{ + return js_new_string8_len(ctx, buf, strlen(buf)); +} + +static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len) +{ + JSString *str; + str = js_alloc_string(ctx, len, 1); + if (!str) + return JS_EXCEPTION; + memcpy(str->u.str16, buf, len * 2); + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue js_new_string_char(JSContext *ctx, uint16_t c) +{ + if (c < 0x100) { + uint8_t ch8 = c; + return js_new_string8_len(ctx, (const char *)&ch8, 1); + } else { + uint16_t ch16 = c; + return js_new_string16_len(ctx, &ch16, 1); + } +} + +static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) +{ + int len = end - start; + if (start == 0 && end == p->len) { + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); + } + if (p->is_wide_char && len > 0) { + JSString *str; + int i; + uint16_t c = 0; + for (i = start; i < end; i++) { + c |= p->u.str16[i]; + } + if (c > 0xFF) + return js_new_string16_len(ctx, p->u.str16 + start, len); + + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + for (i = 0; i < len; i++) { + str->u.str8[i] = p->u.str16[start + i]; + } + str->u.str8[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); + } else { + return js_new_string8_len(ctx, (const char *)(p->u.str8 + start), len); + } +} + +typedef struct StringBuffer { + JSContext *ctx; + JSString *str; + int len; + int size; + int is_wide_char; + int error_status; +} StringBuffer; + +/* It is valid to call string_buffer_end() and all string_buffer functions even + if string_buffer_init() or another string_buffer function returns an error. + If the error_status is set, string_buffer_end() returns JS_EXCEPTION. + */ +static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size, + int is_wide) +{ + s->ctx = ctx; + s->size = size; + s->len = 0; + s->is_wide_char = is_wide; + s->error_status = 0; + s->str = js_alloc_string(ctx, size, is_wide); + if (unlikely(!s->str)) { + s->size = 0; + return s->error_status = -1; + } +#ifdef DUMP_LEAKS + /* the StringBuffer may reallocate the JSString, only link it at the end */ + list_del(&s->str->link); +#endif + return 0; +} + +static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size) +{ + return string_buffer_init2(ctx, s, size, 0); +} + +static void string_buffer_free(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; +} + +static int string_buffer_set_error(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; + s->size = 0; + s->len = 0; + return s->error_status = -1; +} + +static no_inline int string_buffer_widen(StringBuffer *s, int size) +{ + JSString *str; + size_t slack; + int i; + + if (s->error_status) + return -1; + + str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack); + if (!str) + return string_buffer_set_error(s); + size += slack >> 1; + for(i = s->len; i-- > 0;) { + str->u.str16[i] = str->u.str8[i]; + } + s->is_wide_char = 1; + s->size = size; + s->str = str; + return 0; +} + +static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c) +{ + JSString *new_str; + int new_size; + size_t new_size_bytes, slack; + + if (s->error_status) + return -1; + + if (new_len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(s->ctx, "string too long"); + return string_buffer_set_error(s); + } + new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX); + if (!s->is_wide_char && c >= 0x100) { + return string_buffer_widen(s, new_size); + } + new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char; + new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack); + if (!new_str) + return string_buffer_set_error(s); + new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX); + s->size = new_size; + s->str = new_str; + return 0; +} + +static no_inline int string_buffer_putc16_slow(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + s->str->u.str16[s->len++] = c; + } else if (c < 0x100) { + s->str->u.str8[s->len++] = c; + } else { + if (string_buffer_widen(s, s->size)) + return -1; + s->str->u.str16[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xff */ +static int string_buffer_putc8(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + s->str->u.str16[s->len++] = c; + } else { + s->str->u.str8[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xffff */ +static int string_buffer_putc16(StringBuffer *s, uint32_t c) +{ + if (likely(s->len < s->size)) { + if (s->is_wide_char) { + s->str->u.str16[s->len++] = c; + return 0; + } else if (c < 0x100) { + s->str->u.str8[s->len++] = c; + return 0; + } + } + return string_buffer_putc16_slow(s, c); +} + +static int string_buffer_putc_slow(StringBuffer *s, uint32_t c) +{ + if (unlikely(c >= 0x10000)) { + /* surrogate pair */ + if (string_buffer_putc16(s, get_hi_surrogate(c))) + return -1; + c = get_lo_surrogate(c); + } + return string_buffer_putc16(s, c); +} + +/* 0 <= c <= 0x10ffff */ +static inline int string_buffer_putc(StringBuffer *s, uint32_t c) +{ + if (likely(s->len < s->size)) { + if (s->is_wide_char) { + if (c < 0x10000) { + s->str->u.str16[s->len++] = c; + return 0; + } else if (likely((s->len + 1) < s->size)) { + s->str->u.str16[s->len++] = get_hi_surrogate(c); + s->str->u.str16[s->len++] = get_lo_surrogate(c); + return 0; + } + } else if (c < 0x100) { + s->str->u.str8[s->len++] = c; + return 0; + } + } + return string_buffer_putc_slow(s, c); +} + +static int string_getc(const JSString *p, int *pidx) +{ + int idx, c, c1; + idx = *pidx; + if (p->is_wide_char) { + c = p->u.str16[idx++]; + if (is_hi_surrogate(c) && idx < p->len) { + c1 = p->u.str16[idx]; + if (is_lo_surrogate(c1)) { + c = from_surrogate(c, c1); + idx++; + } + } + } else { + c = p->u.str8[idx++]; + } + *pidx = idx; + return c; +} + +static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len) +{ + int i; + + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, 0)) + return -1; + } + if (s->is_wide_char) { + for (i = 0; i < len; i++) { + s->str->u.str16[s->len + i] = p[i]; + } + s->len += len; + } else { + memcpy(&s->str->u.str8[s->len], p, len); + s->len += len; + } + return 0; +} + +static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len) +{ + int c = 0, i; + + for (i = 0; i < len; i++) { + c |= p[i]; + } + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, c)) + return -1; + } else if (!s->is_wide_char && c >= 0x100) { + if (string_buffer_widen(s, s->size)) + return -1; + } + if (s->is_wide_char) { + memcpy(&s->str->u.str16[s->len], p, len << 1); + s->len += len; + } else { + for (i = 0; i < len; i++) { + s->str->u.str8[s->len + i] = p[i]; + } + s->len += len; + } + return 0; +} + +/* appending an ASCII string */ +static int string_buffer_puts8(StringBuffer *s, const char *str) +{ + return string_buffer_write8(s, (const uint8_t *)str, strlen(str)); +} + +static int string_buffer_concat(StringBuffer *s, const JSString *p, + uint32_t from, uint32_t to) +{ + if (to <= from) + return 0; + if (p->is_wide_char) + return string_buffer_write16(s, p->u.str16 + from, to - from); + else + return string_buffer_write8(s, p->u.str8 + from, to - from); +} + +static int string_buffer_concat_value(StringBuffer *s, JSValueConst v) +{ + JSString *p; + JSValue v1; + int res; + + if (s->error_status) { + /* prevent exception overload */ + return -1; + } + if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { + if (JS_VALUE_GET_TAG(v) == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(v); + /* recursion is acceptable because the rope depth is bounded */ + if (string_buffer_concat_value(s, r->left)) + return -1; + return string_buffer_concat_value(s, r->right); + } else { + v1 = JS_ToString(s->ctx, v); + if (JS_IsException(v1)) + return string_buffer_set_error(s); + p = JS_VALUE_GET_STRING(v1); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v1); + return res; + } + } + p = JS_VALUE_GET_STRING(v); + return string_buffer_concat(s, p, 0, p->len); +} + +static int string_buffer_concat_value_free(StringBuffer *s, JSValue v) +{ + JSString *p; + int res; + + if (s->error_status) { + /* prevent exception overload */ + JS_FreeValue(s->ctx, v); + return -1; + } + if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { + v = JS_ToStringFree(s->ctx, v); + if (JS_IsException(v)) + return string_buffer_set_error(s); + } + p = JS_VALUE_GET_STRING(v); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v); + return res; +} + +static int string_buffer_fill(StringBuffer *s, int c, int count) +{ + /* XXX: optimize */ + if (s->len + count > s->size) { + if (string_buffer_realloc(s, s->len + count, c)) + return -1; + } + while (count-- > 0) { + if (string_buffer_putc16(s, c)) + return -1; + } + return 0; +} + +static JSValue string_buffer_end(StringBuffer *s) +{ + JSString *str; + str = s->str; + if (s->error_status) + return JS_EXCEPTION; + if (s->len == 0) { + js_free(s->ctx, str); + s->str = NULL; + return JS_AtomToString(s->ctx, JS_ATOM_empty_string); + } + if (s->len < s->size) { + /* smaller size so js_realloc should not fail, but OK if it does */ + /* XXX: should add some slack to avoid unnecessary calls */ + /* XXX: might need to use malloc+free to ensure smaller size */ + str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) + + (s->len << s->is_wide_char) + 1 - s->is_wide_char); + if (str == NULL) + str = s->str; + s->str = str; + } + if (!s->is_wide_char) + str->u.str8[s->len] = 0; +#ifdef DUMP_LEAKS + list_add_tail(&str->link, &s->ctx->rt->string_list); +#endif + str->is_wide_char = s->is_wide_char; + str->len = s->len; + s->str = NULL; + return JS_MKPTR(JS_TAG_STRING, str); +} + +/* create a string from a UTF-8 buffer */ +JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len) +{ + const uint8_t *p, *p_end, *p_start, *p_next; + uint32_t c; + StringBuffer b_s, *b = &b_s; + size_t len1; + + p_start = (const uint8_t *)buf; + p_end = p_start + buf_len; + len1 = count_ascii(p_start, buf_len); + p = p_start + len1; + if (len1 > JS_STRING_LEN_MAX) + return JS_ThrowInternalError(ctx, "string too long"); + if (p == p_end) { + /* ASCII string */ + return js_new_string8_len(ctx, buf, buf_len); + } else { + if (string_buffer_init(ctx, b, buf_len)) + goto fail; + string_buffer_write8(b, p_start, len1); + while (p < p_end) { + if (*p < 128) { + string_buffer_putc8(b, *p++); + } else { + /* parse utf-8 sequence, return 0xFFFFFFFF for error */ + c = unicode_from_utf8(p, p_end - p, &p_next); + if (c < 0x10000) { + p = p_next; + } else if (c <= 0x10FFFF) { + p = p_next; + /* surrogate pair */ + string_buffer_putc16(b, get_hi_surrogate(c)); + c = get_lo_surrogate(c); + } else { + /* invalid char */ + c = 0xfffd; + /* skip the invalid chars */ + /* XXX: seems incorrect. Why not just use c = *p++; ? */ + while (p < p_end && (*p >= 0x80 && *p < 0xc0)) + p++; + if (p < p_end) { + p++; + while (p < p_end && (*p >= 0x80 && *p < 0xc0)) + p++; + } + } + string_buffer_putc16(b, c); + } + } + } + return string_buffer_end(b); + + fail: + string_buffer_free(b); + return JS_EXCEPTION; +} + +static JSValue JS_ConcatString3(JSContext *ctx, const char *str1, + JSValue str2, const char *str3) +{ + StringBuffer b_s, *b = &b_s; + int len1, len3; + JSString *p; + + if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) { + str2 = JS_ToStringFree(ctx, str2); + if (JS_IsException(str2)) + goto fail; + } + p = JS_VALUE_GET_STRING(str2); + len1 = strlen(str1); + len3 = strlen(str3); + + if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char)) + goto fail; + + string_buffer_write8(b, (const uint8_t *)str1, len1); + string_buffer_concat(b, p, 0, p->len); + string_buffer_write8(b, (const uint8_t *)str3, len3); + + JS_FreeValue(ctx, str2); + return string_buffer_end(b); + + fail: + JS_FreeValue(ctx, str2); + return JS_EXCEPTION; +} + +JSValue JS_NewAtomString(JSContext *ctx, const char *str) +{ + JSAtom atom = JS_NewAtom(ctx, str); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + JSValue val = JS_AtomToString(ctx, atom); + JS_FreeAtom(ctx, atom); + return val; +} + +/* return (NULL, 0) if exception. */ +/* return pointer into a JSString with a live ref_count */ +/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */ +const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, BOOL cesu8) +{ + JSValue val; + JSString *str, *str_new; + int pos, len, c, c1; + uint8_t *q; + + if (JS_VALUE_GET_TAG(val1) != JS_TAG_STRING) { + val = JS_ToString(ctx, val1); + if (JS_IsException(val)) + goto fail; + } else { + val = JS_DupValue(ctx, val1); + } + + str = JS_VALUE_GET_STRING(val); + len = str->len; + if (!str->is_wide_char) { + const uint8_t *src = str->u.str8; + int count; + + /* count the number of non-ASCII characters */ + /* Scanning the whole string is required for ASCII strings, + and computing the number of non-ASCII bytes is less expensive + than testing each byte, hence this method is faster for ASCII + strings, which is the most common case. + */ + count = 0; + for (pos = 0; pos < len; pos++) { + count += src[pos] >> 7; + } + if (count == 0) { + if (plen) + *plen = len; + return (const char *)src; + } + str_new = js_alloc_string(ctx, len + count, 0); + if (!str_new) + goto fail; + q = str_new->u.str8; + for (pos = 0; pos < len; pos++) { + c = src[pos]; + if (c < 0x80) { + *q++ = c; + } else { + *q++ = (c >> 6) | 0xc0; + *q++ = (c & 0x3f) | 0x80; + } + } + } else { + const uint16_t *src = str->u.str16; + /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may + produce 4 bytes but use 2 code points. + */ + str_new = js_alloc_string(ctx, len * 3, 0); + if (!str_new) + goto fail; + q = str_new->u.str8; + pos = 0; + while (pos < len) { + c = src[pos++]; + if (c < 0x80) { + *q++ = c; + } else { + if (is_hi_surrogate(c)) { + if (pos < len && !cesu8) { + c1 = src[pos]; + if (is_lo_surrogate(c1)) { + pos++; + c = from_surrogate(c, c1); + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } + q += unicode_to_utf8(q, c); + } + } + } + + *q = '\0'; + str_new->len = q - str_new->u.str8; + JS_FreeValue(ctx, val); + if (plen) + *plen = str_new->len; + return (const char *)str_new->u.str8; + fail: + if (plen) + *plen = 0; + return NULL; +} + +void JS_FreeCString(JSContext *ctx, const char *ptr) +{ + JSString *p; + if (!ptr) + return; + /* purposely removing constness */ + p = container_of(ptr, JSString, u); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); +} + +static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2, + int pos2, int len) +{ + int res; + + if (likely(!p1->is_wide_char)) { + if (likely(!p2->is_wide_char)) + res = memcmp(p1->u.str8 + pos1, p2->u.str8 + pos2, len); + else + res = -memcmp16_8(p2->u.str16 + pos2, p1->u.str8 + pos1, len); + } else { + if (!p2->is_wide_char) + res = memcmp16_8(p1->u.str16 + pos1, p2->u.str8 + pos2, len); + else + res = memcmp16(p1->u.str16 + pos1, p2->u.str16 + pos2, len); + } + return res; +} + +static BOOL js_string_eq(JSContext *ctx, + const JSString *p1, const JSString *p2) +{ + if (p1->len != p2->len) + return FALSE; + if (p1 == p2) + return TRUE; + return js_string_memcmp(p1, 0, p2, 0, p1->len) == 0; +} + +/* return < 0, 0 or > 0 */ +static int js_string_compare(JSContext *ctx, + const JSString *p1, const JSString *p2) +{ + int res, len; + len = min_int(p1->len, p2->len); + res = js_string_memcmp(p1, 0, p2, 0, len); + if (res == 0) { + if (p1->len == p2->len) + res = 0; + else if (p1->len < p2->len) + res = -1; + else + res = 1; + } + return res; +} + +static void copy_str16(uint16_t *dst, const JSString *p, int offset, int len) +{ + if (p->is_wide_char) { + memcpy(dst, p->u.str16 + offset, len * 2); + } else { + const uint8_t *src1 = p->u.str8 + offset; + int i; + + for(i = 0; i < len; i++) + dst[i] = src1[i]; + } +} + +static JSValue JS_ConcatString1(JSContext *ctx, + const JSString *p1, const JSString *p2) +{ + JSString *p; + uint32_t len; + int is_wide_char; + + len = p1->len + p2->len; + if (len > JS_STRING_LEN_MAX) + return JS_ThrowInternalError(ctx, "string too long"); + is_wide_char = p1->is_wide_char | p2->is_wide_char; + p = js_alloc_string(ctx, len, is_wide_char); + if (!p) + return JS_EXCEPTION; + if (!is_wide_char) { + memcpy(p->u.str8, p1->u.str8, p1->len); + memcpy(p->u.str8 + p1->len, p2->u.str8, p2->len); + p->u.str8[len] = '\0'; + } else { + copy_str16(p->u.str16, p1, 0, p1->len); + copy_str16(p->u.str16 + p1->len, p2, 0, p2->len); + } + return JS_MKPTR(JS_TAG_STRING, p); +} + +static BOOL JS_ConcatStringInPlace(JSContext *ctx, JSString *p1, JSValueConst op2) { + if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + JSString *p2 = JS_VALUE_GET_STRING(op2); + size_t size1; + + if (p2->len == 0) + return TRUE; + if (p1->header.ref_count != 1) + return FALSE; + size1 = js_malloc_usable_size(ctx, p1); + if (p1->is_wide_char) { + if (size1 >= sizeof(*p1) + ((p1->len + p2->len) << 1)) { + if (p2->is_wide_char) { + memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1); + p1->len += p2->len; + return TRUE; + } else { + size_t i; + for (i = 0; i < p2->len; i++) { + p1->u.str16[p1->len++] = p2->u.str8[i]; + } + return TRUE; + } + } + } else if (!p2->is_wide_char) { + if (size1 >= sizeof(*p1) + p1->len + p2->len + 1) { + memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len); + p1->len += p2->len; + p1->u.str8[p1->len] = '\0'; + return TRUE; + } + } + } + return FALSE; +} + +static JSValue JS_ConcatString2(JSContext *ctx, JSValue op1, JSValue op2) +{ + JSValue ret; + JSString *p1, *p2; + p1 = JS_VALUE_GET_STRING(op1); + if (JS_ConcatStringInPlace(ctx, p1, op2)) { + JS_FreeValue(ctx, op2); + return op1; + } + p2 = JS_VALUE_GET_STRING(op2); + ret = JS_ConcatString1(ctx, p1, p2); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return ret; +} + +/* Return the character at position 'idx'. 'val' must be a string or rope */ +static int string_rope_get(JSValueConst val, uint32_t idx) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + return string_get(JS_VALUE_GET_STRING(val), idx); + } else { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + uint32_t len; + if (JS_VALUE_GET_TAG(r->left) == JS_TAG_STRING) + len = JS_VALUE_GET_STRING(r->left)->len; + else + len = JS_VALUE_GET_STRING_ROPE(r->left)->len; + if (idx < len) + return string_rope_get(r->left, idx); + else + return string_rope_get(r->right, idx - len); + } +} + +typedef struct { + JSValueConst stack[JS_STRING_ROPE_MAX_DEPTH]; + int stack_len; +} JSStringRopeIter; + +static void string_rope_iter_init(JSStringRopeIter *s, JSValueConst val) +{ + s->stack_len = 0; + s->stack[s->stack_len++] = val; +} + +/* iterate thru a rope and return the strings in order */ +static JSString *string_rope_iter_next(JSStringRopeIter *s) +{ + JSValueConst val; + JSStringRope *r; + + if (s->stack_len == 0) + return NULL; + val = s->stack[--s->stack_len]; + for(;;) { + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) + return JS_VALUE_GET_STRING(val); + r = JS_VALUE_GET_STRING_ROPE(val); + assert(s->stack_len < JS_STRING_ROPE_MAX_DEPTH); + s->stack[s->stack_len++] = r->right; + val = r->left; + } +} + +static uint32_t string_rope_get_len(JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) + return JS_VALUE_GET_STRING(val)->len; + else + return JS_VALUE_GET_STRING_ROPE(val)->len; +} + +static int js_string_rope_compare(JSContext *ctx, JSValueConst op1, + JSValueConst op2, BOOL eq_only) +{ + uint32_t len1, len2, len, pos1, pos2, l; + int res; + JSStringRopeIter it1, it2; + JSString *p1, *p2; + + len1 = string_rope_get_len(op1); + len2 = string_rope_get_len(op2); + /* no need to go further for equality test if + different length */ + if (eq_only && len1 != len2) + return 1; + len = min_uint32(len1, len2); + string_rope_iter_init(&it1, op1); + string_rope_iter_init(&it2, op2); + p1 = string_rope_iter_next(&it1); + p2 = string_rope_iter_next(&it2); + pos1 = 0; + pos2 = 0; + while (len != 0) { + l = min_uint32(p1->len - pos1, p2->len - pos2); + l = min_uint32(l, len); + res = js_string_memcmp(p1, pos1, p2, pos2, l); + if (res != 0) + return res; + len -= l; + pos1 += l; + if (pos1 >= p1->len) { + p1 = string_rope_iter_next(&it1); + pos1 = 0; + } + pos2 += l; + if (pos2 >= p2->len) { + p2 = string_rope_iter_next(&it2); + pos2 = 0; + } + } + + if (len1 == len2) + res = 0; + else if (len1 < len2) + res = -1; + else + res = 1; + return res; +} + +/* 'rope' must be a rope. return a string and modify the rope so that + it won't need to be linearized again. */ +static JSValue js_linearize_string_rope(JSContext *ctx, JSValue rope) +{ + StringBuffer b_s, *b = &b_s; + JSStringRope *r; + JSValue ret; + + r = JS_VALUE_GET_STRING_ROPE(rope); + + /* check whether it is already linearized */ + if (JS_VALUE_GET_TAG(r->right) == JS_TAG_STRING && + JS_VALUE_GET_STRING(r->right)->len == 0) { + ret = JS_DupValue(ctx, r->left); + JS_FreeValue(ctx, rope); + return ret; + } + if (string_buffer_init2(ctx, b, r->len, r->is_wide_char)) + goto fail; + if (string_buffer_concat_value(b, rope)) + goto fail; + ret = string_buffer_end(b); + if (r->header.ref_count > 1) { + /* update the rope so that it won't need to be linearized again */ + JS_FreeValue(ctx, r->left); + JS_FreeValue(ctx, r->right); + r->left = JS_DupValue(ctx, ret); + r->right = JS_AtomToString(ctx, JS_ATOM_empty_string); + } + JS_FreeValue(ctx, rope); + return ret; + fail: + JS_FreeValue(ctx, rope); + return JS_EXCEPTION; +} + +static JSValue js_rebalancee_string_rope(JSContext *ctx, JSValueConst rope); + +/* op1 and op2 must be strings or string ropes */ +static JSValue js_new_string_rope(JSContext *ctx, JSValue op1, JSValue op2) +{ + uint32_t len; + int is_wide_char, depth; + JSStringRope *r; + JSValue res; + + if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { + JSString *p1 = JS_VALUE_GET_STRING(op1); + len = p1->len; + is_wide_char = p1->is_wide_char; + depth = 0; + } else { + JSStringRope *r1 = JS_VALUE_GET_STRING_ROPE(op1); + len = r1->len; + is_wide_char = r1->is_wide_char; + depth = r1->depth; + } + + if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + JSString *p2 = JS_VALUE_GET_STRING(op2); + len += p2->len; + is_wide_char |= p2->is_wide_char; + } else { + JSStringRope *r2 = JS_VALUE_GET_STRING_ROPE(op2); + len += r2->len; + is_wide_char |= r2->is_wide_char; + depth = max_int(depth, r2->depth); + } + if (len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(ctx, "string too long"); + goto fail; + } + r = js_malloc(ctx, sizeof(*r)); + if (!r) + goto fail; + r->header.ref_count = 1; + r->len = len; + r->is_wide_char = is_wide_char; + r->depth = depth + 1; + r->left = op1; + r->right = op2; + res = JS_MKPTR(JS_TAG_STRING_ROPE, r); + if (r->depth > JS_STRING_ROPE_MAX_DEPTH) { + JSValue res2; +#ifdef DUMP_ROPE_REBALANCE + printf("rebalance: initial depth=%d\n", r->depth); +#endif + res2 = js_rebalancee_string_rope(ctx, res); +#ifdef DUMP_ROPE_REBALANCE + if (JS_VALUE_GET_TAG(res2) == JS_TAG_STRING_ROPE) + printf("rebalance: final depth=%d\n", JS_VALUE_GET_STRING_ROPE(res2)->depth); +#endif + JS_FreeValue(ctx, res); + return res2; + } else { + return res; + } + fail: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; +} + +#define ROPE_N_BUCKETS 44 + +/* Fibonacii numbers starting from F_2 */ +static const uint32_t rope_bucket_len[ROPE_N_BUCKETS] = { + 1, 2, 3, 5, + 8, 13, 21, 34, + 55, 89, 144, 233, + 377, 610, 987, 1597, + 2584, 4181, 6765, 10946, + 17711, 28657, 46368, 75025, + 121393, 196418, 317811, 514229, + 832040, 1346269, 2178309, 3524578, + 5702887, 9227465, 14930352, 24157817, + 39088169, 63245986, 102334155, 165580141, + 267914296, 433494437, 701408733, 1134903170, /* > JS_STRING_LEN_MAX */ +}; + +static int js_rebalancee_string_rope_rec(JSContext *ctx, JSValue *buckets, + JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + uint32_t len, i; + JSValue a, b; + + len = p->len; + if (len == 0) + return 0; /* nothing to do */ + /* find the bucket i so that rope_bucket_len[i] <= len < + rope_bucket_len[i + 1] and concatenate the ropes in the + buckets before */ + a = JS_NULL; + i = 0; + while (len >= rope_bucket_len[i + 1]) { + b = buckets[i]; + if (!JS_IsNull(b)) { + buckets[i] = JS_NULL; + if (JS_IsNull(a)) { + a = b; + } else { + a = js_new_string_rope(ctx, b, a); + if (JS_IsException(a)) + return -1; + } + } + i++; + } + if (!JS_IsNull(a)) { + a = js_new_string_rope(ctx, a, JS_DupValue(ctx, val)); + if (JS_IsException(a)) + return -1; + } else { + a = JS_DupValue(ctx, val); + } + while (!JS_IsNull(buckets[i])) { + a = js_new_string_rope(ctx, buckets[i], a); + buckets[i] = JS_NULL; + if (JS_IsException(a)) + return -1; + i++; + } + buckets[i] = a; + } else { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + js_rebalancee_string_rope_rec(ctx, buckets, r->left); + js_rebalancee_string_rope_rec(ctx, buckets, r->right); + } + return 0; +} + +/* Return a new rope which is balanced. Algorithm from "Ropes: an + Alternative to Strings", Hans-J. Boehm, Russ Atkinson and Michael + Plass. */ +static JSValue js_rebalancee_string_rope(JSContext *ctx, JSValueConst rope) +{ + JSValue buckets[ROPE_N_BUCKETS], a, b; + int i; + + for(i = 0; i < ROPE_N_BUCKETS; i++) + buckets[i] = JS_NULL; + if (js_rebalancee_string_rope_rec(ctx, buckets, rope)) + goto fail; + a = JS_NULL; + for(i = 0; i < ROPE_N_BUCKETS; i++) { + b = buckets[i]; + if (!JS_IsNull(b)) { + buckets[i] = JS_NULL; + if (JS_IsNull(a)) { + a = b; + } else { + a = js_new_string_rope(ctx, b, a); + if (JS_IsException(a)) + goto fail; + } + } + } + /* fail safe */ + if (JS_IsNull(a)) + return JS_AtomToString(ctx, JS_ATOM_empty_string); + else + return a; + fail: + for(i = 0; i < ROPE_N_BUCKETS; i++) { + JS_FreeValue(ctx, buckets[i]); + } + return JS_EXCEPTION; +} + +/* op1 and op2 are converted to strings. For convenience, op1 or op2 = + JS_EXCEPTION are accepted and return JS_EXCEPTION. */ +static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) +{ + JSString *p1, *p2; + + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING && + JS_VALUE_GET_TAG(op1) != JS_TAG_STRING_ROPE)) { + op1 = JS_ToStringFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; + } + } + if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING && + JS_VALUE_GET_TAG(op2) != JS_TAG_STRING_ROPE)) { + op2 = JS_ToStringFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + return JS_EXCEPTION; + } + } + + /* normal concatenation for short strings */ + if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + p2 = JS_VALUE_GET_STRING(op2); + if (p2->len == 0) { + JS_FreeValue(ctx, op2); + return op1; + } + if (p2->len <= JS_STRING_ROPE_SHORT_LEN) { + if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { + p1 = JS_VALUE_GET_STRING(op1); + if (p1->len <= JS_STRING_ROPE_SHORT2_LEN) { + return JS_ConcatString2(ctx, op1, op2); + } else { + return js_new_string_rope(ctx, op1, op2); + } + } else { + JSStringRope *r1; + r1 = JS_VALUE_GET_STRING_ROPE(op1); + if (JS_VALUE_GET_TAG(r1->right) == JS_TAG_STRING && + JS_VALUE_GET_STRING(r1->right)->len <= JS_STRING_ROPE_SHORT_LEN) { + JSValue val, ret; + val = JS_ConcatString2(ctx, JS_DupValue(ctx, r1->right), op2); + if (JS_IsException(val)) { + JS_FreeValue(ctx, op1); + return JS_EXCEPTION; + } + ret = js_new_string_rope(ctx, JS_DupValue(ctx, r1->left), val); + JS_FreeValue(ctx, op1); + return ret; + } + } + } + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_STRING) { + JSStringRope *r2; + p1 = JS_VALUE_GET_STRING(op1); + if (p1->len == 0) { + JS_FreeValue(ctx, op1); + return op2; + } + r2 = JS_VALUE_GET_STRING_ROPE(op2); + if (JS_VALUE_GET_TAG(r2->left) == JS_TAG_STRING && + JS_VALUE_GET_STRING(r2->left)->len <= JS_STRING_ROPE_SHORT_LEN) { + JSValue val, ret; + val = JS_ConcatString2(ctx, op1, JS_DupValue(ctx, r2->left)); + if (JS_IsException(val)) { + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; + } + ret = js_new_string_rope(ctx, val, JS_DupValue(ctx, r2->right)); + JS_FreeValue(ctx, op2); + return ret; + } + } + return js_new_string_rope(ctx, op1, op2); +} + +/* Shape support */ + +static inline size_t get_shape_size(size_t hash_size, size_t prop_size) +{ + return hash_size * sizeof(uint32_t) + sizeof(JSShape) + + prop_size * sizeof(JSShapeProperty); +} + +static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) +{ + return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); +} + +static inline uint32_t *prop_hash_end(JSShape *sh) +{ + return (uint32_t *)sh; +} + +static inline void *get_alloc_from_shape(JSShape *sh) +{ + return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); +} + +static inline JSShapeProperty *get_shape_prop(JSShape *sh) +{ + return sh->prop; +} + +static int init_shape_hash(JSRuntime *rt) +{ + rt->shape_hash_bits = 4; /* 16 shapes */ + rt->shape_hash_size = 1 << rt->shape_hash_bits; + rt->shape_hash_count = 0; + rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + rt->shape_hash_size); + if (!rt->shape_hash) + return -1; + return 0; +} + +/* same magic hash multiplier as the Linux kernel */ +static uint32_t shape_hash(uint32_t h, uint32_t val) +{ + return (h + val) * 0x9e370001; +} + +/* truncate the shape hash to 'hash_bits' bits */ +static uint32_t get_shape_hash(uint32_t h, int hash_bits) +{ + return h >> (32 - hash_bits); +} + +static uint32_t shape_initial_hash(JSObject *proto) +{ + uint32_t h; + h = shape_hash(1, (uintptr_t)proto); + if (sizeof(proto) > 4) + h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32); + return h; +} + +static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits) +{ + int new_shape_hash_size, i; + uint32_t h; + JSShape **new_shape_hash, *sh, *sh_next; + + new_shape_hash_size = 1 << new_shape_hash_bits; + new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + new_shape_hash_size); + if (!new_shape_hash) + return -1; + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) { + sh_next = sh->shape_hash_next; + h = get_shape_hash(sh->hash, new_shape_hash_bits); + sh->shape_hash_next = new_shape_hash[h]; + new_shape_hash[h] = sh; + } + } + js_free_rt(rt, rt->shape_hash); + rt->shape_hash_bits = new_shape_hash_bits; + rt->shape_hash_size = new_shape_hash_size; + rt->shape_hash = new_shape_hash; + return 0; +} + +static void js_shape_hash_link(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + sh->shape_hash_next = rt->shape_hash[h]; + rt->shape_hash[h] = sh; + rt->shape_hash_count++; +} + +static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + JSShape **psh; + + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + psh = &rt->shape_hash[h]; + while (*psh != sh) + psh = &(*psh)->shape_hash_next; + *psh = sh->shape_hash_next; + rt->shape_hash_count--; +} + +/* create a new empty shape with prototype 'proto'. It is not hashed */ +static inline JSShape *js_new_shape_nohash(JSContext *ctx, JSObject *proto, + int hash_size, int prop_size) +{ + JSRuntime *rt = ctx->rt; + void *sh_alloc; + JSShape *sh; + + sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); + if (!sh_alloc) + return NULL; + sh = get_shape_from_alloc(sh_alloc, hash_size); + sh->header.ref_count = 1; + add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + if (proto) + JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, proto)); + sh->proto = proto; + memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * + hash_size); + sh->prop_hash_mask = hash_size - 1; + sh->prop_size = prop_size; + sh->prop_count = 0; + sh->deleted_prop_count = 0; + sh->is_hashed = FALSE; + return sh; +} + +/* create a new empty shape with prototype 'proto' */ +static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, + int hash_size, int prop_size) +{ + JSRuntime *rt = ctx->rt; + JSShape *sh; + + /* resize the shape hash table if necessary */ + if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { + resize_shape_hash(rt, rt->shape_hash_bits + 1); + } + + sh = js_new_shape_nohash(ctx, proto, hash_size, prop_size); + if (!sh) + return NULL; + + /* insert in the hash table */ + sh->hash = shape_initial_hash(proto); + sh->is_hashed = TRUE; + js_shape_hash_link(ctx->rt, sh); + return sh; +} + +static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) +{ + return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE, + JS_PROP_INITIAL_SIZE); +} + +/* The shape is cloned. The new shape is not inserted in the shape + hash table */ +static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) +{ + JSShape *sh; + void *sh_alloc, *sh_alloc1; + size_t size; + JSShapeProperty *pr; + uint32_t i, hash_size; + + hash_size = sh1->prop_hash_mask + 1; + size = get_shape_size(hash_size, sh1->prop_size); + sh_alloc = js_malloc(ctx, size); + if (!sh_alloc) + return NULL; + sh_alloc1 = get_alloc_from_shape(sh1); + memcpy(sh_alloc, sh_alloc1, size); + sh = get_shape_from_alloc(sh_alloc, hash_size); + sh->header.ref_count = 1; + add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + sh->is_hashed = FALSE; + if (sh->proto) { + JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { + JS_DupAtom(ctx, pr->atom); + } + return sh; +} + +static JSShape *js_dup_shape(JSShape *sh) +{ + sh->header.ref_count++; + return sh; +} + +static void js_free_shape0(JSRuntime *rt, JSShape *sh) +{ + uint32_t i; + JSShapeProperty *pr; + + assert(sh->header.ref_count == 0); + if (sh->is_hashed) + js_shape_hash_unlink(rt, sh); + if (sh->proto != NULL) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JS_FreeAtomRT(rt, pr->atom); + pr++; + } + remove_gc_object(&sh->header); + js_free_rt(rt, get_alloc_from_shape(sh)); +} + +static void js_free_shape(JSRuntime *rt, JSShape *sh) +{ + if (unlikely(--sh->header.ref_count <= 0)) { + js_free_shape0(rt, sh); + } +} + +static void js_free_shape_null(JSRuntime *rt, JSShape *sh) +{ + if (sh) + js_free_shape(rt, sh); +} + +/* make space to hold at least 'count' properties */ +static no_inline int resize_properties(JSContext *ctx, JSShape **psh, + JSObject *p, uint32_t count) +{ + JSShape *sh; + uint32_t new_size, new_hash_size, new_hash_mask, i; + JSShapeProperty *pr; + void *sh_alloc; + intptr_t h; + JSShape *old_sh; + + sh = *psh; + new_size = max_int(count, sh->prop_size * 3 / 2); + /* Reallocate prop array first to avoid crash or size inconsistency + in case of memory allocation failure */ + if (p) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (unlikely(!new_prop)) + return -1; + p->prop = new_prop; + } + new_hash_size = sh->prop_hash_mask + 1; + while (new_hash_size < new_size) + new_hash_size = 2 * new_hash_size; + /* resize the property shapes. Using js_realloc() is not possible in + case the GC runs during the allocation */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + /* copy all the shape properties */ + memcpy(sh, old_sh, + sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + + if (new_hash_size != (sh->prop_hash_mask + 1)) { + /* resize the hash table and the properties */ + new_hash_mask = new_hash_size - 1; + sh->prop_hash_mask = new_hash_mask; + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { + if (pr->atom != JS_ATOM_NULL) { + h = ((uintptr_t)pr->atom & new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = i + 1; + } + } + } else { + /* just copy the previous hash table */ + memcpy(prop_hash_end(sh) - new_hash_size, prop_hash_end(old_sh) - new_hash_size, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + } + js_free(ctx, get_alloc_from_shape(old_sh)); + *psh = sh; + sh->prop_size = new_size; + return 0; +} + +/* remove the deleted properties. */ +static int compact_properties(JSContext *ctx, JSObject *p) +{ + JSShape *sh, *old_sh; + void *sh_alloc; + intptr_t h; + uint32_t new_hash_size, i, j, new_hash_mask, new_size; + JSShapeProperty *old_pr, *pr; + JSProperty *prop, *new_prop; + + sh = p->shape; + assert(!sh->is_hashed); + + new_size = max_int(JS_PROP_INITIAL_SIZE, + sh->prop_count - sh->deleted_prop_count); + assert(new_size <= sh->prop_size); + + new_hash_size = sh->prop_hash_mask + 1; + while ((new_hash_size / 2) >= new_size) + new_hash_size = new_hash_size / 2; + new_hash_mask = new_hash_size - 1; + + /* resize the hash table and the properties */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + memcpy(sh, old_sh, sizeof(JSShape)); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + + j = 0; + old_pr = old_sh->prop; + pr = sh->prop; + prop = p->prop; + for(i = 0; i < sh->prop_count; i++) { + if (old_pr->atom != JS_ATOM_NULL) { + pr->atom = old_pr->atom; + pr->flags = old_pr->flags; + h = ((uintptr_t)old_pr->atom & new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = j + 1; + prop[j] = prop[i]; + j++; + pr++; + } + old_pr++; + } + assert(j == (sh->prop_count - sh->deleted_prop_count)); + sh->prop_hash_mask = new_hash_mask; + sh->prop_size = new_size; + sh->deleted_prop_count = 0; + sh->prop_count = j; + + p->shape = sh; + js_free(ctx, get_alloc_from_shape(old_sh)); + + /* reduce the size of the object properties */ + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (new_prop) + p->prop = new_prop; + return 0; +} + +static int add_shape_property(JSContext *ctx, JSShape **psh, + JSObject *p, JSAtom atom, int prop_flags) +{ + JSRuntime *rt = ctx->rt; + JSShape *sh = *psh; + JSShapeProperty *pr, *prop; + uint32_t hash_mask, new_shape_hash = 0; + intptr_t h; + + /* update the shape hash */ + if (sh->is_hashed) { + js_shape_hash_unlink(rt, sh); + new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags); + } + + if (unlikely(sh->prop_count >= sh->prop_size)) { + if (resize_properties(ctx, psh, p, sh->prop_count + 1)) { + /* in case of error, reinsert in the hash table. + sh is still valid if resize_properties() failed */ + if (sh->is_hashed) + js_shape_hash_link(rt, sh); + return -1; + } + sh = *psh; + } + if (sh->is_hashed) { + sh->hash = new_shape_hash; + js_shape_hash_link(rt, sh); + } + /* Initialize the new shape property. + The object property at p->prop[sh->prop_count] is uninitialized */ + prop = get_shape_prop(sh); + pr = &prop[sh->prop_count++]; + pr->atom = JS_DupAtom(ctx, atom); + pr->flags = prop_flags; + /* add in hash table */ + hash_mask = sh->prop_hash_mask; + h = atom & hash_mask; + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = sh->prop_count; + return 0; +} + +/* find a hashed empty shape matching the prototype. Return NULL if + not found */ +static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto) +{ + JSShape *sh1; + uint32_t h, h1; + + h = shape_initial_hash(proto); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + if (sh1->hash == h && + sh1->proto == proto && + sh1->prop_count == 0) { + return sh1; + } + } + return NULL; +} + +/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if + not found */ +static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, + JSAtom atom, int prop_flags) +{ + JSShape *sh1; + uint32_t h, h1, i, n; + + h = sh->hash; + h = shape_hash(h, atom); + h = shape_hash(h, prop_flags); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + /* we test the hash first so that the rest is done only if the + shapes really match */ + if (sh1->hash == h && + sh1->proto == sh->proto && + sh1->prop_count == ((n = sh->prop_count) + 1)) { + for(i = 0; i < n; i++) { + if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) || + unlikely(sh1->prop[i].flags != sh->prop[i].flags)) + goto next; + } + if (unlikely(sh1->prop[n].atom != atom) || + unlikely(sh1->prop[n].flags != prop_flags)) + goto next; + return sh1; + } + next: ; + } + return NULL; +} + +static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) +{ + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + int j; + + /* XXX: should output readable class prototype */ + printf("%5d %3d%c %14p %5d %5d", i, + sh->header.ref_count, " *"[sh->is_hashed], + (void *)sh->proto, sh->prop_size, sh->prop_count); + for(j = 0; j < sh->prop_count; j++) { + printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), + sh->prop[j].atom)); + } + printf("\n"); +} + +static __maybe_unused void JS_DumpShapes(JSRuntime *rt) +{ + int i; + JSShape *sh; + struct list_head *el; + JSObject *p; + JSGCObjectHeader *gp; + + printf("JSShapes: {\n"); + printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS"); + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + JS_DumpShape(rt, i, sh); + assert(sh->is_hashed); + } + } + /* dump non-hashed shapes */ + list_for_each(el, &rt->gc_obj_list) { + gp = list_entry(el, JSGCObjectHeader, link); + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + if (!p->shape->is_hashed) { + JS_DumpShape(rt, -1, p->shape); + } + } + } + printf("}\n"); +} + +/* 'props[]' is used to initialized the object properties. The number + of elements depends on the shape. */ +static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id, + JSProperty *props) +{ + JSObject *p; + int i; + + js_trigger_gc(ctx->rt, sizeof(JSObject)); + p = js_malloc(ctx, sizeof(JSObject)); + if (unlikely(!p)) + goto fail; + p->class_id = class_id; + p->is_prototype = 0; + p->extensible = TRUE; + p->free_mark = 0; + p->is_exotic = 0; + p->fast_array = 0; + p->is_constructor = 0; + p->has_immutable_prototype = 0; + p->tmp_mark = 0; + p->is_HTMLDDA = 0; + p->weakref_count = 0; + p->u.opaque = NULL; + p->shape = sh; + p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); + if (unlikely(!p->prop)) { + js_free(ctx, p); + fail: + if (props) { + JSShapeProperty *prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + free_property(ctx->rt, &props[i], prs->flags); + prs++; + } + } + js_free_shape(ctx->rt, sh); + return JS_EXCEPTION; + } + + switch(class_id) { + case JS_CLASS_OBJECT: + break; + case JS_CLASS_ARRAY: + { + JSProperty *pr; + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.values = NULL; + p->u.array.count = 0; + p->u.array.u1.size = 0; + if (!props) { + /* XXX: remove */ + /* the length property is always the first one */ + if (likely(sh == ctx->array_shape)) { + pr = &p->prop[0]; + } else { + /* only used for the first array */ + /* cannot fail */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_LENGTH); + } + pr->u.value = JS_NewInt32(ctx, 0); + } + } + break; + case JS_CLASS_C_FUNCTION: + p->prop[0].u.value = JS_UNDEFINED; + break; + case JS_CLASS_ARGUMENTS: + case JS_CLASS_MAPPED_ARGUMENTS: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT16_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_DATAVIEW: + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: + p->u.object_data = JS_UNDEFINED; + goto set_exotic; + case JS_CLASS_REGEXP: + p->u.regexp.pattern = NULL; + p->u.regexp.bytecode = NULL; + break; + case JS_CLASS_GLOBAL_OBJECT: + p->u.global_object.uninitialized_vars = JS_UNDEFINED; + break; + default: + set_exotic: + if (ctx->rt->class_array[class_id].exotic) { + p->is_exotic = 1; + } + break; + } + p->header.ref_count = 1; + add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); + if (props) { + for(i = 0; i < sh->prop_count; i++) + p->prop[i] = props[i]; + } + return JS_MKPTR(JS_TAG_OBJECT, p); +} + +static JSObject *get_proto_obj(JSValueConst proto_val) +{ + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) + return NULL; + else + return JS_VALUE_GET_OBJ(proto_val); +} + +/* WARNING: proto must be an object or JS_NULL */ +JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val, + JSClassID class_id) +{ + JSShape *sh; + JSObject *proto; + + proto = get_proto_obj(proto_val); + sh = find_hashed_shape_proto(ctx->rt, proto); + if (likely(sh)) { + sh = js_dup_shape(sh); + } else { + sh = js_new_shape(ctx, proto); + if (!sh) + return JS_EXCEPTION; + } + return JS_NewObjectFromShape(ctx, sh, class_id, NULL); +} + +/* WARNING: the shape is not hashed. It is used for objects where + factorizing the shape is not relevant (prototypes, constructors) */ +static JSValue JS_NewObjectProtoClassAlloc(JSContext *ctx, JSValueConst proto_val, + JSClassID class_id, int n_alloc_props) +{ + JSShape *sh; + JSObject *proto; + int hash_size, hash_bits; + + if (n_alloc_props <= JS_PROP_INITIAL_SIZE) { + n_alloc_props = JS_PROP_INITIAL_SIZE; + hash_size = JS_PROP_INITIAL_HASH_SIZE; + } else { + hash_bits = 32 - clz32(n_alloc_props - 1); /* ceil(log2(radix)) */ + hash_size = 1 << hash_bits; + } + proto = get_proto_obj(proto_val); + sh = js_new_shape_nohash(ctx, proto, hash_size, n_alloc_props); + if (!sh) + return JS_EXCEPTION; + return JS_NewObjectFromShape(ctx, sh, class_id, NULL); +} + +#if 0 +static JSValue JS_GetObjectData(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + switch(p->class_id) { + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: + return JS_DupValue(ctx, p->u.object_data); + } + } + return JS_UNDEFINED; +} +#endif + +static int JS_SetObjectData(JSContext *ctx, JSValueConst obj, JSValue val) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + switch(p->class_id) { + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: + JS_FreeValue(ctx, p->u.object_data); + p->u.object_data = val; /* for JS_CLASS_STRING, 'val' must + be JS_TAG_STRING (and not a + rope) */ + return 0; + } + } + JS_FreeValue(ctx, val); + if (!JS_IsException(obj)) + JS_ThrowTypeError(ctx, "invalid object type"); + return -1; +} + +JSValue JS_NewObjectClass(JSContext *ctx, int class_id) +{ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id); +} + +JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto) +{ + return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT); +} + +JSValue JS_NewArray(JSContext *ctx) +{ + return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape), + JS_CLASS_ARRAY, NULL); +} + +JSValue JS_NewObject(JSContext *ctx) +{ + /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); +} + +static void js_function_set_properties(JSContext *ctx, JSValueConst func_obj, + JSAtom name, int len) +{ + /* ES6 feature non compatible with ES5.1: length is configurable */ + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, JS_NewInt32(ctx, len), + JS_PROP_CONFIGURABLE); + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, + JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE); +} + +static BOOL js_class_has_bytecode(JSClassID class_id) +{ + return (class_id == JS_CLASS_BYTECODE_FUNCTION || + class_id == JS_CLASS_GENERATOR_FUNCTION || + class_id == JS_CLASS_ASYNC_FUNCTION || + class_id == JS_CLASS_ASYNC_GENERATOR_FUNCTION); +} + +/* return NULL without exception if not a function or no bytecode */ +static JSFunctionBytecode *JS_GetFunctionBytecode(JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(val); + if (!js_class_has_bytecode(p->class_id)) + return NULL; + return p->u.func.function_bytecode; +} + +static void js_method_set_home_object(JSContext *ctx, JSValueConst func_obj, + JSValueConst home_obj) +{ + JSObject *p, *p1; + JSFunctionBytecode *b; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(func_obj); + if (!js_class_has_bytecode(p->class_id)) + return; + b = p->u.func.function_bytecode; + if (b->need_home_object) { + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT) + p1 = JS_VALUE_GET_OBJ(JS_DupValue(ctx, home_obj)); + else + p1 = NULL; + p->u.func.home_object = p1; + } +} + +static JSValue js_get_function_name(JSContext *ctx, JSAtom name) +{ + JSValue name_str; + + name_str = JS_AtomToString(ctx, name); + if (JS_AtomSymbolHasDescription(ctx, name)) { + name_str = JS_ConcatString3(ctx, "[", name_str, "]"); + } + return name_str; +} + +/* Modify the name of a method according to the atom and + 'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and + JS_PROP_HAS_SET. Also set the home object of the method. + Return < 0 if exception. */ +static int js_method_set_properties(JSContext *ctx, JSValueConst func_obj, + JSAtom name, int flags, JSValueConst home_obj) +{ + JSValue name_str; + + name_str = js_get_function_name(ctx, name); + if (flags & JS_PROP_HAS_GET) { + name_str = JS_ConcatString3(ctx, "get ", name_str, ""); + } else if (flags & JS_PROP_HAS_SET) { + name_str = JS_ConcatString3(ctx, "set ", name_str, ""); + } + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str, + JS_PROP_CONFIGURABLE) < 0) + return -1; + js_method_set_home_object(ctx, func_obj, home_obj); + return 0; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +static JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic, + JSValueConst proto_val, int n_fields) +{ + JSValue func_obj; + JSObject *p; + JSAtom name_atom; + + if (n_fields > 0) { + func_obj = JS_NewObjectProtoClassAlloc(ctx, proto_val, JS_CLASS_C_FUNCTION, n_fields); + } else { + func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); + } + if (JS_IsException(func_obj)) + return func_obj; + p = JS_VALUE_GET_OBJ(func_obj); + p->u.cfunc.realm = JS_DupContext(ctx); + p->u.cfunc.c_function.generic = func; + p->u.cfunc.length = length; + p->u.cfunc.cproto = cproto; + p->u.cfunc.magic = magic; + p->is_constructor = (cproto == JS_CFUNC_constructor || + cproto == JS_CFUNC_constructor_magic || + cproto == JS_CFUNC_constructor_or_func || + cproto == JS_CFUNC_constructor_or_func_magic); + if (!name) + name = ""; + name_atom = JS_NewAtom(ctx, name); + if (name_atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + js_function_set_properties(ctx, func_obj, name_atom, length); + JS_FreeAtom(ctx, name_atom); + return func_obj; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic) +{ + return JS_NewCFunction3(ctx, func, name, length, cproto, magic, + ctx->function_proto, 0); +} + +typedef struct JSCFunctionDataRecord { + JSCFunctionData *func; + uint8_t length; + uint8_t data_len; + uint16_t magic; + JSValue data[0]; +} JSCFunctionDataRecord; + +static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_FreeValueRT(rt, s->data[i]); + } + js_free_rt(rt, s); + } +} + +static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_MarkValue(rt, s->data[i], mark_func); + } + } +} + +static JSValue js_c_function_data_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA); + JSValueConst *arg_buf; + int i; + + /* XXX: could add the function on the stack for debug */ + if (unlikely(argc < s->length)) { + arg_buf = alloca(sizeof(arg_buf[0]) * s->length); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < s->length; i++) + arg_buf[i] = JS_UNDEFINED; + } else { + arg_buf = argv; + } + + return s->func(ctx, this_val, argc, arg_buf, s->magic, s->data); +} + +JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, + int length, int magic, int data_len, + JSValueConst *data) +{ + JSCFunctionDataRecord *s; + JSValue func_obj; + int i; + + func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_C_FUNCTION_DATA); + if (JS_IsException(func_obj)) + return func_obj; + s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue)); + if (!s) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + s->func = func; + s->length = length; + s->data_len = data_len; + s->magic = magic; + for(i = 0; i < data_len; i++) + s->data[i] = JS_DupValue(ctx, data[i]); + JS_SetOpaque(func_obj, s); + js_function_set_properties(ctx, func_obj, + JS_ATOM_empty_string, length); + return func_obj; +} + +static JSContext *js_autoinit_get_realm(JSProperty *pr) +{ + return (JSContext *)(pr->u.init.realm_and_id & ~3); +} + +static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr) +{ + return pr->u.init.realm_and_id & 3; +} + +static void js_autoinit_free(JSRuntime *rt, JSProperty *pr) +{ + JS_FreeContext(js_autoinit_get_realm(pr)); +} + +static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr, + JS_MarkFunc *mark_func) +{ + mark_func(rt, &js_autoinit_get_realm(pr)->header); +} + +static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags) +{ + if (unlikely(prop_flags & JS_PROP_TMASK)) { + if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + free_var_ref(rt, pr->u.var_ref); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_free(rt, pr); + } + } else { + JS_FreeValueRT(rt, pr->u.value); + } +} + +static force_inline JSShapeProperty *find_own_property1(JSObject *p, + JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = (uintptr_t)atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + return pr; + } + h = pr->hash_next; + } + return NULL; +} + +static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, + JSObject *p, + JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = (uintptr_t)atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + *ppr = &p->prop[h - 1]; + /* the compiler should be able to assume that pr != NULL here */ + return pr; + } + h = pr->hash_next; + } + *ppr = NULL; + return NULL; +} + +/* indicate that the object may be part of a function prototype cycle */ +static void set_cycle_flag(JSContext *ctx, JSValueConst obj) +{ +} + +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) +{ + if (var_ref) { + assert(var_ref->header.ref_count > 0); + if (--var_ref->header.ref_count == 0) { + if (var_ref->is_detached) { + JS_FreeValueRT(rt, var_ref->value); + } else { + JSStackFrame *sf = var_ref->stack_frame; + assert(sf->var_refs[var_ref->var_ref_idx] == var_ref); + sf->var_refs[var_ref->var_ref_idx] = NULL; + if (sf->js_mode & JS_MODE_ASYNC) { + JSAsyncFunctionState *async_func = container_of(sf, JSAsyncFunctionState, frame); + async_func_free(rt, async_func); + } + } + remove_gc_object(&var_ref->header); + js_free_rt(rt, var_ref); + } + } +} + +static void js_array_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + int i; + + for(i = 0; i < p->u.array.count; i++) { + JS_FreeValueRT(rt, p->u.array.u.values[i]); + } + js_free_rt(rt, p->u.array.u.values); +} + +static void js_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + int i; + + for(i = 0; i < p->u.array.count; i++) { + JS_MarkValue(rt, p->u.array.u.values[i], mark_func); + } +} + +static void js_object_data_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_FreeValueRT(rt, p->u.object_data); + p->u.object_data = JS_UNDEFINED; +} + +static void js_object_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_MarkValue(rt, p->u.object_data, mark_func); +} + +static void js_c_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + JS_FreeContext(p->u.cfunc.realm); +} + +static void js_c_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + mark_func(rt, &p->u.cfunc.realm->header); +} + +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p1, *p = JS_VALUE_GET_OBJ(val); + JSFunctionBytecode *b; + JSVarRef **var_refs; + int i; + + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + b = p->u.func.function_bytecode; + if (b) { + var_refs = p->u.func.var_refs; + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) + free_var_ref(rt, var_refs[i]); + js_free_rt(rt, var_refs); + } + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b)); + } +} + +static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSVarRef **var_refs = p->u.func.var_refs; + JSFunctionBytecode *b = p->u.func.function_bytecode; + int i; + + if (p->u.func.home_object) { + JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object), + mark_func); + } + if (b) { + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) { + JSVarRef *var_ref = var_refs[i]; + if (var_ref) { + mark_func(rt, &var_ref->header); + } + } + } + /* must mark the function bytecode because template objects may be + part of a cycle */ + JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func); + } +} + +static void js_bound_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_FreeValueRT(rt, bf->func_obj); + JS_FreeValueRT(rt, bf->this_val); + for(i = 0; i < bf->argc; i++) { + JS_FreeValueRT(rt, bf->argv[i]); + } + js_free_rt(rt, bf); +} + +static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_MarkValue(rt, bf->func_obj, mark_func); + JS_MarkValue(rt, bf->this_val, mark_func); + for(i = 0; i < bf->argc; i++) + JS_MarkValue(rt, bf->argv[i], mark_func); +} + +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + int i; + + JS_FreeValueRT(rt, it->obj); + if (!it->is_array) { + for(i = 0; i < it->atom_count; i++) { + JS_FreeAtomRT(rt, it->tab_atom[i].atom); + } + js_free_rt(rt, it->tab_atom); + } + js_free_rt(rt, it); +} + +static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + JS_MarkValue(rt, it->obj, mark_func); +} + +static void free_object(JSRuntime *rt, JSObject *p) +{ + int i; + JSClassFinalizer *finalizer; + JSShape *sh; + JSShapeProperty *pr; + + p->free_mark = 1; /* used to tell the object is invalid when + freeing cycles */ + /* free all the fields */ + sh = p->shape; + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + free_property(rt, &p->prop[i], pr->flags); + pr++; + } + js_free_rt(rt, p->prop); + /* as an optimization we destroy the shape immediately without + putting it in gc_zero_ref_count_list */ + js_free_shape(rt, sh); + + /* fail safe */ + p->shape = NULL; + p->prop = NULL; + + finalizer = rt->class_array[p->class_id].finalizer; + if (finalizer) + (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); + + /* fail safe */ + p->class_id = 0; + p->u.opaque = NULL; + p->u.func.var_refs = NULL; + p->u.func.home_object = NULL; + + remove_gc_object(&p->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES) { + if (p->header.ref_count == 0 && p->weakref_count == 0) { + js_free_rt(rt, p); + } else { + /* keep the object structure because there are may be + references to it */ + list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); + } + } else { + /* keep the object structure in case there are weak references to it */ + if (p->weakref_count == 0) { + js_free_rt(rt, p); + } else { + p->header.mark = 0; /* reset the mark so that the weakref can be freed */ + } + } +} + +static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) +{ + switch(gp->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + free_object(rt, (JSObject *)gp); + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + free_function_bytecode(rt, (JSFunctionBytecode *)gp); + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + __async_func_free(rt, (JSAsyncFunctionState *)gp); + break; + case JS_GC_OBJ_TYPE_MODULE: + js_free_module_def(rt, (JSModuleDef *)gp); + break; + default: + abort(); + } +} + +static void free_zero_refcount(JSRuntime *rt) +{ + struct list_head *el; + JSGCObjectHeader *p; + + rt->gc_phase = JS_GC_PHASE_DECREF; + for(;;) { + el = rt->gc_zero_ref_count_list.next; + if (el == &rt->gc_zero_ref_count_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + assert(p->ref_count == 0); + free_gc_object(rt, p); + } + rt->gc_phase = JS_GC_PHASE_NONE; +} + +/* called with the ref_count of 'v' reaches zero. */ +void __JS_FreeValueRT(JSRuntime *rt, JSValue v) +{ + uint32_t tag = JS_VALUE_GET_TAG(v); + +#ifdef DUMP_FREE + { + printf("Freeing "); + if (tag == JS_TAG_OBJECT) { + JS_DumpObject(rt, JS_VALUE_GET_OBJ(v)); + } else { + JS_DumpValueShort(rt, v); + printf("\n"); + } + } +#endif + + switch(tag) { + case JS_TAG_STRING: + { + JSString *p = JS_VALUE_GET_STRING(v); + if (p->atom_type) { + JS_FreeAtomStruct(rt, p); + } else { +#ifdef DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + } + } + break; + case JS_TAG_STRING_ROPE: + /* Note: recursion is acceptable because the rope depth is bounded */ + { + JSStringRope *p = JS_VALUE_GET_STRING_ROPE(v); + JS_FreeValueRT(rt, p->left); + JS_FreeValueRT(rt, p->right); + js_free_rt(rt, p); + } + break; + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + case JS_TAG_MODULE: + { + JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); + if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { + list_del(&p->link); + list_add(&p->link, &rt->gc_zero_ref_count_list); + p->mark = 1; /* indicate that the object is about to be freed */ + if (rt->gc_phase == JS_GC_PHASE_NONE) { + free_zero_refcount(rt); + } + } + } + break; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(v); + js_free_rt(rt, p); + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(v); + JS_FreeAtomStruct(rt, p); + } + break; + default: + abort(); + } +} + +void __JS_FreeValue(JSContext *ctx, JSValue v) +{ + __JS_FreeValueRT(ctx->rt, v); +} + +/* garbage collection */ + +static void gc_remove_weak_objects(JSRuntime *rt) +{ + struct list_head *el; + + /* add the freed objects to rt->gc_zero_ref_count_list so that + rt->weakref_list is not modified while we traverse it */ + rt->gc_phase = JS_GC_PHASE_DECREF; + + list_for_each(el, &rt->weakref_list) { + JSWeakRefHeader *wh = list_entry(el, JSWeakRefHeader, link); + switch(wh->weakref_type) { + case JS_WEAKREF_TYPE_MAP: + map_delete_weakrefs(rt, wh); + break; + case JS_WEAKREF_TYPE_WEAKREF: + weakref_delete_weakref(rt, wh); + break; + case JS_WEAKREF_TYPE_FINREC: + finrec_delete_weakref(rt, wh); + break; + default: + abort(); + } + } + + rt->gc_phase = JS_GC_PHASE_NONE; + /* free the freed objects here. */ + free_zero_refcount(rt); +} + +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type) +{ + h->mark = 0; + h->gc_obj_type = type; + list_add_tail(&h->link, &rt->gc_obj_list); +} + +static void remove_gc_object(JSGCObjectHeader *h) +{ + list_del(&h->link); +} + +void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + if (JS_VALUE_HAS_REF_COUNT(val)) { + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + case JS_TAG_MODULE: + mark_func(rt, JS_VALUE_GET_PTR(val)); + break; + default: + break; + } + } +} + +static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, + JS_MarkFunc *mark_func) +{ + switch(gp->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + { + JSObject *p = (JSObject *)gp; + JSShapeProperty *prs; + JSShape *sh; + int i; + sh = p->shape; + mark_func(rt, &sh->header); + /* mark all the fields */ + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL) { + if (prs->flags & JS_PROP_TMASK) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + mark_func(rt, &pr->u.getset.getter->header); + if (pr->u.getset.setter) + mark_func(rt, &pr->u.getset.setter->header); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* Note: the tag does not matter + provided it is a GC object */ + mark_func(rt, &pr->u.var_ref->header); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_mark(rt, pr, mark_func); + } + } else { + JS_MarkValue(rt, pr->u.value, mark_func); + } + } + prs++; + } + + if (p->class_id != JS_CLASS_OBJECT) { + JSClassGCMark *gc_mark; + gc_mark = rt->class_array[p->class_id].gc_mark; + if (gc_mark) + gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func); + } + } + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + /* the template objects can be part of a cycle */ + { + JSFunctionBytecode *b = (JSFunctionBytecode *)gp; + int i; + for(i = 0; i < b->cpool_count; i++) { + JS_MarkValue(rt, b->cpool[i], mark_func); + } + if (b->realm) + mark_func(rt, &b->realm->header); + } + break; + case JS_GC_OBJ_TYPE_VAR_REF: + { + JSVarRef *var_ref = (JSVarRef *)gp; + if (var_ref->is_detached) { + JS_MarkValue(rt, *var_ref->pvalue, mark_func); + } else { + JSStackFrame *sf = var_ref->stack_frame; + if (sf->js_mode & JS_MODE_ASYNC) { + JSAsyncFunctionState *async_func = container_of(sf, JSAsyncFunctionState, frame); + mark_func(rt, &async_func->header); + } + } + } + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + { + JSAsyncFunctionState *s = (JSAsyncFunctionState *)gp; + JSStackFrame *sf = &s->frame; + JSValue *sp; + + if (!s->is_completed) { + JS_MarkValue(rt, sf->cur_func, mark_func); + JS_MarkValue(rt, s->this_val, mark_func); + /* sf->cur_sp = NULL if the function is running */ + if (sf->cur_sp) { + /* if the function is running, cur_sp is not known so we + cannot mark the stack. Marking the variables is not needed + because a running function cannot be part of a removable + cycle */ + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) + JS_MarkValue(rt, *sp, mark_func); + } + } + JS_MarkValue(rt, s->resolving_funcs[0], mark_func); + JS_MarkValue(rt, s->resolving_funcs[1], mark_func); + } + break; + case JS_GC_OBJ_TYPE_SHAPE: + { + JSShape *sh = (JSShape *)gp; + if (sh->proto != NULL) { + mark_func(rt, &sh->proto->header); + } + } + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + { + JSContext *ctx = (JSContext *)gp; + JS_MarkContext(rt, ctx, mark_func); + } + break; + case JS_GC_OBJ_TYPE_MODULE: + { + JSModuleDef *m = (JSModuleDef *)gp; + js_mark_module_def(rt, m, mark_func); + } + break; + default: + abort(); + } +} + +static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + assert(p->ref_count > 0); + p->ref_count--; + if (p->ref_count == 0 && p->mark == 1) { + list_del(&p->link); + list_add_tail(&p->link, &rt->tmp_obj_list); + } +} + +static void gc_decref(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; + + init_list_head(&rt->tmp_obj_list); + + /* decrement the refcount of all the children of all the GC + objects and move the GC objects with zero refcount to + tmp_obj_list */ + list_for_each_safe(el, el1, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->mark == 0); + mark_children(rt, p, gc_decref_child); + p->mark = 1; + if (p->ref_count == 0) { + list_del(&p->link); + list_add_tail(&p->link, &rt->tmp_obj_list); + } + } +} + +static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + p->ref_count++; + if (p->ref_count == 1) { + /* ref_count was 0: remove from tmp_obj_list and add at the + end of gc_obj_list */ + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_obj_list); + p->mark = 0; /* reset the mark for the next GC call */ + } +} + +static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) +{ + p->ref_count++; +} + +static void gc_scan(JSRuntime *rt) +{ + struct list_head *el; + JSGCObjectHeader *p; + + /* keep the objects with a refcount > 0 and their children. */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->ref_count > 0); + p->mark = 0; /* reset the mark for the next GC call */ + mark_children(rt, p, gc_scan_incref_child); + } + + /* restore the refcount of the objects to be deleted. */ + list_for_each(el, &rt->tmp_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + mark_children(rt, p, gc_scan_incref_child2); + } +} + +static void gc_free_cycles(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; +#ifdef DUMP_GC_FREE + BOOL header_done = FALSE; +#endif + + rt->gc_phase = JS_GC_PHASE_REMOVE_CYCLES; + + for(;;) { + el = rt->tmp_obj_list.next; + if (el == &rt->tmp_obj_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + /* Only need to free the GC object associated with JS values + or async functions. The rest will be automatically removed + because they must be referenced by them. */ + switch(p->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + case JS_GC_OBJ_TYPE_MODULE: +#ifdef DUMP_GC_FREE + if (!header_done) { + printf("Freeing cycles:\n"); + JS_DumpObjectHeader(rt); + header_done = TRUE; + } + JS_DumpGCObject(rt, p); +#endif + free_gc_object(rt, p); + break; + default: + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_zero_ref_count_list); + break; + } + } + rt->gc_phase = JS_GC_PHASE_NONE; + + list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || + p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE || + p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION || + p->gc_obj_type == JS_GC_OBJ_TYPE_MODULE); + if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT && + ((JSObject *)p)->weakref_count != 0) { + /* keep the object because there are weak references to it */ + p->mark = 0; + } else { + js_free_rt(rt, p); + } + } + + init_list_head(&rt->gc_zero_ref_count_list); +} + +static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects) +{ + if (remove_weak_objects) { + /* free the weakly referenced object or symbol structures, delete + the associated Map/Set entries and queue the finalization + registry callbacks. */ + gc_remove_weak_objects(rt); + } + + /* decrement the reference of the children of each object. mark = + 1 after this pass. */ + gc_decref(rt); + + /* keep the GC objects with a non zero refcount and their childs */ + gc_scan(rt); + + /* free the GC objects in a cycle */ + gc_free_cycles(rt); +} + +void JS_RunGC(JSRuntime *rt) +{ + JS_RunGCInternal(rt, TRUE); +} + +/* Return false if not an object or if the object has already been + freed (zombie objects are visible in finalizers when freeing + cycles). */ +BOOL JS_IsLiveObject(JSRuntime *rt, JSValueConst obj) +{ + JSObject *p; + if (!JS_IsObject(obj)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + return !p->free_mark; +} + +/* Compute memory used by various object types */ +/* XXX: poor man's approach to handling multiply referenced objects */ +typedef struct JSMemoryUsage_helper { + double memory_used_count; + double str_count; + double str_size; + int64_t js_func_count; + double js_func_size; + int64_t js_func_code_size; + int64_t js_func_pc2line_count; + int64_t js_func_pc2line_size; +} JSMemoryUsage_helper; + +static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp); + +static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) +{ + if (!str->atom_type) { /* atoms are handled separately */ + double s_ref_count = str->header.ref_count; + hp->str_count += 1 / s_ref_count; + hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + + 1 - str->is_wide_char) / s_ref_count); + } +} + +static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp) +{ + int memory_used_count, js_func_size, i; + + memory_used_count = 0; + js_func_size = offsetof(JSFunctionBytecode, debug); + if (b->vardefs) { + js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs); + } + if (b->cpool) { + js_func_size += b->cpool_count * sizeof(*b->cpool); + for (i = 0; i < b->cpool_count; i++) { + JSValueConst val = b->cpool[i]; + compute_value_size(val, hp); + } + } + if (b->closure_var) { + js_func_size += b->closure_var_count * sizeof(*b->closure_var); + } + if (!b->read_only_bytecode && b->byte_code_buf) { + hp->js_func_code_size += b->byte_code_len; + } + if (b->has_debug) { + js_func_size += sizeof(*b) - offsetof(JSFunctionBytecode, debug); + if (b->debug.source) { + memory_used_count++; + js_func_size += b->debug.source_len + 1; + } + if (b->debug.pc2line_len) { + memory_used_count++; + hp->js_func_pc2line_count += 1; + hp->js_func_pc2line_size += b->debug.pc2line_len; + } + } + hp->js_func_size += js_func_size; + hp->js_func_count += 1; + hp->memory_used_count += memory_used_count; +} + +static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp) +{ + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_STRING: + compute_jsstring_size(JS_VALUE_GET_STRING(val), hp); + break; + case JS_TAG_BIG_INT: + /* should track JSBigInt usage */ + break; + } +} + +void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) +{ + struct list_head *el, *el1; + int i; + JSMemoryUsage_helper mem = { 0 }, *hp = &mem; + + memset(s, 0, sizeof(*s)); + s->malloc_count = rt->malloc_state.malloc_count; + s->malloc_size = rt->malloc_state.malloc_size; + s->malloc_limit = rt->malloc_state.malloc_limit; + + s->memory_used_count = 2; /* rt + rt->class_array */ + s->memory_used_size = sizeof(JSRuntime) + sizeof(JSValue) * rt->class_count; + + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSShape *sh = ctx->array_shape; + s->memory_used_count += 2; /* ctx + ctx->class_proto */ + s->memory_used_size += sizeof(JSContext) + + sizeof(JSValue) * rt->class_count; + s->binary_object_count += ctx->binary_object_count; + s->binary_object_size += ctx->binary_object_size; + + /* the hashed shapes are counted separately */ + if (sh && !sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + list_for_each(el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el1, JSModuleDef, link); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*m); + if (m->req_module_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries); + } + if (m->export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries); + for (i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) { + /* potential multiple count */ + s->memory_used_count += 1; + compute_value_size(me->u.local.var_ref->value, hp); + } + } + } + if (m->star_export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries); + } + if (m->import_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries); + } + compute_value_size(m->module_ns, hp); + compute_value_size(m->func_obj, hp); + } + } + + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + JSShape *sh; + JSShapeProperty *prs; + + /* XXX: could count the other GC object types too */ + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { + compute_bytecode_size((JSFunctionBytecode *)gp, hp); + continue; + } else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { + continue; + } + p = (JSObject *)gp; + sh = p->shape; + s->obj_count++; + if (p->prop) { + s->memory_used_count++; + s->prop_size += sh->prop_size * sizeof(*p->prop); + s->prop_count += sh->prop_count; + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) { + compute_value_size(pr->u.value, hp); + } + prs++; + } + } + /* the hashed shapes are counted separately */ + if (!sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + + switch(p->class_id) { + case JS_CLASS_ARRAY: /* u.array | length */ + case JS_CLASS_ARGUMENTS: /* u.array | length */ + s->array_count++; + if (p->fast_array) { + s->fast_array_count++; + if (p->u.array.u.values) { + s->memory_used_count++; + s->memory_used_size += p->u.array.count * + sizeof(*p->u.array.u.values); + s->fast_array_elements += p->u.array.count; + for (i = 0; i < p->u.array.count; i++) { + compute_value_size(p->u.array.u.values[i], hp); + } + } + } + break; + case JS_CLASS_MAPPED_ARGUMENTS: /* u.array | length */ + if (p->fast_array) { + s->fast_array_count++; + if (p->u.array.u.values) { + s->memory_used_count++; + s->memory_used_size += p->u.array.count * + sizeof(*p->u.array.u.var_refs); + s->fast_array_elements += p->u.array.count; + for (i = 0; i < p->u.array.count; i++) { + compute_value_size(*p->u.array.u.var_refs[i]->pvalue, hp); + } + } + } + break; + case JS_CLASS_NUMBER: /* u.object_data */ + case JS_CLASS_STRING: /* u.object_data */ + case JS_CLASS_BOOLEAN: /* u.object_data */ + case JS_CLASS_SYMBOL: /* u.object_data */ + case JS_CLASS_DATE: /* u.object_data */ + case JS_CLASS_BIG_INT: /* u.object_data */ + compute_value_size(p->u.object_data, hp); + break; + case JS_CLASS_C_FUNCTION: /* u.cfunc */ + s->c_func_count++; + break; + case JS_CLASS_BYTECODE_FUNCTION: /* u.func */ + { + JSFunctionBytecode *b = p->u.func.function_bytecode; + JSVarRef **var_refs = p->u.func.var_refs; + /* home_object: object will be accounted for in list scan */ + if (var_refs) { + s->memory_used_count++; + s->js_func_size += b->closure_var_count * sizeof(*var_refs); + for (i = 0; i < b->closure_var_count; i++) { + if (var_refs[i]) { + double ref_count = var_refs[i]->header.ref_count; + s->memory_used_count += 1 / ref_count; + s->js_func_size += sizeof(*var_refs[i]) / ref_count; + /* handle non object closed values */ + if (var_refs[i]->pvalue == &var_refs[i]->value) { + /* potential multiple count */ + compute_value_size(var_refs[i]->value, hp); + } + } + } + } + } + break; + case JS_CLASS_BOUND_FUNCTION: /* u.bound_function */ + { + JSBoundFunction *bf = p->u.bound_function; + /* func_obj and this_val are objects */ + for (i = 0; i < bf->argc; i++) { + compute_value_size(bf->argv[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv); + } + break; + case JS_CLASS_C_FUNCTION_DATA: /* u.c_function_data_record */ + { + JSCFunctionDataRecord *fd = p->u.c_function_data_record; + if (fd) { + for (i = 0; i < fd->data_len; i++) { + compute_value_size(fd->data[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data); + } + } + break; + case JS_CLASS_REGEXP: /* u.regexp */ + compute_jsstring_size(p->u.regexp.pattern, hp); + compute_jsstring_size(p->u.regexp.bytecode, hp); + break; + + case JS_CLASS_FOR_IN_ITERATOR: /* u.for_in_iterator */ + { + JSForInIterator *it = p->u.for_in_iterator; + if (it) { + compute_value_size(it->obj, hp); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*it); + } + } + break; + case JS_CLASS_ARRAY_BUFFER: /* u.array_buffer */ + case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */ + { + JSArrayBuffer *abuf = p->u.array_buffer; + if (abuf) { + s->memory_used_count += 1; + s->memory_used_size += sizeof(*abuf); + if (abuf->data) { + s->memory_used_count += 1; + s->memory_used_size += abuf->byte_length; + } + } + } + break; + case JS_CLASS_GENERATOR: /* u.generator_data */ + case JS_CLASS_UINT8C_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_DATAVIEW: /* u.typed_array */ + case JS_CLASS_MAP: /* u.map_state */ + case JS_CLASS_SET: /* u.map_state */ + case JS_CLASS_WEAKMAP: /* u.map_state */ + case JS_CLASS_WEAKSET: /* u.map_state */ + case JS_CLASS_MAP_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_SET_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_ARRAY_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_STRING_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_PROXY: /* u.proxy_data */ + case JS_CLASS_PROMISE: /* u.promise_data */ + case JS_CLASS_PROMISE_RESOLVE_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_PROMISE_REJECT_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_ASYNC_FUNCTION_RESOLVE: /* u.async_function_data */ + case JS_CLASS_ASYNC_FUNCTION_REJECT: /* u.async_function_data */ + case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR: /* u.async_from_sync_iterator_data */ + case JS_CLASS_ASYNC_GENERATOR: /* u.async_generator_data */ + /* TODO */ + default: + /* XXX: class definition should have an opaque block size */ + if (p->u.opaque) { + s->memory_used_count += 1; + } + break; + } + } + s->obj_size += s->obj_count * sizeof(JSObject); + + /* hashed shapes */ + s->memory_used_count++; /* rt->shape_hash */ + s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size; + for(i = 0; i < rt->shape_hash_size; i++) { + JSShape *sh; + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + } + + /* atoms */ + s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */ + s->atom_count = rt->atom_count; + s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size + + sizeof(rt->atom_hash[0]) * rt->atom_hash_size; + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { + s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) + + 1 - p->is_wide_char); + } + } + s->str_count = round(mem.str_count); + s->str_size = round(mem.str_size); + s->js_func_count = mem.js_func_count; + s->js_func_size = round(mem.js_func_size); + s->js_func_code_size = mem.js_func_code_size; + s->js_func_pc2line_count = mem.js_func_pc2line_count; + s->js_func_pc2line_size = mem.js_func_pc2line_size; + s->memory_used_count += round(mem.memory_used_count) + + s->atom_count + s->str_count + + s->obj_count + s->shape_count + + s->js_func_count + s->js_func_pc2line_count; + s->memory_used_size += s->atom_size + s->str_size + + s->obj_size + s->prop_size + s->shape_size + + s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size; +} + +void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) +{ + fprintf(fp, "QuickJS memory usage -- version, %d-bit, malloc limit: %"PRId64"\n\n", + (int)sizeof(void *) * 8, s->malloc_limit); +#if 1 + if (rt) { + static const struct { + const char *name; + size_t size; + } object_types[] = { + { "JSRuntime", sizeof(JSRuntime) }, + { "JSContext", sizeof(JSContext) }, + { "JSObject", sizeof(JSObject) }, + { "JSString", sizeof(JSString) }, + { "JSFunctionBytecode", sizeof(JSFunctionBytecode) }, + }; + int i, usage_size_ok = 0; + for(i = 0; i < countof(object_types); i++) { + unsigned int size = object_types[i].size; + void *p = js_malloc_rt(rt, size); + if (p) { + unsigned int size1 = js_malloc_usable_size_rt(rt, p); + if (size1 >= size) { + usage_size_ok = 1; + fprintf(fp, " %3u + %-2u %s\n", + size, size1 - size, object_types[i].name); + } + js_free_rt(rt, p); + } + } + if (!usage_size_ok) { + fprintf(fp, " malloc_usable_size unavailable\n"); + } + { + int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 }; + int class_id; + struct list_head *el; + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; + } + } + fprintf(fp, "\n" "JSObject classes\n"); + if (obj_classes[0]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none"); + for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { + if (obj_classes[class_id] && class_id < rt->class_count) { + char buf[ATOM_GET_STR_BUF_SIZE]; + fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id, + JS_AtomGetStrRT(rt, buf, sizeof(buf), rt->class_array[class_id].class_name)); + } + } + if (obj_classes[JS_CLASS_INIT_COUNT]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other"); + } + fprintf(fp, "\n"); + } +#endif + fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE"); + + if (s->malloc_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per block)\n", + "memory allocated", s->malloc_count, s->malloc_size, + (double)s->malloc_size / s->malloc_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%d overhead, %0.1f average slack)\n", + "memory used", s->memory_used_count, s->memory_used_size, + MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) / + s->memory_used_count)); + } + if (s->atom_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per atom)\n", + "atoms", s->atom_count, s->atom_size, + (double)s->atom_size / s->atom_count); + } + if (s->str_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per string)\n", + "strings", s->str_count, s->str_size, + (double)s->str_size / s->str_count); + } + if (s->obj_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + "objects", s->obj_count, s->obj_size, + (double)s->obj_size / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + " properties", s->prop_count, s->prop_size, + (double)s->prop_count / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per shape)\n", + " shapes", s->shape_count, s->shape_size, + (double)s->shape_size / s->shape_count); + } + if (s->js_func_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "bytecode functions", s->js_func_count, s->js_func_size); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " bytecode", s->js_func_count, s->js_func_code_size, + (double)s->js_func_code_size / s->js_func_count); + if (s->js_func_pc2line_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " pc2line", s->js_func_pc2line_count, + s->js_func_pc2line_size, + (double)s->js_func_pc2line_size / s->js_func_pc2line_count); + } + } + if (s->c_func_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count); + } + if (s->array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count); + if (s->fast_array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", " fast arrays", s->fast_array_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per fast array)\n", + " elements", s->fast_array_elements, + s->fast_array_elements * (int)sizeof(JSValue), + (double)s->fast_array_elements / s->fast_array_count); + } + } + if (s->binary_object_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "binary objects", s->binary_object_count, s->binary_object_size); + } +} + +JSValue JS_GetGlobalObject(JSContext *ctx) +{ + return JS_DupValue(ctx, ctx->global_obj); +} + +/* WARNING: obj is freed */ +JSValue JS_Throw(JSContext *ctx, JSValue obj) +{ + JSRuntime *rt = ctx->rt; + JS_FreeValue(ctx, rt->current_exception); + rt->current_exception = obj; + rt->current_exception_is_uncatchable = FALSE; + return JS_EXCEPTION; +} + +/* return the pending exception (cannot be called twice). */ +JSValue JS_GetException(JSContext *ctx) +{ + JSValue val; + JSRuntime *rt = ctx->rt; + val = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + return val; +} + +JS_BOOL JS_HasException(JSContext *ctx) +{ + return !JS_IsUninitialized(ctx->rt->current_exception); +} + +static void dbuf_put_leb128(DynBuf *s, uint32_t v) +{ + uint32_t a; + for(;;) { + a = v & 0x7f; + v >>= 7; + if (v != 0) { + dbuf_putc(s, a | 0x80); + } else { + dbuf_putc(s, a); + break; + } + } +} + +static void dbuf_put_sleb128(DynBuf *s, int32_t v1) +{ + uint32_t v = v1; + dbuf_put_leb128(s, (2 * v) ^ -(v >> 31)); +} + +static int get_leb128(uint32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + const uint8_t *ptr = buf; + uint32_t v, a, i; + v = 0; + for(i = 0; i < 5; i++) { + if (unlikely(ptr >= buf_end)) + break; + a = *ptr++; + v |= (a & 0x7f) << (i * 7); + if (!(a & 0x80)) { + *pval = v; + return ptr - buf; + } + } + *pval = 0; + return -1; +} + +static int get_sleb128(int32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + int ret; + uint32_t val; + ret = get_leb128(&val, buf, buf_end); + if (ret < 0) { + *pval = 0; + return -1; + } + *pval = (val >> 1) ^ -(val & 1); + return ret; +} + +/* use pc_value = -1 to get the position of the function definition */ +static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, + uint32_t pc_value, int *pcol_num) +{ + const uint8_t *p_end, *p; + int new_line_num, line_num, pc, v, ret, new_col_num, col_num; + uint32_t val; + unsigned int op; + + if (!b->has_debug || !b->debug.pc2line_buf) + goto fail; /* function was stripped */ + + p = b->debug.pc2line_buf; + p_end = p + b->debug.pc2line_len; + + /* get the function line and column numbers */ + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + line_num = val + 1; + + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + p += ret; + col_num = val + 1; + + if (pc_value != -1) { + pc = 0; + while (p < p_end) { + op = *p++; + if (op == 0) { + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + pc += val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + new_line_num = line_num + v; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + new_col_num = col_num + v; + + if (pc_value < pc) + goto done; + line_num = new_line_num; + col_num = new_col_num; + } + } + done: + *pcol_num = col_num; + return line_num; + fail: + *pcol_num = 0; + return 0; +} + +/* return a string property without executing arbitrary JS code (used + when dumping the stack trace or in debug print). */ +static const char *get_prop_string(JSContext *ctx, JSValueConst obj, JSAtom prop) +{ + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + JSValueConst val; + + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + /* we look at one level in the prototype to handle the 'name' + field of the Error objects */ + p = p->shape->proto; + if (!p) + return NULL; + prs = find_own_property(&pr, p, prop); + if (!prs) + return NULL; + } + + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return NULL; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return NULL; + return JS_ToCString(ctx, val); +} + +#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) + +/* if filename != NULL, an additional level is added with the filename + and line number information (used for parse error). */ +static void build_backtrace(JSContext *ctx, JSValueConst error_obj, + const char *filename, int line_num, int col_num, + int backtrace_flags) +{ + JSStackFrame *sf; + JSValue str; + DynBuf dbuf; + const char *func_name_str; + const char *str1; + JSObject *p; + + if (!JS_IsObject(error_obj)) + return; /* protection in the out of memory case */ + + js_dbuf_init(ctx, &dbuf); + if (filename) { + dbuf_printf(&dbuf, " at %s", filename); + if (line_num != -1) + dbuf_printf(&dbuf, ":%d:%d", line_num, col_num); + dbuf_putc(&dbuf, '\n'); + str = JS_NewString(ctx, filename); + if (JS_IsException(str)) + return; + /* Note: SpiderMonkey does that, could update once there is a standard */ + if (JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_fileName, str, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0 || + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, line_num), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0 || + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_columnNumber, JS_NewInt32(ctx, col_num), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0) { + return; + } + } + for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) + break; + if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { + backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; + continue; + } + func_name_str = get_prop_string(ctx, sf->cur_func, JS_ATOM_name); + if (!func_name_str || func_name_str[0] == '\0') + str1 = ""; + else + str1 = func_name_str; + dbuf_printf(&dbuf, " at %s", str1); + JS_FreeCString(ctx, func_name_str); + + p = JS_VALUE_GET_OBJ(sf->cur_func); + if (js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b; + const char *atom_str; + int line_num1, col_num1; + + b = p->u.func.function_bytecode; + if (b->has_debug) { + line_num1 = find_line_num(ctx, b, + sf->cur_pc - b->byte_code_buf - 1, &col_num1); + atom_str = JS_AtomToCString(ctx, b->debug.filename); + dbuf_printf(&dbuf, " (%s", + atom_str ? atom_str : ""); + JS_FreeCString(ctx, atom_str); + if (line_num1 != 0) + dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1); + dbuf_putc(&dbuf, ')'); + } + } else { + dbuf_printf(&dbuf, " (native)"); + } + dbuf_putc(&dbuf, '\n'); + } + dbuf_putc(&dbuf, '\0'); + if (dbuf_error(&dbuf)) + str = JS_NULL; + else + str = JS_NewString(ctx, (char *)dbuf.buf); + dbuf_free(&dbuf); + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, str, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); +} + +/* Note: it is important that no exception is returned by this function */ +static BOOL is_backtrace_needed(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != JS_CLASS_ERROR) + return FALSE; + if (find_own_property1(p, JS_ATOM_stack)) + return FALSE; + return TRUE; +} + +JSValue JS_NewError(JSContext *ctx) +{ + JSValue error = JS_NewObjectClass(ctx, JS_CLASS_ERROR); + build_backtrace(ctx, error, NULL, 0, 0, 0); + return error; + +} + +static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, + const char *fmt, va_list ap, BOOL add_backtrace) +{ + char buf[256]; + JSValue obj, ret; + + vsnprintf(buf, sizeof(buf), fmt, ap); + obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num], + JS_CLASS_ERROR); + if (unlikely(JS_IsException(obj))) { + /* out of memory: throw JS_NULL to avoid recursing */ + obj = JS_NULL; + } else { + JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, + JS_NewString(ctx, buf), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (add_backtrace) { + build_backtrace(ctx, obj, NULL, 0, 0, 0); + } + } + ret = JS_Throw(ctx, obj); + return ret; +} + +static JSValue JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, + const char *fmt, va_list ap) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame *sf; + BOOL add_backtrace; + + /* the backtrace is added later if called from a bytecode function */ + sf = rt->current_stack_frame; + add_backtrace = !rt->in_out_of_memory && + (!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL)); + return JS_ThrowError2(ctx, error_num, fmt, ap, add_backtrace); +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_SYNTAX_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); + va_end(ap); + return val; +} + +static int __attribute__((format(printf, 3, 4))) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, const char *fmt, ...) +{ + va_list ap; + + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + va_start(ap, fmt); + JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); + va_end(ap); + return -1; + } else { + return FALSE; + } +} + +/* never use it directly */ +static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowTypeErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowTypeError(ctx, fmt, + JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); +} + +/* never use it directly */ +static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowSyntaxErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowSyntaxError(ctx, fmt, + JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); +} + +/* %s is replaced by 'atom'. The macro is used so that gcc can check + the format string. */ +#define JS_ThrowTypeErrorAtom(ctx, fmt, atom) __JS_ThrowTypeErrorAtom(ctx, atom, fmt, "") +#define JS_ThrowSyntaxErrorAtom(ctx, fmt, atom) __JS_ThrowSyntaxErrorAtom(ctx, atom, fmt, "") + +static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) +{ + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); + return -1; + } else { + return FALSE; + } +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowReferenceError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_REFERENCE_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowRangeError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_RANGE_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_INTERNAL_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue JS_ThrowOutOfMemory(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + if (!rt->in_out_of_memory) { + rt->in_out_of_memory = TRUE; + JS_ThrowInternalError(ctx, "out of memory"); + rt->in_out_of_memory = FALSE; + } + return JS_EXCEPTION; +} + +static JSValue JS_ThrowStackOverflow(JSContext *ctx) +{ + return JS_ThrowInternalError(ctx, "stack overflow"); +} + +static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not an object"); +} + +static JSValue JS_ThrowTypeErrorNotAConstructor(JSContext *ctx, + JSValueConst func_obj) +{ + const char *name; + if (!JS_IsFunction(ctx, func_obj)) + goto fail; + name = get_prop_string(ctx, func_obj, JS_ATOM_name); + if (!name) { + fail: + return JS_ThrowTypeError(ctx, "not a constructor"); + } + JS_ThrowTypeError(ctx, "%s is not a constructor", name); + JS_FreeCString(ctx, name); + return JS_EXCEPTION; +} + +static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not a symbol"); +} + +static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "'%s' is not defined", + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "%s is not initialized", + name == JS_ATOM_NULL ? "lexical variable" : + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx, + JSFunctionBytecode *b, + int idx, BOOL is_ref) +{ + JSAtom atom = JS_ATOM_NULL; + if (is_ref) { + atom = b->closure_var[idx].var_name; + } else { + /* not present if the function is stripped and contains no eval() */ + if (b->vardefs) + atom = b->vardefs[b->arg_count + idx].var_name; + } + return JS_ThrowReferenceErrorUninitialized(ctx, atom); +} + +static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) +{ + JSRuntime *rt = ctx->rt; + JSAtom name; + name = rt->class_array[class_id].class_name; + return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); +} + +static void JS_ThrowInterrupted(JSContext *ctx) +{ + JS_ThrowInternalError(ctx, "interrupted"); + JS_SetUncatchableException(ctx, TRUE); +} + +static no_inline __exception int __js_poll_interrupts(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (rt->interrupt_handler) { + if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { + JS_ThrowInterrupted(ctx); + return -1; + } + } + return 0; +} + +static inline __exception int js_poll_interrupts(JSContext *ctx) +{ + if (unlikely(--ctx->interrupt_counter <= 0)) { + return __js_poll_interrupts(ctx); + } else { + return 0; + } +} + +static void JS_SetImmutablePrototype(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(obj); + p->has_immutable_prototype = TRUE; +} + +/* Return -1 (exception) or TRUE/FALSE. 'throw_flag' = FALSE indicates + that it is called from Reflect.setPrototypeOf(). */ +static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, + JSValueConst proto_val, + BOOL throw_flag) +{ + JSObject *proto, *p, *p1; + JSShape *sh; + + if (throw_flag) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL || + JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED) + goto not_obj; + } else { + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + goto not_obj; + } + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) { + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + proto = NULL; + } else { + proto = JS_VALUE_GET_OBJ(proto_val); + } + + if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return TRUE; + + if (unlikely(p->is_exotic)) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + int ret; + if (em && em->set_prototype) { + ret = em->set_prototype(ctx, obj, proto_val); + if (ret == 0 && throw_flag) { + JS_ThrowTypeError(ctx, "proxy: bad prototype"); + return -1; + } else { + return ret; + } + } + } + + sh = p->shape; + if (sh->proto == proto) + return TRUE; + if (unlikely(p->has_immutable_prototype)) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "prototype is immutable"); + return -1; + } else { + return FALSE; + } + } + if (unlikely(!p->extensible)) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "object is not extensible"); + return -1; + } else { + return FALSE; + } + } + if (proto) { + /* check if there is a cycle */ + p1 = proto; + do { + if (p1 == p) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "circular prototype chain"); + return -1; + } else { + return FALSE; + } + } + /* Note: for Proxy objects, proto is NULL */ + p1 = p1->shape->proto; + } while (p1 != NULL); + JS_DupValue(ctx, proto_val); + } + + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + sh = p->shape; + if (sh->proto) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + sh->proto = proto; + if (proto) + proto->is_prototype = TRUE; + if (p->is_prototype) { + /* track modification of Array.prototype */ + if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]))) { + ctx->std_array_prototype = FALSE; + } + } + return TRUE; +} + +/* return -1 (exception) or TRUE/FALSE */ +int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val) +{ + return JS_SetPrototypeInternal(ctx, obj, proto_val, TRUE); +} + +/* Only works for primitive types, otherwise return JS_NULL. */ +static JSValueConst JS_GetPrototypePrimitive(JSContext *ctx, JSValueConst val) +{ + switch(JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + val = ctx->class_proto[JS_CLASS_BIG_INT]; + break; + case JS_TAG_INT: + case JS_TAG_FLOAT64: + val = ctx->class_proto[JS_CLASS_NUMBER]; + break; + case JS_TAG_BOOL: + val = ctx->class_proto[JS_CLASS_BOOLEAN]; + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + val = ctx->class_proto[JS_CLASS_STRING]; + break; + case JS_TAG_SYMBOL: + val = ctx->class_proto[JS_CLASS_SYMBOL]; + break; + case JS_TAG_OBJECT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + default: + val = JS_NULL; + break; + } + return val; +} + +/* Return an Object, JS_NULL or JS_EXCEPTION in case of exotic object. */ +JSValue JS_GetPrototype(JSContext *ctx, JSValueConst obj) +{ + JSValue val; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->is_exotic)) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_prototype) { + return em->get_prototype(ctx, obj); + } + } + p = p->shape->proto; + if (!p) + val = JS_NULL; + else + val = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + } else { + val = JS_DupValue(ctx, JS_GetPrototypePrimitive(ctx, obj)); + } + return val; +} + +static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj) +{ + JSValue obj1; + obj1 = JS_GetPrototype(ctx, obj); + JS_FreeValue(ctx, obj); + return obj1; +} + +/* return TRUE, FALSE or (-1) in case of exception */ +static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val, + JSValueConst obj) +{ + JSValue obj_proto; + JSObject *proto; + const JSObject *p, *proto1; + BOOL ret; + + if (!JS_IsFunction(ctx, obj)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_BOUND_FUNCTION) { + JSBoundFunction *s = p->u.bound_function; + return JS_IsInstanceOf(ctx, val, s->func_obj); + } + + /* Only explicitly boxed values are instances of constructors */ + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype); + if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) { + if (!JS_IsException(obj_proto)) + JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object"); + ret = -1; + goto done; + } + proto = JS_VALUE_GET_OBJ(obj_proto); + p = JS_VALUE_GET_OBJ(val); + for(;;) { + proto1 = p->shape->proto; + if (!proto1) { + /* slow case if exotic object in the prototype chain */ + if (unlikely(p->is_exotic && !p->fast_array)) { + JSValue obj1; + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsException(obj1)) { + ret = -1; + break; + } + if (JS_IsNull(obj1)) { + ret = FALSE; + break; + } + if (proto == JS_VALUE_GET_OBJ(obj1)) { + JS_FreeValue(ctx, obj1); + ret = TRUE; + break; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + ret = -1; + break; + } + } + } else { + ret = FALSE; + } + break; + } + p = proto1; + if (proto == p) { + ret = TRUE; + break; + } + } +done: + JS_FreeValue(ctx, obj_proto); + return ret; +} + +/* return TRUE, FALSE or (-1) in case of exception */ +int JS_IsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj) +{ + JSValue method; + + if (!JS_IsObject(obj)) + goto fail; + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance); + if (JS_IsException(method)) + return -1; + if (!JS_IsNull(method) && !JS_IsUndefined(method)) { + JSValue ret; + ret = JS_CallFree(ctx, method, obj, 1, &val); + return JS_ToBoolFree(ctx, ret); + } + + /* legacy case */ + if (!JS_IsFunction(ctx, obj)) { + fail: + JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); + return -1; + } + return JS_OrdinaryIsInstanceOf(ctx, val, obj); +} + +/* return the value associated to the autoinit property or an exception */ +typedef JSValue JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); + +static JSAutoInitFunc *js_autoinit_func_table[] = { + js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */ + js_module_ns_autoinit, /* JS_AUTOINIT_ID_MODULE_NS */ + JS_InstantiateFunctionListItem2, /* JS_AUTOINIT_ID_PROP */ +}; + +/* warning: 'prs' is reallocated after it */ +static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, + JSProperty *pr, JSShapeProperty *prs) +{ + JSValue val; + JSContext *realm; + JSAutoInitFunc *func; + JSAutoInitIDEnum id; + + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + + realm = js_autoinit_get_realm(pr); + id = js_autoinit_get_id(pr); + func = js_autoinit_func_table[id]; + /* 'func' shall not modify the object properties 'pr' */ + val = func(realm, p, prop, pr->u.init.opaque); + js_autoinit_free(ctx->rt, pr); + prs->flags &= ~JS_PROP_TMASK; + pr->u.value = JS_UNDEFINED; + if (JS_IsException(val)) + return -1; + if (id == JS_AUTOINIT_ID_MODULE_NS && + JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + /* WARNING: a varref is returned as a string ! */ + prs->flags |= JS_PROP_VARREF; + pr->u.var_ref = JS_VALUE_GET_PTR(val); + pr->u.var_ref->header.ref_count++; + } else if (p->class_id == JS_CLASS_GLOBAL_OBJECT) { + JSVarRef *var_ref; + /* in the global object we use references */ + var_ref = js_create_var_ref(ctx, FALSE); + if (!var_ref) + return -1; + prs->flags |= JS_PROP_VARREF; + pr->u.var_ref = var_ref; + var_ref->value = val; + var_ref->is_const = !(prs->flags & JS_PROP_WRITABLE); + } else { + pr->u.value = val; + } + return 0; +} + +JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, + JSAtom prop, JSValueConst this_obj, + BOOL throw_ref_error) +{ + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + uint32_t tag; + + tag = JS_VALUE_GET_TAG(obj); + if (unlikely(tag != JS_TAG_OBJECT)) { + switch(tag) { + case JS_TAG_NULL: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", prop); + case JS_TAG_UNDEFINED: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", prop); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_STRING: + { + JSString *p1 = JS_VALUE_GET_STRING(obj); + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx; + idx = __JS_AtomToUInt32(prop); + if (idx < p1->len) { + return js_new_string_char(ctx, string_get(p1, idx)); + } + } else if (prop == JS_ATOM_length) { + return JS_NewInt32(ctx, p1->len); + } + } + break; + case JS_TAG_STRING_ROPE: + { + JSStringRope *p1 = JS_VALUE_GET_STRING_ROPE(obj); + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx; + idx = __JS_AtomToUInt32(prop); + if (idx < p1->len) { + return js_new_string_char(ctx, string_rope_get(obj, idx)); + } + } else if (prop == JS_ATOM_length) { + return JS_NewInt32(ctx, p1->len); + } + } + break; + default: + break; + } + /* cannot raise an exception */ + p = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); + if (!p) + return JS_UNDEFINED; + } else { + p = JS_VALUE_GET_OBJ(obj); + } + + for(;;) { + prs = find_own_property(&pr, p, prop); + if (prs) { + /* found */ + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (unlikely(!pr->u.getset.getter)) { + return JS_UNDEFINED; + } else { + JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter); + /* Note: the field could be removed in the getter */ + func = JS_DupValue(ctx, func); + return JS_CallFree(ctx, func, this_obj, 0, NULL); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) + return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return JS_DupValue(ctx, val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return JS_EXCEPTION; + continue; + } + } else { + return JS_DupValue(ctx, pr->u.value); + } + } + if (unlikely(p->is_exotic)) { + /* exotic behaviors */ + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + /* we avoid duplicating the code */ + return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + return JS_UNDEFINED; + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + int ret; + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return JS_EXCEPTION; + return JS_UNDEFINED; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->get_property) { + JSValue obj1, retval; + /* XXX: should pass throw_ref_error */ + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + retval = em->get_property(ctx, obj1, prop, this_obj); + JS_FreeValue(ctx, obj1); + return retval; + } + if (em->get_own_property) { + JSPropertyDescriptor desc; + int ret; + JSValue obj1; + + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->get_own_property(ctx, &desc, obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) + return JS_EXCEPTION; + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.setter); + return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL); + } else { + return desc.value; + } + } + } + } + } + } + p = p->shape->proto; + if (!p) + break; + } + if (unlikely(throw_ref_error)) { + return JS_ThrowReferenceErrorNotDefined(ctx, prop); + } else { + return JS_UNDEFINED; + } +} + +static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) +{ + return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist", + atom); +} + +/* Private fields can be added even on non extensible objects or + Proxies */ +static int JS_DefinePrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, (JSValue)name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", + prop); + goto fail; + } + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (unlikely(!pr)) { + fail: + JS_FreeValue(ctx, val); + return -1; + } + pr->u.value = val; + return 0; +} + +static JSValue JS_GetPrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return JS_ThrowTypeErrorNotAnObject(ctx); + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) + return JS_ThrowTypeErrorNotASymbol(ctx); + prop = js_symbol_to_atom(ctx, (JSValue)name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + return JS_EXCEPTION; + } + return JS_DupValue(ctx, pr->u.value); +} + +static int JS_SetPrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, (JSValue)name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + fail: + JS_FreeValue(ctx, val); + return -1; + } + set_value(ctx, &pr->u.value, val); + return 0; +} + +/* add a private brand field to 'home_obj' if not already present and + if obj is != null add a private brand to it */ +static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + JSValue brand; + JSAtom brand_atom; + + if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(home_obj); + prs = find_own_property(&pr, p, JS_ATOM_Private_brand); + if (!prs) { + /* if the brand is not present, add it */ + brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(brand)) + return -1; + pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); + if (!pr) { + JS_FreeValue(ctx, brand); + return -1; + } + pr->u.value = JS_DupValue(ctx, brand); + } else { + brand = JS_DupValue(ctx, pr->u.value); + } + brand_atom = js_symbol_to_atom(ctx, brand); + + if (JS_IsObject(obj)) { + p1 = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p1, brand_atom); + if (unlikely(prs)) { + JS_FreeAtom(ctx, brand_atom); + JS_ThrowTypeError(ctx, "private method is already present"); + return -1; + } + pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); + JS_FreeAtom(ctx, brand_atom); + if (!pr) + return -1; + pr->u.value = JS_UNDEFINED; + } else { + JS_FreeAtom(ctx, brand_atom); + } + return 0; +} + +/* return a boolean telling if the brand of the home object of 'func' + is present on 'obj' or -1 in case of exception */ +static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func) +{ + JSObject *p, *p1, *home_obj; + JSShapeProperty *prs; + JSProperty *pr; + JSValueConst brand; + + /* get the home object of 'func' */ + if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) + goto not_obj; + p1 = JS_VALUE_GET_OBJ(func); + if (!js_class_has_bytecode(p1->class_id)) + goto not_obj; + home_obj = p1->u.func.home_object; + if (!home_obj) + goto not_obj; + prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand); + if (!prs) { + JS_ThrowTypeError(ctx, "expecting private field"); + return -1; + } + brand = pr->u.value; + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL)) + goto not_obj; + + /* get the brand array of 'obj' */ + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, (JSValue)brand)); + return (prs != NULL); +} + +static uint32_t js_string_obj_get_length(JSContext *ctx, + JSValueConst obj) +{ + JSObject *p; + uint32_t len = 0; + + /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { + JSString *p1 = JS_VALUE_GET_STRING(p->u.object_data); + len = p1->len; + } + return len; +} + +static int num_keys_cmp(const void *p1, const void *p2, void *opaque) +{ + JSContext *ctx = opaque; + JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom; + JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom; + uint32_t v1, v2; + BOOL atom1_is_integer, atom2_is_integer; + + atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1); + atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2); + assert(atom1_is_integer && atom2_is_integer); + if (v1 < v2) + return -1; + else if (v1 == v2) + return 0; + else + return 1; +} + +void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) +{ + uint32_t i; + if (tab) { + for(i = 0; i < len; i++) + JS_FreeAtom(ctx, tab[i].atom); + js_free(ctx, tab); + } +} + +/* return < 0 in case if exception, 0 if OK. ptab and its atoms must + be freed by the user. */ +static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, + JSPropertyEnum **ptab, + uint32_t *plen, + JSObject *p, int flags) +{ + int i, j; + JSShape *sh; + JSShapeProperty *prs; + JSPropertyEnum *tab_atom, *tab_exotic; + JSAtom atom; + uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count; + uint32_t num_index, str_index, sym_index, exotic_count, exotic_keys_count; + BOOL is_enumerable, num_sorted; + uint32_t num_key; + JSAtomKindEnum kind; + + /* clear pointer for consistency in case of failure */ + *ptab = NULL; + *plen = 0; + + /* compute the number of returned properties */ + num_keys_count = 0; + str_keys_count = 0; + sym_keys_count = 0; + exotic_keys_count = 0; + exotic_count = 0; + tab_exotic = NULL; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + /* need to raise an exception in case of the module + name space (implicit GetOwnProperty) */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) && + (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) { + JSVarRef *var_ref = p->prop[i].u.var_ref; + if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + num_keys_count++; + } else if (kind == JS_ATOM_KIND_STRING) { + str_keys_count++; + } else { + sym_keys_count++; + } + } + } + } + + if (p->is_exotic) { + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += p->u.array.count; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property_names) { + if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count, + JS_MKPTR(JS_TAG_OBJECT, p))) + return -1; + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + kind = JS_AtomGetKind(ctx, atom); + if (((flags >> kind) & 1) != 0) { + is_enumerable = FALSE; + if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) { + JSPropertyDescriptor desc; + int res; + /* set the "is_enumerable" field if necessary */ + res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); + if (res < 0) { + JS_FreePropertyEnum(ctx, tab_exotic, exotic_count); + return -1; + } + if (res) { + is_enumerable = + ((desc.flags & JS_PROP_ENUMERABLE) != 0); + js_free_desc(ctx, &desc); + } + tab_exotic[i].is_enumerable = is_enumerable; + } + if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) { + exotic_keys_count++; + } + } + } + } + } + } + + /* fill them */ + + atom_count = num_keys_count + str_keys_count; + if (atom_count < str_keys_count) + goto add_overflow; + atom_count += sym_keys_count; + if (atom_count < sym_keys_count) + goto add_overflow; + atom_count += exotic_keys_count; + if (atom_count < exotic_keys_count || atom_count > INT32_MAX) { + add_overflow: + JS_ThrowOutOfMemory(ctx); + JS_FreePropertyEnum(ctx, tab_exotic, exotic_count); + return -1; + } + /* XXX: need generic way to test for js_malloc(ctx, a * b) overflow */ + + /* avoid allocating 0 bytes */ + tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); + if (!tab_atom) { + JS_FreePropertyEnum(ctx, tab_exotic, exotic_count); + return -1; + } + + num_index = 0; + str_index = num_keys_count; + sym_index = str_index + str_keys_count; + + num_sorted = TRUE; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + j = num_index++; + num_sorted = FALSE; + } else if (kind == JS_ATOM_KIND_STRING) { + j = str_index++; + } else { + j = sym_index++; + } + tab_atom[j].atom = JS_DupAtom(ctx, atom); + tab_atom[j].is_enumerable = is_enumerable; + } + } + } + + if (p->is_exotic) { + int len; + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + len = p->u.array.count; + goto add_array_keys; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + len = js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + add_array_keys: + for(i = 0; i < len; i++) { + tab_atom[num_index].atom = __JS_AtomFromUInt32(i); + if (tab_atom[num_index].atom == JS_ATOM_NULL) { + JS_FreePropertyEnum(ctx, tab_atom, num_index); + return -1; + } + tab_atom[num_index].is_enumerable = TRUE; + num_index++; + } + } + } else { + /* Note: exotic keys are not reordered and comes after the object own properties. */ + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + is_enumerable = tab_exotic[i].is_enumerable; + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + tab_atom[sym_index].atom = atom; + tab_atom[sym_index].is_enumerable = is_enumerable; + sym_index++; + } else { + JS_FreeAtom(ctx, atom); + } + } + js_free(ctx, tab_exotic); + } + } + + assert(num_index == num_keys_count); + assert(str_index == num_keys_count + str_keys_count); + assert(sym_index == atom_count); + + if (num_keys_count != 0 && !num_sorted) { + rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp, + ctx); + } + *ptab = tab_atom; + *plen = atom_count; + return 0; +} + +int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, JSValueConst obj, int flags) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, + JS_VALUE_GET_OBJ(obj), flags); +} + +/* Return -1 if exception, + FALSE if the property does not exist, TRUE if it exists. If TRUE is + returned, the property descriptor 'desc' is filled present. */ +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop) +{ + JSShapeProperty *prs; + JSProperty *pr; + +retry: + prs = find_own_property(&pr, p, prop); + if (prs) { + if (desc) { + desc->flags = prs->flags & JS_PROP_C_W_E; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_UNDEFINED; + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + desc->flags |= JS_PROP_GETSET; + if (pr->u.getset.getter) + desc->getter = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + desc->setter = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + desc->value = JS_DupValue(ctx, val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto retry; + } + } else { + desc->value = JS_DupValue(ctx, pr->u.value); + } + } else { + /* for consistency, send the exception even if desc is NULL */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) { + if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* nothing to do: delay instantiation until actual value and/or attributes are read */ + } + } + return TRUE; + } + if (p->is_exotic) { + if (p->fast_array) { + /* specific case for fast arrays */ + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx; + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + if (desc) { + desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | + JS_PROP_CONFIGURABLE; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } + return TRUE; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property) { + return em->get_own_property(ctx, desc, + JS_MKPTR(JS_TAG_OBJECT, p), prop); + } + } + } + return FALSE; +} + +int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop); +} + +/* return -1 if exception (exotic object only) or TRUE/FALSE */ +int JS_IsExtensible(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->is_exotic)) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->is_extensible) { + return em->is_extensible(ctx, obj); + } + } + return p->extensible; +} + +/* return -1 if exception (exotic object only) or TRUE/FALSE */ +int JS_PreventExtensions(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->is_exotic)) { + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + JSTypedArray *ta; + JSArrayBuffer *abuf; + /* resizable type arrays return FALSE */ + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (ta->track_rab || + (array_buffer_is_resizable(abuf) && !abuf->shared)) + return FALSE; + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->prevent_extensions) { + return em->prevent_extensions(ctx, obj); + } + } + } + p->extensible = FALSE; + return TRUE; +} + +/* return -1 if exception otherwise TRUE or FALSE */ +int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop) +{ + JSObject *p; + int ret; + JSValue obj1; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + for(;;) { + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->has_property) { + /* has_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->has_property(ctx, obj1, prop); + JS_FreeValue(ctx, obj1); + return ret; + } + } + /* JS_GetOwnPropertyInternal can free the prototype */ + JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret != 0) + return ret; + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return FALSE; + } + } + p = p->shape->proto; + if (!p) + break; + } + return FALSE; +} + +/* val must be a symbol */ +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val) +{ + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + return js_get_atom_index(ctx->rt, p); +} + +/* return JS_ATOM_NULL in case of exception */ +JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val) +{ + JSAtom atom; + uint32_t tag; + tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_INT && + (uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) { + /* fast path for integer values */ + atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val)); + } else if (tag == JS_TAG_SYMBOL) { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p)); + } else { + JSValue str; + str = JS_ToPropertyKey(ctx, val); + if (JS_IsException(str)) + return JS_ATOM_NULL; + if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) { + atom = js_symbol_to_atom(ctx, str); + } else { + atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str)); + } + } + return atom; +} + +static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop) +{ + JSAtom atom; + JSValue ret; + + if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { + JSObject *p; + uint32_t idx; + /* fast path for array access */ + p = JS_VALUE_GET_OBJ(this_obj); + idx = JS_VALUE_GET_INT(prop); + switch(p->class_id) { + case JS_CLASS_ARRAY: + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_DupValue(ctx, p->u.array.u.values[idx]); + case JS_CLASS_MAPPED_ARGUMENTS: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_DupValue(ctx, *p->u.array.u.var_refs[idx]->pvalue); + case JS_CLASS_INT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.int8_ptr[idx]); + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.uint8_ptr[idx]); + case JS_CLASS_INT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.int16_ptr[idx]); + case JS_CLASS_UINT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.uint16_ptr[idx]); + case JS_CLASS_INT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.int32_ptr[idx]); + case JS_CLASS_UINT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewUint32(ctx, p->u.array.u.uint32_ptr[idx]); + case JS_CLASS_BIG_INT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); + case JS_CLASS_BIG_UINT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); + case JS_CLASS_FLOAT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return __JS_NewFloat64(ctx, fromfp16(p->u.array.u.fp16_ptr[idx])); + case JS_CLASS_FLOAT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return __JS_NewFloat64(ctx, p->u.array.u.float_ptr[idx]); + case JS_CLASS_FLOAT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return __JS_NewFloat64(ctx, p->u.array.u.double_ptr[idx]); + default: + goto slow_path; + } + } else { + slow_path: + /* ToObject() must be done before ToPropertyKey() */ + if (JS_IsNull(this_obj) || JS_IsUndefined(this_obj)) { + JS_FreeValue(ctx, prop); + return JS_ThrowTypeError(ctx, "cannot read property of %s", JS_IsNull(this_obj) ? "null" : "undefined"); + } + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; + } +} + +JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx) +{ + return JS_GetPropertyValue(ctx, this_obj, JS_NewUint32(ctx, idx)); +} + +/* Check if an object has a generalized numeric property. Return value: + -1 for exception, + TRUE if property exists, stored into *pval, + FALSE if proprty does not exist. + */ +static int JS_TryGetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, JSValue *pval) +{ + JSValue val = JS_UNDEFINED; + JSAtom prop; + int present; + + if (likely((uint64_t)idx <= JS_ATOM_MAX_INT)) { + /* fast path */ + present = JS_HasProperty(ctx, obj, __JS_AtomFromUInt32(idx)); + if (present > 0) { + val = JS_GetPropertyValue(ctx, obj, JS_NewInt32(ctx, idx)); + if (unlikely(JS_IsException(val))) + present = -1; + } + } else { + prop = JS_NewAtomInt64(ctx, idx); + present = -1; + if (likely(prop != JS_ATOM_NULL)) { + present = JS_HasProperty(ctx, obj, prop); + if (present > 0) { + val = JS_GetProperty(ctx, obj, prop); + if (unlikely(JS_IsException(val))) + present = -1; + } + JS_FreeAtom(ctx, prop); + } + } + *pval = val; + return present; +} + +static JSValue JS_GetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx) +{ + JSAtom prop; + JSValue val; + + if ((uint64_t)idx <= INT32_MAX) { + /* fast path for fast arrays */ + return JS_GetPropertyValue(ctx, obj, JS_NewInt32(ctx, idx)); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return JS_EXCEPTION; + + val = JS_GetProperty(ctx, obj, prop); + JS_FreeAtom(ctx, prop); + return val; +} + +JSValue JS_GetPropertyStr(JSContext *ctx, JSValueConst this_obj, + const char *prop) +{ + JSAtom atom; + JSValue ret; + atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* Note: the property value is not initialized. Return NULL if memory + error. */ +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags) +{ + JSShape *sh, *new_sh; + + if (unlikely(p->is_prototype)) { + /* track addition of small integer properties to Array.prototype and Object.prototype */ + if (unlikely((p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) || + p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) && + __JS_AtomIsTaggedInt(prop))) { + ctx->std_array_prototype = FALSE; + } + } + sh = p->shape; + if (sh->is_hashed) { + /* try to find an existing shape */ + new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags); + if (new_sh) { + /* matching shape found: use it */ + /* the property array may need to be resized */ + if (new_sh->prop_size != sh->prop_size) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) * + new_sh->prop_size); + if (!new_prop) + return NULL; + p->prop = new_prop; + } + p->shape = js_dup_shape(new_sh); + js_free_shape(ctx->rt, sh); + return &p->prop[new_sh->prop_count - 1]; + } else if (sh->header.ref_count != 1) { + /* if the shape is shared, clone it */ + new_sh = js_clone_shape(ctx, sh); + if (!new_sh) + return NULL; + /* hash the cloned shape */ + new_sh->is_hashed = TRUE; + js_shape_hash_link(ctx->rt, new_sh); + js_free_shape(ctx->rt, p->shape); + p->shape = new_sh; + } + } + assert(p->shape->header.ref_count == 1); + if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) + return NULL; + return &p->prop[p->shape->prop_count - 1]; +} + +/* can be called on JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS or + JS_CLASS_MAPPED_ARGUMENTS objects. return < 0 if memory alloc + error. */ +static no_inline __exception int convert_fast_array_to_array(JSContext *ctx, + JSObject *p) +{ + JSProperty *pr; + JSShape *sh; + uint32_t i, len, new_count; + + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + len = p->u.array.count; + /* resize the properties once to simplify the error handling */ + sh = p->shape; + new_count = sh->prop_count + len; + if (new_count > sh->prop_size) { + if (resize_properties(ctx, &p->shape, p, new_count)) + return -1; + } + + if (p->class_id == JS_CLASS_MAPPED_ARGUMENTS) { + JSVarRef **tab = p->u.array.u.var_refs; + for(i = 0; i < len; i++) { + /* add_property cannot fail here but + __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF); + pr->u.var_ref = *tab++; + } + } else { + JSValue *tab = p->u.array.u.values; + for(i = 0; i < len; i++) { + /* add_property cannot fail here but + __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E); + pr->u.value = *tab++; + } + } + js_free(ctx, p->u.array.u.values); + p->u.array.count = 0; + p->u.array.u.values = NULL; /* fail safe */ + p->u.array.u1.size = 0; + p->fast_array = 0; + + /* track modification of Array.prototype */ + if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]))) { + ctx->std_array_prototype = FALSE; + } + return 0; +} + +static int remove_global_object_property(JSContext *ctx, JSObject *p, + JSShapeProperty *prs, JSProperty *pr) +{ + JSVarRef *var_ref; + JSObject *p1; + JSProperty *pr1; + + var_ref = pr->u.var_ref; + if (var_ref->header.ref_count == 1) + return 0; + p1 = JS_VALUE_GET_OBJ(p->u.global_object.uninitialized_vars); + pr1 = add_property(ctx, p1, prs->atom, JS_PROP_C_W_E | JS_PROP_VARREF); + if (!pr1) + return -1; + pr1->u.var_ref = var_ref; + var_ref->header.ref_count++; + JS_FreeValue(ctx, var_ref->value); + var_ref->is_lexical = FALSE; + var_ref->is_const = FALSE; + var_ref->value = JS_UNINITIALIZED; + return 0; +} + +static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *lpr, *prop; + JSProperty *pr1; + uint32_t lpr_idx; + intptr_t h, h1; + + redo: + sh = p->shape; + h1 = atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h1 - 1]; + prop = get_shape_prop(sh); + lpr = NULL; + lpr_idx = 0; /* prevent warning */ + while (h != 0) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + /* found ! */ + if (!(pr->flags & JS_PROP_CONFIGURABLE)) + return FALSE; + /* realloc the shape if needed */ + if (lpr) + lpr_idx = lpr - get_shape_prop(sh); + if (js_shape_prepare_update(ctx, p, &pr)) + return -1; + sh = p->shape; + /* remove property */ + if (lpr) { + lpr = get_shape_prop(sh) + lpr_idx; + lpr->hash_next = pr->hash_next; + } else { + prop_hash_end(sh)[-h1 - 1] = pr->hash_next; + } + sh->deleted_prop_count++; + /* free the entry */ + pr1 = &p->prop[h - 1]; + if (unlikely(p->class_id == JS_CLASS_GLOBAL_OBJECT)) { + if ((pr->flags & JS_PROP_TMASK) == JS_PROP_VARREF) + if (remove_global_object_property(ctx, p, pr, pr1)) + return -1; + } + free_property(ctx->rt, pr1, pr->flags); + JS_FreeAtom(ctx, pr->atom); + /* put default values */ + pr->flags = 0; + pr->atom = JS_ATOM_NULL; + pr1->u.value = JS_UNDEFINED; + + /* compact the properties if too many deleted properties */ + if (sh->deleted_prop_count >= 8 && + sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { + compact_properties(ctx, p); + } + return TRUE; + } + lpr = pr; + h = pr->hash_next; + } + + if (p->is_exotic) { + if (p->fast_array) { + uint32_t idx; + if (JS_AtomIsArrayIndex(ctx, &idx, atom) && + idx < p->u.array.count) { + if (p->class_id == JS_CLASS_ARRAY || + p->class_id == JS_CLASS_ARGUMENTS || + p->class_id == JS_CLASS_MAPPED_ARGUMENTS) { + /* Special case deleting the last element of a fast Array */ + if (idx == p->u.array.count - 1) { + if (p->class_id == JS_CLASS_MAPPED_ARGUMENTS) { + free_var_ref(ctx->rt, p->u.array.u.var_refs[idx]); + } else { + JS_FreeValue(ctx, p->u.array.u.values[idx]); + } + p->u.array.count = idx; + return TRUE; + } + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto redo; + } else { + return FALSE; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->delete_property) { + return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom); + } + } + } + /* not found */ + return TRUE; +} + +static int call_setter(JSContext *ctx, JSObject *setter, + JSValueConst this_obj, JSValue val, int flags) +{ + JSValue ret, func; + if (likely(setter)) { + func = JS_MKPTR(JS_TAG_OBJECT, setter); + /* Note: the field could be removed in the setter */ + func = JS_DupValue(ctx, func); + ret = JS_CallFree(ctx, func, this_obj, 1, (JSValueConst *)&val); + JS_FreeValue(ctx, val); + if (JS_IsException(ret)) + return -1; + JS_FreeValue(ctx, ret); + return TRUE; + } else { + JS_FreeValue(ctx, val); + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "no setter for property"); + return -1; + } + return FALSE; + } +} + +/* set the array length and remove the array elements if necessary. */ +static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, + int flags) +{ + uint32_t len, idx, cur_len; + int i, ret; + + /* Note: this call can reallocate the properties of 'p' */ + ret = JS_ToArrayLengthFree(ctx, &len, val, FALSE); + if (ret) + return -1; + /* JS_ToArrayLengthFree() must be done before the read-only test */ + if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + + if (likely(p->fast_array)) { + uint32_t old_len = p->u.array.count; + if (len < old_len) { + for(i = len; i < old_len; i++) { + JS_FreeValue(ctx, p->u.array.u.values[i]); + } + p->u.array.count = len; + } + p->prop[0].u.value = JS_NewUint32(ctx, len); + } else { + /* Note: length is always a uint32 because the object is an + array */ + JS_ToUint32(ctx, &cur_len, p->prop[0].u.value); + if (len < cur_len) { + uint32_t d; + JSShape *sh; + JSShapeProperty *pr; + + d = cur_len - len; + sh = p->shape; + if (d <= sh->prop_count) { + JSAtom atom; + + /* faster to iterate */ + while (cur_len > len) { + atom = JS_NewAtomUInt32(ctx, cur_len - 1); + ret = delete_property(ctx, p, atom); + JS_FreeAtom(ctx, atom); + if (unlikely(!ret)) { + /* unlikely case: property is not + configurable */ + break; + } + cur_len--; + } + } else { + /* faster to iterate thru all the properties. Need two + passes in case one of the property is not + configurable */ + cur_len = len; + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len && + !(pr->flags & JS_PROP_CONFIGURABLE)) { + cur_len = idx + 1; + } + } + } + + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len) { + /* remove the property */ + delete_property(ctx, p, pr->atom); + /* WARNING: the shape may have been modified */ + sh = p->shape; + pr = get_shape_prop(sh) + i; + } + } + } + } + } else { + cur_len = len; + } + set_value(ctx, &p->prop[0].u.value, JS_NewUint32(ctx, cur_len)); + if (unlikely(cur_len > len)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable"); + } + } + return TRUE; +} + +/* return -1 if exception */ +static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) +{ + uint32_t new_size; + size_t slack; + JSValue *new_array_prop; + /* XXX: potential arithmetic overflow */ + new_size = max_int(new_len, p->u.array.u1.size * 3 / 2); + new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack); + if (!new_array_prop) + return -1; + new_size += slack / sizeof(*new_array_prop); + p->u.array.u.values = new_array_prop; + p->u.array.u1.size = new_size; + return 0; +} + +/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = + TRUE and p->extensible = TRUE */ +static inline int add_fast_array_element(JSContext *ctx, JSObject *p, + JSValue val, int flags) +{ + uint32_t new_len, array_len; + /* extend the array by one */ + /* XXX: convert to slow array if new_len > 2^31-1 elements */ + new_len = p->u.array.count + 1; + /* update the length if necessary. We assume that if the length is + not an integer, then if it >= 2^31. */ + if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) { + array_len = JS_VALUE_GET_INT(p->prop[0].u.value); + if (new_len > array_len) { + if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + } + p->prop[0].u.value = JS_NewInt32(ctx, new_len); + } + } + if (unlikely(new_len > p->u.array.u1.size)) { + if (expand_fast_array(ctx, p, new_len)) { + JS_FreeValue(ctx, val); + return -1; + } + } + p->u.array.u.values[new_len - 1] = val; + p->u.array.count = new_len; + return TRUE; +} + +/* Allocate a new fast array. Its 'length' property is set to zero. It + maximum size is 2^31-1 elements. For convenience, 'len' is a 64 bit + integer. WARNING: the content of the array is not initialized. */ +static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len) +{ + JSValue arr; + JSObject *p; + + if (len > INT32_MAX) + return JS_ThrowRangeError(ctx, "invalid array length"); + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + return arr; + if (len > 0) { + p = JS_VALUE_GET_OBJ(arr); + if (expand_fast_array(ctx, p, len) < 0) { + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; + } + p->u.array.count = len; + } + return arr; +} + +static JSValue js_create_array(JSContext *ctx, int len, JSValueConst *tab) +{ + JSValue obj; + JSObject *p; + int i; + + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + if (len > 0) { + p = JS_VALUE_GET_OBJ(obj); + if (expand_fast_array(ctx, p, len) < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + p->u.array.count = len; + for(i = 0; i < len; i++) + p->u.array.u.values[i] = JS_DupValue(ctx, tab[i]); + /* update the 'length' field */ + set_value(ctx, &p->prop[0].u.value, JS_NewInt32(ctx, len)); + } + return obj; +} + +static JSValue js_create_array_free(JSContext *ctx, int len, JSValue *tab) +{ + JSValue obj; + JSObject *p; + int i; + + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + goto fail; + if (len > 0) { + p = JS_VALUE_GET_OBJ(obj); + if (expand_fast_array(ctx, p, len) < 0) { + JS_FreeValue(ctx, obj); + fail: + for(i = 0; i < len; i++) + JS_FreeValue(ctx, tab[i]); + return JS_EXCEPTION; + } + p->u.array.count = len; + for(i = 0; i < len; i++) + p->u.array.u.values[i] = tab[i]; + /* update the 'length' field */ + set_value(ctx, &p->prop[0].u.value, JS_NewInt32(ctx, len)); + } + return obj; +} + +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) +{ + JS_FreeValue(ctx, desc->getter); + JS_FreeValue(ctx, desc->setter); + JS_FreeValue(ctx, desc->value); +} + +/* return -1 in case of exception or TRUE or FALSE. Warning: 'val' is + freed by the function. 'flags' is a bitmask of JS_PROP_THROW and + JS_PROP_THROW_STRICT. 'this_obj' is the receiver. If obj != + this_obj, then obj must be an object (Reflect.set case). */ +int JS_SetPropertyInternal(JSContext *ctx, JSValueConst obj, + JSAtom prop, JSValue val, JSValueConst this_obj, int flags) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + uint32_t tag; + JSPropertyDescriptor desc; + int ret; +#if 0 + printf("JS_SetPropertyInternal: "); print_atom(ctx, prop); printf("\n"); +#endif + tag = JS_VALUE_GET_TAG(this_obj); + if (unlikely(tag != JS_TAG_OBJECT)) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = NULL; + p1 = JS_VALUE_GET_OBJ(obj); + goto prototype_lookup; + } else { + switch(tag) { + case JS_TAG_NULL: + JS_FreeValue(ctx, val); + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); + return -1; + case JS_TAG_UNDEFINED: + JS_FreeValue(ctx, val); + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); + return -1; + default: + /* even on a primitive type we can have setters on the prototype */ + p = NULL; + p1 = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); + goto prototype_lookup; + } + } + } else { + p = JS_VALUE_GET_OBJ(this_obj); + p1 = JS_VALUE_GET_OBJ(obj); + if (unlikely(p != p1)) + goto retry2; + } + + /* fast path if obj == this_obj */ + retry: + prs = find_own_property(&pr, p1, prop); + if (prs) { + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast case */ + set_value(ctx, &pr->u.value, val); + return TRUE; + } else if (prs->flags & JS_PROP_LENGTH) { + assert(p->class_id == JS_CLASS_ARRAY); + assert(prop == JS_ATOM_length); + return set_array_length(ctx, p, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* XXX: already use var_ref->is_const. Cannot simplify use the + writable flag for JS_CLASS_MODULE_NS. */ + if (p->class_id == JS_CLASS_MODULE_NS || pr->u.var_ref->is_const) + goto read_only_prop; + set_value(ctx, pr->u.var_ref->pvalue, val); + return TRUE; + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) { + JS_FreeValue(ctx, val); + return -1; + } + goto retry; + } else { + goto read_only_prop; + } + } + + for(;;) { + if (p1->is_exotic) { + if (p1->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p1->u.array.count) { + if (unlikely(p == p1)) + return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), val, flags); + else + break; + } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && + p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { + goto typed_array_oob; + } + } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && + p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) { + JS_FreeValue(ctx, val); + return -1; + } + typed_array_oob: + if (p == p1) { + /* must convert the argument even if out of bound access */ + if (p1->class_id == JS_CLASS_BIG_INT64_ARRAY || + p1->class_id == JS_CLASS_BIG_UINT64_ARRAY) { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + return -1; + } else { + val = JS_ToNumberFree(ctx, val); + JS_FreeValue(ctx, val); + if (JS_IsException(val)) + return -1; + } + } else { + JS_FreeValue(ctx, val); + } + return TRUE; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; + if (em) { + JSValue obj1; + if (em->set_property) { + /* set_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->set_property(ctx, obj1, prop, + val, this_obj, flags); + JS_FreeValue(ctx, obj1); + JS_FreeValue(ctx, val); + return ret; + } + if (em->get_own_property) { + /* get_own_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->get_own_property(ctx, &desc, + obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) { + JS_FreeValue(ctx, val); + return ret; + } + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JSObject *setter; + if (JS_IsUndefined(desc.setter)) + setter = NULL; + else + setter = JS_VALUE_GET_OBJ(desc.setter); + ret = call_setter(ctx, setter, this_obj, val, flags); + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + return ret; + } else { + JS_FreeValue(ctx, desc.value); + if (!(desc.flags & JS_PROP_WRITABLE)) + goto read_only_prop; + if (likely(p == p1)) { + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + JS_FreeValue(ctx, val); + return ret; + } else { + break; + } + } + } + } + } + } + } + p1 = p1->shape->proto; + prototype_lookup: + if (!p1) + break; + + retry2: + prs = find_own_property(&pr, p1, prop); + if (prs) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p1, prop, pr, prs)) + return -1; + goto retry2; + } else if (!(prs->flags & JS_PROP_WRITABLE)) { + goto read_only_prop; + } else { + break; + } + } + } + + if (unlikely(!p)) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object"); + } + + if (unlikely(!p->extensible)) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + } + + if (likely(p == JS_VALUE_GET_OBJ(obj))) { + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY && p->fast_array && + __JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + /* fast case */ + return add_fast_array_element(ctx, p, val, flags); + } else { + goto generic_create_prop; + } + } else { + goto generic_create_prop; + } + } else { + if (unlikely(p->class_id == JS_CLASS_GLOBAL_OBJECT)) + goto generic_create_prop; + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (unlikely(!pr)) { + JS_FreeValue(ctx, val); + return -1; + } + pr->u.value = val; + return TRUE; + } + } else { + /* generic case: modify the property in this_obj if it already exists */ + ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); + if (ret < 0) { + JS_FreeValue(ctx, val); + return ret; + } + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); + } else { + JS_FreeValue(ctx, desc.value); + if (!(desc.flags & JS_PROP_WRITABLE) || + p->class_id == JS_CLASS_MODULE_NS) { + read_only_prop: + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, flags, prop); + } + } + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + JS_FreeValue(ctx, val); + return ret; + } else { + generic_create_prop: + ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | + JS_PROP_HAS_VALUE | + JS_PROP_HAS_ENUMERABLE | + JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_CONFIGURABLE | + JS_PROP_C_W_E); + JS_FreeValue(ctx, val); + return ret; + } + } +} + +/* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */ +static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags) +{ + if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { + JSObject *p; + uint32_t idx; + double d; + int32_t v; + + /* fast path for array access */ + p = JS_VALUE_GET_OBJ(this_obj); + idx = JS_VALUE_GET_INT(prop); + switch(p->class_id) { + case JS_CLASS_ARRAY: + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + /* fast path to add an element to the array */ + if (unlikely(idx != (uint32_t)p->u.array.count || + !p->fast_array || + !p->extensible || + p->shape->proto != JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) || + !ctx->std_array_prototype)) { + goto slow_path; + } + /* add element */ + return add_fast_array_element(ctx, p, val, flags); + } + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto slow_path; + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_MAPPED_ARGUMENTS: + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto slow_path; + set_value(ctx, p->u.array.u.var_refs[idx]->pvalue, val); + break; + case JS_CLASS_UINT8C_ARRAY: + if (JS_ToUint8ClampFree(ctx, &v, val)) + return -1; + /* Note: the conversion can detach the typed array, so the + array bound check must be done after */ + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint16_ptr[idx] = v; + break; + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint32_ptr[idx] = v; + break; + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + /* XXX: need specific conversion function */ + { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint64_ptr[idx] = v; + } + break; + case JS_CLASS_FLOAT16_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.fp16_ptr[idx] = tofp16(d); + break; + case JS_CLASS_FLOAT32_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.float_ptr[idx] = d; + break; + case JS_CLASS_FLOAT64_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + ta_out_of_bound: + return TRUE; + } + p->u.array.u.double_ptr[idx] = d; + break; + default: + goto slow_path; + } + return TRUE; + } else { + JSAtom atom; + int ret; + slow_path: + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, this_obj, flags); + JS_FreeAtom(ctx, atom); + return ret; + } +} + +int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx, JSValue val) +{ + return JS_SetPropertyValue(ctx, this_obj, JS_NewUint32(ctx, idx), val, + JS_PROP_THROW); +} + +int JS_SetPropertyInt64(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= INT32_MAX) { + /* fast path for fast arrays */ + return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), val, + JS_PROP_THROW); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + res = JS_SetProperty(ctx, this_obj, prop, val); + JS_FreeAtom(ctx, prop); + return res; +} + +int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj, + const char *prop, JSValue val) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, this_obj, JS_PROP_THROW); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* compute the property flags. For each flag: (JS_PROP_HAS_x forces + it, otherwise def_flags is used) + Note: makes assumption about the bit pattern of the flags +*/ +static int get_prop_flags(int flags, int def_flags) +{ + int mask; + mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E; + return (flags & mask) | (def_flags & ~mask); +} + +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, + int flags) +{ + JSProperty *pr; + int ret, prop_flags; + JSVarRef *var_ref; + JSObject *delete_obj; + + /* add a new property or modify an existing exotic one */ + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY) { + uint32_t idx, len; + + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + if (!p->extensible) + goto not_extensible; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) + goto convert_to_array; + prop_flags = get_prop_flags(flags, 0); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_array; + return add_fast_array_element(ctx, p, + JS_DupValue(ctx, val), flags); + } else { + goto convert_to_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + /* convert the fast array to normal array */ + convert_to_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto generic_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + JSProperty *plen; + JSShapeProperty *pslen; + generic_array: + /* update the length field */ + plen = &p->prop[0]; + JS_ToUint32(ctx, &len, plen->u.value); + if ((idx + 1) > len) { + pslen = get_shape_prop(p->shape); + if (unlikely(!(pslen->flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + /* XXX: should update the length after defining + the property */ + len = idx + 1; + set_value(ctx, &plen->u.value, JS_NewUint32(ctx, len)); + } + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array"); + } + } else if (!(flags & JS_PROP_NO_EXOTIC)) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->define_own_property) { + return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), + prop, val, getter, setter, flags); + } + ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret < 0) + return -1; + if (!ret) + goto not_extensible; + } + } + } + + if (!p->extensible) { + not_extensible: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + } + + var_ref = NULL; + delete_obj = NULL; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + } else { + prop_flags = flags & JS_PROP_C_W_E; + if (p->class_id == JS_CLASS_GLOBAL_OBJECT) { + JSObject *p1 = JS_VALUE_GET_OBJ(p->u.global_object.uninitialized_vars); + JSShapeProperty *prs1; + JSProperty *pr1; + prs1 = find_own_property(&pr1, p1, prop); + if (prs1) { + delete_obj = p1; + var_ref = pr1->u.var_ref; + var_ref->header.ref_count++; + } else { + var_ref = js_create_var_ref(ctx, FALSE); + if (!var_ref) + return -1; + } + var_ref->is_const = !(prop_flags & JS_PROP_WRITABLE); + prop_flags |= JS_PROP_VARREF; + } + } + pr = add_property(ctx, p, prop, prop_flags); + if (unlikely(!pr)) { + if (var_ref) + free_var_ref(ctx->rt, var_ref); + return -1; + } + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + pr->u.getset.getter = NULL; + if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) { + pr->u.getset.getter = + JS_VALUE_GET_OBJ(JS_DupValue(ctx, getter)); + } + pr->u.getset.setter = NULL; + if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) { + pr->u.getset.setter = + JS_VALUE_GET_OBJ(JS_DupValue(ctx, setter)); + } + } else if (p->class_id == JS_CLASS_GLOBAL_OBJECT) { + if (delete_obj) + delete_property(ctx, delete_obj, prop); + pr->u.var_ref = var_ref; + if (flags & JS_PROP_HAS_VALUE) { + *var_ref->pvalue = JS_DupValue(ctx, val); + } else { + *var_ref->pvalue = JS_UNDEFINED; + } + } else { + if (flags & JS_PROP_HAS_VALUE) { + pr->u.value = JS_DupValue(ctx, val); + } else { + pr->u.value = JS_UNDEFINED; + } + } + return TRUE; +} + +/* return FALSE if not OK */ +static BOOL check_define_prop_flags(int prop_flags, int flags) +{ + BOOL has_accessor, is_getset; + + if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == + (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) { + return FALSE; + } + if ((flags & JS_PROP_HAS_ENUMERABLE) && + (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE)) + return FALSE; + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0); + is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET); + if (has_accessor != is_getset) + return FALSE; + if (!is_getset && !(prop_flags & JS_PROP_WRITABLE)) { + /* not writable: cannot set the writable bit */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) + return FALSE; + } + } + } + return TRUE; +} + +/* ensure that the shape can be safely modified */ +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs) +{ + JSShape *sh; + uint32_t idx = 0; /* prevent warning */ + + sh = p->shape; + if (sh->is_hashed) { + if (sh->header.ref_count != 1) { + if (pprs) + idx = *pprs - get_shape_prop(sh); + /* clone the shape (the resulting one is no longer hashed) */ + sh = js_clone_shape(ctx, sh); + if (!sh) + return -1; + js_free_shape(ctx->rt, p->shape); + p->shape = sh; + if (pprs) + *pprs = get_shape_prop(sh) + idx; + } else { + js_shape_hash_unlink(ctx->rt, sh); + sh->is_hashed = FALSE; + } + } + return 0; +} + +static int js_update_property_flags(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs, int flags) +{ + if (flags != (*pprs)->flags) { + if (js_shape_prepare_update(ctx, p, pprs)) + return -1; + (*pprs)->flags = flags; + } + return 0; +} + +/* allowed flags: + JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE + JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE, + JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE, + JS_PROP_THROW, JS_PROP_NO_EXOTIC. + If JS_PROP_THROW is set, return an exception instead of FALSE. + if JS_PROP_NO_EXOTIC is set, do not call the exotic + define_own_property callback. + return -1 (exception), FALSE or TRUE. +*/ +int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int mask, res; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(this_obj); + + redo_prop_update: + prs = find_own_property(&pr, p, prop); + if (prs) { + /* the range of the Array length property is always tested before */ + if ((prs->flags & JS_PROP_LENGTH) && (flags & JS_PROP_HAS_VALUE)) { + uint32_t array_length; + if (JS_ToArrayLengthFree(ctx, &array_length, + JS_DupValue(ctx, val), FALSE)) { + return -1; + } + /* this code relies on the fact that Uint32 are never allocated */ + val = (JSValueConst)JS_NewUint32(ctx, array_length); + /* prs may have been modified */ + prs = find_own_property(&pr, p, prop); + assert(prs != NULL); + } + /* property already exists */ + if (!check_define_prop_flags(prs->flags, flags)) { + not_configurable: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); + } + + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto redo_prop_update; + } + + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + JSObject *new_getter, *new_setter; + + if (JS_IsFunction(ctx, getter)) { + new_getter = JS_VALUE_GET_OBJ(getter); + } else { + new_getter = NULL; + } + if (JS_IsFunction(ctx, setter)) { + new_setter = JS_VALUE_GET_OBJ(setter); + } else { + new_setter = NULL; + } + + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) { + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + /* convert to getset */ + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (unlikely(p->class_id == JS_CLASS_GLOBAL_OBJECT)) { + if (remove_global_object_property(ctx, p, prs, pr)) + return -1; + } + free_var_ref(ctx->rt, pr->u.var_ref); + } else { + JS_FreeValue(ctx, pr->u.value); + } + prs->flags = (prs->flags & + (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + pr->u.getset.getter = NULL; + pr->u.getset.setter = NULL; + } else { + if (!(prs->flags & JS_PROP_CONFIGURABLE)) { + if ((flags & JS_PROP_HAS_GET) && + new_getter != pr->u.getset.getter) { + goto not_configurable; + } + if ((flags & JS_PROP_HAS_SET) && + new_setter != pr->u.getset.setter) { + goto not_configurable; + } + } + } + if (flags & JS_PROP_HAS_GET) { + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (new_getter) + JS_DupValue(ctx, getter); + pr->u.getset.getter = new_getter; + } + if (flags & JS_PROP_HAS_SET) { + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + if (new_setter) + JS_DupValue(ctx, setter); + pr->u.getset.setter = new_setter; + } + } else { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + /* convert to data descriptor */ + JSVarRef *var_ref; + if (unlikely(p->class_id == JS_CLASS_GLOBAL_OBJECT)) { + var_ref = js_global_object_find_uninitialized_var(ctx, p, prop, FALSE); + if (!var_ref) + return -1; + } else { + var_ref = NULL; + } + if (js_shape_prepare_update(ctx, p, &prs)) { + if (var_ref) + free_var_ref(ctx->rt, var_ref); + return -1; + } + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + if (var_ref) { + prs->flags = (prs->flags & ~JS_PROP_TMASK) | + JS_PROP_VARREF | JS_PROP_WRITABLE; + pr->u.var_ref = var_ref; + } else { + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + pr->u.value = JS_UNDEFINED; + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* Note: JS_PROP_VARREF is always writable */ + } else { + if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && + (flags & JS_PROP_HAS_VALUE)) { + if (!js_same_value(ctx, val, pr->u.value)) { + goto not_configurable; + } else { + return TRUE; + } + } + } + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (flags & JS_PROP_HAS_VALUE) { + if (p->class_id == JS_CLASS_MODULE_NS) { + /* JS_PROP_WRITABLE is always true for variable + references, but they are write protected in module name + spaces. */ + if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue)) + goto not_configurable; + } else { + /* update the reference */ + set_value(ctx, pr->u.var_ref->pvalue, + JS_DupValue(ctx, val)); + } + } + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { + JSValue val1; + if (p->class_id == JS_CLASS_MODULE_NS) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "module namespace properties have writable = false"); + } + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + if (p->class_id == JS_CLASS_GLOBAL_OBJECT) { + pr->u.var_ref->is_const = TRUE; /* mark as read-only */ + prs->flags &= ~JS_PROP_WRITABLE; + } else { + /* if writable is set to false, no longer a + reference (for mapped arguments) */ + val1 = JS_DupValue(ctx, *pr->u.var_ref->pvalue); + free_var_ref(ctx->rt, pr->u.var_ref); + pr->u.value = val1; + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + } + } + } else if (prs->flags & JS_PROP_LENGTH) { + if (flags & JS_PROP_HAS_VALUE) { + /* Note: no JS code is executable because + 'val' is guaranted to be a Uint32 */ + res = set_array_length(ctx, p, JS_DupValue(ctx, val), + flags); + } else { + res = TRUE; + } + /* still need to reset the writable flag if + needed. The JS_PROP_LENGTH is kept because the + Uint32 test is still done if the length + property is read-only. */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + JS_PROP_HAS_WRITABLE) { + prs = get_shape_prop(p->shape); + if (js_update_property_flags(ctx, p, &prs, + prs->flags & ~JS_PROP_WRITABLE)) + return -1; + } + return res; + } else { + if (flags & JS_PROP_HAS_VALUE) { + JS_FreeValue(ctx, pr->u.value); + pr->u.value = JS_DupValue(ctx, val); + } + if (flags & JS_PROP_HAS_WRITABLE) { + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~JS_PROP_WRITABLE) | + (flags & JS_PROP_WRITABLE))) + return -1; + } + } + } + } + mask = 0; + if (flags & JS_PROP_HAS_CONFIGURABLE) + mask |= JS_PROP_CONFIGURABLE; + if (flags & JS_PROP_HAS_ENUMERABLE) + mask |= JS_PROP_ENUMERABLE; + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~mask) | (flags & mask))) + return -1; + return TRUE; + } + + /* handle modification of fast array elements */ + if (p->fast_array) { + uint32_t idx; + uint32_t prop_flags; + if (p->class_id == JS_CLASS_ARRAY) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + prop_flags = get_prop_flags(flags, JS_PROP_C_W_E); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_slow_array; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + convert_to_slow_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + else + goto redo_prop_update; + } + if (flags & JS_PROP_HAS_VALUE) { + set_value(ctx, &p->u.array.u.values[idx], JS_DupValue(ctx, val)); + } + return TRUE; + } + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + JSValue num; + int ret; + + if (!__JS_AtomIsTaggedInt(prop)) { + /* slow path with to handle all numeric indexes */ + num = JS_AtomIsNumericIndex1(ctx, prop); + if (JS_IsUndefined(num)) + goto typed_array_done; + if (JS_IsException(num)) + return -1; + ret = JS_NumberIsInteger(ctx, num); + if (ret < 0) { + JS_FreeValue(ctx, num); + return -1; + } + if (!ret) { + JS_FreeValue(ctx, num); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array"); + } + ret = JS_NumberIsNegativeOrMinusZero(ctx, num); + JS_FreeValue(ctx, num); + if (ret) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array"); + } + if (!__JS_AtomIsTaggedInt(prop)) + goto typed_array_oob; + } + idx = __JS_AtomToUInt32(prop); + /* if the typed array is detached, p->u.array.count = 0 */ + if (idx >= p->u.array.count) { + typed_array_oob: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array"); + } + prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) || + prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags"); + } + if (flags & JS_PROP_HAS_VALUE) { + return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), JS_DupValue(ctx, val), flags); + } + return TRUE; + typed_array_done: ; + } + } + + return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags); +} + +static int JS_DefineAutoInitProperty(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSAutoInitIDEnum id, + void *opaque, int flags) +{ + JSObject *p; + JSProperty *pr; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) + return FALSE; + + p = JS_VALUE_GET_OBJ(this_obj); + + if (find_own_property(&pr, p, prop)) { + /* property already exists */ + abort(); + return FALSE; + } + + /* Specialized CreateProperty */ + pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT); + if (unlikely(!pr)) + return -1; + pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx); + assert((pr->u.init.realm_and_id & 3) == 0); + assert(id <= 3); + pr->u.init.realm_and_id |= id; + pr->u.init.opaque = opaque; + return TRUE; +} + +/* shortcut to add or redefine a new property value */ +int JS_DefinePropertyValue(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValue val, int flags) +{ + int ret; + ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE); + JS_FreeValue(ctx, val); + return ret; +} + +int JS_DefinePropertyValueValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +int JS_DefinePropertyValueUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewUint32(ctx, idx), + val, flags); +} + +int JS_DefinePropertyValueInt64(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx), + val, flags); +} + +int JS_DefinePropertyValueStr(JSContext *ctx, JSValueConst this_obj, + const char *prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* shortcut to add getter & setter */ +int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValue getter, JSValue setter, + int flags) +{ + int ret; + ret = JS_DefineProperty(ctx, this_obj, prop, JS_UNDEFINED, getter, setter, + flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE); + JS_FreeValue(ctx, getter); + JS_FreeValue(ctx, setter); + return ret; +} + +static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx), + val, flags | JS_PROP_CONFIGURABLE | + JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); +} + + +/* return TRUE if 'obj' has a non empty 'name' string */ +static BOOL js_object_has_name(JSContext *ctx, JSValueConst obj) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValueConst val; + JSString *p; + + prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name); + if (!prs) + return FALSE; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return TRUE; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return TRUE; + p = JS_VALUE_GET_STRING(val); + return (p->len != 0); +} + +static int JS_DefineObjectName(JSContext *ctx, JSValueConst obj, + JSAtom name, int flags) +{ + if (name != JS_ATOM_NULL + && JS_IsObject(obj) + && !js_object_has_name(ctx, obj) + && JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) { + return -1; + } + return 0; +} + +static int JS_DefineObjectNameComputed(JSContext *ctx, JSValueConst obj, + JSValueConst str, int flags) +{ + if (JS_IsObject(obj) && + !js_object_has_name(ctx, obj)) { + JSAtom prop; + JSValue name_str; + prop = JS_ValueToAtom(ctx, str); + if (prop == JS_ATOM_NULL) + return -1; + name_str = js_get_function_name(ctx, prop); + JS_FreeAtom(ctx, prop); + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0) + return -1; + } + return 0; +} + +#define DEFINE_GLOBAL_LEX_VAR (1 << 7) +#define DEFINE_GLOBAL_FUNC_VAR (1 << 6) + +static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) +{ + return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); +} + +/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ +/* XXX: could support exotic global object. */ +static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property1(p, prop); + /* XXX: should handle JS_PROP_AUTOINIT */ + if (flags & DEFINE_GLOBAL_LEX_VAR) { + if (prs && !(prs->flags & JS_PROP_CONFIGURABLE)) + goto fail_redeclaration; + } else { + if (!prs && !p->extensible) + goto define_error; + if (flags & DEFINE_GLOBAL_FUNC_VAR) { + if (prs) { + if (!(prs->flags & JS_PROP_CONFIGURABLE) && + ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET || + ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != + (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { + define_error: + JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", + prop); + return -1; + } + } + } + } + /* check if there already is a lexical declaration */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property1(p, prop); + if (prs) { + fail_redeclaration: + JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop); + return -1; + } + return 0; +} + +/* construct a reference to a global variable */ +static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: conformance: do these tests in + OP_put_var_ref/OP_get_var_ref ? */ + if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { + return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); + } + sp[0] = JS_DupValue(ctx, ctx->global_var_obj); + } else { + int ret; + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + if (ret) { + sp[0] = JS_DupValue(ctx, ctx->global_obj); + } else { + sp[0] = JS_UNDEFINED; + } + } + sp[1] = JS_AtomToValue(ctx, prop); + return 0; +} + +/* return -1, FALSE or TRUE */ +static int JS_DeleteGlobalVar(JSContext *ctx, JSAtom prop) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int ret; + + /* 9.1.1.4.7 DeleteBinding ( N ) */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) + return FALSE; /* lexical variables cannot be deleted */ + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + if (ret) { + return JS_DeleteProperty(ctx, ctx->global_obj, prop, 0); + } else { + return TRUE; + } +} + +/* return -1, FALSE or TRUE. return FALSE if not configurable or + invalid object. return -1 in case of exception. + flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */ +int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags) +{ + JSValue obj1; + JSObject *p; + int res; + + obj1 = JS_ToObject(ctx, obj); + if (JS_IsException(obj1)) + return -1; + p = JS_VALUE_GET_OBJ(obj1); + res = delete_property(ctx, p, prop); + JS_FreeValue(ctx, obj1); + if (res != FALSE) + return res; + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "could not delete property"); + return -1; + } + return FALSE; +} + +int JS_DeletePropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, int flags) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= JS_ATOM_MAX_INT) { + /* fast path for fast arrays */ + return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return -1; + res = JS_DeleteProperty(ctx, obj, prop, flags); + JS_FreeAtom(ctx, prop); + return res; +} + +BOOL JS_IsFunction(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + switch(p->class_id) { + case JS_CLASS_BYTECODE_FUNCTION: + return TRUE; + case JS_CLASS_PROXY: + return p->u.proxy_data->is_func; + default: + return (ctx->rt->class_array[p->class_id].call != NULL); + } +} + +BOOL JS_IsCFunction(JSContext *ctx, JSValueConst val, JSCFunction *func, int magic) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + if (p->class_id == JS_CLASS_C_FUNCTION) + return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic); + else + return FALSE; +} + +BOOL JS_IsConstructor(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + return p->is_constructor; +} + +BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, BOOL val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(func_obj); + p->is_constructor = val; + return TRUE; +} + +BOOL JS_IsError(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + return (p->class_id == JS_CLASS_ERROR); +} + +/* must be called after JS_Throw() */ +void JS_SetUncatchableException(JSContext *ctx, BOOL flag) +{ + ctx->rt->current_exception_is_uncatchable = flag; +} + +void JS_SetOpaque(JSValue obj, void *opaque) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + p->u.opaque = opaque; + } +} + +/* return NULL if not an object of class class_id */ +void *JS_GetOpaque(JSValueConst obj, JSClassID class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != class_id) + return NULL; + return p->u.opaque; +} + +void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id) +{ + void *p = JS_GetOpaque(obj, class_id); + if (unlikely(!p)) { + JS_ThrowTypeErrorInvalidClass(ctx, class_id); + } + return p; +} + +void *JS_GetAnyOpaque(JSValueConst obj, JSClassID *class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + *class_id = 0; + return NULL; + } + p = JS_VALUE_GET_OBJ(obj); + *class_id = p->class_id; + return p->u.opaque; +} + +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) +{ + int i; + BOOL force_ordinary; + + JSAtom method_name; + JSValue method, ret; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return val; + force_ordinary = hint & HINT_FORCE_ORDINARY; + hint &= ~HINT_FORCE_ORDINARY; + if (!force_ordinary) { + method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive); + if (JS_IsException(method)) + goto exception; + /* ECMA says *If exoticToPrim is not undefined* but tests in + test262 use null as a non callable converter */ + if (!JS_IsUndefined(method) && !JS_IsNull(method)) { + JSAtom atom; + JSValue arg; + switch(hint) { + case HINT_STRING: + atom = JS_ATOM_string; + break; + case HINT_NUMBER: + atom = JS_ATOM_number; + break; + default: + case HINT_NONE: + atom = JS_ATOM_default; + break; + } + arg = JS_AtomToString(ctx, atom); + ret = JS_CallFree(ctx, method, val, 1, (JSValueConst *)&arg); + JS_FreeValue(ctx, arg); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) + return ret; + JS_FreeValue(ctx, ret); + return JS_ThrowTypeError(ctx, "toPrimitive"); + } + } + if (hint != HINT_STRING) + hint = HINT_NUMBER; + for(i = 0; i < 2; i++) { + if ((i ^ hint) == 0) { + method_name = JS_ATOM_toString; + } else { + method_name = JS_ATOM_valueOf; + } + method = JS_GetProperty(ctx, val, method_name); + if (JS_IsException(method)) + goto exception; + if (JS_IsFunction(ctx, method)) { + ret = JS_CallFree(ctx, method, val, 0, NULL); + if (JS_IsException(ret)) + goto exception; + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { + JS_FreeValue(ctx, val); + return ret; + } + JS_FreeValue(ctx, ret); + } else { + JS_FreeValue(ctx, method); + } + } + JS_ThrowTypeError(ctx, "toPrimitive"); +exception: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue JS_ToPrimitive(JSContext *ctx, JSValueConst val, int hint) +{ + return JS_ToPrimitiveFree(ctx, JS_DupValue(ctx, val), hint); +} + +void JS_SetIsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(obj); + p->is_HTMLDDA = TRUE; +} + +static inline BOOL JS_IsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + return p->is_HTMLDDA; +} + +static int JS_ToBoolFree(JSContext *ctx, JSValue val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + return JS_VALUE_GET_INT(val) != 0; + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + return JS_VALUE_GET_INT(val); + case JS_TAG_EXCEPTION: + return -1; + case JS_TAG_STRING: + { + BOOL ret = JS_VALUE_GET_STRING(val)->len != 0; + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_STRING_ROPE: + { + BOOL ret = JS_VALUE_GET_STRING_ROPE(val)->len != 0; + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_SHORT_BIG_INT: + return JS_VALUE_GET_SHORT_BIG_INT(val) != 0; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + BOOL ret; + int i; + + /* fail safe: we assume it is not necessarily + normalized. Beginning from the MSB ensures that the + test is fast. */ + ret = FALSE; + for(i = p->len - 1; i >= 0; i--) { + if (p->tab[i] != 0) { + ret = TRUE; + break; + } + } + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + BOOL ret; + ret = !p->is_HTMLDDA; + JS_FreeValue(ctx, val); + return ret; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d = JS_VALUE_GET_FLOAT64(val); + return !isnan(d) && d != 0; + } else { + JS_FreeValue(ctx, val); + return TRUE; + } + } +} + +int JS_ToBool(JSContext *ctx, JSValueConst val) +{ + return JS_ToBoolFree(ctx, JS_DupValue(ctx, val)); +} + +static int skip_spaces(const char *pc) +{ + const uint8_t *p, *p_next, *p_start; + uint32_t c; + + p = p_start = (const uint8_t *)pc; + for (;;) { + c = *p; + if (c < 128) { + if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) + break; + p++; + } else { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); + if (!lre_is_space(c)) + break; + p = p_next; + } + } + return p - p_start; +} + +static inline int to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* bigint support */ + +#define JS_BIGINT_MAX_SIZE ((1024 * 1024) / JS_LIMB_BITS) /* in limbs */ + +/* it is currently assumed that JS_SHORT_BIG_INT_BITS = JS_LIMB_BITS */ +#if JS_SHORT_BIG_INT_BITS == 32 +#define JS_SHORT_BIG_INT_MIN INT32_MIN +#define JS_SHORT_BIG_INT_MAX INT32_MAX +#elif JS_SHORT_BIG_INT_BITS == 64 +#define JS_SHORT_BIG_INT_MIN INT64_MIN +#define JS_SHORT_BIG_INT_MAX INT64_MAX +#else +#error unsupported +#endif + +#define ADDC(res, carry_out, op1, op2, carry_in) \ +do { \ + js_limb_t __v, __a, __k, __k1; \ + __v = (op1); \ + __a = __v + (op2); \ + __k1 = __a < __v; \ + __k = (carry_in); \ + __a = __a + __k; \ + carry_out = (__a < __k) | __k1; \ + res = __a; \ +} while (0) + +#if JS_LIMB_BITS == 32 +/* a != 0 */ +static inline js_limb_t js_limb_clz(js_limb_t a) +{ + return clz32(a); +} +#else +static inline js_limb_t js_limb_clz(js_limb_t a) +{ + return clz64(a); +} +#endif + +/* handle a = 0 too */ +static inline js_limb_t js_limb_safe_clz(js_limb_t a) +{ + if (a == 0) + return JS_LIMB_BITS; + else + return js_limb_clz(a); +} + +static js_limb_t mp_add(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, + js_limb_t n, js_limb_t carry) +{ + int i; + for(i = 0;i < n; i++) { + ADDC(res[i], carry, op1[i], op2[i], carry); + } + return carry; +} + +static js_limb_t mp_sub(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, + int n, js_limb_t carry) +{ + int i; + js_limb_t k, a, v, k1; + + k = carry; + for(i=0;i v; + v = a - k; + k = (v > a) | k1; + res[i] = v; + } + return k; +} + +/* compute 0 - op2. carry = 0 or 1. */ +static js_limb_t mp_neg(js_limb_t *res, const js_limb_t *op2, int n) +{ + int i; + js_limb_t v, carry; + + carry = 1; + for(i=0;i> JS_LIMB_BITS; + } + return l; +} + +static js_limb_t mp_div1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b, js_limb_t r) +{ + js_slimb_t i; + js_dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + return r; +} + +/* tabr[] += taba[] * b, return the high word. */ +static js_limb_t mp_add_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b) +{ + js_limb_t i, l; + js_dlimb_t t; + + l = 0; + for(i = 0; i < n; i++) { + t = (js_dlimb_t)taba[i] * (js_dlimb_t)b + l + tabr[i]; + tabr[i] = t; + l = t >> JS_LIMB_BITS; + } + return l; +} + +/* size of the result : op1_size + op2_size. */ +static void mp_mul_basecase(js_limb_t *result, + const js_limb_t *op1, js_limb_t op1_size, + const js_limb_t *op2, js_limb_t op2_size) +{ + int i; + js_limb_t r; + + result[op1_size] = mp_mul1(result, op1, op1_size, op2[0], 0); + for(i=1;i> JS_LIMB_BITS); + } + return l; +} + +/* WARNING: d must be >= 2^(JS_LIMB_BITS-1) */ +static inline js_limb_t udiv1norm_init(js_limb_t d) +{ + js_limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((js_dlimb_t)a1 << JS_LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^JS_LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline js_limb_t udiv1norm(js_limb_t *pr, js_limb_t a1, js_limb_t a0, + js_limb_t d, js_limb_t d_inv) +{ + js_limb_t n1m, n_adj, q, r, ah; + js_dlimb_t a; + n1m = ((js_slimb_t)a0 >> (JS_LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (js_dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> JS_LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is\ + between 0 and d - 1 */ + a = ((js_dlimb_t)a1 << JS_LIMB_BITS) | a0; + a = a - (js_dlimb_t)q * d - d; + ah = a >> JS_LIMB_BITS; + q += 1 + ah; + r = (js_limb_t)a + (ah & d); + *pr = r; + return q; +} + +#define UDIV1NORM_THRESHOLD 3 + +/* b must be >= 1 << (JS_LIMB_BITS - 1) */ +static js_limb_t mp_div1norm(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b, js_limb_t r) +{ + js_slimb_t i; + + if (n >= UDIV1NORM_THRESHOLD) { + js_limb_t b_inv; + b_inv = udiv1norm_init(b); + for(i = n - 1; i >= 0; i--) { + tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); + } + } else { + js_dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + } + return r; +} + +/* base case division: divides taba[0..na-1] by tabb[0..nb-1]. tabb[nb + - 1] must be >= 1 << (JS_LIMB_BITS - 1). na - nb must be >= 0. 'taba' + is modified and contains the remainder (nb limbs). tabq[0..na-nb] + contains the quotient with tabq[na - nb] <= 1. */ +static void mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, + const js_limb_t *tabb, js_limb_t nb) +{ + js_limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r; + int i, j; + + b1 = tabb[nb - 1]; + if (nb == 1) { + taba[0] = mp_div1norm(tabq, taba, na, b1, 0); + return; + } + n = na - nb; + + if (n >= UDIV1NORM_THRESHOLD) + b1_inv = udiv1norm_init(b1); + else + b1_inv = 0; + + /* first iteration: the quotient is only 0 or 1 */ + q = 1; + for(j = nb - 1; j >= 0; j--) { + if (taba[n + j] != tabb[j]) { + if (taba[n + j] < tabb[j]) + q = 0; + break; + } + } + tabq[n] = q; + if (q) { + mp_sub(taba + n, taba + n, tabb, nb, 0); + } + + for(i = n - 1; i >= 0; i--) { + if (unlikely(taba[i + nb] >= b1)) { + q = -1; + } else if (b1_inv) { + q = udiv1norm(&dummy_r, taba[i + nb], taba[i + nb - 1], b1, b1_inv); + } else { + js_dlimb_t al; + al = ((js_dlimb_t)taba[i + nb] << JS_LIMB_BITS) | taba[i + nb - 1]; + q = al / b1; + r = al % b1; + } + r = mp_sub_mul1(taba + i, tabb, nb, q); + + v = taba[i + nb]; + a = v - r; + c = (a > v); + taba[i + nb] = a; + + if (c != 0) { + /* negative result */ + for(;;) { + q--; + c = mp_add(taba + i, taba + i, tabb, nb, 0); + /* propagate carry and test if positive result */ + if (c != 0) { + if (++taba[i + nb] == 0) { + break; + } + } + } + } + tabq[i] = q; + } +} + +/* 1 <= shift <= JS_LIMB_BITS - 1 */ +static js_limb_t mp_shl(js_limb_t *tabr, const js_limb_t *taba, int n, + int shift) +{ + int i; + js_limb_t l, v; + l = 0; + for(i = 0; i < n; i++) { + v = taba[i]; + tabr[i] = (v << shift) | l; + l = v >> (JS_LIMB_BITS - shift); + } + return l; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static js_limb_t mp_shr(js_limb_t *tab_r, const js_limb_t *tab, int n, + int shift, js_limb_t high) +{ + int i; + js_limb_t l, a; + + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + tab_r[i] = (a >> shift) | (l << (JS_LIMB_BITS - shift)); + l = a; + } + return l & (((js_limb_t)1 << shift) - 1); +} + +static JSBigInt *js_bigint_new(JSContext *ctx, int len) +{ + JSBigInt *r; + if (len > JS_BIGINT_MAX_SIZE) { + JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); + return NULL; + } + r = js_malloc(ctx, sizeof(JSBigInt) + len * sizeof(js_limb_t)); + if (!r) + return NULL; + r->header.ref_count = 1; + r->len = len; + return r; +} + +static JSBigInt *js_bigint_set_si(JSBigIntBuf *buf, js_slimb_t a) +{ + JSBigInt *r = (JSBigInt *)buf->big_int_buf; + r->header.ref_count = 0; /* fail safe */ + r->len = 1; + r->tab[0] = a; + return r; +} + +static JSBigInt *js_bigint_set_si64(JSBigIntBuf *buf, int64_t a) +{ +#if JS_LIMB_BITS == 64 + return js_bigint_set_si(buf, a); +#else + JSBigInt *r = (JSBigInt *)buf->big_int_buf; + r->header.ref_count = 0; /* fail safe */ + if (a >= INT32_MIN && a <= INT32_MAX) { + r->len = 1; + r->tab[0] = a; + } else { + r->len = 2; + r->tab[0] = a; + r->tab[1] = a >> JS_LIMB_BITS; + } + return r; +#endif +} + +/* val must be a short big int */ +static JSBigInt *js_bigint_set_short(JSBigIntBuf *buf, JSValueConst val) +{ + return js_bigint_set_si(buf, JS_VALUE_GET_SHORT_BIG_INT(val)); +} + +static __maybe_unused void js_bigint_dump1(JSContext *ctx, const char *str, + const js_limb_t *tab, int len) +{ + int i; + printf("%s: ", str); + for(i = len - 1; i >= 0; i--) { +#if JS_LIMB_BITS == 32 + printf(" %08x", tab[i]); +#else + printf(" %016" PRIx64, tab[i]); +#endif + } + printf("\n"); +} + +static __maybe_unused void js_bigint_dump(JSContext *ctx, const char *str, + const JSBigInt *p) +{ + js_bigint_dump1(ctx, str, p->tab, p->len); +} + +static JSBigInt *js_bigint_new_si(JSContext *ctx, js_slimb_t a) +{ + JSBigInt *r; + r = js_bigint_new(ctx, 1); + if (!r) + return NULL; + r->tab[0] = a; + return r; +} + +static JSBigInt *js_bigint_new_si64(JSContext *ctx, int64_t a) +{ +#if JS_LIMB_BITS == 64 + return js_bigint_new_si(ctx, a); +#else + if (a >= INT32_MIN && a <= INT32_MAX) { + return js_bigint_new_si(ctx, a); + } else { + JSBigInt *r; + r = js_bigint_new(ctx, 2); + if (!r) + return NULL; + r->tab[0] = a; + r->tab[1] = a >> 32; + return r; + } +#endif +} + +static JSBigInt *js_bigint_new_ui64(JSContext *ctx, uint64_t a) +{ + if (a <= INT64_MAX) { + return js_bigint_new_si64(ctx, a); + } else { + JSBigInt *r; + r = js_bigint_new(ctx, (65 + JS_LIMB_BITS - 1) / JS_LIMB_BITS); + if (!r) + return NULL; +#if JS_LIMB_BITS == 64 + r->tab[0] = a; + r->tab[1] = 0; +#else + r->tab[0] = a; + r->tab[1] = a >> 32; + r->tab[2] = 0; +#endif + return r; + } +} + +static JSBigInt *js_bigint_new_di(JSContext *ctx, js_sdlimb_t a) +{ + JSBigInt *r; + if (a == (js_slimb_t)a) { + r = js_bigint_new(ctx, 1); + if (!r) + return NULL; + r->tab[0] = a; + } else { + r = js_bigint_new(ctx, 2); + if (!r) + return NULL; + r->tab[0] = a; + r->tab[1] = a >> JS_LIMB_BITS; + } + return r; +} + +/* Remove redundant high order limbs. Warning: 'a' may be + reallocated. Can never fail. +*/ +static JSBigInt *js_bigint_normalize1(JSContext *ctx, JSBigInt *a, int l) +{ + js_limb_t v; + + assert(a->header.ref_count == 1); + while (l > 1) { + v = a->tab[l - 1]; + if ((v != 0 && v != -1) || + (v & 1) != (a->tab[l - 2] >> (JS_LIMB_BITS - 1))) { + break; + } + l--; + } + if (l != a->len) { + JSBigInt *a1; + /* realloc to reduce the size */ + a->len = l; + a1 = js_realloc(ctx, a, sizeof(JSBigInt) + l * sizeof(js_limb_t)); + if (a1) + a = a1; + } + return a; +} + +static JSBigInt *js_bigint_normalize(JSContext *ctx, JSBigInt *a) +{ + return js_bigint_normalize1(ctx, a, a->len); +} + +/* return 0 or 1 depending on the sign */ +static inline int js_bigint_sign(const JSBigInt *a) +{ + return a->tab[a->len - 1] >> (JS_LIMB_BITS - 1); +} + +static js_slimb_t js_bigint_get_si_sat(const JSBigInt *a) +{ + if (a->len == 1) { + return a->tab[0]; + } else { +#if JS_LIMB_BITS == 32 + if (js_bigint_sign(a)) + return INT32_MIN; + else + return INT32_MAX; +#else + if (js_bigint_sign(a)) + return INT64_MIN; + else + return INT64_MAX; +#endif + } +} + +/* add the op1 limb */ +static JSBigInt *js_bigint_extend(JSContext *ctx, JSBigInt *r, + js_limb_t op1) +{ + int n2 = r->len; + if ((op1 != 0 && op1 != -1) || + (op1 & 1) != r->tab[n2 - 1] >> (JS_LIMB_BITS - 1)) { + JSBigInt *r1; + r1 = js_realloc(ctx, r, + sizeof(JSBigInt) + (n2 + 1) * sizeof(js_limb_t)); + if (!r1) { + js_free(ctx, r); + return NULL; + } + r = r1; + r->len = n2 + 1; + r->tab[n2] = op1; + } else { + /* otherwise still need to normalize the result */ + r = js_bigint_normalize(ctx, r); + } + return r; +} + +/* return NULL in case of error. Compute a + b (b_neg = 0) or a - b + (b_neg = 1) */ +/* XXX: optimize */ +static JSBigInt *js_bigint_add(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, int b_neg) +{ + JSBigInt *r; + int n1, n2, i; + js_limb_t carry, op1, op2, a_sign, b_sign; + + n2 = max_int(a->len, b->len); + n1 = min_int(a->len, b->len); + r = js_bigint_new(ctx, n2); + if (!r) + return NULL; + /* XXX: optimize */ + /* common part */ + carry = b_neg; + for(i = 0; i < n1; i++) { + op1 = a->tab[i]; + op2 = b->tab[i] ^ (-b_neg); + ADDC(r->tab[i], carry, op1, op2, carry); + } + a_sign = -js_bigint_sign(a); + b_sign = (-js_bigint_sign(b)) ^ (-b_neg); + /* part with sign extension of one operand */ + if (a->len > b->len) { + for(i = n1; i < n2; i++) { + op1 = a->tab[i]; + ADDC(r->tab[i], carry, op1, b_sign, carry); + } + } else if (a->len < b->len) { + for(i = n1; i < n2; i++) { + op2 = b->tab[i] ^ (-b_neg); + ADDC(r->tab[i], carry, a_sign, op2, carry); + } + } + + /* part with sign extension for both operands. Extend the result + if necessary */ + return js_bigint_extend(ctx, r, a_sign + b_sign + carry); +} + +/* XXX: optimize */ +static JSBigInt *js_bigint_neg(JSContext *ctx, const JSBigInt *a) +{ + JSBigIntBuf buf; + JSBigInt *b; + b = js_bigint_set_si(&buf, 0); + return js_bigint_add(ctx, b, a, 1); +} + +static JSBigInt *js_bigint_mul(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b) +{ + JSBigInt *r; + + r = js_bigint_new(ctx, a->len + b->len); + if (!r) + return NULL; + mp_mul_basecase(r->tab, a->tab, a->len, b->tab, b->len); + /* correct the result if negative operands (no overflow is + possible) */ + if (js_bigint_sign(a)) + mp_sub(r->tab + a->len, r->tab + a->len, b->tab, b->len, 0); + if (js_bigint_sign(b)) + mp_sub(r->tab + b->len, r->tab + b->len, a->tab, a->len, 0); + return js_bigint_normalize(ctx, r); +} + +/* return the division or the remainder. 'b' must be != 0. return NULL + in case of exception (division by zero or memory error) */ +static JSBigInt *js_bigint_divrem(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, BOOL is_rem) +{ + JSBigInt *r, *q; + js_limb_t *tabb, h; + int na, nb, a_sign, b_sign, shift; + + if (b->len == 1 && b->tab[0] == 0) { + JS_ThrowRangeError(ctx, "BigInt division by zero"); + return NULL; + } + + a_sign = js_bigint_sign(a); + b_sign = js_bigint_sign(b); + na = a->len; + nb = b->len; + + r = js_bigint_new(ctx, na + 2); + if (!r) + return NULL; + if (a_sign) { + mp_neg(r->tab, a->tab, na); + } else { + memcpy(r->tab, a->tab, na * sizeof(a->tab[0])); + } + /* normalize */ + while (na > 1 && r->tab[na - 1] == 0) + na--; + + tabb = js_malloc(ctx, nb * sizeof(tabb[0])); + if (!tabb) { + js_free(ctx, r); + return NULL; + } + if (b_sign) { + mp_neg(tabb, b->tab, nb); + } else { + memcpy(tabb, b->tab, nb * sizeof(tabb[0])); + } + /* normalize */ + while (nb > 1 && tabb[nb - 1] == 0) + nb--; + + /* trivial case if 'a' is small */ + if (na < nb) { + js_free(ctx, r); + js_free(ctx, tabb); + if (is_rem) { + /* r = a */ + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + memcpy(r->tab, a->tab, a->len * sizeof(a->tab[0])); + return r; + } else { + /* q = 0 */ + return js_bigint_new_si(ctx, 0); + } + } + + /* normalize 'b' */ + shift = js_limb_clz(tabb[nb - 1]); + if (shift != 0) { + mp_shl(tabb, tabb, nb, shift); + h = mp_shl(r->tab, r->tab, na, shift); + if (h != 0) + r->tab[na++] = h; + } + + q = js_bigint_new(ctx, na - nb + 2); /* one more limb for the sign */ + if (!q) { + js_free(ctx, r); + js_free(ctx, tabb); + return NULL; + } + + // js_bigint_dump1(ctx, "a", r->tab, na); + // js_bigint_dump1(ctx, "b", tabb, nb); + mp_divnorm(q->tab, r->tab, na, tabb, nb); + js_free(ctx, tabb); + + if (is_rem) { + js_free(ctx, q); + if (shift != 0) + mp_shr(r->tab, r->tab, nb, shift, 0); + r->tab[nb++] = 0; + if (a_sign) + mp_neg(r->tab, r->tab, nb); + r = js_bigint_normalize1(ctx, r, nb); + return r; + } else { + js_free(ctx, r); + q->tab[na - nb + 1] = 0; + if (a_sign ^ b_sign) { + mp_neg(q->tab, q->tab, q->len); + } + q = js_bigint_normalize(ctx, q); + return q; + } +} + +/* and, or, xor */ +static JSBigInt *js_bigint_logic(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, OPCodeEnum op) +{ + JSBigInt *r; + js_limb_t b_sign; + int a_len, b_len, i; + + if (a->len < b->len) { + const JSBigInt *tmp; + tmp = a; + a = b; + b = tmp; + } + /* a_len >= b_len */ + a_len = a->len; + b_len = b->len; + b_sign = -js_bigint_sign(b); + + r = js_bigint_new(ctx, a_len); + if (!r) + return NULL; + switch(op) { + case OP_or: + for(i = 0; i < b_len; i++) { + r->tab[i] = a->tab[i] | b->tab[i]; + } + for(i = b_len; i < a_len; i++) { + r->tab[i] = a->tab[i] | b_sign; + } + break; + case OP_and: + for(i = 0; i < b_len; i++) { + r->tab[i] = a->tab[i] & b->tab[i]; + } + for(i = b_len; i < a_len; i++) { + r->tab[i] = a->tab[i] & b_sign; + } + break; + case OP_xor: + for(i = 0; i < b_len; i++) { + r->tab[i] = a->tab[i] ^ b->tab[i]; + } + for(i = b_len; i < a_len; i++) { + r->tab[i] = a->tab[i] ^ b_sign; + } + break; + default: + abort(); + } + return js_bigint_normalize(ctx, r); +} + +static JSBigInt *js_bigint_not(JSContext *ctx, const JSBigInt *a) +{ + JSBigInt *r; + int i; + + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + for(i = 0; i < a->len; i++) { + r->tab[i] = ~a->tab[i]; + } + /* no normalization is needed */ + return r; +} + +static JSBigInt *js_bigint_shl(JSContext *ctx, const JSBigInt *a, + unsigned int shift1) +{ + int d, i, shift; + JSBigInt *r; + js_limb_t l; + + if (a->len == 1 && a->tab[0] == 0) + return js_bigint_new_si(ctx, 0); /* zero case */ + d = shift1 / JS_LIMB_BITS; + shift = shift1 % JS_LIMB_BITS; + r = js_bigint_new(ctx, a->len + d); + if (!r) + return NULL; + for(i = 0; i < d; i++) + r->tab[i] = 0; + if (shift == 0) { + for(i = 0; i < a->len; i++) { + r->tab[i + d] = a->tab[i]; + } + } else { + l = mp_shl(r->tab + d, a->tab, a->len, shift); + if (js_bigint_sign(a)) + l |= (js_limb_t)(-1) << shift; + r = js_bigint_extend(ctx, r, l); + } + return r; +} + +static JSBigInt *js_bigint_shr(JSContext *ctx, const JSBigInt *a, + unsigned int shift1) +{ + int d, i, shift, a_sign, n1; + JSBigInt *r; + + d = shift1 / JS_LIMB_BITS; + shift = shift1 % JS_LIMB_BITS; + a_sign = js_bigint_sign(a); + if (d >= a->len) + return js_bigint_new_si(ctx, -a_sign); + n1 = a->len - d; + r = js_bigint_new(ctx, n1); + if (!r) + return NULL; + if (shift == 0) { + for(i = 0; i < n1; i++) { + r->tab[i] = a->tab[i + d]; + } + /* no normalization is needed */ + } else { + mp_shr(r->tab, a->tab + d, n1, shift, -a_sign); + r = js_bigint_normalize(ctx, r); + } + return r; +} + +static JSBigInt *js_bigint_pow(JSContext *ctx, const JSBigInt *a, JSBigInt *b) +{ + uint32_t e; + int n_bits, i; + JSBigInt *r, *r1; + + /* b must be >= 0 */ + if (js_bigint_sign(b)) { + JS_ThrowRangeError(ctx, "BigInt negative exponent"); + return NULL; + } + if (b->len == 1 && b->tab[0] == 0) { + /* a^0 = 1 */ + return js_bigint_new_si(ctx, 1); + } else if (a->len == 1) { + js_limb_t v; + BOOL is_neg; + + v = a->tab[0]; + if (v <= 1) + return js_bigint_new_si(ctx, v); + else if (v == -1) + return js_bigint_new_si(ctx, 1 - 2 * (b->tab[0] & 1)); + is_neg = (js_slimb_t)v < 0; + if (is_neg) + v = -v; + if ((v & (v - 1)) == 0) { + uint64_t e1; + int n; + /* v = 2^n */ + n = JS_LIMB_BITS - 1 - js_limb_clz(v); + if (b->len > 1) + goto overflow; + if (b->tab[0] > INT32_MAX) + goto overflow; + e = b->tab[0]; + e1 = (uint64_t)e * n; + if (e1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) + goto overflow; + e = e1; + if (is_neg) + is_neg = b->tab[0] & 1; + r = js_bigint_new(ctx, + (e + JS_LIMB_BITS + 1 - is_neg) / JS_LIMB_BITS); + if (!r) + return NULL; + memset(r->tab, 0, sizeof(r->tab[0]) * r->len); + r->tab[e / JS_LIMB_BITS] = + (js_limb_t)(1 - 2 * is_neg) << (e % JS_LIMB_BITS); + return r; + } + } + if (b->len > 1) + goto overflow; + if (b->tab[0] > INT32_MAX) + goto overflow; + e = b->tab[0]; + n_bits = 32 - clz32(e); + + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + memcpy(r->tab, a->tab, a->len * sizeof(a->tab[0])); + for(i = n_bits - 2; i >= 0; i--) { + r1 = js_bigint_mul(ctx, r, r); + if (!r1) + return NULL; + js_free(ctx, r); + r = r1; + if ((e >> i) & 1) { + r1 = js_bigint_mul(ctx, r, a); + if (!r1) + return NULL; + js_free(ctx, r); + r = r1; + } + } + return r; + overflow: + JS_ThrowRangeError(ctx, "BigInt is too large"); + return NULL; +} + +/* return (mant, exp) so that abs(a) ~ mant*2^(exp - (limb_bits - + 1). a must be != 0. */ +static uint64_t js_bigint_get_mant_exp(JSContext *ctx, + int *pexp, const JSBigInt *a) +{ + js_limb_t t[4 - JS_LIMB_BITS / 32], carry, v, low_bits; + int n1, n2, sgn, shift, i, j, e; + uint64_t a1, a0; + + n2 = 4 - JS_LIMB_BITS / 32; + n1 = a->len - n2; + sgn = js_bigint_sign(a); + + /* low_bits != 0 if there are a non zero low bit in abs(a) */ + low_bits = 0; + carry = sgn; + for(i = 0; i < n1; i++) { + v = (a->tab[i] ^ (-sgn)) + carry; + carry = v < carry; + low_bits |= v; + } + /* get the n2 high limbs of abs(a) */ + for(j = 0; j < n2; j++) { + i = j + n1; + if (i < 0) { + v = 0; + } else { + v = (a->tab[i] ^ (-sgn)) + carry; + carry = v < carry; + } + t[j] = v; + } + +#if JS_LIMB_BITS == 32 + a1 = ((uint64_t)t[2] << 32) | t[1]; + a0 = (uint64_t)t[0] << 32; +#else + a1 = t[1]; + a0 = t[0]; +#endif + a0 |= (low_bits != 0); + /* normalize */ + if (a1 == 0) { + /* JS_LIMB_BITS = 64 bit only */ + shift = 64; + a1 = a0; + a0 = 0; + } else { + shift = clz64(a1); + if (shift != 0) { + a1 = (a1 << shift) | (a0 >> (64 - shift)); + a0 <<= shift; + } + } + a1 |= (a0 != 0); /* keep the bits for the final rounding */ + /* compute the exponent */ + e = a->len * JS_LIMB_BITS - shift - 1; + *pexp = e; + return a1; +} + +/* shift left with round to nearest, ties to even. n >= 1 */ +static uint64_t shr_rndn(uint64_t a, int n) +{ + uint64_t addend = ((a >> n) & 1) + ((1 << (n - 1)) - 1); + return (a + addend) >> n; +} + +/* convert to float64 with round to nearest, ties to even. Return + +/-infinity if too large. */ +static double js_bigint_to_float64(JSContext *ctx, const JSBigInt *a) +{ + int sgn, e; + uint64_t mant; + + if (a->len == 1) { + /* fast case, including zero */ + return (double)(js_slimb_t)a->tab[0]; + } + + sgn = js_bigint_sign(a); + mant = js_bigint_get_mant_exp(ctx, &e, a); + if (e > 1023) { + /* overflow: return infinity */ + mant = 0; + e = 1024; + } else { + mant = (mant >> 1) | (mant & 1); /* avoid overflow in rounding */ + mant = shr_rndn(mant, 10); + /* rounding can cause an overflow */ + if (mant >= ((uint64_t)1 << 53)) { + mant >>= 1; + e++; + } + mant &= (((uint64_t)1 << 52) - 1); + } + return uint64_as_float64(((uint64_t)sgn << 63) | + ((uint64_t)(e + 1023) << 52) | + mant); +} + +/* return (1, NULL) if not an integer, (2, NULL) if NaN or Infinity, + (0, n) if an integer, (0, NULL) in case of memory error */ +static JSBigInt *js_bigint_from_float64(JSContext *ctx, int *pres, double a1) +{ + uint64_t a = float64_as_uint64(a1); + int sgn, e, shift; + uint64_t mant; + JSBigIntBuf buf; + JSBigInt *r; + + sgn = a >> 63; + e = (a >> 52) & ((1 << 11) - 1); + mant = a & (((uint64_t)1 << 52) - 1); + if (e == 2047) { + /* NaN, Infinity */ + *pres = 2; + return NULL; + } + if (e == 0 && mant == 0) { + /* zero */ + *pres = 0; + return js_bigint_new_si(ctx, 0); + } + e -= 1023; + /* 0 < a < 1 : not an integer */ + if (e < 0) + goto not_an_integer; + mant |= (uint64_t)1 << 52; + if (e < 52) { + shift = 52 - e; + /* check that there is no fractional part */ + if (mant & (((uint64_t)1 << shift) - 1)) { + not_an_integer: + *pres = 1; + return NULL; + } + mant >>= shift; + e = 0; + } else { + e -= 52; + } + if (sgn) + mant = -mant; + /* the integer is mant*2^e */ + r = js_bigint_set_si64(&buf, (int64_t)mant); + *pres = 0; + return js_bigint_shl(ctx, r, e); +} + +/* return -1, 0, 1 or (2) (unordered) */ +static int js_bigint_float64_cmp(JSContext *ctx, const JSBigInt *a, + double b) +{ + int b_sign, a_sign, e, f; + uint64_t mant, b1, a_mant; + + b1 = float64_as_uint64(b); + b_sign = b1 >> 63; + e = (b1 >> 52) & ((1 << 11) - 1); + mant = b1 & (((uint64_t)1 << 52) - 1); + a_sign = js_bigint_sign(a); + if (e == 2047) { + if (mant != 0) { + /* NaN */ + return 2; + } else { + /* +/- infinity */ + return 2 * b_sign - 1; + } + } else if (e == 0 && mant == 0) { + /* b = +/-0 */ + if (a->len == 1 && a->tab[0] == 0) + return 0; + else + return 1 - 2 * a_sign; + } else if (a->len == 1 && a->tab[0] == 0) { + /* a = 0, b != 0 */ + return 2 * b_sign - 1; + } else if (a_sign != b_sign) { + return 1 - 2 * a_sign; + } else { + e -= 1023; + /* Note: handling denormals is not necessary because we + compare to integers hence f >= 0 */ + /* compute f so that 2^f <= abs(a) < 2^(f+1) */ + a_mant = js_bigint_get_mant_exp(ctx, &f, a); + if (f != e) { + if (f < e) + return -1; + else + return 1; + } else { + mant = (mant | ((uint64_t)1 << 52)) << 11; /* align to a_mant */ + if (a_mant < mant) + return 2 * a_sign - 1; + else if (a_mant > mant) + return 1 - 2 * a_sign; + else + return 0; + } + } +} + +/* return -1, 0 or 1 */ +static int js_bigint_cmp(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b) +{ + int a_sign, b_sign, res, i; + a_sign = js_bigint_sign(a); + b_sign = js_bigint_sign(b); + if (a_sign != b_sign) { + res = 1 - 2 * a_sign; + } else { + /* we assume the numbers are normalized */ + if (a->len != b->len) { + if (a->len < b->len) + res = 2 * a_sign - 1; + else + res = 1 - 2 * a_sign; + } else { + res = 0; + for(i = a->len -1; i >= 0; i--) { + if (a->tab[i] != b->tab[i]) { + if (a->tab[i] < b->tab[i]) + res = -1; + else + res = 1; + break; + } + } + } + } + return res; +} + +/* contains 10^i */ +static const js_limb_t js_pow_dec[JS_LIMB_DIGITS + 1] = { + 1U, + 10U, + 100U, + 1000U, + 10000U, + 100000U, + 1000000U, + 10000000U, + 100000000U, + 1000000000U, +#if JS_LIMB_BITS == 64 + 10000000000U, + 100000000000U, + 1000000000000U, + 10000000000000U, + 100000000000000U, + 1000000000000000U, + 10000000000000000U, + 100000000000000000U, + 1000000000000000000U, + 10000000000000000000U, +#endif +}; + +/* syntax: [-]digits in base radix. Return NULL if memory error. radix + = 10, 2, 8 or 16. */ +static JSBigInt *js_bigint_from_string(JSContext *ctx, + const char *str, int radix) +{ + const char *p = str; + size_t n_digits1; + int is_neg, n_digits, n_limbs, len, log2_radix, n_bits, i; + JSBigInt *r; + js_limb_t v, c, h; + + is_neg = 0; + if (*p == '-') { + is_neg = 1; + p++; + } + while (*p == '0') + p++; + n_digits1 = strlen(p); + /* the real check for overflox is done js_bigint_new(). Here + we just avoid integer overflow */ + if (n_digits1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) { + JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); + return NULL; + } + n_digits = n_digits1; + log2_radix = 32 - clz32(radix - 1); /* ceil(log2(radix)) */ + /* compute the maximum number of limbs */ + if (radix == 10) { + n_bits = (n_digits * 27 + 7) / 8; /* >= ceil(n_digits * log2(10)) */ + } else { + n_bits = n_digits * log2_radix; + } + /* we add one extra bit for the sign */ + n_limbs = max_int(1, n_bits / JS_LIMB_BITS + 1); + r = js_bigint_new(ctx, n_limbs); + if (!r) + return NULL; + if (radix == 10) { + int digits_per_limb = JS_LIMB_DIGITS; + len = 1; + r->tab[0] = 0; + for(;;) { + /* XXX: slow */ + v = 0; + for(i = 0; i < digits_per_limb; i++) { + c = to_digit(*p); + if (c >= radix) + break; + p++; + v = v * 10 + c; + } + if (i == 0) + break; + if (len == 1 && r->tab[0] == 0) { + r->tab[0] = v; + } else { + h = mp_mul1(r->tab, r->tab, len, js_pow_dec[i], v); + if (h != 0) { + r->tab[len++] = h; + } + } + } + /* add one extra limb to have the correct sign*/ + if ((r->tab[len - 1] >> (JS_LIMB_BITS - 1)) != 0) + r->tab[len++] = 0; + r->len = len; + } else { + unsigned int bit_pos, shift, pos; + + /* power of two base: no multiplication is needed */ + r->len = n_limbs; + memset(r->tab, 0, sizeof(r->tab[0]) * n_limbs); + for(i = 0; i < n_digits; i++) { + c = to_digit(p[n_digits - 1 - i]); + assert(c < radix); + bit_pos = i * log2_radix; + shift = bit_pos & (JS_LIMB_BITS - 1); + pos = bit_pos / JS_LIMB_BITS; + r->tab[pos] |= c << shift; + /* if log2_radix does not divide JS_LIMB_BITS, needed an + additional op */ + if (shift + log2_radix > JS_LIMB_BITS) { + r->tab[pos + 1] |= c >> (JS_LIMB_BITS - shift); + } + } + } + r = js_bigint_normalize(ctx, r); + /* XXX: could do it in place */ + if (is_neg) { + JSBigInt *r1; + r1 = js_bigint_neg(ctx, r); + js_free(ctx, r); + r = r1; + } + return r; +} + +/* 2 <= base <= 36 */ +static char const digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +/* special version going backwards */ +/* XXX: use dtoa.c */ +static char *js_u64toa(char *q, int64_t n, unsigned int base) +{ + int digit; + if (base == 10) { + /* division by known base uses multiplication */ + do { + digit = (uint64_t)n % 10; + n = (uint64_t)n / 10; + *--q = '0' + digit; + } while (n != 0); + } else { + do { + digit = (uint64_t)n % base; + n = (uint64_t)n / base; + *--q = digits[digit]; + } while (n != 0); + } + return q; +} + +/* len >= 1. 2 <= radix <= 36 */ +static char *limb_to_a(char *q, js_limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ + /* XXX: optimize */ + for(i = 0; i < len; i++) { + digit = (js_limb_t)n % 10; + n = (js_limb_t)n / 10; + *--q = digit + '0'; + } + } else { + for(i = 0; i < len; i++) { + digit = (js_limb_t)n % radix; + n = (js_limb_t)n / radix; + *--q = digits[digit]; + } + } + return q; +} + +#define JS_RADIX_MAX 36 + +static const uint8_t digits_per_limb_table[JS_RADIX_MAX - 1] = { +#if JS_LIMB_BITS == 32 +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +#else +64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, +#endif +}; + +static const js_limb_t radix_base_table[JS_RADIX_MAX - 1] = { +#if JS_LIMB_BITS == 32 + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +#else + 0x0000000000000000, 0xa8b8b452291fe821, 0x0000000000000000, 0x6765c793fa10079d, + 0x41c21cb8e1000000, 0x3642798750226111, 0x8000000000000000, 0xa8b8b452291fe821, + 0x8ac7230489e80000, 0x4d28cb56c33fa539, 0x1eca170c00000000, 0x780c7372621bd74d, + 0x1e39a5057d810000, 0x5b27ac993df97701, 0x0000000000000000, 0x27b95e997e21d9f1, + 0x5da0e1e53c5c8000, 0xd2ae3299c1c4aedb, 0x16bcc41e90000000, 0x2d04b7fdd9c0ef49, + 0x5658597bcaa24000, 0xa0e2073737609371, 0x0c29e98000000000, 0x14adf4b7320334b9, + 0x226ed36478bfa000, 0x383d9170b85ff80b, 0x5a3c23e39c000000, 0x8e65137388122bcd, + 0xdd41bb36d259e000, 0x0aee5720ee830681, 0x1000000000000000, 0x172588ad4f5f0981, + 0x211e44f7d02c1000, 0x2ee56725f06e5c71, 0x41c21cb8e1000000, +#endif +}; + +static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { + char buf[66]; + int len; + len = i64toa_radix(buf, JS_VALUE_GET_SHORT_BIG_INT(val), radix); + return js_new_string8_len(ctx, buf, len); + } else { + JSBigInt *r, *tmp = NULL; + char *buf, *q, *buf_end; + int is_neg, n_bits, log2_radix, n_digits; + BOOL is_binary_radix; + JSValue res; + + assert(JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT); + r = JS_VALUE_GET_PTR(val); + if (r->len == 1 && r->tab[0] == 0) { + /* '0' case */ + return js_new_string8_len(ctx, "0", 1); + } + is_binary_radix = ((radix & (radix - 1)) == 0); + is_neg = js_bigint_sign(r); + if (is_neg) { + tmp = js_bigint_neg(ctx, r); + if (!tmp) + return JS_EXCEPTION; + r = tmp; + } else if (!is_binary_radix) { + /* need to modify 'r' */ + tmp = js_bigint_new(ctx, r->len); + if (!tmp) + return JS_EXCEPTION; + memcpy(tmp->tab, r->tab, r->len * sizeof(r->tab[0])); + r = tmp; + } + log2_radix = 31 - clz32(radix); /* floor(log2(radix)) */ + n_bits = r->len * JS_LIMB_BITS - js_limb_safe_clz(r->tab[r->len - 1]); + /* n_digits is exact only if radix is a power of + two. Otherwise it is >= the exact number of digits */ + n_digits = (n_bits + log2_radix - 1) / log2_radix; + /* XXX: could directly build the JSString */ + buf = js_malloc(ctx, n_digits + is_neg + 1); + if (!buf) { + js_free(ctx, tmp); + return JS_EXCEPTION; + } + q = buf + n_digits + is_neg + 1; + *--q = '\0'; + buf_end = q; + if (!is_binary_radix) { + int len; + js_limb_t radix_base, v; + radix_base = radix_base_table[radix - 2]; + len = r->len; + for(;;) { + /* remove leading zero limbs */ + while (len > 1 && r->tab[len - 1] == 0) + len--; + if (len == 1 && r->tab[0] < radix_base) { + v = r->tab[0]; + if (v != 0) { + q = js_u64toa(q, v, radix); + } + break; + } else { + v = mp_div1(r->tab, r->tab, len, radix_base, 0); + q = limb_to_a(q, v, radix, digits_per_limb_table[radix - 2]); + } + } + } else { + int i, shift; + unsigned int bit_pos, pos, c; + + /* radix is a power of two */ + for(i = 0; i < n_digits; i++) { + bit_pos = i * log2_radix; + pos = bit_pos / JS_LIMB_BITS; + shift = bit_pos % JS_LIMB_BITS; + c = r->tab[pos] >> shift; + if ((shift + log2_radix) > JS_LIMB_BITS && + (pos + 1) < r->len) { + c |= r->tab[pos + 1] << (JS_LIMB_BITS - shift); + } + c &= (radix - 1); + *--q = digits[c]; + } + } + if (is_neg) + *--q = '-'; + js_free(ctx, tmp); + res = js_new_string8_len(ctx, q, buf_end - q); + js_free(ctx, buf); + return res; + } +} + +/* if possible transform a BigInt to short big and free it, otherwise + return a normal bigint */ +static JSValue JS_CompactBigInt(JSContext *ctx, JSBigInt *p) +{ + JSValue res; + if (p->len == 1) { + res = __JS_NewShortBigInt(ctx, (js_slimb_t)p->tab[0]); + js_free(ctx, p); + return res; + } else { + return JS_MKPTR(JS_TAG_BIG_INT, p); + } +} + +#define ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define ATOD_ACCEPT_BIN_OCT (1 << 2) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4) +/* accept _ between digits as a digit separator */ +#define ATOD_ACCEPT_UNDERSCORES (1 << 5) +/* allow a suffix to override the type */ +#define ATOD_ACCEPT_SUFFIX (1 << 6) +/* default type */ +#define ATOD_TYPE_MASK (3 << 7) +#define ATOD_TYPE_FLOAT64 (0 << 7) +#define ATOD_TYPE_BIG_INT (1 << 7) +/* accept -0x1 */ +#define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) + +/* return an exception in case of memory error. Return JS_NAN if + invalid syntax */ +/* XXX: directly use js_atod() */ +static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, + int radix, int flags) +{ + const char *p, *p_start; + int sep, is_neg; + BOOL is_float, has_legacy_octal; + int atod_type = flags & ATOD_TYPE_MASK; + char buf1[64], *buf; + int i, j, len; + BOOL buf_allocated = FALSE; + JSValue val; + JSATODTempMem atod_mem; + + /* optional separator between digits */ + sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + has_legacy_octal = FALSE; + + p = str; + p_start = p; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start++; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; + } else if (p[0] == '-') { + p++; + p_start++; + is_neg = 1; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; + } + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + has_legacy_octal = TRUE; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + no_radix_prefix: + if (!(flags & ATOD_INT_ONLY) && + (atod_type == ATOD_TYPE_FLOAT64) && + strstart(p, "Infinity", &p)) { + double d = 1.0 / 0.0; + if (is_neg) + d = -d; + val = JS_NewFloat64(ctx, d); + goto done; + } + } + if (radix == 0) + radix = 10; + is_float = FALSE; + p_start = p; + while (to_digit((uint8_t)*p) < radix + || (*p == sep && (radix != 10 || + p != p_start + 1 || p[-1] != '0') && + to_digit((uint8_t)p[1]) < radix)) { + p++; + } + if (!(flags & ATOD_INT_ONLY)) { + if (*p == '.' && (p > p_start || to_digit((uint8_t)p[1]) < radix)) { + is_float = TRUE; + p++; + if (*p == sep) + goto fail; + while (to_digit((uint8_t)*p) < radix || + (*p == sep && to_digit((uint8_t)p[1]) < radix)) + p++; + } + if (p > p_start && + (((*p == 'e' || *p == 'E') && radix == 10) || + ((*p == 'p' || *p == 'P') && (radix == 2 || radix == 8 || radix == 16)))) { + const char *p1 = p + 1; + is_float = TRUE; + if (*p1 == '+') { + p1++; + } else if (*p1 == '-') { + p1++; + } + if (is_digit((uint8_t)*p1)) { + p = p1 + 1; + while (is_digit((uint8_t)*p) || (*p == sep && is_digit((uint8_t)p[1]))) + p++; + } + } + } + if (p == p_start) + goto fail; + + buf = buf1; + buf_allocated = FALSE; + len = p - p_start; + if (unlikely((len + 2) > sizeof(buf1))) { + buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */ + if (!buf) + goto mem_error; + buf_allocated = TRUE; + } + /* remove the separators and the radix prefixes */ + j = 0; + if (is_neg) + buf[j++] = '-'; + for (i = 0; i < len; i++) { + if (p_start[i] != '_') + buf[j++] = p_start[i]; + } + buf[j] = '\0'; + + if (flags & ATOD_ACCEPT_SUFFIX) { + if (*p == 'n') { + p++; + atod_type = ATOD_TYPE_BIG_INT; + } else { + if (is_float && radix != 10) + goto fail; + } + } else { + if (atod_type == ATOD_TYPE_FLOAT64) { + if (is_float && radix != 10) + goto fail; + } + } + + switch(atod_type) { + case ATOD_TYPE_FLOAT64: + { + double d; + d = js_atod(buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, + &atod_mem); + /* return int or float64 */ + val = JS_NewFloat64(ctx, d); + } + break; + case ATOD_TYPE_BIG_INT: + { + JSBigInt *r; + if (has_legacy_octal || is_float) + goto fail; + r = js_bigint_from_string(ctx, buf, radix); + if (!r) { + val = JS_EXCEPTION; + goto done; + } + val = JS_CompactBigInt(ctx, r); + } + break; + default: + abort(); + } + +done: + if (buf_allocated) + js_free_rt(ctx->rt, buf); + if (pp) + *pp = p; + return val; + fail: + val = JS_NAN; + goto done; + mem_error: + val = JS_ThrowOutOfMemory(ctx); + goto done; +} + +typedef enum JSToNumberHintEnum { + TON_FLAG_NUMBER, + TON_FLAG_NUMERIC, +} JSToNumberHintEnum; + +static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val, + JSToNumberHintEnum flag) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_BIG_INT: + case JS_TAG_SHORT_BIG_INT: + if (flag != TON_FLAG_NUMERIC) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert bigint to number"); + } + ret = val; + break; + case JS_TAG_FLOAT64: + case JS_TAG_INT: + case JS_TAG_EXCEPTION: + ret = val; + break; + case JS_TAG_BOOL: + case JS_TAG_NULL: + ret = JS_NewInt32(ctx, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_UNDEFINED: + ret = JS_NAN; + break; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return JS_EXCEPTION; + goto redo; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + { + const char *str; + const char *p; + size_t len; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + ret = JS_NewInt32(ctx, 0); + } else { + int flags = ATOD_ACCEPT_BIN_OCT; + ret = js_atof(ctx, p, &p, 0, flags); + if (!JS_IsException(ret)) { + p += skip_spaces(p); + if ((p - str) != len) { + JS_FreeValue(ctx, ret); + ret = JS_NAN; + } + } + } + JS_FreeCString(ctx, str); + } + break; + case JS_TAG_SYMBOL: + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert symbol to number"); + default: + JS_FreeValue(ctx, val); + ret = JS_NAN; + break; + } + return ret; +} + +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER); +} + +static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC); +} + +static JSValue JS_ToNumeric(JSContext *ctx, JSValueConst val) +{ + return JS_ToNumericFree(ctx, JS_DupValue(ctx, val)); +} + +static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres, + JSValue val) +{ + double d; + uint32_t tag; + + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + goto fail; + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + d = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + d = JS_VALUE_GET_FLOAT64(val); + break; + default: + abort(); + } + *pres = d; + return 0; + fail: + *pres = JS_FLOAT64_NAN; + return -1; +} + +static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_TAG(val); + if (tag <= JS_TAG_NULL) { + *pres = JS_VALUE_GET_INT(val); + return 0; + } else if (JS_TAG_IS_FLOAT64(tag)) { + *pres = JS_VALUE_GET_FLOAT64(val); + return 0; + } else { + return __JS_ToFloat64Free(ctx, pres, val); + } +} + +int JS_ToFloat64(JSContext *ctx, double *pres, JSValueConst val) +{ + return JS_ToFloat64Free(ctx, pres, JS_DupValue(ctx, val)); +} + +static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val) +{ + return JS_ToNumberFree(ctx, JS_DupValue(ctx, val)); +} + +/* same as JS_ToNumber() but return 0 in case of NaN/Undefined */ +static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_NewInt32(ctx, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = JS_NewInt32(ctx, 0); + } else { + /* convert -0 to +0 */ + d = trunc(d) + 0.0; + ret = JS_NewFloat64(ctx, d); + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return val; + goto redo; + } + return ret; +} + +/* Note: the integer value is satured to 32 bits */ +static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val) +{ + uint32_t tag; + int ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = 0; + } else { + if (d < INT32_MIN) + ret = INT32_MIN; + else if (d > INT32_MAX) + ret = INT32_MAX; + else + ret = (int)d; + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValueConst val) +{ + return JS_ToInt32SatFree(ctx, pres, JS_DupValue(ctx, val)); +} + +int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValueConst val, + int min, int max, int min_offset) +{ + int res = JS_ToInt32SatFree(ctx, pres, JS_DupValue(ctx, val)); + if (res == 0) { + if (*pres < min) { + *pres += min_offset; + if (*pres < min) + *pres = min; + } else { + if (*pres > max) + *pres = max; + } + } + return res; +} + +static int JS_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + *pres = JS_VALUE_GET_INT(val); + return 0; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + *pres = 0; + } else { + if (d < INT64_MIN) + *pres = INT64_MIN; + else if (d >= 0x1p63) /* must use INT64_MAX + 1 because INT64_MAX cannot be exactly represented as a double */ + *pres = INT64_MAX; + else + *pres = (int64_t)d; + } + } + return 0; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } +} + +int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToInt64SatFree(ctx, pres, JS_DupValue(ctx, val)); +} + +int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValueConst val, + int64_t min, int64_t max, int64_t neg_offset) +{ + int res = JS_ToInt64SatFree(ctx, pres, JS_DupValue(ctx, val)); + if (res == 0) { + if (*pres < 0) + *pres += neg_offset; + if (*pres < min) + *pres = min; + else if (*pres > max) + *pres = max; + } + return res; +} + +/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0) + in case of exception */ +static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + int64_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^64) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 62))) { + /* fast case */ + ret = (int64_t)d; + } else if (e <= (1023 + 62 + 53)) { + uint64_t v; + /* remainder modulo 2^64 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + ret = v << ((e - 1023) - 52); + /* take the sign into account */ + if (u.u64 >> 63) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToInt64Free(ctx, pres, JS_DupValue(ctx, val)); +} + +int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + if (JS_IsBigInt(ctx, val)) + return JS_ToBigInt64(ctx, pres, val); + else + return JS_ToInt64(ctx, pres, val); +} + +/* return (<0, 0) in case of exception */ +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int32_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^32) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 30))) { + /* fast case */ + ret = (int32_t)d; + } else if (e <= (1023 + 30 + 53)) { + uint64_t v; + /* remainder modulo 2^32 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + v = v << ((e - 1023) - 52 + 32); + ret = v >> 32; + /* take the sign into account */ + if (u.u64 >> 63) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val) +{ + return JS_ToInt32Free(ctx, pres, JS_DupValue(ctx, val)); +} + +static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val) +{ + return JS_ToInt32Free(ctx, (int32_t *)pres, val); +} + +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int res; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = JS_VALUE_GET_INT(val); + res = max_int(0, min_int(255, res)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + res = 0; + } else { + if (d < 0) + res = 0; + else if (d > 255) + res = 255; + else + res = lrint(d); + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = res; + return 0; +} + +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, BOOL is_array_ctor) +{ + uint32_t tag, len; + + tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + { + int v; + v = JS_VALUE_GET_INT(val); + if (v < 0) + goto fail; + len = v; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d; + d = JS_VALUE_GET_FLOAT64(val); + if (!(d >= 0 && d <= UINT32_MAX)) + goto fail; + len = (uint32_t)d; + if (len != d) + goto fail; + } else { + uint32_t len1; + + if (is_array_ctor) { + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len, val, TRUE)) + return -1; + } else { + /* legacy behavior: must do the conversion twice and compare */ + if (JS_ToUint32(ctx, &len, val)) { + JS_FreeValue(ctx, val); + return -1; + } + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len1, val, FALSE)) + return -1; + if (len1 != len) { + fail: + JS_ThrowRangeError(ctx, "invalid array length"); + return -1; + } + } + } + break; + } + *plen = len; + return 0; +} + +#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) + +static BOOL is_safe_integer(double d) +{ + return isfinite(d) && floor(d) == d && + fabs(d) <= (double)MAX_SAFE_INTEGER; +} + +int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValueConst val) +{ + int64_t v; + if (JS_ToInt64Sat(ctx, &v, val)) + return -1; + if (v < 0 || v > MAX_SAFE_INTEGER) { + JS_ThrowRangeError(ctx, "invalid array index"); + *plen = 0; + return -1; + } + *plen = v; + return 0; +} + +/* convert a value to a length between 0 and MAX_SAFE_INTEGER. + return -1 for exception */ +static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen, + JSValue val) +{ + int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); + JS_FreeValue(ctx, val); + return res; +} + +/* Note: can return an exception */ +static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val) +{ + double d; + if (!JS_IsNumber(val)) + return FALSE; + if (unlikely(JS_ToFloat64(ctx, &d, val))) + return -1; + return isfinite(d) && floor(d) == d; +} + +static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + { + int v; + v = JS_VALUE_GET_INT(val); + return (v < 0); + } + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + u.d = JS_VALUE_GET_FLOAT64(val); + return (u.u64 >> 63); + } + case JS_TAG_SHORT_BIG_INT: + return (JS_VALUE_GET_SHORT_BIG_INT(val) < 0); + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + return js_bigint_sign(p); + } + default: + return FALSE; + } +} + +static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val) +{ + return js_bigint_to_string1(ctx, val, 10); +} + +static JSValue js_dtoa2(JSContext *ctx, + double d, int radix, int n_digits, int flags) +{ + char static_buf[128], *buf, *tmp_buf; + int len, len_max; + JSValue res; + JSDTOATempMem dtoa_mem; + len_max = js_dtoa_max_len(d, radix, n_digits, flags); + + /* longer buffer may be used if radix != 10 */ + if (len_max > sizeof(static_buf) - 1) { + tmp_buf = js_malloc(ctx, len_max + 1); + if (!tmp_buf) + return JS_EXCEPTION; + buf = tmp_buf; + } else { + tmp_buf = NULL; + buf = static_buf; + } + len = js_dtoa(buf, d, radix, n_digits, flags, &dtoa_mem); + res = js_new_string8_len(ctx, buf, len); + js_free(ctx, tmp_buf); + return res; +} + +static JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToPropertyKey) +{ + uint32_t tag; + char buf[32]; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_STRING: + return JS_DupValue(ctx, val); + case JS_TAG_STRING_ROPE: + return js_linearize_string_rope(ctx, JS_DupValue(ctx, val)); + case JS_TAG_INT: + { + size_t len; + len = i32toa(buf, JS_VALUE_GET_INT(val)); + return js_new_string8_len(ctx, buf, len); + } + break; + case JS_TAG_BOOL: + return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? + JS_ATOM_true : JS_ATOM_false); + case JS_TAG_NULL: + return JS_AtomToString(ctx, JS_ATOM_null); + case JS_TAG_UNDEFINED: + return JS_AtomToString(ctx, JS_ATOM_undefined); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_OBJECT: + { + JSValue val1, ret; + val1 = JS_ToPrimitive(ctx, val, HINT_STRING); + if (JS_IsException(val1)) + return val1; + ret = JS_ToStringInternal(ctx, val1, is_ToPropertyKey); + JS_FreeValue(ctx, val1); + return ret; + } + break; + case JS_TAG_FUNCTION_BYTECODE: + return js_new_string8(ctx, "[function bytecode]"); + case JS_TAG_SYMBOL: + if (is_ToPropertyKey) { + return JS_DupValue(ctx, val); + } else { + return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); + } + case JS_TAG_FLOAT64: + return js_dtoa2(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0, + JS_DTOA_FORMAT_FREE); + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + return js_bigint_to_string(ctx, val); + default: + return js_new_string8(ctx, "[unsupported type]"); + } +} + +JSValue JS_ToString(JSContext *ctx, JSValueConst val) +{ + return JS_ToStringInternal(ctx, val, FALSE); +} + +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val) +{ + JSValue ret; + ret = JS_ToString(ctx, val); + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val) +{ + if (JS_IsUndefined(val) || JS_IsNull(val)) + return JS_ToStringFree(ctx, val); + return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL); +} + +JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val) +{ + return JS_ToStringInternal(ctx, val, TRUE); +} + +static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValueConst val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return JS_ThrowTypeError(ctx, "null or undefined are forbidden"); + return JS_ToString(ctx, val); +} + +#define JS_PRINT_MAX_DEPTH 8 + +typedef struct { + JSRuntime *rt; + JSContext *ctx; /* may be NULL */ + JSPrintValueOptions options; + JSPrintValueWrite *write_func; + void *write_opaque; + int level; + JSObject *print_stack[JS_PRINT_MAX_DEPTH]; /* level values */ +} JSPrintValueState; + +static void js_print_value(JSPrintValueState *s, JSValueConst val); + +static void js_putc(JSPrintValueState *s, char c) +{ + s->write_func(s->write_opaque, &c, 1); +} + +static void js_puts(JSPrintValueState *s, const char *str) +{ + s->write_func(s->write_opaque, str, strlen(str)); +} + +static void __attribute__((format(printf, 2, 3))) js_printf(JSPrintValueState *s, const char *fmt, ...) +{ + va_list ap; + char buf[256]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + s->write_func(s->write_opaque, buf, strlen(buf)); +} + +static void js_print_float64(JSPrintValueState *s, double d) +{ + JSDTOATempMem dtoa_mem; + char buf[32]; + int len; + len = js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &dtoa_mem); + s->write_func(s->write_opaque, buf, len); +} + +static uint32_t js_string_get_length(JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + return p->len; + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_PTR(val); + return r->len; + } else { + return 0; + } +} + +/* pretty print the first 'len' characters of 'p' */ +static void js_print_string1(JSPrintValueState *s, JSString *p, int len, int sep) +{ + uint8_t buf[UTF8_CHAR_LEN_MAX]; + int l, i, c, c1; + + for(i = 0; i < len; i++) { + c = string_get(p, i); + switch(c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\\': + quote: + js_putc(s, '\\'); + js_putc(s, c); + break; + default: + if (c == sep) + goto quote; + if (c >= 32 && c <= 126) { + js_putc(s, c); + } else if (c < 32 || + (c >= 0x7f && c <= 0x9f)) { + escape: + js_printf(s, "\\u%04x", c); + } else { + if (is_hi_surrogate(c)) { + if ((i + 1) >= len) + goto escape; + c1 = string_get(p, i + 1); + if (!is_lo_surrogate(c1)) + goto escape; + i++; + c = from_surrogate(c, c1); + } else if (is_lo_surrogate(c)) { + goto escape; + } + l = unicode_to_utf8(buf, c); + s->write_func(s->write_opaque, (char *)buf, l); + } + break; + } + } +} + +static void js_print_string_rec(JSPrintValueState *s, JSValueConst val, + int sep, uint32_t pos) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + uint32_t len; + if (pos < s->options.max_string_length) { + len = min_uint32(p->len, s->options.max_string_length - pos); + js_print_string1(s, p, len, sep); + } + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_PTR(val); + js_print_string_rec(s, r->left, sep, pos); + js_print_string_rec(s, r->right, sep, pos + js_string_get_length(r->left)); + } else { + js_printf(s, "", (int)JS_VALUE_GET_TAG(val)); + } +} + +static void js_print_string(JSPrintValueState *s, JSValueConst val) +{ + int sep; + if (s->options.raw_dump && JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + JSString *p = JS_VALUE_GET_STRING(val); + js_printf(s, "%d", p->header.ref_count); + sep = (p->header.ref_count == 1) ? '\"' : '\''; + } else { + sep = '\"'; + } + js_putc(s, sep); + js_print_string_rec(s, val, sep, 0); + js_putc(s, sep); + if (js_string_get_length(val) > s->options.max_string_length) { + uint32_t n = js_string_get_length(val) - s->options.max_string_length; + js_printf(s, "... %u more character%s", n, n > 1 ? "s" : ""); + } +} + +static void js_print_raw_string(JSPrintValueState *s, JSValueConst val) +{ + const char *cstr; + size_t len; + cstr = JS_ToCStringLen(s->ctx, &len, val); + if (cstr) { + s->write_func(s->write_opaque, cstr, len); + JS_FreeCString(s->ctx, cstr); + } +} + +static BOOL is_ascii_ident(const JSString *p) +{ + int i, c; + + if (p->len == 0) + return FALSE; + for(i = 0; i < p->len; i++) { + c = string_get(p, i); + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) + return FALSE; + } + return TRUE; +} + +static void js_print_atom(JSPrintValueState *s, JSAtom atom) +{ + int i; + if (__JS_AtomIsTaggedInt(atom)) { + js_printf(s, "%u", __JS_AtomToUInt32(atom)); + } else if (atom == JS_ATOM_NULL) { + js_puts(s, ""); + } else { + assert(atom < s->rt->atom_size); + JSString *p; + p = s->rt->atom_array[atom]; + if (is_ascii_ident(p)) { + for(i = 0; i < p->len; i++) { + js_putc(s, string_get(p, i)); + } + } else { + js_putc(s, '"'); + js_print_string1(s, p, p->len, '\"'); + js_putc(s, '"'); + } + } +} + +/* return 0 if invalid length */ +static uint32_t js_print_array_get_length(JSObject *p) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValueConst val; + + prs = find_own_property(&pr, p, JS_ATOM_length); + if (!prs) + return 0; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return 0; + val = pr->u.value; + switch(JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_INT: + return JS_VALUE_GET_INT(val); + case JS_TAG_FLOAT64: + return (uint32_t)JS_VALUE_GET_FLOAT64(val); + default: + return 0; + } +} + +static void js_print_comma(JSPrintValueState *s, int *pcomma_state) +{ + switch(*pcomma_state) { + case 0: + break; + case 1: + js_printf(s, ", "); + break; + case 2: + js_printf(s, " { "); + break; + } + *pcomma_state = 1; +} + +static void js_print_more_items(JSPrintValueState *s, int *pcomma_state, + uint32_t n) +{ + js_print_comma(s, pcomma_state); + js_printf(s, "... %u more item%s", n, n > 1 ? "s" : ""); +} + +/* similar to js_regexp_toString() but without side effect */ +static void js_print_regexp(JSPrintValueState *s, JSObject *p1) +{ + JSRegExp *re = &p1->u.regexp; + JSString *p; + int i, n, c, c2, bra, flags; + static const char regexp_flags[] = { 'g', 'i', 'm', 's', 'u', 'y', 'd', 'v' }; + + if (!re->pattern || !re->bytecode) { + /* the regexp fields are zeroed at init */ + js_puts(s, "[uninitialized_regexp]"); + return; + } + p = re->pattern; + js_putc(s, '/'); + if (p->len == 0) { + js_puts(s, "(?:)"); + } else { + bra = 0; + for (i = 0, n = p->len; i < n;) { + c2 = -1; + switch (c = string_get(p, i++)) { + case '\\': + if (i < n) + c2 = string_get(p, i++); + break; + case ']': + bra = 0; + break; + case '[': + if (!bra) { + if (i < n && string_get(p, i) == ']') + c2 = string_get(p, i++); + bra = 1; + } + break; + case '\n': + c = '\\'; + c2 = 'n'; + break; + case '\r': + c = '\\'; + c2 = 'r'; + break; + case '/': + if (!bra) { + c = '\\'; + c2 = '/'; + } + break; + } + js_putc(s, c); + if (c2 >= 0) + js_putc(s, c2); + } + } + js_putc(s, '/'); + + flags = lre_get_flags(re->bytecode->u.str8); + for(i = 0; i < countof(regexp_flags); i++) { + if ((flags >> i) & 1) { + js_putc(s, regexp_flags[i]); + } + } +} + +/* similar to js_error_toString() but without side effect */ +static void js_print_error(JSPrintValueState *s, JSObject *p) +{ + const char *str; + size_t len; + + str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_name); + if (!str) { + js_puts(s, "Error"); + } else { + js_puts(s, str); + JS_FreeCString(s->ctx, str); + } + + str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_message); + if (str && str[0] != '\0') { + js_puts(s, ": "); + js_puts(s, str); + } + JS_FreeCString(s->ctx, str); + + /* dump the stack if present */ + str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_stack); + if (str) { + js_putc(s, '\n'); + + /* XXX: should remove the last '\n' in stack as + v8. SpiderMonkey does not do it */ + len = strlen(str); + if (len > 0 && str[len - 1] == '\n') + len--; + s->write_func(s->write_opaque, str, len); + + JS_FreeCString(s->ctx, str); + } +} + +static void js_print_object(JSPrintValueState *s, JSObject *p) +{ + JSRuntime *rt = s->rt; + JSShape *sh; + JSShapeProperty *prs; + JSProperty *pr; + int comma_state; + BOOL is_array; + uint32_t i; + + comma_state = 0; + is_array = FALSE; + if (p->class_id == JS_CLASS_ARRAY) { + is_array = TRUE; + js_printf(s, "[ "); + /* XXX: print array like properties even if not fast array */ + if (p->fast_array) { + uint32_t len, n, len1; + len = js_print_array_get_length(p); + + len1 = min_uint32(p->u.array.count, s->options.max_item_count); + for(i = 0; i < len1; i++) { + js_print_comma(s, &comma_state); + js_print_value(s, p->u.array.u.values[i]); + } + if (len1 < p->u.array.count) + js_print_more_items(s, &comma_state, p->u.array.count - len1); + if (p->u.array.count < len) { + n = len - p->u.array.count; + js_print_comma(s, &comma_state); + js_printf(s, "<%u empty item%s>", n, n > 1 ? "s" : ""); + } + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + uint32_t size = 1 << typed_array_size_log2(p->class_id); + uint32_t len1; + int64_t v; + + js_print_atom(s, rt->class_array[p->class_id].class_name); + js_printf(s, "(%u) [ ", p->u.array.count); + + is_array = TRUE; + len1 = min_uint32(p->u.array.count, s->options.max_item_count); + for(i = 0; i < len1; i++) { + const uint8_t *ptr = p->u.array.u.uint8_ptr + i * size; + js_print_comma(s, &comma_state); + switch(p->class_id) { + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + v = *ptr; + goto ta_int64; + case JS_CLASS_INT8_ARRAY: + v = *(int8_t *)ptr; + goto ta_int64; + case JS_CLASS_INT16_ARRAY: + v = *(int16_t *)ptr; + goto ta_int64; + case JS_CLASS_UINT16_ARRAY: + v = *(uint16_t *)ptr; + goto ta_int64; + case JS_CLASS_INT32_ARRAY: + v = *(int32_t *)ptr; + goto ta_int64; + case JS_CLASS_UINT32_ARRAY: + v = *(uint32_t *)ptr; + goto ta_int64; + case JS_CLASS_BIG_INT64_ARRAY: + v = *(int64_t *)ptr; + ta_int64: + js_printf(s, "%" PRId64, v); + break; + case JS_CLASS_BIG_UINT64_ARRAY: + js_printf(s, "%" PRIu64, *(uint64_t *)ptr); + break; + case JS_CLASS_FLOAT16_ARRAY: + js_print_float64(s, fromfp16(*(uint16_t *)ptr)); + break; + case JS_CLASS_FLOAT32_ARRAY: + js_print_float64(s, *(float *)ptr); + break; + case JS_CLASS_FLOAT64_ARRAY: + js_print_float64(s, *(double *)ptr); + break; + } + } + if (len1 < p->u.array.count) + js_print_more_items(s, &comma_state, p->u.array.count - len1); + } else if (p->class_id == JS_CLASS_BYTECODE_FUNCTION || + (rt->class_array[p->class_id].call != NULL && + p->class_id != JS_CLASS_PROXY)) { + js_printf(s, "[Function"); + /* XXX: allow dump without ctx */ + if (!s->options.raw_dump && s->ctx) { + const char *func_name_str; + js_putc(s, ' '); + func_name_str = get_prop_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), JS_ATOM_name); + if (!func_name_str || func_name_str[0] == '\0') + js_puts(s, "(anonymous)"); + else + js_puts(s, func_name_str); + JS_FreeCString(s->ctx, func_name_str); + } + js_printf(s, "]"); + comma_state = 2; + } else if (p->class_id == JS_CLASS_MAP || p->class_id == JS_CLASS_SET) { + JSMapState *ms = p->u.opaque; + struct list_head *el; + + if (!ms) + goto default_obj; + js_print_atom(s, rt->class_array[p->class_id].class_name); + js_printf(s, "(%u) { ", ms->record_count); + i = 0; + list_for_each(el, &ms->records) { + JSMapRecord *mr = list_entry(el, JSMapRecord, link); + js_print_comma(s, &comma_state); + if (mr->empty) + continue; + js_print_value(s, mr->key); + if (p->class_id == JS_CLASS_MAP) { + js_printf(s, " => "); + js_print_value(s, mr->value); + } + i++; + if (i >= s->options.max_item_count) + break; + } + if (i < ms->record_count) + js_print_more_items(s, &comma_state, ms->record_count - i); + } else if (p->class_id == JS_CLASS_REGEXP && s->ctx) { + js_print_regexp(s, p); + comma_state = 2; + } else if (p->class_id == JS_CLASS_DATE && s->ctx) { + /* get_date_string() has no side effect */ + JSValue str = get_date_string(s->ctx, JS_MKPTR(JS_TAG_OBJECT, p), 0, NULL, 0x23); /* toISOString() */ + if (JS_IsException(str)) + goto default_obj; + js_print_raw_string(s, str); + JS_FreeValueRT(s->rt, str); + comma_state = 2; + } else if (p->class_id == JS_CLASS_ERROR && s->ctx) { + js_print_error(s, p); + comma_state = 2; + } else { + default_obj: + if (p->class_id != JS_CLASS_OBJECT) { + js_print_atom(s, rt->class_array[p->class_id].class_name); + js_printf(s, " "); + } + js_printf(s, "{ "); + } + + sh = p->shape; /* the shape can be NULL while freeing an object */ + if (sh) { + uint32_t j; + + j = 0; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->atom != JS_ATOM_NULL) { + if (!(prs->flags & JS_PROP_ENUMERABLE) && + !s->options.show_hidden) { + continue; + } + if (j < s->options.max_item_count) { + pr = &p->prop[i]; + js_print_comma(s, &comma_state); + js_print_atom(s, prs->atom); + js_printf(s, ": "); + + /* XXX: autoinit property */ + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (s->options.raw_dump) { + js_printf(s, "[Getter %p Setter %p]", + pr->u.getset.getter, pr->u.getset.setter); + } else { + if (pr->u.getset.getter && pr->u.getset.setter) { + js_printf(s, "[Getter/Setter]"); + } else if (pr->u.getset.setter) { + js_printf(s, "[Setter]"); + } else { + js_printf(s, "[Getter]"); + } + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (s->options.raw_dump) { + js_printf(s, "[varref %p]", (void *)pr->u.var_ref); + } else { + js_print_value(s, *pr->u.var_ref->pvalue); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + if (s->options.raw_dump) { + js_printf(s, "[autoinit %p %d %p]", + (void *)js_autoinit_get_realm(pr), + js_autoinit_get_id(pr), + (void *)pr->u.init.opaque); + } else { + /* XXX: could autoinit but need to restart + the iteration */ + js_printf(s, "[autoinit]"); + } + } else { + js_print_value(s, pr->u.value); + } + } + j++; + } + } + if (j > s->options.max_item_count) + js_print_more_items(s, &comma_state, j - s->options.max_item_count); + } + if (s->options.raw_dump && js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b = p->u.func.function_bytecode; + if (b->closure_var_count) { + JSVarRef **var_refs; + var_refs = p->u.func.var_refs; + + js_print_comma(s, &comma_state); + js_printf(s, "[[Closure]]: ["); + for(i = 0; i < b->closure_var_count; i++) { + if (i != 0) + js_printf(s, ", "); + js_print_value(s, var_refs[i]->value); + } + js_printf(s, " ]"); + } + if (p->u.func.home_object) { + js_print_comma(s, &comma_state); + js_printf(s, "[[HomeObject]]: "); + js_print_value(s, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); + } + } + + if (!is_array) { + if (comma_state != 2) { + js_printf(s, " }"); + } + } else { + js_printf(s, " ]"); + } +} + +static int js_print_stack_index(JSPrintValueState *s, JSObject *p) +{ + int i; + for(i = 0; i < s->level; i++) + if (s->print_stack[i] == p) + return i; + return -1; +} + +static void js_print_value(JSPrintValueState *s, JSValueConst val) +{ + uint32_t tag = JS_VALUE_GET_NORM_TAG(val); + const char *str; + + switch(tag) { + case JS_TAG_INT: + js_printf(s, "%d", JS_VALUE_GET_INT(val)); + break; + case JS_TAG_BOOL: + if (JS_VALUE_GET_BOOL(val)) + str = "true"; + else + str = "false"; + goto print_str; + case JS_TAG_NULL: + str = "null"; + goto print_str; + case JS_TAG_EXCEPTION: + str = "exception"; + goto print_str; + case JS_TAG_UNINITIALIZED: + str = "uninitialized"; + goto print_str; + case JS_TAG_UNDEFINED: + str = "undefined"; + print_str: + js_puts(s, str); + break; + case JS_TAG_FLOAT64: + js_print_float64(s, JS_VALUE_GET_FLOAT64(val)); + break; + case JS_TAG_SHORT_BIG_INT: + js_printf(s, "%" PRId64 "n", (int64_t)JS_VALUE_GET_SHORT_BIG_INT(val)); + break; + case JS_TAG_BIG_INT: + if (!s->options.raw_dump && s->ctx) { + JSValue str = js_bigint_to_string(s->ctx, val); + if (JS_IsException(str)) + goto raw_bigint; + js_print_raw_string(s, str); + js_putc(s, 'n'); + JS_FreeValueRT(s->rt, str); + } else { + JSBigInt *p; + int sgn, i; + raw_bigint: + p = JS_VALUE_GET_PTR(val); + /* In order to avoid allocations we just dump the limbs */ + sgn = js_bigint_sign(p); + if (sgn) + js_printf(s, "BigInt.asIntN(%d,", p->len * JS_LIMB_BITS); + js_printf(s, "0x"); + for(i = p->len - 1; i >= 0; i--) { + if (i != p->len - 1) + js_putc(s, '_'); +#if JS_LIMB_BITS == 32 + js_printf(s, "%08x", p->tab[i]); +#else + js_printf(s, "%016" PRIx64, p->tab[i]); +#endif + } + js_putc(s, 'n'); + if (sgn) + js_putc(s, ')'); + } + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + if (s->options.raw_dump && tag == JS_TAG_STRING_ROPE) { + JSStringRope *r = JS_VALUE_GET_STRING_ROPE(val); + js_printf(s, "[rope len=%d depth=%d]", r->len, r->depth); + } else { + js_print_string(s, val); + } + break; + case JS_TAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); + js_puts(s, "[bytecode "); + js_print_atom(s, b->func_name); + js_putc(s, ']'); + } + break; + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + int idx; + idx = js_print_stack_index(s, p); + if (idx >= 0) { + js_printf(s, "[circular %d]", idx); + } else if (s->level < s->options.max_depth) { + s->print_stack[s->level++] = p; + js_print_object(s, JS_VALUE_GET_OBJ(val)); + s->level--; + } else { + JSAtom atom = s->rt->class_array[p->class_id].class_name; + js_putc(s, '['); + js_print_atom(s, atom); + if (s->options.raw_dump) { + js_printf(s, " %p", (void *)p); + } + js_putc(s, ']'); + } + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + js_puts(s, "Symbol("); + js_print_atom(s, js_get_atom_index(s->rt, p)); + js_putc(s, ')'); + } + break; + case JS_TAG_MODULE: + js_puts(s, "[module]"); + break; + default: + js_printf(s, "[unknown tag %d]", tag); + break; + } +} + +void JS_PrintValueSetDefaultOptions(JSPrintValueOptions *options) +{ + memset(options, 0, sizeof(*options)); + options->max_depth = 2; + options->max_string_length = 1000; + options->max_item_count = 100; +} + +static void JS_PrintValueInternal(JSRuntime *rt, JSContext *ctx, + JSPrintValueWrite *write_func, void *write_opaque, + JSValueConst val, const JSPrintValueOptions *options) +{ + JSPrintValueState ss, *s = &ss; + if (options) + s->options = *options; + else + JS_PrintValueSetDefaultOptions(&s->options); + if (s->options.max_depth <= 0) + s->options.max_depth = JS_PRINT_MAX_DEPTH; + else + s->options.max_depth = min_int(s->options.max_depth, JS_PRINT_MAX_DEPTH); + if (s->options.max_string_length == 0) + s->options.max_string_length = UINT32_MAX; + if (s->options.max_item_count == 0) + s->options.max_item_count = UINT32_MAX; + s->rt = rt; + s->ctx = ctx; + s->write_func = write_func; + s->write_opaque = write_opaque; + s->level = 0; + js_print_value(s, val); +} + +void JS_PrintValueRT(JSRuntime *rt, JSPrintValueWrite *write_func, void *write_opaque, + JSValueConst val, const JSPrintValueOptions *options) +{ + JS_PrintValueInternal(rt, NULL, write_func, write_opaque, val, options); +} + +void JS_PrintValue(JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, + JSValueConst val, const JSPrintValueOptions *options) +{ + JS_PrintValueInternal(ctx->rt, ctx, write_func, write_opaque, val, options); +} + +static void js_dump_value_write(void *opaque, const char *buf, size_t len) +{ + FILE *fo = opaque; + fwrite(buf, 1, len, fo); +} + +static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) +{ + JSPrintValueState ss, *s = &ss; + memset(s, 0, sizeof(*s)); + s->rt = ctx->rt; + s->ctx = ctx; + s->write_func = js_dump_value_write; + s->write_opaque = stdout; + js_print_atom(s, atom); +} + +static __maybe_unused void JS_DumpAtom(JSContext *ctx, const char *str, JSAtom atom) +{ + printf("%s=", str); + print_atom(ctx, atom); + printf("\n"); +} + +static __maybe_unused void JS_DumpValue(JSContext *ctx, const char *str, JSValueConst val) +{ + printf("%s=", str); + JS_PrintValue(ctx, js_dump_value_write, stdout, val, NULL); + printf("\n"); +} + +static __maybe_unused void JS_DumpValueRT(JSRuntime *rt, const char *str, JSValueConst val) +{ + printf("%s=", str); + JS_PrintValueRT(rt, js_dump_value_write, stdout, val, NULL); + printf("\n"); +} + +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) +{ + printf("%14s %4s %4s %14s %s\n", + "ADDRESS", "REFS", "SHRF", "PROTO", "CONTENT"); +} + +/* for debug only: dump an object without side effect */ +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) +{ + JSShape *sh; + JSPrintValueOptions options; + + /* XXX: should encode atoms with special characters */ + sh = p->shape; /* the shape can be NULL while freeing an object */ + printf("%14p %4d ", + (void *)p, + p->header.ref_count); + if (sh) { + printf("%3d%c %14p ", + sh->header.ref_count, + " *"[sh->is_hashed], + (void *)sh->proto); + } else { + printf("%3s %14s ", "-", "-"); + } + + JS_PrintValueSetDefaultOptions(&options); + options.max_depth = 1; + options.show_hidden = TRUE; + options.raw_dump = TRUE; + JS_PrintValueRT(rt, js_dump_value_write, stdout, JS_MKPTR(JS_TAG_OBJECT, p), &options); + + printf("\n"); +} + +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) +{ + if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + JS_DumpObject(rt, (JSObject *)p); + } else { + printf("%14p %4d ", + (void *)p, + p->ref_count); + switch(p->gc_obj_type) { + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + printf("[function bytecode]"); + break; + case JS_GC_OBJ_TYPE_SHAPE: + printf("[shape]"); + break; + case JS_GC_OBJ_TYPE_VAR_REF: + printf("[var_ref]"); + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + printf("[async_function]"); + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + printf("[js_context]"); + break; + case JS_GC_OBJ_TYPE_MODULE: + printf("[module]"); + break; + default: + printf("[unknown %d]", p->gc_obj_type); + break; + } + printf("\n"); + } +} + +/* return -1 if exception (proxy case) or TRUE/FALSE */ +// TODO: should take flags to make proxy resolution and exceptions optional +int JS_IsArray(JSContext *ctx, JSValueConst val) +{ + if (js_resolve_proxy(ctx, &val, TRUE)) + return -1; + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(val); + return p->class_id == JS_CLASS_ARRAY; + } else { + return FALSE; + } +} + +static double js_pow(double a, double b) +{ + if (unlikely(!isfinite(b)) && fabs(a) == 1) { + /* not compatible with IEEE 754 */ + return JS_FLOAT64_NAN; + } else { + return pow(a, b); + } +} + +JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) +{ +#if JS_SHORT_BIG_INT_BITS == 64 + return __JS_NewShortBigInt(ctx, v); +#else + if (v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX) { + return __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *p; + p = js_bigint_new_si64(ctx, v); + if (!p) + return JS_EXCEPTION; + return JS_MKPTR(JS_TAG_BIG_INT, p); + } +#endif +} + +JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) +{ + if (v <= JS_SHORT_BIG_INT_MAX) { + return __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *p; + p = js_bigint_new_ui64(ctx, v); + if (!p) + return JS_EXCEPTION; + return JS_MKPTR(JS_TAG_BIG_INT, p); + } +} + +/* return NaN if bad bigint literal */ +static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) +{ + const char *str, *p; + size_t len; + int flags; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + val = JS_NewBigInt64(ctx, 0); + } else { + flags = ATOD_INT_ONLY | ATOD_ACCEPT_BIN_OCT | ATOD_TYPE_BIG_INT; + val = js_atof(ctx, p, &p, 0, flags); + p += skip_spaces(p); + if (!JS_IsException(val)) { + if ((p - str) != len) { + JS_FreeValue(ctx, val); + val = JS_NAN; + } + } + } + JS_FreeCString(ctx, str); + return val; +} + +static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val) +{ + val = JS_StringToBigInt(ctx, val); + if (JS_VALUE_IS_NAN(val)) + return JS_ThrowSyntaxError(ctx, "invalid bigint literal"); + return val; +} + +/* JS Numbers are not allowed */ +static JSValue JS_ToBigIntFree(JSContext *ctx, JSValue val) +{ + uint32_t tag; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + break; + case JS_TAG_INT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + case JS_TAG_FLOAT64: + goto fail; + case JS_TAG_BOOL: + val = __JS_NewShortBigInt(ctx, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + val = JS_StringToBigIntErr(ctx, val); + if (JS_IsException(val)) + return val; + goto redo; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return val; + goto redo; + default: + fail: + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert to bigint"); + } + return val; +} + +static JSValue JS_ToBigInt(JSContext *ctx, JSValueConst val) +{ + return JS_ToBigIntFree(ctx, JS_DupValue(ctx, val)); +} + +/* XXX: merge with JS_ToInt64Free with a specific flag ? */ +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint64_t res; + + val = JS_ToBigIntFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { + res = JS_VALUE_GET_SHORT_BIG_INT(val); + } else { + JSBigInt *p = JS_VALUE_GET_PTR(val); + /* return the value mod 2^64 */ + res = p->tab[0]; +#if JS_LIMB_BITS == 32 + if (p->len >= 2) + res |= (uint64_t)p->tab[1] << 32; +#endif + JS_FreeValue(ctx, val); + } + *pres = res; + return 0; +} + +int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToBigInt64Free(ctx, pres, JS_DupValue(ctx, val)); +} + +static no_inline __exception int js_unary_arith_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1; + int v; + uint32_t tag; + JSBigIntBuf buf1; + JSBigInt *p1; + + op1 = sp[-1]; + /* fast path for float64 */ + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) + goto handle_float64; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) + goto exception; + tag = JS_VALUE_GET_TAG(op1); + switch(tag) { + case JS_TAG_INT: + { + int64_t v64; + v64 = JS_VALUE_GET_INT(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + v64 += v; + break; + case OP_plus: + break; + case OP_neg: + if (v64 == 0) { + sp[-1] = __JS_NewFloat64(ctx, -0.0); + return 0; + } else { + v64 = -v64; + } + break; + default: + abort(); + } + sp[-1] = JS_NewInt64(ctx, v64); + } + break; + case JS_TAG_SHORT_BIG_INT: + { + int64_t v; + v = JS_VALUE_GET_SHORT_BIG_INT(op1); + switch(op) { + case OP_plus: + JS_ThrowTypeError(ctx, "bigint argument with unary +"); + goto exception; + case OP_inc: + if (v == JS_SHORT_BIG_INT_MAX) + goto bigint_slow_case; + sp[-1] = __JS_NewShortBigInt(ctx, v + 1); + break; + case OP_dec: + if (v == JS_SHORT_BIG_INT_MIN) + goto bigint_slow_case; + sp[-1] = __JS_NewShortBigInt(ctx, v - 1); + break; + case OP_neg: + v = JS_VALUE_GET_SHORT_BIG_INT(op1); + if (v == JS_SHORT_BIG_INT_MIN) { + bigint_slow_case: + p1 = js_bigint_set_short(&buf1, op1); + goto bigint_slow_case1; + } + sp[-1] = __JS_NewShortBigInt(ctx, -v); + break; + default: + abort(); + } + } + break; + case JS_TAG_BIG_INT: + { + JSBigInt *r; + p1 = JS_VALUE_GET_PTR(op1); + bigint_slow_case1: + switch(op) { + case OP_plus: + JS_ThrowTypeError(ctx, "bigint argument with unary +"); + JS_FreeValue(ctx, op1); + goto exception; + case OP_inc: + case OP_dec: + { + JSBigIntBuf buf2; + JSBigInt *p2; + p2 = js_bigint_set_si(&buf2, 2 * (op - OP_dec) - 1); + r = js_bigint_add(ctx, p1, p2, 0); + } + break; + case OP_neg: + r = js_bigint_neg(ctx, p1); + break; + case OP_not: + r = js_bigint_not(ctx, p1); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + if (!r) + goto exception; + sp[-1] = JS_CompactBigInt(ctx, r); + } + break; + default: + handle_float64: + { + double d; + d = JS_VALUE_GET_FLOAT64(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + d += v; + break; + case OP_plus: + break; + case OP_neg: + d = -d; + break; + default: + abort(); + } + sp[-1] = __JS_NewFloat64(ctx, d); + } + break; + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static __exception int js_post_inc_slow(JSContext *ctx, + JSValue *sp, OPCodeEnum op) +{ + JSValue op1; + + /* XXX: allow custom operators */ + op1 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + sp[-1] = JS_UNDEFINED; + return -1; + } + sp[-1] = op1; + sp[0] = JS_DupValue(ctx, op1); + return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec); +} + +static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1; + + op1 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) + goto exception; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) { + sp[-1] = __JS_NewShortBigInt(ctx, ~JS_VALUE_GET_SHORT_BIG_INT(op1)); + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { + JSBigInt *r; + r = js_bigint_not(ctx, JS_VALUE_GET_PTR(op1)); + JS_FreeValue(ctx, op1); + if (!r) + goto exception; + sp[-1] = JS_CompactBigInt(ctx, r); + } else { + int32_t v1; + if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) + goto exception; + sp[-1] = JS_NewInt32(ctx, ~v1); + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + double d1, d2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float operations */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + goto handle_float64; + } + /* fast path for short big int operations */ + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2; + js_sdlimb_t v; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + switch(op) { + case OP_sub: + v = (js_sdlimb_t)v1 - (js_sdlimb_t)v2; + break; + case OP_mul: + v = (js_sdlimb_t)v1 * (js_sdlimb_t)v2; + break; + case OP_div: + if (v2 == 0 || + ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && + v2 == -1)) { + goto slow_big_int; + } + sp[-2] = __JS_NewShortBigInt(ctx, v1 / v2); + return 0; + case OP_mod: + if (v2 == 0 || + ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && + v2 == -1)) { + goto slow_big_int; + } + sp[-2] = __JS_NewShortBigInt(ctx, v1 % v2); + return 0; + case OP_pow: + goto slow_big_int; + default: + abort(); + } + if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { + sp[-2] = __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *r = js_bigint_new_di(ctx, v); + if (!r) + goto exception; + sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); + } + return 0; + } + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + switch(op) { + case OP_sub: + v = (int64_t)v1 - (int64_t)v2; + break; + case OP_mul: + v = (int64_t)v1 * (int64_t)v2; + if (v == 0 && (v1 | v2) < 0) { + sp[-2] = __JS_NewFloat64(ctx, -0.0); + return 0; + } + break; + case OP_div: + sp[-2] = JS_NewFloat64(ctx, (double)v1 / (double)v2); + return 0; + case OP_mod: + if (v1 < 0 || v2 <= 0) { + sp[-2] = JS_NewFloat64(ctx, fmod(v1, v2)); + return 0; + } else { + v = (int64_t)v1 % (int64_t)v2; + } + break; + case OP_pow: + sp[-2] = JS_NewFloat64(ctx, js_pow(v1, v2)); + return 0; + default: + abort(); + } + sp[-2] = JS_NewInt64(ctx, v); + } else if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_BIG_INT) && + (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + slow_big_int: + /* bigint result */ + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + switch(op) { + case OP_add: + r = js_bigint_add(ctx, p1, p2, 0); + break; + case OP_sub: + r = js_bigint_add(ctx, p1, p2, 1); + break; + case OP_mul: + r = js_bigint_mul(ctx, p1, p2); + break; + case OP_div: + r = js_bigint_divrem(ctx, p1, p2, FALSE); + break; + case OP_mod: + r = js_bigint_divrem(ctx, p1, p2, TRUE); + break; + case OP_pow: + r = js_bigint_pow(ctx, p1, p2); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp[-2] = JS_CompactBigInt(ctx, r); + } else { + double dr; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + handle_float64: + switch(op) { + case OP_sub: + dr = d1 - d2; + break; + case OP_mul: + dr = d1 * d2; + break; + case OP_div: + dr = d1 / d2; + break; + case OP_mod: + dr = fmod(d1, d2); + break; + case OP_pow: + dr = js_pow(d1, d2); + break; + default: + abort(); + } + sp[-2] = __JS_NewFloat64(ctx, dr); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static inline BOOL tag_is_string(uint32_t tag) +{ + return tag == JS_TAG_STRING || tag == JS_TAG_STRING_ROPE; +} + +static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float64 */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + double d1, d2; + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + sp[-2] = __JS_NewFloat64(ctx, d1 + d2); + return 0; + } + /* fast path for short bigint */ + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2; + js_sdlimb_t v; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + v = (js_sdlimb_t)v1 + (js_sdlimb_t)v2; + if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { + sp[-2] = __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *r = js_bigint_new_di(ctx, v); + if (!r) + goto exception; + sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); + } + return 0; + } + + if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + } + + if (tag_is_string(tag1) || tag_is_string(tag2)) { + sp[-2] = JS_ConcatString(ctx, op1, op2); + if (JS_IsException(sp[-2])) + goto exception; + return 0; + } + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + v = (int64_t)v1 + (int64_t)v2; + sp[-2] = JS_NewInt64(ctx, v); + } else if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + /* bigint result */ + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + r = js_bigint_add(ctx, p1, p2, 0); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp[-2] = JS_CompactBigInt(ctx, r); + } else { + double d1, d2; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + sp[-2] = __JS_NewFloat64(ctx, d1 + d2); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_binary_logic_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2, v; + js_sdlimb_t vd; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + /* bigint fast path */ + switch(op) { + case OP_and: + v = v1 & v2; + break; + case OP_or: + v = v1 | v2; + break; + case OP_xor: + v = v1 ^ v2; + break; + case OP_sar: + if (v2 > (JS_LIMB_BITS - 1)) { + goto slow_big_int; + } else if (v2 < 0) { + if (v2 < -(JS_LIMB_BITS - 1)) + goto slow_big_int; + v2 = -v2; + goto bigint_shl; + } + bigint_sar: + v = v1 >> v2; + break; + case OP_shl: + if (v2 > (JS_LIMB_BITS - 1)) { + goto slow_big_int; + } else if (v2 < 0) { + if (v2 < -(JS_LIMB_BITS - 1)) + goto slow_big_int; + v2 = -v2; + goto bigint_sar; + } + bigint_shl: + vd = (js_dlimb_t)v1 << v2; + if (likely(vd >= JS_SHORT_BIG_INT_MIN && + vd <= JS_SHORT_BIG_INT_MAX)) { + v = vd; + } else { + JSBigInt *r = js_bigint_new_di(ctx, vd); + if (!r) + goto exception; + sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); + return 0; + } + break; + default: + abort(); + } + sp[-2] = __JS_NewShortBigInt(ctx, v); + return 0; + } + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + + tag1 = JS_VALUE_GET_TAG(op1); + tag2 = JS_VALUE_GET_TAG(op2); + if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + slow_big_int: + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + switch(op) { + case OP_and: + case OP_or: + case OP_xor: + r = js_bigint_logic(ctx, p1, p2, op); + break; + case OP_shl: + case OP_sar: + { + js_slimb_t shift; + shift = js_bigint_get_si_sat(p2); + if (shift > INT32_MAX) + shift = INT32_MAX; + else if (shift < -INT32_MAX) + shift = -INT32_MAX; + if (op == OP_sar) + shift = -shift; + if (shift >= 0) + r = js_bigint_shl(ctx, p1, shift); + else + r = js_bigint_shr(ctx, p1, -shift); + } + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp[-2] = JS_CompactBigInt(ctx, r); + } else { + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) + goto exception; + switch(op) { + case OP_shl: + r = v1 << (v2 & 0x1f); + break; + case OP_sar: + r = (int)v1 >> (v2 & 0x1f); + break; + case OP_and: + r = v1 & v2; + break; + case OP_or: + r = v1 | v2; + break; + case OP_xor: + r = v1 ^ v2; + break; + default: + abort(); + } + sp[-2] = JS_NewInt32(ctx, r); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +/* op1 must be a bigint or int. */ +static JSBigInt *JS_ToBigIntBuf(JSContext *ctx, JSBigIntBuf *buf1, + JSValue op1) +{ + JSBigInt *p1; + + switch(JS_VALUE_GET_TAG(op1)) { + case JS_TAG_INT: + p1 = js_bigint_set_si(buf1, JS_VALUE_GET_INT(op1)); + break; + case JS_TAG_SHORT_BIG_INT: + p1 = js_bigint_set_short(buf1, op1); + break; + case JS_TAG_BIG_INT: + p1 = JS_VALUE_GET_PTR(op1); + break; + default: + abort(); + } + return p1; +} + +/* op1 and op2 must be numeric types and at least one must be a + bigint. No exception is generated. */ +static int js_compare_bigint(JSContext *ctx, OPCodeEnum op, + JSValue op1, JSValue op2) +{ + int res, val, tag1, tag2; + JSBigIntBuf buf1, buf2; + JSBigInt *p1, *p2; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_INT) && + (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_INT)) { + /* fast path */ + js_slimb_t v1, v2; + if (tag1 == JS_TAG_INT) + v1 = JS_VALUE_GET_INT(op1); + else + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + if (tag2 == JS_TAG_INT) + v2 = JS_VALUE_GET_INT(op2); + else + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + val = (v1 > v2) - (v1 < v2); + } else { + if (tag1 == JS_TAG_FLOAT64) { + p2 = JS_ToBigIntBuf(ctx, &buf2, op2); + val = js_bigint_float64_cmp(ctx, p2, JS_VALUE_GET_FLOAT64(op1)); + if (val == 2) + goto unordered; + val = -val; + } else if (tag2 == JS_TAG_FLOAT64) { + p1 = JS_ToBigIntBuf(ctx, &buf1, op1); + val = js_bigint_float64_cmp(ctx, p1, JS_VALUE_GET_FLOAT64(op2)); + if (val == 2) { + unordered: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return FALSE; + } + } else { + p1 = JS_ToBigIntBuf(ctx, &buf1, op1); + p2 = JS_ToBigIntBuf(ctx, &buf2, op2); + val = js_bigint_cmp(ctx, p1, p2); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } + + switch(op) { + case OP_lt: + res = val < 0; + break; + case OP_lte: + res = val <= 0; + break; + case OP_gt: + res = val > 0; + break; + case OP_gte: + res = val >= 0; + break; + case OP_eq: + res = val == 0; + break; + default: + abort(); + } + return res; +} + +static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag_is_string(tag1) && tag_is_string(tag2)) { + if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + res = js_string_compare(ctx, JS_VALUE_GET_STRING(op1), + JS_VALUE_GET_STRING(op2)); + } else { + res = js_string_rope_compare(ctx, op1, op2, FALSE); + } + switch(op) { + case OP_lt: + res = (res < 0); + break; + case OP_lte: + res = (res <= 0); + break; + case OP_gt: + res = (res > 0); + break; + default: + case OP_gte: + res = (res >= 0); + break; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) && + (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) { + /* fast path for float64/int */ + goto float64_compare; + } else { + if ((((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + tag_is_string(tag2)) || + ((tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) && + tag_is_string(tag1)))) { + if (tag_is_string(tag1)) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) + goto invalid_bigint_string; + } + if (tag_is_string(tag2)) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = FALSE; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || + tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { + res = js_compare_bigint(ctx, op, op1, op2); + } else { + double d1, d2; + + float64_compare: + /* can use floating point comparison */ + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + switch(op) { + case OP_lt: + res = (d1 < d2); /* if NaN return false */ + break; + case OP_lte: + res = (d1 <= d2); /* if NaN return false */ + break; + case OP_gt: + res = (d1 > d2); /* if NaN return false */ + break; + default: + case OP_gte: + res = (d1 >= d2); /* if NaN return false */ + break; + } + } + } + done: + sp[-2] = JS_NewBool(ctx, res); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static BOOL tag_is_number(uint32_t tag) +{ + return (tag == JS_TAG_INT || + tag == JS_TAG_FLOAT64 || + tag == JS_TAG_BIG_INT || tag == JS_TAG_SHORT_BIG_INT); +} + +static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, + BOOL is_neq) +{ + JSValue op1, op2; + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + redo: + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + if (tag_is_number(tag1) && tag_is_number(tag2)) { + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + } else if ((tag1 == JS_TAG_FLOAT64 && + (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) || + (tag2 == JS_TAG_FLOAT64 && + (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) { + double d1, d2; + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + res = (d1 == d2); + } else { + res = js_compare_bigint(ctx, OP_eq, op1, op2); + } + } else if (tag1 == tag2) { + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || + (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { + res = TRUE; + } else if (tag_is_string(tag1) && tag_is_string(tag2)) { + /* needed when comparing strings and ropes */ + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + } else if ((tag_is_string(tag1) && tag_is_number(tag2)) || + (tag_is_string(tag2) && tag_is_number(tag1))) { + + if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || + tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { + if (tag_is_string(tag1)) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) + goto invalid_bigint_string; + } + if (tag_is_string(tag2)) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT ) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = FALSE; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + } else if (tag1 == JS_TAG_BOOL) { + op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1)); + goto redo; + } else if (tag2 == JS_TAG_BOOL) { + op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2)); + goto redo; + } else if ((tag1 == JS_TAG_OBJECT && + (tag_is_number(tag2) || tag_is_string(tag2) || tag2 == JS_TAG_SYMBOL)) || + (tag2 == JS_TAG_OBJECT && + (tag_is_number(tag1) || tag_is_string(tag1) || tag1 == JS_TAG_SYMBOL))) { + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + goto redo; + } else { + /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ + if ((JS_IsHTMLDDA(ctx, op1) && + (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || + (JS_IsHTMLDDA(ctx, op2) && + (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { + res = TRUE; + } else { + res = FALSE; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } + done: + sp[-2] = JS_NewBool(ctx, res ^ is_neq); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) { + JS_ThrowTypeError(ctx, "bigint operands are forbidden for >>>"); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + goto exception; + } + /* cannot give an exception */ + JS_ToUint32Free(ctx, &v1, op1); + JS_ToUint32Free(ctx, &v2, op2); + r = v1 >> (v2 & 0x1f); + sp[-2] = JS_NewUint32(ctx, r); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +/* XXX: Should take JSValueConst arguments */ +static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, + JSStrictEqModeEnum eq_mode) +{ + BOOL res; + int tag1, tag2; + double d1, d2; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + switch(tag1) { + case JS_TAG_BOOL: + if (tag1 != tag2) { + res = FALSE; + } else { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + goto done_no_free; + } + break; + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = (tag1 == tag2); + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + { + if (!tag_is_string(tag2)) { + res = FALSE; + } else if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + res = js_string_eq(ctx, JS_VALUE_GET_STRING(op1), + JS_VALUE_GET_STRING(op2)); + } else { + res = (js_string_rope_compare(ctx, op1, op2, TRUE) == 0); + } + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p1, *p2; + if (tag1 != tag2) { + res = FALSE; + } else { + p1 = JS_VALUE_GET_PTR(op1); + p2 = JS_VALUE_GET_PTR(op2); + res = (p1 == p2); + } + } + break; + case JS_TAG_OBJECT: + if (tag1 != tag2) + res = FALSE; + else + res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); + break; + case JS_TAG_INT: + d1 = JS_VALUE_GET_INT(op1); + if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + goto number_test; + } else if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + goto number_test; + } else { + res = FALSE; + } + break; + case JS_TAG_FLOAT64: + d1 = JS_VALUE_GET_FLOAT64(op1); + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + } else { + res = FALSE; + break; + } + number_test: + if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { + JSFloat64Union u1, u2; + /* NaN is not always normalized, so this test is necessary */ + if (isnan(d1) || isnan(d2)) { + res = isnan(d1) == isnan(d2); + } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) { + res = (d1 == d2); /* +0 == -0 */ + } else { + u1.d = d1; + u2.d = d2; + res = (u1.u64 == u2.u64); /* +0 != -0 */ + } + } else { + res = (d1 == d2); /* if NaN return false and +0 == -0 */ + } + goto done_no_free; + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + { + JSBigIntBuf buf1, buf2; + JSBigInt *p1, *p2; + + if (tag2 != JS_TAG_SHORT_BIG_INT && + tag2 != JS_TAG_BIG_INT) { + res = FALSE; + break; + } + + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + res = (js_bigint_cmp(ctx, p1, p2) == 0); + } + break; + default: + res = FALSE; + break; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + done_no_free: + return res; +} + +static BOOL js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, + JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), + JS_EQ_STRICT); +} + +BOOL JS_StrictEq(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq(ctx, op1, op2); +} + +static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, + JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), + JS_EQ_SAME_VALUE); +} + +BOOL JS_SameValue(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_same_value(ctx, op1, op2); +} + +static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, + JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), + JS_EQ_SAME_VALUE_ZERO); +} + +BOOL JS_SameValueZero(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_same_value_zero(ctx, op1, op2); +} + +static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, + BOOL is_neq) +{ + BOOL res; + res = js_strict_eq2(ctx, sp[-2], sp[-1], JS_EQ_STRICT); + sp[-2] = JS_NewBool(ctx, res ^ is_neq); + return 0; +} + +static __exception int js_operator_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + + if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + atom = JS_ValueToAtom(ctx, op1); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_HasProperty(ctx, op2, atom); + JS_FreeAtom(ctx, atom); + if (ret < 0) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = JS_NewBool(ctx, ret); + return 0; +} + +static __exception int js_operator_private_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + int ret; + + op1 = sp[-2]; /* object */ + op2 = sp[-1]; /* field name or method function */ + + if (JS_VALUE_GET_TAG(op1) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + if (JS_IsObject(op2)) { + /* method: use the brand */ + ret = JS_CheckBrand(ctx, op1, op2); + if (ret < 0) + return -1; + } else { + JSAtom atom; + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + /* field */ + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + p = JS_VALUE_GET_OBJ(op1); + prs = find_own_property(&pr, p, atom); + JS_FreeAtom(ctx, atom); + ret = (prs != NULL); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = JS_NewBool(ctx, ret); + return 0; +} + +static __exception int js_has_unscopable(JSContext *ctx, JSValueConst obj, + JSAtom atom) +{ + JSValue arr, val; + int ret; + + arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables); + if (JS_IsException(arr)) + return -1; + ret = 0; + if (JS_IsObject(arr)) { + val = JS_GetProperty(ctx, arr, atom); + ret = JS_ToBoolFree(ctx, val); + } + JS_FreeValue(ctx, arr); + return ret; +} + +static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + BOOL ret; + + op1 = sp[-2]; + op2 = sp[-1]; + ret = JS_IsInstanceOf(ctx, op1, op2); + if (ret < 0) + return ret; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = JS_NewBool(ctx, ret); + return 0; +} + +static __exception int js_operator_typeof(JSContext *ctx, JSValueConst op1) +{ + JSAtom atom; + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(op1); + switch(tag) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + atom = JS_ATOM_bigint; + break; + case JS_TAG_INT: + case JS_TAG_FLOAT64: + atom = JS_ATOM_number; + break; + case JS_TAG_UNDEFINED: + atom = JS_ATOM_undefined; + break; + case JS_TAG_BOOL: + atom = JS_ATOM_boolean; + break; + case JS_TAG_STRING: + case JS_TAG_STRING_ROPE: + atom = JS_ATOM_string; + break; + case JS_TAG_OBJECT: + { + JSObject *p; + p = JS_VALUE_GET_OBJ(op1); + if (unlikely(p->is_HTMLDDA)) + atom = JS_ATOM_undefined; + else if (JS_IsFunction(ctx, op1)) + atom = JS_ATOM_function; + else + goto obj_type; + } + break; + case JS_TAG_NULL: + obj_type: + atom = JS_ATOM_object; + break; + case JS_TAG_SYMBOL: + atom = JS_ATOM_symbol; + break; + default: + atom = JS_ATOM_unknown; + break; + } + return atom; +} + +static __exception int js_operator_delete(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + if (unlikely(ret < 0)) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = JS_NewBool(ctx, ret); + return 0; +} + +/* XXX: not 100% compatible, but mozilla seems to use a similar + implementation to ensure that caller in non strict mode does not + throw (ES5 compatibility) */ +static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (!b || (b->js_mode & JS_MODE_STRICT) || !b->has_prototype || argc >= 1) { + return JS_ThrowTypeError(ctx, "invalid property access"); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_fileName(JSContext *ctx, + JSValueConst this_val) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b && b->has_debug) { + return JS_AtomToString(ctx, b->debug.filename); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_lineNumber(JSContext *ctx, + JSValueConst this_val, int is_col) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b && b->has_debug) { + int line_num, col_num; + line_num = find_line_num(ctx, b, -1, &col_num); + if (is_col) + return JS_NewInt32(ctx, col_num); + else + return JS_NewInt32(ctx, line_num); + } + return JS_UNDEFINED; +} + +static int js_arguments_define_own_property(JSContext *ctx, + JSValueConst this_obj, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, int flags) +{ + JSObject *p; + uint32_t idx; + p = JS_VALUE_GET_OBJ(this_obj); + /* convert to normal array when redefining an existing numeric field */ + if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) && + idx < p->u.array.count) { + if (convert_fast_array_to_array(ctx, p)) + return -1; + } + /* run the default define own property */ + return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, + flags | JS_PROP_NO_EXOTIC); +} + +static const JSClassExoticMethods js_arguments_exotic_methods = { + .define_own_property = js_arguments_define_own_property, +}; + +static JSValue js_build_arguments(JSContext *ctx, int argc, JSValueConst *argv) +{ + JSValue val, *tab; + JSProperty props[3]; + JSObject *p; + int i; + + props[0].u.value = JS_NewInt32(ctx, argc); /* length */ + props[1].u.value = JS_DupValue(ctx, ctx->array_proto_values); /* Symbol.iterator */ + props[2].u.getset.getter = JS_VALUE_GET_OBJ(JS_DupValue(ctx, ctx->throw_type_error)); /* callee */ + props[2].u.getset.setter = JS_VALUE_GET_OBJ(JS_DupValue(ctx, ctx->throw_type_error)); /* callee */ + + val = JS_NewObjectFromShape(ctx, js_dup_shape(ctx->arguments_shape), + JS_CLASS_ARGUMENTS, props); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* initialize the fast array part */ + tab = NULL; + if (argc > 0) { + tab = js_malloc(ctx, sizeof(tab[0]) * argc); + if (!tab) + goto fail; + for(i = 0; i < argc; i++) { + tab[i] = JS_DupValue(ctx, argv[i]); + } + } + p->u.array.u.values = tab; + p->u.array.count = argc; + return val; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +#define GLOBAL_VAR_OFFSET 0x40000000 +#define ARGUMENT_VAR_OFFSET 0x20000000 + +static void js_mapped_arguments_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSVarRef **var_refs = p->u.array.u.var_refs; + int i; + for(i = 0; i < p->u.array.count; i++) + free_var_ref(rt, var_refs[i]); + js_free_rt(rt, var_refs); +} + +static void js_mapped_arguments_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSVarRef **var_refs = p->u.array.u.var_refs; + int i; + + for(i = 0; i < p->u.array.count; i++) + mark_func(rt, &var_refs[i]->header); +} + +/* legacy arguments object: add references to the function arguments */ +static JSValue js_build_mapped_arguments(JSContext *ctx, int argc, + JSValueConst *argv, + JSStackFrame *sf, int arg_count) +{ + JSValue val; + JSProperty props[3]; + JSVarRef **tab, *var_ref; + JSObject *p; + int i, j; + + props[0].u.value = JS_NewInt32(ctx, argc); /* length */ + props[1].u.value = JS_DupValue(ctx, ctx->array_proto_values); /* Symbol.iterator */ + props[2].u.value = JS_DupValue(ctx, ctx->rt->current_stack_frame->cur_func); /* callee */ + + val = JS_NewObjectFromShape(ctx, js_dup_shape(ctx->mapped_arguments_shape), + JS_CLASS_MAPPED_ARGUMENTS, props); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* initialize the fast array part */ + tab = NULL; + if (argc > 0) { + tab = js_malloc(ctx, sizeof(tab[0]) * argc); + if (!tab) + goto fail; + for(i = 0; i < arg_count; i++) { + var_ref = get_var_ref(ctx, sf, i, TRUE); + if (!var_ref) + goto fail1; + tab[i] = var_ref; + } + for(i = arg_count; i < argc; i++) { + var_ref = js_create_var_ref(ctx, FALSE); + if (!var_ref) { + fail1: + for(j = 0; j < i; j++) + free_var_ref(ctx->rt, tab[j]); + js_free(ctx, tab); + goto fail; + } + var_ref->value = JS_DupValue(ctx, argv[i]); + tab[i] = var_ref; + } + } + p->u.array.u.var_refs = tab; + p->u.array.count = argc; + return val; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) +{ + JSObject *p, *p1; + JSPropertyEnum *tab_atom; + int i; + JSValue enum_obj; + JSForInIterator *it; + uint32_t tag, tab_atom_count; + + tag = JS_VALUE_GET_TAG(obj); + if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) { + obj = JS_ToObjectFree(ctx, obj); + } + + it = js_malloc(ctx, sizeof(*it)); + if (!it) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR); + if (JS_IsException(enum_obj)) { + js_free(ctx, it); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + it->is_array = FALSE; + it->obj = obj; + it->idx = 0; + it->tab_atom = NULL; + it->atom_count = 0; + it->in_prototype_chain = FALSE; + p1 = JS_VALUE_GET_OBJ(enum_obj); + p1->u.for_in_iterator = it; + + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return enum_obj; + + p = JS_VALUE_GET_OBJ(obj); + if (p->fast_array) { + JSShape *sh; + JSShapeProperty *prs; + /* check that there are no enumerable normal fields */ + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->flags & JS_PROP_ENUMERABLE) + goto normal_case; + } + /* for fast arrays, we only store the number of elements */ + it->is_array = TRUE; + it->atom_count = p->u.array.count; + } else { + normal_case: + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + JS_FreeValue(ctx, enum_obj); + return JS_EXCEPTION; + } + it->tab_atom = tab_atom; + it->atom_count = tab_atom_count; + } + return enum_obj; +} + +/* obj -> enum_obj */ +static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) +{ + sp[-1] = build_for_in_iterator(ctx, sp[-1]); + if (JS_IsException(sp[-1])) + return -1; + return 0; +} + +/* return -1 if exception, 0 if slow case, 1 if the enumeration is finished */ +static __exception int js_for_in_prepare_prototype_chain_enum(JSContext *ctx, + JSValueConst enum_obj) +{ + JSObject *p; + JSForInIterator *it; + JSPropertyEnum *tab_atom; + uint32_t tab_atom_count, i; + JSValue obj1; + + p = JS_VALUE_GET_OBJ(enum_obj); + it = p->u.for_in_iterator; + + /* check if there are enumerable properties in the prototype chain (fast path) */ + obj1 = JS_DupValue(ctx, it->obj); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsNull(obj1)) + break; + if (JS_IsException(obj1)) + goto fail; + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(obj1), + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); + if (tab_atom_count != 0) { + JS_FreeValue(ctx, obj1); + goto slow_path; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + } + JS_FreeValue(ctx, obj1); + return 1; + + slow_path: + /* add the visited properties, even if they are not enumerable */ + if (it->is_array) { + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(it->obj), + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + goto fail; + } + it->is_array = FALSE; + it->tab_atom = tab_atom; + it->atom_count = tab_atom_count; + } + + for(i = 0; i < it->atom_count; i++) { + if (JS_DefinePropertyValue(ctx, enum_obj, it->tab_atom[i].atom, JS_NULL, JS_PROP_ENUMERABLE) < 0) + goto fail; + } + return 0; + fail: + return -1; +} + +/* enum_obj -> enum_obj value done */ +static __exception int js_for_in_next(JSContext *ctx, JSValue *sp) +{ + JSValueConst enum_obj; + JSObject *p; + JSAtom prop; + JSForInIterator *it; + JSPropertyEnum *tab_atom; + uint32_t tab_atom_count; + int ret; + + enum_obj = sp[-1]; + /* fail safe */ + if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT) + goto done; + p = JS_VALUE_GET_OBJ(enum_obj); + if (p->class_id != JS_CLASS_FOR_IN_ITERATOR) + goto done; + it = p->u.for_in_iterator; + + for(;;) { + if (it->idx >= it->atom_count) { + if (JS_IsNull(it->obj) || JS_IsUndefined(it->obj)) + goto done; /* not an object */ + /* no more property in the current object: look in the prototype */ + if (!it->in_prototype_chain) { + ret = js_for_in_prepare_prototype_chain_enum(ctx, enum_obj); + if (ret < 0) + return -1; + if (ret) + goto done; + it->in_prototype_chain = TRUE; + } + it->obj = JS_GetPrototypeFree(ctx, it->obj); + if (JS_IsException(it->obj)) + return -1; + if (JS_IsNull(it->obj)) + goto done; /* no more prototype */ + + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) + return -1; + + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(it->obj), + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + return -1; + } + JS_FreePropertyEnum(ctx, it->tab_atom, it->atom_count); + it->tab_atom = tab_atom; + it->atom_count = tab_atom_count; + it->idx = 0; + } else { + if (it->is_array) { + prop = __JS_AtomFromUInt32(it->idx); + it->idx++; + } else { + BOOL is_enumerable; + prop = it->tab_atom[it->idx].atom; + is_enumerable = it->tab_atom[it->idx].is_enumerable; + it->idx++; + if (it->in_prototype_chain) { + /* slow case: we are in the prototype chain */ + ret = JS_GetOwnPropertyInternal(ctx, NULL, JS_VALUE_GET_OBJ(enum_obj), prop); + if (ret < 0) + return ret; + if (ret) + continue; /* already visited */ + /* add to the visited property list */ + if (JS_DefinePropertyValue(ctx, enum_obj, prop, JS_NULL, + JS_PROP_ENUMERABLE) < 0) + return -1; + } + if (!is_enumerable) + continue; + } + /* check if the property was deleted */ + ret = JS_GetOwnPropertyInternal(ctx, NULL, JS_VALUE_GET_OBJ(it->obj), prop); + if (ret < 0) + return ret; + if (ret) + break; + } + } + /* return the property */ + sp[0] = JS_AtomToValue(ctx, prop); + sp[1] = JS_FALSE; + return 0; + done: + /* return the end */ + sp[0] = JS_UNDEFINED; + sp[1] = JS_TRUE; + return 0; +} + +static JSValue JS_GetIterator2(JSContext *ctx, JSValueConst obj, + JSValueConst method) +{ + JSValue enum_obj; + + enum_obj = JS_Call(ctx, method, obj, 0, NULL); + if (JS_IsException(enum_obj)) + return enum_obj; + if (!JS_IsObject(enum_obj)) { + JS_FreeValue(ctx, enum_obj); + return JS_ThrowTypeErrorNotAnObject(ctx); + } + return enum_obj; +} + +static JSValue JS_GetIterator(JSContext *ctx, JSValueConst obj, BOOL is_async) +{ + JSValue method, ret, sync_iter; + + if (is_async) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator); + if (JS_IsException(method)) + return method; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + sync_iter = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + if (JS_IsException(sync_iter)) + return sync_iter; + ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter); + JS_FreeValue(ctx, sync_iter); + return ret; + } + } else { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + } + if (!JS_IsFunction(ctx, method)) { + JS_FreeValue(ctx, method); + return JS_ThrowTypeError(ctx, "value is not iterable"); + } + ret = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + return ret; +} + +/* return *pdone = 2 if the iterator object is not parsed */ +static JSValue JS_IteratorNext2(JSContext *ctx, JSValueConst enum_obj, + JSValueConst method, + int argc, JSValueConst *argv, int *pdone) +{ + JSValue obj; + + /* fast path for the built-in iterators (avoid creating the + intermediate result object) */ + if (JS_IsObject(method)) { + JSObject *p = JS_VALUE_GET_OBJ(method); + if (p->class_id == JS_CLASS_C_FUNCTION && + p->u.cfunc.cproto == JS_CFUNC_iterator_next) { + JSCFunctionType func; + JSValueConst args[1]; + + /* in case the function expects one argument */ + if (argc == 0) { + args[0] = JS_UNDEFINED; + argv = args; + } + func = p->u.cfunc.c_function; + return func.iterator_next(ctx, enum_obj, argc, argv, + pdone, p->u.cfunc.magic); + } + } + obj = JS_Call(ctx, method, enum_obj, argc, argv); + if (JS_IsException(obj)) + goto fail; + if (!JS_IsObject(obj)) { + JS_FreeValue(ctx, obj); + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto fail; + } + *pdone = 2; + return obj; + fail: + *pdone = FALSE; + return JS_EXCEPTION; +} + +/* Note: always return JS_UNDEFINED when *pdone = TRUE. */ +static JSValue JS_IteratorNext(JSContext *ctx, JSValueConst enum_obj, + JSValueConst method, + int argc, JSValueConst *argv, BOOL *pdone) +{ + JSValue obj, value, done_val; + int done; + + obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done); + if (JS_IsException(obj)) + goto fail; + if (likely(done == 0)) { + *pdone = FALSE; + return obj; + } else if (done != 2) { + JS_FreeValue(ctx, obj); + *pdone = TRUE; + return JS_UNDEFINED; + } else { + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + *pdone = JS_ToBoolFree(ctx, done_val); + value = JS_UNDEFINED; + if (!*pdone) { + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + } + JS_FreeValue(ctx, obj); + return value; + } + fail: + JS_FreeValue(ctx, obj); + *pdone = FALSE; + return JS_EXCEPTION; +} + +/* return < 0 in case of exception */ +static int JS_IteratorClose(JSContext *ctx, JSValueConst enum_obj, + BOOL is_exception_pending) +{ + JSValue method, ret, ex_obj; + int res; + + if (is_exception_pending) { + ex_obj = ctx->rt->current_exception; + ctx->rt->current_exception = JS_UNINITIALIZED; + res = -1; + } else { + ex_obj = JS_UNDEFINED; + res = 0; + } + method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return); + if (JS_IsException(method)) { + res = -1; + goto done; + } + if (JS_IsUndefined(method) || JS_IsNull(method)) { + goto done; + } + ret = JS_CallFree(ctx, method, enum_obj, 0, NULL); + if (!is_exception_pending) { + if (JS_IsException(ret)) { + res = -1; + } else if (!JS_IsObject(ret)) { + JS_ThrowTypeErrorNotAnObject(ctx); + res = -1; + } + } + JS_FreeValue(ctx, ret); + done: + if (is_exception_pending) { + JS_Throw(ctx, ex_obj); + } + return res; +} + +/* obj -> enum_rec (3 slots) */ +static __exception int js_for_of_start(JSContext *ctx, JSValue *sp, + BOOL is_async) +{ + JSValue op1, obj, method; + op1 = sp[-1]; + obj = JS_GetIterator(ctx, op1, is_async); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, op1); + sp[-1] = obj; + method = JS_GetProperty(ctx, obj, JS_ATOM_next); + if (JS_IsException(method)) + return -1; + sp[0] = method; + return 0; +} + +/* enum_rec [objs] -> enum_rec [objs] value done. There are 'offset' + objs. If 'done' is true or in case of exception, 'enum_rec' is set + to undefined. If 'done' is true, 'value' is always set to + undefined. */ +static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) +{ + JSValue value = JS_UNDEFINED; + int done = 1; + + if (likely(!JS_IsUndefined(sp[offset]))) { + value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done); + if (JS_IsException(value)) + done = -1; + if (done) { + /* value is JS_UNDEFINED or JS_EXCEPTION */ + /* replace the iteration object with undefined */ + JS_FreeValue(ctx, sp[offset]); + sp[offset] = JS_UNDEFINED; + if (done < 0) { + return -1; + } else { + JS_FreeValue(ctx, value); + value = JS_UNDEFINED; + } + } + } + sp[0] = value; + sp[1] = JS_NewBool(ctx, done); + return 0; +} + +static __exception int js_for_await_of_next(JSContext *ctx, JSValue *sp) +{ + JSValue obj, iter, next; + + sp[-1] = JS_UNDEFINED; /* disable the catch offset so that + exceptions do not close the iterator */ + iter = sp[-3]; + next = sp[-2]; + obj = JS_Call(ctx, next, iter, 0, NULL); + if (JS_IsException(obj)) + return -1; + sp[0] = obj; + return 0; +} + +static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValueConst obj, + BOOL *pdone) +{ + JSValue done_val, value; + BOOL done; + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + done = JS_ToBoolFree(ctx, done_val); + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + if (JS_IsException(value)) + goto fail; + *pdone = done; + return value; + fail: + *pdone = FALSE; + return JS_EXCEPTION; +} + +static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) +{ + JSValue obj, value; + BOOL done; + obj = sp[-1]; + if (!JS_IsObject(obj)) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + return -1; + } + value = JS_IteratorGetCompleteValue(ctx, obj, &done); + if (JS_IsException(value)) + return -1; + JS_FreeValue(ctx, obj); + /* put again the catch offset so that exceptions close the + iterator */ + sp[-2] = JS_NewCatchOffset(ctx, 0); + sp[-1] = value; + sp[0] = JS_NewBool(ctx, done); + return 0; +} + +static JSValue js_create_iterator_result(JSContext *ctx, + JSValue val, + BOOL done) +{ + JSValue obj; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, val); + return obj; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value, + val, JS_PROP_C_W_E) < 0) { + goto fail; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done, + JS_NewBool(ctx, done), JS_PROP_C_W_E) < 0) { + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + BOOL *pdone, int magic); + +static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); + +static BOOL js_is_fast_array(JSContext *ctx, JSValueConst obj) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + return TRUE; + } + } + return FALSE; +} + +/* Access an Array's internal JSValue array if available */ +static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj, + JSValue **arrpp, uint32_t *countp) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + *countp = p->u.array.count; + *arrpp = p->u.array.u.values; + return TRUE; + } + } + return FALSE; +} + +static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) +{ + JSValue iterator, enumobj, method, value; + int is_array_iterator; + JSValue *arrp; + uint32_t i, count32, pos; + JSCFunctionType ft; + + if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) { + JS_ThrowInternalError(ctx, "invalid index for append"); + return -1; + } + + pos = JS_VALUE_GET_INT(sp[-2]); + + /* XXX: further optimisations: + - use ctx->array_proto_values? + - check if array_iterator_prototype next method is built-in and + avoid constructing actual iterator object? + - build this into js_for_of_start and use in all `for (x of o)` loops + */ + iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator); + if (JS_IsException(iterator)) + return -1; + ft.generic_magic = js_create_array_iterator; + is_array_iterator = JS_IsCFunction(ctx, iterator, ft.generic, + JS_ITERATOR_KIND_VALUE); + JS_FreeValue(ctx, iterator); + + enumobj = JS_GetIterator(ctx, sp[-1], FALSE); + if (JS_IsException(enumobj)) + return -1; + method = JS_GetProperty(ctx, enumobj, JS_ATOM_next); + if (JS_IsException(method)) { + JS_FreeValue(ctx, enumobj); + return -1; + } + + ft.iterator_next = js_array_iterator_next; + if (is_array_iterator + && JS_IsCFunction(ctx, method, ft.generic, 0) + && js_get_fast_array(ctx, sp[-1], &arrp, &count32)) { + uint32_t len; + if (js_get_length32(ctx, &len, sp[-1])) + goto exception; + /* if len > count32, the elements >= count32 might be read in + the prototypes and might have side effects */ + if (len != count32) + goto general_case; + /* Handle fast arrays explicitly */ + for (i = 0; i < count32; i++) { + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, + JS_DupValue(ctx, arrp[i]), JS_PROP_C_W_E) < 0) + goto exception; + } + } else { + general_case: + for (;;) { + BOOL done; + value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done); + if (JS_IsException(value)) + goto exception; + if (done) { + /* value is JS_UNDEFINED */ + break; + } + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0) + goto exception; + } + } + /* Note: could raise an error if too many elements */ + sp[-2] = JS_NewInt32(ctx, pos); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return 0; + +exception: + JS_IteratorClose(ctx, enumobj, TRUE); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return -1; +} + +static __exception int JS_CopyDataProperties(JSContext *ctx, + JSValueConst target, + JSValueConst source, + JSValueConst excluded, + BOOL setprop) +{ + JSPropertyEnum *tab_atom; + JSValue val; + uint32_t i, tab_atom_count; + JSObject *p; + JSObject *pexcl = NULL; + int ret, gpn_flags; + JSPropertyDescriptor desc; + BOOL is_enumerable; + + if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT) + return 0; + + if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT) + pexcl = JS_VALUE_GET_OBJ(excluded); + + p = JS_VALUE_GET_OBJ(source); + + gpn_flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY; + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + /* cannot use JS_GPN_ENUM_ONLY with e.g. proxies because it + introduces a visible change */ + if (em && em->get_own_property_names) { + gpn_flags &= ~JS_GPN_ENUM_ONLY; + } + } + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + gpn_flags)) + return -1; + + for (i = 0; i < tab_atom_count; i++) { + if (pexcl) { + ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom); + if (ret) { + if (ret < 0) + goto exception; + continue; + } + } + if (!(gpn_flags & JS_GPN_ENUM_ONLY)) { + /* test if the property is enumerable */ + ret = JS_GetOwnPropertyInternal(ctx, &desc, p, tab_atom[i].atom); + if (ret < 0) + goto exception; + if (!ret) + continue; + is_enumerable = (desc.flags & JS_PROP_ENUMERABLE) != 0; + js_free_desc(ctx, &desc); + if (!is_enumerable) + continue; + } + val = JS_GetProperty(ctx, source, tab_atom[i].atom); + if (JS_IsException(val)) + goto exception; + if (setprop) + ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val); + else + ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, + JS_PROP_C_W_E); + if (ret < 0) + goto exception; + } + JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); + return 0; + exception: + JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); + return -1; +} + +/* only valid inside C functions */ +static JSValueConst JS_GetActiveFunction(JSContext *ctx) +{ + return ctx->rt->current_stack_frame->cur_func; +} + +static JSVarRef *js_create_var_ref(JSContext *ctx, BOOL is_lexical) +{ + JSVarRef *var_ref; + var_ref = js_malloc(ctx, sizeof(JSVarRef)); + if (!var_ref) + return NULL; + var_ref->header.ref_count = 1; + if (is_lexical) + var_ref->value = JS_UNINITIALIZED; + else + var_ref->value = JS_UNDEFINED; + var_ref->pvalue = &var_ref->value; + var_ref->is_detached = TRUE; + var_ref->is_lexical = FALSE; + var_ref->is_const = FALSE; + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + return var_ref; +} + +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, + BOOL is_arg) +{ + JSObject *p; + JSFunctionBytecode *b; + JSVarRef *var_ref; + JSValue *pvalue; + int var_ref_idx; + JSBytecodeVarDef *vd; + + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = p->u.func.function_bytecode; + + if (is_arg) { + vd = &b->vardefs[var_idx]; + pvalue = &sf->arg_buf[var_idx]; + } else { + vd = &b->vardefs[b->arg_count + var_idx]; + pvalue = &sf->var_buf[var_idx]; + } + assert(vd->is_captured); + var_ref_idx = vd->var_ref_idx; + assert(var_ref_idx < b->var_ref_count); + var_ref = sf->var_refs[var_ref_idx]; + if (var_ref) { + /* reference to the already created local variable */ + assert(var_ref->pvalue == pvalue); + var_ref->header.ref_count++; + return var_ref; + } + + /* create a new one */ + var_ref = js_malloc(ctx, sizeof(JSVarRef)); + if (!var_ref) + return NULL; + var_ref->header.ref_count = 1; + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + var_ref->is_detached = FALSE; + var_ref->is_lexical = FALSE; + var_ref->is_const = FALSE; + var_ref->var_ref_idx = var_ref_idx; + var_ref->stack_frame = sf; + sf->var_refs[var_ref_idx] = var_ref; + if (sf->js_mode & JS_MODE_ASYNC) { + JSAsyncFunctionState *async_func = container_of(sf, JSAsyncFunctionState, frame); + /* The stack frame is detached and may be destroyed at any + time so its reference count must be increased. Calling + close_var_refs() when destroying the stack frame is not + possible because it would change the graph between the GC + objects. Another solution could be to temporarily detach + the JSVarRef of async functions during the GC. It would + have the advantage of allowing the release of unused stack + frames in a cycle. */ + async_func->header.ref_count++; + } + var_ref->pvalue = pvalue; + return var_ref; +} + +static void js_global_object_finalizer(JSRuntime *rt, JSValue obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + JS_FreeValueRT(rt, p->u.global_object.uninitialized_vars); +} + +static void js_global_object_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_MarkValue(rt, p->u.global_object.uninitialized_vars, mark_func); +} + +static JSVarRef *js_global_object_get_uninitialized_var(JSContext *ctx, JSObject *p1, + JSAtom atom) +{ + JSObject *p = JS_VALUE_GET_OBJ(p1->u.global_object.uninitialized_vars); + JSShapeProperty *prs; + JSProperty *pr; + JSVarRef *var_ref; + + prs = find_own_property(&pr, p, atom); + if (prs) { + assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); + var_ref = pr->u.var_ref; + var_ref->header.ref_count++; + return var_ref; + } + + var_ref = js_create_var_ref(ctx, TRUE); + if (!var_ref) + return NULL; + pr = add_property(ctx, p, atom, JS_PROP_C_W_E | JS_PROP_VARREF); + if (unlikely(!pr)) { + free_var_ref(ctx->rt, var_ref); + return NULL; + } + pr->u.var_ref = var_ref; + var_ref->header.ref_count++; + return var_ref; +} + +/* return a new variable reference. Get it from the uninitialized + variables if it is present. Return NULL in case of memory error. */ +static JSVarRef *js_global_object_find_uninitialized_var(JSContext *ctx, JSObject *p, + JSAtom atom, BOOL is_lexical) +{ + JSObject *p1; + JSShapeProperty *prs; + JSProperty *pr; + JSVarRef *var_ref; + + p1 = JS_VALUE_GET_OBJ(p->u.global_object.uninitialized_vars); + prs = find_own_property(&pr, p1, atom); + if (prs) { + assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); + var_ref = pr->u.var_ref; + var_ref->header.ref_count++; + delete_property(ctx, p1, atom); + if (!is_lexical) + var_ref->value = JS_UNDEFINED; + } else { + var_ref = js_create_var_ref(ctx, is_lexical); + if (!var_ref) + return NULL; + } + return var_ref; +} + +static JSVarRef *js_closure_define_global_var(JSContext *ctx, JSClosureVar *cv, + BOOL is_direct_or_indirect_eval) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + int flags; + JSProperty *pr; + JSVarRef *var_ref; + + if (cv->is_lexical) { + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + flags = JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE; + if (!cv->is_const) + flags |= JS_PROP_WRITABLE; + + prs = find_own_property(&pr, p, cv->var_name); + if (prs) { + assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); + var_ref = pr->u.var_ref; + var_ref->header.ref_count++; + return var_ref; + } + + /* if there is a corresponding global variable, reuse its + reference and create a new one for the global variable */ + p1 = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property(&pr, p1, cv->var_name); + if (prs && (prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSVarRef *var_ref1; + var_ref1 = js_create_var_ref(ctx, FALSE); + if (!var_ref1) + return NULL; + var_ref = pr->u.var_ref; + var_ref1->value = var_ref->value; + var_ref->value = JS_UNINITIALIZED; + pr->u.var_ref = var_ref1; + goto add_var_ref; + } + } else { + p = JS_VALUE_GET_OBJ(ctx->global_obj); + flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE; + if (is_direct_or_indirect_eval) + flags |= JS_PROP_CONFIGURABLE; + + retry: + prs = find_own_property(&pr, p, cv->var_name); + if (prs) { + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT)) { + if (JS_AutoInitProperty(ctx, p, cv->var_name, pr, prs)) + return NULL; + goto retry; + } else if ((prs->flags & JS_PROP_TMASK) != JS_PROP_VARREF) { + var_ref = js_global_object_get_uninitialized_var(ctx, p, cv->var_name); + if (!var_ref) + return NULL; + } else { + var_ref = pr->u.var_ref; + var_ref->header.ref_count++; + } + if (cv->var_kind == JS_VAR_GLOBAL_FUNCTION_DECL && + (prs->flags & JS_PROP_CONFIGURABLE)) { + /* update the property flags if possible when + declaring a global function */ + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + free_property(ctx->rt, pr, prs->flags); + prs->flags = flags | JS_PROP_VARREF; + pr->u.var_ref = var_ref; + var_ref->header.ref_count++; + } else { + assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); + prs->flags = (prs->flags & ~JS_PROP_C_W_E) | flags; + } + var_ref->is_const = FALSE; + } + return var_ref; + } + + if (!p->extensible) { + return js_global_object_get_uninitialized_var(ctx, p, cv->var_name); + } + } + + /* if there is a corresponding uninitialized variable, use it */ + p1 = JS_VALUE_GET_OBJ(ctx->global_obj); + var_ref = js_global_object_find_uninitialized_var(ctx, p1, cv->var_name, cv->is_lexical); + if (!var_ref) + return NULL; + add_var_ref: + if (cv->is_lexical) { + var_ref->is_lexical = TRUE; + var_ref->is_const = cv->is_const; + } + + pr = add_property(ctx, p, cv->var_name, flags | JS_PROP_VARREF); + if (unlikely(!pr)) { + free_var_ref(ctx->rt, var_ref); + return NULL; + } + pr->u.var_ref = var_ref; + var_ref->header.ref_count++; + return var_ref; +} + +static JSVarRef *js_closure_global_var(JSContext *ctx, JSClosureVar *cv) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSVarRef *var_ref; + + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, cv->var_name); + if (prs) { + assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); + var_ref = pr->u.var_ref; + var_ref->header.ref_count++; + return var_ref; + } + p = JS_VALUE_GET_OBJ(ctx->global_obj); + redo: + prs = find_own_property(&pr, p, cv->var_name); + if (prs) { + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT)) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, cv->var_name, pr, prs)) + return NULL; + goto redo; + } + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + var_ref = pr->u.var_ref; + var_ref->header.ref_count++; + return var_ref; + } + } + return js_global_object_get_uninitialized_var(ctx, p, cv->var_name); +} + +static JSValue js_closure2(JSContext *ctx, JSValue func_obj, + JSFunctionBytecode *b, + JSVarRef **cur_var_refs, + JSStackFrame *sf, + BOOL is_eval, JSModuleDef *m) +{ + JSObject *p; + JSVarRef **var_refs; + int i; + + p = JS_VALUE_GET_OBJ(func_obj); + p->u.func.function_bytecode = b; + p->u.func.home_object = NULL; + p->u.func.var_refs = NULL; + if (b->closure_var_count) { + var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count); + if (!var_refs) + goto fail; + p->u.func.var_refs = var_refs; + if (is_eval) { + /* first pass to check the global variable definitions */ + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + if (cv->closure_type == JS_CLOSURE_GLOBAL_DECL) { + int flags; + flags = 0; + if (cv->is_lexical) + flags |= DEFINE_GLOBAL_LEX_VAR; + if (cv->var_kind == JS_VAR_GLOBAL_FUNCTION_DECL) + flags |= DEFINE_GLOBAL_FUNC_VAR; + if (JS_CheckDefineGlobalVar(ctx, cv->var_name, flags)) + goto fail; + } + } + } + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + JSVarRef *var_ref; + switch(cv->closure_type) { + case JS_CLOSURE_MODULE_IMPORT: + /* imported from other modules */ + continue; + case JS_CLOSURE_MODULE_DECL: + var_ref = js_create_var_ref(ctx, cv->is_lexical); + break; + case JS_CLOSURE_GLOBAL_DECL: + var_ref = js_closure_define_global_var(ctx, cv, b->is_direct_or_indirect_eval); + break; + case JS_CLOSURE_GLOBAL: + var_ref = js_closure_global_var(ctx, cv); + break; + case JS_CLOSURE_LOCAL: + /* reuse the existing variable reference if it already exists */ + var_ref = get_var_ref(ctx, sf, cv->var_idx, FALSE); + break; + case JS_CLOSURE_ARG: + /* reuse the existing variable reference if it already exists */ + var_ref = get_var_ref(ctx, sf, cv->var_idx, TRUE); + break; + case JS_CLOSURE_REF: + case JS_CLOSURE_GLOBAL_REF: + var_ref = cur_var_refs[cv->var_idx]; + var_ref->header.ref_count++; + break; + default: + abort(); + } + if (!var_ref) + goto fail; + var_refs[i] = var_ref; + } + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) +{ + JSValue obj, this_val; + int ret; + + this_val = JS_MKPTR(JS_TAG_OBJECT, p); + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + set_cycle_flag(ctx, obj); + set_cycle_flag(ctx, this_val); + ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor, + JS_DupValue(ctx, this_val), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (ret < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static const uint16_t func_kind_to_class_id[] = { + [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION, + [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION, + [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION, + [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION, +}; + +static JSValue js_closure(JSContext *ctx, JSValue bfunc, + JSVarRef **cur_var_refs, + JSStackFrame *sf, BOOL is_eval) +{ + JSFunctionBytecode *b; + JSValue func_obj; + JSAtom name_atom; + + b = JS_VALUE_GET_PTR(bfunc); + func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]); + if (JS_IsException(func_obj)) { + JS_FreeValue(ctx, bfunc); + return JS_EXCEPTION; + } + func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf, is_eval, NULL); + if (JS_IsException(func_obj)) { + /* bfunc has been freed */ + goto fail; + } + name_atom = b->func_name; + if (name_atom == JS_ATOM_NULL) + name_atom = JS_ATOM_empty_string; + js_function_set_properties(ctx, func_obj, name_atom, + b->defined_arg_count); + + if (b->func_kind & JS_FUNC_GENERATOR) { + JSValue proto; + int proto_class_id; + /* generators have a prototype field which is used as + prototype for the generator object */ + if (b->func_kind == JS_FUNC_ASYNC_GENERATOR) + proto_class_id = JS_CLASS_ASYNC_GENERATOR; + else + proto_class_id = JS_CLASS_GENERATOR; + proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]); + if (JS_IsException(proto)) + goto fail; + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto, + JS_PROP_WRITABLE); + } else if (b->has_prototype) { + /* add the 'prototype' property: delay instantiation to avoid + creating cycles for every javascript function. The prototype + object is created on the fly when first accessed */ + JS_SetConstructorBit(ctx, func_obj, TRUE); + JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype, + JS_AUTOINIT_ID_PROTOTYPE, NULL, + JS_PROP_WRITABLE); + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +#define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0) + +static int js_op_define_class(JSContext *ctx, JSValue *sp, + JSAtom class_name, int class_flags, + JSVarRef **cur_var_refs, + JSStackFrame *sf, BOOL is_computed_name) +{ + JSValue bfunc, parent_class, proto = JS_UNDEFINED; + JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED; + JSFunctionBytecode *b; + + parent_class = sp[-2]; + bfunc = sp[-1]; + + if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) { + if (JS_IsNull(parent_class)) { + parent_proto = JS_NULL; + parent_class = JS_DupValue(ctx, ctx->function_proto); + } else { + if (!JS_IsConstructor(ctx, parent_class)) { + JS_ThrowTypeError(ctx, "parent class must be constructor"); + goto fail; + } + parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype); + if (JS_IsException(parent_proto)) + goto fail; + if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) { + JS_ThrowTypeError(ctx, "parent prototype must be an object or null"); + goto fail; + } + } + } else { + /* parent_class is JS_UNDEFINED in this case */ + parent_proto = JS_DupValue(ctx, ctx->class_proto[JS_CLASS_OBJECT]); + parent_class = JS_DupValue(ctx, ctx->function_proto); + } + proto = JS_NewObjectProto(ctx, parent_proto); + if (JS_IsException(proto)) + goto fail; + + b = JS_VALUE_GET_PTR(bfunc); + assert(b->func_kind == JS_FUNC_NORMAL); + ctor = JS_NewObjectProtoClass(ctx, parent_class, + JS_CLASS_BYTECODE_FUNCTION); + if (JS_IsException(ctor)) + goto fail; + ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf, FALSE, NULL); + bfunc = JS_UNDEFINED; + if (JS_IsException(ctor)) + goto fail; + js_method_set_home_object(ctx, ctor, proto); + JS_SetConstructorBit(ctx, ctor, TRUE); + + JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length, + JS_NewInt32(ctx, b->defined_arg_count), + JS_PROP_CONFIGURABLE); + + if (is_computed_name) { + if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3], + JS_PROP_CONFIGURABLE) < 0) + goto fail; + } else { + if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0) + goto fail; + } + + /* the constructor property must be first. It can be overriden by + computed property names */ + if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, + JS_DupValue(ctx, ctor), + JS_PROP_CONFIGURABLE | + JS_PROP_WRITABLE | JS_PROP_THROW) < 0) + goto fail; + /* set the prototype property */ + if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype, + JS_DupValue(ctx, proto), JS_PROP_THROW) < 0) + goto fail; + set_cycle_flag(ctx, ctor); + set_cycle_flag(ctx, proto); + + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, parent_class); + + sp[-2] = ctor; + sp[-1] = proto; + return 0; + fail: + JS_FreeValue(ctx, parent_class); + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, bfunc); + JS_FreeValue(ctx, proto); + JS_FreeValue(ctx, ctor); + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static void close_var_ref(JSRuntime *rt, JSStackFrame *sf, JSVarRef *var_ref) +{ + if (sf->js_mode & JS_MODE_ASYNC) { + JSAsyncFunctionState *async_func = container_of(sf, JSAsyncFunctionState, frame); + async_func_free(rt, async_func); + } + var_ref->value = JS_DupValueRT(rt, *var_ref->pvalue); + var_ref->pvalue = &var_ref->value; + /* the reference is no longer to a local variable */ + var_ref->is_detached = TRUE; +} + +static void close_var_refs(JSRuntime *rt, JSFunctionBytecode *b, JSStackFrame *sf) +{ + JSVarRef *var_ref; + int i; + + for(i = 0; i < b->var_ref_count; i++) { + var_ref = sf->var_refs[i]; + if (var_ref) + close_var_ref(rt, sf, var_ref); + } +} + +static void close_lexical_var(JSContext *ctx, JSFunctionBytecode *b, + JSStackFrame *sf, int var_idx) +{ + JSVarRef *var_ref; + int var_ref_idx; + + var_ref_idx = b->vardefs[b->arg_count + var_idx].var_ref_idx; + var_ref = sf->var_refs[var_ref_idx]; + if (var_ref) { + close_var_ref(ctx->rt, sf, var_ref); + sf->var_refs[var_ref_idx] = NULL; + } +} + +#define JS_CALL_FLAG_COPY_ARGV (1 << 1) +#define JS_CALL_FLAG_GENERATOR (1 << 2) + +static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSRuntime *rt = ctx->rt; + JSCFunctionType func; + JSObject *p; + JSStackFrame sf_s, *sf = &sf_s, *prev_sf; + JSValue ret_val; + JSValueConst *arg_buf; + int arg_count, i; + JSCFunctionEnum cproto; + + p = JS_VALUE_GET_OBJ(func_obj); + cproto = p->u.cfunc.cproto; + arg_count = p->u.cfunc.length; + + /* better to always check stack overflow */ + if (js_check_stack_overflow(rt, sizeof(arg_buf[0]) * arg_count)) + return JS_ThrowStackOverflow(ctx); + + prev_sf = rt->current_stack_frame; + sf->prev_frame = prev_sf; + rt->current_stack_frame = sf; + ctx = p->u.cfunc.realm; /* change the current realm */ + sf->js_mode = 0; + sf->cur_func = (JSValue)func_obj; + sf->arg_count = argc; + arg_buf = argv; + + if (unlikely(argc < arg_count)) { + /* ensure that at least argc_count arguments are readable */ + arg_buf = alloca(sizeof(arg_buf[0]) * arg_count); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = arg_count; + } + sf->arg_buf = (JSValue*)arg_buf; + + func = p->u.cfunc.c_function; + switch(cproto) { + case JS_CFUNC_constructor: + case JS_CFUNC_constructor_or_func: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor) { + not_a_constructor: + ret_val = JS_ThrowTypeError(ctx, "must be called with new"); + break; + } else { + this_obj = JS_UNDEFINED; + } + } + /* here this_obj is new_target */ + /* fall thru */ + case JS_CFUNC_generic: + ret_val = func.generic(ctx, this_obj, argc, arg_buf); + break; + case JS_CFUNC_constructor_magic: + case JS_CFUNC_constructor_or_func_magic: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor_magic) { + goto not_a_constructor; + } else { + this_obj = JS_UNDEFINED; + } + } + /* fall thru */ + case JS_CFUNC_generic_magic: + ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf, + p->u.cfunc.magic); + break; + case JS_CFUNC_getter: + ret_val = func.getter(ctx, this_obj); + break; + case JS_CFUNC_setter: + ret_val = func.setter(ctx, this_obj, arg_buf[0]); + break; + case JS_CFUNC_getter_magic: + ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic); + break; + case JS_CFUNC_setter_magic: + ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic); + break; + case JS_CFUNC_f_f: + { + double d1; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = JS_NewFloat64(ctx, func.f_f(d1)); + } + break; + case JS_CFUNC_f_f_f: + { + double d1, d2; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = JS_NewFloat64(ctx, func.f_f_f(d1, d2)); + } + break; + case JS_CFUNC_iterator_next: + { + int done; + ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf, + &done, p->u.cfunc.magic); + if (!JS_IsException(ret_val) && done != 2) { + ret_val = js_create_iterator_result(ctx, ret_val, done); + } + } + break; + default: + abort(); + } + + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSObject *p; + JSBoundFunction *bf; + JSValueConst *arg_buf, new_target; + int arg_count, i; + + p = JS_VALUE_GET_OBJ(func_obj); + bf = p->u.bound_function; + arg_count = bf->argc + argc; + if (js_check_stack_overflow(ctx->rt, sizeof(JSValue) * arg_count)) + return JS_ThrowStackOverflow(ctx); + arg_buf = alloca(sizeof(JSValue) * arg_count); + for(i = 0; i < bf->argc; i++) { + arg_buf[i] = bf->argv[i]; + } + for(i = 0; i < argc; i++) { + arg_buf[bf->argc + i] = argv[i]; + } + if (flags & JS_CALL_FLAG_CONSTRUCTOR) { + new_target = this_obj; + if (js_same_value(ctx, func_obj, new_target)) + new_target = bf->func_obj; + return JS_CallConstructor2(ctx, bf->func_obj, new_target, + arg_count, arg_buf); + } else { + return JS_Call(ctx, bf->func_obj, bf->this_val, + arg_count, arg_buf); + } +} + +/* argument of OP_special_object */ +typedef enum { + OP_SPECIAL_OBJECT_ARGUMENTS, + OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS, + OP_SPECIAL_OBJECT_THIS_FUNC, + OP_SPECIAL_OBJECT_NEW_TARGET, + OP_SPECIAL_OBJECT_HOME_OBJECT, + OP_SPECIAL_OBJECT_VAR_OBJECT, + OP_SPECIAL_OBJECT_IMPORT_META, +} OPSpecialObjectEnum; + +#define FUNC_RET_AWAIT 0 +#define FUNC_RET_YIELD 1 +#define FUNC_RET_YIELD_STAR 2 +#define FUNC_RET_INITIAL_YIELD 3 + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValue *argv, int flags) +{ + JSRuntime *rt = caller_ctx->rt; + JSContext *ctx; + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame sf_s, *sf = &sf_s; + const uint8_t *pc; + int opcode, arg_allocated_size, i; + JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; + JSVarRef **var_refs; + size_t alloca_size; + +#if !DIRECT_DISPATCH +#define SWITCH(pc) switch (opcode = *pc++) +#define CASE(op) case op +#define DEFAULT default +#define BREAK break +#else + static const void * const dispatch_table[256] = { +#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, +#if SHORT_OPCODES +#define def(id, size, n_pop, n_push, f) +#else +#define def(id, size, n_pop, n_push, f) && case_default, +#endif +#include "quickjs-opcode.h" + [ OP_COUNT ... 255 ] = &&case_default + }; +#define SWITCH(pc) goto *dispatch_table[opcode = *pc++]; +#define CASE(op) case_ ## op +#define DEFAULT case_default +#define BREAK SWITCH(pc) +#endif + + if (js_poll_interrupts(caller_ctx)) + return JS_EXCEPTION; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { + if (flags & JS_CALL_FLAG_GENERATOR) { + JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); + /* func_obj get contains a pointer to JSFuncAsyncState */ + /* the stack frame is already allocated */ + sf = &s->frame; + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = p->u.func.function_bytecode; + ctx = b->realm; + var_refs = p->u.func.var_refs; + local_buf = arg_buf = sf->arg_buf; + var_buf = sf->var_buf; + stack_buf = sf->var_buf + b->var_count; + sp = sf->cur_sp; + sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */ + pc = sf->cur_pc; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + if (s->throw_flag) + goto exception; + else + goto restart; + } else { + goto not_a_function; + } + } + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeError(caller_ctx, "not a function"); + } + return call_func(caller_ctx, func_obj, this_obj, argc, + (JSValueConst *)argv, flags); + } + b = p->u.func.function_bytecode; + + if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { + arg_allocated_size = b->arg_count; + } else { + arg_allocated_size = 0; + } + + alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count + + b->stack_size) + + sizeof(JSVarRef *) * b->var_ref_count; + if (js_check_stack_overflow(rt, alloca_size)) + return JS_ThrowStackOverflow(caller_ctx); + + sf->js_mode = b->js_mode; + arg_buf = argv; + sf->arg_count = argc; + sf->cur_func = (JSValue)func_obj; + var_refs = p->u.func.var_refs; + + local_buf = alloca(alloca_size); + if (unlikely(arg_allocated_size)) { + int n = min_int(argc, b->arg_count); + arg_buf = local_buf; + for(i = 0; i < n; i++) + arg_buf[i] = JS_DupValue(caller_ctx, argv[i]); + for(; i < b->arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = b->arg_count; + } + var_buf = local_buf + arg_allocated_size; + sf->var_buf = var_buf; + sf->arg_buf = arg_buf; + + for(i = 0; i < b->var_count; i++) + var_buf[i] = JS_UNDEFINED; + + stack_buf = var_buf + b->var_count; + sf->var_refs = (JSVarRef **)(stack_buf + b->stack_size); + for(i = 0; i < b->var_ref_count; i++) + sf->var_refs[i] = NULL; + sp = stack_buf; + pc = b->byte_code_buf; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + ctx = b->realm; /* set the current realm */ + + restart: + for(;;) { + int call_argc; + JSValue *call_argv; + + SWITCH(pc) { + CASE(OP_push_i32): + *sp++ = JS_NewInt32(ctx, get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_push_bigint_i32): + *sp++ = __JS_NewShortBigInt(ctx, (int)get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_push_const): + *sp++ = JS_DupValue(ctx, b->cpool[get_u32(pc)]); + pc += 4; + BREAK; +#if SHORT_OPCODES + CASE(OP_push_minus1): + CASE(OP_push_0): + CASE(OP_push_1): + CASE(OP_push_2): + CASE(OP_push_3): + CASE(OP_push_4): + CASE(OP_push_5): + CASE(OP_push_6): + CASE(OP_push_7): + *sp++ = JS_NewInt32(ctx, opcode - OP_push_0); + BREAK; + CASE(OP_push_i8): + *sp++ = JS_NewInt32(ctx, get_i8(pc)); + pc += 1; + BREAK; + CASE(OP_push_i16): + *sp++ = JS_NewInt32(ctx, get_i16(pc)); + pc += 2; + BREAK; + CASE(OP_push_const8): + *sp++ = JS_DupValue(ctx, b->cpool[*pc++]); + BREAK; + CASE(OP_fclosure8): + *sp++ = js_closure(ctx, JS_DupValue(ctx, b->cpool[*pc++]), var_refs, sf, FALSE); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_push_empty_string): + *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string); + BREAK; +#endif + CASE(OP_push_atom_value): + *sp++ = JS_AtomToValue(ctx, get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_undefined): + *sp++ = JS_UNDEFINED; + BREAK; + CASE(OP_null): + *sp++ = JS_NULL; + BREAK; + CASE(OP_push_this): + /* OP_push_this is only called at the start of a function */ + { + JSValue val; + if (!(b->js_mode & JS_MODE_STRICT)) { + uint32_t tag = JS_VALUE_GET_TAG(this_obj); + if (likely(tag == JS_TAG_OBJECT)) + goto normal_this; + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) { + val = JS_DupValue(ctx, ctx->global_obj); + } else { + val = JS_ToObject(ctx, this_obj); + if (JS_IsException(val)) + goto exception; + } + } else { + normal_this: + val = JS_DupValue(ctx, this_obj); + } + *sp++ = val; + } + BREAK; + CASE(OP_push_false): + *sp++ = JS_FALSE; + BREAK; + CASE(OP_push_true): + *sp++ = JS_TRUE; + BREAK; + CASE(OP_object): + *sp++ = JS_NewObject(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_special_object): + { + int arg = *pc++; + switch(arg) { + case OP_SPECIAL_OBJECT_ARGUMENTS: + *sp++ = js_build_arguments(ctx, argc, (JSValueConst *)argv); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS: + *sp++ = js_build_mapped_arguments(ctx, argc, (JSValueConst *)argv, + sf, min_int(argc, b->arg_count)); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_THIS_FUNC: + *sp++ = JS_DupValue(ctx, sf->cur_func); + break; + case OP_SPECIAL_OBJECT_NEW_TARGET: + *sp++ = JS_DupValue(ctx, new_target); + break; + case OP_SPECIAL_OBJECT_HOME_OBJECT: + { + JSObject *p1; + p1 = p->u.func.home_object; + if (unlikely(!p1)) + *sp++ = JS_UNDEFINED; + else + *sp++ = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + break; + case OP_SPECIAL_OBJECT_VAR_OBJECT: + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_IMPORT_META: + *sp++ = js_import_meta(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + default: + abort(); + } + } + BREAK; + CASE(OP_rest): + { + int first = get_u16(pc); + pc += 2; + first = min_int(first, argc); + *sp++ = js_create_array(ctx, argc - first, (JSValueConst *)(argv + first)); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; + + CASE(OP_drop): + JS_FreeValue(ctx, sp[-1]); + sp--; + BREAK; + CASE(OP_nip): + JS_FreeValue(ctx, sp[-2]); + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_nip1): /* a b c -> b c */ + JS_FreeValue(ctx, sp[-3]); + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_dup): + sp[0] = JS_DupValue(ctx, sp[-1]); + sp++; + BREAK; + CASE(OP_dup2): /* a b -> a b a b */ + sp[0] = JS_DupValue(ctx, sp[-2]); + sp[1] = JS_DupValue(ctx, sp[-1]); + sp += 2; + BREAK; + CASE(OP_dup3): /* a b c -> a b c a b c */ + sp[0] = JS_DupValue(ctx, sp[-3]); + sp[1] = JS_DupValue(ctx, sp[-2]); + sp[2] = JS_DupValue(ctx, sp[-1]); + sp += 3; + BREAK; + CASE(OP_dup1): /* a b -> a a b */ + sp[0] = sp[-1]; + sp[-1] = JS_DupValue(ctx, sp[-2]); + sp++; + BREAK; + CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = JS_DupValue(ctx, sp[0]); + sp++; + BREAK; + CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = JS_DupValue(ctx, sp[0]); + sp++; + BREAK; + CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = JS_DupValue(ctx, sp[0]); + sp++; + BREAK; + CASE(OP_perm3): /* obj a b -> a obj b (213) */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_rot3l): /* x a b -> a b x (231) */ + { + JSValue tmp; + tmp = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot4l): /* x a b c -> a b c x */ + { + JSValue tmp; + tmp = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot5l): /* x a b c d -> a b c d x */ + { + JSValue tmp; + tmp = sp[-5]; + sp[-5] = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot3r): /* a b x -> x a b (312) */ + { + JSValue tmp; + tmp = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_perm4): /* obj prop a b -> a obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = tmp; + } + BREAK; + CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = sp[-5]; + sp[-5] = tmp; + } + BREAK; + CASE(OP_swap): /* a b -> b a */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_swap2): /* a b c d -> c d a b */ + { + JSValue tmp1, tmp2; + tmp1 = sp[-4]; + tmp2 = sp[-3]; + sp[-4] = sp[-2]; + sp[-3] = sp[-1]; + sp[-2] = tmp1; + sp[-1] = tmp2; + } + BREAK; + + CASE(OP_fclosure): + { + JSValue bfunc = JS_DupValue(ctx, b->cpool[get_u32(pc)]); + pc += 4; + *sp++ = js_closure(ctx, bfunc, var_refs, sf, FALSE); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; +#if SHORT_OPCODES + CASE(OP_call0): + CASE(OP_call1): + CASE(OP_call2): + CASE(OP_call3): + call_argc = opcode - OP_call0; + goto has_call_argc; +#endif + CASE(OP_call): + CASE(OP_tail_call): + { + call_argc = get_u16(pc); + pc += 2; + goto has_call_argc; + has_call_argc: + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call) + goto done; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_constructor): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallConstructorInternal(ctx, call_argv[-2], + call_argv[-1], + call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_method): + CASE(OP_tail_call_method): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2], + JS_UNDEFINED, call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call_method) + goto done; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_array_from): + call_argc = get_u16(pc); + pc += 2; + ret_val = js_create_array_free(ctx, call_argc, sp - call_argc); + sp -= call_argc; + if (unlikely(JS_IsException(ret_val))) + goto exception; + *sp++ = ret_val; + BREAK; + + CASE(OP_apply): + { + int magic; + magic = get_u16(pc); + pc += 2; + sf->cur_pc = pc; + + ret_val = js_function_apply(ctx, sp[-3], 2, (JSValueConst *)&sp[-2], magic); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + *sp++ = ret_val; + } + BREAK; + CASE(OP_return): + ret_val = *--sp; + goto done; + CASE(OP_return_undef): + ret_val = JS_UNDEFINED; + goto done; + + CASE(OP_check_ctor_return): + /* return TRUE if 'this' should be returned */ + if (!JS_IsObject(sp[-1])) { + if (!JS_IsUndefined(sp[-1])) { + JS_ThrowTypeError(caller_ctx, "derived class constructor must return an object or undefined"); + goto exception; + } + sp[0] = JS_TRUE; + } else { + sp[0] = JS_FALSE; + } + sp++; + BREAK; + CASE(OP_check_ctor): + if (JS_IsUndefined(new_target)) { + non_ctor_call: + JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'"); + goto exception; + } + BREAK; + CASE(OP_init_ctor): + { + JSValue super, ret; + sf->cur_pc = pc; + if (JS_IsUndefined(new_target)) + goto non_ctor_call; + super = JS_GetPrototype(ctx, func_obj); + if (JS_IsException(super)) + goto exception; + ret = JS_CallConstructor2(ctx, super, new_target, argc, (JSValueConst *)argv); + JS_FreeValue(ctx, super); + if (JS_IsException(ret)) + goto exception; + *sp++ = ret; + } + BREAK; + CASE(OP_check_brand): + { + int ret = JS_CheckBrand(ctx, sp[-2], sp[-1]); + if (ret < 0) + goto exception; + if (!ret) { + JS_ThrowTypeError(ctx, "invalid brand on object"); + goto exception; + } + } + BREAK; + CASE(OP_add_brand): + if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + BREAK; + + CASE(OP_throw): + JS_Throw(ctx, *--sp); + goto exception; + + CASE(OP_throw_error): +#define JS_THROW_VAR_RO 0 +#define JS_THROW_VAR_REDECL 1 +#define JS_THROW_VAR_UNINITIALIZED 2 +#define JS_THROW_ERROR_DELETE_SUPER 3 +#define JS_THROW_ERROR_ITERATOR_THROW 4 + { + JSAtom atom; + int type; + atom = get_u32(pc); + type = pc[4]; + pc += 5; + if (type == JS_THROW_VAR_RO) + JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom); + else + if (type == JS_THROW_VAR_REDECL) + JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom); + else + if (type == JS_THROW_VAR_UNINITIALIZED) + JS_ThrowReferenceErrorUninitialized(ctx, atom); + else + if (type == JS_THROW_ERROR_DELETE_SUPER) + JS_ThrowReferenceError(ctx, "unsupported reference to 'super'"); + else + if (type == JS_THROW_ERROR_ITERATOR_THROW) + JS_ThrowTypeError(ctx, "iterator does not have a throw method"); + else + JS_ThrowInternalError(ctx, "invalid throw var type %d", type); + } + goto exception; + + CASE(OP_eval): + { + JSValueConst obj; + int scope_idx; + call_argc = get_u16(pc); + scope_idx = get_u16(pc + 2) + ARG_SCOPE_END; + pc += 4; + call_argv = sp - call_argc; + sf->cur_pc = pc; + if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) { + if (call_argc >= 1) + obj = call_argv[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, call_argv, 0); + } + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + /* could merge with OP_apply */ + CASE(OP_apply_eval): + { + int scope_idx; + uint32_t len; + JSValue *tab; + JSValueConst obj; + + scope_idx = get_u16(pc) + ARG_SCOPE_END; + pc += 2; + sf->cur_pc = pc; + tab = build_arg_list(ctx, &len, sp[-1]); + if (!tab) + goto exception; + if (js_same_value(ctx, sp[-2], ctx->eval_obj)) { + if (len >= 1) + obj = tab[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, len, + (JSValueConst *)tab); + } + free_arg_list(ctx, tab, len); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_regexp): + { + sp[-2] = JS_NewRegexp(ctx, sp[-2], sp[-1]); + sp--; + if (JS_IsException(sp[-1])) + goto exception; + } + BREAK; + + CASE(OP_get_super): + { + JSValue proto; + sf->cur_pc = pc; + proto = JS_GetPrototype(ctx, sp[-1]); + if (JS_IsException(proto)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = proto; + } + BREAK; + + CASE(OP_import): + { + JSValue val; + sf->cur_pc = pc; + val = js_dynamic_import(ctx, sp[-2], sp[-1]); + if (JS_IsException(val)) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp--; + sp[-1] = val; + } + BREAK; + + CASE(OP_get_var_undef): + CASE(OP_get_var): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JSClosureVar *cv = &b->closure_var[idx]; + if (cv->is_lexical) { + JS_ThrowReferenceErrorUninitialized(ctx, cv->var_name); + goto exception; + } else { + sf->cur_pc = pc; + sp[0] = JS_GetPropertyInternal(ctx, ctx->global_obj, + cv->var_name, + ctx->global_obj, + opcode - OP_get_var_undef); + if (JS_IsException(sp[0])) + goto exception; + } + } else { + sp[0] = JS_DupValue(ctx, val); + } + sp++; + } + BREAK; + + CASE(OP_put_var): + CASE(OP_put_var_init): + { + int idx, ret; + JSVarRef *var_ref; + idx = get_u16(pc); + pc += 2; + var_ref = var_refs[idx]; + if (unlikely(JS_IsUninitialized(*var_ref->pvalue) || + var_ref->is_const)) { + JSClosureVar *cv = &b->closure_var[idx]; + if (var_ref->is_lexical) { + if (opcode == OP_put_var_init) + goto put_var_ok; + if (JS_IsUninitialized(*var_ref->pvalue)) + JS_ThrowReferenceErrorUninitialized(ctx, cv->var_name); + else + JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, cv->var_name); + goto exception; + } else { + sf->cur_pc = pc; + ret = JS_HasProperty(ctx, ctx->global_obj, cv->var_name); + if (ret < 0) + goto exception; + if (ret == 0 && is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, cv->var_name); + goto exception; + } + ret = JS_SetPropertyInternal(ctx, ctx->global_obj, cv->var_name, sp[-1], + ctx->global_obj, JS_PROP_THROW_STRICT); + sp--; + if (ret < 0) + goto exception; + } + } else { + put_var_ok: + set_value(ctx, var_ref->pvalue, sp[-1]); + sp--; + } + } + BREAK; + CASE(OP_get_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = JS_DupValue(ctx, var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], JS_DupValue(ctx, sp[-1])); + } + BREAK; + CASE(OP_get_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = JS_DupValue(ctx, arg_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], JS_DupValue(ctx, sp[-1])); + } + BREAK; + +#if SHORT_OPCODES + CASE(OP_get_loc8): *sp++ = JS_DupValue(ctx, var_buf[*pc++]); BREAK; + CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; + CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], JS_DupValue(ctx, sp[-1])); BREAK; + + CASE(OP_get_loc0): *sp++ = JS_DupValue(ctx, var_buf[0]); BREAK; + CASE(OP_get_loc1): *sp++ = JS_DupValue(ctx, var_buf[1]); BREAK; + CASE(OP_get_loc2): *sp++ = JS_DupValue(ctx, var_buf[2]); BREAK; + CASE(OP_get_loc3): *sp++ = JS_DupValue(ctx, var_buf[3]); BREAK; + CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; + CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; + CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; + CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK; + CASE(OP_set_loc0): set_value(ctx, &var_buf[0], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_loc1): set_value(ctx, &var_buf[1], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_loc2): set_value(ctx, &var_buf[2], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_loc3): set_value(ctx, &var_buf[3], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_get_arg0): *sp++ = JS_DupValue(ctx, arg_buf[0]); BREAK; + CASE(OP_get_arg1): *sp++ = JS_DupValue(ctx, arg_buf[1]); BREAK; + CASE(OP_get_arg2): *sp++ = JS_DupValue(ctx, arg_buf[2]); BREAK; + CASE(OP_get_arg3): *sp++ = JS_DupValue(ctx, arg_buf[3]); BREAK; + CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK; + CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK; + CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK; + CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK; + CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_get_var_ref0): *sp++ = JS_DupValue(ctx, *var_refs[0]->pvalue); BREAK; + CASE(OP_get_var_ref1): *sp++ = JS_DupValue(ctx, *var_refs[1]->pvalue); BREAK; + CASE(OP_get_var_ref2): *sp++ = JS_DupValue(ctx, *var_refs[2]->pvalue); BREAK; + CASE(OP_get_var_ref3): *sp++ = JS_DupValue(ctx, *var_refs[3]->pvalue); BREAK; + CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK; + CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; +#endif + + CASE(OP_get_var_ref): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + sp[0] = JS_DupValue(ctx, val); + sp++; + } + BREAK; + CASE(OP_put_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, JS_DupValue(ctx, sp[-1])); + } + BREAK; + CASE(OP_get_var_ref_check): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + sp[0] = JS_DupValue(ctx, val); + sp++; + } + BREAK; + CASE(OP_put_var_ref_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_var_ref_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc_uninitialized): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], JS_UNINITIALIZED); + } + BREAK; + CASE(OP_get_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, FALSE); + goto exception; + } + sp[0] = JS_DupValue(ctx, var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_get_loc_checkthis): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, FALSE); + goto exception; + } + sp[0] = JS_DupValue(ctx, var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, FALSE); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_loc_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceError(ctx, "'this' can be initialized only once"); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_close_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + close_lexical_var(ctx, b, sf, idx); + } + BREAK; + + CASE(OP_make_loc_ref): + CASE(OP_make_arg_ref): + CASE(OP_make_var_ref_ref): + { + JSVarRef *var_ref; + JSProperty *pr; + JSAtom atom; + int idx; + atom = get_u32(pc); + idx = get_u16(pc + 4); + pc += 6; + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + if (opcode == OP_make_var_ref_ref) { + var_ref = var_refs[idx]; + var_ref->header.ref_count++; + } else { + var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); + if (!var_ref) + goto exception; + } + pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom, + JS_PROP_WRITABLE | JS_PROP_VARREF); + if (!pr) { + free_var_ref(rt, var_ref); + goto exception; + } + pr->u.var_ref = var_ref; + *sp++ = JS_AtomToValue(ctx, atom); + } + BREAK; + CASE(OP_make_var_ref): + { + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + if (JS_GetGlobalVarRef(ctx, atom, sp)) + goto exception; + sp += 2; + } + BREAK; + + CASE(OP_goto): + pc += (int32_t)get_u32(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; +#if SHORT_OPCODES + CASE(OP_goto16): + pc += (int16_t)get_u16(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_goto8): + pc += (int8_t)pc[0]; + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; +#endif + CASE(OP_if_true): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + /* quick and dirty test for JS_TAG_INT, JS_TAG_BOOL, JS_TAG_NULL and JS_TAG_UNDEFINED */ + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; +#if SHORT_OPCODES + CASE(OP_if_true8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; +#endif + CASE(OP_catch): + { + int32_t diff; + diff = get_u32(pc); + sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf); + sp++; + pc += 4; + } + BREAK; + CASE(OP_gosub): + { + int32_t diff; + diff = get_u32(pc); + /* XXX: should have a different tag to avoid security flaw */ + sp[0] = JS_NewInt32(ctx, pc + 4 - b->byte_code_buf); + sp++; + pc += diff; + } + BREAK; + CASE(OP_ret): + { + JSValue op1; + uint32_t pos; + op1 = sp[-1]; + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) + goto ret_fail; + pos = JS_VALUE_GET_INT(op1); + if (unlikely(pos >= b->byte_code_len)) { + ret_fail: + JS_ThrowInternalError(ctx, "invalid ret value"); + goto exception; + } + sp--; + pc = b->byte_code_buf + pos; + } + BREAK; + + CASE(OP_for_in_start): + sf->cur_pc = pc; + if (js_for_in_start(ctx, sp)) + goto exception; + BREAK; + CASE(OP_for_in_next): + sf->cur_pc = pc; + if (js_for_in_next(ctx, sp)) + goto exception; + sp += 2; + BREAK; + CASE(OP_for_of_start): + sf->cur_pc = pc; + if (js_for_of_start(ctx, sp, FALSE)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_for_of_next): + { + int offset = -3 - pc[0]; + pc += 1; + sf->cur_pc = pc; + if (js_for_of_next(ctx, sp, offset)) + goto exception; + sp += 2; + } + BREAK; + CASE(OP_for_await_of_next): + sf->cur_pc = pc; + if (js_for_await_of_next(ctx, sp)) + goto exception; + sp++; + BREAK; + CASE(OP_for_await_of_start): + sf->cur_pc = pc; + if (js_for_of_start(ctx, sp, TRUE)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_iterator_get_value_done): + sf->cur_pc = pc; + if (js_iterator_get_value_done(ctx, sp)) + goto exception; + sp += 1; + BREAK; + CASE(OP_iterator_check_object): + if (unlikely(!JS_IsObject(sp[-1]))) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto exception; + } + BREAK; + + CASE(OP_iterator_close): + /* iter_obj next catch_offset -> */ + sp--; /* drop the catch offset to avoid getting caught by exception */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + if (!JS_IsUndefined(sp[-1])) { + sf->cur_pc = pc; + if (JS_IteratorClose(ctx, sp[-1], FALSE)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + } + sp--; + BREAK; + CASE(OP_nip_catch): + { + JSValue ret_val; + /* catch_offset ... ret_val -> ret_eval */ + ret_val = *--sp; + while (sp > stack_buf && + JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { + JS_FreeValue(ctx, *--sp); + } + if (unlikely(sp == stack_buf)) { + JS_ThrowInternalError(ctx, "nip_catch"); + JS_FreeValue(ctx, ret_val); + goto exception; + } + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_iterator_next): + /* stack: iter_obj next catch_offset val */ + { + JSValue ret; + sf->cur_pc = pc; + ret = JS_Call(ctx, sp[-3], sp[-4], + 1, (JSValueConst *)(sp - 1)); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + } + BREAK; + + CASE(OP_iterator_call): + /* stack: iter_obj next catch_offset val */ + { + JSValue method, ret; + BOOL ret_flag; + int flags; + flags = *pc++; + sf->cur_pc = pc; + method = JS_GetProperty(ctx, sp[-4], (flags & 1) ? + JS_ATOM_throw : JS_ATOM_return); + if (JS_IsException(method)) + goto exception; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + ret_flag = TRUE; + } else { + if (flags & 2) { + /* no argument */ + ret = JS_CallFree(ctx, method, sp[-4], + 0, NULL); + } else { + ret = JS_CallFree(ctx, method, sp[-4], + 1, (JSValueConst *)(sp - 1)); + } + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + ret_flag = FALSE; + } + sp[0] = JS_NewBool(ctx, ret_flag); + sp += 1; + } + BREAK; + + CASE(OP_lnot): + { + int res; + JSValue op1; + + op1 = sp[-1]; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1) != 0; + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp[-1] = JS_NewBool(ctx, !res); + } + BREAK; + +#define GET_FIELD_INLINE(name, keep, is_length) \ + { \ + JSValue val, obj; \ + JSAtom atom; \ + JSObject *p; \ + JSProperty *pr; \ + JSShapeProperty *prs; \ + \ + if (is_length) { \ + atom = JS_ATOM_length; \ + } else { \ + atom = get_u32(pc); \ + pc += 4; \ + } \ + \ + obj = sp[-1]; \ + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { \ + p = JS_VALUE_GET_OBJ(obj); \ + for(;;) { \ + prs = find_own_property(&pr, p, atom); \ + if (prs) { \ + /* found */ \ + if (unlikely(prs->flags & JS_PROP_TMASK)) \ + goto name ## _slow_path; \ + val = JS_DupValue(ctx, pr->u.value); \ + break; \ + } \ + if (unlikely(p->is_exotic)) { \ + /* XXX: should avoid the slow path for arrays \ + and typed arrays by ensuring that 'prop' is \ + not numeric */ \ + obj = JS_MKPTR(JS_TAG_OBJECT, p); \ + goto name ## _slow_path; \ + } \ + p = p->shape->proto; \ + if (!p) { \ + val = JS_UNDEFINED; \ + break; \ + } \ + } \ + } else { \ + name ## _slow_path: \ + sf->cur_pc = pc; \ + val = JS_GetPropertyInternal(ctx, obj, atom, sp[-1], 0); \ + if (unlikely(JS_IsException(val))) \ + goto exception; \ + } \ + if (keep) { \ + *sp++ = val; \ + } else { \ + JS_FreeValue(ctx, sp[-1]); \ + sp[-1] = val; \ + } \ + } + + + CASE(OP_get_field): + GET_FIELD_INLINE(get_field, 0, 0); + BREAK; + + CASE(OP_get_field2): + GET_FIELD_INLINE(get_field2, 1, 0); + BREAK; + +#if SHORT_OPCODES + CASE(OP_get_length): + GET_FIELD_INLINE(get_length, 0, 1); + BREAK; +#endif + + CASE(OP_put_field): + { + int ret; + JSValue obj; + JSAtom atom; + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + + atom = get_u32(pc); + pc += 4; + + obj = sp[-2]; + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT)) { + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, atom); + if (!prs) + goto put_field_slow_path; + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast path */ + set_value(ctx, &pr->u.value, sp[-1]); + } else { + goto put_field_slow_path; + } + JS_FreeValue(ctx, obj); + sp -= 2; + } else { + put_field_slow_path: + sf->cur_pc = pc; + ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-1], obj, + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, obj); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + + } + BREAK; + + CASE(OP_private_symbol): + { + JSAtom atom; + JSValue val; + + atom = get_u32(pc); + pc += 4; + val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(val)) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_get_private_field): + { + JSValue val; + + val = JS_GetPrivateField(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_put_private_field): + { + int ret; + ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_private_field): + { + int ret; + ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_field): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_set_name): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_name_computed): + { + int ret; + ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_proto): + { + JSValue proto; + sf->cur_pc = pc; + proto = sp[-1]; + if (JS_IsObject(proto) || JS_IsNull(proto)) { + if (JS_SetPrototypeInternal(ctx, sp[-2], proto, TRUE) < 0) + goto exception; + } + JS_FreeValue(ctx, proto); + sp--; + } + BREAK; + CASE(OP_set_home_object): + js_method_set_home_object(ctx, sp[-1], sp[-2]); + BREAK; + CASE(OP_define_method): + CASE(OP_define_method_computed): + { + JSValue getter, setter, value; + JSValueConst obj; + JSAtom atom; + int flags, ret, op_flags; + BOOL is_computed; +#define OP_DEFINE_METHOD_METHOD 0 +#define OP_DEFINE_METHOD_GETTER 1 +#define OP_DEFINE_METHOD_SETTER 2 +#define OP_DEFINE_METHOD_ENUMERABLE 4 + + is_computed = (opcode == OP_define_method_computed); + if (is_computed) { + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + opcode += OP_define_method - OP_define_method_computed; + } else { + atom = get_u32(pc); + pc += 4; + } + op_flags = *pc++; + + obj = sp[-2 - is_computed]; + flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | + JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; + if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) + flags |= JS_PROP_ENUMERABLE; + op_flags &= 3; + value = JS_UNDEFINED; + getter = JS_UNDEFINED; + setter = JS_UNDEFINED; + if (op_flags == OP_DEFINE_METHOD_METHOD) { + value = sp[-1]; + flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; + } else if (op_flags == OP_DEFINE_METHOD_GETTER) { + getter = sp[-1]; + flags |= JS_PROP_HAS_GET; + } else { + setter = sp[-1]; + flags |= JS_PROP_HAS_SET; + } + ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj); + if (ret >= 0) { + ret = JS_DefineProperty(ctx, obj, atom, value, + getter, setter, flags); + } + JS_FreeValue(ctx, sp[-1]); + if (is_computed) { + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-2]); + } + sp -= 1 + is_computed; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_class): + CASE(OP_define_class_computed): + { + int class_flags; + JSAtom atom; + + atom = get_u32(pc); + class_flags = pc[4]; + pc += 5; + if (js_op_define_class(ctx, sp, atom, class_flags, + var_refs, sf, + (opcode == OP_define_class_computed)) < 0) + goto exception; + } + BREAK; + +#define GET_ARRAY_EL_INLINE(name, keep) \ + { \ + JSValue val, obj, prop; \ + JSObject *p; \ + uint32_t idx; \ + \ + obj = sp[-2]; \ + prop = sp[-1]; \ + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && \ + JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { \ + p = JS_VALUE_GET_OBJ(obj); \ + idx = JS_VALUE_GET_INT(prop); \ + if (unlikely(p->class_id != JS_CLASS_ARRAY)) \ + goto name ## _slow_path; \ + if (unlikely(idx >= p->u.array.count)) \ + goto name ## _slow_path; \ + val = JS_DupValue(ctx, p->u.array.u.values[idx]); \ + } else { \ + name ## _slow_path: \ + sf->cur_pc = pc; \ + val = JS_GetPropertyValue(ctx, obj, prop); \ + if (unlikely(JS_IsException(val))) { \ + if (keep) \ + sp[-1] = JS_UNDEFINED; \ + else \ + sp--; \ + goto exception; \ + } \ + } \ + if (keep) { \ + sp[-1] = val; \ + } else { \ + JS_FreeValue(ctx, obj); \ + sp[-2] = val; \ + sp--; \ + } \ + } + + CASE(OP_get_array_el): + GET_ARRAY_EL_INLINE(get_array_el, 0); + BREAK; + + CASE(OP_get_array_el2): + GET_ARRAY_EL_INLINE(get_array_el2, 1); + BREAK; + + CASE(OP_get_array_el3): + { + JSValue val; + JSObject *p; + uint32_t idx; + + if (likely(JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_INT)) { + p = JS_VALUE_GET_OBJ(sp[-2]); + idx = JS_VALUE_GET_INT(sp[-1]); + if (unlikely(p->class_id != JS_CLASS_ARRAY)) + goto get_array_el3_slow_path; + if (unlikely(idx >= p->u.array.count)) + goto get_array_el3_slow_path; + val = JS_DupValue(ctx, p->u.array.u.values[idx]); + } else { + get_array_el3_slow_path: + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + /* undefined and null are tested in JS_GetPropertyValue() */ + break; + default: + /* must be tested before JS_ToPropertyKey */ + if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) { + JS_ThrowTypeError(ctx, "value has no property"); + goto exception; + } + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], JS_DupValue(ctx, sp[-1])); + if (unlikely(JS_IsException(val))) + goto exception; + } + *sp++ = val; + } + BREAK; + + CASE(OP_get_ref_value): + { + JSValue val; + JSAtom atom; + int ret; + + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp[-1]); + if (atom == JS_ATOM_NULL) + goto exception; + if (unlikely(JS_IsUndefined(sp[-2]))) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + } + ret = JS_HasProperty(ctx, sp[-2], atom); + if (unlikely(ret <= 0)) { + if (ret < 0) { + JS_FreeAtom(ctx, atom); + goto exception; + } + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + } + val = JS_UNDEFINED; + } else { + val = JS_GetProperty(ctx, sp[-2], atom); + } + JS_FreeAtom(ctx, atom); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + sp++; + } + BREAK; + + CASE(OP_get_super_value): + { + JSValue val; + JSAtom atom; + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp[-1]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + val = JS_GetPropertyInternal(ctx, sp[-2], atom, sp[-3], FALSE); + JS_FreeAtom(ctx, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-3]); + sp[-3] = val; + sp -= 2; + } + BREAK; + + CASE(OP_put_array_el): + { + int ret; + JSObject *p; + uint32_t idx; + + if (likely(JS_VALUE_GET_TAG(sp[-3]) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_INT)) { + p = JS_VALUE_GET_OBJ(sp[-3]); + idx = JS_VALUE_GET_INT(sp[-2]); + if (unlikely(p->class_id != JS_CLASS_ARRAY)) + goto put_array_el_slow_path; + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + uint32_t new_len, array_len; + if (unlikely(idx != (uint32_t)p->u.array.count || + !p->fast_array || + !p->extensible || + p->shape->proto != JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) || + !ctx->std_array_prototype)) { + goto put_array_el_slow_path; + } + if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) != JS_TAG_INT)) + goto put_array_el_slow_path; + /* cannot overflow otherwise the length would not be an integer */ + new_len = idx + 1; + if (unlikely(new_len > p->u.array.u1.size)) + goto put_array_el_slow_path; + array_len = JS_VALUE_GET_INT(p->prop[0].u.value); + if (new_len > array_len) { + if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) + goto put_array_el_slow_path; + p->prop[0].u.value = JS_NewInt32(ctx, new_len); + } + p->u.array.count = new_len; + p->u.array.u.values[idx] = sp[-1]; + } else { + set_value(ctx, &p->u.array.u.values[idx], sp[-1]); + } + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + } else { + put_array_el_slow_path: + sf->cur_pc = pc; + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + } + BREAK; + + CASE(OP_put_ref_value): + { + int ret; + JSAtom atom; + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + if (unlikely(JS_IsUndefined(sp[-3]))) { + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + } else { + sp[-3] = JS_DupValue(ctx, ctx->global_obj); + } + } + ret = JS_HasProperty(ctx, sp[-3], atom); + if (unlikely(ret <= 0)) { + if (unlikely(ret < 0)) { + JS_FreeAtom(ctx, atom); + goto exception; + } + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + goto exception; + } + } + ret = JS_SetPropertyInternal(ctx, sp[-3], atom, sp[-1], sp[-3], JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_super_value): + { + int ret; + JSAtom atom; + sf->cur_pc = pc; + if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto exception; + } + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + ret = JS_SetPropertyInternal(ctx, sp[-3], atom, sp[-1], sp[-4], + JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-4]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + sp -= 4; + if (ret < 0) + goto exception; + } + BREAK; + + CASE(OP_define_array_el): + { + int ret; + ret = JS_DefinePropertyValueValue(ctx, sp[-3], JS_DupValue(ctx, sp[-2]), sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp -= 1; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_append): /* array pos enumobj -- array pos */ + { + sf->cur_pc = pc; + if (js_append_enumerate(ctx, sp)) + goto exception; + JS_FreeValue(ctx, *--sp); + } + BREAK; + + CASE(OP_copy_data_properties): /* target source excludeList */ + { + /* stack offsets (-1 based): + 2 bits for target, + 3 bits for source, + 2 bits for exclusionList */ + int mask; + + mask = *pc++; + sf->cur_pc = pc; + if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)], + sp[-1 - ((mask >> 2) & 7)], + sp[-1 - ((mask >> 5) & 7)], 0)) + goto exception; + } + BREAK; + + CASE(OP_add): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) { + sp[-2] = __JS_NewFloat64(ctx, (double)r); + } else { + sp[-2] = JS_NewInt32(ctx, r); + } + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) + + JS_VALUE_GET_FLOAT64(op2)); + sp--; + } else if (JS_IsString(op1) && JS_IsString(op2)) { + sp[-2] = JS_ConcatString(ctx, op1, op2); + sp--; + if (JS_IsException(sp[-1])) + goto exception; + } else { + sf->cur_pc = pc; + if (js_add_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_add_loc): + { + JSValue op2; + JSValue *pv; + int idx; + idx = *pc; + pc += 1; + + op2 = sp[-1]; + pv = &var_buf[idx]; + if (likely(JS_VALUE_IS_BOTH_INT(*pv, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(*pv) + JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) { + *pv = __JS_NewFloat64(ctx, (double)r); + } else { + *pv = JS_NewInt32(ctx, r); + } + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(*pv, op2)) { + *pv = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(*pv) + + JS_VALUE_GET_FLOAT64(op2)); + sp--; + } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING && + JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + sp--; + sf->cur_pc = pc; + if (JS_ConcatStringInPlace(ctx, JS_VALUE_GET_STRING(*pv), op2)) { + JS_FreeValue(ctx, op2); + } else { + op2 = JS_ConcatString(ctx, JS_DupValue(ctx, *pv), op2); + if (JS_IsException(op2)) + goto exception; + set_value(ctx, pv, op2); + } + } else { + JSValue ops[2]; + /* In case of exception, js_add_slow frees ops[0] + and ops[1], so we must duplicate *pv */ + sf->cur_pc = pc; + ops[0] = JS_DupValue(ctx, *pv); + ops[1] = op2; + sp--; + if (js_add_slow(ctx, ops + 2)) + goto exception; + set_value(ctx, pv, ops[0]); + } + } + BREAK; + CASE(OP_sub): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) { + sp[-2] = __JS_NewFloat64(ctx, (double)r); + } else { + sp[-2] = JS_NewInt32(ctx, r); + } + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) - + JS_VALUE_GET_FLOAT64(op2)); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mul): + { + JSValue op1, op2; + double d; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int32_t v1, v2; + int64_t r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + r = (int64_t)v1 * v2; + if (unlikely((int)r != r)) { + d = (double)r; + goto mul_fp_res; + } + /* need to test zero case for -0 result */ + if (unlikely(r == 0 && (v1 | v2) < 0)) { + d = -0.0; + goto mul_fp_res; + } + sp[-2] = JS_NewInt32(ctx, r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); + mul_fp_res: + sp[-2] = __JS_NewFloat64(ctx, d); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_div): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + sp[-2] = JS_NewFloat64(ctx, (double)v1 / (double)v2); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mod): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2, r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = + -1 and the cases where the result is -0. */ + if (unlikely(v1 < 0 || v2 <= 0)) + goto binary_arith_slow; + r = v1 % v2; + sp[-2] = JS_NewInt32(ctx, r); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_pow): + binary_arith_slow: + sf->cur_pc = pc; + if (js_binary_arith_slow(ctx, sp, opcode)) + goto exception; + sp--; + BREAK; + + CASE(OP_plus): + { + JSValue op1; + uint32_t tag; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { + } else if (tag == JS_TAG_NULL || tag == JS_TAG_BOOL) { + sp[-1] = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1)); + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_neg): + { + JSValue op1; + uint32_t tag; + int val; + double d; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT || + tag == JS_TAG_BOOL || + tag == JS_TAG_NULL) { + val = JS_VALUE_GET_INT(op1); + /* Note: -0 cannot be expressed as integer */ + if (unlikely(val == 0)) { + d = -0.0; + goto neg_fp_res; + } + if (unlikely(val == INT32_MIN)) { + d = -(double)val; + goto neg_fp_res; + } + sp[-1] = JS_NewInt32(ctx, -val); + } else if (JS_TAG_IS_FLOAT64(tag)) { + d = -JS_VALUE_GET_FLOAT64(op1); + neg_fp_res: + sp[-1] = __JS_NewFloat64(ctx, d); + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_inc): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_slow; + sp[-1] = JS_NewInt32(ctx, val + 1); + } else { + inc_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_dec): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_slow; + sp[-1] = JS_NewInt32(ctx, val - 1); + } else { + dec_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_post_inc): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto post_inc_slow; + sp[0] = JS_NewInt32(ctx, val + 1); + } else { + post_inc_slow: + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + } + sp++; + } + BREAK; + CASE(OP_post_dec): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto post_dec_slow; + sp[0] = JS_NewInt32(ctx, val - 1); + } else { + post_dec_slow: + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + } + sp++; + } + BREAK; + CASE(OP_inc_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_loc_slow; + var_buf[idx] = JS_NewInt32(ctx, val + 1); + } else { + inc_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = JS_DupValue(ctx, op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_dec_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_loc_slow; + var_buf[idx] = JS_NewInt32(ctx, val - 1); + } else { + dec_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = JS_DupValue(ctx, op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_not): + { + JSValue op1; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + sp[-1] = JS_NewInt32(ctx, ~JS_VALUE_GET_INT(op1)); + } else { + sf->cur_pc = pc; + if (js_not_slow(ctx, sp)) + goto exception; + } + } + BREAK; + + CASE(OP_shl): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32(ctx, v1 << v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_shr): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + v2 &= 0x1f; + sp[-2] = JS_NewUint32(ctx, + (uint32_t)JS_VALUE_GET_INT(op1) >> + v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_shr_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_sar): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32(ctx, + (int)JS_VALUE_GET_INT(op1) >> v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_and): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = JS_NewInt32(ctx, + JS_VALUE_GET_INT(op1) & + JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_or): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = JS_NewInt32(ctx, + JS_VALUE_GET_INT(op1) | + JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_xor): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = JS_NewInt32(ctx, + JS_VALUE_GET_INT(op1) ^ + JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + + +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ + sp[-2] = JS_NewBool(ctx, JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ + sp--; \ + } else { \ + sf->cur_pc = pc; \ + if (slow_call) \ + goto exception; \ + sp--; \ + } \ + } \ + BREAK + + OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); + OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); + OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); + OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); + + CASE(OP_in): + sf->cur_pc = pc; + if (js_operator_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_private_in): + sf->cur_pc = pc; + if (js_operator_private_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_instanceof): + sf->cur_pc = pc; + if (js_operator_instanceof(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_typeof): + { + JSValue op1; + JSAtom atom; + + op1 = sp[-1]; + atom = js_operator_typeof(ctx, op1); + JS_FreeValue(ctx, op1); + sp[-1] = JS_AtomToString(ctx, atom); + } + BREAK; + CASE(OP_delete): + sf->cur_pc = pc; + if (js_operator_delete(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_delete_var): + { + JSAtom atom; + int ret; + + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + ret = JS_DeleteGlobalVar(ctx, atom); + if (unlikely(ret < 0)) + goto exception; + *sp++ = JS_NewBool(ctx, ret); + } + BREAK; + + CASE(OP_to_object): + if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) { + sf->cur_pc = pc; + ret_val = JS_ToObject(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_to_propkey): + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; + +#if 0 + CASE(OP_to_string): + if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_STRING) { + ret_val = JS_ToString(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + } + BREAK; +#endif + CASE(OP_with_get_var): + CASE(OP_with_put_var): + CASE(OP_with_delete_var): + CASE(OP_with_make_ref): + CASE(OP_with_get_ref): + { + JSAtom atom; + int32_t diff; + JSValue obj, val; + int ret, is_with; + atom = get_u32(pc); + diff = get_u32(pc + 4); + is_with = pc[8]; + pc += 9; + sf->cur_pc = pc; + + obj = sp[-1]; + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) { + if (is_with) { + ret = js_has_unscopable(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) + goto no_with; + } + switch (opcode) { + case OP_with_get_var: + /* in Object Environment Records, GetBindingValue() calls HasProperty() */ + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret <= 0)) { + if (ret < 0) + goto exception; + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + val = JS_UNDEFINED; + } else { + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + } + set_value(ctx, &sp[-1], val); + break; + case OP_with_put_var: /* used e.g. in for in/of */ + /* in Object Environment Records, SetMutableBinding() calls HasProperty() */ + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret <= 0)) { + if (ret < 0) + goto exception; + if (is_strict_mode(ctx)) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + } + ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2], obj, + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + break; + case OP_with_delete_var: + ret = JS_DeleteProperty(ctx, obj, atom, 0); + if (unlikely(ret < 0)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_NewBool(ctx, ret); + break; + case OP_with_make_ref: + /* produce a pair object/propname on the stack */ + *sp++ = JS_AtomToValue(ctx, atom); + break; + case OP_with_get_ref: + /* produce a pair object/method on the stack */ + /* in Object Environment Records, GetBindingValue() calls HasProperty() */ + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (!ret) { + val = JS_UNDEFINED; + } else { + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + } + *sp++ = val; + break; + } + pc += diff - 5; + } else { + no_with: + /* if not jumping, drop the object argument */ + JS_FreeValue(ctx, sp[-1]); + sp--; + } + } + BREAK; + + CASE(OP_await): + ret_val = JS_NewInt32(ctx, FUNC_RET_AWAIT); + goto done_generator; + CASE(OP_yield): + ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD); + goto done_generator; + CASE(OP_yield_star): + CASE(OP_async_yield_star): + ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD_STAR); + goto done_generator; + CASE(OP_return_async): + ret_val = JS_UNDEFINED; + goto done_generator; + CASE(OP_initial_yield): + ret_val = JS_NewInt32(ctx, FUNC_RET_INITIAL_YIELD); + goto done_generator; + + CASE(OP_nop): + BREAK; + CASE(OP_is_undefined_or_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED || + JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } +#if SHORT_OPCODES + CASE(OP_is_undefined): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) { + goto set_true; + } else { + goto free_and_set_false; + } + CASE(OP_is_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } + /* XXX: could merge to a single opcode */ + CASE(OP_typeof_is_undefined): + /* different from OP_is_undefined because of isHTMLDDA */ + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_undefined) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + CASE(OP_typeof_is_function): + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + free_and_set_true: + JS_FreeValue(ctx, sp[-1]); +#endif + set_true: + sp[-1] = JS_TRUE; + BREAK; + free_and_set_false: + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_FALSE; + BREAK; + CASE(OP_invalid): + DEFAULT: + JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", + (int)(pc - b->byte_code_buf - 1), opcode); + goto exception; + } + } + exception: + if (is_backtrace_needed(ctx, rt->current_exception)) { + /* add the backtrace information now (it is not done + before if the exception happens in a bytecode + operation */ + sf->cur_pc = pc; + build_backtrace(ctx, rt->current_exception, NULL, 0, 0, 0); + } + if (!rt->current_exception_is_uncatchable) { + while (sp > stack_buf) { + JSValue val = *--sp; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { + int pos = JS_VALUE_GET_INT(val); + if (pos == 0) { + /* enumerator: close it with a throw */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + JS_IteratorClose(ctx, sp[-1], TRUE); + } else { + *sp++ = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + pc = b->byte_code_buf + pos; + goto restart; + } + } + } + } + ret_val = JS_EXCEPTION; + /* the local variables are freed by the caller in the generator + case. Hence the label 'done' should never be reached in a + generator function. */ + if (b->func_kind != JS_FUNC_NORMAL) { + done_generator: + sf->cur_pc = pc; + sf->cur_sp = sp; + } else { + done: + if (unlikely(b->var_ref_count != 0)) { + /* variable references reference the stack: must close them */ + close_var_refs(rt, b, sf); + } + /* free the local variables and stack */ + for(pval = local_buf; pval < sp; pval++) { + JS_FreeValue(ctx, *pval); + } + } + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + return JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); +} + +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); + JS_FreeValue(ctx, func_obj); + return res; +} + +/* warning: the refcount of the context is not incremented. Return + NULL in case of exception (case of revoked proxy only) */ +static JSContext *JS_GetFunctionRealm(JSContext *ctx, JSValueConst func_obj) +{ + JSObject *p; + JSContext *realm; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return ctx; + p = JS_VALUE_GET_OBJ(func_obj); + switch(p->class_id) { + case JS_CLASS_C_FUNCTION: + realm = p->u.cfunc.realm; + break; + case JS_CLASS_BYTECODE_FUNCTION: + case JS_CLASS_GENERATOR_FUNCTION: + case JS_CLASS_ASYNC_FUNCTION: + case JS_CLASS_ASYNC_GENERATOR_FUNCTION: + { + JSFunctionBytecode *b; + b = p->u.func.function_bytecode; + realm = b->realm; + } + break; + case JS_CLASS_PROXY: + { + JSProxyData *s = p->u.opaque; + if (!s) + return ctx; + if (s->is_revoked) { + JS_ThrowTypeErrorRevokedProxy(ctx); + return NULL; + } else { + realm = JS_GetFunctionRealm(ctx, s->target); + } + } + break; + case JS_CLASS_BOUND_FUNCTION: + { + JSBoundFunction *bf = p->u.bound_function; + realm = JS_GetFunctionRealm(ctx, bf->func_obj); + } + break; + default: + realm = ctx; + break; + } + return realm; +} + +static JSValue js_create_from_ctor(JSContext *ctx, JSValueConst ctor, + int class_id) +{ + JSValue proto, obj; + JSContext *realm; + + if (JS_IsUndefined(ctor)) { + proto = JS_DupValue(ctx, ctx->class_proto[class_id]); + } else { + proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(proto)) { + JS_FreeValue(ctx, proto); + realm = JS_GetFunctionRealm(ctx, ctor); + if (!realm) + return JS_EXCEPTION; + proto = JS_DupValue(ctx, realm->class_proto[class_id]); + } + } + obj = JS_NewObjectProtoClass(ctx, proto, class_id); + JS_FreeValue(ctx, proto); + return obj; +} + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValue *argv, int flags) +{ + JSObject *p; + JSFunctionBytecode *b; + + if (js_poll_interrupts(ctx)) + return JS_EXCEPTION; + flags |= JS_CALL_FLAG_CONSTRUCTOR; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) + goto not_a_function; + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(!p->is_constructor)) + return JS_ThrowTypeErrorNotAConstructor(ctx, func_obj); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = ctx->rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeError(ctx, "not a function"); + } + return call_func(ctx, func_obj, new_target, argc, + (JSValueConst *)argv, flags); + } + + b = p->u.func.function_bytecode; + if (b->is_derived_class_constructor) { + return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags); + } else { + JSValue obj, ret; + /* legacy constructor behavior */ + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); + if (JS_IsException(obj)) + return JS_EXCEPTION; + ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags); + if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT || + JS_IsException(ret)) { + JS_FreeValue(ctx, obj); + return ret; + } else { + JS_FreeValue(ctx, ret); + return obj; + } + } +} + +JSValue JS_CallConstructor2(JSContext *ctx, JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValueConst *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, new_target, + argc, (JSValue *)argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_CallConstructor(JSContext *ctx, JSValueConst func_obj, + int argc, JSValueConst *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, func_obj, + argc, (JSValue *)argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_Invoke(JSContext *ctx, JSValueConst this_val, JSAtom atom, + int argc, JSValueConst *argv) +{ + JSValue func_obj; + func_obj = JS_GetProperty(ctx, this_val, atom); + if (JS_IsException(func_obj)) + return func_obj; + return JS_CallFree(ctx, func_obj, this_val, argc, argv); +} + +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValueConst *argv) +{ + JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv); + JS_FreeValue(ctx, this_val); + return res; +} + +/* JSAsyncFunctionState (used by generator and async functions) */ +static JSAsyncFunctionState *async_func_init(JSContext *ctx, + JSValueConst func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + JSAsyncFunctionState *s; + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame *sf; + int i, arg_buf_len, n; + + p = JS_VALUE_GET_OBJ(func_obj); + b = p->u.func.function_bytecode; + arg_buf_len = max_int(b->arg_count, argc); + s = js_malloc(ctx, sizeof(*s) + sizeof(JSValue) * (arg_buf_len + b->var_count + b->stack_size) + sizeof(JSVarRef *) * b->var_ref_count); + if (!s) + return NULL; + memset(s, 0, sizeof(*s)); + s->header.ref_count = 1; + add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); + + sf = &s->frame; + sf->js_mode = b->js_mode | JS_MODE_ASYNC; + sf->cur_pc = b->byte_code_buf; + sf->arg_buf = (JSValue *)(s + 1); + sf->cur_func = JS_DupValue(ctx, func_obj); + s->this_val = JS_DupValue(ctx, this_obj); + s->argc = argc; + sf->arg_count = arg_buf_len; + sf->var_buf = sf->arg_buf + arg_buf_len; + sf->cur_sp = sf->var_buf + b->var_count; + sf->var_refs = (JSVarRef **)(sf->cur_sp + b->stack_size); + for(i = 0; i < b->var_ref_count; i++) + sf->var_refs[i] = NULL; + for(i = 0; i < argc; i++) + sf->arg_buf[i] = JS_DupValue(ctx, argv[i]); + n = arg_buf_len + b->var_count; + for(i = argc; i < n; i++) + sf->arg_buf[i] = JS_UNDEFINED; + s->resolving_funcs[0] = JS_UNDEFINED; + s->resolving_funcs[1] = JS_UNDEFINED; + s->is_completed = FALSE; + return s; +} + +static void async_func_free_frame(JSRuntime *rt, JSAsyncFunctionState *s) +{ + JSStackFrame *sf = &s->frame; + JSValue *sp; + + /* cannot free the function if it is running */ + assert(sf->cur_sp != NULL); + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) { + JS_FreeValueRT(rt, *sp); + } + JS_FreeValueRT(rt, sf->cur_func); + JS_FreeValueRT(rt, s->this_val); +} + +static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame *sf = &s->frame; + JSValue func_obj, ret; + + assert(!s->is_completed); + if (js_check_stack_overflow(ctx->rt, 0)) { + ret = JS_ThrowStackOverflow(ctx); + } else { + /* the tag does not matter provided it is not an object */ + func_obj = JS_MKPTR(JS_TAG_INT, s); + ret = JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, + s->argc, sf->arg_buf, JS_CALL_FLAG_GENERATOR); + } + if (JS_IsException(ret) || JS_IsUndefined(ret)) { + JSObject *p; + JSFunctionBytecode *b; + + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = p->u.func.function_bytecode; + + if (JS_IsUndefined(ret)) { + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + } + /* end of execution */ + s->is_completed = TRUE; + + /* close the closure variables. */ + close_var_refs(rt, b, sf); + + async_func_free_frame(rt, s); + } + return ret; +} + +static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +{ + /* cannot close the closure variables here because it would + potentially modify the object graph */ + if (!s->is_completed) { + async_func_free_frame(rt, s); + } + + JS_FreeValueRT(rt, s->resolving_funcs[0]); + JS_FreeValueRT(rt, s->resolving_funcs[1]); + + remove_gc_object(&s->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && s->header.ref_count != 0) { + list_add_tail(&s->header.link, &rt->gc_zero_ref_count_list); + } else { + js_free_rt(rt, s); + } +} + +static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +{ + if (--s->header.ref_count == 0) { + if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { + list_del(&s->header.link); + list_add(&s->header.link, &rt->gc_zero_ref_count_list); + if (rt->gc_phase == JS_GC_PHASE_NONE) { + free_zero_refcount(rt); + } + } + } +} + +/* Generators */ + +typedef enum JSGeneratorStateEnum { + JS_GENERATOR_STATE_SUSPENDED_START, + JS_GENERATOR_STATE_SUSPENDED_YIELD, + JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_GENERATOR_STATE_EXECUTING, + JS_GENERATOR_STATE_COMPLETED, +} JSGeneratorStateEnum; + +typedef struct JSGeneratorData { + JSGeneratorStateEnum state; + JSAsyncFunctionState *func_state; +} JSGeneratorData; + +static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s) +{ + if (s->state == JS_GENERATOR_STATE_COMPLETED) + return; + if (s->func_state) { + async_func_free(rt, s->func_state); + s->func_state = NULL; + } + s->state = JS_GENERATOR_STATE_COMPLETED; +} + +static void js_generator_finalizer(JSRuntime *rt, JSValue obj) +{ + JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR); + + if (s) { + free_generator_stack_rt(rt, s); + js_free_rt(rt, s); + } +} + +static void free_generator_stack(JSContext *ctx, JSGeneratorData *s) +{ + free_generator_stack_rt(ctx->rt, s); +} + +static void js_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSGeneratorData *s = p->u.generator_data; + + if (!s || !s->func_state) + return; + mark_func(rt, &s->func_state->header); +} + +/* XXX: use enum */ +#define GEN_MAGIC_NEXT 0 +#define GEN_MAGIC_RETURN 1 +#define GEN_MAGIC_THROW 2 + +static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + BOOL *pdone, int magic) +{ + JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR); + JSStackFrame *sf; + JSValue ret, func_ret; + + *pdone = TRUE; + if (!s) + return JS_ThrowTypeError(ctx, "not a generator"); + switch(s->state) { + default: + case JS_GENERATOR_STATE_SUSPENDED_START: + sf = &s->func_state->frame; + if (magic == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + free_generator_stack(ctx, s); + goto done; + } + break; + case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + case JS_GENERATOR_STATE_SUSPENDED_YIELD: + sf = &s->func_state->frame; + /* cur_sp[-1] was set to JS_UNDEFINED in the previous call */ + ret = JS_DupValue(ctx, argv[0]); + if (magic == GEN_MAGIC_THROW && + s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, ret); + s->func_state->throw_flag = TRUE; + } else { + sf->cur_sp[-1] = ret; + sf->cur_sp[0] = JS_NewInt32(ctx, magic); + sf->cur_sp++; + exec_no_arg: + s->func_state->throw_flag = FALSE; + } + s->state = JS_GENERATOR_STATE_EXECUTING; + func_ret = async_func_resume(ctx, s->func_state); + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD; + if (s->func_state->is_completed) { + /* finalize the execution in case of exception or normal return */ + free_generator_stack(ctx, s); + return func_ret; + } else { + assert(JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT); + /* get the returned yield value at the top of the stack */ + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) { + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + /* return (value, done) object */ + *pdone = 2; + } else { + *pdone = FALSE; + } + } + break; + case JS_GENERATOR_STATE_COMPLETED: + done: + /* execution is finished */ + switch(magic) { + default: + case GEN_MAGIC_NEXT: + ret = JS_UNDEFINED; + break; + case GEN_MAGIC_RETURN: + ret = JS_DupValue(ctx, argv[0]); + break; + case GEN_MAGIC_THROW: + ret = JS_Throw(ctx, JS_DupValue(ctx, argv[0])); + break; + } + break; + case JS_GENERATOR_STATE_EXECUTING: + ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator"); + break; + } + return ret; +} + +static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSValue obj, func_ret; + JSGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_GENERATOR_STATE_SUSPENDED_START; + s->func_state = async_func_init(ctx, func_obj, this_obj, argc, argv); + if (!s->func_state) { + s->state = JS_GENERATOR_STATE_COMPLETED; + goto fail; + } + + /* execute the function up to 'OP_initial_yield' */ + func_ret = async_func_resume(ctx, s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR); + if (JS_IsException(obj)) + goto fail; + JS_SetOpaque(obj, s); + return obj; + fail: + free_generator_stack_rt(ctx->rt, s); + js_free(ctx, s); + return JS_EXCEPTION; +} + +/* AsyncFunction */ + +static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionState *s = p->u.async_function_data; + if (s) { + async_func_free(rt, s); + } +} + +static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionState *s = p->u.async_function_data; + if (s) { + mark_func(rt, &s->header); + } +} + +static int js_async_function_resolve_create(JSContext *ctx, + JSAsyncFunctionState *s, + JSValue *resolving_funcs) +{ + int i; + JSObject *p; + + for(i = 0; i < 2; i++) { + resolving_funcs[i] = + JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_ASYNC_FUNCTION_RESOLVE + i); + if (JS_IsException(resolving_funcs[i])) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + return -1; + } + p = JS_VALUE_GET_OBJ(resolving_funcs[i]); + s->header.ref_count++; + p->u.async_function_data = s; + } + return 0; +} + +static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionState *s) +{ + JSValue func_ret, ret2; + + func_ret = async_func_resume(ctx, s); + if (s->is_completed) { + if (JS_IsException(func_ret)) { + JSValue error; + fail: + error = JS_GetException(ctx); + ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED, + 1, (JSValueConst *)&error); + JS_FreeValue(ctx, error); + JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ + } else { + /* normal return */ + ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED, + 1, (JSValueConst *)&func_ret); + JS_FreeValue(ctx, func_ret); + JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ + } + } else { + JSValue value, promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + value = s->frame.cur_sp[-1]; + s->frame.cur_sp[-1] = JS_UNDEFINED; + + /* await */ + JS_FreeValue(ctx, func_ret); /* not used */ + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, (JSValueConst *)&value, 0); + JS_FreeValue(ctx, value); + if (JS_IsException(promise)) + goto fail; + if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + (JSValueConst *)resolving_funcs, + (JSValueConst *)resolving_funcs1); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + } +} + +static JSValue js_async_function_resolve_call(JSContext *ctx, + JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSObject *p = JS_VALUE_GET_OBJ(func_obj); + JSAsyncFunctionState *s = p->u.async_function_data; + BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE; + JSValueConst arg; + + if (argc > 0) + arg = argv[0]; + else + arg = JS_UNDEFINED; + s->throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, JS_DupValue(ctx, arg)); + } else { + /* return value of await */ + s->frame.cur_sp[-1] = JS_DupValue(ctx, arg); + } + js_async_function_resume(ctx, s); + return JS_UNDEFINED; +} + +static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSValue promise; + JSAsyncFunctionState *s; + + s = async_func_init(ctx, func_obj, this_obj, argc, argv); + if (!s) + return JS_EXCEPTION; + + promise = JS_NewPromiseCapability(ctx, s->resolving_funcs); + if (JS_IsException(promise)) { + async_func_free(ctx->rt, s); + return JS_EXCEPTION; + } + + js_async_function_resume(ctx, s); + + async_func_free(ctx->rt, s); + + return promise; +} + +/* AsyncGenerator */ + +typedef enum JSAsyncGeneratorStateEnum { + JS_ASYNC_GENERATOR_STATE_SUSPENDED_START, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_ASYNC_GENERATOR_STATE_EXECUTING, + JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN, + JS_ASYNC_GENERATOR_STATE_COMPLETED, +} JSAsyncGeneratorStateEnum; + +typedef struct JSAsyncGeneratorRequest { + struct list_head link; + /* completion */ + int completion_type; /* GEN_MAGIC_x */ + JSValue result; + /* promise capability */ + JSValue promise; + JSValue resolving_funcs[2]; +} JSAsyncGeneratorRequest; + +typedef struct JSAsyncGeneratorData { + JSObject *generator; /* back pointer to the object (const) */ + JSAsyncGeneratorStateEnum state; + /* func_state is NULL is state AWAITING_RETURN and COMPLETED */ + JSAsyncFunctionState *func_state; + struct list_head queue; /* list of JSAsyncGeneratorRequest.link */ +} JSAsyncGeneratorData; + +static void js_async_generator_free(JSRuntime *rt, + JSAsyncGeneratorData *s) +{ + struct list_head *el, *el1; + JSAsyncGeneratorRequest *req; + + list_for_each_safe(el, el1, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_FreeValueRT(rt, req->result); + JS_FreeValueRT(rt, req->promise); + JS_FreeValueRT(rt, req->resolving_funcs[0]); + JS_FreeValueRT(rt, req->resolving_funcs[1]); + js_free_rt(rt, req); + } + if (s->func_state) + async_func_free(rt, s->func_state); + js_free_rt(rt, s); +} + +static void js_async_generator_finalizer(JSRuntime *rt, JSValue obj) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR); + + if (s) { + js_async_generator_free(rt, s); + } +} + +static void js_async_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR); + struct list_head *el; + JSAsyncGeneratorRequest *req; + if (s) { + list_for_each(el, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_MarkValue(rt, req->result, mark_func); + JS_MarkValue(rt, req->promise, mark_func); + JS_MarkValue(rt, req->resolving_funcs[0], mark_func); + JS_MarkValue(rt, req->resolving_funcs[1], mark_func); + } + if (s->func_state) { + mark_func(rt, &s->func_state->header); + } + } +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int magic, JSValue *func_data); + +static int js_async_generator_resolve_function_create(JSContext *ctx, + JSValueConst generator, + JSValue *resolving_funcs, + BOOL is_resume_next) +{ + int i; + JSValue func; + + for(i = 0; i < 2; i++) { + func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1, + i + is_resume_next * 2, 1, &generator); + if (JS_IsException(func)) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + return -1; + } + resolving_funcs[i] = func; + } + return 0; +} + +static int js_async_generator_await(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, &value, 0); + if (JS_IsException(promise)) + goto fail; + + if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs, FALSE)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + (JSValueConst *)resolving_funcs, + (JSValueConst *)resolving_funcs1); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + return 0; + fail: + return -1; +} + +static void js_async_generator_resolve_or_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst result, + int is_reject) +{ + JSAsyncGeneratorRequest *next; + JSValue ret; + + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + list_del(&next->link); + ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1, + &result); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, next->result); + JS_FreeValue(ctx, next->promise); + JS_FreeValue(ctx, next->resolving_funcs[0]); + JS_FreeValue(ctx, next->resolving_funcs[1]); + js_free(ctx, next); +} + +static void js_async_generator_resolve(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst value, + BOOL done) +{ + JSValue result; + result = js_create_iterator_result(ctx, JS_DupValue(ctx, value), done); + /* XXX: better exception handling ? */ + js_async_generator_resolve_or_reject(ctx, s, result, 0); + JS_FreeValue(ctx, result); + } + +static void js_async_generator_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst exception) +{ + js_async_generator_resolve_or_reject(ctx, s, exception, 1); +} + +static void js_async_generator_complete(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) { + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + async_func_free(ctx->rt, s->func_state); + s->func_state = NULL; + } +} + +static int js_async_generator_completed_return(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int res; + + // Can fail looking up JS_ATOM_constructor when is_reject==0. + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &value, + /*is_reject*/0); + // A poisoned .constructor property is observable and the resulting + // exception should be delivered to the catch handler. + if (JS_IsException(promise)) { + JSValue err = JS_GetException(ctx); + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, (JSValueConst *)&err, + /*is_reject*/1); + JS_FreeValue(ctx, err); + if (JS_IsException(promise)) + return -1; + } + if (js_async_generator_resolve_function_create(ctx, + JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs1, + TRUE)) { + JS_FreeValue(ctx, promise); + return -1; + } + resolving_funcs[0] = JS_UNDEFINED; + resolving_funcs[1] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + (JSValueConst *)resolving_funcs1, + (JSValueConst *)resolving_funcs); + JS_FreeValue(ctx, resolving_funcs1[0]); + JS_FreeValue(ctx, resolving_funcs1[1]); + JS_FreeValue(ctx, promise); + return res; +} + +static void js_async_generator_resume_next(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + JSAsyncGeneratorRequest *next; + JSValue func_ret, value; + + for(;;) { + if (list_empty(&s->queue)) + break; + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + switch(s->state) { + case JS_ASYNC_GENERATOR_STATE_EXECUTING: + /* only happens when restarting execution after await() */ + goto resume_exec; + case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN: + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START: + if (next->completion_type == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + js_async_generator_complete(ctx, s); + } + break; + case JS_ASYNC_GENERATOR_STATE_COMPLETED: + if (next->completion_type == GEN_MAGIC_NEXT) { + js_async_generator_resolve(ctx, s, JS_UNDEFINED, TRUE); + } else if (next->completion_type == GEN_MAGIC_RETURN) { + s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN; + js_async_generator_completed_return(ctx, s, next->result); + } else { + js_async_generator_reject(ctx, s, next->result); + } + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD: + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + value = JS_DupValue(ctx, next->result); + if (next->completion_type == GEN_MAGIC_THROW && + s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, value); + s->func_state->throw_flag = TRUE; + } else { + /* 'yield' returns a value. 'yield *' also returns a value + in case the 'throw' method is called */ + s->func_state->frame.cur_sp[-1] = value; + s->func_state->frame.cur_sp[0] = + JS_NewInt32(ctx, next->completion_type); + s->func_state->frame.cur_sp++; + exec_no_arg: + s->func_state->throw_flag = FALSE; + } + s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING; + resume_exec: + func_ret = async_func_resume(ctx, s->func_state); + if (s->func_state->is_completed) { + if (JS_IsException(func_ret)) { + value = JS_GetException(ctx); + js_async_generator_complete(ctx, s); + js_async_generator_reject(ctx, s, value); + JS_FreeValue(ctx, value); + } else { + /* end of function */ + js_async_generator_complete(ctx, s); + js_async_generator_resolve(ctx, s, func_ret, TRUE); + JS_FreeValue(ctx, func_ret); + } + } else { + int func_ret_code, ret; + assert(JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT); + func_ret_code = JS_VALUE_GET_INT(func_ret); + value = s->func_state->frame.cur_sp[-1]; + s->func_state->frame.cur_sp[-1] = JS_UNDEFINED; + switch(func_ret_code) { + case FUNC_RET_YIELD: + case FUNC_RET_YIELD_STAR: + if (func_ret_code == FUNC_RET_YIELD_STAR) + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + else + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD; + js_async_generator_resolve(ctx, s, value, FALSE); + JS_FreeValue(ctx, value); + break; + case FUNC_RET_AWAIT: + ret = js_async_generator_await(ctx, s, value); + JS_FreeValue(ctx, value); + if (ret < 0) { + /* exception: throw it */ + s->func_state->throw_flag = TRUE; + goto resume_exec; + } + goto done; + default: + abort(); + } + } + break; + default: + abort(); + } + } + done: ; +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int magic, JSValue *func_data) +{ + BOOL is_reject = magic & 1; + JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR); + JSValueConst arg = argv[0]; + + /* XXX: what if s == NULL */ + + if (magic >= 2) { + /* resume next case in AWAITING_RETURN state */ + assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN || + s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED); + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + if (is_reject) { + js_async_generator_reject(ctx, s, arg); + } else { + js_async_generator_resolve(ctx, s, arg, TRUE); + } + } else { + /* restart function execution after await() */ + assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); + s->func_state->throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, JS_DupValue(ctx, arg)); + } else { + /* return value of await */ + s->func_state->frame.cur_sp[-1] = JS_DupValue(ctx, arg); + } + js_async_generator_resume_next(ctx, s); + } + return JS_UNDEFINED; +} + +/* magic = GEN_MAGIC_x */ +static JSValue js_async_generator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int magic) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR); + JSValue promise, resolving_funcs[2]; + JSAsyncGeneratorRequest *req; + + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + if (!s) { + JSValue err, res2; + JS_ThrowTypeError(ctx, "not an AsyncGenerator object"); + err = JS_GetException(ctx); + res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, + 1, (JSValueConst *)&err); + JS_FreeValue(ctx, err); + JS_FreeValue(ctx, res2); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; + } + req = js_mallocz(ctx, sizeof(*req)); + if (!req) + goto fail; + req->completion_type = magic; + req->result = JS_DupValue(ctx, argv[0]); + req->promise = JS_DupValue(ctx, promise); + req->resolving_funcs[0] = resolving_funcs[0]; + req->resolving_funcs[1] = resolving_funcs[1]; + list_add_tail(&req->link, &s->queue); + if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) { + js_async_generator_resume_next(ctx, s); + } + return promise; + fail: + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + JS_FreeValue(ctx, promise); + return JS_EXCEPTION; +} + +static JSValue js_async_generator_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSValue obj, func_ret; + JSAsyncGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START; + init_list_head(&s->queue); + s->func_state = async_func_init(ctx, func_obj, this_obj, argc, argv); + if (!s->func_state) + goto fail; + /* execute the function up to 'OP_initial_yield' (no yield nor + await are possible) */ + func_ret = async_func_resume(ctx, s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR); + if (JS_IsException(obj)) + goto fail; + s->generator = JS_VALUE_GET_OBJ(obj); + JS_SetOpaque(obj, s); + return obj; + fail: + js_async_generator_free(ctx->rt, s); + return JS_EXCEPTION; +} + +/* JS parser */ + +enum { + TOK_NUMBER = -128, + TOK_STRING, + TOK_TEMPLATE, + TOK_IDENT, + TOK_REGEXP, + /* warning: order matters (see js_parse_assign_expr) */ + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_MOD_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_SHL_ASSIGN, + TOK_SAR_ASSIGN, + TOK_SHR_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, + TOK_POW_ASSIGN, + TOK_LAND_ASSIGN, + TOK_LOR_ASSIGN, + TOK_DOUBLE_QUESTION_MARK_ASSIGN, + TOK_DEC, + TOK_INC, + TOK_SHL, + TOK_SAR, + TOK_SHR, + TOK_LT, + TOK_LTE, + TOK_GT, + TOK_GTE, + TOK_EQ, + TOK_STRICT_EQ, + TOK_NEQ, + TOK_STRICT_NEQ, + TOK_LAND, + TOK_LOR, + TOK_POW, + TOK_ARROW, + TOK_ELLIPSIS, + TOK_DOUBLE_QUESTION_MARK, + TOK_QUESTION_MARK_DOT, + TOK_ERROR, + TOK_PRIVATE_NAME, + TOK_EOF, + /* keywords: WARNING: same order as atoms */ + TOK_NULL, /* must be first */ + TOK_FALSE, + TOK_TRUE, + TOK_IF, + TOK_ELSE, + TOK_RETURN, + TOK_VAR, + TOK_THIS, + TOK_DELETE, + TOK_VOID, + TOK_TYPEOF, + TOK_NEW, + TOK_IN, + TOK_INSTANCEOF, + TOK_DO, + TOK_WHILE, + TOK_FOR, + TOK_BREAK, + TOK_CONTINUE, + TOK_SWITCH, + TOK_CASE, + TOK_DEFAULT, + TOK_THROW, + TOK_TRY, + TOK_CATCH, + TOK_FINALLY, + TOK_FUNCTION, + TOK_DEBUGGER, + TOK_WITH, + /* FutureReservedWord */ + TOK_CLASS, + TOK_CONST, + TOK_ENUM, + TOK_EXPORT, + TOK_EXTENDS, + TOK_IMPORT, + TOK_SUPER, + /* FutureReservedWords when parsing strict mode code */ + TOK_IMPLEMENTS, + TOK_INTERFACE, + TOK_LET, + TOK_PACKAGE, + TOK_PRIVATE, + TOK_PROTECTED, + TOK_PUBLIC, + TOK_STATIC, + TOK_YIELD, + TOK_AWAIT, /* must be last */ + TOK_OF, /* only used for js_parse_skip_parens_token() */ +}; + +#define TOK_FIRST_KEYWORD TOK_NULL +#define TOK_LAST_KEYWORD TOK_AWAIT + +/* unicode code points */ +#define CP_NBSP 0x00a0 +#define CP_BOM 0xfeff + +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +typedef struct BlockEnv { + struct BlockEnv *prev; + JSAtom label_name; /* JS_ATOM_NULL if none */ + int label_break; /* -1 if none */ + int label_cont; /* -1 if none */ + int drop_count; /* number of stack elements to drop */ + int label_finally; /* -1 if none */ + int scope_level; + uint8_t has_iterator : 1; + uint8_t is_regular_stmt : 1; /* i.e. not a loop statement */ +} BlockEnv; + +typedef struct JSGlobalVar { + int cpool_idx; /* if >= 0, index in the constant pool for hoisted + function defintion*/ + uint8_t force_init : 1; /* force initialization to undefined */ + uint8_t is_lexical : 1; /* global let/const definition */ + uint8_t is_const : 1; /* const definition */ + int scope_level; /* scope of definition */ + JSAtom var_name; /* variable name */ +} JSGlobalVar; + +typedef struct RelocEntry { + struct RelocEntry *next; + uint32_t addr; /* address to patch */ + int size; /* address size: 1, 2 or 4 bytes */ +} RelocEntry; + +typedef struct JumpSlot { + int op; + int size; + int pos; + int label; +} JumpSlot; + +typedef struct LabelSlot { + int ref_count; + int pos; /* phase 1 address, -1 means not resolved yet */ + int pos2; /* phase 2 address, -1 means not resolved yet */ + int addr; /* phase 3 address, -1 means not resolved yet */ + RelocEntry *first_reloc; +} LabelSlot; + +typedef struct LineNumberSlot { + uint32_t pc; + uint32_t source_pos; +} LineNumberSlot; + +typedef struct { + /* last source position */ + const uint8_t *ptr; + int line_num; + int col_num; + const uint8_t *buf_start; +} GetLineColCache; + +typedef enum JSParseFunctionEnum { + JS_PARSE_FUNC_STATEMENT, + JS_PARSE_FUNC_VAR, + JS_PARSE_FUNC_EXPR, + JS_PARSE_FUNC_ARROW, + JS_PARSE_FUNC_GETTER, + JS_PARSE_FUNC_SETTER, + JS_PARSE_FUNC_METHOD, + JS_PARSE_FUNC_CLASS_STATIC_INIT, + JS_PARSE_FUNC_CLASS_CONSTRUCTOR, + JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, +} JSParseFunctionEnum; + +typedef enum JSParseExportEnum { + JS_PARSE_EXPORT_NONE, + JS_PARSE_EXPORT_NAMED, + JS_PARSE_EXPORT_DEFAULT, +} JSParseExportEnum; + +typedef struct JSVarScope { + int parent; /* index into fd->scopes of the enclosing scope */ + int first; /* index into fd->vars of the last variable in this scope */ +} JSVarScope; + +typedef struct JSVarDef { + JSAtom var_name; + /* index into fd->scopes of this variable lexical scope */ + int scope_level; + /* - if scope_level = 0: scope in which the variable is defined + - if scope_level != 0: index into fd->vars of the next + variable in the same or enclosing lexical scope + */ + int scope_next; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t is_captured : 1; /* XXX: could remove and use a var_ref_idx value */ + uint8_t is_static_private : 1; /* only used during private class field parsing */ + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* if is_captured = TRUE, provides, the index of the corresponding + JSVarRef on stack */ + uint16_t var_ref_idx; + /* function pool index for lexical variables with var_kind = + JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of + the definition of the 'var' variables (they have scope_level = + 0) */ + int func_pool_idx; +} JSVarDef; + +typedef struct JSFunctionDef { + JSContext *ctx; + struct JSFunctionDef *parent; + int parent_cpool_idx; /* index in the constant pool of the parent + or -1 if none */ + int parent_scope_level; /* scope level in parent at point of definition */ + struct list_head child_list; /* list of JSFunctionDef.link */ + struct list_head link; + + BOOL is_eval; /* TRUE if eval code */ + int eval_type; /* only valid if is_eval = TRUE */ + BOOL is_global_var; /* TRUE if variables are not defined locally: + eval global, eval module or non strict eval */ + BOOL is_func_expr; /* TRUE if function expression */ + BOOL has_home_object; /* TRUE if the home object is available */ + BOOL has_prototype; /* true if a prototype field is necessary */ + BOOL has_simple_parameter_list; + BOOL has_parameter_expressions; /* if true, an argument scope is created */ + BOOL has_use_strict; /* to reject directive in special cases */ + BOOL has_eval_call; /* true if the function contains a call to eval() */ + BOOL has_arguments_binding; /* true if the 'arguments' binding is + available in the function */ + BOOL has_this_binding; /* true if the 'this' and new.target binding are + available in the function */ + BOOL new_target_allowed; /* true if the 'new.target' does not + throw a syntax error */ + BOOL super_call_allowed; /* true if super() is allowed */ + BOOL super_allowed; /* true if super. or super[] is allowed */ + BOOL arguments_allowed; /* true if the 'arguments' identifier is allowed */ + BOOL is_derived_class_constructor; + BOOL in_function_body; + JSFunctionKindEnum func_kind : 8; + JSParseFunctionEnum func_type : 8; + uint8_t js_mode; /* bitmap of JS_MODE_x */ + JSAtom func_name; /* JS_ATOM_NULL if no name */ + + JSVarDef *vars; + int var_size; /* allocated size for vars[] */ + int var_count; + JSVarDef *args; + int arg_size; /* allocated size for args[] */ + int arg_count; /* number of arguments */ + int defined_arg_count; + int var_ref_count; /* number of local/arg variable references */ + int var_object_idx; /* -1 if none */ + int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ + int arguments_var_idx; /* -1 if none */ + int arguments_arg_idx; /* argument variable definition in argument scope, + -1 if none */ + int func_var_idx; /* variable containing the current function (-1 + if none, only used if is_func_expr is true) */ + int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */ + int this_var_idx; /* variable containg the 'this' value, -1 if none */ + int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */ + int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */ + int home_object_var_idx; + BOOL need_home_object; + + int scope_level; /* index into fd->scopes if the current lexical scope */ + int scope_first; /* index into vd->vars of first lexically scoped variable */ + int scope_size; /* allocated size of fd->scopes array */ + int scope_count; /* number of entries used in the fd->scopes array */ + JSVarScope *scopes; + JSVarScope def_scope_array[4]; + int body_scope; /* scope of the body of the function or eval */ + + int global_var_count; + int global_var_size; + JSGlobalVar *global_vars; + + DynBuf byte_code; + int last_opcode_pos; /* -1 if no last opcode */ + const uint8_t *last_opcode_source_ptr; + BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */ + + LabelSlot *label_slots; + int label_size; /* allocated size for label_slots[] */ + int label_count; + BlockEnv *top_break; /* break/continue label stack */ + + /* constant pool (strings, functions, numbers) */ + JSValue *cpool; + int cpool_count; + int cpool_size; + + /* list of variables in the closure */ + int closure_var_count; + int closure_var_size; + JSClosureVar *closure_var; + + JumpSlot *jump_slots; + int jump_size; + int jump_count; + + LineNumberSlot *line_number_slots; + int line_number_size; + int line_number_count; + int line_number_last; + int line_number_last_pc; + + /* pc2line table */ + BOOL strip_debug : 1; /* strip all debug info (implies strip_source = TRUE) */ + BOOL strip_source : 1; /* strip only source code */ + JSAtom filename; + uint32_t source_pos; /* pointer in the eval() source */ + GetLineColCache *get_line_col_cache; /* XXX: could remove to save memory */ + DynBuf pc2line; + + char *source; /* raw source, utf-8 encoded */ + int source_len; + + JSModuleDef *module; /* != NULL when parsing a module */ + BOOL has_await; /* TRUE if await is used (used in module eval) */ +} JSFunctionDef; + +typedef struct JSToken { + int val; + const uint8_t *ptr; /* position in the source */ + union { + struct { + JSValue str; + int sep; + } str; + struct { + JSValue val; + } num; + struct { + JSAtom atom; + BOOL has_escape; + BOOL is_reserved; + } ident; + struct { + JSValue body; + JSValue flags; + } regexp; + } u; +} JSToken; + +typedef struct JSParseState { + JSContext *ctx; + const char *filename; + JSToken token; + BOOL got_lf; /* true if got line feed before the current token */ + const uint8_t *last_ptr; + const uint8_t *buf_start; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + + /* current function code */ + JSFunctionDef *cur_func; + BOOL is_module; /* parsing a module */ + BOOL allow_html_comments; + BOOL ext_json; /* true if accepting JSON superset */ + GetLineColCache get_line_col_cache; +} JSParseState; + +typedef struct JSOpCode { +#ifdef DUMP_BYTECODE + const char *name; +#endif + uint8_t size; /* in bytes */ + /* the opcodes remove n_pop items from the top of the stack, then + pushes n_push items */ + uint8_t n_pop; + uint8_t n_push; + uint8_t fmt; +} JSOpCode; + +static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { +#define FMT(f) +#ifdef DUMP_BYTECODE +#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f }, +#else +#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f }, +#endif +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +}; + +#if SHORT_OPCODES +/* After the final compilation pass, short opcodes are used. Their + opcodes overlap with the temporary opcodes which cannot appear in + the final bytecode. Their description is after the temporary + opcodes in opcode_info[]. */ +#define short_opcode_info(op) \ + opcode_info[(op) >= OP_TEMP_START ? \ + (op) + (OP_TEMP_END - OP_TEMP_START) : (op)] +#else +#define short_opcode_info(op) opcode_info[op] +#endif + +static __exception int next_token(JSParseState *s); + +static void free_token(JSParseState *s, JSToken *token) +{ + switch(token->val) { + case TOK_NUMBER: + JS_FreeValue(s->ctx, token->u.num.val); + break; + case TOK_STRING: + case TOK_TEMPLATE: + JS_FreeValue(s->ctx, token->u.str.str); + break; + case TOK_REGEXP: + JS_FreeValue(s->ctx, token->u.regexp.body); + JS_FreeValue(s->ctx, token->u.regexp.flags); + break; + case TOK_IDENT: + case TOK_PRIVATE_NAME: + JS_FreeAtom(s->ctx, token->u.ident.atom); + break; + default: + if (token->val >= TOK_FIRST_KEYWORD && + token->val <= TOK_LAST_KEYWORD) { + JS_FreeAtom(s->ctx, token->u.ident.atom); + } + break; + } +} + +static void __attribute((unused)) dump_token(JSParseState *s, + const JSToken *token) +{ + switch(token->val) { + case TOK_NUMBER: + { + double d; + JS_ToFloat64(s->ctx, &d, token->u.num.val); /* no exception possible */ + printf("number: %.14g\n", d); + } + break; + case TOK_IDENT: + dump_atom: + { + char buf[ATOM_GET_STR_BUF_SIZE]; + printf("ident: '%s'\n", + JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom)); + } + break; + case TOK_STRING: + { + const char *str; + /* XXX: quote the string */ + str = JS_ToCString(s->ctx, token->u.str.str); + printf("string: '%s'\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_TEMPLATE: + { + const char *str; + str = JS_ToCString(s->ctx, token->u.str.str); + printf("template: `%s`\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_REGEXP: + { + const char *str, *str2; + str = JS_ToCString(s->ctx, token->u.regexp.body); + str2 = JS_ToCString(s->ctx, token->u.regexp.flags); + printf("regexp: '%s' '%s'\n", str, str2); + JS_FreeCString(s->ctx, str); + JS_FreeCString(s->ctx, str2); + } + break; + case TOK_EOF: + printf("eof\n"); + break; + default: + if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { + goto dump_atom; + } else if (s->token.val >= 256) { + printf("token: %d\n", token->val); + } else { + printf("token: '%c'\n", token->val); + } + break; + } +} + +/* return the zero based line and column number in the source. */ +/* Note: we no longer support '\r' as line terminator */ +static int get_line_col(int *pcol_num, const uint8_t *buf, size_t len) +{ + int line_num, col_num, c; + size_t i; + + line_num = 0; + col_num = 0; + for(i = 0; i < len; i++) { + c = buf[i]; + if (c == '\n') { + line_num++; + col_num = 0; + } else if (c < 0x80 || c >= 0xc0) { + col_num++; + } + } + *pcol_num = col_num; + return line_num; +} + +static int get_line_col_cached(GetLineColCache *s, int *pcol_num, const uint8_t *ptr) +{ + int line_num, col_num; + if (ptr >= s->ptr) { + line_num = get_line_col(&col_num, s->ptr, ptr - s->ptr); + if (line_num == 0) { + s->col_num += col_num; + } else { + s->line_num += line_num; + s->col_num = col_num; + } + } else { + line_num = get_line_col(&col_num, ptr, s->ptr - ptr); + if (line_num == 0) { + s->col_num -= col_num; + } else { + const uint8_t *p; + s->line_num -= line_num; + /* find the absolute column position */ + col_num = 0; + for(p = ptr - 1; p >= s->buf_start; p--) { + if (*p == '\n') { + break; + } else if (*p < 0x80 || *p >= 0xc0) { + col_num++; + } + } + s->col_num = col_num; + } + } + s->ptr = ptr; + *pcol_num = s->col_num; + return s->line_num; +} + +/* 'ptr' is the position of the error in the source */ +static int js_parse_error_v(JSParseState *s, const uint8_t *ptr, const char *fmt, va_list ap) +{ + JSContext *ctx = s->ctx; + int line_num, col_num; + line_num = get_line_col(&col_num, s->buf_start, ptr - s->buf_start); + JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); + build_backtrace(ctx, ctx->rt->current_exception, s->filename, + line_num + 1, col_num + 1, 0); + return -1; +} + +static __attribute__((format(printf, 3, 4))) int js_parse_error_pos(JSParseState *s, const uint8_t *ptr, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = js_parse_error_v(s, ptr, fmt, ap); + va_end(ap); + return ret; +} + +static __attribute__((format(printf, 2, 3))) int js_parse_error(JSParseState *s, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = js_parse_error_v(s, s->token.ptr, fmt, ap); + va_end(ap); + return ret; +} + +static int js_parse_expect(JSParseState *s, int tok) +{ + if (s->token.val != tok) { + /* XXX: dump token correctly in all cases */ + return js_parse_error(s, "expecting '%c'", tok); + } + return next_token(s); +} + +static int js_parse_expect_semi(JSParseState *s) +{ + if (s->token.val != ';') { + /* automatic insertion of ';' */ + if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { + return 0; + } + return js_parse_error(s, "expecting '%c'", ';'); + } + return next_token(s); +} + +static int js_parse_error_reserved_identifier(JSParseState *s) +{ + char buf1[ATOM_GET_STR_BUF_SIZE]; + return js_parse_error(s, "'%s' is a reserved identifier", + JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), + s->token.u.ident.atom)); +} + +static __exception int js_parse_template_part(JSParseState *s, const uint8_t *p) +{ + uint32_t c; + StringBuffer b_s, *b = &b_s; + JSValue str; + + /* p points to the first byte of the template part */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + if (c == '`') { + /* template end part */ + break; + } + if (c == '$' && *p == '{') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + } + /* newline sequences are normalized as single '\n' bytes */ + if (c == '\r') { + if (*p == '\n') + p++; + c = '\n'; + } + if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + js_parse_error_pos(s, p - 1, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + str = string_buffer_end(b); + if (JS_IsException(str)) + return -1; + s->token.val = TOK_TEMPLATE; + s->token.u.str.sep = c; + s->token.u.str.str = str; + s->buf_ptr = p; + return 0; + + unexpected_eof: + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static __exception int js_parse_string(JSParseState *s, int sep, + BOOL do_throw, const uint8_t *p, + JSToken *token, const uint8_t **pp) +{ + int ret; + uint32_t c; + StringBuffer b_s, *b = &b_s; + const uint8_t *p_escape; + JSValue str; + + /* string */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto invalid_char; + c = *p; + if (c < 0x20) { + if (sep == '`') { + if (c == '\r') { + if (p[1] == '\n') + p++; + c = '\n'; + } + /* do not update s->line_num */ + } else if (c == '\n' || c == '\r') + goto invalid_char; + } + p++; + if (c == sep) + break; + if (c == '$' && *p == '{' && sep == '`') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + p_escape = p - 1; + c = *p; + /* XXX: need a specific JSON case to avoid + accepting invalid escapes */ + switch(c) { + case '\0': + if (p >= s->buf_end) + goto invalid_char; + p++; + break; + case '\'': + case '\"': + case '\\': + p++; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + /* ignore escaped newline sequence */ + p++; + continue; + default: + if (c >= '0' && c <= '9') { + if (!(s->cur_func->js_mode & JS_MODE_STRICT) && sep != '`') + goto parse_escape; + if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { + p++; + c = '\0'; + } else { + if (c >= '8' || sep == '`') { + /* Note: according to ES2021, \8 and \9 are not + accepted in strict mode or in templates. */ + goto invalid_escape; + } else { + if (do_throw) + js_parse_error_pos(s, p_escape, "octal escape sequences are not allowed in strict mode"); + } + goto fail; + } + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + goto invalid_utf8; + } + p = p_next; + /* LS or PS are skipped */ + if (c == CP_LS || c == CP_PS) + continue; + } else { + parse_escape: + ret = lre_parse_escape(&p, TRUE); + if (ret == -1) { + invalid_escape: + if (do_throw) + js_parse_error_pos(s, p_escape, "malformed escape sequence in string literal"); + goto fail; + } else if (ret < 0) { + /* ignore the '\' (could output a warning) */ + p++; + } else { + c = ret; + } + } + break; + } + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) + goto invalid_utf8; + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + str = string_buffer_end(b); + if (JS_IsException(str)) + return -1; + token->val = TOK_STRING; + token->u.str.sep = c; + token->u.str.str = str; + *pp = p; + return 0; + + invalid_utf8: + if (do_throw) + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + invalid_char: + if (do_throw) + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static inline BOOL token_is_pseudo_keyword(JSParseState *s, JSAtom atom) { + return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom && + !s->token.u.ident.has_escape; +} + +static __exception int js_parse_regexp(JSParseState *s) +{ + const uint8_t *p; + BOOL in_class; + StringBuffer b_s, *b = &b_s; + StringBuffer b2_s, *b2 = &b2_s; + uint32_t c; + JSValue body_str, flags_str; + + p = s->buf_ptr; + p++; + in_class = FALSE; + if (string_buffer_init(s->ctx, b, 32)) + return -1; + if (string_buffer_init(s->ctx, b2, 1)) + goto fail; + for(;;) { + if (p >= s->buf_end) { + eof_error: + js_parse_error(s, "unexpected end of regexp"); + goto fail; + } + c = *p++; + if (c == '\n' || c == '\r') { + goto eol_error; + } else if (c == '/') { + if (!in_class) + break; + } else if (c == '[') { + in_class = TRUE; + } else if (c == ']') { + /* XXX: incorrect as the first character in a class */ + in_class = FALSE; + } else if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + c = *p++; + if (c == '\n' || c == '\r') + goto eol_error; + else if (c == '\0' && p >= s->buf_end) + goto eof_error; + else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + goto invalid_utf8; + } + p = p_next; + if (c == CP_LS || c == CP_PS) + goto eol_error; + } + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + invalid_utf8: + js_parse_error_pos(s, p - 1, "invalid UTF-8 sequence"); + goto fail; + } + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + eol_error: + js_parse_error_pos(s, p - 1, "unexpected line terminator in regexp"); + goto fail; + } + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + + /* flags */ + for(;;) { + const uint8_t *p_next = p; + c = *p_next++; + if (c >= 0x80) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + p++; + goto invalid_utf8; + } + } + if (!lre_js_is_ident_next(c)) + break; + if (string_buffer_putc(b2, c)) + goto fail; + p = p_next; + } + + body_str = string_buffer_end(b); + flags_str = string_buffer_end(b2); + if (JS_IsException(body_str) || + JS_IsException(flags_str)) { + JS_FreeValue(s->ctx, body_str); + JS_FreeValue(s->ctx, flags_str); + return -1; + } + s->token.val = TOK_REGEXP; + s->token.u.regexp.body = body_str; + s->token.u.regexp.flags = flags_str; + s->buf_ptr = p; + return 0; + fail: + string_buffer_free(b); + string_buffer_free(b2); + return -1; +} + +static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize, + char *static_buf) +{ + char *buf, *new_buf; + size_t size, new_size; + + buf = *pbuf; + size = *psize; + if (size >= (SIZE_MAX / 3) * 2) + new_size = SIZE_MAX; + else + new_size = size + (size >> 1); + if (buf == static_buf) { + new_buf = js_malloc(ctx, new_size); + if (!new_buf) + return -1; + memcpy(new_buf, buf, size); + } else { + new_buf = js_realloc(ctx, buf, new_size); + if (!new_buf) + return -1; + } + *pbuf = new_buf; + *psize = new_size; + return 0; +} + +/* convert a TOK_IDENT to a keyword when needed */ +static void update_token_ident(JSParseState *s) +{ + if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || + (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && + (s->cur_func->js_mode & JS_MODE_STRICT)) || + (s->token.u.ident.atom == JS_ATOM_yield && + ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || + (s->token.u.ident.atom == JS_ATOM_await && + (s->is_module || + (s->cur_func->func_kind & JS_FUNC_ASYNC) || + s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + ((s->cur_func->parent->func_kind & JS_FUNC_ASYNC) || + s->cur_func->parent->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))))) { + if (s->token.u.ident.has_escape) { + s->token.u.ident.is_reserved = TRUE; + s->token.val = TOK_IDENT; + } else { + /* The keywords atoms are pre allocated */ + s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; + } + } +} + +/* if the current token is an identifier or keyword, reparse it + according to the current function type */ +static void reparse_ident_token(JSParseState *s) +{ + if (s->token.val == TOK_IDENT || + (s->token.val >= TOK_FIRST_KEYWORD && + s->token.val <= TOK_LAST_KEYWORD)) { + s->token.val = TOK_IDENT; + s->token.u.ident.is_reserved = FALSE; + update_token_ident(s); + } +} + +/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ +static JSAtom parse_ident(JSParseState *s, const uint8_t **pp, + BOOL *pident_has_escape, int c, BOOL is_private) +{ + const uint8_t *p, *p1; + char ident_buf[128], *buf; + size_t ident_size, ident_pos; + JSAtom atom; + + p = *pp; + buf = ident_buf; + ident_size = sizeof(ident_buf); + ident_pos = 0; + if (is_private) + buf[ident_pos++] = '#'; + for(;;) { + p1 = p; + + if (c < 128) { + buf[ident_pos++] = c; + } else { + ident_pos += unicode_to_utf8((uint8_t*)buf + ident_pos, c); + } + c = *p1++; + if (c == '\\' && *p1 == 'u') { + c = lre_parse_escape(&p1, TRUE); + *pident_has_escape = TRUE; + } else if (c >= 128) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); + } + if (!lre_js_is_ident_next(c)) + break; + p = p1; + if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { + if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) { + atom = JS_ATOM_NULL; + goto done; + } + } + } + atom = JS_NewAtomLen(s->ctx, buf, ident_pos); + done: + if (unlikely(buf != ident_buf)) + js_free(s->ctx, buf); + *pp = p; + return atom; +} + + +static __exception int next_token(JSParseState *s) +{ + const uint8_t *p; + int c; + BOOL ident_has_escape; + JSAtom atom; + + if (js_check_stack_overflow(s->ctx->rt, 0)) { + return js_parse_error(s, "stack overflow"); + } + + free_token(s, &s->token); + + p = s->last_ptr = s->buf_ptr; + s->got_lf = FALSE; + redo: + s->token.ptr = p; + c = *p; + switch(c) { + case 0: + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { + goto def_token; + } + break; + case '`': + if (js_parse_template_part(s, p + 1)) + goto fail; + p = s->buf_ptr; + break; + case '\'': + case '\"': + if (js_parse_string(s, c, TRUE, p + 1, &s->token, &p)) + goto fail; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + p++; + line_terminator: + s->got_lf = TRUE; + goto redo; + case '\f': + case '\v': + case ' ': + case '\t': + p++; + goto redo; + case '/': + if (p[1] == '*') { + /* comment */ + p += 2; + for(;;) { + if (*p == '\0' && p >= s->buf_end) { + js_parse_error(s, "unexpected end of comment"); + goto fail; + } + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + if (*p == '\n' || *p == '\r') { + s->got_lf = TRUE; /* considered as LF for ASI */ + p++; + } else if (*p >= 0x80) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + if (c == CP_LS || c == CP_PS) { + s->got_lf = TRUE; /* considered as LF for ASI */ + } else if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + skip_line_comment: + for(;;) { + if (*p == '\0' && p >= s->buf_end) + break; + if (*p == '\r' || *p == '\n') + break; + if (*p >= 0x80) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + break; + } else if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + goto redo; + } else if (p[1] == '=') { + p += 2; + s->token.val = TOK_DIV_ASSIGN; + } else { + p++; + s->token.val = c; + } + break; + case '\\': + if (p[1] == 'u') { + const uint8_t *p1 = p + 1; + int c1 = lre_parse_escape(&p1, TRUE); + if (c1 >= 0 && lre_js_is_ident_first(c1)) { + c = c1; + p = p1; + ident_has_escape = TRUE; + goto has_ident; + } else { + /* XXX: syntax error? */ + } + } + goto def_token; + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case '$': + /* identifier */ + p++; + ident_has_escape = FALSE; + has_ident: + atom = parse_ident(s, &p, &ident_has_escape, c, FALSE); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.u.ident.has_escape = ident_has_escape; + s->token.u.ident.is_reserved = FALSE; + s->token.val = TOK_IDENT; + update_token_ident(s); + break; + case '#': + /* private name */ + { + const uint8_t *p1; + p++; + p1 = p; + c = *p1++; + if (c == '\\' && *p1 == 'u') { + c = lre_parse_escape(&p1, TRUE); + } else if (c >= 128) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); + } + if (!lre_js_is_ident_first(c)) { + js_parse_error(s, "invalid first character of private name"); + goto fail; + } + p = p1; + ident_has_escape = FALSE; /* not used */ + atom = parse_ident(s, &p, &ident_has_escape, c, TRUE); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.val = TOK_PRIVATE_NAME; + } + break; + case '.': + if (p[1] == '.' && p[2] == '.') { + p += 3; + s->token.val = TOK_ELLIPSIS; + break; + } + if (p[1] >= '0' && p[1] <= '9') { + goto parse_number; + } else { + goto def_token; + } + break; + case '0': + /* in strict mode, octal literals are not accepted */ + if (is_digit(p[1]) && (s->cur_func->js_mode & JS_MODE_STRICT)) { + js_parse_error(s, "octal literals are deprecated in strict mode"); + goto fail; + } + goto parse_number; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + /* number */ + parse_number: + { + JSValue ret; + const uint8_t *p1; + int flags; + flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | + ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; + ret = js_atof(s->ctx, (const char *)p, (const char **)&p, 0, + flags); + if (JS_IsException(ret)) + goto fail; + /* reject `10instanceof Number` */ + if (JS_VALUE_IS_NAN(ret) || + lre_js_is_ident_next(unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1))) { + JS_FreeValue(s->ctx, ret); + js_parse_error(s, "invalid number literal"); + goto fail; + } + s->token.val = TOK_NUMBER; + s->token.u.num.val = ret; + } + break; + case '*': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MUL_ASSIGN; + } else if (p[1] == '*') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_POW_ASSIGN; + } else { + p += 2; + s->token.val = TOK_POW; + } + } else { + goto def_token; + } + break; + case '%': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MOD_ASSIGN; + } else { + goto def_token; + } + break; + case '+': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_PLUS_ASSIGN; + } else if (p[1] == '+') { + p += 2; + s->token.val = TOK_INC; + } else { + goto def_token; + } + break; + case '-': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MINUS_ASSIGN; + } else if (p[1] == '-') { + if (s->allow_html_comments && p[2] == '>' && + (s->got_lf || s->last_ptr == s->buf_start)) { + /* Annex B: `-->` at beginning of line is an html comment end. + It extends to the end of the line. + */ + goto skip_line_comment; + } + p += 2; + s->token.val = TOK_DEC; + } else { + goto def_token; + } + break; + case '<': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_LTE; + } else if (p[1] == '<') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_SHL_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SHL; + } + } else if (s->allow_html_comments && + p[1] == '!' && p[2] == '-' && p[3] == '-') { + /* Annex B: handle ` 0;) { + } + return n; +} + +function empty_do_loop(n) { + var j = n; + do { } while (--j > 0); + return n; +} + +function date_now(n) { + var j; + for(j = 0; j < n; j++) { + Date.now(); + } + return n; +} + +function date_parse(n) { + var x0 = 0, dx = 0; + var j; + for(j = 0; j < n; j++) { + var x1 = x0 - x0 % 1000; + var x2 = -x0; + var x3 = -x1; + var d0 = new Date(x0); + var d1 = new Date(x1); + var d2 = new Date(x2); + var d3 = new Date(x3); + if (Date.parse(d0.toISOString()) != x0 + || Date.parse(d1.toGMTString()) != x1 + || Date.parse(d1.toString()) != x1 + || Date.parse(d2.toISOString()) != x2 + || Date.parse(d3.toGMTString()) != x3 + || Date.parse(d3.toString()) != x3) { + console.log("Date.parse error for " + x0); + return -1; + } + dx = (dx * 1.1 + 1) >> 0; + x0 = (x0 + dx) % 8.64e15; + } + return n * 6; +} + +function prop_read(n) +{ + var obj, sum, j; + obj = {a: 1, b: 2, c:3, d:4 }; + sum = 0; + for(j = 0; j < n; j++) { + sum += obj.a; + sum += obj.b; + sum += obj.c; + sum += obj.d; + } + global_res = sum; + return n * 4; +} + +function prop_write(n) +{ + var obj, j; + obj = {a: 1, b: 2, c:3, d:4 }; + for(j = 0; j < n; j++) { + obj.a = j; + obj.b = j; + obj.c = j; + obj.d = j; + } + return n * 4; +} + +function prop_update(n) +{ + var obj, j; + obj = {a: 1, b: 2, c:3, d:4 }; + for(j = 0; j < n; j++) { + obj.a += j; + obj.b += j; + obj.c += j; + obj.d += j; + } + return n * 4; +} + +function prop_create(n) +{ + var obj, i, j; + for(j = 0; j < n; j++) { + obj = {}; + obj.a = 1; + obj.b = 2; + obj.c = 3; + obj.d = 4; + obj.e = 5; + obj.f = 6; + obj.g = 7; + obj.h = 8; + obj.i = 9; + obj.j = 10; + for(i = 0; i < 10; i++) { + obj[i] = i; + } + } + return n * 20; +} + +function prop_clone(n) +{ + var ref, obj, j, k; + ref = { a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10 }; + for(k = 0; k < 10; k++) { + ref[k] = k; + } + for (j = 0; j < n; j++) { + global_res = { ...ref }; + } + return n * 20; +} + +function prop_delete(n) +{ + var ref, obj, j, k; + ref = { a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10 }; + for(k = 0; k < 10; k++) { + ref[k] = k; + } + for (j = 0; j < n; j++) { + obj = { ...ref }; + delete obj.a; + delete obj.b; + delete obj.c; + delete obj.d; + delete obj.e; + delete obj.f; + delete obj.g; + delete obj.h; + delete obj.i; + delete obj.j; + for(k = 0; k < 10; k++) { + delete obj[k]; + } + } + return n * 20; +} + +function array_read(n) +{ + var tab, len, sum, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab[0]; + sum += tab[1]; + sum += tab[2]; + sum += tab[3]; + sum += tab[4]; + sum += tab[5]; + sum += tab[6]; + sum += tab[7]; + sum += tab[8]; + sum += tab[9]; + } + global_res = sum; + return len * n; +} + +function array_write(n) +{ + var tab, len, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] = j; + tab[1] = j; + tab[2] = j; + tab[3] = j; + tab[4] = j; + tab[5] = j; + tab[6] = j; + tab[7] = j; + tab[8] = j; + tab[9] = j; + } + return len * n; +} + +function array_update(n) +{ + var tab, len, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] += j; + tab[1] += j; + tab[2] += j; + tab[3] += j; + tab[4] += j; + tab[5] += j; + tab[6] += j; + tab[7] += j; + tab[8] += j; + tab[9] += j; + } + return len * n; +} + +function array_prop_create(n) +{ + var tab, i, j, len; + len = 1000; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab[i] = i; + } + return len * n; +} + +function array_slice(n) +{ + var ref, a, i, j, len; + len = 1000; + ref = []; + for(i = 0; i < len; i++) + ref[i] = i; + for(j = 0; j < n; j++) { + ref[0] = j; + a = ref.slice(); + a[0] = 0; + global_res = a; + } + return len * n; +} + +function array_length_read(n) +{ + var tab, sum, j; + tab = [1, 2, 3]; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab.length; + sum += tab.length; + sum += tab.length; + sum += tab.length; + } + global_res = sum; + return n * 4; +} + +function array_length_decr(n) +{ + var tab, ref, i, j, len; + len = 1000; + ref = []; + for(i = 0; i < len; i++) + ref[i] = i; + for(j = 0; j < n; j++) { + tab = ref.slice(); + for(i = len; i --> 0;) + tab.length = i; + } + return len * n; +} + +function array_hole_length_decr(n) +{ + var tab, ref, i, j, len; + len = 1000; + ref = []; + for(i = 0; i < len; i++) { + if (i % 10 == 9) + ref[i] = i; + } + for(j = 0; j < n; j++) { + tab = ref.slice(); + for(i = len; i --> 0;) + tab.length = i; + } + return len * n; +} + +function array_push(n) +{ + var tab, i, j, len; + len = 500; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab.push(i); + } + return len * n; +} + +function array_pop(n) +{ + var tab, ref, i, j, len, sum; + len = 500; + ref = []; + for(i = 0; i < len; i++) + ref[i] = i; + for(j = 0; j < n; j++) { + tab = ref.slice(); + sum = 0; + for(i = 0; i < len; i++) + sum += tab.pop(); + global_res = sum; + } + return len * n; +} + +function typed_array_read(n) +{ + var tab, len, sum, i, j; + len = 10; + tab = new Int32Array(len); + for(i = 0; i < len; i++) + tab[i] = i; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab[0]; + sum += tab[1]; + sum += tab[2]; + sum += tab[3]; + sum += tab[4]; + sum += tab[5]; + sum += tab[6]; + sum += tab[7]; + sum += tab[8]; + sum += tab[9]; + } + global_res = sum; + return len * n; +} + +function typed_array_write(n) +{ + var tab, len, i, j; + len = 10; + tab = new Int32Array(len); + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] = j; + tab[1] = j; + tab[2] = j; + tab[3] = j; + tab[4] = j; + tab[5] = j; + tab[6] = j; + tab[7] = j; + tab[8] = j; + tab[9] = j; + } + return len * n; +} + +function arguments_test() +{ + return arguments[0] + arguments[1] + arguments[2]; +} + +function arguments_read(n) +{ + sum = 0; + for(j = 0; j < n; j++) { + sum += arguments_test(j, j, j); + sum += arguments_test(j, j, j); + sum += arguments_test(j, j, j); + sum += arguments_test(j, j, j); + } + global_res = sum; + return n * 4; +} + +function arguments_strict_test() +{ + "use strict"; + return arguments[0] + arguments[1] + arguments[2]; +} + +function arguments_strict_read(n) +{ + sum = 0; + for(j = 0; j < n; j++) { + sum += arguments_strict_test(j, j, j); + sum += arguments_strict_test(j, j, j); + sum += arguments_strict_test(j, j, j); + sum += arguments_strict_test(j, j, j); + } + global_res = sum; + return n * 4; +} + +var global_var0; + +function global_read(n) +{ + var sum, j; + global_var0 = 0; + sum = 0; + for(j = 0; j < n; j++) { + sum += global_var0; + sum += global_var0; + sum += global_var0; + sum += global_var0; + } + global_res = sum; + return n * 4; +} + +function global_write(n) +{ + var j; + for(j = 0; j < n; j++) { + global_var0 = j; + global_var0 = j; + global_var0 = j; + global_var0 = j; + } + return n * 4; +} + +function global_write_strict(n) +{ + "use strict"; + var j; + for(j = 0; j < n; j++) { + global_var0 = j; + global_var0 = j; + global_var0 = j; + global_var0 = j; + } + return n * 4; +} + +function local_destruct(n) +{ + var j, v1, v2, v3, v4; + var array = [ 1, 2, 3, 4, 5]; + var o = { a:1, b:2, c:3, d:4 }; + var a, b, c, d; + for(j = 0; j < n; j++) { + [ v1, v2,, v3, ...v4] = array; + ({ a, b, c, d } = o); + ({ a: a, b: b, c: c, d: d } = o); + } + return n * 12; +} + +var global_v1, global_v2, global_v3, global_v4; +var global_a, global_b, global_c, global_d; + +function global_destruct(n) +{ + var j, v1, v2, v3, v4; + var array = [ 1, 2, 3, 4, 5 ]; + var o = { a:1, b:2, c:3, d:4 }; + var a, b, c, d; + for(j = 0; j < n; j++) { + [ global_v1, global_v2,, global_v3, ...global_v4] = array; + ({ a: global_a, b: global_b, c: global_c, d: global_d } = o); + } + return n * 8; +} + +function global_destruct_strict(n) +{ + "use strict"; + var j, v1, v2, v3, v4; + var array = [ 1, 2, 3, 4, 5 ]; + var o = { a:1, b:2, c:3, d:4 }; + var a, b, c, d; + for(j = 0; j < n; j++) { + [ global_v1, global_v2,, global_v3, ...global_v4] = array; + ({ a: global_a, b: global_b, c: global_c, d: global_d } = o); + } + return n * 8; +} + +function g(a) +{ + return 1; +} + +function global_func_call(n) +{ + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + sum += g(j); + sum += g(j); + sum += g(j); + sum += g(j); + } + global_res = sum; + return n * 4; +} + +function func_call(n) +{ + function f(a) + { + return 1; + } + + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + sum += f(j); + sum += f(j); + sum += f(j); + sum += f(j); + } + global_res = sum; + return n * 4; +} + +function func_closure_call(n) +{ + function f(a) + { + sum++; + } + + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + f(j); + f(j); + f(j); + f(j); + } + global_res = sum; + return n * 4; +} + +function int_arith(n) +{ + var i, j, sum; + global_res = 0; + for(j = 0; j < n; j++) { + sum = 0; + for(i = 0; i < 1000; i++) { + sum += i * i; + } + global_res += sum; + } + return n * 1000; +} + +function float_arith(n) +{ + var i, j, sum, a, incr, a0; + global_res = 0; + a0 = 0.1; + incr = 1.1; + for(j = 0; j < n; j++) { + sum = 0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigint_arith(n, bits) +{ + var i, j, sum, a, incr, a0, sum0; + sum0 = global_res = BigInt(0); + a0 = BigInt(1) << BigInt(Math.floor((bits - 10) * 0.5)); + incr = BigInt(1); + for(j = 0; j < n; j++) { + sum = sum0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigint32_arith(n) +{ + return bigint_arith(n, 32); +} + +function bigint64_arith(n) +{ + return bigint_arith(n, 64); +} + +function bigint256_arith(n) +{ + return bigint_arith(n, 256); +} + +function map_set_string(n) +{ + var s, i, j, len = 1000; + for(j = 0; j < n; j++) { + s = new Map(); + for(i = 0; i < len; i++) { + s.set(String(i), i); + } + for(i = 0; i < len; i++) { + if (!s.has(String(i))) + throw Error("bug in Map"); + } + } + return n * len; +} + +function map_set_int(n) +{ + var s, i, j, len = 1000; + for(j = 0; j < n; j++) { + s = new Map(); + for(i = 0; i < len; i++) { + s.set(i, i); + } + for(i = 0; i < len; i++) { + if (!s.has(i)) + throw Error("bug in Map"); + } + } + return n * len; +} + +function map_set_bigint(n) +{ + var s, i, j, len = 1000; + for(j = 0; j < n; j++) { + s = new Map(); + for(i = 0; i < len; i++) { + s.set(BigInt(i), i); + } + for(i = 0; i < len; i++) { + if (!s.has(BigInt(i))) + throw Error("bug in Map"); + } + } + return n * len; +} + +function map_delete(n) +{ + var a, i, j; + + len = 1000; + for(j = 0; j < n; j++) { + a = new Map(); + for(i = 0; i < len; i++) { + a.set(String(i), i); + } + for(i = 0; i < len; i++) { + a.delete(String(i)); + } + } + return len * n; +} + +function weak_map_set(n) +{ + var a, i, j, tab; + + len = 1000; + tab = []; + for(i = 0; i < len; i++) { + tab.push({ key: i }); + } + for(j = 0; j < n; j++) { + a = new WeakMap(); + for(i = 0; i < len; i++) { + a.set(tab[i], i); + } + } + return len * n; +} + +function weak_map_delete(n) +{ + var a, i, j, tab; + + len = 1000; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) { + tab.push({ key: i }); + } + a = new WeakMap(); + for(i = 0; i < len; i++) { + a.set(tab[i], i); + } + for(i = 0; i < len; i++) { + tab[i] = null; + } + } + return len * n; +} + + +function array_for(n) +{ + var r, i, j, sum, len = 100; + r = []; + for(i = 0; i < len; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i = 0; i < len; i++) { + sum += r[i]; + } + global_res = sum; + } + return n * len; +} + +function array_for_in(n) +{ + var r, i, j, sum, len = 100; + r = []; + for(i = 0; i < len; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i in r) { + sum += r[i]; + } + global_res = sum; + } + return n * len; +} + +function array_for_of(n) +{ + var r, i, j, sum, len = 100; + r = []; + for(i = 0; i < len; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i of r) { + sum += i; + } + global_res = sum; + } + return n * len; +} + +function math_min(n) +{ + var i, j, r; + r = 0; + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = Math.min(i, 500); + global_res = r; + } + return n * 1000; +} + +function regexp_ascii(n) +{ + var i, j, r, s; + s = "the quick brown fox jumped over the lazy dog" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = /the quick brown fox/.exec(s) + global_res = r; + } + return n * 1000; +} + +function regexp_utf16(n) +{ + var i, j, r, s; + s = "the quick brown ᶠᵒˣ jumped over the lazy ᵈᵒᵍ" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = /the quick brown ᶠᵒˣ/.exec(s) + global_res = r; + } + return n * 1000; +} + +function regexp_replace(n) +{ + var i, j, r, s; + s = "the quick abc brown fox jumped abc over the lazy dog" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = s.replace(/abc /g, "-"); + global_res = r; + } + return n * 1000; +} + +function string_length(n) +{ + var str, sum, j; + str = "abcde"; + sum = 0; + for(j = 0; j < n; j++) { + sum += str.length; + sum += str.length; + sum += str.length; + sum += str.length; + } + global_res = sum; + return n * 4; +} + +/* incremental string contruction as local var */ +function string_build1(n) +{ + var i, j, r; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) + r += "x"; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction using + */ +function string_build1x(n) +{ + var i, j, r; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) + r = r + "x"; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction using +2c */ +function string_build2c(n) +{ + var i, j; + for(j = 0; j < n; j++) { + var r = ""; + for(i = 0; i < 1000; i++) + r += "xy"; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction as arg */ +function string_build2(n, r) +{ + var i, j; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) + r += "x"; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction by prepending */ +function string_build3(n) +{ + var i, j, r; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) + r = "x" + r; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction with multiple reference */ +function string_build4(n) +{ + var i, j, r, s; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) { + s = r; + r += "x"; + } + global_res = r; + } + return n * 1000; +} + +/* append */ +function string_build_large1(n) +{ + var i, j, r, len = 20000; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < len; i++) + r += "abcdef"; + global_res = r; + } + return n * len; +} + +/* prepend */ +function string_build_large2(n) +{ + var i, j, r, len = 20000; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < len; i++) + r = "abcdef" + r; + global_res = r; + } + return n * len; +} + +/* sort bench */ + +function sort_bench(text) { + function random(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * n) >> 0]; + } + function random8(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * 256) >> 0]; + } + function random1(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * 2) >> 0]; + } + function hill(arr, n, def) { + var mid = n >> 1; + for (var i = 0; i < mid; i++) + arr[i] = def[i]; + for (var i = mid; i < n; i++) + arr[i] = def[n - i]; + } + function comb(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(i & 1) * i]; + } + function crisscross(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(i & 1) ? n - i : i]; + } + function zero(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[0]; + } + function increasing(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i]; + } + function decreasing(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[n - 1 - i]; + } + function alternate(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i ^ 1]; + } + function jigsaw(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i % (n >> 4)]; + } + function incbutone(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i]; + if (n > 0) + arr[n >> 2] = def[n]; + } + function incbutfirst(arr, n, def) { + if (n > 0) + arr[0] = def[n]; + for (var i = 1; i < n; i++) + arr[i] = def[i]; + } + function incbutlast(arr, n, def) { + for (var i = 0; i < n - 1; i++) + arr[i] = def[i + 1]; + if (n > 0) + arr[n - 1] = def[0]; + } + + var sort_cases = [ random, random8, random1, jigsaw, hill, comb, + crisscross, zero, increasing, decreasing, alternate, + incbutone, incbutlast, incbutfirst ]; + + var n = sort_bench.array_size || 10000; + var array_type = sort_bench.array_type || Array; + var def, arr; + var i, j, x, y; + var total = 0; + + var save_total_score = total_score; + var save_total_scale = total_scale; + + // initialize default sorted array (n + 1 elements) + def = new array_type(n + 1); + if (array_type == Array) { + for (i = 0; i <= n; i++) { + def[i] = i + ""; + } + } else { + for (i = 0; i <= n; i++) { + def[i] = i; + } + } + def.sort(); + for (var f of sort_cases) { + var ti = 0, tx = 0; + for (j = 0; j < 100; j++) { + arr = new array_type(n); + f(arr, n, def); + var t1 = get_clock(); + arr.sort(); + t1 = get_clock() - t1; + tx += t1; + if (!ti || ti > t1) + ti = t1; + if (tx >= clocks_per_sec) + break; + } + total += ti; + + i = 0; + x = arr[0]; + if (x !== void 0) { + for (i = 1; i < n; i++) { + y = arr[i]; + if (y === void 0) + break; + if (x > y) + break; + x = y; + } + } + while (i < n && arr[i] === void 0) + i++; + if (i < n) { + console.log("sort_bench: out of order error for " + f.name + + " at offset " + (i - 1) + + ": " + arr[i - 1] + " > " + arr[i]); + } + if (sort_bench.verbose) + log_one("sort_" + f.name, 1, ti / 100); + } + total_score = save_total_score; + total_scale = save_total_scale; + return total / n / 100; +} +sort_bench.bench = true; +sort_bench.verbose = false; + +function int_to_string(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j % 10) + ''; + s = (j % 100) + ''; + s = (j) + ''; + } + return n * 3; +} + +function int_toString(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j % 10).toString(); + s = (j % 100).toString(); + s = (j).toString(); + } + return n * 3; +} + +function float_to_string(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j % 10 + 0.1) + ''; + s = (j + 0.1) + ''; + s = (j * 12345678 + 0.1) + ''; + } + return n * 3; +} + +function float_toString(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j % 10 + 0.1).toString(); + s = (j + 0.1).toString(); + s = (j * 12345678 + 0.1).toString(); + } + return n * 3; +} + +function float_toFixed(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j % 10 + 0.1).toFixed(j % 16); + s = (j + 0.1).toFixed(j % 16); + s = (j * 12345678 + 0.1).toFixed(j % 16); + } + return n * 3; +} + +function float_toPrecision(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j % 10 + 0.1).toPrecision(j % 16 + 1); + s = (j + 0.1).toPrecision(j % 16 + 1); + s = (j * 12345678 + 0.1).toPrecision(j % 16 + 1); + } + return n * 3; +} + +function float_toExponential(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j % 10 + 0.1).toExponential(j % 16); + s = (j + 0.1).toExponential(j % 16); + s = (j * 12345678 + 0.1).toExponential(j % 16); + } + return n * 3; +} + +function string_to_int(n) +{ + var s, r, j; + r = 0; + s = "12345"; + for(j = 0; j < n; j++) { + r += (s | 0); + } + global_res = r; + return n; +} + +function string_to_float(n) +{ + var s, r, j; + r = 0; + s = "12345.6"; + for(j = 0; j < n; j++) { + r -= s; + } + global_res = r; + return n; +} + +function load_result(filename) +{ + var has_filename = filename; + var has_error = false; + var str, res; + + if (!filename) + filename = "microbench.txt"; + + if (typeof fs !== "undefined") { + // read the file in Node.js + try { + str = fs.readFileSync(filename, { encoding: "utf8" }); + } catch { + has_error = true; + } + } else + if (typeof std !== "undefined") { + // read the file in QuickJS + var f = std.open(filename, "r"); + if (f) { + str = f.readAsString(); + f.close(); + } else { + has_error = true; + } + } else { + return null; + } + if (has_error) { + if (has_filename) { + // Should throw exception? + console.log("cannot load " + filename); + } + return null; + } + res = JSON.parse(str); + return res; +} + +function save_result(filename, obj) +{ + var str = JSON.stringify(obj, null, 2) + "\n"; + var has_error = false; + + if (typeof fs !== "undefined") { + // save the file in Node.js + try { + str = fs.writeFileSync(filename, str, { encoding: "utf8" }); + } catch { + has_error = true; + } + } else + if (typeof std !== "undefined") { + // save the file in QuickJS + var f = std.open(filename, "w"); + if (f) { + f.puts(str); + f.close(); + } else { + has_error = 'true'; + } + } else { + return; + } + if (has_error) + console.log("cannot save " + filename); +} + +function main(argc, argv, g) +{ + var test_list = [ + empty_loop, + empty_down_loop, + empty_down_loop2, + empty_do_loop, + date_now, + date_parse, + prop_read, + prop_write, + prop_update, + prop_create, + prop_clone, + prop_delete, + array_read, + array_write, + array_update, + array_prop_create, + array_slice, + array_length_read, + array_length_decr, + array_hole_length_decr, + array_push, + array_pop, + typed_array_read, + typed_array_write, + arguments_read, + arguments_strict_read, + global_read, + global_write, + global_write_strict, + local_destruct, + global_destruct, + global_destruct_strict, + global_func_call, + func_call, + func_closure_call, + int_arith, + float_arith, + map_set_string, + map_set_int, + map_set_bigint, + map_delete, + weak_map_set, + weak_map_delete, + array_for, + array_for_in, + array_for_of, + math_min, + regexp_ascii, + regexp_utf16, + regexp_replace, + string_length, + string_build1, + string_build1x, + string_build2c, + string_build2, + string_build3, + string_build4, + string_build_large1, + string_build_large2, + int_to_string, + int_toString, + float_to_string, + float_toString, + float_toFixed, + float_toPrecision, + float_toExponential, + string_to_int, + string_to_float, + ]; + var tests = []; + var i, j, n, f, name, found; + var ref_file, new_ref_file = "microbench-new.txt"; + + if (typeof BigInt === "function") { + /* BigInt test */ + test_list.push(bigint32_arith); + test_list.push(bigint64_arith); + test_list.push(bigint256_arith); + } + test_list.push(sort_bench); + + for (i = 1; i < argc;) { + name = argv[i++]; + if (name == "-a") { + sort_bench.verbose = true; + continue; + } + if (name == "-t") { + name = argv[i++]; + sort_bench.array_type = g[name]; + if (typeof sort_bench.array_type !== "function") { + console.log("unknown array type: " + name); + return 1; + } + continue; + } + if (name == "-n") { + sort_bench.array_size = +argv[i++]; + continue; + } + if (name == "-r") { + ref_file = argv[i++]; + continue; + } + if (name == "-s") { + new_ref_file = argv[i++]; + continue; + } + for (j = 0, found = false; j < test_list.length; j++) { + f = test_list[j]; + if (f.name.startsWith(name)) { + tests.push(f); + found = true; + } + } + if (!found) { + console.log("unknown benchmark: " + name); + return 1; + } + } + if (tests.length == 0) + tests = test_list; + + ref_data = load_result(ref_file); + log_data = {}; + log_line.apply(null, heads); + n = 0; + + for(i = 0; i < tests.length; i++) { + f = tests[i]; + bench(f, f.name, ref_data, log_data); + if (ref_data && ref_data[f.name]) + n++; + } + if (ref_data) + log_line("total", "", total[2], total[3], Math.round(total_scale * 1000 / total_score)); + else + log_line("total", "", total[2]); + + if (tests == test_list && new_ref_file) + save_result(new_ref_file, log_data); +} + +if (typeof scriptArgs === "undefined") { + scriptArgs = []; + if (typeof process.argv === "object") + scriptArgs = process.argv.slice(1); +} +main(scriptArgs.length, scriptArgs, this); diff --git a/NativeScript/napi/android/quickjs/source/tests/test262.patch b/NativeScript/napi/android/quickjs/source/tests/test262.patch new file mode 100755 index 000000000..d7cba88cc --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test262.patch @@ -0,0 +1,72 @@ +diff --git a/harness/atomicsHelper.js b/harness/atomicsHelper.js +index 9828b15..9e24d64 100644 +--- a/harness/atomicsHelper.js ++++ b/harness/atomicsHelper.js +@@ -272,10 +272,14 @@ $262.agent.waitUntil = function(typedArray, index, expected) { + * } + */ + $262.agent.timeouts = { +- yield: 100, +- small: 200, +- long: 1000, +- huge: 10000, ++// yield: 100, ++// small: 200, ++// long: 1000, ++// huge: 10000, ++ yield: 40, ++ small: 40, ++ long: 200, ++ huge: 1000, + }; + + /** +diff --git a/harness/regExpUtils.js b/harness/regExpUtils.js +index b397be0..c197ddc 100644 +--- a/harness/regExpUtils.js ++++ b/harness/regExpUtils.js +@@ -6,27 +6,30 @@ description: | + defines: [buildString, testPropertyEscapes, testPropertyOfStrings, testExtendedCharacterClass, matchValidator] + ---*/ + ++if ($262 && typeof $262.codePointRange === "function") { ++ /* use C function to build the codePointRange (much faster with ++ slow JS engines) */ ++ codePointRange = $262.codePointRange; ++} else { ++ codePointRange = function codePointRange(start, end) { ++ const codePoints = []; ++ let length = 0; ++ for (codePoint = start; codePoint < end; codePoint++) { ++ codePoints[length++] = codePoint; ++ } ++ return String.fromCodePoint.apply(null, codePoints); ++ } ++} ++ + function buildString(args) { + // Use member expressions rather than destructuring `args` for improved + // compatibility with engines that only implement assignment patterns + // partially or not at all. + const loneCodePoints = args.loneCodePoints; + const ranges = args.ranges; +- const CHUNK_SIZE = 10000; + let result = String.fromCodePoint.apply(null, loneCodePoints); +- for (let i = 0; i < ranges.length; i++) { +- let range = ranges[i]; +- let start = range[0]; +- let end = range[1]; +- let codePoints = []; +- for (let length = 0, codePoint = start; codePoint <= end; codePoint++) { +- codePoints[length++] = codePoint; +- if (length === CHUNK_SIZE) { +- result += String.fromCodePoint.apply(null, codePoints); +- codePoints.length = length = 0; +- } +- } +- result += String.fromCodePoint.apply(null, codePoints); ++ for (const [start, end] of ranges) { ++ result += codePointRange(start, end + 1); + } + return result; + } diff --git a/NativeScript/napi/android/quickjs/source/tests/test_bigint.js b/NativeScript/napi/android/quickjs/source/tests/test_bigint.js new file mode 100755 index 000000000..a0d028c91 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_bigint.js @@ -0,0 +1,249 @@ +"use strict"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assertThrows(err, func) +{ + var ex; + ex = false; + try { + func(); + } catch(e) { + ex = true; + assert(e instanceof err); + } + assert(ex, true, "exception expected"); +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function bigint_pow(a, n) +{ + var r, i; + r = 1n; + for(i = 0n; i < n; i++) + r *= a; + return r; +} + +/* a must be < b */ +function test_less(a, b) +{ + assert(a < b); + assert(!(b < a)); + assert(a <= b); + assert(!(b <= a)); + assert(b > a); + assert(!(a > b)); + assert(b >= a); + assert(!(a >= b)); + assert(a != b); + assert(!(a == b)); +} + +/* a must be numerically equal to b */ +function test_eq(a, b) +{ + assert(a == b); + assert(b == a); + assert(!(a != b)); + assert(!(b != a)); + assert(a <= b); + assert(b <= a); + assert(!(a < b)); + assert(a >= b); + assert(b >= a); + assert(!(a > b)); +} + +function test_bigint1() +{ + var a, r; + + test_less(2n, 3n); + test_eq(3n, 3n); + + test_less(2, 3n); + test_eq(3, 3n); + + test_less(2.1, 3n); + test_eq(Math.sqrt(4), 2n); + + a = bigint_pow(3n, 100n); + assert((a - 1n) != a); + assert(a == 515377520732011331036461129765621272702107522001n); + assert(a == 0x5a4653ca673768565b41f775d6947d55cf3813d1n); + + r = 1n << 31n; + assert(r, 2147483648n, "1 << 31n === 2147483648n"); + + r = 1n << 32n; + assert(r, 4294967296n, "1 << 32n === 4294967296n"); +} + +function test_bigint2() +{ + assert(BigInt(""), 0n); + assert(BigInt(" 123"), 123n); + assert(BigInt(" 123 "), 123n); + assertThrows(SyntaxError, () => { BigInt("+") } ); + assertThrows(SyntaxError, () => { BigInt("-") } ); + assertThrows(SyntaxError, () => { BigInt("\x00a") } ); + assertThrows(SyntaxError, () => { BigInt(" 123 r") } ); +} + +function test_bigint3() +{ + assert(Number(0xffffffffffffffffn), 18446744073709552000); + assert(Number(-0xffffffffffffffffn), -18446744073709552000); + assert(100000000000000000000n == 1e20, true); + assert(100000000000000000001n == 1e20, false); + assert((1n << 100n).toString(10), "1267650600228229401496703205376"); + assert((-1n << 100n).toString(36), "-3ewfdnca0n6ld1ggvfgg"); + assert((1n << 100n).toString(8), "2000000000000000000000000000000000"); + + assert(0x5a4653ca673768565b41f775n << 78n, 8443945299673273647701379149826607537748959488376832n); + assert(-0x5a4653ca673768565b41f775n << 78n, -8443945299673273647701379149826607537748959488376832n); + assert(0x5a4653ca673768565b41f775n >> 78n, 92441n); + assert(-0x5a4653ca673768565b41f775n >> 78n, -92442n); + + assert(~0x5a653ca6n, -1516584103n); + assert(0x5a463ca6n | 0x67376856n, 2138537206n); + assert(0x5a463ca6n & 0x67376856n, 1107699718n); + assert(0x5a463ca6n ^ 0x67376856n, 1030837488n); + + assert(3213213213213213432453243n / 123434343439n, 26031760073331n); + assert(-3213213213213213432453243n / 123434343439n, -26031760073331n); + assert(-3213213213213213432453243n % -123434343439n, -26953727934n); + assert(3213213213213213432453243n % 123434343439n, 26953727934n); + + assert((-2n) ** 127n, -170141183460469231731687303715884105728n); + assert((2n) ** 127n, 170141183460469231731687303715884105728n); + assert((-256n) ** 11n, -309485009821345068724781056n); + assert((7n) ** 20n, 79792266297612001n); +} + +/* pi computation */ + +/* return floor(log2(a)) for a > 0 and 0 for a = 0 */ +function floor_log2(a) +{ + var k_max, a1, k, i; + k_max = 0n; + while ((a >> (2n ** k_max)) != 0n) { + k_max++; + } + k = 0n; + a1 = a; + for(i = k_max - 1n; i >= 0n; i--) { + a1 = a >> (2n ** i); + if (a1 != 0n) { + a = a1; + k |= (1n << i); + } + } + return k; +} + +/* return ceil(log2(a)) for a > 0 */ +function ceil_log2(a) +{ + return floor_log2(a - 1n) + 1n; +} + +/* return floor(sqrt(a)) (not efficient but simple) */ +function int_sqrt(a) +{ + var l, u, s; + if (a == 0n) + return a; + l = ceil_log2(a); + u = 1n << ((l + 1n) / 2n); + /* u >= floor(sqrt(a)) */ + for(;;) { + s = u; + u = ((a / s) + s) / 2n; + if (u >= s) + break; + } + return s; +} + +/* return pi * 2**prec */ +function calc_pi(prec) { + const CHUD_A = 13591409n; + const CHUD_B = 545140134n; + const CHUD_C = 640320n; + const CHUD_C3 = 10939058860032000n; /* C^3/24 */ + const CHUD_BITS_PER_TERM = 47.11041313821584202247; /* log2(C/12)*3 */ + + /* return [P, Q, G] */ + function chud_bs(a, b, need_G) { + var c, P, Q, G, P1, Q1, G1, P2, Q2, G2; + if (a == (b - 1n)) { + G = (2n * b - 1n) * (6n * b - 1n) * (6n * b - 5n); + P = G * (CHUD_B * b + CHUD_A); + if (b & 1n) + P = -P; + Q = b * b * b * CHUD_C3; + } else { + c = (a + b) >> 1n; + [P1, Q1, G1] = chud_bs(a, c, true); + [P2, Q2, G2] = chud_bs(c, b, need_G); + P = P1 * Q2 + P2 * G1; + Q = Q1 * Q2; + if (need_G) + G = G1 * G2; + else + G = 0n; + } + return [P, Q, G]; + } + + var n, P, Q, G; + /* number of serie terms */ + n = BigInt(Math.ceil(Number(prec) / CHUD_BITS_PER_TERM)) + 10n; + [P, Q, G] = chud_bs(0n, n, false); + Q = (CHUD_C / 12n) * (Q << prec) / (P + Q * CHUD_A); + G = int_sqrt(CHUD_C << (2n * prec)); + return (Q * G) >> prec; +} + +function compute_pi(n_digits) { + var r, n_digits, n_bits, out; + /* we add more bits to reduce the probability of bad rounding for + the last digits */ + n_bits = BigInt(Math.ceil(n_digits * Math.log2(10))) + 32n; + r = calc_pi(n_bits); + r = ((10n ** BigInt(n_digits)) * r) >> n_bits; + out = r.toString(); + return out[0] + "." + out.slice(1); +} + +function test_pi() +{ + assert(compute_pi(2000), "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036001133053054882046652138414695194151160943305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912983367336244065664308602139494639522473719070217986094370277053921717629317675238467481846766940513200056812714526356082778577134275778960917363717872146844090122495343014654958537105079227968925892354201995611212902196086403441815981362977477130996051870721134999999837297804995105973173281609631859502445945534690830264252230825334468503526193118817101000313783875288658753320838142061717766914730359825349042875546873115956286388235378759375195778185778053217122680661300192787661119590921642019893809525720106548586327886593615338182796823030195203530185296899577362259941389124972177528347913151557485724245415069595082953311686172785588907509838175463746493931925506040092770167113900984882401285836160356370766010471018194295559619894676783744944825537977472684710404753464620804668425906949129331367702898915210475216205696602405803815019351125338243003558764024749647326391419927260426992279678235478163600934172164121992458631503028618297455570674983850549458858692699569092721079750930295532116534498720275596023648066549911988183479775356636980742654252786255181841757467289097777279380008164706001614524919217321721477235014144197356854816136115735255213347574184946843852332390739414333454776241686251898356948556209921922218427255025425688767179049460165346680498862723279178608578438382796797668145410095388378636095068006422512520511739298489608412848862694560424196528502221066118630674427862203919494504712371378696095636437191728746776465757396241389086583264599581339047802759009"); +} + +test_bigint1(); +test_bigint2(); +test_bigint3(); +test_pi(); diff --git a/NativeScript/napi/android/quickjs/source/tests/test_bjson.js b/NativeScript/napi/android/quickjs/source/tests/test_bjson.js new file mode 100755 index 000000000..12180d163 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_bjson.js @@ -0,0 +1,224 @@ +import * as bjson from "./bjson.so"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function toHex(a) +{ + var i, s = "", tab, v; + tab = new Uint8Array(a); + for(i = 0; i < tab.length; i++) { + v = tab[i].toString(16); + if (v.length < 2) + v = "0" + v; + if (i !== 0) + s += " "; + s += v; + } + return s; +} + +function isArrayLike(a) +{ + return Array.isArray(a) || + (a instanceof Uint8ClampedArray) || + (a instanceof Uint8Array) || + (a instanceof Uint16Array) || + (a instanceof Uint32Array) || + (a instanceof Int8Array) || + (a instanceof Int16Array) || + (a instanceof Int32Array) || + (a instanceof Float16Array) || + (a instanceof Float32Array) || + (a instanceof Float64Array); +} + +function toStr(a) +{ + var s, i, props, prop; + + switch(typeof(a)) { + case "object": + if (a === null) + return "null"; + if (a instanceof Date) { + s = "Date(" + toStr(a.valueOf()) + ")"; + } else if (a instanceof Number) { + s = "Number(" + toStr(a.valueOf()) + ")"; + } else if (a instanceof String) { + s = "String(" + toStr(a.valueOf()) + ")"; + } else if (a instanceof Boolean) { + s = "Boolean(" + toStr(a.valueOf()) + ")"; + } else if (isArrayLike(a)) { + s = "["; + for(i = 0; i < a.length; i++) { + if (i != 0) + s += ","; + s += toStr(a[i]); + } + s += "]"; + } else { + props = Object.keys(a); + s = "{"; + for(i = 0; i < props.length; i++) { + if (i != 0) + s += ","; + prop = props[i]; + s += prop + ":" + toStr(a[prop]); + } + s += "}"; + } + return s; + case "undefined": + return "undefined"; + case "string": + return JSON.stringify(a); + case "number": + if (a == 0 && 1 / a < 0) + return "-0"; + else + return a.toString(); + break; + default: + return a.toString(); + } +} + +function bjson_test(a) +{ + var buf, r, a_str, r_str; + a_str = toStr(a); + buf = bjson.write(a); + if (0) { + print(a_str, "->", toHex(buf)); + } + r = bjson.read(buf, 0, buf.byteLength); + r_str = toStr(r); + if (a_str != r_str) { + print(a_str); + print(r_str); + assert(false); + } +} + +function bjson_test_arraybuffer() +{ + var buf, array_buffer; + + array_buffer = new ArrayBuffer(4); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 4); + assert(array_buffer.resizable, false); + buf = bjson.write(array_buffer); + array_buffer = bjson.read(buf, 0, buf.byteLength); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 4); + assert(array_buffer.resizable, false); + + array_buffer = new ArrayBuffer(4, {maxByteLength: 4}); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 4); + assert(array_buffer.resizable, true); + buf = bjson.write(array_buffer); + array_buffer = bjson.read(buf, 0, buf.byteLength); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 4); + assert(array_buffer.resizable, true); + + array_buffer = new ArrayBuffer(4, {maxByteLength: 8}); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 8); + assert(array_buffer.resizable, true); + buf = bjson.write(array_buffer); + array_buffer = bjson.read(buf, 0, buf.byteLength); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 8); + assert(array_buffer.resizable, true); +} + +/* test multiple references to an object including circular + references */ +function bjson_test_reference() +{ + var array, buf, i, n, array_buffer; + n = 16; + array = []; + for(i = 0; i < n; i++) + array[i] = {}; + array_buffer = new ArrayBuffer(n); + for(i = 0; i < n; i++) { + array[i].next = array[(i + 1) % n]; + array[i].idx = i; + array[i].typed_array = new Uint8Array(array_buffer, i, 1); + } + buf = bjson.write(array, true); + + array = bjson.read(buf, 0, buf.byteLength, true); + + /* check the result */ + for(i = 0; i < n; i++) { + assert(array[i].next, array[(i + 1) % n]); + assert(array[i].idx, i); + assert(array[i].typed_array.buffer, array_buffer); + assert(array[i].typed_array.length, 1); + assert(array[i].typed_array.byteOffset, i); + } +} + +function bjson_test_all() +{ + var obj; + + bjson_test({x:1, y:2, if:3}); + + bjson_test([1, 2, 3]); + + /* array with holes */ + bjson_test([1, , 2, , 3]); + + /* fast array with hole */ + obj = new Array(5); + obj[0] = 1; + obj[1] = 2; + bjson_test(obj); + + bjson_test([1.0, "aa", true, false, undefined, null, NaN, -Infinity, -0.0]); + if (typeof BigInt !== "undefined") { + bjson_test([BigInt("1"), -BigInt("0x123456789"), + BigInt("0x123456789abcdef123456789abcdef")]); + } + bjson_test([new Date(1234), new String("abc"), new Number(-12.1), new Boolean(true)]); + + bjson_test(new Int32Array([123123, 222111, -32222])); + bjson_test(new Float16Array([1024, 1024.5])); + bjson_test(new Float64Array([123123, 222111.5])); + + /* tested with a circular reference */ + obj = {}; + obj.x = obj; + try { + bjson.write(obj); + assert(false); + } catch(e) { + assert(e instanceof TypeError); + } + + bjson_test_arraybuffer(); + bjson_test_reference(); +} + +bjson_test_all(); diff --git a/NativeScript/napi/android/quickjs/source/tests/test_builtin.js b/NativeScript/napi/android/quickjs/source/tests/test_builtin.js new file mode 100755 index 000000000..14bcc06d3 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_builtin.js @@ -0,0 +1,1193 @@ +"use strict"; + +var status = 0; +var throw_errors = true; + +function throw_error(msg) { + if (throw_errors) + throw Error(msg); + console.log(msg); + status = 1; +} + +function assert(actual, expected, message) { + function get_full_type(o) { + var type = typeof(o); + if (type === 'object') { + if (o === null) + return 'null'; + if (o.constructor && o.constructor.name) + return o.constructor.name; + } + return type; + } + + if (arguments.length == 1) + expected = true; + + if (typeof actual === typeof expected) { + if (actual === expected) { + if (actual !== 0 || (1 / actual) === (1 / expected)) + return; + } + if (typeof actual === 'number') { + if (isNaN(actual) && isNaN(expected)) + return true; + } + if (typeof actual === 'object') { + if (actual !== null && expected !== null + && actual.constructor === expected.constructor + && actual.toString() === expected.toString()) + return; + } + } + // Should output the source file and line number and extract + // the expression from the assert call + throw_error("assertion failed: got " + + get_full_type(actual) + ":|" + actual + "|, expected " + + get_full_type(expected) + ":|" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assert_throws(expected_error, func) +{ + var err = false; + try { + func(); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("unexpected exception type"); + return; + } + } + if (!err) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("expected exception"); + } +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function my_func(a, b) +{ + return a + b; +} + +function test_function() +{ + function f(a, b) { + var i, tab = []; + tab.push(this); + for(i = 0; i < arguments.length; i++) + tab.push(arguments[i]); + return tab; + } + function constructor1(a) { + this.x = a; + } + + var r, g; + + r = my_func.call(null, 1, 2); + assert(r, 3, "call"); + + r = my_func.apply(null, [1, 2]); + assert(r, 3, "apply"); + + r = (function () { return 1; }).apply(null, undefined); + assert(r, 1); + + assert_throws(TypeError, (function() { + Reflect.apply((function () { return 1; }), null, undefined); + })); + + r = new Function("a", "b", "return a + b;"); + assert(r(2,3), 5, "function"); + + g = f.bind(1, 2); + assert(g.length, 1); + assert(g.name, "bound f"); + assert(g(3), [1,2,3]); + + g = constructor1.bind(null, 1); + r = new g(); + assert(r.x, 1); +} + +function test() +{ + var r, a, b, c, err; + + r = Error("hello"); + assert(r.message, "hello", "Error"); + + a = new Object(); + a.x = 1; + assert(a.x, 1, "Object"); + + assert(Object.getPrototypeOf(a), Object.prototype, "getPrototypeOf"); + Object.defineProperty(a, "y", { value: 3, writable: true, configurable: true, enumerable: true }); + assert(a.y, 3, "defineProperty"); + + Object.defineProperty(a, "z", { get: function () { return 4; }, set: function(val) { this.z_val = val; }, configurable: true, enumerable: true }); + assert(a.z, 4, "get"); + a.z = 5; + assert(a.z_val, 5, "set"); + + a = { get z() { return 4; }, set z(val) { this.z_val = val; } }; + assert(a.z, 4, "get"); + a.z = 5; + assert(a.z_val, 5, "set"); + + b = Object.create(a); + assert(Object.getPrototypeOf(b), a, "create"); + c = {u:2}; + /* XXX: refcount bug in 'b' instead of 'a' */ + Object.setPrototypeOf(a, c); + assert(Object.getPrototypeOf(a), c, "setPrototypeOf"); + + a = {}; + assert(a.toString(), "[object Object]", "toString"); + + a = {x:1}; + assert(Object.isExtensible(a), true, "extensible"); + Object.preventExtensions(a); + + err = false; + try { + a.y = 2; + } catch(e) { + err = true; + } + assert(Object.isExtensible(a), false, "extensible"); + assert(typeof a.y, "undefined", "extensible"); + assert(err, true, "extensible"); +} + +function test_enum() +{ + var a, tab; + a = {x:1, + "18014398509481984": 1, + "9007199254740992": 1, + "9007199254740991": 1, + "4294967296": 1, + "4294967295": 1, + y:1, + "4294967294": 1, + "1": 2}; + tab = Object.keys(a); +// console.log("tab=" + tab.toString()); + assert(tab, ["1","4294967294","x","18014398509481984","9007199254740992","9007199254740991","4294967296","4294967295","y"], "keys"); +} + +function test_array() +{ + var a, err; + + a = [1, 2, 3]; + assert(a.length, 3, "array"); + assert(a[2], 3, "array1"); + + a = new Array(10); + assert(a.length, 10, "array2"); + + a = new Array(1, 2); + assert(a.length === 2 && a[0] === 1 && a[1] === 2, true, "array3"); + + a = [1, 2, 3]; + a.length = 2; + assert(a.length === 2 && a[0] === 1 && a[1] === 2, true, "array4"); + + a = []; + a[1] = 10; + a[4] = 3; + assert(a.length, 5); + + a = [1,2]; + a.length = 5; + a[4] = 1; + a.length = 4; + assert(a[4] !== 1, true, "array5"); + + a = [1,2]; + a.push(3,4); + assert(a.join(), "1,2,3,4", "join"); + + a = [1,2,3,4,5]; + Object.defineProperty(a, "3", { configurable: false }); + err = false; + try { + a.length = 2; + } catch(e) { + err = true; + } + assert(err && a.toString() === "1,2,3,4"); +} + +function test_string() +{ + var a; + a = String("abc"); + assert(a.length, 3, "string"); + assert(a[1], "b", "string"); + assert(a.charCodeAt(1), 0x62, "string"); + assert(String.fromCharCode(65), "A", "string"); + assert(String.fromCharCode.apply(null, [65, 66, 67]), "ABC", "string"); + assert(a.charAt(1), "b"); + assert(a.charAt(-1), ""); + assert(a.charAt(3), ""); + + a = "abcd"; + assert(a.substring(1, 3), "bc", "substring"); + a = String.fromCharCode(0x20ac); + assert(a.charCodeAt(0), 0x20ac, "unicode"); + assert(a, "€", "unicode"); + assert(a, "\u20ac", "unicode"); + assert(a, "\u{20ac}", "unicode"); + assert("a", "\x61", "unicode"); + + a = "\u{10ffff}"; + assert(a.length, 2, "unicode"); + assert(a, "\u{dbff}\u{dfff}", "unicode"); + assert(a.codePointAt(0), 0x10ffff); + assert(String.fromCodePoint(0x10ffff), a); + + assert("a".concat("b", "c"), "abc"); + + assert("abcabc".indexOf("cab"), 2); + assert("abcabc".indexOf("cab2"), -1); + assert("abc".indexOf("c"), 2); + + assert("aaa".indexOf("a"), 0); + assert("aaa".indexOf("a", NaN), 0); + assert("aaa".indexOf("a", -Infinity), 0); + assert("aaa".indexOf("a", -1), 0); + assert("aaa".indexOf("a", -0), 0); + assert("aaa".indexOf("a", 0), 0); + assert("aaa".indexOf("a", 1), 1); + assert("aaa".indexOf("a", 2), 2); + assert("aaa".indexOf("a", 3), -1); + assert("aaa".indexOf("a", 4), -1); + assert("aaa".indexOf("a", Infinity), -1); + + assert("aaa".indexOf(""), 0); + assert("aaa".indexOf("", NaN), 0); + assert("aaa".indexOf("", -Infinity), 0); + assert("aaa".indexOf("", -1), 0); + assert("aaa".indexOf("", -0), 0); + assert("aaa".indexOf("", 0), 0); + assert("aaa".indexOf("", 1), 1); + assert("aaa".indexOf("", 2), 2); + assert("aaa".indexOf("", 3), 3); + assert("aaa".indexOf("", 4), 3); + assert("aaa".indexOf("", Infinity), 3); + + assert("aaa".lastIndexOf("a"), 2); + assert("aaa".lastIndexOf("a", NaN), 2); + assert("aaa".lastIndexOf("a", -Infinity), 0); + assert("aaa".lastIndexOf("a", -1), 0); + assert("aaa".lastIndexOf("a", -0), 0); + assert("aaa".lastIndexOf("a", 0), 0); + assert("aaa".lastIndexOf("a", 1), 1); + assert("aaa".lastIndexOf("a", 2), 2); + assert("aaa".lastIndexOf("a", 3), 2); + assert("aaa".lastIndexOf("a", 4), 2); + assert("aaa".lastIndexOf("a", Infinity), 2); + + assert("aaa".lastIndexOf(""), 3); + assert("aaa".lastIndexOf("", NaN), 3); + assert("aaa".lastIndexOf("", -Infinity), 0); + assert("aaa".lastIndexOf("", -1), 0); + assert("aaa".lastIndexOf("", -0), 0); + assert("aaa".lastIndexOf("", 0), 0); + assert("aaa".lastIndexOf("", 1), 1); + assert("aaa".lastIndexOf("", 2), 2); + assert("aaa".lastIndexOf("", 3), 3); + assert("aaa".lastIndexOf("", 4), 3); + assert("aaa".lastIndexOf("", Infinity), 3); + + assert("a,b,c".split(","), ["a","b","c"]); + assert(",b,c".split(","), ["","b","c"]); + assert("a,b,".split(","), ["a","b",""]); + + assert("aaaa".split(), [ "aaaa" ]); + assert("aaaa".split(undefined, 0), [ ]); + assert("aaaa".split(""), [ "a", "a", "a", "a" ]); + assert("aaaa".split("", 0), [ ]); + assert("aaaa".split("", 1), [ "a" ]); + assert("aaaa".split("", 2), [ "a", "a" ]); + assert("aaaa".split("a"), [ "", "", "", "", "" ]); + assert("aaaa".split("a", 2), [ "", "" ]); + assert("aaaa".split("aa"), [ "", "", "" ]); + assert("aaaa".split("aa", 0), [ ]); + assert("aaaa".split("aa", 1), [ "" ]); + assert("aaaa".split("aa", 2), [ "", "" ]); + assert("aaaa".split("aaa"), [ "", "a" ]); + assert("aaaa".split("aaaa"), [ "", "" ]); + assert("aaaa".split("aaaaa"), [ "aaaa" ]); + assert("aaaa".split("aaaaa", 0), [ ]); + assert("aaaa".split("aaaaa", 1), [ "aaaa" ]); + + assert(eval('"\0"'), "\0"); + + assert("abc".padStart(Infinity, ""), "abc"); +} + +function test_math() +{ + var a; + a = 1.4; + assert(Math.floor(a), 1); + assert(Math.ceil(a), 2); + assert(Math.imul(0x12345678, 123), -1088058456); + assert(Math.imul(0xB505, 0xB504), 2147441940); + assert(Math.imul(0xB505, 0xB505), -2147479015); + assert(Math.imul((-2)**31, (-2)**31), 0); + assert(Math.imul(2**31-1, 2**31-1), 1); + assert(Math.fround(0.1), 0.10000000149011612); + assert(Math.hypot(), 0); + assert(Math.hypot(-2), 2); + assert(Math.hypot(3, 4), 5); + assert(Math.abs(Math.hypot(3, 4, 5) - 7.0710678118654755) <= 1e-15); + assert(Math.sumPrecise([1,Number.EPSILON/2,Number.MIN_VALUE]), 1.0000000000000002); +} + +function test_number() +{ + assert(parseInt("123"), 123); + assert(parseInt(" 123r"), 123); + assert(parseInt("0x123"), 0x123); + assert(parseInt("0o123"), 0); + assert(+" 123 ", 123); + assert(+"0b111", 7); + assert(+"0o123", 83); + assert(parseFloat("2147483647"), 2147483647); + assert(parseFloat("2147483648"), 2147483648); + assert(parseFloat("-2147483647"), -2147483647); + assert(parseFloat("-2147483648"), -2147483648); + assert(parseFloat("0x1234"), 0); + assert(parseFloat("Infinity"), Infinity); + assert(parseFloat("-Infinity"), -Infinity); + assert(parseFloat("123.2"), 123.2); + assert(parseFloat("123.2e3"), 123200); + assert(Number.isNaN(Number("+"))); + assert(Number.isNaN(Number("-"))); + assert(Number.isNaN(Number("\x00a"))); + + assert((1-2**-53).toString(12), "0.bbbbbbbbbbbbbba"); + assert((1000000000000000128).toString(), "1000000000000000100"); + assert((1000000000000000128).toFixed(0), "1000000000000000128"); + assert((25).toExponential(0), "3e+1"); + assert((-25).toExponential(0), "-3e+1"); + assert((2.5).toPrecision(1), "3"); + assert((-2.5).toPrecision(1), "-3"); + assert((25).toPrecision(1) === "3e+1"); + assert((1.125).toFixed(2), "1.13"); + assert((-1.125).toFixed(2), "-1.13"); + assert((0.5).toFixed(0), "1"); + assert((-0.5).toFixed(0), "-1"); + assert((-1e-10).toFixed(0), "-0"); + + assert((1.3).toString(7), "1.2046204620462046205"); + assert((1.3).toString(35), "1.ahhhhhhhhhm"); +} + +function test_eval2() +{ + var g_call_count = 0; + /* force non strict mode for f1 and f2 */ + var f1 = new Function("eval", "eval(1, 2)"); + var f2 = new Function("eval", "eval(...[1, 2])"); + function g(a, b) { + assert(a, 1); + assert(b, 2); + g_call_count++; + } + f1(g); + f2(g); + assert(g_call_count, 2); +} + +function test_eval() +{ + function f(b) { + var x = 1; + return eval(b); + } + var r, a; + + r = eval("1+1;"); + assert(r, 2, "eval"); + + r = eval("var my_var=2; my_var;"); + assert(r, 2, "eval"); + assert(typeof my_var, "undefined"); + + assert(eval("if (1) 2; else 3;"), 2); + assert(eval("if (0) 2; else 3;"), 3); + + assert(f.call(1, "this"), 1); + + a = 2; + assert(eval("a"), 2); + + eval("a = 3"); + assert(a, 3); + + assert(f("arguments.length", 1), 2); + assert(f("arguments[1]", 1), 1); + + a = 4; + assert(f("a"), 4); + f("a=3"); + assert(a, 3); + + test_eval2(); +} + +function test_typed_array() +{ + var buffer, a, i, str; + + a = new Uint8Array(4); + assert(a.length, 4); + for(i = 0; i < a.length; i++) + a[i] = i; + assert(a.join(","), "0,1,2,3"); + a[0] = -1; + assert(a[0], 255); + + a = new Int8Array(3); + a[0] = 255; + assert(a[0], -1); + + a = new Int32Array(3); + a[0] = Math.pow(2, 32) - 1; + assert(a[0], -1); + assert(a.BYTES_PER_ELEMENT, 4); + + a = new Uint8ClampedArray(4); + a[0] = -100; + a[1] = 1.5; + a[2] = 0.5; + a[3] = 1233.5; + assert(a.toString(), "0,2,0,255"); + + buffer = new ArrayBuffer(16); + assert(buffer.byteLength, 16); + a = new Uint32Array(buffer, 12, 1); + assert(a.length, 1); + a[0] = -1; + + a = new Uint16Array(buffer, 2); + a[0] = -1; + + a = new Float16Array(buffer, 8, 1); + a[0] = 1; + + a = new Float32Array(buffer, 8, 1); + a[0] = 1; + + a = new Uint8Array(buffer); + + str = a.toString(); + /* test little and big endian cases */ + if (str !== "0,0,255,255,0,0,0,0,0,0,128,63,255,255,255,255" && + str !== "0,0,255,255,0,0,0,0,63,128,0,0,255,255,255,255") { + assert(false); + } + + assert(a.buffer, buffer); + + a = new Uint8Array([1, 2, 3, 4]); + assert(a.toString(), "1,2,3,4"); + a.set([10, 11], 2); + assert(a.toString(), "1,2,10,11"); + + // https://github.com/quickjs-ng/quickjs/issues/1208 + buffer = new ArrayBuffer(16); + a = new Uint8Array(buffer); + a.fill(42); + assert(a[0], 42); + buffer.transfer(); + assert(a[0], undefined); +} + +/* return [s, line_num, col_num] where line_num and col_num are the + position of the '@' character in 'str'. 's' is str without the '@' + character */ +function get_string_pos(str) +{ + var p, line_num, col_num, s, q, r; + p = str.indexOf('@'); + assert(p >= 0, true); + q = 0; + line_num = 1; + for(;;) { + r = str.indexOf('\n', q); + if (r < 0 || r >= p) + break; + q = r + 1; + line_num++; + } + col_num = p - q + 1; + s = str.slice(0, p) + str.slice(p + 1); + return [s, line_num, col_num]; +} + +function check_error_pos(e, expected_error, line_num, col_num, level) +{ + var expected_pos, tab, line; + level |= 0; + expected_pos = ":" + line_num + ":" + col_num; + tab = e.stack.split("\n"); + line = tab[level]; + if (line.slice(-1) == ')') + line = line.slice(0, -1); + if (line.indexOf(expected_pos) < 0) { + throw_error("unexpected line or column number. error=" + e.message + + ".got |" + line + "|, expected |" + expected_pos + "|"); + } +} + +function assert_json_error(str, line_num, col_num) +{ + var err = false; + var expected_pos, tab; + + tab = get_string_pos(str); + + try { + JSON.parse(tab[0]); + } catch(e) { + err = true; + if (!(e instanceof SyntaxError)) { + throw_error("unexpected exception type"); + return; + } + /* XXX: the way quickjs returns JSON errors is not similar to Node or spiderMonkey */ + check_error_pos(e, SyntaxError, tab[1], tab[2]); + } + if (!err) { + throw_error("expected exception"); + } +} + +function test_json() +{ + var a, s; + s = '{"x":1,"y":true,"z":null,"a":[1,2,3],"s":"str"}'; + a = JSON.parse(s); + assert(a.x, 1); + assert(a.y, true); + assert(a.z, null); + assert(JSON.stringify(a), s); + + /* indentation test */ + assert(JSON.stringify([[{x:1,y:{},z:[]},2,3]],undefined,1), +`[ + [ + { + "x": 1, + "y": {}, + "z": [] + }, + 2, + 3 + ] +]`); + + assert_json_error('\n" \\@x"'); + assert_json_error('\n{ "a": @x }"'); +} + +function test_date() +{ + // Date Time String format is YYYY-MM-DDTHH:mm:ss.sssZ + // accepted date formats are: YYYY, YYYY-MM and YYYY-MM-DD + // accepted time formats are: THH:mm, THH:mm:ss, THH:mm:ss.sss + // expanded years are represented with 6 digits prefixed by + or - + // -000000 is invalid. + // A string containing out-of-bounds or nonconforming elements + // is not a valid instance of this format. + // Hence the fractional part after . should have 3 digits and how + // a different number of digits is handled is implementation defined. + assert(Date.parse(""), NaN); + assert(Date.parse("2000"), 946684800000); + assert(Date.parse("2000-01"), 946684800000); + assert(Date.parse("2000-01-01"), 946684800000); + //assert(Date.parse("2000-01-01T"), NaN); + //assert(Date.parse("2000-01-01T00Z"), NaN); + assert(Date.parse("2000-01-01T00:00Z"), 946684800000); + assert(Date.parse("2000-01-01T00:00:00Z"), 946684800000); + assert(Date.parse("2000-01-01T00:00:00.1Z"), 946684800100); + assert(Date.parse("2000-01-01T00:00:00.10Z"), 946684800100); + assert(Date.parse("2000-01-01T00:00:00.100Z"), 946684800100); + assert(Date.parse("2000-01-01T00:00:00.1000Z"), 946684800100); + assert(Date.parse("2000-01-01T00:00:00+00:00"), 946684800000); + //assert(Date.parse("2000-01-01T00:00:00+00:30"), 946686600000); + var d = new Date("2000T00:00"); // Jan 1st 2000, 0:00:00 local time + assert(typeof d === 'object' && d.toString() != 'Invalid Date'); + assert((new Date('Jan 1 2000')).toISOString(), + d.toISOString()); + assert((new Date('Jan 1 2000 00:00')).toISOString(), + d.toISOString()); + assert((new Date('Jan 1 2000 00:00:00')).toISOString(), + d.toISOString()); + assert((new Date('Jan 1 2000 00:00:00 GMT+0100')).toISOString(), + '1999-12-31T23:00:00.000Z'); + assert((new Date('Jan 1 2000 00:00:00 GMT+0200')).toISOString(), + '1999-12-31T22:00:00.000Z'); + assert((new Date('Sat Jan 1 2000')).toISOString(), + d.toISOString()); + assert((new Date('Sat Jan 1 2000 00:00')).toISOString(), + d.toISOString()); + assert((new Date('Sat Jan 1 2000 00:00:00')).toISOString(), + d.toISOString()); + assert((new Date('Sat Jan 1 2000 00:00:00 GMT+0100')).toISOString(), + '1999-12-31T23:00:00.000Z'); + assert((new Date('Sat Jan 1 2000 00:00:00 GMT+0200')).toISOString(), + '1999-12-31T22:00:00.000Z'); + + var d = new Date(1506098258091); + assert(d.toISOString(), "2017-09-22T16:37:38.091Z"); + d.setUTCHours(18, 10, 11); + assert(d.toISOString(), "2017-09-22T18:10:11.091Z"); + var a = Date.parse(d.toISOString()); + assert((new Date(a)).toISOString(), d.toISOString()); + + assert((new Date("2020-01-01T01:01:01.123Z")).toISOString(), + "2020-01-01T01:01:01.123Z"); + /* implementation defined behavior */ + assert((new Date("2020-01-01T01:01:01.1Z")).toISOString(), + "2020-01-01T01:01:01.100Z"); + assert((new Date("2020-01-01T01:01:01.12Z")).toISOString(), + "2020-01-01T01:01:01.120Z"); + assert((new Date("2020-01-01T01:01:01.1234Z")).toISOString(), + "2020-01-01T01:01:01.123Z"); + assert((new Date("2020-01-01T01:01:01.12345Z")).toISOString(), + "2020-01-01T01:01:01.123Z"); + assert((new Date("2020-01-01T01:01:01.1235Z")).toISOString(), + "2020-01-01T01:01:01.123Z"); + assert((new Date("2020-01-01T01:01:01.9999Z")).toISOString(), + "2020-01-01T01:01:01.999Z"); + + assert(Date.UTC(2017), 1483228800000); + assert(Date.UTC(2017, 9), 1506816000000); + assert(Date.UTC(2017, 9, 22), 1508630400000); + assert(Date.UTC(2017, 9, 22, 18), 1508695200000); + assert(Date.UTC(2017, 9, 22, 18, 10), 1508695800000); + assert(Date.UTC(2017, 9, 22, 18, 10, 11), 1508695811000); + assert(Date.UTC(2017, 9, 22, 18, 10, 11, 91), 1508695811091); + + assert(Date.UTC(NaN), NaN); + assert(Date.UTC(2017, NaN), NaN); + assert(Date.UTC(2017, 9, NaN), NaN); + assert(Date.UTC(2017, 9, 22, NaN), NaN); + assert(Date.UTC(2017, 9, 22, 18, NaN), NaN); + assert(Date.UTC(2017, 9, 22, 18, 10, NaN), NaN); + assert(Date.UTC(2017, 9, 22, 18, 10, 11, NaN), NaN); + assert(Date.UTC(2017, 9, 22, 18, 10, 11, 91, NaN), 1508695811091); + + // TODO: Fix rounding errors on Windows/Cygwin. + if (!(typeof os !== 'undefined' && ['win32', 'cygwin'].includes(os.platform))) { + // from test262/test/built-ins/Date/UTC/fp-evaluation-order.js + assert(Date.UTC(1970, 0, 1, 80063993375, 29, 1, -288230376151711740), 29312, + 'order of operations / precision in MakeTime'); + assert(Date.UTC(1970, 0, 213503982336, 0, 0, 0, -18446744073709552000), 34447360, + 'precision in MakeDate'); + } + //assert(Date.UTC(2017 - 1e9, 9 + 12e9), 1506816000000); // node fails this + assert(Date.UTC(2017, 9, 22 - 1e10, 18 + 24e10), 1508695200000); + assert(Date.UTC(2017, 9, 22, 18 - 1e10, 10 + 60e10), 1508695800000); + assert(Date.UTC(2017, 9, 22, 18, 10 - 1e10, 11 + 60e10), 1508695811000); + assert(Date.UTC(2017, 9, 22, 18, 10, 11 - 1e12, 91 + 1000e12), 1508695811091); +} + +function test_regexp() +{ + var a, str; + str = "abbbbbc"; + a = /(b+)c/.exec(str); + assert(a[0], "bbbbbc"); + assert(a[1], "bbbbb"); + assert(a.index, 1); + assert(a.input, str); + a = /(b+)c/.test(str); + assert(a, true); + assert(/\x61/.exec("a")[0], "a"); + assert(/\u0061/.exec("a")[0], "a"); + assert(/\ca/.exec("\x01")[0], "\x01"); + assert(/\\a/.exec("\\a")[0], "\\a"); + assert(/\c0/.exec("\\c0")[0], "\\c0"); + + a = /(\.(?=com|org)|\/)/.exec("ah.com"); + assert(a.index === 2 && a[0] === "."); + + a = /(\.(?!com|org)|\/)/.exec("ah.com"); + assert(a, null); + + a = /(?=(a+))/.exec("baaabac"); + assert(a.index === 1 && a[0] === "" && a[1] === "aaa"); + + a = /(z)((a+)?(b+)?(c))*/.exec("zaacbbbcac"); + assert(a, ["zaacbbbcac","z","ac","a",,"c"]); + + a = eval("/\0a/"); + assert(a.toString(), "/\0a/"); + assert(a.exec("\0a")[0], "\0a"); + + assert(/{1a}/.toString(), "/{1a}/"); + a = /a{1+/.exec("a{11"); + assert(a, ["a{11"]); + + /* test zero length matches */ + a = /(?:(?=(abc)))a/.exec("abc"); + assert(a, ["a", "abc"]); + a = /(?:(?=(abc)))?a/.exec("abc"); + assert(a, ["a", undefined]); + a = /(?:(?=(abc))){0,2}a/.exec("abc"); + assert(a, ["a", undefined]); + a = /(?:|[\w])+([0-9])/.exec("123a23"); + assert(a, ["123a23", "3"]); + a = /()*?a/.exec(","); + assert(a, null); + + /* test \b escape */ + assert(/[\q{a\b}]/.test("a\b"), true); + assert(/[\b]/.test("\b"), true); + + /* test case insensitive matching (test262 hardly tests it) */ + assert("aAbBcC#4".replace(/\p{Lower}/gu,"X"), "XAXBXC#4"); + + assert("aAbBcC#4".replace(/\p{Lower}/gui,"X"), "XXXXXX#4"); + assert("aAbBcC#4".replace(/\p{Upper}/gui,"X"), "XXXXXX#4"); + assert("aAbBcC#4".replace(/\P{Lower}/gui,"X"), "XXXXXXXX"); + assert("aAbBcC#4".replace(/\P{Upper}/gui,"X"), "XXXXXXXX"); + assert("aAbBcC".replace(/[^b]/gui, "X"), "XXbBXX"); + assert("aAbBcC".replace(/[^A-B]/gui, "X"), "aAbBXX"); + + assert("aAbBcC#4".replace(/\p{Lower}/gvi,"X"), "XXXXXX#4"); + assert("aAbBcC#4".replace(/\P{Lower}/gvi,"X"), "aAbBcCXX"); + assert("aAbBcC#4".replace(/[^\P{Lower}]/gvi,"X"), "XXXXXX#4"); + assert("aAbBcC#4".replace(/\P{Upper}/gvi,"X"), "aAbBcCXX"); + assert("aAbBcC".replace(/[^b]/gvi, "X"), "XXbBXX"); + assert("aAbBcC".replace(/[^A-B]/gvi, "X"), "aAbBXX"); + assert("aAbBcC".replace(/[[a-c]&&B]/gvi, "X"), "aAXXcC"); + assert("aAbBcC".replace(/[[a-c]--B]/gvi, "X"), "XXbBXX"); + + assert("abcAbC".replace(/[\q{AbC}]/gvi,"X"), "XX"); + /* Note: SpiderMonkey and v8 may not be correct */ + assert("abcAbC".replace(/[\q{BC|A}]/gvi,"X"), "XXXX"); + assert("abcAbC".replace(/[\q{BC|A}--a]/gvi,"X"), "aXAX"); + + /* case where lastIndex points to the second element of a + surrogate pair */ + a = /(?:)/gu; + a.lastIndex = 1; + a.exec("🐱"); + assert(a.lastIndex, 0); + + a.lastIndex = 1; + a.exec("a\udc00"); + assert(a.lastIndex, 1); + + a = /\u{10000}/vgd; + a.lastIndex = 1; + a = a.exec("\u{10000}_\u{10000}"); + assert(a.indices[0][0], 0); + assert(a.indices[0][1], 2); +} + +function test_symbol() +{ + var a, b, obj, c; + a = Symbol("abc"); + obj = {}; + obj[a] = 2; + assert(obj[a], 2); + assert(typeof obj["abc"], "undefined"); + assert(String(a), "Symbol(abc)"); + b = Symbol("abc"); + assert(a == a); + assert(a === a); + assert(a != b); + assert(a !== b); + + b = Symbol.for("abc"); + c = Symbol.for("abc"); + assert(b === c); + assert(b !== a); + + assert(Symbol.keyFor(b), "abc"); + assert(Symbol.keyFor(a), undefined); + + a = Symbol("aaa"); + assert(a.valueOf(), a); + assert(a.toString(), "Symbol(aaa)"); + + b = Object(a); + assert(b.valueOf(), a); + assert(b.toString(), "Symbol(aaa)"); +} + +function test_map1(key_type, n) +{ + var a, i, tab, o, v; + a = new Map(); + tab = []; + for(i = 0; i < n; i++) { + v = { }; + switch(key_type) { + case "small_bigint": + o = BigInt(i); + break; + case "bigint": + o = BigInt(i) + (1n << 128n); + break; + case "object": + o = { id: i }; + break; + default: + assert(false); + } + tab[i] = [o, v]; + a.set(o, v); + } + + assert(a.size, n); + for(i = 0; i < n; i++) { + assert(a.get(tab[i][0]), tab[i][1]); + } + + i = 0; + a.forEach(function (v, o) { + assert(o, tab[i++][0]); + assert(a.has(o)); + assert(a.delete(o)); + assert(!a.has(o)); + }); + + assert(a.size, 0); +} + +function test_map() +{ + var a, i, n, tab, o, v; + n = 1000; + + a = new Map(); + for (var i = 0; i < n; i++) { + a.set(i, i); + } + a.set(-2147483648, 1); + assert(a.get(-2147483648), 1); + assert(a.get(-2147483647 - 1), 1); + assert(a.get(-2147483647.5 - 0.5), 1); + + a.set(1n, 1n); + assert(a.get(1n), 1n); + assert(a.get(2n**1000n - (2n**1000n - 1n)), 1n); + + test_map1("object", n); + test_map1("small_bigint", n); + test_map1("bigint", n); +} + +function test_weak_map() +{ + var a, i, n, tab, o, v, n2; + a = new WeakMap(); + n = 10; + tab = []; + for(i = 0; i < n; i++) { + v = { }; + if (i & 1) + o = Symbol("x" + i); + else + o = { id: i }; + tab[i] = [o, v]; + a.set(o, v); + } + o = null; + + n2 = 5; + for(i = 0; i < n2; i++) { + a.delete(tab[i][0]); + } + for(i = n2; i < n; i++) { + tab[i][0] = null; /* should remove the object from the WeakMap too */ + } + std.gc(); + /* the WeakMap should be empty here */ +} + +function test_weak_map_cycles() +{ + const weak1 = new WeakMap(); + const weak2 = new WeakMap(); + function createCyclicKey() { + const parent = {}; + const child = {parent}; + parent.child = child; + return child; + } + function testWeakMap() { + const cyclicKey = createCyclicKey(); + const valueOfCyclicKey = {}; + weak1.set(cyclicKey, valueOfCyclicKey); + weak2.set(valueOfCyclicKey, 1); + } + testWeakMap(); + // Force to free cyclicKey. + std.gc(); + // Here will cause sigsegv because [cyclicKey] and [valueOfCyclicKey] in [weak1] was free, + // but weak2's map record was not removed, and it's key refers [valueOfCyclicKey] which is free. + weak2.get({}); + std.gc(); +} + +function test_weak_ref() +{ + var w1, w2, o, i; + + for(i = 0; i < 2; i++) { + if (i == 0) + o = { }; + else + o = Symbol("x"); + w1 = new WeakRef(o); + assert(w1.deref(), o); + w2 = new WeakRef(o); + assert(w2.deref(), o); + + o = null; + assert(w1.deref(), undefined); + assert(w2.deref(), undefined); + std.gc(); + assert(w1.deref(), undefined); + assert(w2.deref(), undefined); + } +} + +function test_finalization_registry() +{ + { + let expected = {}; + let actual; + let finrec = new FinalizationRegistry(v => { actual = v }); + finrec.register({}, expected); + os.setTimeout(() => { + assert(actual, expected); + }, 0); + } + { + let expected = 42; + let actual; + let finrec = new FinalizationRegistry(v => { actual = v }); + finrec.register({}, expected); + os.setTimeout(() => { + assert(actual, expected); + }, 0); + } + std.gc(); +} + +function test_generator() +{ + function *f() { + var ret; + yield 1; + ret = yield 2; + assert(ret, "next_arg"); + return 3; + } + function *f2() { + yield 1; + yield 2; + return "ret_val"; + } + function *f1() { + var ret = yield *f2(); + assert(ret, "ret_val"); + return 3; + } + function *f3() { + var ret; + /* test stack consistency with nip_n to handle yield return + + * finally clause */ + try { + ret = 2 + (yield 1); + } catch(e) { + } finally { + ret++; + } + return ret; + } + var g, v; + g = f(); + v = g.next(); + assert(v.value === 1 && v.done === false); + v = g.next(); + assert(v.value === 2 && v.done === false); + v = g.next("next_arg"); + assert(v.value === 3 && v.done === true); + v = g.next(); + assert(v.value === undefined && v.done === true); + + g = f1(); + v = g.next(); + assert(v.value === 1 && v.done === false); + v = g.next(); + assert(v.value === 2 && v.done === false); + v = g.next(); + assert(v.value === 3 && v.done === true); + v = g.next(); + assert(v.value === undefined && v.done === true); + + g = f3(); + v = g.next(); + assert(v.value === 1 && v.done === false); + v = g.next(3); + assert(v.value === 6 && v.done === true); +} + +function rope_concat(n, dir) +{ + var i, s; + s = ""; + if (dir > 0) { + for(i = 0; i < n; i++) + s += String.fromCharCode(i & 0xffff); + } else { + for(i = n - 1; i >= 0; i--) + s = String.fromCharCode(i & 0xffff) + s; + } + + for(i = 0; i < n; i++) { + /* test before the assert to go faster */ + if (s.charCodeAt(i) != (i & 0xffff)) { + assert(s.charCodeAt(i), i & 0xffff); + } + } +} + +function test_rope() +{ + rope_concat(100000, 1); + rope_concat(100000, -1); +} + +function eval_error(eval_str, expected_error, level) +{ + var err = false; + var expected_pos, tab; + + tab = get_string_pos(eval_str); + + try { + eval(tab[0]); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + throw_error("unexpected exception type"); + return; + } + check_error_pos(e, expected_error, tab[1], tab[2], level); + } + if (!err) { + throw_error("expected exception"); + } +} + +var poisoned_number = { + valueOf: function() { throw Error("poisoned number") }, +}; + +function test_line_column_numbers() +{ + var f, e, tab; + + /* The '@' character provides the expected position of the + error. It is removed before evaluating the string. */ + + /* parsing */ + eval_error("\n 123 @a ", SyntaxError); + eval_error("\n @/* ", SyntaxError); + eval_error("function f @a", SyntaxError); + /* currently regexp syntax errors point to the start of the regexp */ + eval_error("\n @/aaa]/u", SyntaxError); + + /* function definitions */ + + tab = get_string_pos("\n @function f() { }; f;"); + e = eval(tab[0]); + assert(e.lineNumber, tab[1]); + assert(e.columnNumber, tab[2]); + + /* errors */ + tab = get_string_pos('\n Error@("hello");'); + e = eval(tab[0]); + check_error_pos(e, Error, tab[1], tab[2]); + + eval_error('\n throw Error@("hello");', Error); + + /* operators */ + eval_error('\n 1 + 2 @* poisoned_number;', Error, 1); + eval_error('\n 1 + "café" @* poisoned_number;', Error, 1); + eval_error('\n 1 + 2 @** poisoned_number;', Error, 1); + eval_error('\n 2 * @+ poisoned_number;', Error, 1); + eval_error('\n 2 * @- poisoned_number;', Error, 1); + eval_error('\n 2 * @~ poisoned_number;', Error, 1); + eval_error('\n 2 * @++ poisoned_number;', Error, 1); + eval_error('\n 2 * @-- poisoned_number;', Error, 1); + eval_error('\n 2 * poisoned_number @++;', Error, 1); + eval_error('\n 2 * poisoned_number @--;', Error, 1); + + /* accessors */ + eval_error('\n 1 + null@[0];', TypeError); + eval_error('\n 1 + null @. abcd;', TypeError); + eval_error('\n 1 + null @( 1234 );', TypeError); + eval_error('var obj = { get a() { throw Error("test"); } }\n 1 + obj @. a;', + Error, 1); + eval_error('var obj = { set a(b) { throw Error("test"); } }\n obj @. a = 1;', + Error, 1); + + /* variables reference */ + eval_error('\n 1 + @not_def', ReferenceError, 0); + + /* assignments */ + eval_error('1 + (@not_def = 1)', ReferenceError, 0); + eval_error('1 + (@not_def += 2)', ReferenceError, 0); + eval_error('var a;\n 1 + (a @+= poisoned_number);', Error, 1); +} + +test(); +test_function(); +test_enum(); +test_array(); +test_string(); +test_math(); +test_number(); +test_eval(); +test_typed_array(); +test_json(); +test_date(); +test_regexp(); +test_symbol(); +test_map(); +test_weak_map(); +test_weak_map_cycles(); +test_weak_ref(); +test_finalization_registry(); +test_generator(); +test_rope(); +test_line_column_numbers(); diff --git a/NativeScript/napi/android/quickjs/source/tests/test_closure.js b/NativeScript/napi/android/quickjs/source/tests/test_closure.js new file mode 100755 index 000000000..f5d4160e0 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_closure.js @@ -0,0 +1,221 @@ +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +var log_str = ""; + +function log(str) +{ + log_str += str + ","; +} + +function f(a, b, c) +{ + var x = 10; + log("a="+a); + function g(d) { + function h() { + log("d=" + d); + log("x=" + x); + } + log("b=" + b); + log("c=" + c); + h(); + } + g(4); + return g; +} + +var g1 = f(1, 2, 3); +g1(5); + +assert(log_str, "a=1,b=2,c=3,d=4,x=10,b=2,c=3,d=5,x=10,", "closure1"); + +function test_closure1() +{ + function f2() + { + var val = 1; + + function set(a) { + val = a; + } + function get(a) { + return val; + } + return { "set": set, "get": get }; + } + + var obj = f2(); + obj.set(10); + var r; + r = obj.get(); + assert(r, 10, "closure2"); +} + +function test_closure2() +{ + var expr_func = function myfunc1(n) { + function myfunc2(n) { + return myfunc1(n - 1); + } + if (n == 0) + return 0; + else + return myfunc2(n); + }; + var r; + r = expr_func(1); + assert(r, 0, "expr_func"); +} + +function test_closure3() +{ + function fib(n) + { + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib(n - 1) + fib(n - 2); + } + + var fib_func = function fib1(n) + { + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib1(n - 1) + fib1(n - 2); + }; + + assert(fib(6), 8, "fib"); + assert(fib_func(6), 8, "fib_func"); +} + +function test_arrow_function() +{ + "use strict"; + + function f1() { + return (() => arguments)(); + } + function f2() { + return (() => this)(); + } + function f3() { + return (() => eval("this"))(); + } + function f4() { + return (() => eval("new.target"))(); + } + var a; + + a = f1(1, 2); + assert(a.length, 2); + assert(a[0] === 1 && a[1] === 2); + + assert(f2.call("this_val") === "this_val"); + assert(f3.call("this_val") === "this_val"); + assert(new f4() === f4); + + var o1 = { f() { return this; } }; + var o2 = { f() { + return (() => eval("super.f()"))(); + } }; + o2.__proto__ = o1; + + assert(o2.f() === o2); +} + +function test_with() +{ + var o1 = { x: "o1", y: "o1" }; + var x = "local"; + eval('var z="var_obj";'); + assert(z === "var_obj"); + with (o1) { + assert(x === "o1"); + assert(eval("x") === "o1"); + var f = function () { + o2 = { x: "o2" }; + with (o2) { + assert(x === "o2"); + assert(y === "o1"); + assert(z === "var_obj"); + assert(eval("x") === "o2"); + assert(eval("y") === "o1"); + assert(eval("z") === "var_obj"); + assert(eval('eval("x")') === "o2"); + } + }; + f(); + } +} + +function test_eval_closure() +{ + var tab; + + tab = []; + for(let i = 0; i < 3; i++) { + eval("tab.push(function g1() { return i; })"); + } + for(let i = 0; i < 3; i++) { + assert(tab[i]() === i); + } + + tab = []; + for(let i = 0; i < 3; i++) { + let f = function f() { + eval("tab.push(function g2() { return i; })"); + }; + f(); + } + for(let i = 0; i < 3; i++) { + assert(tab[i]() === i); + } +} + +function test_eval_const() +{ + const a = 1; + var success = false; + var f = function () { + eval("a = 1"); + }; + try { + f(); + } catch(e) { + success = (e instanceof TypeError); + } + assert(success); +} + +test_closure1(); +test_closure2(); +test_closure3(); +test_arrow_function(); +test_with(); +test_eval_closure(); +test_eval_const(); diff --git a/NativeScript/napi/android/quickjs/source/tests/test_cyclic_import.js b/NativeScript/napi/android/quickjs/source/tests/test_cyclic_import.js new file mode 100755 index 000000000..bf51d9b1d --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_cyclic_import.js @@ -0,0 +1,12 @@ +/*--- +negative: + phase: resolution + type: SyntaxError +---*/ +// FIXME(bnoordhuis) shouldn't throw SyntaxError but that's still better +// than segfaulting, see https://github.com/quickjs-ng/quickjs/issues/567 +import {assert} from "./assert.js" +import {f} from "./fixture_cyclic_import.js" +export {f} +export function g(x) { return x + 1 } +assert(f(1), 4) diff --git a/NativeScript/napi/android/quickjs/source/tests/test_language.js b/NativeScript/napi/android/quickjs/source/tests/test_language.js new file mode 100755 index 000000000..5c51f0df5 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_language.js @@ -0,0 +1,692 @@ +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (Object.is(actual, expected)) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assert_throws(expected_error, func) +{ + var err = false; + try { + func(); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + throw Error("unexpected exception type"); + } + } + if (!err) { + throw Error("expected exception"); + } +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function test_op1() +{ + var r, a; + r = 1 + 2; + assert(r, 3, "1 + 2 === 3"); + + r = 1 - 2; + assert(r, -1, "1 - 2 === -1"); + + r = -1; + assert(r, -1, "-1 === -1"); + + r = +2; + assert(r, 2, "+2 === 2"); + + r = 2 * 3; + assert(r, 6, "2 * 3 === 6"); + + r = 4 / 2; + assert(r, 2, "4 / 2 === 2"); + + r = 4 % 3; + assert(r, 1, "4 % 3 === 3"); + + r = 4 << 2; + assert(r, 16, "4 << 2 === 16"); + + r = 1 << 0; + assert(r, 1, "1 << 0 === 1"); + + r = 1 << 31; + assert(r, -2147483648, "1 << 31 === -2147483648"); + + r = 1 << 32; + assert(r, 1, "1 << 32 === 1"); + + r = (1 << 31) < 0; + assert(r, true, "(1 << 31) < 0 === true"); + + r = -4 >> 1; + assert(r, -2, "-4 >> 1 === -2"); + + r = -4 >>> 1; + assert(r, 0x7ffffffe, "-4 >>> 1 === 0x7ffffffe"); + + r = 1 & 1; + assert(r, 1, "1 & 1 === 1"); + + r = 0 | 1; + assert(r, 1, "0 | 1 === 1"); + + r = 1 ^ 1; + assert(r, 0, "1 ^ 1 === 0"); + + r = ~1; + assert(r, -2, "~1 === -2"); + + r = !1; + assert(r, false, "!1 === false"); + + assert((1 < 2), true, "(1 < 2) === true"); + + assert((2 > 1), true, "(2 > 1) === true"); + + assert(('b' > 'a'), true, "('b' > 'a') === true"); + + assert(2 ** 8, 256, "2 ** 8 === 256"); +} + +function test_cvt() +{ + assert((NaN | 0) === 0); + assert((Infinity | 0) === 0); + assert(((-Infinity) | 0) === 0); + assert(("12345" | 0) === 12345); + assert(("0x12345" | 0) === 0x12345); + assert(((4294967296 * 3 - 4) | 0) === -4); + + assert(("12345" >>> 0) === 12345); + assert(("0x12345" >>> 0) === 0x12345); + assert((NaN >>> 0) === 0); + assert((Infinity >>> 0) === 0); + assert(((-Infinity) >>> 0) === 0); + assert(((4294967296 * 3 - 4) >>> 0) === (4294967296 - 4)); + assert((19686109595169230000).toString() === "19686109595169230000"); +} + +function test_eq() +{ + assert(null == undefined); + assert(undefined == null); + assert(true == 1); + assert(0 == false); + assert("" == 0); + assert("123" == 123); + assert("122" != 123); + assert((new Number(1)) == 1); + assert(2 == (new Number(2))); + assert((new String("abc")) == "abc"); + assert({} != "abc"); +} + +function test_inc_dec() +{ + var a, r; + + a = 1; + r = a++; + assert(r === 1 && a === 2, true, "++"); + + a = 1; + r = ++a; + assert(r === 2 && a === 2, true, "++"); + + a = 1; + r = a--; + assert(r === 1 && a === 0, true, "--"); + + a = 1; + r = --a; + assert(r === 0 && a === 0, true, "--"); + + a = {x:true}; + a.x++; + assert(a.x, 2, "++"); + + a = {x:true}; + a.x--; + assert(a.x, 0, "--"); + + a = [true]; + a[0]++; + assert(a[0], 2, "++"); + + a = {x:true}; + r = a.x++; + assert(r === 1 && a.x === 2, true, "++"); + + a = {x:true}; + r = a.x--; + assert(r === 1 && a.x === 0, true, "--"); + + a = [true]; + r = a[0]++; + assert(r === 1 && a[0] === 2, true, "++"); + + a = [true]; + r = a[0]--; + assert(r === 1 && a[0] === 0, true, "--"); +} + +function F(x) +{ + this.x = x; +} + +function test_op2() +{ + var a, b; + a = new Object; + a.x = 1; + assert(a.x, 1, "new"); + b = new F(2); + assert(b.x, 2, "new"); + + a = {x : 2}; + assert(("x" in a), true, "in"); + assert(("y" in a), false, "in"); + + a = {}; + assert((a instanceof Object), true, "instanceof"); + assert((a instanceof String), false, "instanceof"); + + assert((typeof 1), "number", "typeof"); + assert((typeof Object), "function", "typeof"); + assert((typeof null), "object", "typeof"); + assert((typeof unknown_var), "undefined", "typeof"); + + a = {x: 1, if: 2, async: 3}; + assert(a.if === 2); + assert(a.async === 3); +} + +function test_delete() +{ + var a, err; + + a = {x: 1, y: 1}; + assert((delete a.x), true, "delete"); + assert(("x" in a), false, "delete"); + + /* the following are not tested by test262 */ + assert(delete "abc"[100], true); + + err = false; + try { + delete null.a; + } catch(e) { + err = (e instanceof TypeError); + } + assert(err, true, "delete"); + + err = false; + try { + a = { f() { delete super.a; } }; + a.f(); + } catch(e) { + err = (e instanceof ReferenceError); + } + assert(err, true, "delete"); +} + +function test_constructor() +{ + function *G() {} + let ex + try { new G() } catch (ex_) { ex = ex_ } + assert(ex instanceof TypeError) + assert(ex.message, "G is not a constructor") +} + +function test_prototype() +{ + var f = function f() { }; + assert(f.prototype.constructor, f, "prototype"); + + var g = function g() { }; + /* QuickJS bug */ + Object.defineProperty(g, "prototype", { writable: false }); + assert(g.prototype.constructor, g, "prototype"); +} + +function test_arguments() +{ + function f2() { + assert(arguments.length, 2, "arguments"); + assert(arguments[0], 1, "arguments"); + assert(arguments[1], 3, "arguments"); + } + f2(1, 3); +} + +function test_class() +{ + var o; + class C { + constructor() { + this.x = 10; + } + f() { + return 1; + } + static F() { + return -1; + } + get y() { + return 12; + } + }; + class D extends C { + constructor() { + super(); + this.z = 20; + } + g() { + return 2; + } + static G() { + return -2; + } + h() { + return super.f(); + } + static H() { + return super["F"](); + } + } + + assert(C.F() === -1); + assert(Object.getOwnPropertyDescriptor(C.prototype, "y").get.name === "get y"); + + o = new C(); + assert(o.f() === 1); + assert(o.x === 10); + + assert(D.F() === -1); + assert(D.G() === -2); + assert(D.H() === -1); + + o = new D(); + assert(o.f() === 1); + assert(o.g() === 2); + assert(o.x === 10); + assert(o.z === 20); + assert(o.h() === 1); + + /* test class name scope */ + var E1 = class E { static F() { return E; } }; + assert(E1 === E1.F()); + + class S { + static x = 42; + static y = S.x; + static z = this.x; + } + assert(S.x === 42); + assert(S.y === 42); + assert(S.z === 42); + + class P { + get = () => "123"; + static() { return 42; } + } + assert(new P().get() === "123"); + assert(new P().static() === 42); +}; + +function test_template() +{ + var a, b; + b = 123; + a = `abc${b}d`; + assert(a, "abc123d"); + + a = String.raw `abc${b}d`; + assert(a, "abc123d"); + + a = "aaa"; + b = "bbb"; + assert(`aaa${a, b}ccc`, "aaabbbccc"); +} + +function test_template_skip() +{ + var a = "Bar"; + var { b = `${a + `a${a}` }baz` } = {}; + assert(b, "BaraBarbaz"); +} + +function test_object_literal() +{ + var x = 0, get = 1, set = 2, async = 3; + a = { get: 2, set: 3, async: 4, get a(){ return this.get} }; + assert(JSON.stringify(a), '{"get":2,"set":3,"async":4,"a":2}'); + assert(a.a === 2); + + a = { x, get, set, async }; + assert(JSON.stringify(a), '{"x":0,"get":1,"set":2,"async":3}'); +} + +function test_regexp_skip() +{ + var a, b; + [a, b = /abc\(/] = [1]; + assert(a === 1); + + [a, b =/abc\(/] = [2]; + assert(a === 2); +} + +function test_labels() +{ + do x: { break x; } while(0); + if (1) + x: { break x; } + else + x: { break x; } + with ({}) x: { break x; }; + while (0) x: { break x; }; +} + +function test_labels2() +{ + while (1) label: break + var i = 0 + while (i < 3) label: { + if (i > 0) + break + i++ + } + assert(i, 1) + for (;;) label: break + for (i = 0; i < 3; i++) label: { + if (i > 0) + break + } + assert(i, 1) +} + +function test_destructuring() +{ + function * g () { return 0; }; + var [x] = g(); + assert(x, void 0); +} + +function test_spread() +{ + var x; + x = [1, 2, ...[3, 4]]; + assert(x.toString(), "1,2,3,4"); + + x = [ ...[ , ] ]; + assert(Object.getOwnPropertyNames(x).toString(), "0,length"); +} + +function test_function_length() +{ + assert( ((a, b = 1, c) => {}).length, 1); + assert( (([a,b]) => {}).length, 1); + assert( (({a,b}) => {}).length, 1); + assert( ((c, [a,b] = 1, d) => {}).length, 1); +} + +function test_argument_scope() +{ + var f; + var c = "global"; + + (function() { + "use strict"; + // XXX: node only throws in strict mode + f = function(a = eval("var arguments")) {}; + assert_throws(SyntaxError, f); + })(); + + f = function(a = eval("1"), b = arguments[0]) { return b; }; + assert(f(12), 12); + + f = function(a, b = arguments[0]) { return b; }; + assert(f(12), 12); + + f = function(a, b = () => arguments) { return b; }; + assert(f(12)()[0], 12); + + f = function(a = eval("1"), b = () => arguments) { return b; }; + assert(f(12)()[0], 12); + + (function() { + "use strict"; + f = function(a = this) { return a; }; + assert(f.call(123), 123); + + f = function f(a = f) { return a; }; + assert(f(), f); + + f = function f(a = eval("f")) { return a; }; + assert(f(), f); + })(); + + f = (a = eval("var c = 1"), probe = () => c) => { + var c = 2; + assert(c, 2); + assert(probe(), 1); + } + f(); + + f = (a = eval("var arguments = 1"), probe = () => arguments) => { + var arguments = 2; + assert(arguments, 2); + assert(probe(), 1); + } + f(); + + f = function f(a = eval("var c = 1"), b = c, probe = () => c) { + assert(b, 1); + assert(c, 1); + assert(probe(), 1) + } + f(); + + assert(c, "global"); + f = function f(a, b = c, probe = () => c) { + eval("var c = 1"); + assert(c, 1); + assert(b, "global"); + assert(probe(), "global") + } + f(); + assert(c, "global"); + + f = function f(a = eval("var c = 1"), probe = (d = eval("c")) => d) { + assert(probe(), 1) + } + f(); +} + +function test_function_expr_name() +{ + var f; + + /* non strict mode test : assignment to the function name silently + fails */ + + f = function myfunc() { + myfunc = 1; + return myfunc; + }; + assert(f(), f); + + f = function myfunc() { + myfunc = 1; + (() => { + myfunc = 1; + })(); + return myfunc; + }; + assert(f(), f); + + f = function myfunc() { + eval("myfunc = 1"); + return myfunc; + }; + assert(f(), f); + + /* strict mode test : assignment to the function name raises a + TypeError exception */ + + f = function myfunc() { + "use strict"; + myfunc = 1; + }; + assert_throws(TypeError, f); + + f = function myfunc() { + "use strict"; + (() => { + myfunc = 1; + })(); + }; + assert_throws(TypeError, f); + + f = function myfunc() { + "use strict"; + eval("myfunc = 1"); + }; + assert_throws(TypeError, f); +} + +function test_parse_semicolon() +{ + /* 'yield' or 'await' may not be considered as a token if the + previous ';' is missing */ + function *f() + { + function func() { + } + yield 1; + var h = x => x + 1 + yield 2; + } + async function g() + { + function func() { + } + await 1; + var h = x => x + 1 + await 2; + } +} + +function test_parse_arrow_function() +{ + assert(typeof eval("() => {}\n() => {}"), "function"); + assert(eval("() => {}\n+1"), 1); + assert(typeof eval("x => {}\n() => {}"), "function"); + assert(typeof eval("async () => {}\n() => {}"), "function"); + assert(typeof eval("async x => {}\n() => {}"), "function"); +} + +/* optional chaining tests not present in test262 */ +function test_optional_chaining() +{ + var a, z; + z = null; + a = { b: { c: 2 } }; + assert(delete z?.b.c, true); + assert(delete a?.b.c, true); + assert(JSON.stringify(a), '{"b":{}}', "optional chaining delete"); + + a = { b: { c: 2 } }; + assert(delete z?.b["c"], true); + assert(delete a?.b["c"], true); + assert(JSON.stringify(a), '{"b":{}}'); + + a = { + b() { return this._b; }, + _b: { c: 42 } + }; + + assert((a?.b)().c, 42); + + assert((a?.["b"])().c, 42); +} + +function test_unicode_ident() +{ + var õ = 3; + assert(typeof õ, "undefined"); +} + +/* check global variable optimization */ +function test_global_var_opt() +{ + var v2; + (1, eval)('var gvar1'); /* create configurable global variables */ + + gvar1 = 1; + Object.defineProperty(globalThis, "gvar1", { writable: false }); + gvar1 = 2; + assert(gvar1, 1); + + Object.defineProperty(globalThis, "gvar1", { get: function() { return "hello" }, + set: function(v) { v2 = v; } }); + assert(gvar1, "hello"); + gvar1 = 3; + assert(v2, 3); + + Object.defineProperty(globalThis, "gvar1", { value: 4, writable: true, configurable: true }); + assert(gvar1, 4); + gvar1 = 6; + + delete gvar1; + assert_throws(ReferenceError, function() { return gvar1 }); + gvar1 = 5; + assert(gvar1, 5); +} + +test_op1(); +test_cvt(); +test_eq(); +test_inc_dec(); +test_op2(); +test_constructor(); +test_delete(); +test_prototype(); +test_arguments(); +test_class(); +test_template(); +test_template_skip(); +test_object_literal(); +test_regexp_skip(); +test_labels(); +test_labels2(); +test_destructuring(); +test_spread(); +test_function_length(); +test_argument_scope(); +test_function_expr_name(); +test_parse_semicolon(); +test_optional_chaining(); +test_parse_arrow_function(); +test_unicode_ident(); +test_global_var_opt(); diff --git a/NativeScript/napi/android/quickjs/source/tests/test_loop.js b/NativeScript/napi/android/quickjs/source/tests/test_loop.js new file mode 100755 index 000000000..8f1934d38 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_loop.js @@ -0,0 +1,402 @@ +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function test_while() +{ + var i, c; + i = 0; + c = 0; + while (i < 3) { + c++; + i++; + } + assert(c === 3); +} + +function test_while_break() +{ + var i, c; + i = 0; + c = 0; + while (i < 3) { + c++; + if (i == 1) + break; + i++; + } + assert(c === 2 && i === 1); +} + +function test_do_while() +{ + var i, c; + i = 0; + c = 0; + do { + c++; + i++; + } while (i < 3); + assert(c === 3 && i === 3); +} + +function test_for() +{ + var i, c; + c = 0; + for(i = 0; i < 3; i++) { + c++; + } + assert(c === 3 && i === 3); + + c = 0; + for(var j = 0; j < 3; j++) { + c++; + } + assert(c === 3 && j === 3); +} + +function test_for_in() +{ + var i, tab, a, b; + + tab = []; + for(i in {x:1, y: 2}) { + tab.push(i); + } + assert(tab.toString(), "x,y", "for_in"); + + /* prototype chain test */ + a = {x:2, y: 2, "1": 3}; + b = {"4" : 3 }; + Object.setPrototypeOf(a, b); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "1,x,y,4", "for_in"); + + /* non enumerable properties hide enumerables ones in the + prototype chain */ + a = {y: 2, "1": 3}; + Object.defineProperty(a, "x", { value: 1 }); + b = {"x" : 3 }; + Object.setPrototypeOf(a, b); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "1,y", "for_in"); + + /* array optimization */ + a = []; + for(i = 0; i < 10; i++) + a.push(i); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "0,1,2,3,4,5,6,7,8,9", "for_in"); + + /* iterate with a field */ + a={x:0}; + tab = []; + for(a.x in {x:1, y: 2}) { + tab.push(a.x); + } + assert(tab.toString(), "x,y", "for_in"); + + /* iterate with a variable field */ + a=[0]; + tab = []; + for(a[0] in {x:1, y: 2}) { + tab.push(a[0]); + } + assert(tab.toString(), "x,y", "for_in"); + + /* variable definition in the for in */ + tab = []; + for(var j in {x:1, y: 2}) { + tab.push(j); + } + assert(tab.toString(), "x,y", "for_in"); + + /* variable assigment in the for in */ + tab = []; + for(var k = 2 in {x:1, y: 2}) { + tab.push(k); + } + assert(tab.toString(), "x,y", "for_in"); +} + +function test_for_in2() +{ + var i, tab; + tab = []; + for(i in {x:1, y: 2, z:3}) { + if (i === "y") + continue; + tab.push(i); + } + assert(tab.toString() == "x,z"); + + tab = []; + for(i in {x:1, y: 2, z:3}) { + if (i === "z") + break; + tab.push(i); + } + assert(tab.toString() == "x,y"); +} + +function test_for_in_proxy() { + let removed_key = ""; + let target = {} + let proxy = new Proxy(target, { + ownKeys: function() { + return ["a", "b", "c"]; + }, + getOwnPropertyDescriptor: function(target, key) { + if (removed_key != "" && key == removed_key) + return undefined; + else + return { enumerable: true, configurable: true, value: this[key] }; + } + }); + let str = ""; + for(let o in proxy) { + str += " " + o; + if (o == "a") + removed_key = "b"; + } + assert(str == " a c"); +} + +function test_for_break() +{ + var i, c; + c = 0; + L1: for(i = 0; i < 3; i++) { + c++; + if (i == 0) + continue; + while (1) { + break L1; + } + } + assert(c === 2 && i === 1); +} + +function test_switch1() +{ + var i, a, s; + s = ""; + for(i = 0; i < 3; i++) { + a = "?"; + switch(i) { + case 0: + a = "a"; + break; + case 1: + a = "b"; + break; + default: + a = "c"; + break; + } + s += a; + } + assert(s === "abc" && i === 3); +} + +function test_switch2() +{ + var i, a, s; + s = ""; + for(i = 0; i < 4; i++) { + a = "?"; + switch(i) { + case 0: + a = "a"; + break; + case 1: + a = "b"; + break; + case 2: + continue; + default: + a = "" + i; + break; + } + s += a; + } + assert(s === "ab3" && i === 4); +} + +function test_try_catch1() +{ + try { + throw "hello"; + } catch (e) { + assert(e, "hello", "catch"); + return; + } + assert(false, "catch"); +} + +function test_try_catch2() +{ + var a; + try { + a = 1; + } catch (e) { + a = 2; + } + assert(a, 1, "catch"); +} + +function test_try_catch3() +{ + var s; + s = ""; + try { + s += "t"; + } catch (e) { + s += "c"; + } finally { + s += "f"; + } + assert(s, "tf", "catch"); +} + +function test_try_catch4() +{ + var s; + s = ""; + try { + s += "t"; + throw "c"; + } catch (e) { + s += e; + } finally { + s += "f"; + } + assert(s, "tcf", "catch"); +} + +function test_try_catch5() +{ + var s; + s = ""; + for(;;) { + try { + s += "t"; + break; + s += "b"; + } finally { + s += "f"; + } + } + assert(s, "tf", "catch"); +} + +function test_try_catch6() +{ + function f() { + try { + s += 't'; + return 1; + } finally { + s += "f"; + } + } + var s = ""; + assert(f() === 1); + assert(s, "tf", "catch6"); +} + +function test_try_catch7() +{ + var s; + s = ""; + + try { + try { + s += "t"; + throw "a"; + } finally { + s += "f"; + } + } catch(e) { + s += e; + } finally { + s += "g"; + } + assert(s, "tfag", "catch"); +} + +function test_try_catch8() +{ + var i, s; + + s = ""; + for(var i in {x:1, y:2}) { + try { + s += i; + throw "a"; + } catch (e) { + s += e; + } finally { + s += "f"; + } + } + assert(s === "xafyaf"); +} + +function test_cyclic_labels() +{ + /* just check that it compiles without a crash */ + for (;;) { + l: break l; + l: break l; + l: break l; + } +} + +test_while(); +test_while_break(); +test_do_while(); +test_for(); +test_for_break(); +test_switch1(); +test_switch2(); +test_for_in(); +test_for_in2(); +test_for_in_proxy(); + +test_try_catch1(); +test_try_catch2(); +test_try_catch3(); +test_try_catch4(); +test_try_catch5(); +test_try_catch6(); +test_try_catch7(); +test_try_catch8(); diff --git a/NativeScript/napi/android/quickjs/source/tests/test_std.js b/NativeScript/napi/android/quickjs/source/tests/test_std.js new file mode 100755 index 000000000..3debe4039 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_std.js @@ -0,0 +1,336 @@ +#! (shebang test) +import * as std from "std"; +import * as os from "os"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (Object.is(actual, expected)) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +// load more elaborate version of assert if available +try { std.loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function test_printf() +{ + assert(std.sprintf("a=%d s=%s", 123, "abc"), "a=123 s=abc"); + assert(std.sprintf("%010d", 123), "0000000123"); + assert(std.sprintf("%x", -2), "fffffffe"); + assert(std.sprintf("%lx", -2), "fffffffffffffffe"); + assert(std.sprintf("%10.1f", 2.1), " 2.1"); + assert(std.sprintf("%*.*f", 10, 2, -2.13), " -2.13"); + assert(std.sprintf("%#lx", 0x7fffffffffffffffn), "0x7fffffffffffffff"); +} + +function test_file1() +{ + var f, len, str, size, buf, ret, i, str1; + + f = std.tmpfile(); + str = "hello world\n"; + f.puts(str); + + f.seek(0, std.SEEK_SET); + str1 = f.readAsString(); + assert(str1 === str); + + f.seek(0, std.SEEK_END); + size = f.tell(); + assert(size === str.length); + + f.seek(0, std.SEEK_SET); + + buf = new Uint8Array(size); + ret = f.read(buf.buffer, 0, size); + assert(ret === size); + for(i = 0; i < size; i++) + assert(buf[i] === str.charCodeAt(i)); + + f.close(); +} + +function test_file2() +{ + var f, str, i, size; + f = std.tmpfile(); + str = "hello world\n"; + size = str.length; + for(i = 0; i < size; i++) + f.putByte(str.charCodeAt(i)); + f.seek(0, std.SEEK_SET); + for(i = 0; i < size; i++) { + assert(str.charCodeAt(i) === f.getByte()); + } + assert(f.getByte() === -1); + f.close(); +} + +function test_getline() +{ + var f, line, line_count, lines, i; + + lines = ["hello world", "line 1", "line 2" ]; + f = std.tmpfile(); + for(i = 0; i < lines.length; i++) { + f.puts(lines[i], "\n"); + } + + f.seek(0, std.SEEK_SET); + assert(!f.eof()); + line_count = 0; + for(;;) { + line = f.getline(); + if (line === null) + break; + assert(line == lines[line_count]); + line_count++; + } + assert(f.eof()); + assert(line_count === lines.length); + + f.close(); +} + +function test_popen() +{ + var str, f, fname = "tmp_file.txt"; + var content = "hello world"; + + f = std.open(fname, "w"); + f.puts(content); + f.close(); + + /* test loadFile */ + assert(std.loadFile(fname), content); + + /* execute the 'cat' shell command */ + f = std.popen("cat " + fname, "r"); + str = f.readAsString(); + f.close(); + + assert(str, content); + + os.remove(fname); +} + +function test_ext_json() +{ + var expected, input, obj; + expected = '{"x":false,"y":true,"z2":null,"a":[1,8,160],"b":"abc\\u000bd","s":"str"}'; + input = `{ "x":false, /*comments are allowed */ + "y":true, // also a comment + z2:null, // unquoted property names + "a":[+1,0o10,0xa0,], // plus prefix, octal, hexadecimal + "b": "ab\ +c\\vd", // multi-line strings, '\v' escape + "s":'str',} // trailing comma in objects and arrays, single quoted string + `; + obj = std.parseExtJSON(input); + assert(JSON.stringify(obj), expected); + + obj = std.parseExtJSON('[Infinity, +Infinity, -Infinity, NaN, +NaN, -NaN, .1, -.2]'); + assert(obj[0], Infinity); + assert(obj[1], Infinity); + assert(obj[2], -Infinity); + assert(obj[3], NaN); + assert(obj[4], NaN); + assert(obj[5], NaN); + assert(obj[6], 0.1); + assert(obj[7], -0.2); +} + +function test_os() +{ + var fd, fpath, fname, fdir, buf, buf2, i, files, err, fdate, st, link_path; + + const stdinIsTTY = !os.exec(["/bin/sh", "-c", "test -t 0"], { usePath: false }); + + assert(os.isatty(0), stdinIsTTY, `isatty(STDIN)`); + + fdir = "test_tmp_dir"; + fname = "tmp_file.txt"; + fpath = fdir + "/" + fname; + link_path = fdir + "/test_link"; + + os.remove(link_path); + os.remove(fpath); + os.remove(fdir); + + err = os.mkdir(fdir, 0o755); + assert(err === 0); + + fd = os.open(fpath, os.O_RDWR | os.O_CREAT | os.O_TRUNC); + assert(fd >= 0); + + buf = new Uint8Array(10); + for(i = 0; i < buf.length; i++) + buf[i] = i; + assert(os.write(fd, buf.buffer, 0, buf.length) === buf.length); + + assert(os.seek(fd, 0, std.SEEK_SET) === 0); + buf2 = new Uint8Array(buf.length); + assert(os.read(fd, buf2.buffer, 0, buf2.length) === buf2.length); + + for(i = 0; i < buf.length; i++) + assert(buf[i] == buf2[i]); + + if (typeof BigInt !== "undefined") { + assert(os.seek(fd, BigInt(6), std.SEEK_SET), BigInt(6)); + assert(os.read(fd, buf2.buffer, 0, 1) === 1); + assert(buf[6] == buf2[0]); + } + + assert(os.close(fd) === 0); + + [files, err] = os.readdir(fdir); + assert(err, 0); + assert(files.indexOf(fname) >= 0); + + fdate = 10000; + + err = os.utimes(fpath, fdate, fdate); + assert(err, 0); + + [st, err] = os.stat(fpath); + assert(err, 0); + assert(st.mode & os.S_IFMT, os.S_IFREG); + assert(st.mtime, fdate); + + err = os.symlink(fname, link_path); + assert(err === 0); + + [st, err] = os.lstat(link_path); + assert(err, 0); + assert(st.mode & os.S_IFMT, os.S_IFLNK); + + [buf, err] = os.readlink(link_path); + assert(err, 0); + assert(buf, fname); + + assert(os.remove(link_path) === 0); + + [buf, err] = os.getcwd(); + assert(err, 0); + + [buf2, err] = os.realpath("."); + assert(err, 0); + + assert(buf, buf2); + + assert(os.remove(fpath) === 0); + + fd = os.open(fpath, os.O_RDONLY); + assert(fd < 0); + + assert(os.remove(fdir) === 0); +} + +function test_os_exec() +{ + var ret, fds, pid, f, status; + + ret = os.exec(["true"]); + assert(ret, 0); + + ret = os.exec(["/bin/sh", "-c", "exit 1"], { usePath: false }); + assert(ret, 1); + + fds = os.pipe(); + pid = os.exec(["sh", "-c", "echo $FOO"], { + stdout: fds[1], + block: false, + env: { FOO: "hello" }, + } ); + assert(pid >= 0); + os.close(fds[1]); /* close the write end (as it is only in the child) */ + f = std.fdopen(fds[0], "r"); + assert(f.getline(), "hello"); + assert(f.getline(), null); + f.close(); + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + assert(status & 0x7f, 0); /* exited */ + assert(status >> 8, 0); /* exit code */ + + pid = os.exec(["cat"], { block: false } ); + assert(pid >= 0); + os.kill(pid, os.SIGTERM); + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + assert(status !== 0, true, `expect nonzero exit code (got ${status})`); + assert(status & 0x7f, os.SIGTERM); +} + +function test_timer() +{ + var th, i; + + /* just test that a timer can be inserted and removed */ + th = []; + for(i = 0; i < 3; i++) + th[i] = os.setTimeout(function () { }, 1000); + for(i = 0; i < 3; i++) + os.clearTimeout(th[i]); +} + +/* test closure variable handling when freeing asynchronous + function */ +function test_async_gc() +{ + (async function run () { + let obj = {} + + let done = () => { + obj + std.gc(); + } + + Promise.resolve().then(done) + + const p = new Promise(() => {}) + + await p + })(); +} + +/* check that the promise async rejection handler is not invoked when + the rejection is handled not too late after the promise + rejection. */ +function test_async_promise_rejection() +{ + var counter = 0; + var p1, p2, p3; + p1 = Promise.reject(); + p2 = Promise.reject(); + p3 = Promise.resolve(); + p1.catch(() => counter++); + p2.catch(() => counter++); + p3.then(() => counter++) + os.setTimeout(() => { assert(counter, 3) }, 10); +} + +test_printf(); +test_file1(); +test_file2(); +test_getline(); +test_popen(); +test_os(); +test_os_exec(); +test_timer(); +test_ext_json(); +test_async_gc(); +test_async_promise_rejection(); + diff --git a/NativeScript/napi/android/quickjs/source/tests/test_worker.js b/NativeScript/napi/android/quickjs/source/tests/test_worker.js new file mode 100755 index 000000000..4b52bf82c --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_worker.js @@ -0,0 +1,62 @@ +/* os.Worker API test */ +import * as std from "std"; +import * as os from "os"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +var worker; + +function test_worker() +{ + var counter; + + worker = new os.Worker("./test_worker_module.js"); + + counter = 0; + worker.onmessage = function (e) { + var ev = e.data; +// print("recv", JSON.stringify(ev)); + switch(ev.type) { + case "num": + assert(ev.num, counter); + counter++; + if (counter == 10) { + /* test SharedArrayBuffer modification */ + let sab = new SharedArrayBuffer(10); + let buf = new Uint8Array(sab); + worker.postMessage({ type: "sab", buf: buf }); + } + break; + case "sab_done": + { + let buf = ev.buf; + /* check that the SharedArrayBuffer was modified */ + assert(buf[2], 10); + worker.postMessage({ type: "abort" }); + } + break; + case "done": + /* terminate */ + worker.onmessage = null; + break; + } + }; +} + + +test_worker(); diff --git a/NativeScript/napi/android/quickjs/source/tests/test_worker_module.js b/NativeScript/napi/android/quickjs/source/tests/test_worker_module.js new file mode 100755 index 000000000..ddf8e4007 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/tests/test_worker_module.js @@ -0,0 +1,32 @@ +/* Worker code for test_worker.js */ +import * as std from "std"; +import * as os from "os"; + +var parent = os.Worker.parent; + +function handle_msg(e) { + var ev = e.data; + // print("child_recv", JSON.stringify(ev)); + switch(ev.type) { + case "abort": + parent.postMessage({ type: "done" }); + parent.onmessage = null; /* terminate the worker */ + break; + case "sab": + /* modify the SharedArrayBuffer */ + ev.buf[2] = 10; + parent.postMessage({ type: "sab_done", buf: ev.buf }); + break; + } +} + +function worker_main() { + var i; + + parent.onmessage = handle_msg; + for(i = 0; i < 10; i++) { + parent.postMessage({ type: "num", num: i }); + } +} + +worker_main(); diff --git a/NativeScript/napi/android/quickjs/source/unicode_download.sh b/NativeScript/napi/android/quickjs/source/unicode_download.sh new file mode 100755 index 000000000..ef8b30d2e --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/unicode_download.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -e + +version="16.0.0" +emoji_version="16.0" +url="ftp://ftp.unicode.org/Public" + +files="CaseFolding.txt DerivedNormalizationProps.txt PropList.txt \ +SpecialCasing.txt CompositionExclusions.txt ScriptExtensions.txt \ +UnicodeData.txt DerivedCoreProperties.txt NormalizationTest.txt Scripts.txt \ +PropertyValueAliases.txt" + +mkdir -p unicode + +for f in $files; do + g="${url}/${version}/ucd/${f}" + wget $g -O unicode/$f +done + +wget "${url}/${version}/ucd/emoji/emoji-data.txt" -O unicode/emoji-data.txt + +wget "${url}/emoji/${emoji_version}/emoji-sequences.txt" -O unicode/emoji-sequences.txt +wget "${url}/emoji/${emoji_version}/emoji-zwj-sequences.txt" -O unicode/emoji-zwj-sequences.txt diff --git a/NativeScript/napi/android/quickjs/source/unicode_gen.c b/NativeScript/napi/android/quickjs/source/unicode_gen.c new file mode 100755 index 000000000..c793ba1e6 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/unicode_gen.c @@ -0,0 +1,3779 @@ +/* + * Generation of Unicode tables + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" + +uint32_t total_tables; +uint32_t total_table_bytes; +uint32_t total_index; +uint32_t total_index_bytes; + +/* define it to be able to test unicode.c */ +//#define USE_TEST +/* profile tests */ +//#define PROFILE + +//#define DUMP_CASE_CONV_TABLE +//#define DUMP_TABLE_SIZE +//#define DUMP_CC_TABLE +//#define DUMP_DECOMP_TABLE +//#define DUMP_CASE_FOLDING_SPECIAL_CASES + +/* Ideas: + - Generalize run length encoding + index for all tables + - remove redundant tables for ID_start, ID_continue, Case_Ignorable, Cased + + Case conversion: + - use a single entry for consecutive U/LF runs + - allow EXT runs of length > 1 + + Decomposition: + - Greek lower case (+1f10/1f10) ? + - allow holes in B runs + - suppress more upper / lower case redundancy +*/ + +#ifdef USE_TEST +#include "libunicode.c" +#endif + +#define CHARCODE_MAX 0x10ffff +#define CC_LEN_MAX 3 + +void *mallocz(size_t size) +{ + void *ptr; + ptr = malloc(size); + memset(ptr, 0, size); + return ptr; +} + +const char *get_field(const char *p, int n) +{ + int i; + for(i = 0; i < n; i++) { + while (*p != ';' && *p != '\0') + p++; + if (*p == '\0') + return NULL; + p++; + } + return p; +} + +const char *get_field_buf(char *buf, size_t buf_size, const char *p, int n) +{ + char *q; + p = get_field(p, n); + q = buf; + while (*p != ';' && *p != '\0') { + if ((q - buf) < buf_size - 1) + *q++ = *p; + p++; + } + *q = '\0'; + return buf; +} + +void add_char(int **pbuf, int *psize, int *plen, int c) +{ + int len, size, *buf; + buf = *pbuf; + size = *psize; + len = *plen; + if (len >= size) { + size = *psize; + size = max_int(len + 1, size * 3 / 2); + buf = realloc(buf, sizeof(buf[0]) * size); + *pbuf = buf; + *psize = size; + } + buf[len++] = c; + *plen = len; +} + +int *get_field_str(int *plen, const char *str, int n) +{ + const char *p; + int *buf, len, size; + p = get_field(str, n); + if (!p) { + *plen = 0; + return NULL; + } + len = 0; + size = 0; + buf = NULL; + for(;;) { + while (isspace(*p)) + p++; + if (!isxdigit(*p)) + break; + add_char(&buf, &size, &len, strtoul(p, (char **)&p, 16)); + } + *plen = len; + return buf; +} + +char *get_line(char *buf, int buf_size, FILE *f) +{ + int len; + if (!fgets(buf, buf_size, f)) + return NULL; + len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + return buf; +} + +typedef struct REString { + struct REString *next; + uint32_t hash; + uint32_t len; + uint32_t flags; + uint32_t buf[]; +} REString; + +typedef struct { + uint32_t n_strings; + uint32_t hash_size; + int hash_bits; + REString **hash_table; +} REStringList; + +static uint32_t re_string_hash(int len, const uint32_t *buf) +{ + int i; + uint32_t h; + h = 1; + for(i = 0; i < len; i++) + h = h * 263 + buf[i]; + return h * 0x61C88647; +} + +static void re_string_list_init(REStringList *s) +{ + s->n_strings = 0; + s->hash_size = 0; + s->hash_bits = 0; + s->hash_table = NULL; +} + +static __maybe_unused void re_string_list_free(REStringList *s) +{ + REString *p, *p_next; + int i; + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_table[i]; p != NULL; p = p_next) { + p_next = p->next; + free(p); + } + } + free(s->hash_table); +} + +static void lre_print_char(int c, BOOL is_range) +{ + if (c == '\'' || c == '\\' || + (is_range && (c == '-' || c == ']'))) { + printf("\\%c", c); + } else if (c >= ' ' && c <= 126) { + printf("%c", c); + } else { + printf("\\u{%04x}", c); + } +} + +static __maybe_unused void re_string_list_dump(const char *str, const REStringList *s) +{ + REString *p; + int i, j, k; + + printf("%s:\n", str); + + j = 0; + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_table[i]; p != NULL; p = p->next) { + printf(" %d/%d: '", j, s->n_strings); + for(k = 0; k < p->len; k++) { + lre_print_char(p->buf[k], FALSE); + } + printf("'\n"); + j++; + } + } +} + +static REString *re_string_find2(REStringList *s, int len, const uint32_t *buf, + uint32_t h0, BOOL add_flag) +{ + uint32_t h = 0; /* avoid warning */ + REString *p; + if (s->n_strings != 0) { + h = h0 >> (32 - s->hash_bits); + for(p = s->hash_table[h]; p != NULL; p = p->next) { + if (p->hash == h0 && p->len == len && + !memcmp(p->buf, buf, len * sizeof(buf[0]))) { + return p; + } + } + } + /* not found */ + if (!add_flag) + return NULL; + /* increase the size of the hash table if needed */ + if (unlikely((s->n_strings + 1) > s->hash_size)) { + REString **new_hash_table, *p_next; + int new_hash_bits, i; + uint32_t new_hash_size; + new_hash_bits = max_int(s->hash_bits + 1, 4); + new_hash_size = 1 << new_hash_bits; + new_hash_table = malloc(sizeof(new_hash_table[0]) * new_hash_size); + if (!new_hash_table) + return NULL; + memset(new_hash_table, 0, sizeof(new_hash_table[0]) * new_hash_size); + for(i = 0; i < s->hash_size; i++) { + for(p = s->hash_table[i]; p != NULL; p = p_next) { + p_next = p->next; + h = p->hash >> (32 - new_hash_bits); + p->next = new_hash_table[h]; + new_hash_table[h] = p; + } + } + free(s->hash_table); + s->hash_bits = new_hash_bits; + s->hash_size = new_hash_size; + s->hash_table = new_hash_table; + h = h0 >> (32 - s->hash_bits); + } + + p = malloc(sizeof(REString) + len * sizeof(buf[0])); + if (!p) + return NULL; + p->next = s->hash_table[h]; + s->hash_table[h] = p; + s->n_strings++; + p->hash = h0; + p->len = len; + p->flags = 0; + memcpy(p->buf, buf, sizeof(buf[0]) * len); + return p; +} + +static REString *re_string_find(REStringList *s, int len, const uint32_t *buf, + BOOL add_flag) +{ + uint32_t h0; + h0 = re_string_hash(len, buf); + return re_string_find2(s, len, buf, h0, add_flag); +} + +static void re_string_add(REStringList *s, int len, const uint32_t *buf) +{ + re_string_find(s, len, buf, TRUE); +} + +#define UNICODE_GENERAL_CATEGORY + +typedef enum { +#define DEF(id, str) GCAT_ ## id, +#include "unicode_gen_def.h" +#undef DEF + GCAT_COUNT, +} UnicodeGCEnum1; + +static const char *unicode_gc_name[] = { +#define DEF(id, str) #id, +#include "unicode_gen_def.h" +#undef DEF +}; + +static const char *unicode_gc_short_name[] = { +#define DEF(id, str) str, +#include "unicode_gen_def.h" +#undef DEF +}; + +#undef UNICODE_GENERAL_CATEGORY + +#define UNICODE_SCRIPT + +typedef enum { +#define DEF(id, str) SCRIPT_ ## id, +#include "unicode_gen_def.h" +#undef DEF + SCRIPT_COUNT, +} UnicodeScriptEnum1; + +static const char *unicode_script_name[] = { +#define DEF(id, str) #id, +#include "unicode_gen_def.h" +#undef DEF +}; + +const char *unicode_script_short_name[] = { +#define DEF(id, str) str, +#include "unicode_gen_def.h" +#undef DEF +}; + +#undef UNICODE_SCRIPT + +#define UNICODE_PROP_LIST + +typedef enum { +#define DEF(id, str) PROP_ ## id, +#include "unicode_gen_def.h" +#undef DEF + PROP_COUNT, +} UnicodePropEnum1; + +static const char *unicode_prop_name[] = { +#define DEF(id, str) #id, +#include "unicode_gen_def.h" +#undef DEF +}; + +static const char *unicode_prop_short_name[] = { +#define DEF(id, str) str, +#include "unicode_gen_def.h" +#undef DEF +}; + +#undef UNICODE_PROP_LIST + +#define UNICODE_SEQUENCE_PROP_LIST + +typedef enum { +#define DEF(id) SEQUENCE_PROP_ ## id, +#include "unicode_gen_def.h" +#undef DEF + SEQUENCE_PROP_COUNT, +} UnicodeSequencePropEnum1; + +static const char *unicode_sequence_prop_name[] = { +#define DEF(id) #id, +#include "unicode_gen_def.h" +#undef DEF +}; + +#undef UNICODE_SEQUENCE_PROP_LIST + +typedef struct { + /* case conv */ + uint8_t u_len; + uint8_t l_len; + uint8_t f_len; + int u_data[CC_LEN_MAX]; /* to upper case */ + int l_data[CC_LEN_MAX]; /* to lower case */ + int f_data[CC_LEN_MAX]; /* to case folding */ + + uint8_t combining_class; + uint8_t is_compat:1; + uint8_t is_excluded:1; + uint8_t general_category; + uint8_t script; + uint8_t script_ext_len; + uint8_t *script_ext; + uint32_t prop_bitmap_tab[3]; + /* decomposition */ + int decomp_len; + int *decomp_data; +} CCInfo; + +typedef struct { + int count; + int size; + int *tab; +} UnicodeSequenceProperties; + +CCInfo *unicode_db; +REStringList rgi_emoji_zwj_sequence; +DynBuf rgi_emoji_tag_sequence; + +int find_name(const char **tab, int tab_len, const char *name) +{ + int i, len, name_len; + const char *p, *r; + + name_len = strlen(name); + for(i = 0; i < tab_len; i++) { + p = tab[i]; + for(;;) { + r = strchr(p, ','); + if (!r) + len = strlen(p); + else + len = r - p; + if (len == name_len && memcmp(p, name, len) == 0) + return i; + if (!r) + break; + p = r + 1; + } + } + return -1; +} + +static BOOL get_prop(uint32_t c, int prop_idx) +{ + return (unicode_db[c].prop_bitmap_tab[prop_idx >> 5] >> (prop_idx & 0x1f)) & 1; +} + +static void set_prop(uint32_t c, int prop_idx, int val) +{ + uint32_t mask; + mask = 1U << (prop_idx & 0x1f); + if (val) + unicode_db[c].prop_bitmap_tab[prop_idx >> 5] |= mask; + else + unicode_db[c].prop_bitmap_tab[prop_idx >> 5] &= ~mask; +} + +void parse_unicode_data(const char *filename) +{ + FILE *f; + char line[1024]; + char buf1[256]; + const char *p; + int code, lc, uc, last_code; + CCInfo *ci, *tab = unicode_db; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + last_code = 0; + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#') + continue; + + p = get_field(line, 0); + if (!p) + continue; + code = strtoul(p, NULL, 16); + lc = 0; + uc = 0; + + p = get_field(line, 12); + if (p && *p != ';') { + uc = strtoul(p, NULL, 16); + } + + p = get_field(line, 13); + if (p && *p != ';') { + lc = strtoul(p, NULL, 16); + } + ci = &tab[code]; + if (uc > 0 || lc > 0) { + assert(code <= CHARCODE_MAX); + if (uc > 0) { + assert(ci->u_len == 0); + ci->u_len = 1; + ci->u_data[0] = uc; + } + if (lc > 0) { + assert(ci->l_len == 0); + ci->l_len = 1; + ci->l_data[0] = lc; + } + } + + { + int i; + get_field_buf(buf1, sizeof(buf1), line, 2); + i = find_name(unicode_gc_name, countof(unicode_gc_name), buf1); + if (i < 0) { + fprintf(stderr, "General category '%s' not found\n", + buf1); + exit(1); + } + ci->general_category = i; + } + + p = get_field(line, 3); + if (p && *p != ';' && *p != '\0') { + int cc; + cc = strtoul(p, NULL, 0); + if (cc != 0) { + assert(code <= CHARCODE_MAX); + ci->combining_class = cc; + // printf("%05x: %d\n", code, ci->combining_class); + } + } + + p = get_field(line, 5); + if (p && *p != ';' && *p != '\0') { + int size; + assert(code <= CHARCODE_MAX); + ci->is_compat = 0; + if (*p == '<') { + while (*p != '\0' && *p != '>') + p++; + if (*p == '>') + p++; + ci->is_compat = 1; + } + size = 0; + for(;;) { + while (isspace(*p)) + p++; + if (!isxdigit(*p)) + break; + add_char(&ci->decomp_data, &size, &ci->decomp_len, strtoul(p, (char **)&p, 16)); + } +#if 0 + { + int i; + static int count, d_count; + + printf("%05x: %c", code, ci->is_compat ? 'C': ' '); + for(i = 0; i < ci->decomp_len; i++) + printf(" %05x", ci->decomp_data[i]); + printf("\n"); + count++; + d_count += ci->decomp_len; + // printf("%d %d\n", count, d_count); + } +#endif + } + + p = get_field(line, 9); + if (p && *p == 'Y') { + set_prop(code, PROP_Bidi_Mirrored, 1); + } + + /* handle ranges */ + get_field_buf(buf1, sizeof(buf1), line, 1); + if (strstr(buf1, " Last>")) { + int i; + // printf("range: 0x%x-%0x\n", last_code, code); + assert(ci->decomp_len == 0); + assert(ci->script_ext_len == 0); + for(i = last_code + 1; i < code; i++) { + unicode_db[i] = *ci; + } + } + last_code = code; + } + + fclose(f); +} + +void parse_special_casing(CCInfo *tab, const char *filename) +{ + FILE *f; + char line[1024]; + const char *p; + int code; + CCInfo *ci; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#') + continue; + + p = get_field(line, 0); + if (!p) + continue; + code = strtoul(p, NULL, 16); + assert(code <= CHARCODE_MAX); + ci = &tab[code]; + + p = get_field(line, 4); + if (p) { + /* locale dependent casing */ + while (isspace(*p)) + p++; + if (*p != '#' && *p != '\0') + continue; + } + + + p = get_field(line, 1); + if (p && *p != ';') { + ci->l_len = 0; + for(;;) { + while (isspace(*p)) + p++; + if (*p == ';') + break; + assert(ci->l_len < CC_LEN_MAX); + ci->l_data[ci->l_len++] = strtoul(p, (char **)&p, 16); + } + + if (ci->l_len == 1 && ci->l_data[0] == code) + ci->l_len = 0; + } + + p = get_field(line, 3); + if (p && *p != ';') { + ci->u_len = 0; + for(;;) { + while (isspace(*p)) + p++; + if (*p == ';') + break; + assert(ci->u_len < CC_LEN_MAX); + ci->u_data[ci->u_len++] = strtoul(p, (char **)&p, 16); + } + + if (ci->u_len == 1 && ci->u_data[0] == code) + ci->u_len = 0; + } + } + + fclose(f); +} + +void parse_case_folding(CCInfo *tab, const char *filename) +{ + FILE *f; + char line[1024]; + const char *p; + int code, status; + CCInfo *ci; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#') + continue; + + p = get_field(line, 0); + if (!p) + continue; + code = strtoul(p, NULL, 16); + assert(code <= CHARCODE_MAX); + ci = &tab[code]; + + p = get_field(line, 1); + if (!p) + continue; + /* locale dependent casing */ + while (isspace(*p)) + p++; + status = *p; + if (status != 'C' && status != 'S' && status != 'F') + continue; + + p = get_field(line, 2); + assert(p != NULL); + if (status == 'S') { + /* we always select the simple case folding and assume it + * comes after the full case folding case */ + assert(ci->f_len >= 2); + ci->f_len = 0; + } else { + assert(ci->f_len == 0); + } + for(;;) { + while (isspace(*p)) + p++; + if (*p == ';') + break; + assert(ci->l_len < CC_LEN_MAX); + ci->f_data[ci->f_len++] = strtoul(p, (char **)&p, 16); + } + } + + fclose(f); +} + +void parse_composition_exclusions(const char *filename) +{ + FILE *f; + char line[4096], *p; + uint32_t c0; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + assert(c0 > 0 && c0 <= CHARCODE_MAX); + unicode_db[c0].is_excluded = TRUE; + } + fclose(f); +} + +void parse_derived_core_properties(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + int i; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + if (*p == ';') { + p++; + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t' && *p != ';') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + i = find_name(unicode_prop_name, + countof(unicode_prop_name), buf); + if (i < 0) { + if (!strcmp(buf, "Grapheme_Link")) + goto next; + fprintf(stderr, "Property not found: %s\n", buf); + exit(1); + } + for(c = c0; c <= c1; c++) { + set_prop(c, i, 1); + } +next: ; + } + } + fclose(f); +} + +void parse_derived_norm_properties(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + if (*p == ';') { + p++; + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + if (!strcmp(buf, "Changes_When_NFKC_Casefolded")) { + for(c = c0; c <= c1; c++) { + set_prop(c, PROP_Changes_When_NFKC_Casefolded, 1); + } + } + } + } + fclose(f); +} + +void parse_prop_list(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + int i; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + if (*p == ';') { + p++; + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + i = find_name(unicode_prop_name, + countof(unicode_prop_name), buf); + if (i < 0) { + fprintf(stderr, "Property not found: %s\n", buf); + exit(1); + } + for(c = c0; c <= c1; c++) { + set_prop(c, i, 1); + } + } + } + fclose(f); +} + +#define SEQ_MAX_LEN 16 + +static BOOL is_emoji_modifier(uint32_t c) +{ + return (c >= 0x1f3fb && c <= 0x1f3ff); +} + +static void add_sequence_prop(int idx, int seq_len, int *seq) +{ + int i; + + assert(idx < SEQUENCE_PROP_COUNT); + switch(idx) { + case SEQUENCE_PROP_Basic_Emoji: + /* convert to 2 properties lists */ + if (seq_len == 1) { + set_prop(seq[0], PROP_Basic_Emoji1, 1); + } else if (seq_len == 2 && seq[1] == 0xfe0f) { + set_prop(seq[0], PROP_Basic_Emoji2, 1); + } else { + abort(); + } + break; + case SEQUENCE_PROP_RGI_Emoji_Modifier_Sequence: + assert(seq_len == 2); + assert(is_emoji_modifier(seq[1])); + assert(get_prop(seq[0], PROP_Emoji_Modifier_Base)); + set_prop(seq[0], PROP_RGI_Emoji_Modifier_Sequence, 1); + break; + case SEQUENCE_PROP_RGI_Emoji_Flag_Sequence: + { + int code; + assert(seq_len == 2); + assert(seq[0] >= 0x1F1E6 && seq[0] <= 0x1F1FF); + assert(seq[1] >= 0x1F1E6 && seq[1] <= 0x1F1FF); + code = (seq[0] - 0x1F1E6) * 26 + (seq[1] - 0x1F1E6); + /* XXX: would be more compact with a simple bitmap -> 676 bits */ + set_prop(code, PROP_RGI_Emoji_Flag_Sequence, 1); + } + break; + case SEQUENCE_PROP_RGI_Emoji_ZWJ_Sequence: + re_string_add(&rgi_emoji_zwj_sequence, seq_len, (uint32_t *)seq); + break; + case SEQUENCE_PROP_RGI_Emoji_Tag_Sequence: + { + assert(seq_len >= 3); + assert(seq[0] == 0x1F3F4); + assert(seq[seq_len - 1] == 0xE007F); + for(i = 1; i < seq_len - 1; i++) { + assert(seq[i] >= 0xe0001 && seq[i] <= 0xe007e); + dbuf_putc(&rgi_emoji_tag_sequence, seq[i] - 0xe0000); + } + dbuf_putc(&rgi_emoji_tag_sequence, 0); + } + break; + case SEQUENCE_PROP_Emoji_Keycap_Sequence: + assert(seq_len == 3); + assert(seq[1] == 0xfe0f); + assert(seq[2] == 0x20e3); + set_prop(seq[0], PROP_Emoji_Keycap_Sequence, 1); + break; + default: + assert(0); + } +} + +void parse_sequence_prop_list(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q, *p_start; + uint32_t c0, c1, c; + int idx, seq_len; + int seq[SEQ_MAX_LEN]; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + p_start = p; + + /* find the sequence property name */ + p = strchr(p, ';'); + if (!p) + continue; + p++; + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t' && *p != ';') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + idx = find_name(unicode_sequence_prop_name, + countof(unicode_sequence_prop_name), buf); + if (idx < 0) { + fprintf(stderr, "Property not found: %s\n", buf); + exit(1); + } + + p = p_start; + c0 = strtoul(p, (char **)&p, 16); + assert(c0 <= CHARCODE_MAX); + + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + assert(c1 <= CHARCODE_MAX); + for(c = c0; c <= c1; c++) { + seq[0] = c; + add_sequence_prop(idx, 1, seq); + } + } else { + seq_len = 0; + seq[seq_len++] = c0; + for(;;) { + while (isspace(*p)) + p++; + if (*p == ';' || *p == '\0') + break; + c0 = strtoul(p, (char **)&p, 16); + assert(c0 <= CHARCODE_MAX); + assert(seq_len < countof(seq)); + seq[seq_len++] = c0; + } + add_sequence_prop(idx, seq_len, seq); + } + } + fclose(f); +} + +void parse_scripts(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + int i; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + if (*p == ';') { + p++; + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + i = find_name(unicode_script_name, + countof(unicode_script_name), buf); + if (i < 0) { + fprintf(stderr, "Unknown script: '%s'\n", buf); + exit(1); + } + for(c = c0; c <= c1; c++) + unicode_db[c].script = i; + } + } + fclose(f); +} + +void parse_script_extensions(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + int i; + uint8_t script_ext[255]; + int script_ext_len; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + script_ext_len = 0; + if (*p == ';') { + p++; + for(;;) { + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + if (buf[0] == '\0') + break; + i = find_name(unicode_script_short_name, + countof(unicode_script_short_name), buf); + if (i < 0) { + fprintf(stderr, "Script not found: %s\n", buf); + exit(1); + } + assert(script_ext_len < sizeof(script_ext)); + script_ext[script_ext_len++] = i; + } + for(c = c0; c <= c1; c++) { + CCInfo *ci = &unicode_db[c]; + ci->script_ext_len = script_ext_len; + ci->script_ext = malloc(sizeof(ci->script_ext[0]) * script_ext_len); + for(i = 0; i < script_ext_len; i++) + ci->script_ext[i] = script_ext[i]; + } + } + } + fclose(f); +} + +void dump_cc_info(CCInfo *ci, int i) +{ + int j; + printf("%05x:", i); + if (ci->u_len != 0) { + printf(" U:"); + for(j = 0; j < ci->u_len; j++) + printf(" %05x", ci->u_data[j]); + } + if (ci->l_len != 0) { + printf(" L:"); + for(j = 0; j < ci->l_len; j++) + printf(" %05x", ci->l_data[j]); + } + if (ci->f_len != 0) { + printf(" F:"); + for(j = 0; j < ci->f_len; j++) + printf(" %05x", ci->f_data[j]); + } + printf("\n"); +} + +void dump_unicode_data(CCInfo *tab) +{ + int i; + CCInfo *ci; + for(i = 0; i <= CHARCODE_MAX; i++) { + ci = &tab[i]; + if (ci->u_len != 0 || ci->l_len != 0 || ci->f_len != 0) { + dump_cc_info(ci, i); + } + } +} + +BOOL is_complicated_case(const CCInfo *ci) +{ + return (ci->u_len > 1 || ci->l_len > 1 || + (ci->u_len > 0 && ci->l_len > 0) || + (ci->f_len != ci->l_len) || + (memcmp(ci->f_data, ci->l_data, ci->f_len * sizeof(ci->f_data[0])) != 0)); +} + +#ifndef USE_TEST +enum { + RUN_TYPE_U, + RUN_TYPE_L, + RUN_TYPE_UF, + RUN_TYPE_LF, + RUN_TYPE_UL, + RUN_TYPE_LSU, + RUN_TYPE_U2L_399_EXT2, + RUN_TYPE_UF_D20, + RUN_TYPE_UF_D1_EXT, + RUN_TYPE_U_EXT, + RUN_TYPE_LF_EXT, + RUN_TYPE_UF_EXT2, + RUN_TYPE_LF_EXT2, + RUN_TYPE_UF_EXT3, +}; +#endif + +const char *run_type_str[] = { + "U", + "L", + "UF", + "LF", + "UL", + "LSU", + "U2L_399_EXT2", + "UF_D20", + "UF_D1_EXT", + "U_EXT", + "LF_EXT", + "UF_EXT2", + "LF_EXT2", + "UF_EXT3", +}; + +typedef struct { + int code; + int len; + int type; + int data; + int ext_len; + int ext_data[3]; + int data_index; /* 'data' coming from the table */ +} TableEntry; + +static int simple_to_lower(CCInfo *tab, int c) +{ + if (tab[c].l_len != 1) + return c; + return tab[c].l_data[0]; +} + +/* code (17), len (7), type (4) */ + +void find_run_type(TableEntry *te, CCInfo *tab, int code) +{ + int is_lower, len; + CCInfo *ci, *ci1, *ci2; + + ci = &tab[code]; + ci1 = &tab[code + 1]; + ci2 = &tab[code + 2]; + te->code = code; + + if (ci->l_len == 1 && ci->l_data[0] == code + 2 && + ci->f_len == 1 && ci->f_data[0] == ci->l_data[0] && + ci->u_len == 0 && + + ci1->l_len == 1 && ci1->l_data[0] == code + 2 && + ci1->f_len == 1 && ci1->f_data[0] == ci1->l_data[0] && + ci1->u_len == 1 && ci1->u_data[0] == code && + + ci2->l_len == 0 && + ci2->f_len == 0 && + ci2->u_len == 1 && ci2->u_data[0] == code) { + te->len = 3; + te->data = 0; + te->type = RUN_TYPE_LSU; + return; + } + + if (is_complicated_case(ci)) { + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (ci1->u_len != 1 || + ci1->u_data[0] != ci->u_data[0] + len || + ci1->l_len != 0 || + ci1->f_len != 1 || ci1->f_data[0] != ci1->u_data[0]) + break; + len++; + } + if (len > 1) { + te->len = len; + te->type = RUN_TYPE_UF; + te->data = ci->u_data[0]; + return; + } + + if (ci->l_len == 0 && + ci->u_len == 2 && ci->u_data[1] == 0x399 && + ci->f_len == 2 && ci->f_data[1] == 0x3B9 && + ci->f_data[0] == simple_to_lower(tab, ci->u_data[0])) { + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (!(ci1->u_len == 2 && + ci1->u_data[1] == ci->u_data[1] && + ci1->u_data[0] == ci->u_data[0] + len && + ci1->f_len == 2 && + ci1->f_data[1] == ci->f_data[1] && + ci1->f_data[0] == ci->f_data[0] + len && + ci1->l_len == 0)) + break; + len++; + } + te->len = len; + te->type = RUN_TYPE_UF_EXT2; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->u_data[1]; + te->ext_len = 2; + return; + } + + if (ci->u_len == 2 && ci->u_data[1] == 0x399 && + ci->l_len == 1 && + ci->f_len == 1 && ci->f_data[0] == ci->l_data[0]) { + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (!(ci1->u_len == 2 && + ci1->u_data[1] == 0x399 && + ci1->u_data[0] == ci->u_data[0] + len && + ci1->l_len == 1 && + ci1->l_data[0] == ci->l_data[0] + len && + ci1->f_len == 1 && ci1->f_data[0] == ci1->l_data[0])) + break; + len++; + } + te->len = len; + te->type = RUN_TYPE_U2L_399_EXT2; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->l_data[0]; + te->ext_len = 2; + return; + } + + if (ci->l_len == 1 && ci->u_len == 0 && ci->f_len == 0) { + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (!(ci1->l_len == 1 && + ci1->l_data[0] == ci->l_data[0] + len && + ci1->u_len == 0 && ci1->f_len == 0)) + break; + len++; + } + te->len = len; + te->type = RUN_TYPE_L; + te->data = ci->l_data[0]; + return; + } + + if (ci->l_len == 0 && + ci->u_len == 1 && + ci->u_data[0] < 0x1000 && + ci->f_len == 1 && ci->f_data[0] == ci->u_data[0] + 0x20) { + te->len = 1; + te->type = RUN_TYPE_UF_D20; + te->data = ci->u_data[0]; + } else if (ci->l_len == 0 && + ci->u_len == 1 && + ci->f_len == 1 && ci->f_data[0] == ci->u_data[0] + 1) { + te->len = 1; + te->type = RUN_TYPE_UF_D1_EXT; + te->ext_data[0] = ci->u_data[0]; + te->ext_len = 1; + } else if (ci->l_len == 2 && ci->u_len == 0 && ci->f_len == 2 && + ci->l_data[0] == ci->f_data[0] && + ci->l_data[1] == ci->f_data[1]) { + te->len = 1; + te->type = RUN_TYPE_LF_EXT2; + te->ext_data[0] = ci->l_data[0]; + te->ext_data[1] = ci->l_data[1]; + te->ext_len = 2; + } else if (ci->u_len == 2 && ci->l_len == 0 && ci->f_len == 2 && + ci->f_data[0] == simple_to_lower(tab, ci->u_data[0]) && + ci->f_data[1] == simple_to_lower(tab, ci->u_data[1])) { + te->len = 1; + te->type = RUN_TYPE_UF_EXT2; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->u_data[1]; + te->ext_len = 2; + } else if (ci->u_len == 3 && ci->l_len == 0 && ci->f_len == 3 && + ci->f_data[0] == simple_to_lower(tab, ci->u_data[0]) && + ci->f_data[1] == simple_to_lower(tab, ci->u_data[1]) && + ci->f_data[2] == simple_to_lower(tab, ci->u_data[2])) { + te->len = 1; + te->type = RUN_TYPE_UF_EXT3; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->u_data[1]; + te->ext_data[2] = ci->u_data[2]; + te->ext_len = 3; + } else if (ci->u_len == 2 && ci->l_len == 0 && ci->f_len == 1) { + // U+FB05 LATIN SMALL LIGATURE LONG S T + assert(code == 0xFB05); + te->len = 1; + te->type = RUN_TYPE_UF_EXT2; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->u_data[1]; + te->ext_len = 2; + } else if (ci->u_len == 3 && ci->l_len == 0 && ci->f_len == 1) { + // U+1FD3 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA or + // U+1FE3 GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA + assert(code == 0x1FD3 || code == 0x1FE3); + te->len = 1; + te->type = RUN_TYPE_UF_EXT3; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->u_data[1]; + te->ext_data[2] = ci->u_data[2]; + te->ext_len = 3; + } else { + printf("unsupported encoding case:\n"); + dump_cc_info(ci, code); + abort(); + } + } else { + /* look for a run of identical conversions */ + len = 0; + for(;;) { + if (code >= CHARCODE_MAX || len >= 126) + break; + ci = &tab[code + len]; + ci1 = &tab[code + len + 1]; + if (is_complicated_case(ci) || is_complicated_case(ci1)) { + break; + } + if (ci->l_len != 1 || ci->l_data[0] != code + len + 1) + break; + if (ci1->u_len != 1 || ci1->u_data[0] != code + len) + break; + len += 2; + } + if (len > 0) { + te->len = len; + te->type = RUN_TYPE_UL; + te->data = 0; + return; + } + + ci = &tab[code]; + is_lower = ci->l_len > 0; + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (is_complicated_case(ci1)) + break; + if (is_lower) { + if (ci1->l_len != 1 || + ci1->l_data[0] != ci->l_data[0] + len) + break; + } else { + if (ci1->u_len != 1 || + ci1->u_data[0] != ci->u_data[0] + len) + break; + } + len++; + } + te->len = len; + if (is_lower) { + te->type = RUN_TYPE_LF; + te->data = ci->l_data[0]; + } else { + te->type = RUN_TYPE_U; + te->data = ci->u_data[0]; + } + } +} + +TableEntry conv_table[1000]; +int conv_table_len; +int ext_data[1000]; +int ext_data_len; + +void dump_case_conv_table1(void) +{ + int i, j; + const TableEntry *te; + + for(i = 0; i < conv_table_len; i++) { + te = &conv_table[i]; + printf("%05x %02x %-10s %05x", + te->code, te->len, run_type_str[te->type], te->data); + for(j = 0; j < te->ext_len; j++) { + printf(" %05x", te->ext_data[j]); + } + printf("\n"); + } + printf("table_len=%d ext_len=%d\n", conv_table_len, ext_data_len); +} + +int find_data_index(const TableEntry *conv_table, int len, int data) +{ + int i; + const TableEntry *te; + for(i = 0; i < len; i++) { + te = &conv_table[i]; + if (te->code == data) + return i; + } + return -1; +} + +int find_ext_data_index(int data) +{ + int i; + for(i = 0; i < ext_data_len; i++) { + if (ext_data[i] == data) + return i; + } + assert(ext_data_len < countof(ext_data)); + ext_data[ext_data_len++] = data; + return ext_data_len - 1; +} + +void build_conv_table(CCInfo *tab) +{ + int code, i, j; + CCInfo *ci; + TableEntry *te; + + te = conv_table; + for(code = 0; code <= CHARCODE_MAX; code++) { + ci = &tab[code]; + if (ci->u_len == 0 && ci->l_len == 0 && ci->f_len == 0) + continue; + assert(te - conv_table < countof(conv_table)); + find_run_type(te, tab, code); +#if 0 + if (te->type == RUN_TYPE_TODO) { + printf("TODO: "); + dump_cc_info(ci, code); + } +#endif + assert(te->len <= 127); + code += te->len - 1; + te++; + } + conv_table_len = te - conv_table; + + /* find the data index */ + for(i = 0; i < conv_table_len; i++) { + int data_index; + te = &conv_table[i]; + + switch(te->type) { + case RUN_TYPE_U: + case RUN_TYPE_L: + case RUN_TYPE_UF: + case RUN_TYPE_LF: + data_index = find_data_index(conv_table, conv_table_len, te->data); + if (data_index < 0) { + switch(te->type) { + case RUN_TYPE_U: + te->type = RUN_TYPE_U_EXT; + te->ext_len = 1; + te->ext_data[0] = te->data; + break; + case RUN_TYPE_LF: + te->type = RUN_TYPE_LF_EXT; + te->ext_len = 1; + te->ext_data[0] = te->data; + break; + default: + printf("%05x: index not found\n", te->code); + exit(1); + } + } else { + te->data_index = data_index; + } + break; + case RUN_TYPE_UF_D20: + te->data_index = te->data; + break; + } + } + + /* find the data index for ext_data */ + for(i = 0; i < conv_table_len; i++) { + te = &conv_table[i]; + if (te->type == RUN_TYPE_UF_EXT3) { + int p, v; + v = 0; + for(j = 0; j < 3; j++) { + p = find_ext_data_index(te->ext_data[j]); + assert(p < 16); + v = (v << 4) | p; + } + te->data_index = v; + } + } + + for(i = 0; i < conv_table_len; i++) { + te = &conv_table[i]; + if (te->type == RUN_TYPE_LF_EXT2 || + te->type == RUN_TYPE_UF_EXT2 || + te->type == RUN_TYPE_U2L_399_EXT2) { + int p, v; + v = 0; + for(j = 0; j < 2; j++) { + p = find_ext_data_index(te->ext_data[j]); + assert(p < 64); + v = (v << 6) | p; + } + te->data_index = v; + } + } + + for(i = 0; i < conv_table_len; i++) { + te = &conv_table[i]; + if (te->type == RUN_TYPE_UF_D1_EXT || + te->type == RUN_TYPE_U_EXT || + te->type == RUN_TYPE_LF_EXT) { + te->data_index = find_ext_data_index(te->ext_data[0]); + } + } +#ifdef DUMP_CASE_CONV_TABLE + dump_case_conv_table1(); +#endif +} + +void dump_case_conv_table(FILE *f) +{ + int i; + uint32_t v; + const TableEntry *te; + + total_tables++; + total_table_bytes += conv_table_len * sizeof(uint32_t); + fprintf(f, "static const uint32_t case_conv_table1[%d] = {", conv_table_len); + for(i = 0; i < conv_table_len; i++) { + if (i % 4 == 0) + fprintf(f, "\n "); + te = &conv_table[i]; + v = te->code << (32 - 17); + v |= te->len << (32 - 17 - 7); + v |= te->type << (32 - 17 - 7 - 4); + v |= te->data_index >> 8; + fprintf(f, " 0x%08x,", v); + } + fprintf(f, "\n};\n\n"); + + total_tables++; + total_table_bytes += conv_table_len; + fprintf(f, "static const uint8_t case_conv_table2[%d] = {", conv_table_len); + for(i = 0; i < conv_table_len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + te = &conv_table[i]; + fprintf(f, " 0x%02x,", te->data_index & 0xff); + } + fprintf(f, "\n};\n\n"); + + total_tables++; + total_table_bytes += ext_data_len * sizeof(uint16_t); + fprintf(f, "static const uint16_t case_conv_ext[%d] = {", ext_data_len); + for(i = 0; i < ext_data_len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + fprintf(f, " 0x%04x,", ext_data[i]); + } + fprintf(f, "\n};\n\n"); +} + + +static CCInfo *global_tab; + +static int sp_cc_cmp(const void *p1, const void *p2) +{ + CCInfo *c1 = &global_tab[*(const int *)p1]; + CCInfo *c2 = &global_tab[*(const int *)p2]; + if (c1->f_len < c2->f_len) { + return -1; + } else if (c2->f_len < c1->f_len) { + return 1; + } else { + return memcmp(c1->f_data, c2->f_data, sizeof(c1->f_data[0]) * c1->f_len); + } +} + +/* dump the case special cases (multi character results which are + identical and need specific handling in lre_canonicalize() */ +void dump_case_folding_special_cases(CCInfo *tab) +{ + int i, len, j; + int *perm; + + perm = malloc(sizeof(perm[0]) * (CHARCODE_MAX + 1)); + for(i = 0; i <= CHARCODE_MAX; i++) + perm[i] = i; + global_tab = tab; + qsort(perm, CHARCODE_MAX + 1, sizeof(perm[0]), sp_cc_cmp); + for(i = 0; i <= CHARCODE_MAX;) { + if (tab[perm[i]].f_len <= 1) { + i++; + } else { + len = 1; + while ((i + len) <= CHARCODE_MAX && !sp_cc_cmp(&perm[i], &perm[i + len])) + len++; + + if (len > 1) { + for(j = i; j < i + len; j++) + dump_cc_info(&tab[perm[j]], perm[j]); + } + i += len; + } + } + free(perm); + global_tab = NULL; +} + + +int tabcmp(const int *tab1, const int *tab2, int n) +{ + int i; + for(i = 0; i < n; i++) { + if (tab1[i] != tab2[i]) + return -1; + } + return 0; +} + +void dump_str(const char *str, const int *buf, int len) +{ + int i; + printf("%s=", str); + for(i = 0; i < len; i++) + printf(" %05x", buf[i]); + printf("\n"); +} + +void compute_internal_props(void) +{ + int i; + BOOL has_ul; + + for(i = 0; i <= CHARCODE_MAX; i++) { + CCInfo *ci = &unicode_db[i]; + has_ul = (ci->u_len != 0 || ci->l_len != 0 || ci->f_len != 0); + if (has_ul) { + assert(get_prop(i, PROP_Cased)); + } else { + set_prop(i, PROP_Cased1, get_prop(i, PROP_Cased)); + } + set_prop(i, PROP_ID_Continue1, + get_prop(i, PROP_ID_Continue) & (get_prop(i, PROP_ID_Start) ^ 1)); + set_prop(i, PROP_XID_Start1, + get_prop(i, PROP_ID_Start) ^ get_prop(i, PROP_XID_Start)); + set_prop(i, PROP_XID_Continue1, + get_prop(i, PROP_ID_Continue) ^ get_prop(i, PROP_XID_Continue)); + set_prop(i, PROP_Changes_When_Titlecased1, + get_prop(i, PROP_Changes_When_Titlecased) ^ (ci->u_len != 0)); + set_prop(i, PROP_Changes_When_Casefolded1, + get_prop(i, PROP_Changes_When_Casefolded) ^ (ci->f_len != 0)); + /* XXX: reduce table size (438 bytes) */ + set_prop(i, PROP_Changes_When_NFKC_Casefolded1, + get_prop(i, PROP_Changes_When_NFKC_Casefolded) ^ (ci->f_len != 0)); +#if 0 + /* TEST */ +#define M(x) (1U << GCAT_ ## x) + { + int b; + b = ((M(Mn) | M(Cf) | M(Lm) | M(Sk)) >> + unicode_db[i].general_category) & 1; + set_prop(i, PROP_Cased1, + get_prop(i, PROP_Case_Ignorable) ^ b); + } +#undef M +#endif + } +} + +void dump_byte_table(FILE *f, const char *cname, const uint8_t *tab, int len) +{ + int i; + + total_tables++; + total_table_bytes += len; + fprintf(f, "static const uint8_t %s[%d] = {", cname, len); + for(i = 0; i < len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + fprintf(f, " 0x%02x,", tab[i]); + } + fprintf(f, "\n};\n\n"); +} + +void dump_index_table(FILE *f, const char *cname, const uint8_t *tab, int len) +{ + int i, code, offset; + + total_index++; + total_index_bytes += len; + fprintf(f, "static const uint8_t %s[%d] = {\n", cname, len); + for(i = 0; i < len; i += 3) { + code = tab[i] + (tab[i+1] << 8) + ((tab[i+2] & 0x1f) << 16); + offset = ((i / 3) + 1) * 32 + (tab[i+2] >> 5); + fprintf(f, " 0x%02x, 0x%02x, 0x%02x,", tab[i], tab[i+1], tab[i+2]); + fprintf(f, " // %6.5X at %d%s\n", code, offset, + i == len - 3 ? " (upper bound)" : ""); + } + fprintf(f, "};\n\n"); +} + +#define PROP_BLOCK_LEN 32 + +void build_prop_table(FILE *f, const char *name, int prop_index, BOOL add_index) +{ + int i, j, n, v, offset, code; + DynBuf dbuf_s, *dbuf = &dbuf_s; + DynBuf dbuf1_s, *dbuf1 = &dbuf1_s; + DynBuf dbuf2_s, *dbuf2 = &dbuf2_s; + const uint32_t *buf; + int buf_len, block_end_pos, bit; + char cname[128]; + + dbuf_init(dbuf1); + + for(i = 0; i <= CHARCODE_MAX;) { + v = get_prop(i, prop_index); + j = i + 1; + while (j <= CHARCODE_MAX && get_prop(j, prop_index) == v) { + j++; + } + n = j - i; + if (j == (CHARCODE_MAX + 1) && v == 0) + break; /* no need to encode last zero run */ + //printf("%05x: %d %d\n", i, n, v); + dbuf_put_u32(dbuf1, n - 1); + i += n; + } + + dbuf_init(dbuf); + dbuf_init(dbuf2); + buf = (uint32_t *)dbuf1->buf; + buf_len = dbuf1->size / sizeof(buf[0]); + + /* the first value is assumed to be 0 */ + assert(get_prop(0, prop_index) == 0); + + block_end_pos = PROP_BLOCK_LEN; + i = 0; + code = 0; + bit = 0; + while (i < buf_len) { + if (add_index && dbuf->size >= block_end_pos && bit == 0) { + offset = (dbuf->size - block_end_pos); + /* XXX: offset could be larger in case of runs of small + lengths. Could add code to change the encoding to + prevent it at the expense of one byte loss */ + assert(offset <= 7); + v = code | (offset << 21); + dbuf_putc(dbuf2, v); + dbuf_putc(dbuf2, v >> 8); + dbuf_putc(dbuf2, v >> 16); + block_end_pos += PROP_BLOCK_LEN; + } + + /* Compressed byte encoding: + 00..3F: 2 packed lengths: 3-bit + 3-bit + 40..5F: 5-bits plus extra byte for length + 60..7F: 5-bits plus 2 extra bytes for length + 80..FF: 7-bit length + lengths must be incremented to get character count + Ranges alternate between false and true return value. + */ + v = buf[i]; + code += v + 1; + bit ^= 1; + if (v < 8 && (i + 1) < buf_len && buf[i + 1] < 8) { + code += buf[i + 1] + 1; + bit ^= 1; + dbuf_putc(dbuf, (v << 3) | buf[i + 1]); + i += 2; + } else if (v < 128) { + dbuf_putc(dbuf, 0x80 + v); + i++; + } else if (v < (1 << 13)) { + dbuf_putc(dbuf, 0x40 + (v >> 8)); + dbuf_putc(dbuf, v); + i++; + } else { + assert(v < (1 << 21)); + dbuf_putc(dbuf, 0x60 + (v >> 16)); + dbuf_putc(dbuf, v >> 8); + dbuf_putc(dbuf, v); + i++; + } + } + + if (add_index) { + /* last index entry */ + v = code; + dbuf_putc(dbuf2, v); + dbuf_putc(dbuf2, v >> 8); + dbuf_putc(dbuf2, v >> 16); + } + +#ifdef DUMP_TABLE_SIZE + printf("prop %s: length=%d bytes\n", unicode_prop_name[prop_index], + (int)(dbuf->size + dbuf2->size)); +#endif + snprintf(cname, sizeof(cname), "unicode_prop_%s_table", unicode_prop_name[prop_index]); + dump_byte_table(f, cname, dbuf->buf, dbuf->size); + if (add_index) { + snprintf(cname, sizeof(cname), "unicode_prop_%s_index", unicode_prop_name[prop_index]); + dump_index_table(f, cname, dbuf2->buf, dbuf2->size); + } + + dbuf_free(dbuf); + dbuf_free(dbuf1); + dbuf_free(dbuf2); +} + +void build_flags_tables(FILE *f) +{ + build_prop_table(f, "Cased1", PROP_Cased1, TRUE); + build_prop_table(f, "Case_Ignorable", PROP_Case_Ignorable, TRUE); + build_prop_table(f, "ID_Start", PROP_ID_Start, TRUE); + build_prop_table(f, "ID_Continue1", PROP_ID_Continue1, TRUE); +} + +void dump_name_table(FILE *f, const char *cname, const char **tab_name, int len, + const char **tab_short_name) +{ + int i, w, maxw; + + maxw = 0; + for(i = 0; i < len; i++) { + w = strlen(tab_name[i]); + if (tab_short_name && tab_short_name[i][0] != '\0') { + w += 1 + strlen(tab_short_name[i]); + } + if (maxw < w) + maxw = w; + } + + /* generate a sequence of strings terminated by an empty string */ + fprintf(f, "static const char %s[] =\n", cname); + for(i = 0; i < len; i++) { + fprintf(f, " \""); + w = fprintf(f, "%s", tab_name[i]); + if (tab_short_name && tab_short_name[i][0] != '\0') { + w += fprintf(f, ",%s", tab_short_name[i]); + } + fprintf(f, "\"%*s\"\\0\"\n", 1 + maxw - w, ""); + } + fprintf(f, ";\n\n"); +} + +void build_general_category_table(FILE *f) +{ + int i, v, j, n, n1; + DynBuf dbuf_s, *dbuf = &dbuf_s; +#ifdef DUMP_TABLE_SIZE + int cw_count, cw_len_count[4], cw_start; +#endif + + fprintf(f, "typedef enum {\n"); + for(i = 0; i < GCAT_COUNT; i++) + fprintf(f, " UNICODE_GC_%s,\n", unicode_gc_name[i]); + fprintf(f, " UNICODE_GC_COUNT,\n"); + fprintf(f, "} UnicodeGCEnum;\n\n"); + + dump_name_table(f, "unicode_gc_name_table", + unicode_gc_name, GCAT_COUNT, + unicode_gc_short_name); + + + dbuf_init(dbuf); +#ifdef DUMP_TABLE_SIZE + cw_count = 0; + for(i = 0; i < 4; i++) + cw_len_count[i] = 0; +#endif + for(i = 0; i <= CHARCODE_MAX;) { + v = unicode_db[i].general_category; + j = i + 1; + while (j <= CHARCODE_MAX && unicode_db[j].general_category == v) + j++; + n = j - i; + /* compress Lu/Ll runs */ + if (v == GCAT_Lu) { + n1 = 1; + while ((i + n1) <= CHARCODE_MAX && unicode_db[i + n1].general_category == (v + (n1 & 1))) { + n1++; + } + if (n1 > n) { + v = 31; + n = n1; + } + } + // printf("%05x %05x %d\n", i, n, v); + n--; +#ifdef DUMP_TABLE_SIZE + cw_count++; + cw_start = dbuf->size; +#endif + if (n < 7) { + dbuf_putc(dbuf, (n << 5) | v); + } else if (n < 7 + 128) { + n1 = n - 7; + assert(n1 < 128); + dbuf_putc(dbuf, (0xf << 5) | v); + dbuf_putc(dbuf, n1); + } else if (n < 7 + 128 + (1 << 14)) { + n1 = n - (7 + 128); + assert(n1 < (1 << 14)); + dbuf_putc(dbuf, (0xf << 5) | v); + dbuf_putc(dbuf, (n1 >> 8) + 128); + dbuf_putc(dbuf, n1); + } else { + n1 = n - (7 + 128 + (1 << 14)); + assert(n1 < (1 << 22)); + dbuf_putc(dbuf, (0xf << 5) | v); + dbuf_putc(dbuf, (n1 >> 16) + 128 + 64); + dbuf_putc(dbuf, n1 >> 8); + dbuf_putc(dbuf, n1); + } +#ifdef DUMP_TABLE_SIZE + cw_len_count[dbuf->size - cw_start - 1]++; +#endif + i += n + 1; + } +#ifdef DUMP_TABLE_SIZE + printf("general category: %d entries [", cw_count); + for(i = 0; i < 4; i++) + printf(" %d", cw_len_count[i]); + printf(" ], length=%d bytes\n", (int)dbuf->size); +#endif + + dump_byte_table(f, "unicode_gc_table", dbuf->buf, dbuf->size); + + dbuf_free(dbuf); +} + +void build_script_table(FILE *f) +{ + int i, v, j, n, n1, type; + DynBuf dbuf_s, *dbuf = &dbuf_s; +#ifdef DUMP_TABLE_SIZE + int cw_count, cw_len_count[4], cw_start; +#endif + + fprintf(f, "typedef enum {\n"); + for(i = 0; i < SCRIPT_COUNT; i++) + fprintf(f, " UNICODE_SCRIPT_%s,\n", unicode_script_name[i]); + fprintf(f, " UNICODE_SCRIPT_COUNT,\n"); + fprintf(f, "} UnicodeScriptEnum;\n\n"); + + dump_name_table(f, "unicode_script_name_table", + unicode_script_name, SCRIPT_COUNT, + unicode_script_short_name); + + dbuf_init(dbuf); +#ifdef DUMP_TABLE_SIZE + cw_count = 0; + for(i = 0; i < 4; i++) + cw_len_count[i] = 0; +#endif + for(i = 0; i <= CHARCODE_MAX;) { + v = unicode_db[i].script; + j = i + 1; + while (j <= CHARCODE_MAX && unicode_db[j].script == v) + j++; + n = j - i; + if (v == 0 && j == (CHARCODE_MAX + 1)) + break; + // printf("%05x %05x %d\n", i, n, v); + n--; +#ifdef DUMP_TABLE_SIZE + cw_count++; + cw_start = dbuf->size; +#endif + if (v == 0) + type = 0; + else + type = 1; + if (n < 96) { + dbuf_putc(dbuf, n | (type << 7)); + } else if (n < 96 + (1 << 12)) { + n1 = n - 96; + assert(n1 < (1 << 12)); + dbuf_putc(dbuf, ((n1 >> 8) + 96) | (type << 7)); + dbuf_putc(dbuf, n1); + } else { + n1 = n - (96 + (1 << 12)); + assert(n1 < (1 << 20)); + dbuf_putc(dbuf, ((n1 >> 16) + 112) | (type << 7)); + dbuf_putc(dbuf, n1 >> 8); + dbuf_putc(dbuf, n1); + } + if (type != 0) + dbuf_putc(dbuf, v); + +#ifdef DUMP_TABLE_SIZE + cw_len_count[dbuf->size - cw_start - 1]++; +#endif + i += n + 1; + } +#ifdef DUMP_TABLE_SIZE + printf("script: %d entries [", cw_count); + for(i = 0; i < 4; i++) + printf(" %d", cw_len_count[i]); + printf(" ], length=%d bytes\n", (int)dbuf->size); +#endif + + dump_byte_table(f, "unicode_script_table", dbuf->buf, dbuf->size); + + dbuf_free(dbuf); +} + +void build_script_ext_table(FILE *f) +{ + int i, j, n, n1, script_ext_len; + DynBuf dbuf_s, *dbuf = &dbuf_s; +#if defined(DUMP_TABLE_SIZE) + int cw_count = 0; +#endif + + dbuf_init(dbuf); + for(i = 0; i <= CHARCODE_MAX;) { + script_ext_len = unicode_db[i].script_ext_len; + j = i + 1; + while (j <= CHARCODE_MAX && + unicode_db[j].script_ext_len == script_ext_len && + !memcmp(unicode_db[j].script_ext, unicode_db[i].script_ext, + script_ext_len)) { + j++; + } + n = j - i; +#if defined(DUMP_TABLE_SIZE) + cw_count++; +#endif + n--; + if (n < 128) { + dbuf_putc(dbuf, n); + } else if (n < 128 + (1 << 14)) { + n1 = n - 128; + assert(n1 < (1 << 14)); + dbuf_putc(dbuf, (n1 >> 8) + 128); + dbuf_putc(dbuf, n1); + } else { + n1 = n - (128 + (1 << 14)); + assert(n1 < (1 << 22)); + dbuf_putc(dbuf, (n1 >> 16) + 128 + 64); + dbuf_putc(dbuf, n1 >> 8); + dbuf_putc(dbuf, n1); + } + dbuf_putc(dbuf, script_ext_len); + for(j = 0; j < script_ext_len; j++) + dbuf_putc(dbuf, unicode_db[i].script_ext[j]); + i += n + 1; + } +#ifdef DUMP_TABLE_SIZE + printf("script_ext: %d entries", cw_count); + printf(", length=%d bytes\n", (int)dbuf->size); +#endif + + dump_byte_table(f, "unicode_script_ext_table", dbuf->buf, dbuf->size); + + dbuf_free(dbuf); +} + +/* the following properties are synthetized so no table is necessary */ +#define PROP_TABLE_COUNT PROP_ASCII + +void build_prop_list_table(FILE *f) +{ + int i; + + for(i = 0; i < PROP_TABLE_COUNT; i++) { + if (i == PROP_ID_Start || + i == PROP_Case_Ignorable || + i == PROP_ID_Continue1) { + /* already generated */ + } else { + build_prop_table(f, unicode_prop_name[i], i, FALSE); + } + } + + fprintf(f, "typedef enum {\n"); + for(i = 0; i < PROP_COUNT; i++) + fprintf(f, " UNICODE_PROP_%s,\n", unicode_prop_name[i]); + fprintf(f, " UNICODE_PROP_COUNT,\n"); + fprintf(f, "} UnicodePropertyEnum;\n\n"); + + i = PROP_ASCII_Hex_Digit; + dump_name_table(f, "unicode_prop_name_table", + unicode_prop_name + i, PROP_XID_Start - i + 1, + unicode_prop_short_name + i); + + fprintf(f, "static const uint8_t * const unicode_prop_table[] = {\n"); + for(i = 0; i < PROP_TABLE_COUNT; i++) { + fprintf(f, " unicode_prop_%s_table,\n", unicode_prop_name[i]); + } + fprintf(f, "};\n\n"); + + fprintf(f, "static const uint16_t unicode_prop_len_table[] = {\n"); + for(i = 0; i < PROP_TABLE_COUNT; i++) { + fprintf(f, " countof(unicode_prop_%s_table),\n", unicode_prop_name[i]); + } + fprintf(f, "};\n\n"); +} + +static BOOL is_emoji_hair_color(uint32_t c) +{ + return (c >= 0x1F9B0 && c <= 0x1F9B3); +} + +#define EMOJI_MOD_NONE 0 +#define EMOJI_MOD_TYPE1 1 +#define EMOJI_MOD_TYPE2 2 +#define EMOJI_MOD_TYPE2D 3 + +static BOOL mark_zwj_string(REStringList *sl, uint32_t *buf, int len, int mod_type, int *mod_pos, + int hc_pos, BOOL mark_flag) +{ + REString *p; + int i, n_mod, i0, i1, hc_count, j; + +#if 0 + if (mark_flag) + printf("mod_type=%d\n", mod_type); +#endif + + switch(mod_type) { + case EMOJI_MOD_NONE: + n_mod = 1; + break; + case EMOJI_MOD_TYPE1: + n_mod = 5; + break; + case EMOJI_MOD_TYPE2: + n_mod = 25; + break; + case EMOJI_MOD_TYPE2D: + n_mod = 20; + break; + default: + assert(0); + } + if (hc_pos >= 0) + hc_count = 4; + else + hc_count = 1; + /* check that all the related strings are present */ + for(j = 0; j < hc_count; j++) { + for(i = 0; i < n_mod; i++) { + switch(mod_type) { + case EMOJI_MOD_NONE: + break; + case EMOJI_MOD_TYPE1: + buf[mod_pos[0]] = 0x1f3fb + i; + break; + case EMOJI_MOD_TYPE2: + case EMOJI_MOD_TYPE2D: + i0 = i / 5; + i1 = i % 5; + /* avoid identical values */ + if (mod_type == EMOJI_MOD_TYPE2D && i0 >= i1) + i0++; + buf[mod_pos[0]] = 0x1f3fb + i0; + buf[mod_pos[1]] = 0x1f3fb + i1; + break; + default: + assert(0); + } + + if (hc_pos >= 0) + buf[hc_pos] = 0x1F9B0 + j; + + p = re_string_find(sl, len, buf, FALSE); + if (!p) + return FALSE; + if (mark_flag) + p->flags |= 1; + } + } + return TRUE; +} + +static void zwj_encode_string(DynBuf *dbuf, const uint32_t *buf, int len, int mod_type, int *mod_pos, + int hc_pos) +{ + int i, j; + int c, code; + uint32_t buf1[SEQ_MAX_LEN]; + + j = 0; + for(i = 0; i < len;) { + c = buf[i++]; + if (c >= 0x2000 && c <= 0x2fff) { + code = c - 0x2000; + } else if (c >= 0x1f000 && c <= 0x1ffff) { + code = c - 0x1f000 + 0x1000; + } else { + assert(0); + } + if (i < len && is_emoji_modifier(buf[i])) { + /* modifier */ + code |= (mod_type << 13); + i++; + } + if (i < len && buf[i] == 0xfe0f) { + /* presentation selector present */ + code |= 0x8000; + i++; + } + if (i < len) { + /* zero width join */ + assert(buf[i] == 0x200d); + i++; + } + buf1[j++] = code; + } + dbuf_putc(dbuf, j); + for(i = 0; i < j; i++) { + dbuf_putc(dbuf, buf1[i]); + dbuf_putc(dbuf, buf1[i] >> 8); + } +} + +static void build_rgi_emoji_zwj_sequence(FILE *f, REStringList *sl) +{ + int mod_pos[2], mod_count, hair_color_pos, j, h; + REString *p; + uint32_t buf[SEQ_MAX_LEN]; + DynBuf dbuf; + +#if 0 + { + for(h = 0; h < sl->hash_size; h++) { + for(p = sl->hash_table[h]; p != NULL; p = p->next) { + for(j = 0; j < p->len; j++) + printf(" %04x", p->buf[j]); + printf("\n"); + } + } + exit(0); + } +#endif + // printf("rgi_emoji_zwj_sequence: n=%d\n", sl->n_strings); + + dbuf_init(&dbuf); + + /* avoid duplicating strings with emoji modifiers or hair colors */ + for(h = 0; h < sl->hash_size; h++) { + for(p = sl->hash_table[h]; p != NULL; p = p->next) { + if (p->flags) /* already examined */ + continue; + mod_count = 0; + hair_color_pos = -1; + for(j = 0; j < p->len; j++) { + if (is_emoji_modifier(p->buf[j])) { + assert(mod_count < 2); + mod_pos[mod_count++] = j; + } else if (is_emoji_hair_color(p->buf[j])) { + hair_color_pos = j; + } + buf[j] = p->buf[j]; + } + + if (mod_count != 0 || hair_color_pos >= 0) { + int mod_type; + if (mod_count == 0) + mod_type = EMOJI_MOD_NONE; + else if (mod_count == 1) + mod_type = EMOJI_MOD_TYPE1; + else + mod_type = EMOJI_MOD_TYPE2; + + if (mark_zwj_string(sl, buf, p->len, mod_type, mod_pos, hair_color_pos, FALSE)) { + mark_zwj_string(sl, buf, p->len, mod_type, mod_pos, hair_color_pos, TRUE); + } else if (mod_type == EMOJI_MOD_TYPE2) { + mod_type = EMOJI_MOD_TYPE2D; + if (mark_zwj_string(sl, buf, p->len, mod_type, mod_pos, hair_color_pos, FALSE)) { + mark_zwj_string(sl, buf, p->len, mod_type, mod_pos, hair_color_pos, TRUE); + } else { + dump_str("not_found", (int *)p->buf, p->len); + goto keep; + } + } + if (hair_color_pos >= 0) + buf[hair_color_pos] = 0x1f9b0; + /* encode the string */ + zwj_encode_string(&dbuf, buf, p->len, mod_type, mod_pos, hair_color_pos); + } else { + keep: + zwj_encode_string(&dbuf, buf, p->len, EMOJI_MOD_NONE, NULL, -1); + } + } + } + + /* Encode */ + dump_byte_table(f, "unicode_rgi_emoji_zwj_sequence", dbuf.buf, dbuf.size); + + dbuf_free(&dbuf); +} + +void build_sequence_prop_list_table(FILE *f) +{ + int i; + fprintf(f, "typedef enum {\n"); + for(i = 0; i < SEQUENCE_PROP_COUNT; i++) + fprintf(f, " UNICODE_SEQUENCE_PROP_%s,\n", unicode_sequence_prop_name[i]); + fprintf(f, " UNICODE_SEQUENCE_PROP_COUNT,\n"); + fprintf(f, "} UnicodeSequencePropertyEnum;\n\n"); + + dump_name_table(f, "unicode_sequence_prop_name_table", + unicode_sequence_prop_name, SEQUENCE_PROP_COUNT, NULL); + + dump_byte_table(f, "unicode_rgi_emoji_tag_sequence", rgi_emoji_tag_sequence.buf, rgi_emoji_tag_sequence.size); + + build_rgi_emoji_zwj_sequence(f, &rgi_emoji_zwj_sequence); +} + +#ifdef USE_TEST +int check_conv(uint32_t *res, uint32_t c, int conv_type) +{ + return lre_case_conv(res, c, conv_type); +} + +void check_case_conv(void) +{ + CCInfo *tab = unicode_db; + uint32_t res[3]; + int l, error; + CCInfo ci_s, *ci1, *ci = &ci_s; + int code; + + for(code = 0; code <= CHARCODE_MAX; code++) { + ci1 = &tab[code]; + *ci = *ci1; + if (ci->l_len == 0) { + ci->l_len = 1; + ci->l_data[0] = code; + } + if (ci->u_len == 0) { + ci->u_len = 1; + ci->u_data[0] = code; + } + if (ci->f_len == 0) { + ci->f_len = 1; + ci->f_data[0] = code; + } + + error = 0; + l = check_conv(res, code, 0); + if (l != ci->u_len || tabcmp((int *)res, ci->u_data, l)) { + printf("ERROR: L\n"); + error++; + } + l = check_conv(res, code, 1); + if (l != ci->l_len || tabcmp((int *)res, ci->l_data, l)) { + printf("ERROR: U\n"); + error++; + } + l = check_conv(res, code, 2); + if (l != ci->f_len || tabcmp((int *)res, ci->f_data, l)) { + printf("ERROR: F\n"); + error++; + } + if (error) { + dump_cc_info(ci, code); + exit(1); + } + } +} + +#ifdef PROFILE +static int64_t get_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec; +} +#endif + + +void check_flags(void) +{ + int c; + BOOL flag_ref, flag; + for(c = 0; c <= CHARCODE_MAX; c++) { + flag_ref = get_prop(c, PROP_Cased); + flag = !!lre_is_cased(c); + if (flag != flag_ref) { + printf("ERROR: c=%05x cased=%d ref=%d\n", + c, flag, flag_ref); + exit(1); + } + + flag_ref = get_prop(c, PROP_Case_Ignorable); + flag = !!lre_is_case_ignorable(c); + if (flag != flag_ref) { + printf("ERROR: c=%05x case_ignorable=%d ref=%d\n", + c, flag, flag_ref); + exit(1); + } + + flag_ref = get_prop(c, PROP_ID_Start); + flag = !!lre_is_id_start(c); + if (flag != flag_ref) { + printf("ERROR: c=%05x id_start=%d ref=%d\n", + c, flag, flag_ref); + exit(1); + } + + flag_ref = get_prop(c, PROP_ID_Continue); + flag = !!lre_is_id_continue(c); + if (flag != flag_ref) { + printf("ERROR: c=%05x id_cont=%d ref=%d\n", + c, flag, flag_ref); + exit(1); + } + } +#ifdef PROFILE + { + int64_t ti, count; + ti = get_time_ns(); + count = 0; + for(c = 0x20; c <= 0xffff; c++) { + flag_ref = get_prop(c, PROP_ID_Start); + flag = !!lre_is_id_start(c); + assert(flag == flag_ref); + count++; + } + ti = get_time_ns() - ti; + printf("flags time=%0.1f ns/char\n", + (double)ti / count); + } +#endif +} + +#endif + +#define CC_BLOCK_LEN 32 + +void build_cc_table(FILE *f) +{ + // Compress combining class table + // see: https://www.unicode.org/reports/tr44/#Canonical_Combining_Class_Values + int i, cc, n, type, n1, block_end_pos; + DynBuf dbuf_s, *dbuf = &dbuf_s; + DynBuf dbuf1_s, *dbuf1 = &dbuf1_s; +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + int cw_len_tab[3], cw_start, cc_table_len; +#endif + uint32_t v; + + dbuf_init(dbuf); + dbuf_init(dbuf1); +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + cc_table_len = 0; + for(i = 0; i < countof(cw_len_tab); i++) + cw_len_tab[i] = 0; +#endif + block_end_pos = CC_BLOCK_LEN; + for(i = 0; i <= CHARCODE_MAX;) { + cc = unicode_db[i].combining_class; + assert(cc <= 255); + /* check increasing values */ + n = 1; + while ((i + n) <= CHARCODE_MAX && + unicode_db[i + n].combining_class == (cc + n)) + n++; + if (n >= 2) { + type = 1; + } else { + type = 0; + n = 1; + while ((i + n) <= CHARCODE_MAX && + unicode_db[i + n].combining_class == cc) + n++; + } + /* no need to encode the last run */ + if (cc == 0 && (i + n - 1) == CHARCODE_MAX) + break; +#ifdef DUMP_CC_TABLE + printf("%05x %6d %d %d\n", i, n, type, cc); +#endif + if (type == 0) { + if (cc == 0) + type = 2; + else if (cc == 230) + type = 3; + } + n1 = n - 1; + + /* add an entry to the index if necessary */ + if (dbuf->size >= block_end_pos) { + v = i | ((dbuf->size - block_end_pos) << 21); + dbuf_putc(dbuf1, v); + dbuf_putc(dbuf1, v >> 8); + dbuf_putc(dbuf1, v >> 16); + block_end_pos += CC_BLOCK_LEN; + } +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + cw_start = dbuf->size; +#endif + /* Compressed run length encoding: + - 2 high order bits are combining class type + - 0:0, 1:230, 2:extra byte linear progression, 3:extra byte + - 00..2F: range length (add 1) + - 30..37: 3-bit range-length + 1 extra byte + - 38..3F: 3-bit range-length + 2 extra byte + */ + if (n1 < 48) { + dbuf_putc(dbuf, n1 | (type << 6)); + } else if (n1 < 48 + (1 << 11)) { + n1 -= 48; + dbuf_putc(dbuf, ((n1 >> 8) + 48) | (type << 6)); + dbuf_putc(dbuf, n1); + } else { + n1 -= 48 + (1 << 11); + assert(n1 < (1 << 20)); + dbuf_putc(dbuf, ((n1 >> 16) + 56) | (type << 6)); + dbuf_putc(dbuf, n1 >> 8); + dbuf_putc(dbuf, n1); + } +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + cw_len_tab[dbuf->size - cw_start - 1]++; + cc_table_len++; +#endif + if (type == 0 || type == 1) + dbuf_putc(dbuf, cc); + i += n; + } + + /* last index entry */ + v = i; + dbuf_putc(dbuf1, v); + dbuf_putc(dbuf1, v >> 8); + dbuf_putc(dbuf1, v >> 16); + + dump_byte_table(f, "unicode_cc_table", dbuf->buf, dbuf->size); + dump_index_table(f, "unicode_cc_index", dbuf1->buf, dbuf1->size); + +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + printf("CC table: size=%d (%d entries) [", + (int)(dbuf->size + dbuf1->size), + cc_table_len); + for(i = 0; i < countof(cw_len_tab); i++) + printf(" %d", cw_len_tab[i]); + printf(" ]\n"); +#endif + dbuf_free(dbuf); + dbuf_free(dbuf1); +} + +/* maximum length of decomposition: 18 chars (1), then 8 */ +#ifndef USE_TEST +typedef enum { + DECOMP_TYPE_C1, /* 16 bit char */ + DECOMP_TYPE_L1, /* 16 bit char table */ + DECOMP_TYPE_L2, + DECOMP_TYPE_L3, + DECOMP_TYPE_L4, + DECOMP_TYPE_L5, /* XXX: not used */ + DECOMP_TYPE_L6, /* XXX: could remove */ + DECOMP_TYPE_L7, /* XXX: could remove */ + DECOMP_TYPE_LL1, /* 18 bit char table */ + DECOMP_TYPE_LL2, + DECOMP_TYPE_S1, /* 8 bit char table */ + DECOMP_TYPE_S2, + DECOMP_TYPE_S3, + DECOMP_TYPE_S4, + DECOMP_TYPE_S5, + DECOMP_TYPE_I1, /* increment 16 bit char value */ + DECOMP_TYPE_I2_0, + DECOMP_TYPE_I2_1, + DECOMP_TYPE_I3_1, + DECOMP_TYPE_I3_2, + DECOMP_TYPE_I4_1, + DECOMP_TYPE_I4_2, + DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */ + DECOMP_TYPE_B2, + DECOMP_TYPE_B3, + DECOMP_TYPE_B4, + DECOMP_TYPE_B5, + DECOMP_TYPE_B6, + DECOMP_TYPE_B7, + DECOMP_TYPE_B8, + DECOMP_TYPE_B18, + DECOMP_TYPE_LS2, + DECOMP_TYPE_PAT3, + DECOMP_TYPE_S2_UL, + DECOMP_TYPE_LS2_UL, +} DecompTypeEnum; +#endif + +const char *decomp_type_str[] = { + "C1", + "L1", + "L2", + "L3", + "L4", + "L5", + "L6", + "L7", + "LL1", + "LL2", + "S1", + "S2", + "S3", + "S4", + "S5", + "I1", + "I2_0", + "I2_1", + "I3_1", + "I3_2", + "I4_1", + "I4_2", + "B1", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B18", + "LS2", + "PAT3", + "S2_UL", + "LS2_UL", +}; + +const int decomp_incr_tab[4][4] = { + { DECOMP_TYPE_I1, 0, -1 }, + { DECOMP_TYPE_I2_0, 0, 1, -1 }, + { DECOMP_TYPE_I3_1, 1, 2, -1 }, + { DECOMP_TYPE_I4_1, 1, 2, -1 }, +}; + +/* + entry size: + type bits + code 18 + len 7 + compat 1 + type 5 + index 16 + total 47 +*/ + +typedef struct { + int code; + uint8_t len; + uint8_t type; + uint8_t c_len; + uint16_t c_min; + uint16_t data_index; + int cost; /* size in bytes from this entry to the end */ +} DecompEntry; + +int get_decomp_run_size(const DecompEntry *de) +{ + int s; + s = 6; + if (de->type <= DECOMP_TYPE_C1) { + /* nothing more */ + } else if (de->type <= DECOMP_TYPE_L7) { + s += de->len * de->c_len * 2; + } else if (de->type <= DECOMP_TYPE_LL2) { + /* 18 bits per char */ + s += (de->len * de->c_len * 18 + 7) / 8; + } else if (de->type <= DECOMP_TYPE_S5) { + s += de->len * de->c_len; + } else if (de->type <= DECOMP_TYPE_I4_2) { + s += de->c_len * 2; + } else if (de->type <= DECOMP_TYPE_B18) { + s += 2 + de->len * de->c_len; + } else if (de->type <= DECOMP_TYPE_LS2) { + s += de->len * 3; + } else if (de->type <= DECOMP_TYPE_PAT3) { + s += 4 + de->len * 2; + } else if (de->type <= DECOMP_TYPE_S2_UL) { + s += de->len; + } else if (de->type <= DECOMP_TYPE_LS2_UL) { + s += (de->len / 2) * 3; + } else { + abort(); + } + return s; +} + +static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 }; + +/* return -1 if not found */ +int get_short_code(int c) +{ + int i; + if (c < 0x80) { + return c; + } else if (c >= 0x300 && c < 0x350) { + return c - 0x300 + 0x80; + } else { + for(i = 0; i < countof(unicode_short_table); i++) { + if (c == unicode_short_table[i]) + return i + 0x80 + 0x50; + } + return -1; + } +} + +static BOOL is_short(int code) +{ + return get_short_code(code) >= 0; +} + +static BOOL is_short_tab(const int *tab, int len) +{ + int i; + for(i = 0; i < len; i++) { + if (!is_short(tab[i])) + return FALSE; + } + return TRUE; +} + +static BOOL is_16bit(const int *tab, int len) +{ + int i; + for(i = 0; i < len; i++) { + if (tab[i] > 0xffff) + return FALSE; + } + return TRUE; +} + +static uint32_t to_lower_simple(uint32_t c) +{ + /* Latin1 and Cyrillic */ + if (c < 0x100 || (c >= 0x410 && c <= 0x42f)) + c += 0x20; + else + c++; + return c; +} + +/* select best encoding with dynamic programming */ +void find_decomp_run(DecompEntry *tab_de, int i) +{ + DecompEntry de_s, *de = &de_s; + CCInfo *ci, *ci1, *ci2; + int l, j, n, len_max; + + ci = &unicode_db[i]; + l = ci->decomp_len; + if (l == 0) { + tab_de[i].cost = tab_de[i + 1].cost; + return; + } + + /* the offset for the compose table has only 6 bits, so we must + limit if it can be used by the compose table */ + if (!ci->is_compat && !ci->is_excluded && l == 2) + len_max = 64; + else + len_max = 127; + + tab_de[i].cost = 0x7fffffff; + + if (!is_16bit(ci->decomp_data, l)) { + assert(l <= 2); + + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_LL1 + l - 1; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + /* Note: we accept a hole */ + if (!(ci1->decomp_len == 0 || + (ci1->decomp_len == l && + ci1->is_compat == ci->is_compat))) + break; + n++; + } + return; + } + + if (l <= 7) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + if (l == 1 && n == 1) { + de->type = DECOMP_TYPE_C1; + } else { + assert(l <= 8); + de->type = DECOMP_TYPE_L1 + l - 1; + } + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + /* Note: we accept a hole */ + if (!(ci1->decomp_len == 0 || + (ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + is_16bit(ci1->decomp_data, l)))) + break; + n++; + } + } + + if (l <= 8 || l == 18) { + int c_min, c_max, c; + c_min = c_max = -1; + n = 1; + for(;;) { + ci1 = &unicode_db[i + n - 1]; + for(j = 0; j < l; j++) { + c = ci1->decomp_data[j]; + if (c == 0x20) { + /* we accept space for Arabic */ + } else if (c_min == -1) { + c_min = c_max = c; + } else { + c_min = min_int(c_min, c); + c_max = max_int(c_max, c); + } + } + if ((c_max - c_min) > 254) + break; + de->code = i; + de->len = n; + if (l == 18) + de->type = DECOMP_TYPE_B18; + else + de->type = DECOMP_TYPE_B1 + l - 1; + de->c_len = l; + de->c_min = c_min; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == l && + ci1->is_compat == ci->is_compat)) + break; + n++; + } + } + + /* find an ascii run */ + if (l <= 5 && is_short_tab(ci->decomp_data, l)) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_S1 + l - 1; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + /* Note: we accept a hole */ + if (!(ci1->decomp_len == 0 || + (ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + is_short_tab(ci1->decomp_data, l)))) + break; + n++; + } + } + + /* check if a single char is increasing */ + if (l <= 4) { + int idx1, idx; + + for(idx1 = 1; (idx = decomp_incr_tab[l - 1][idx1]) >= 0; idx1++) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = decomp_incr_tab[l - 1][0] + idx1 - 1; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == l && + ci1->is_compat == ci->is_compat)) + goto next1; + for(j = 0; j < l; j++) { + if (j == idx) { + if (ci1->decomp_data[j] != ci->decomp_data[j] + n) + goto next1; + } else { + if (ci1->decomp_data[j] != ci->decomp_data[j]) + goto next1; + } + } + n++; + } + next1: ; + } + } + + if (l == 3) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_PAT3; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + ci1->decomp_data[1] <= 0xffff && + ci1->decomp_data[0] == ci->decomp_data[0] && + ci1->decomp_data[l - 1] == ci->decomp_data[l - 1])) + break; + n++; + } + } + + if (l == 2 && is_short(ci->decomp_data[1])) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_LS2; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == 0 || + (ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + ci1->decomp_data[0] <= 0xffff && + is_short(ci1->decomp_data[1])))) + break; + n++; + } + } + + if (l == 2) { + BOOL is_16bit; + + n = 0; + is_16bit = FALSE; + for(;;) { + if (!((i + n + 1) <= CHARCODE_MAX && n + 2 <= len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + is_short(ci1->decomp_data[1]))) + break; + if (!is_16bit && !is_short(ci1->decomp_data[0])) + is_16bit = TRUE; + ci2 = &unicode_db[i + n + 1]; + if (!(ci2->decomp_len == l && + ci2->is_compat == ci->is_compat && + ci2->decomp_data[0] == to_lower_simple(ci1->decomp_data[0]) && + ci2->decomp_data[1] == ci1->decomp_data[1])) + break; + n += 2; + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_S2_UL + is_16bit; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + } + } +} + +void put16(uint8_t *data_buf, int *pidx, uint16_t c) +{ + int idx; + idx = *pidx; + data_buf[idx++] = c; + data_buf[idx++] = c >> 8; + *pidx = idx; +} + +void add_decomp_data(uint8_t *data_buf, int *pidx, DecompEntry *de) +{ + int i, j, idx, c; + CCInfo *ci; + + idx = *pidx; + de->data_index = idx; + if (de->type <= DECOMP_TYPE_C1) { + ci = &unicode_db[de->code]; + assert(ci->decomp_len == 1); + de->data_index = ci->decomp_data[0]; + } else if (de->type <= DECOMP_TYPE_L7) { + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + for(j = 0; j < de->c_len; j++) { + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[j]; + put16(data_buf, &idx, c); + } + } + } else if (de->type <= DECOMP_TYPE_LL2) { + int n, p, k; + n = (de->len * de->c_len * 18 + 7) / 8; + p = de->len * de->c_len * 2; + memset(data_buf + idx, 0, n); + k = 0; + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + for(j = 0; j < de->c_len; j++) { + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[j]; + data_buf[idx + k * 2] = c; + data_buf[idx + k * 2 + 1] = c >> 8; + data_buf[idx + p + (k / 4)] |= (c >> 16) << ((k % 4) * 2); + k++; + } + } + idx += n; + } else if (de->type <= DECOMP_TYPE_S5) { + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + for(j = 0; j < de->c_len; j++) { + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[j]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + } + } + } else if (de->type <= DECOMP_TYPE_I4_2) { + ci = &unicode_db[de->code]; + assert(ci->decomp_len == de->c_len); + for(j = 0; j < de->c_len; j++) + put16(data_buf, &idx, ci->decomp_data[j]); + } else if (de->type <= DECOMP_TYPE_B18) { + c = de->c_min; + data_buf[idx++] = c; + data_buf[idx++] = c >> 8; + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + for(j = 0; j < de->c_len; j++) { + assert(ci->decomp_len == de->c_len); + c = ci->decomp_data[j]; + if (c == 0x20) { + c = 0xff; + } else { + c -= de->c_min; + assert((uint32_t)c <= 254); + } + data_buf[idx++] = c; + } + } + } else if (de->type <= DECOMP_TYPE_LS2) { + assert(de->c_len == 2); + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[0]; + put16(data_buf, &idx, c); + + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[1]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + } + } else if (de->type <= DECOMP_TYPE_PAT3) { + ci = &unicode_db[de->code]; + assert(ci->decomp_len == 3); + put16(data_buf, &idx, ci->decomp_data[0]); + put16(data_buf, &idx, ci->decomp_data[2]); + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + assert(ci->decomp_len == 3); + put16(data_buf, &idx, ci->decomp_data[1]); + } + } else if (de->type <= DECOMP_TYPE_S2_UL) { + for(i = 0; i < de->len; i += 2) { + ci = &unicode_db[de->code + i]; + c = ci->decomp_data[0]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + c = ci->decomp_data[1]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + } + } else if (de->type <= DECOMP_TYPE_LS2_UL) { + for(i = 0; i < de->len; i += 2) { + ci = &unicode_db[de->code + i]; + c = ci->decomp_data[0]; + put16(data_buf, &idx, c); + c = ci->decomp_data[1]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + } + } else { + abort(); + } + *pidx = idx; +} + +#if 0 +void dump_large_char(void) +{ + int i, j; + for(i = 0; i <= CHARCODE_MAX; i++) { + CCInfo *ci = &unicode_db[i]; + for(j = 0; j < ci->decomp_len; j++) { + if (ci->decomp_data[j] > 0xffff) + printf("%05x\n", ci->decomp_data[j]); + } + } +} +#endif + +void build_compose_table(FILE *f, const DecompEntry *tab_de); + +void build_decompose_table(FILE *f) +{ + int i, array_len, code_max, data_len, count; + DecompEntry *tab_de, de_s, *de = &de_s; + uint8_t *data_buf; + + code_max = CHARCODE_MAX; + + tab_de = mallocz((code_max + 2) * sizeof(*tab_de)); + + for(i = code_max; i >= 0; i--) { + find_decomp_run(tab_de, i); + } + + /* build the data buffer */ + data_buf = malloc(100000); + data_len = 0; + array_len = 0; + for(i = 0; i <= code_max; i++) { + de = &tab_de[i]; + if (de->len != 0) { + add_decomp_data(data_buf, &data_len, de); + i += de->len - 1; + array_len++; + } + } + +#ifdef DUMP_DECOMP_TABLE + /* dump */ + { + int size, size1; + + printf("START LEN TYPE L C SIZE\n"); + size = 0; + for(i = 0; i <= code_max; i++) { + de = &tab_de[i]; + if (de->len != 0) { + size1 = get_decomp_run_size(de); + printf("%05x %3d %6s %2d %1d %4d\n", i, de->len, + decomp_type_str[de->type], de->c_len, + unicode_db[i].is_compat, size1); + i += de->len - 1; + size += size1; + } + } + + printf("array_len=%d estimated size=%d bytes actual=%d bytes\n", + array_len, size, array_len * 6 + data_len); + } +#endif + + total_tables++; + total_table_bytes += array_len * sizeof(uint32_t); + fprintf(f, "static const uint32_t unicode_decomp_table1[%d] = {", array_len); + count = 0; + for(i = 0; i <= code_max; i++) { + de = &tab_de[i]; + if (de->len != 0) { + uint32_t v; + if (count++ % 4 == 0) + fprintf(f, "\n "); + v = (de->code << (32 - 18)) | + (de->len << (32 - 18 - 7)) | + (de->type << (32 - 18 - 7 - 6)) | + unicode_db[de->code].is_compat; + fprintf(f, " 0x%08x,", v); + i += de->len - 1; + } + } + fprintf(f, "\n};\n\n"); + + total_tables++; + total_table_bytes += array_len * sizeof(uint16_t); + fprintf(f, "static const uint16_t unicode_decomp_table2[%d] = {", array_len); + count = 0; + for(i = 0; i <= code_max; i++) { + de = &tab_de[i]; + if (de->len != 0) { + if (count++ % 8 == 0) + fprintf(f, "\n "); + fprintf(f, " 0x%04x,", de->data_index); + i += de->len - 1; + } + } + fprintf(f, "\n};\n\n"); + + total_tables++; + total_table_bytes += data_len; + fprintf(f, "static const uint8_t unicode_decomp_data[%d] = {", data_len); + for(i = 0; i < data_len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + fprintf(f, " 0x%02x,", data_buf[i]); + } + fprintf(f, "\n};\n\n"); + + build_compose_table(f, tab_de); + + free(data_buf); + + free(tab_de); +} + +typedef struct { + uint32_t c[2]; + uint32_t p; +} ComposeEntry; + +#define COMPOSE_LEN_MAX 10000 + +static int ce_cmp(const void *p1, const void *p2) +{ + const ComposeEntry *ce1 = p1; + const ComposeEntry *ce2 = p2; + int i; + + for(i = 0; i < 2; i++) { + if (ce1->c[i] < ce2->c[i]) + return -1; + else if (ce1->c[i] > ce2->c[i]) + return 1; + } + return 0; +} + + +static int get_decomp_pos(const DecompEntry *tab_de, int c) +{ + int i, v, k; + const DecompEntry *de; + + k = 0; + for(i = 0; i <= CHARCODE_MAX; i++) { + de = &tab_de[i]; + if (de->len != 0) { + if (c >= de->code && c < de->code + de->len) { + v = c - de->code; + assert(v < 64); + v |= k << 6; + assert(v < 65536); + return v; + } + i += de->len - 1; + k++; + } + } + return -1; +} + +void build_compose_table(FILE *f, const DecompEntry *tab_de) +{ + int i, v, tab_ce_len; + ComposeEntry *ce, *tab_ce; + + tab_ce = malloc(sizeof(*tab_ce) * COMPOSE_LEN_MAX); + tab_ce_len = 0; + for(i = 0; i <= CHARCODE_MAX; i++) { + CCInfo *ci = &unicode_db[i]; + if (ci->decomp_len == 2 && !ci->is_compat && + !ci->is_excluded) { + assert(tab_ce_len < COMPOSE_LEN_MAX); + ce = &tab_ce[tab_ce_len++]; + ce->c[0] = ci->decomp_data[0]; + ce->c[1] = ci->decomp_data[1]; + ce->p = i; + } + } + qsort(tab_ce, tab_ce_len, sizeof(*tab_ce), ce_cmp); + +#if 0 + { + printf("tab_ce_len=%d\n", tab_ce_len); + for(i = 0; i < tab_ce_len; i++) { + ce = &tab_ce[i]; + printf("%05x %05x %05x\n", ce->c[0], ce->c[1], ce->p); + } + } +#endif + + total_tables++; + total_table_bytes += tab_ce_len * sizeof(uint16_t); + fprintf(f, "static const uint16_t unicode_comp_table[%u] = {", tab_ce_len); + for(i = 0; i < tab_ce_len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + v = get_decomp_pos(tab_de, tab_ce[i].p); + if (v < 0) { + printf("ERROR: entry for c=%04x not found\n", + tab_ce[i].p); + exit(1); + } + fprintf(f, " 0x%04x,", v); + } + fprintf(f, "\n};\n\n"); + + free(tab_ce); +} + +#ifdef USE_TEST +void check_decompose_table(void) +{ + int c; + CCInfo *ci; + int res[UNICODE_DECOMP_LEN_MAX], *ref; + int len, ref_len, is_compat; + + for(is_compat = 0; is_compat <= 1; is_compat++) { + for(c = 0; c < CHARCODE_MAX; c++) { + ci = &unicode_db[c]; + ref_len = ci->decomp_len; + ref = ci->decomp_data; + if (!is_compat && ci->is_compat) { + ref_len = 0; + } + len = unicode_decomp_char((uint32_t *)res, c, is_compat); + if (len != ref_len || + tabcmp(res, ref, ref_len) != 0) { + printf("ERROR c=%05x compat=%d\n", c, is_compat); + dump_str("res", res, len); + dump_str("ref", ref, ref_len); + exit(1); + } + } + } +} + +void check_compose_table(void) +{ + int i, p; + /* XXX: we don't test all the cases */ + + for(i = 0; i <= CHARCODE_MAX; i++) { + CCInfo *ci = &unicode_db[i]; + if (ci->decomp_len == 2 && !ci->is_compat && + !ci->is_excluded) { + p = unicode_compose_pair(ci->decomp_data[0], ci->decomp_data[1]); + if (p != i) { + printf("ERROR compose: c=%05x %05x -> %05x ref=%05x\n", + ci->decomp_data[0], ci->decomp_data[1], p, i); + exit(1); + } + } + } + + + +} + +#endif + + + +#ifdef USE_TEST + +void check_str(const char *msg, int num, const int *in_buf, int in_len, + const int *buf1, int len1, + const int *buf2, int len2) +{ + if (len1 != len2 || tabcmp(buf1, buf2, len1) != 0) { + printf("%d: ERROR %s:\n", num, msg); + dump_str(" in", in_buf, in_len); + dump_str("res", buf1, len1); + dump_str("ref", buf2, len2); + exit(1); + } +} + +void check_cc_table(void) +{ + int cc, cc_ref, c; + + for(c = 0; c <= CHARCODE_MAX; c++) { + cc_ref = unicode_db[c].combining_class; + cc = unicode_get_cc(c); + if (cc != cc_ref) { + printf("ERROR: c=%04x cc=%d cc_ref=%d\n", + c, cc, cc_ref); + exit(1); + } + } +#ifdef PROFILE + { + int64_t ti, count; + + ti = get_time_ns(); + count = 0; + /* only do it on meaningful chars */ + for(c = 0x20; c <= 0xffff; c++) { + cc_ref = unicode_db[c].combining_class; + cc = unicode_get_cc(c); + count++; + } + ti = get_time_ns() - ti; + printf("cc time=%0.1f ns/char\n", + (double)ti / count); + } +#endif +} + +void normalization_test(const char *filename) +{ + FILE *f; + char line[4096], *p; + int *in_str, *nfc_str, *nfd_str, *nfkc_str, *nfkd_str; + int in_len, nfc_len, nfd_len, nfkc_len, nfkd_len; + int *buf, buf_len, pos; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + pos = 0; + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + pos++; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@') + continue; + in_str = get_field_str(&in_len, p, 0); + nfc_str = get_field_str(&nfc_len, p, 1); + nfd_str = get_field_str(&nfd_len, p, 2); + nfkc_str = get_field_str(&nfkc_len, p, 3); + nfkd_str = get_field_str(&nfkd_len, p, 4); + + // dump_str("in", in_str, in_len); + + buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFD, NULL, NULL); + check_str("nfd", pos, in_str, in_len, buf, buf_len, nfd_str, nfd_len); + free(buf); + + buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFKD, NULL, NULL); + check_str("nfkd", pos, in_str, in_len, buf, buf_len, nfkd_str, nfkd_len); + free(buf); + + buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFC, NULL, NULL); + check_str("nfc", pos, in_str, in_len, buf, buf_len, nfc_str, nfc_len); + free(buf); + + buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFKC, NULL, NULL); + check_str("nfkc", pos, in_str, in_len, buf, buf_len, nfkc_str, nfkc_len); + free(buf); + + free(in_str); + free(nfc_str); + free(nfd_str); + free(nfkc_str); + free(nfkd_str); + } + fclose(f); +} +#endif + +int main(int argc, char *argv[]) +{ + const char *unicode_db_path, *outfilename; + char filename[1024]; + int arg = 1; + + if (arg >= argc || (!strcmp(argv[arg], "-h") || !strcmp(argv[arg], "--help"))) { + printf("usage: %s PATH [OUTPUT]\n" + " PATH path to the Unicode database directory\n" + " OUTPUT name of the output file. If omitted, a self test is performed\n" + " using the files from the Unicode library\n" + , argv[0]); + return 1; + } + unicode_db_path = argv[arg++]; + outfilename = NULL; + if (arg < argc) + outfilename = argv[arg++]; + + unicode_db = mallocz(sizeof(unicode_db[0]) * (CHARCODE_MAX + 1)); + re_string_list_init(&rgi_emoji_zwj_sequence); + dbuf_init(&rgi_emoji_tag_sequence); + + snprintf(filename, sizeof(filename), "%s/UnicodeData.txt", unicode_db_path); + + parse_unicode_data(filename); + + snprintf(filename, sizeof(filename), "%s/SpecialCasing.txt", unicode_db_path); + parse_special_casing(unicode_db, filename); + + snprintf(filename, sizeof(filename), "%s/CaseFolding.txt", unicode_db_path); + parse_case_folding(unicode_db, filename); + + snprintf(filename, sizeof(filename), "%s/CompositionExclusions.txt", unicode_db_path); + parse_composition_exclusions(filename); + + snprintf(filename, sizeof(filename), "%s/DerivedCoreProperties.txt", unicode_db_path); + parse_derived_core_properties(filename); + + snprintf(filename, sizeof(filename), "%s/DerivedNormalizationProps.txt", unicode_db_path); + parse_derived_norm_properties(filename); + + snprintf(filename, sizeof(filename), "%s/PropList.txt", unicode_db_path); + parse_prop_list(filename); + + snprintf(filename, sizeof(filename), "%s/Scripts.txt", unicode_db_path); + parse_scripts(filename); + + snprintf(filename, sizeof(filename), "%s/ScriptExtensions.txt", + unicode_db_path); + parse_script_extensions(filename); + + snprintf(filename, sizeof(filename), "%s/emoji-data.txt", + unicode_db_path); + parse_prop_list(filename); + + snprintf(filename, sizeof(filename), "%s/emoji-sequences.txt", + unicode_db_path); + parse_sequence_prop_list(filename); + + snprintf(filename, sizeof(filename), "%s/emoji-zwj-sequences.txt", + unicode_db_path); + parse_sequence_prop_list(filename); + + // dump_unicode_data(unicode_db); + build_conv_table(unicode_db); + +#ifdef DUMP_CASE_FOLDING_SPECIAL_CASES + dump_case_folding_special_cases(unicode_db); +#endif + + if (!outfilename) { +#ifdef USE_TEST + check_case_conv(); + check_flags(); + check_decompose_table(); + check_compose_table(); + check_cc_table(); + snprintf(filename, sizeof(filename), "%s/NormalizationTest.txt", unicode_db_path); + normalization_test(filename); +#else + fprintf(stderr, "Tests are not compiled\n"); + exit(1); +#endif + } else + { + FILE *fo = fopen(outfilename, "wb"); + + if (!fo) { + perror(outfilename); + exit(1); + } + fprintf(fo, + "/* Compressed unicode tables */\n" + "/* Automatically generated file - do not edit */\n" + "\n" + "#include \n" + "\n"); + dump_case_conv_table(fo); + compute_internal_props(); + build_flags_tables(fo); + fprintf(fo, "#ifdef CONFIG_ALL_UNICODE\n\n"); + build_cc_table(fo); + build_decompose_table(fo); + build_general_category_table(fo); + build_script_table(fo); + build_script_ext_table(fo); + build_prop_list_table(fo); + build_sequence_prop_list_table(fo); + fprintf(fo, "#endif /* CONFIG_ALL_UNICODE */\n"); + fprintf(fo, "/* %u tables / %u bytes, %u index / %u bytes */\n", + total_tables, total_table_bytes, total_index, total_index_bytes); + fclose(fo); + } + re_string_list_free(&rgi_emoji_zwj_sequence); + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source/unicode_gen_def.h b/NativeScript/napi/android/quickjs/source/unicode_gen_def.h new file mode 100755 index 000000000..95c369fab --- /dev/null +++ b/NativeScript/napi/android/quickjs/source/unicode_gen_def.h @@ -0,0 +1,318 @@ +#ifdef UNICODE_GENERAL_CATEGORY +DEF(Cn, "Unassigned") /* must be zero */ +DEF(Lu, "Uppercase_Letter") +DEF(Ll, "Lowercase_Letter") +DEF(Lt, "Titlecase_Letter") +DEF(Lm, "Modifier_Letter") +DEF(Lo, "Other_Letter") +DEF(Mn, "Nonspacing_Mark") +DEF(Mc, "Spacing_Mark") +DEF(Me, "Enclosing_Mark") +DEF(Nd, "Decimal_Number,digit") +DEF(Nl, "Letter_Number") +DEF(No, "Other_Number") +DEF(Sm, "Math_Symbol") +DEF(Sc, "Currency_Symbol") +DEF(Sk, "Modifier_Symbol") +DEF(So, "Other_Symbol") +DEF(Pc, "Connector_Punctuation") +DEF(Pd, "Dash_Punctuation") +DEF(Ps, "Open_Punctuation") +DEF(Pe, "Close_Punctuation") +DEF(Pi, "Initial_Punctuation") +DEF(Pf, "Final_Punctuation") +DEF(Po, "Other_Punctuation") +DEF(Zs, "Space_Separator") +DEF(Zl, "Line_Separator") +DEF(Zp, "Paragraph_Separator") +DEF(Cc, "Control,cntrl") +DEF(Cf, "Format") +DEF(Cs, "Surrogate") +DEF(Co, "Private_Use") +/* synthetic properties */ +DEF(LC, "Cased_Letter") +DEF(L, "Letter") +DEF(M, "Mark,Combining_Mark") +DEF(N, "Number") +DEF(S, "Symbol") +DEF(P, "Punctuation,punct") +DEF(Z, "Separator") +DEF(C, "Other") +#endif + +#ifdef UNICODE_SCRIPT +/* scripts aliases names in PropertyValueAliases.txt */ +DEF(Unknown, "Zzzz") +DEF(Adlam, "Adlm") +DEF(Ahom, "Ahom") +DEF(Anatolian_Hieroglyphs, "Hluw") +DEF(Arabic, "Arab") +DEF(Armenian, "Armn") +DEF(Avestan, "Avst") +DEF(Balinese, "Bali") +DEF(Bamum, "Bamu") +DEF(Bassa_Vah, "Bass") +DEF(Batak, "Batk") +DEF(Bengali, "Beng") +DEF(Bhaiksuki, "Bhks") +DEF(Bopomofo, "Bopo") +DEF(Brahmi, "Brah") +DEF(Braille, "Brai") +DEF(Buginese, "Bugi") +DEF(Buhid, "Buhd") +DEF(Canadian_Aboriginal, "Cans") +DEF(Carian, "Cari") +DEF(Caucasian_Albanian, "Aghb") +DEF(Chakma, "Cakm") +DEF(Cham, "Cham") +DEF(Cherokee, "Cher") +DEF(Chorasmian, "Chrs") +DEF(Common, "Zyyy") +DEF(Coptic, "Copt,Qaac") +DEF(Cuneiform, "Xsux") +DEF(Cypriot, "Cprt") +DEF(Cyrillic, "Cyrl") +DEF(Cypro_Minoan, "Cpmn") +DEF(Deseret, "Dsrt") +DEF(Devanagari, "Deva") +DEF(Dives_Akuru, "Diak") +DEF(Dogra, "Dogr") +DEF(Duployan, "Dupl") +DEF(Egyptian_Hieroglyphs, "Egyp") +DEF(Elbasan, "Elba") +DEF(Elymaic, "Elym") +DEF(Ethiopic, "Ethi") +DEF(Garay, "Gara") +DEF(Georgian, "Geor") +DEF(Glagolitic, "Glag") +DEF(Gothic, "Goth") +DEF(Grantha, "Gran") +DEF(Greek, "Grek") +DEF(Gujarati, "Gujr") +DEF(Gunjala_Gondi, "Gong") +DEF(Gurmukhi, "Guru") +DEF(Gurung_Khema, "Gukh") +DEF(Han, "Hani") +DEF(Hangul, "Hang") +DEF(Hanifi_Rohingya, "Rohg") +DEF(Hanunoo, "Hano") +DEF(Hatran, "Hatr") +DEF(Hebrew, "Hebr") +DEF(Hiragana, "Hira") +DEF(Imperial_Aramaic, "Armi") +DEF(Inherited, "Zinh,Qaai") +DEF(Inscriptional_Pahlavi, "Phli") +DEF(Inscriptional_Parthian, "Prti") +DEF(Javanese, "Java") +DEF(Kaithi, "Kthi") +DEF(Kannada, "Knda") +DEF(Katakana, "Kana") +DEF(Kawi, "Kawi") +DEF(Kayah_Li, "Kali") +DEF(Kharoshthi, "Khar") +DEF(Khmer, "Khmr") +DEF(Khojki, "Khoj") +DEF(Khitan_Small_Script, "Kits") +DEF(Khudawadi, "Sind") +DEF(Kirat_Rai, "Krai") +DEF(Lao, "Laoo") +DEF(Latin, "Latn") +DEF(Lepcha, "Lepc") +DEF(Limbu, "Limb") +DEF(Linear_A, "Lina") +DEF(Linear_B, "Linb") +DEF(Lisu, "Lisu") +DEF(Lycian, "Lyci") +DEF(Lydian, "Lydi") +DEF(Makasar, "Maka") +DEF(Mahajani, "Mahj") +DEF(Malayalam, "Mlym") +DEF(Mandaic, "Mand") +DEF(Manichaean, "Mani") +DEF(Marchen, "Marc") +DEF(Masaram_Gondi, "Gonm") +DEF(Medefaidrin, "Medf") +DEF(Meetei_Mayek, "Mtei") +DEF(Mende_Kikakui, "Mend") +DEF(Meroitic_Cursive, "Merc") +DEF(Meroitic_Hieroglyphs, "Mero") +DEF(Miao, "Plrd") +DEF(Modi, "Modi") +DEF(Mongolian, "Mong") +DEF(Mro, "Mroo") +DEF(Multani, "Mult") +DEF(Myanmar, "Mymr") +DEF(Nabataean, "Nbat") +DEF(Nag_Mundari, "Nagm") +DEF(Nandinagari, "Nand") +DEF(New_Tai_Lue, "Talu") +DEF(Newa, "Newa") +DEF(Nko, "Nkoo") +DEF(Nushu, "Nshu") +DEF(Nyiakeng_Puachue_Hmong, "Hmnp") +DEF(Ogham, "Ogam") +DEF(Ol_Chiki, "Olck") +DEF(Ol_Onal, "Onao") +DEF(Old_Hungarian, "Hung") +DEF(Old_Italic, "Ital") +DEF(Old_North_Arabian, "Narb") +DEF(Old_Permic, "Perm") +DEF(Old_Persian, "Xpeo") +DEF(Old_Sogdian, "Sogo") +DEF(Old_South_Arabian, "Sarb") +DEF(Old_Turkic, "Orkh") +DEF(Old_Uyghur, "Ougr") +DEF(Oriya, "Orya") +DEF(Osage, "Osge") +DEF(Osmanya, "Osma") +DEF(Pahawh_Hmong, "Hmng") +DEF(Palmyrene, "Palm") +DEF(Pau_Cin_Hau, "Pauc") +DEF(Phags_Pa, "Phag") +DEF(Phoenician, "Phnx") +DEF(Psalter_Pahlavi, "Phlp") +DEF(Rejang, "Rjng") +DEF(Runic, "Runr") +DEF(Samaritan, "Samr") +DEF(Saurashtra, "Saur") +DEF(Sharada, "Shrd") +DEF(Shavian, "Shaw") +DEF(Siddham, "Sidd") +DEF(SignWriting, "Sgnw") +DEF(Sinhala, "Sinh") +DEF(Sogdian, "Sogd") +DEF(Sora_Sompeng, "Sora") +DEF(Soyombo, "Soyo") +DEF(Sundanese, "Sund") +DEF(Sunuwar, "Sunu") +DEF(Syloti_Nagri, "Sylo") +DEF(Syriac, "Syrc") +DEF(Tagalog, "Tglg") +DEF(Tagbanwa, "Tagb") +DEF(Tai_Le, "Tale") +DEF(Tai_Tham, "Lana") +DEF(Tai_Viet, "Tavt") +DEF(Takri, "Takr") +DEF(Tamil, "Taml") +DEF(Tangut, "Tang") +DEF(Telugu, "Telu") +DEF(Thaana, "Thaa") +DEF(Thai, "Thai") +DEF(Tibetan, "Tibt") +DEF(Tifinagh, "Tfng") +DEF(Tirhuta, "Tirh") +DEF(Tangsa, "Tnsa") +DEF(Todhri, "Todr") +DEF(Toto, "Toto") +DEF(Tulu_Tigalari, "Tutg") +DEF(Ugaritic, "Ugar") +DEF(Vai, "Vaii") +DEF(Vithkuqi, "Vith") +DEF(Wancho, "Wcho") +DEF(Warang_Citi, "Wara") +DEF(Yezidi, "Yezi") +DEF(Yi, "Yiii") +DEF(Zanabazar_Square, "Zanb") +#endif + +#ifdef UNICODE_PROP_LIST +/* Prop list not exported to regexp */ +DEF(Hyphen, "") +DEF(Other_Math, "") +DEF(Other_Alphabetic, "") +DEF(Other_Lowercase, "") +DEF(Other_Uppercase, "") +DEF(Other_Grapheme_Extend, "") +DEF(Other_Default_Ignorable_Code_Point, "") +DEF(Other_ID_Start, "") +DEF(Other_ID_Continue, "") +DEF(Prepended_Concatenation_Mark, "") +/* additional computed properties for smaller tables */ +DEF(ID_Continue1, "") +DEF(XID_Start1, "") +DEF(XID_Continue1, "") +DEF(Changes_When_Titlecased1, "") +DEF(Changes_When_Casefolded1, "") +DEF(Changes_When_NFKC_Casefolded1, "") +DEF(Basic_Emoji1, "") +DEF(Basic_Emoji2, "") +DEF(RGI_Emoji_Modifier_Sequence, "") +DEF(RGI_Emoji_Flag_Sequence, "") +DEF(Emoji_Keycap_Sequence, "") + +/* Prop list exported to JS */ +DEF(ASCII_Hex_Digit, "AHex") +DEF(Bidi_Control, "Bidi_C") +DEF(Dash, "") +DEF(Deprecated, "Dep") +DEF(Diacritic, "Dia") +DEF(Extender, "Ext") +DEF(Hex_Digit, "Hex") +DEF(IDS_Unary_Operator, "IDSU") +DEF(IDS_Binary_Operator, "IDSB") +DEF(IDS_Trinary_Operator, "IDST") +DEF(Ideographic, "Ideo") +DEF(Join_Control, "Join_C") +DEF(Logical_Order_Exception, "LOE") +DEF(Modifier_Combining_Mark, "MCM") +DEF(Noncharacter_Code_Point, "NChar") +DEF(Pattern_Syntax, "Pat_Syn") +DEF(Pattern_White_Space, "Pat_WS") +DEF(Quotation_Mark, "QMark") +DEF(Radical, "") +DEF(Regional_Indicator, "RI") +DEF(Sentence_Terminal, "STerm") +DEF(Soft_Dotted, "SD") +DEF(Terminal_Punctuation, "Term") +DEF(Unified_Ideograph, "UIdeo") +DEF(Variation_Selector, "VS") +DEF(White_Space, "space") +DEF(Bidi_Mirrored, "Bidi_M") +DEF(Emoji, "") +DEF(Emoji_Component, "EComp") +DEF(Emoji_Modifier, "EMod") +DEF(Emoji_Modifier_Base, "EBase") +DEF(Emoji_Presentation, "EPres") +DEF(Extended_Pictographic, "ExtPict") +DEF(Default_Ignorable_Code_Point, "DI") +DEF(ID_Start, "IDS") +DEF(Case_Ignorable, "CI") + +/* other binary properties */ +DEF(ASCII,"") +DEF(Alphabetic, "Alpha") +DEF(Any, "") +DEF(Assigned,"") +DEF(Cased, "") +DEF(Changes_When_Casefolded, "CWCF") +DEF(Changes_When_Casemapped, "CWCM") +DEF(Changes_When_Lowercased, "CWL") +DEF(Changes_When_NFKC_Casefolded, "CWKCF") +DEF(Changes_When_Titlecased, "CWT") +DEF(Changes_When_Uppercased, "CWU") +DEF(Grapheme_Base, "Gr_Base") +DEF(Grapheme_Extend, "Gr_Ext") +DEF(ID_Continue, "IDC") +DEF(ID_Compat_Math_Start, "") +DEF(ID_Compat_Math_Continue, "") +DEF(InCB, "") +DEF(Lowercase, "Lower") +DEF(Math, "") +DEF(Uppercase, "Upper") +DEF(XID_Continue, "XIDC") +DEF(XID_Start, "XIDS") + +/* internal tables with index */ +DEF(Cased1, "") + +#endif + +#ifdef UNICODE_SEQUENCE_PROP_LIST +DEF(Basic_Emoji) +DEF(Emoji_Keycap_Sequence) +DEF(RGI_Emoji_Modifier_Sequence) +DEF(RGI_Emoji_Flag_Sequence) +DEF(RGI_Emoji_Tag_Sequence) +DEF(RGI_Emoji_ZWJ_Sequence) +DEF(RGI_Emoji) +#endif diff --git a/NativeScript/napi/android/quickjs/source_ng/CMakeLists.txt b/NativeScript/napi/android/quickjs/source_ng/CMakeLists.txt new file mode 100755 index 000000000..f2ada27e9 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/CMakeLists.txt @@ -0,0 +1,461 @@ +cmake_minimum_required(VERSION 3.10) + +project(quickjs LANGUAGES C) + +include(CheckCCompilerFlag) +include(GNUInstallDirs) + +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) +set(CMAKE_C_STANDARD 11) + +# MINGW doesn't exist in older cmake versions, newer versions don't know +# about CMAKE_COMPILER_IS_MINGW, and there is no unique CMAKE_C_COMPILER_ID +# for mingw-based compilers... +if(MINGW) + # do nothing +elseif(CMAKE_C_COMPILER MATCHES "mingw") + set(MINGW TRUE) +else() + set(MINGW FALSE) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "tvOS") + set(TVOS TRUE) +else() + set(TVOS FALSE) +endif() +if(CMAKE_SYSTEM_NAME STREQUAL "watchOS") + set(WATCHOS TRUE) +else() + set(WATCHOS FALSE) +endif() + +if(NOT CMAKE_BUILD_TYPE) + message(STATUS "No build type selected, default to Release") + set(CMAKE_BUILD_TYPE "Release") +endif() + +message(STATUS "Building in ${CMAKE_BUILD_TYPE} mode") +message(STATUS "Building with ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION} on ${CMAKE_SYSTEM}") + +macro(xcheck_add_c_compiler_flag FLAG) + string(REPLACE "-" "" FLAG_NO_HYPHEN ${FLAG}) + check_c_compiler_flag(${FLAG} COMPILER_SUPPORTS_${FLAG_NO_HYPHEN}) + if(COMPILER_SUPPORTS_${FLAG_NO_HYPHEN}) + add_compile_options(${FLAG}) + endif() +endmacro() + +xcheck_add_c_compiler_flag(-Wall) +if(NOT MSVC AND NOT IOS AND NOT TVOS AND NOT WATCHOS) + xcheck_add_c_compiler_flag(-Werror) + xcheck_add_c_compiler_flag(-Wextra) +endif() +xcheck_add_c_compiler_flag(-Wformat=2) +xcheck_add_c_compiler_flag(-Wno-implicit-fallthrough) +xcheck_add_c_compiler_flag(-Wno-sign-compare) +xcheck_add_c_compiler_flag(-Wno-missing-field-initializers) +xcheck_add_c_compiler_flag(-Wno-unused-parameter) +xcheck_add_c_compiler_flag(-Wno-unused-but-set-variable) +xcheck_add_c_compiler_flag(-Wno-unused-result) +xcheck_add_c_compiler_flag(-Wno-stringop-truncation) +xcheck_add_c_compiler_flag(-Wno-array-bounds) +xcheck_add_c_compiler_flag(-funsigned-char) + +# ClangCL is command line compatible with MSVC, so 'MSVC' is set. +if(MSVC) + xcheck_add_c_compiler_flag(-Wno-unsafe-buffer-usage) + xcheck_add_c_compiler_flag(-Wno-sign-conversion) + xcheck_add_c_compiler_flag(-Wno-nonportable-system-include-path) + xcheck_add_c_compiler_flag(-Wno-implicit-int-conversion) + xcheck_add_c_compiler_flag(-Wno-shorten-64-to-32) + xcheck_add_c_compiler_flag(-Wno-reserved-macro-identifier) + xcheck_add_c_compiler_flag(-Wno-reserved-identifier) + xcheck_add_c_compiler_flag(-Wdeprecated-declarations) + xcheck_add_c_compiler_flag(/experimental:c11atomics) + xcheck_add_c_compiler_flag(/wd4018) # -Wno-sign-conversion + xcheck_add_c_compiler_flag(/wd4061) # -Wno-implicit-fallthrough + xcheck_add_c_compiler_flag(/wd4100) # -Wno-unused-parameter + xcheck_add_c_compiler_flag(/wd4200) # -Wno-zero-length-array + xcheck_add_c_compiler_flag(/wd4242) # -Wno-shorten-64-to-32 + xcheck_add_c_compiler_flag(/wd4244) # -Wno-shorten-64-to-32 + xcheck_add_c_compiler_flag(/wd4245) # -Wno-sign-compare + xcheck_add_c_compiler_flag(/wd4267) # -Wno-shorten-64-to-32 + xcheck_add_c_compiler_flag(/wd4388) # -Wno-sign-compare + xcheck_add_c_compiler_flag(/wd4389) # -Wno-sign-compare + xcheck_add_c_compiler_flag(/wd4456) # Hides previous local declaration + xcheck_add_c_compiler_flag(/wd4457) # Hides function parameter + xcheck_add_c_compiler_flag(/wd4710) # Function not inlined + xcheck_add_c_compiler_flag(/wd4711) # Function was inlined + xcheck_add_c_compiler_flag(/wd4820) # Padding added after construct + xcheck_add_c_compiler_flag(/wd4996) # -Wdeprecated-declarations + xcheck_add_c_compiler_flag(/wd5045) # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified +endif() + +# Set a 8MB default stack size on Windows. +# It defaults to 1MB on MSVC, which is the same as our current JS stack size, +# so it will overflow and crash otherwise. +# On MinGW it defaults to 2MB. +if(WIN32) + if(MSVC) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:8388608") + else() + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608") + endif() +endif() + +# MacOS and GCC 11 or later need -Wno-maybe-uninitialized +# https://github.com/quickjs-ng/quickjs/issues/453 +if(APPLE AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 11) + xcheck_add_c_compiler_flag(-Wno-maybe-uninitialized) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "WASI") + add_compile_definitions( + _WASI_EMULATED_PROCESS_CLOCKS + _WASI_EMULATED_SIGNAL + ) + add_link_options( + -lwasi-emulated-process-clocks + -lwasi-emulated-signal + ) +endif() + +if(CMAKE_BUILD_TYPE MATCHES "Debug") + xcheck_add_c_compiler_flag(/Od) + xcheck_add_c_compiler_flag(-O0) + xcheck_add_c_compiler_flag(-ggdb) + xcheck_add_c_compiler_flag(-fno-omit-frame-pointer) +endif() + +macro(xoption OPTION_NAME OPTION_TEXT OPTION_DEFAULT) + option(${OPTION_NAME} ${OPTION_TEXT} ${OPTION_DEFAULT}) + if(DEFINED ENV{${OPTION_NAME}}) + # Allow setting the option through an environment variable. + set(${OPTION_NAME} $ENV{${OPTION_NAME}}) + endif() + if(${OPTION_NAME}) + add_definitions(-D${OPTION_NAME}) + endif() + message(STATUS " ${OPTION_NAME}: ${${OPTION_NAME}}") +endmacro() + +xoption(BUILD_SHARED_LIBS "Build a shared library" OFF) +if(BUILD_SHARED_LIBS) + message(STATUS "Building a shared library") +endif() + +# note: QJS_ENABLE_TSAN is currently incompatible with the other sanitizers but we +# don't explicitly check for that because who knows what the future will bring? +# QJS_ENABLE_MSAN only works with clang at the time of writing; also not checked +# for the same reason +xoption(QJS_BUILD_EXAMPLES "Build examples" OFF) +xoption(QJS_BUILD_CLI_STATIC "Build a static qjs executable" OFF) +xoption(QJS_BUILD_CLI_WITH_MIMALLOC "Build the qjs executable with mimalloc" OFF) +xoption(QJS_BUILD_CLI_WITH_STATIC_MIMALLOC "Build the qjs executable with mimalloc (statically linked)" OFF) +xoption(QJS_DISABLE_PARSER "Disable JS source code parser" OFF) +xoption(QJS_ENABLE_ASAN "Enable AddressSanitizer (ASan)" OFF) +xoption(QJS_ENABLE_MSAN "Enable MemorySanitizer (MSan)" OFF) +xoption(QJS_ENABLE_TSAN "Enable ThreadSanitizer (TSan)" OFF) +xoption(QJS_ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer (UBSan)" OFF) + +if(QJS_ENABLE_ASAN) +message(STATUS "Building with ASan") +add_compile_options( + -fsanitize=address + -fno-sanitize-recover=all + -fno-omit-frame-pointer +) +add_link_options( + -fsanitize=address + -fno-sanitize-recover=all + -fno-omit-frame-pointer +) +endif() + +if(QJS_ENABLE_MSAN) +message(STATUS "Building with MSan") +add_compile_options( + -fsanitize=memory + -fno-sanitize-recover=all + -fno-omit-frame-pointer +) +add_link_options( + -fsanitize=memory + -fno-sanitize-recover=all + -fno-omit-frame-pointer +) +endif() + +if(QJS_ENABLE_TSAN) +message(STATUS "Building with TSan") +add_compile_options( + -fsanitize=thread + -fno-sanitize-recover=all + -fno-omit-frame-pointer +) +add_link_options( + -fsanitize=thread + -fno-sanitize-recover=all + -fno-omit-frame-pointer +) +endif() + +if(QJS_ENABLE_UBSAN) +message(STATUS "Building with UBSan") +add_compile_options( + -fsanitize=undefined + -fno-sanitize-recover=all + -fno-omit-frame-pointer +) +add_link_options( + -fsanitize=undefined + -fno-sanitize-recover=all + -fno-omit-frame-pointer +) +endif() + + +# QuickJS library +# + +xoption(QJS_BUILD_LIBC "Build standard library modules as part of the library" OFF) +macro(add_qjs_libc_if_needed target) + if(NOT QJS_BUILD_LIBC) + target_sources(${target} PRIVATE quickjs-libc.c) + endif() +endmacro() +macro(add_static_if_needed target) + if(QJS_BUILD_CLI_STATIC OR MINGW) + target_link_options(${target} PRIVATE -static) + if(MINGW) + target_link_options(${target} PRIVATE -static-libgcc) + endif() + endif() +endmacro() + +set(qjs_sources + cutils.c + dtoa.c + libregexp.c + libunicode.c + quickjs.c +) + +if(QJS_BUILD_LIBC) + list(APPEND qjs_sources quickjs-libc.c) +endif() +list(APPEND qjs_defines _GNU_SOURCE) +if(WIN32) + # NB: Windows 7 is EOL and we are only supporting in so far as it doesn't interfere with progress. + list(APPEND qjs_defines WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0601) +endif() +if(TVOS) + list(APPEND qjs_defines _TVOS) +endif() +if(WATCHOS) + list(APPEND qjs_defines _WATCHOS) +endif() +list(APPEND qjs_libs ${CMAKE_DL_LIBS}) +find_package(Threads) +if(NOT CMAKE_SYSTEM_NAME STREQUAL "WASI") + list(APPEND qjs_libs ${CMAKE_THREAD_LIBS_INIT}) +endif() + +# try to find libm +find_library(M_LIBRARIES m) +if(M_LIBRARIES OR CMAKE_C_COMPILER_ID STREQUAL "TinyCC") + list(APPEND qjs_libs m) +endif() + +add_library(qjs ${qjs_sources}) +target_compile_definitions(qjs PRIVATE ${qjs_defines}) +target_include_directories(qjs PUBLIC + $ + $ +) +target_link_libraries(qjs PUBLIC ${qjs_libs}) + +if(EMSCRIPTEN) + add_executable(qjs_wasm ${qjs_sources}) + target_link_options(qjs_wasm PRIVATE + # in emscripten 3.x, this will be set to 16k which is too small for quickjs. #write sth. to force github rebuild + -sSTACK_SIZE=2097152 # let it be 2m = 2 * 1024 * 1024 = 2097152, otherwise, stack overflow may be occured at bootstrap + -sNO_INVOKE_RUN + -sNO_EXIT_RUNTIME + -sMODULARIZE # do not mess the global + -sEXPORT_ES6 # export js file to morden es module + -sEXPORT_NAME=getQuickJs # give a name + -sTEXTDECODER=1 # it will be 2 if we use -Oz, and that will cause js -> c string convertion fail + -sNO_DEFAULT_TO_CXX # this project is pure c project, no need for c plus plus handle + -sEXPORTED_RUNTIME_METHODS=ccall,cwrap + ) + target_compile_definitions(qjs_wasm PRIVATE ${qjs_defines}) + target_link_libraries(qjs_wasm m) +endif() + + +# QuickJS bytecode compiler +# + +add_executable(qjsc + qjsc.c +) +add_qjs_libc_if_needed(qjsc) +add_static_if_needed(qjsc) +target_compile_definitions(qjsc PRIVATE ${qjs_defines}) +target_link_libraries(qjsc qjs) + + +# QuickJS CLI +# + +add_executable(qjs_exe + gen/repl.c + gen/standalone.c + qjs.c +) +add_qjs_libc_if_needed(qjs_exe) +add_static_if_needed(qjs_exe) +set_target_properties(qjs_exe PROPERTIES + OUTPUT_NAME "qjs" +) +target_compile_definitions(qjs_exe PRIVATE ${qjs_defines}) +target_link_libraries(qjs_exe qjs) +if(NOT WIN32) + set_target_properties(qjs_exe PROPERTIES ENABLE_EXPORTS TRUE) +endif() +if(QJS_BUILD_CLI_WITH_MIMALLOC OR QJS_BUILD_CLI_WITH_STATIC_MIMALLOC) + find_package(mimalloc REQUIRED) + # Upstream mimalloc doesn't provide a way to know if both libraries are supported. + if(QJS_BUILD_CLI_WITH_STATIC_MIMALLOC) + target_link_libraries(qjs_exe mimalloc-static) + else() + target_link_libraries(qjs_exe mimalloc) + endif() +endif() + +# Test262 runner +# + +if(NOT EMSCRIPTEN) + add_executable(run-test262 + run-test262.c + ) + add_qjs_libc_if_needed(run-test262) + target_compile_definitions(run-test262 PRIVATE ${qjs_defines}) + target_link_libraries(run-test262 qjs) +endif() + +# Interrupt test +# + +add_executable(api-test + api-test.c +) +target_compile_definitions(api-test PRIVATE ${qjs_defines}) +target_link_libraries(api-test qjs) + +# Unicode generator +# + +add_executable(unicode_gen EXCLUDE_FROM_ALL + cutils.c + libunicode.c + unicode_gen.c +) +target_compile_definitions(unicode_gen PRIVATE ${qjs_defines}) + +add_executable(function_source + gen/function_source.c +) +add_qjs_libc_if_needed(function_source) +target_include_directories(function_source PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_definitions(function_source PRIVATE ${qjs_defines}) +target_link_libraries(function_source qjs) + +# Examples +# + +if(QJS_BUILD_EXAMPLES) + add_executable(hello + gen/hello.c + ) + add_qjs_libc_if_needed(hello) + target_include_directories(hello PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_definitions(hello PRIVATE ${qjs_defines}) + target_link_libraries(hello qjs) + + add_executable(hello_module + gen/hello_module.c + ) + add_qjs_libc_if_needed(hello_module) + target_include_directories(hello_module PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_definitions(hello_module PRIVATE ${qjs_defines}) + target_link_libraries(hello_module qjs) + + add_library(fib MODULE examples/fib.c) + set_target_properties(fib PROPERTIES + PREFIX "" + C_VISIBILITY_PRESET default + ) + target_compile_definitions(fib PRIVATE JS_SHARED_LIBRARY) + if(WIN32) + target_link_libraries(fib qjs) + elseif(APPLE) + target_link_options(fib PRIVATE -undefined dynamic_lookup) + endif() + + add_library(point MODULE examples/point.c) + set_target_properties(point PROPERTIES + PREFIX "" + C_VISIBILITY_PRESET default + ) + target_compile_definitions(point PRIVATE JS_SHARED_LIBRARY) + if(WIN32) + target_link_libraries(point qjs) + elseif(APPLE) + target_link_options(point PRIVATE -undefined dynamic_lookup) + endif() + + add_executable(test_fib + examples/fib.c + gen/test_fib.c + ) + add_qjs_libc_if_needed(test_fib) + target_include_directories(test_fib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_definitions(test_fib PRIVATE ${qjs_defines}) + target_link_libraries(test_fib qjs) +endif() + +# Install target +# + +file(STRINGS quickjs.h quickjs_h REGEX QJS_VERSION) +string(REGEX MATCH "QJS_VERSION_MAJOR ([0-9]*)" _ "${quickjs_h}") +set(QJS_VERSION_MAJOR ${CMAKE_MATCH_1}) +string(REGEX MATCH "QJS_VERSION_MINOR ([0-9]*)" _ "${quickjs_h}") +set(QJS_VERSION_MINOR ${CMAKE_MATCH_1}) +string(REGEX MATCH "QJS_VERSION_PATCH ([0-9]*)" _ "${quickjs_h}") +set(QJS_VERSION_PATCH ${CMAKE_MATCH_1}) +set_target_properties(qjs PROPERTIES + VERSION ${QJS_VERSION_MAJOR}.${QJS_VERSION_MINOR}.${QJS_VERSION_PATCH} + SOVERSION ${QJS_VERSION_MAJOR} +) +install(FILES quickjs.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +if(QJS_BUILD_LIBC) + install(FILES quickjs-libc.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +endif() +if(NOT IOS AND NOT TVOS AND NOT WATCHOS) + install(TARGETS qjs_exe RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(TARGETS qjsc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() +install(TARGETS qjs EXPORT qjsConfig + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(EXPORT qjsConfig DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/quickjs) +install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) +install(DIRECTORY examples DESTINATION ${CMAKE_INSTALL_DOCDIR}) diff --git a/NativeScript/napi/android/quickjs/source_ng/LICENSE b/NativeScript/napi/android/quickjs/source_ng/LICENSE new file mode 100755 index 000000000..3c78b6929 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2017-2024 Fabrice Bellard +Copyright (c) 2017-2024 Charlie Gordon +Copyright (c) 2023-2025 Ben Noordhuis +Copyright (c) 2023-2025 Saúl Ibarra Corretgé + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/NativeScript/napi/android/quickjs/source_ng/Makefile b/NativeScript/napi/android/quickjs/source_ng/Makefile new file mode 100755 index 000000000..4ff8ca17e --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/Makefile @@ -0,0 +1,147 @@ +# +# QuickJS Javascript Engine +# +# Copyright (c) 2017-2024 Fabrice Bellard +# Copyright (c) 2017-2024 Charlie Gordon +# Copyright (c) 2023-2025 Ben Noordhuis +# Copyright (c) 2023-2025 Saúl Ibarra Corretgé +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +BUILD_DIR=build +BUILD_TYPE?=Release +INSTALL_PREFIX?=/usr/local + +QJS=$(BUILD_DIR)/qjs +QJSC=$(BUILD_DIR)/qjsc +RUN262=$(BUILD_DIR)/run-test262 + +JOBS?=$(shell getconf _NPROCESSORS_ONLN) +ifeq ($(JOBS),) +JOBS := $(shell sysctl -n hw.ncpu) +endif +ifeq ($(JOBS),) +JOBS := $(shell nproc) +endif +ifeq ($(JOBS),) +JOBS := 4 +endif + +all: $(QJS) + +amalgam: TEMP := $(shell mktemp -d) +amalgam: $(QJS) + $(QJS) amalgam.js $(TEMP)/quickjs-amalgam.c + cp quickjs.h quickjs-libc.h $(TEMP) + cd $(TEMP) && zip -9 quickjs-amalgam.zip quickjs-amalgam.c quickjs.h quickjs-libc.h + cp $(TEMP)/quickjs-amalgam.zip $(BUILD_DIR) + cd $(TEMP) && $(RM) quickjs-amalgam.zip quickjs-amalgam.c quickjs.h quickjs-libc.h + $(RM) -d $(TEMP) + +fuzz: + clang -g -O1 -fsanitize=address,undefined,fuzzer -o fuzz fuzz.c + ./fuzz + +$(BUILD_DIR): + cmake -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_INSTALL_PREFIX=$(INSTALL_PREFIX) + +$(QJS): $(BUILD_DIR) + cmake --build $(BUILD_DIR) -j $(JOBS) + +$(QJSC): $(BUILD_DIR) + cmake --build $(BUILD_DIR) --target qjsc -j $(JOBS) + +install: $(QJS) $(QJSC) + cmake --build $(BUILD_DIR) --target install + +clean: + cmake --build $(BUILD_DIR) --target clean + +codegen: $(QJSC) + $(QJSC) -ss -o gen/repl.c -m repl.js + $(QJSC) -ss -o gen/standalone.c -m standalone.js + $(QJSC) -e -o gen/function_source.c tests/function_source.js + $(QJSC) -e -o gen/hello.c examples/hello.js + $(QJSC) -e -o gen/hello_module.c -m examples/hello_module.js + $(QJSC) -e -o gen/test_fib.c -m examples/test_fib.js + $(QJSC) -C -ss -o builtin-array-fromasync.h builtin-array-fromasync.js + +debug: + BUILD_TYPE=Debug $(MAKE) + +distclean: + @rm -rf $(BUILD_DIR) + +stats: $(QJS) + $(QJS) -qd + +jscheck: CFLAGS=-I. -D_GNU_SOURCE -DJS_CHECK_JSVALUE -Wall -Werror -fsyntax-only -c -o /dev/null +jscheck: + $(CC) $(CFLAGS) api-test.c + $(CC) $(CFLAGS) ctest.c + $(CC) $(CFLAGS) fuzz.c + $(CC) $(CFLAGS) gen/function_source.c + $(CC) $(CFLAGS) gen/hello.c + $(CC) $(CFLAGS) gen/hello_module.c + $(CC) $(CFLAGS) gen/repl.c + $(CC) $(CFLAGS) gen/standalone.c + $(CC) $(CFLAGS) gen/test_fib.c + $(CC) $(CFLAGS) qjs.c + $(CC) $(CFLAGS) qjsc.c + $(CC) $(CFLAGS) quickjs-libc.c + $(CC) $(CFLAGS) quickjs.c + $(CC) $(CFLAGS) run-test262.c + +# effectively .PHONY because it doesn't generate output +ctest: CFLAGS=-std=c11 -fsyntax-only -Wall -Wextra -Werror -pedantic +ctest: ctest.c quickjs.h + $(CC) $(CFLAGS) -DJS_NAN_BOXING=0 $< + $(CC) $(CFLAGS) -DJS_NAN_BOXING=1 $< + +# effectively .PHONY because it doesn't generate output +cxxtest: CXXFLAGS=-std=c++11 -fsyntax-only -Wall -Wextra -Werror -pedantic +cxxtest: cxxtest.cc quickjs.h + $(CXX) $(CXXFLAGS) -DJS_NAN_BOXING=0 $< + $(CXX) $(CXXFLAGS) -DJS_NAN_BOXING=1 $< + +test: $(QJS) + $(RUN262) -c tests.conf + +test262: $(QJS) + $(RUN262) -m -c test262.conf -a + +test262-fast: $(QJS) + $(RUN262) -m -c test262.conf -c test262-fast.conf -a + +test262-update: $(QJS) + $(RUN262) -u -c test262.conf -a -t 1 + +test262-check: $(QJS) + $(RUN262) -m -c test262.conf -E -a + +microbench: $(QJS) + $(QJS) tests/microbench.js + +unicode_gen: $(BUILD_DIR) + cmake --build $(BUILD_DIR) --target unicode_gen + +libunicode-table.h: unicode_gen + $(BUILD_DIR)/unicode_gen unicode $@ + +.PHONY: all amalgam ctest cxxtest debug fuzz jscheck install clean codegen distclean stats test test262 test262-update test262-check microbench unicode_gen $(QJS) $(QJSC) diff --git a/NativeScript/napi/android/quickjs/source_ng/README.md b/NativeScript/napi/android/quickjs/source_ng/README.md new file mode 100755 index 000000000..e4b0e34cd --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/README.md @@ -0,0 +1,24 @@ +# ⚡️ QuickJS - A mighty JavaScript engine + +## Overview + +QuickJS is a small and embeddable JavaScript engine. It aims to support the latest +[ECMAScript] specification. + +This project is a _fork_ of the [original QuickJS project] by Fabrice Bellard and Charlie Gordon, after it went dormant, with the intent of reigniting its development. + +## Getting started + +Head over to the [project website] for instructions on how to get started and more +documentation. + +## Authors + +[@bnoordhuis], [@saghul], and many more [contributors]. + +[ECMAScript]: https://tc39.es/ecma262/ +[original QuickJS project]: https://bellard.org/quickjs +[@bnoordhuis]: https://github.com/bnoordhuis +[@saghul]: https://github.com/saghul +[contributors]: https://github.com/quickjs-ng/quickjs/graphs/contributors +[project website]: https://quickjs-ng.github.io/quickjs/ diff --git a/NativeScript/napi/android/quickjs/source_ng/amalgam.js b/NativeScript/napi/android/quickjs/source_ng/amalgam.js new file mode 100755 index 000000000..028d77985 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/amalgam.js @@ -0,0 +1,49 @@ +import {loadFile, writeFile} from "qjs:std" + +const cutils_c = loadFile("cutils.c") +const cutils_h = loadFile("cutils.h") +const dtoa_c = loadFile("dtoa.c") +const dtoa_h = loadFile("dtoa.h") +const libregexp_c = loadFile("libregexp.c") +const libregexp_h = loadFile("libregexp.h") +const libregexp_opcode_h = loadFile("libregexp-opcode.h") +const libunicode_c = loadFile("libunicode.c") +const libunicode_h = loadFile("libunicode.h") +const libunicode_table_h = loadFile("libunicode-table.h") +const list_h = loadFile("list.h") +const quickjs_atom_h = loadFile("quickjs-atom.h") +const quickjs_c = loadFile("quickjs.c") +const quickjs_c_atomics_h = loadFile("quickjs-c-atomics.h") +const quickjs_h = loadFile("quickjs.h") +const quickjs_libc_c = loadFile("quickjs-libc.c") +const quickjs_libc_h = loadFile("quickjs-libc.h") +const quickjs_opcode_h = loadFile("quickjs-opcode.h") +const gen_builtin_array_fromasync_h = loadFile("builtin-array-fromasync.h") + +let source = "#if defined(QJS_BUILD_LIBC) && defined(__linux__) && !defined(_GNU_SOURCE)\n" + + "#define _GNU_SOURCE\n" + + "#endif\n" + + quickjs_c_atomics_h + + cutils_h + + dtoa_h + + list_h + + libunicode_h // exports lre_is_id_start, used by libregexp.h + + libregexp_h + + libunicode_table_h + + quickjs_h + + quickjs_c + + cutils_c + + dtoa_c + + libregexp_c + + libunicode_c + + "#ifdef QJS_BUILD_LIBC\n" + + quickjs_libc_h + + quickjs_libc_c + + "#endif // QJS_BUILD_LIBC\n" +source = source.replace(/#include "quickjs-atom.h"/g, quickjs_atom_h) +source = source.replace(/#include "quickjs-opcode.h"/g, quickjs_opcode_h) +source = source.replace(/#include "libregexp-opcode.h"/g, libregexp_opcode_h) +source = source.replace(/#include "builtin-array-fromasync.h"/g, + gen_builtin_array_fromasync_h) +source = source.replace(/#include "[^"]+"/g, "") +writeFile(execArgv[2] ?? "quickjs-amalgam.c", source) diff --git a/NativeScript/napi/android/quickjs/source_ng/api-test.c b/NativeScript/napi/android/quickjs/source_ng/api-test.c new file mode 100755 index 000000000..ef28a4f5f --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/api-test.c @@ -0,0 +1,763 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif +#include +#include +#include +#include "quickjs.h" +#include "cutils.h" + +static JSValue eval(JSContext *ctx, const char *code) +{ + return JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_GLOBAL); +} + +static JSValue cfunc_callback(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_ThrowTypeError(ctx, "from cfunc"); +} + +static JSValue cfuncdata_callback(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int magic, JSValueConst *func_data) +{ + return JS_ThrowTypeError(ctx, "from cfuncdata"); +} + +static void cfunctions(void) +{ + uint32_t length; + const char *s; + JSValue ret, stack; + + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + JSValue cfunc = JS_NewCFunction(ctx, cfunc_callback, "cfunc", 42); + JSValue cfuncdata = + JS_NewCFunctionData2(ctx, cfuncdata_callback, "cfuncdata", + /*length*/1337, /*magic*/0, /*data_len*/0, NULL); + JSValue global = JS_GetGlobalObject(ctx); + JS_SetPropertyStr(ctx, global, "cfunc", cfunc); + JS_SetPropertyStr(ctx, global, "cfuncdata", cfuncdata); + JS_FreeValue(ctx, global); + + ret = eval(ctx, "cfunc.name"); + assert(!JS_IsException(ret)); + assert(JS_IsString(ret)); + s = JS_ToCString(ctx, ret); + JS_FreeValue(ctx, ret); + assert(s); + assert(!strcmp(s, "cfunc")); + JS_FreeCString(ctx, s); + ret = eval(ctx, "cfunc.length"); + assert(!JS_IsException(ret)); + assert(JS_IsNumber(ret)); + assert(0 == JS_ToUint32(ctx, &length, ret)); + assert(length == 42); + + ret = eval(ctx, "cfuncdata.name"); + assert(!JS_IsException(ret)); + assert(JS_IsString(ret)); + s = JS_ToCString(ctx, ret); + JS_FreeValue(ctx, ret); + assert(s); + assert(!strcmp(s, "cfuncdata")); + JS_FreeCString(ctx, s); + ret = eval(ctx, "cfuncdata.length"); + assert(!JS_IsException(ret)); + assert(JS_IsNumber(ret)); + assert(0 == JS_ToUint32(ctx, &length, ret)); + assert(length == 1337); + + ret = eval(ctx, "cfunc()"); + assert(JS_IsException(ret)); + ret = JS_GetException(ctx); + assert(JS_IsError(ret)); + stack = JS_GetPropertyStr(ctx, ret, "stack"); + assert(JS_IsString(stack)); + s = JS_ToCString(ctx, stack); + JS_FreeValue(ctx, stack); + assert(s); + assert(!strcmp(s, " at cfunc (native)\n at (:1:1)\n")); + JS_FreeCString(ctx, s); + s = JS_ToCString(ctx, ret); + JS_FreeValue(ctx, ret); + assert(s); + assert(!strcmp(s, "TypeError: from cfunc")); + JS_FreeCString(ctx, s); + + ret = eval(ctx, "cfuncdata()"); + assert(JS_IsException(ret)); + ret = JS_GetException(ctx); + assert(JS_IsError(ret)); + stack = JS_GetPropertyStr(ctx, ret, "stack"); + assert(JS_IsString(stack)); + s = JS_ToCString(ctx, stack); + JS_FreeValue(ctx, stack); + assert(s); + assert(!strcmp(s, " at cfuncdata (native)\n at (:1:1)\n")); + JS_FreeCString(ctx, s); + s = JS_ToCString(ctx, ret); + JS_FreeValue(ctx, ret); + assert(s); + assert(!strcmp(s, "TypeError: from cfuncdata")); + JS_FreeCString(ctx, s); + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +#define MAX_TIME 10 + +static int timeout_interrupt_handler(JSRuntime *rt, void *opaque) +{ + int *time = (int *)opaque; + if (*time <= MAX_TIME) + *time += 1; + return *time > MAX_TIME; +} + +static void sync_call(void) +{ + static const char code[] = +"(function() { \ + try { \ + while (true) {} \ + } catch (e) {} \ +})();"; + + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + int time = 0; + JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time); + JSValue ret = eval(ctx, code); + assert(time > MAX_TIME); + assert(JS_IsException(ret)); + JS_FreeValue(ctx, ret); + assert(JS_HasException(ctx)); + JSValue e = JS_GetException(ctx); + assert(JS_IsUncatchableError(e)); + JS_FreeValue(ctx, e); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static void async_call(void) +{ + static const char code[] = +"(async function() { \ + const loop = async () => { \ + await Promise.resolve(); \ + while (true) {} \ + }; \ + await loop().catch(() => {}); \ +})();"; + + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + int time = 0; + JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time); + JSValue ret = eval(ctx, code); + assert(!JS_IsException(ret)); + JS_FreeValue(ctx, ret); + assert(JS_IsJobPending(rt)); + int r = 0; + while (JS_IsJobPending(rt)) { + r = JS_ExecutePendingJob(rt, &ctx); + } + assert(time > MAX_TIME); + assert(r == -1); + assert(JS_HasException(ctx)); + JSValue e = JS_GetException(ctx); + assert(JS_IsUncatchableError(e)); + JS_FreeValue(ctx, e); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static JSValue save_value(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + assert(argc == 1); + JSValue *p = (JSValue *)JS_GetContextOpaque(ctx); + *p = JS_DupValue(ctx, argv[0]); + return JS_UNDEFINED; +} + +static void async_call_stack_overflow(void) +{ + static const char code[] = +"(async function() { \ + const f = () => f(); \ + try { \ + await Promise.resolve(); \ + f(); \ + } catch (e) { \ + save_value(e); \ + } \ +})();"; + + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + JSValue value = JS_UNDEFINED; + JS_SetContextOpaque(ctx, &value); + JSValue global = JS_GetGlobalObject(ctx); + JS_SetPropertyStr(ctx, global, "save_value", JS_NewCFunction(ctx, save_value, "save_value", 1)); + JS_FreeValue(ctx, global); + JSValue ret = eval(ctx, code); + assert(!JS_IsException(ret)); + JS_FreeValue(ctx, ret); + assert(JS_IsJobPending(rt)); + int r = 0; + while (JS_IsJobPending(rt)) { + r = JS_ExecutePendingJob(rt, &ctx); + } + assert(r == 1); + assert(!JS_HasException(ctx)); + assert(JS_IsError(value)); // stack overflow should be caught + JS_FreeValue(ctx, value); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +// https://github.com/quickjs-ng/quickjs/issues/914 +static void raw_context_global_var(void) +{ + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContextRaw(rt); + JS_AddIntrinsicEval(ctx); + { + JSValue ret = eval(ctx, "globalThis"); + assert(JS_IsException(ret)); + JS_FreeValue(ctx, ret); + } + { + JSValue ret = eval(ctx, "var x = 42"); + assert(JS_IsUndefined(ret)); + JS_FreeValue(ctx, ret); + } + { + JSValue ret = eval(ctx, "function f() {}"); + assert(JS_IsUndefined(ret)); + JS_FreeValue(ctx, ret); + } + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static void is_array(void) +{ + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + { + JSValue ret = eval(ctx, "[]"); + assert(!JS_IsException(ret)); + assert(JS_IsArray(ret)); + JS_FreeValue(ctx, ret); + } + { + JSValue ret = eval(ctx, "new Proxy([], {})"); + assert(!JS_IsException(ret)); + assert(!JS_IsArray(ret)); + assert(JS_IsProxy(ret)); + JSValue handler = JS_GetProxyHandler(ctx, ret); + JSValue target = JS_GetProxyTarget(ctx, ret); + assert(!JS_IsException(handler)); + assert(!JS_IsException(target)); + assert(!JS_IsProxy(handler)); + assert(!JS_IsProxy(target)); + assert(JS_IsObject(handler)); + assert(JS_IsArray(target)); + JS_FreeValue(ctx, handler); + JS_FreeValue(ctx, target); + JS_FreeValue(ctx, ret); + } + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static int loader_calls; + +static JSModuleDef *loader(JSContext *ctx, const char *name, void *opaque) +{ + loader_calls++; + assert(!strcmp(name, "b")); + static const char code[] = "export function f(x){}"; + JSValue ret = JS_Eval(ctx, code, strlen(code), "b", + JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY); + assert(!JS_IsException(ret)); + JSModuleDef *m = JS_VALUE_GET_PTR(ret); + assert(m); + JS_FreeValue(ctx, ret); + return m; +} + +static void module_serde(void) +{ + JSRuntime *rt = JS_NewRuntime(); + //JS_SetDumpFlags(rt, JS_DUMP_MODULE_RESOLVE); + JS_SetModuleLoaderFunc(rt, NULL, loader, NULL); + JSContext *ctx = JS_NewContext(rt); + static const char code[] = "import {f} from 'b'; f()"; + assert(loader_calls == 0); + JSValue mod = JS_Eval(ctx, code, strlen(code), "a", + JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY); + assert(loader_calls == 1); + assert(!JS_IsException(mod)); + assert(JS_IsModule(mod)); + size_t len = 0; + uint8_t *buf = JS_WriteObject(ctx, &len, mod, + JS_WRITE_OBJ_BYTECODE|JS_WRITE_OBJ_REFERENCE); + assert(buf); + assert(len > 0); + JS_FreeValue(ctx, mod); + assert(loader_calls == 1); + mod = JS_ReadObject(ctx, buf, len, JS_READ_OBJ_BYTECODE); + js_free(ctx, buf); + assert(loader_calls == 1); // 'b' is returned from cache + assert(!JS_IsException(mod)); + JSValue ret = JS_EvalFunction(ctx, mod); + assert(!JS_IsException(ret)); + assert(JS_IsPromise(ret)); + JSValue result = JS_PromiseResult(ctx, ret); + assert(!JS_IsException(result)); + assert(JS_IsUndefined(result)); + JS_FreeValue(ctx, result); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, mod); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static void two_byte_string(void) +{ + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + { + JSValue v = JS_NewTwoByteString(ctx, NULL, 0); + assert(!JS_IsException(v)); + const char *s = JS_ToCString(ctx, v); + assert(s); + assert(!strcmp(s, "")); + JS_FreeCString(ctx, s); + JS_FreeValue(ctx, v); + } + { + JSValue v = JS_NewTwoByteString(ctx, (uint16_t[]){'o','k'}, 2); + assert(!JS_IsException(v)); + const char *s = JS_ToCString(ctx, v); + assert(s); + assert(!strcmp(s, "ok")); + JS_FreeCString(ctx, s); + JS_FreeValue(ctx, v); + } + { + JSValue v = JS_NewTwoByteString(ctx, (uint16_t[]){0xD800}, 1); + assert(!JS_IsException(v)); + const char *s = JS_ToCString(ctx, v); + assert(s); + // questionable but surrogates don't map to UTF-8 without WTF-8 + assert(!strcmp(s, "\xED\xA0\x80")); + JS_FreeCString(ctx, s); + JS_FreeValue(ctx, v); + } + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static void weak_map_gc_check(void) +{ + static const char init_code[] = +"const map = new WeakMap(); \ +function addItem() { \ + const k = { \ + text: 'a', \ + }; \ + map.set(k, {k}); \ +}"; + static const char test_code[] = "addItem()"; + + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + + JSValue ret = eval(ctx, init_code); + assert(!JS_IsException(ret)); + + JSValue ret_test = eval(ctx, test_code); + assert(!JS_IsException(ret_test)); + JS_RunGC(rt); + JSMemoryUsage memory_usage; + JS_ComputeMemoryUsage(rt, &memory_usage); + + for (int i = 0; i < 3; i++) { + JSValue ret_test2 = eval(ctx, test_code); + assert(!JS_IsException(ret_test2)); + JS_RunGC(rt); + JSMemoryUsage memory_usage2; + JS_ComputeMemoryUsage(rt, &memory_usage2); + + assert(memory_usage.memory_used_count == memory_usage2.memory_used_count); + assert(memory_usage.memory_used_size == memory_usage2.memory_used_size); + JS_FreeValue(ctx, ret_test2); + } + + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, ret_test); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +struct { + int hook_type_call_count[4]; +} promise_hook_state; + +static void promise_hook_cb(JSContext *ctx, JSPromiseHookType type, + JSValueConst promise, JSValueConst parent_promise, + void *opaque) +{ + assert(type == JS_PROMISE_HOOK_INIT || + type == JS_PROMISE_HOOK_BEFORE || + type == JS_PROMISE_HOOK_AFTER || + type == JS_PROMISE_HOOK_RESOLVE); + promise_hook_state.hook_type_call_count[type]++; + assert(opaque == (void *)&promise_hook_state); + if (!JS_IsUndefined(parent_promise)) { + JSValue global_object = JS_GetGlobalObject(ctx); + JS_SetPropertyStr(ctx, global_object, "actual", + JS_DupValue(ctx, parent_promise)); + JS_FreeValue(ctx, global_object); + } +} + +static void promise_hook(void) +{ + int *cc = promise_hook_state.hook_type_call_count; + JSContext *unused; + JSRuntime *rt = JS_NewRuntime(); + //JS_SetDumpFlags(rt, JS_DUMP_PROMISE); + JS_SetPromiseHook(rt, promise_hook_cb, &promise_hook_state); + JSContext *ctx = JS_NewContext(rt); + JSValue global_object = JS_GetGlobalObject(ctx); + { + // empty module; creates an outer and inner module promise; + // JS_Eval returns the outer promise + JSValue ret = JS_Eval(ctx, "", 0, "", JS_EVAL_TYPE_MODULE); + assert(!JS_IsException(ret)); + assert(JS_IsPromise(ret)); + assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); + JS_FreeValue(ctx, ret); + assert(2 == cc[JS_PROMISE_HOOK_INIT]); + assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); + assert(0 == cc[JS_PROMISE_HOOK_AFTER]); + assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); + assert(!JS_IsJobPending(rt)); + } + memset(&promise_hook_state, 0, sizeof(promise_hook_state)); + { + // module with unresolved promise; the outer and inner module promises + // are resolved but not the user's promise + static const char code[] = "new Promise(() => {})"; + JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); + assert(!JS_IsException(ret)); + assert(JS_IsPromise(ret)); + assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise + JS_FreeValue(ctx, ret); + assert(3 == cc[JS_PROMISE_HOOK_INIT]); + assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); + assert(0 == cc[JS_PROMISE_HOOK_AFTER]); + assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); // outer and inner module promise + assert(!JS_IsJobPending(rt)); + } + memset(&promise_hook_state, 0, sizeof(promise_hook_state)); + { + // module with resolved promise + static const char code[] = "new Promise((resolve,reject) => resolve())"; + JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); + assert(!JS_IsException(ret)); + assert(JS_IsPromise(ret)); + assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise + JS_FreeValue(ctx, ret); + assert(3 == cc[JS_PROMISE_HOOK_INIT]); + assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); + assert(0 == cc[JS_PROMISE_HOOK_AFTER]); + assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]); + assert(!JS_IsJobPending(rt)); + } + memset(&promise_hook_state, 0, sizeof(promise_hook_state)); + { + // module with rejected promise + static const char code[] = "new Promise((resolve,reject) => reject())"; + JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); + assert(!JS_IsException(ret)); + assert(JS_IsPromise(ret)); + assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise + JS_FreeValue(ctx, ret); + assert(3 == cc[JS_PROMISE_HOOK_INIT]); + assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); + assert(0 == cc[JS_PROMISE_HOOK_AFTER]); + assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); + assert(!JS_IsJobPending(rt)); + } + memset(&promise_hook_state, 0, sizeof(promise_hook_state)); + { + // module with promise chain + static const char code[] = + "globalThis.count = 0;" + "globalThis.actual = undefined;" // set by promise_hook_cb + "globalThis.expected = new Promise(resolve => resolve());" + "expected.then(_ => count++)"; + JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); + assert(!JS_IsException(ret)); + assert(JS_IsPromise(ret)); + assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise + JS_FreeValue(ctx, ret); + assert(4 == cc[JS_PROMISE_HOOK_INIT]); + assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); + assert(0 == cc[JS_PROMISE_HOOK_AFTER]); + assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]); + JSValue v = JS_GetPropertyStr(ctx, global_object, "count"); + assert(!JS_IsException(v)); + int32_t count; + assert(0 == JS_ToInt32(ctx, &count, v)); + assert(0 == count); + JS_FreeValue(ctx, v); + assert(JS_IsJobPending(rt)); + assert(1 == JS_ExecutePendingJob(rt, &unused)); + assert(!JS_HasException(ctx)); + assert(4 == cc[JS_PROMISE_HOOK_INIT]); + assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); + assert(0 == cc[JS_PROMISE_HOOK_AFTER]); + assert(4 == cc[JS_PROMISE_HOOK_RESOLVE]); + assert(!JS_IsJobPending(rt)); + v = JS_GetPropertyStr(ctx, global_object, "count"); + assert(!JS_IsException(v)); + assert(0 == JS_ToInt32(ctx, &count, v)); + assert(1 == count); + JS_FreeValue(ctx, v); + JSValue actual = JS_GetPropertyStr(ctx, global_object, "actual"); + JSValue expected = JS_GetPropertyStr(ctx, global_object, "expected"); + assert(!JS_IsException(actual)); + assert(!JS_IsException(expected)); + assert(JS_IsSameValue(ctx, actual, expected)); + JS_FreeValue(ctx, actual); + JS_FreeValue(ctx, expected); + } + memset(&promise_hook_state, 0, sizeof(promise_hook_state)); + { + // module with thenable; fires before and after hooks + static const char code[] = + "new Promise(resolve => resolve({then(resolve){ resolve() }}))"; + JSValue ret = JS_Eval(ctx, code, strlen(code), "", JS_EVAL_TYPE_MODULE); + assert(!JS_IsException(ret)); + assert(JS_IsPromise(ret)); + assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise + JS_FreeValue(ctx, ret); + assert(3 == cc[JS_PROMISE_HOOK_INIT]); + assert(0 == cc[JS_PROMISE_HOOK_BEFORE]); + assert(0 == cc[JS_PROMISE_HOOK_AFTER]); + assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); + assert(JS_IsJobPending(rt)); + assert(1 == JS_ExecutePendingJob(rt, &unused)); + assert(!JS_HasException(ctx)); + assert(3 == cc[JS_PROMISE_HOOK_INIT]); + assert(1 == cc[JS_PROMISE_HOOK_BEFORE]); + assert(1 == cc[JS_PROMISE_HOOK_AFTER]); + assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]); + assert(!JS_IsJobPending(rt)); + } + JS_FreeValue(ctx, global_object); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static void dump_memory_usage(void) +{ + JSMemoryUsage stats; + + JSRuntime *rt = NULL; + JSContext *ctx = NULL; + + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); + + //JS_SetDumpFlags(rt, JS_DUMP_PROMISE); + + static const char code[] = + "globalThis.count = 0;" + "globalThis.actual = undefined;" // set by promise_hook_cb + "globalThis.expected = new Promise(resolve => resolve());" + "expected.then(_ => count++)"; + + JSValue evalVal = JS_Eval(ctx, code, strlen(code), "", 0); + JS_FreeValue(ctx, evalVal); + + FILE *temp = tmpfile(); + assert(temp != NULL); + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(temp, &stats, rt); + // JS_DumpMemoryUsage(stdout, &stats, rt); + fclose(temp); + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static void new_errors(void) +{ + typedef struct { + const char name[16]; + JSValue (*func)(JSContext *, const char *, ...); + } Entry; + static const Entry entries[] = { + {"Error", JS_NewPlainError}, + {"InternalError", JS_NewInternalError}, + {"RangeError", JS_NewRangeError}, + {"ReferenceError", JS_NewReferenceError}, + {"SyntaxError", JS_NewSyntaxError}, + {"TypeError", JS_NewTypeError}, + }; + const Entry *e; + + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + for (e = entries; e < endof(entries); e++) { + JSValue obj = (*e->func)(ctx, "the %s", "needle"); + assert(!JS_IsException(obj)); + assert(JS_IsObject(obj)); + assert(JS_IsError(obj)); + const char *haystack = JS_ToCString(ctx, obj); + char needle[256]; + snprintf(needle, sizeof(needle), "%s: the needle", e->name); + assert(strstr(haystack, needle)); + JS_FreeCString(ctx, haystack); + JSValue stack = JS_GetPropertyStr(ctx, obj, "stack"); + assert(!JS_IsException(stack)); + assert(JS_IsString(stack)); + JS_FreeValue(ctx, stack); + JS_FreeValue(ctx, obj); + } + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static int gop_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop) +{ + const char *name; + int found; + + found = 0; + name = JS_AtomToCString(ctx, prop); + if (!name) + return -1; + if (!strcmp(name, "answer")) { + found = 1; + *desc = (JSPropertyDescriptor){ + .value = JS_NewInt32(ctx, 42), + .flags = JS_PROP_C_W_E | JS_PROP_HAS_VALUE, + }; + } + JS_FreeCString(ctx, name); + return found; +} + +static void global_object_prototype(void) +{ + static const char code[] = "answer"; + JSValue global_object, proto, ret; + JSClassID class_id; + JSRuntime *rt; + JSContext *ctx; + int32_t answer; + int res; + + { + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); + proto = JS_NewObject(ctx); + assert(JS_IsObject(proto)); + JSCFunctionListEntry prop = JS_PROP_INT32_DEF("answer", 42, JS_PROP_C_W_E); + JS_SetPropertyFunctionList(ctx, proto, &prop, 1); + global_object = JS_GetGlobalObject(ctx); + res = JS_SetPrototype(ctx, global_object, proto); + assert(res == true); + JS_FreeValue(ctx, global_object); + JS_FreeValue(ctx, proto); + ret = eval(ctx, code); + assert(!JS_IsException(ret)); + assert(JS_IsNumber(ret)); + res = JS_ToInt32(ctx, &answer, ret); + assert(res == 0); + assert(answer == 42); + JS_FreeValue(ctx, ret); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + } + { + JSClassExoticMethods exotic = (JSClassExoticMethods){ + .get_own_property = gop_get_own_property, + }; + JSClassDef def = (JSClassDef){ + .class_name = "Global Object", + .exotic = &exotic, + }; + rt = JS_NewRuntime(); + class_id = 0; + JS_NewClassID(rt, &class_id); + res = JS_NewClass(rt, class_id, &def); + assert(res == 0); + ctx = JS_NewContext(rt); + proto = JS_NewObjectClass(ctx, class_id); + assert(JS_IsObject(proto)); + global_object = JS_GetGlobalObject(ctx); + res = JS_SetPrototype(ctx, global_object, proto); + assert(res == true); + JS_FreeValue(ctx, global_object); + JS_FreeValue(ctx, proto); + ret = eval(ctx, code); + assert(!JS_IsException(ret)); + assert(JS_IsNumber(ret)); + res = JS_ToInt32(ctx, &answer, ret); + assert(res == 0); + assert(answer == 42); + JS_FreeValue(ctx, ret); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + } +} + +// https://github.com/quickjs-ng/quickjs/issues/1178 +static void slice_string_tocstring(void) +{ + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + JSValue ret = eval(ctx, "'.'.repeat(16384).slice(1, -1)"); + assert(!JS_IsException(ret)); + assert(JS_IsString(ret)); + const char *str = JS_ToCString(ctx, ret); + assert(strlen(str) == 16382); + JS_FreeCString(ctx, str); + JS_FreeValue(ctx, ret); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +int main(void) +{ + cfunctions(); + sync_call(); + async_call(); + async_call_stack_overflow(); + raw_context_global_var(); + is_array(); + module_serde(); + two_byte_string(); + weak_map_gc_check(); + promise_hook(); + dump_memory_usage(); + new_errors(); + global_object_prototype(); + slice_string_tocstring(); + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source_ng/builtin-array-fromasync.h b/NativeScript/napi/android/quickjs/source_ng/builtin-array-fromasync.h new file mode 100755 index 000000000..19f1f0774 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/builtin-array-fromasync.h @@ -0,0 +1,113 @@ +/* File generated automatically by the QuickJS-ng compiler. */ + +#include + +const uint32_t qjsc_builtin_array_fromasync_size = 826; + +const uint8_t qjsc_builtin_array_fromasync[826] = { + 0x15, 0x0d, 0x01, 0x1a, 0x61, 0x73, 0x79, 0x6e, + 0x63, 0x49, 0x74, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x01, 0x10, 0x69, 0x74, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x01, 0x12, 0x61, 0x72, 0x72, + 0x61, 0x79, 0x4c, 0x69, 0x6b, 0x65, 0x01, 0x0a, + 0x6d, 0x61, 0x70, 0x46, 0x6e, 0x01, 0x0e, 0x74, + 0x68, 0x69, 0x73, 0x41, 0x72, 0x67, 0x01, 0x0c, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x01, 0x02, + 0x69, 0x01, 0x1a, 0x69, 0x73, 0x43, 0x6f, 0x6e, + 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72, + 0x01, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x01, 0x0c, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x01, 0x08, + 0x69, 0x74, 0x65, 0x72, 0x01, 0x1c, 0x6e, 0x6f, + 0x74, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x01, 0x08, 0x63, 0x61, + 0x6c, 0x6c, 0x0c, 0x00, 0x02, 0x00, 0xa2, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x04, 0x01, + 0xa4, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x43, 0x02, + 0x01, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x01, + 0x03, 0x05, 0xaa, 0x02, 0x00, 0x01, 0x40, 0xa0, + 0x03, 0x00, 0x01, 0x40, 0xc6, 0x03, 0x00, 0x01, + 0x40, 0xcc, 0x01, 0x00, 0x01, 0x40, 0xc8, 0x03, + 0x00, 0x01, 0x40, 0x0c, 0x60, 0x02, 0x01, 0xf8, + 0x01, 0x03, 0x0e, 0x01, 0x06, 0x05, 0x00, 0x86, + 0x04, 0x11, 0xca, 0x03, 0x00, 0x01, 0x00, 0xcc, + 0x03, 0x00, 0x01, 0x00, 0xce, 0x03, 0x00, 0x01, + 0x00, 0xca, 0x03, 0x01, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x20, 0xcc, 0x03, 0x01, 0x01, 0x20, 0xce, + 0x03, 0x01, 0x02, 0x20, 0xd0, 0x03, 0x02, 0x00, + 0x20, 0xd2, 0x03, 0x02, 0x04, 0x20, 0xd4, 0x03, + 0x02, 0x05, 0x20, 0xd6, 0x03, 0x02, 0x06, 0x20, + 0xd8, 0x03, 0x02, 0x07, 0x20, 0x64, 0x06, 0x08, + 0x20, 0x82, 0x01, 0x07, 0x09, 0x20, 0xda, 0x03, + 0x0a, 0x08, 0x30, 0x82, 0x01, 0x0d, 0x0b, 0x20, + 0xd4, 0x01, 0x0d, 0x0c, 0x20, 0x10, 0x00, 0x01, + 0x00, 0xa0, 0x03, 0x01, 0x03, 0xc6, 0x03, 0x02, + 0x03, 0xc8, 0x03, 0x04, 0x03, 0xaa, 0x02, 0x00, + 0x03, 0xcc, 0x01, 0x03, 0x03, 0x08, 0xc4, 0x0d, + 0x62, 0x02, 0x00, 0x62, 0x01, 0x00, 0x62, 0x00, + 0x00, 0xd3, 0xcb, 0xd4, 0x11, 0xf4, 0xec, 0x08, + 0x0e, 0x39, 0x46, 0x00, 0x00, 0x00, 0xdc, 0xcc, + 0xd5, 0x11, 0xf4, 0xec, 0x08, 0x0e, 0x39, 0x46, + 0x00, 0x00, 0x00, 0xdd, 0xcd, 0x62, 0x07, 0x00, + 0x62, 0x06, 0x00, 0x62, 0x05, 0x00, 0x62, 0x04, + 0x00, 0x62, 0x03, 0x00, 0xd4, 0x39, 0x46, 0x00, + 0x00, 0x00, 0xb0, 0xec, 0x16, 0xd4, 0x98, 0x04, + 0x1b, 0x00, 0x00, 0x00, 0xb0, 0xec, 0x0c, 0xdf, + 0x11, 0x04, 0xee, 0x00, 0x00, 0x00, 0x21, 0x01, + 0x00, 0x30, 0x06, 0xce, 0xb6, 0xc4, 0x04, 0xc3, + 0x0d, 0xf7, 0xc4, 0x05, 0x09, 0xc4, 0x06, 0xd3, + 0xe0, 0x48, 0xc4, 0x07, 0x63, 0x07, 0x00, 0x07, + 0xad, 0xec, 0x0f, 0x0a, 0x11, 0x64, 0x06, 0x00, + 0x0e, 0xd3, 0xe1, 0x48, 0x11, 0x64, 0x07, 0x00, + 0x0e, 0x63, 0x07, 0x00, 0x07, 0xad, 0x6a, 0xa6, + 0x00, 0x00, 0x00, 0x62, 0x08, 0x00, 0x06, 0x11, + 0xf4, 0xed, 0x0c, 0x71, 0x43, 0x32, 0x00, 0x00, + 0x00, 0xc4, 0x08, 0x0e, 0xee, 0x05, 0x0e, 0xd3, + 0xee, 0xf2, 0x63, 0x08, 0x00, 0x8e, 0x11, 0xed, + 0x03, 0x0e, 0xb6, 0x11, 0x64, 0x08, 0x00, 0x0e, + 0x63, 0x05, 0x00, 0xec, 0x0c, 0xc3, 0x0d, 0x11, + 0x63, 0x08, 0x00, 0x21, 0x01, 0x00, 0xee, 0x06, + 0xe2, 0x63, 0x08, 0x00, 0xf1, 0x11, 0x64, 0x03, + 0x00, 0x0e, 0x63, 0x04, 0x00, 0x63, 0x08, 0x00, + 0xa7, 0x6a, 0x2a, 0x01, 0x00, 0x00, 0x62, 0x09, + 0x00, 0xd3, 0x63, 0x04, 0x00, 0x48, 0xc4, 0x09, + 0x63, 0x06, 0x00, 0xec, 0x0a, 0x63, 0x09, 0x00, + 0x8c, 0x11, 0x64, 0x09, 0x00, 0x0e, 0xd4, 0xec, + 0x17, 0xd4, 0x43, 0xef, 0x00, 0x00, 0x00, 0xd5, + 0x63, 0x09, 0x00, 0x63, 0x04, 0x00, 0x24, 0x03, + 0x00, 0x8c, 0x11, 0x64, 0x09, 0x00, 0x0e, 0x5f, + 0x04, 0x00, 0x63, 0x03, 0x00, 0x63, 0x04, 0x00, + 0x92, 0x64, 0x04, 0x00, 0x0b, 0x63, 0x09, 0x00, + 0x4d, 0x41, 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3e, + 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3f, 0x00, 0x00, + 0x00, 0xf3, 0x0e, 0xee, 0x9e, 0x62, 0x0a, 0x00, + 0x63, 0x07, 0x00, 0x43, 0xef, 0x00, 0x00, 0x00, + 0xd3, 0x24, 0x01, 0x00, 0xc4, 0x0a, 0x63, 0x05, + 0x00, 0xec, 0x09, 0xc3, 0x0d, 0x11, 0x21, 0x00, + 0x00, 0xee, 0x03, 0xe2, 0xf0, 0x11, 0x64, 0x03, + 0x00, 0x0e, 0x6d, 0x8c, 0x00, 0x00, 0x00, 0x62, + 0x0c, 0x00, 0x62, 0x0b, 0x00, 0x06, 0x11, 0xf4, + 0xed, 0x13, 0x71, 0x43, 0x41, 0x00, 0x00, 0x00, + 0xc4, 0x0b, 0x43, 0x6a, 0x00, 0x00, 0x00, 0xc4, + 0x0c, 0x0e, 0xee, 0x10, 0x0e, 0x63, 0x0a, 0x00, + 0x43, 0x6b, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, + 0x8c, 0xee, 0xe0, 0x63, 0x0c, 0x00, 0xed, 0x4e, + 0x63, 0x06, 0x00, 0xec, 0x0a, 0x63, 0x0b, 0x00, + 0x8c, 0x11, 0x64, 0x0b, 0x00, 0x0e, 0xd4, 0xec, + 0x17, 0xd4, 0x43, 0xef, 0x00, 0x00, 0x00, 0xd5, + 0x63, 0x0b, 0x00, 0x63, 0x04, 0x00, 0x24, 0x03, + 0x00, 0x8c, 0x11, 0x64, 0x0b, 0x00, 0x0e, 0x5f, + 0x04, 0x00, 0x63, 0x03, 0x00, 0x63, 0x04, 0x00, + 0x92, 0x64, 0x04, 0x00, 0x0b, 0x63, 0x0b, 0x00, + 0x4d, 0x41, 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3e, + 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3f, 0x00, 0x00, + 0x00, 0xf3, 0x0e, 0xee, 0x83, 0x0e, 0x06, 0x6e, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0xee, 0x1e, 0x6e, + 0x05, 0x00, 0x00, 0x00, 0x30, 0x63, 0x0a, 0x00, + 0x42, 0x06, 0x00, 0x00, 0x00, 0xec, 0x0d, 0x63, + 0x0a, 0x00, 0x43, 0x06, 0x00, 0x00, 0x00, 0x24, + 0x00, 0x00, 0x0e, 0x6f, 0x63, 0x03, 0x00, 0x63, + 0x04, 0x00, 0x44, 0x32, 0x00, 0x00, 0x00, 0x63, + 0x03, 0x00, 0x2f, 0xc1, 0x00, 0x28, 0xc1, 0x00, + 0xcf, 0x28, +}; + diff --git a/NativeScript/napi/android/quickjs/source_ng/builtin-array-fromasync.js b/NativeScript/napi/android/quickjs/source_ng/builtin-array-fromasync.js new file mode 100755 index 000000000..a07e40eff --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/builtin-array-fromasync.js @@ -0,0 +1,36 @@ +;(function(Array, TypeError, asyncIterator, defineProperty, iterator) { + "use strict" // result.length=i should throw if .length is not writable + return async function fromAsync(arrayLike, mapFn=undefined, thisArg=undefined) { + if (mapFn !== undefined && typeof mapFn !== "function") throw new TypeError("not a function") + let result, i = 0, isConstructor = typeof this === "function" + let sync = false, method = arrayLike[asyncIterator] + if (method == null) sync = true, method = arrayLike[iterator] + if (method == null) { + let {length} = arrayLike + length = +length || 0 + result = isConstructor ? new this(length) : Array(length) + while (i < length) { + let value = arrayLike[i] + if (sync) value = await value + if (mapFn) value = await mapFn.call(thisArg, value, i) + defineProperty(result, i++, {value, configurable: true, writable: true}) + } + } else { + const iter = method.call(arrayLike) + result = isConstructor ? new this() : Array() + try { + for (;;) { + let {value, done} = await iter.next() + if (done) break + if (sync) value = await value + if (mapFn) value = await mapFn.call(thisArg, value, i) + defineProperty(result, i++, {value, configurable: true, writable: true}) + } + } finally { + if (iter.return) iter.return() + } + } + result.length = i + return result + } +}) diff --git a/NativeScript/napi/android/quickjs/source_ng/ctest.c b/NativeScript/napi/android/quickjs/source_ng/ctest.c new file mode 100755 index 000000000..eeb03e869 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/ctest.c @@ -0,0 +1,17 @@ +// note: file is not actually compiled, only checked for C syntax errors +#include "quickjs.h" + +int main(void) +{ + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + JS_FreeValue(ctx, JS_NAN); + JS_FreeValue(ctx, JS_UNDEFINED); + JS_FreeValue(ctx, JS_NewFloat64(ctx, 42)); + // not a legal way of using JS_MKPTR but this is here + // to have the compiler syntax-check its definition + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_UNINITIALIZED, 0)); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source_ng/cutils.c b/NativeScript/napi/android/quickjs/source_ng/cutils.c new file mode 100755 index 000000000..7d36ddfdb --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/cutils.c @@ -0,0 +1,1342 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) +#include +#endif +#if defined(_WIN32) +#include +#include // _beginthread +#endif +#if defined(__APPLE__) +#include +#endif + +#include "cutils.h" + +#undef NANOSEC +#define NANOSEC ((uint64_t) 1e9) + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif + +void js__pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for(;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +/* strcat and truncate. */ +char *js__pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + js__pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +int js__strstart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (*p != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +int js__has_suffix(const char *str, const char *suffix) +{ + size_t len = strlen(str); + size_t slen = strlen(suffix); + return (len >= slen && !memcmp(str + len - slen, suffix, slen)); +} + +/* Dynamic buffer package */ + +static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size) +{ + if (unlikely(size == 0)) { + free(ptr); + return NULL; + } + return realloc(ptr, size); +} + +void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) +{ + memset(s, 0, sizeof(*s)); + if (!realloc_func) + realloc_func = dbuf_default_realloc; + s->opaque = opaque; + s->realloc_func = realloc_func; +} + +void dbuf_init(DynBuf *s) +{ + dbuf_init2(s, NULL, NULL); +} + +/* return < 0 if error */ +int dbuf_realloc(DynBuf *s, size_t new_size) +{ + size_t size; + uint8_t *new_buf; + if (new_size > s->allocated_size) { + if (s->error) + return -1; + size = s->allocated_size * 3 / 2; + if (size > new_size) + new_size = size; + new_buf = s->realloc_func(s->opaque, s->buf, new_size); + if (!new_buf) { + s->error = true; + return -1; + } + s->buf = new_buf; + s->allocated_size = new_size; + } + return 0; +} + +int dbuf_write(DynBuf *s, size_t offset, const void *data, size_t len) +{ + size_t end; + end = offset + len; + if (dbuf_realloc(s, end)) + return -1; + memcpy(s->buf + offset, data, len); + if (end > s->size) + s->size = end; + return 0; +} + +int dbuf_put(DynBuf *s, const void *data, size_t len) +{ + if (unlikely((s->size + len) > s->allocated_size)) { + if (dbuf_realloc(s, s->size + len)) + return -1; + } + if (len > 0) { + memcpy(s->buf + s->size, data, len); + s->size += len; + } + return 0; +} + +int dbuf_put_self(DynBuf *s, size_t offset, size_t len) +{ + if (unlikely((s->size + len) > s->allocated_size)) { + if (dbuf_realloc(s, s->size + len)) + return -1; + } + memcpy(s->buf + s->size, s->buf + offset, len); + s->size += len; + return 0; +} + +int dbuf_putc(DynBuf *s, uint8_t c) +{ + return dbuf_put(s, &c, 1); +} + +int dbuf_putstr(DynBuf *s, const char *str) +{ + return dbuf_put(s, (const uint8_t *)str, strlen(str)); +} + +int JS_PRINTF_FORMAT_ATTR(2, 3) dbuf_printf(DynBuf *s, JS_PRINTF_FORMAT const char *fmt, ...) +{ + va_list ap; + char buf[128]; + int len; + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (len < (int)sizeof(buf)) { + /* fast case */ + return dbuf_put(s, (uint8_t *)buf, len); + } else { + if (dbuf_realloc(s, s->size + len + 1)) + return -1; + va_start(ap, fmt); + vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size, + fmt, ap); + va_end(ap); + s->size += len; + } + return 0; +} + +void dbuf_free(DynBuf *s) +{ + /* we test s->buf as a fail safe to avoid crashing if dbuf_free() + is called twice */ + if (s->buf) { + s->realloc_func(s->opaque, s->buf, 0); + } + memset(s, 0, sizeof(*s)); +} + +/*--- UTF-8 utility functions --*/ + +/* Note: only encode valid codepoints (0x0000..0x10FFFF). + At most UTF8_CHAR_LEN_MAX bytes are output. */ + +/* Compute the number of bytes of the UTF-8 encoding for a codepoint + `c` is a code-point. + Returns the number of bytes. If a codepoint is beyond 0x10FFFF the + return value is 3 as the codepoint would be encoded as 0xFFFD. + */ +size_t utf8_encode_len(uint32_t c) +{ + if (c < 0x80) + return 1; + if (c < 0x800) + return 2; + if (c < 0x10000) + return 3; + if (c < 0x110000) + return 4; + return 3; +} + +/* Encode a codepoint in UTF-8 + `buf` points to an array of at least `UTF8_CHAR_LEN_MAX` bytes + `c` is a code-point. + Returns the number of bytes. If a codepoint is beyond 0x10FFFF the + return value is 3 and the codepoint is encoded as 0xFFFD. + No null byte is stored after the encoded bytes. + Return value is in range 1..4 + */ +size_t utf8_encode(uint8_t buf[minimum_length(UTF8_CHAR_LEN_MAX)], uint32_t c) +{ + if (c < 0x80) { + buf[0] = c; + return 1; + } + if (c < 0x800) { + buf[0] = (c >> 6) | 0xC0; + buf[1] = (c & 0x3F) | 0x80; + return 2; + } + if (c < 0x10000) { + buf[0] = (c >> 12) | 0xE0; + buf[1] = ((c >> 6) & 0x3F) | 0x80; + buf[2] = (c & 0x3F) | 0x80; + return 3; + } + if (c < 0x110000) { + buf[0] = (c >> 18) | 0xF0; + buf[1] = ((c >> 12) & 0x3F) | 0x80; + buf[2] = ((c >> 6) & 0x3F) | 0x80; + buf[3] = (c & 0x3F) | 0x80; + return 4; + } + buf[0] = (0xFFFD >> 12) | 0xE0; + buf[1] = ((0xFFFD >> 6) & 0x3F) | 0x80; + buf[2] = (0xFFFD & 0x3F) | 0x80; + return 3; +} + +/* Decode a single code point from a UTF-8 encoded array of bytes + `p` is a valid pointer to an array of bytes + `pp` is a valid pointer to a `const uint8_t *` to store a pointer + to the byte following the current sequence. + Return the code point at `p`, in the range `0..0x10FFFF` + Return 0xFFFD on error. Only a single byte is consumed in this case + The maximum length for a UTF-8 byte sequence is 4 bytes. + This implements the algorithm specified in whatwg.org, except it accepts + UTF-8 encoded surrogates as JavaScript allows them in strings. + The source string is assumed to have at least UTF8_CHAR_LEN_MAX bytes + or be null terminated. + If `p[0]` is '\0', the return value is `0` and the byte is consumed. + cf: https://encoding.spec.whatwg.org/#utf-8-encoder + */ +uint32_t utf8_decode(const uint8_t *p, const uint8_t **pp) +{ + uint32_t c; + uint8_t lower, upper; + + c = *p++; + if (c < 0x80) { + *pp = p; + return c; + } + switch(c) { + case 0xC2: case 0xC3: + case 0xC4: case 0xC5: case 0xC6: case 0xC7: + case 0xC8: case 0xC9: case 0xCA: case 0xCB: + case 0xCC: case 0xCD: case 0xCE: case 0xCF: + case 0xD0: case 0xD1: case 0xD2: case 0xD3: + case 0xD4: case 0xD5: case 0xD6: case 0xD7: + case 0xD8: case 0xD9: case 0xDA: case 0xDB: + case 0xDC: case 0xDD: case 0xDE: case 0xDF: + if (*p >= 0x80 && *p <= 0xBF) { + *pp = p + 1; + return ((c - 0xC0) << 6) + (*p - 0x80); + } + // otherwise encoding error + break; + case 0xE0: + lower = 0xA0; /* reject invalid encoding */ + goto need2; + case 0xE1: case 0xE2: case 0xE3: + case 0xE4: case 0xE5: case 0xE6: case 0xE7: + case 0xE8: case 0xE9: case 0xEA: case 0xEB: + case 0xEC: case 0xED: case 0xEE: case 0xEF: + lower = 0x80; + need2: + if (*p >= lower && *p <= 0xBF && p[1] >= 0x80 && p[1] <= 0xBF) { + *pp = p + 2; + return ((c - 0xE0) << 12) + ((*p - 0x80) << 6) + (p[1] - 0x80); + } + // otherwise encoding error + break; + case 0xF0: + lower = 0x90; /* reject invalid encoding */ + upper = 0xBF; + goto need3; + case 0xF4: + lower = 0x80; + upper = 0x8F; /* reject values above 0x10FFFF */ + goto need3; + case 0xF1: case 0xF2: case 0xF3: + lower = 0x80; + upper = 0xBF; + need3: + if (*p >= lower && *p <= upper && p[1] >= 0x80 && p[1] <= 0xBF + && p[2] >= 0x80 && p[2] <= 0xBF) { + *pp = p + 3; + return ((c - 0xF0) << 18) + ((*p - 0x80) << 12) + + ((p[1] - 0x80) << 6) + (p[2] - 0x80); + } + // otherwise encoding error + break; + default: + // invalid lead byte + break; + } + *pp = p; + return 0xFFFD; +} + +uint32_t utf8_decode_len(const uint8_t *p, size_t max_len, const uint8_t **pp) { + switch (max_len) { + case 0: + *pp = p; + return 0xFFFD; + case 1: + if (*p < 0x80) + goto good; + break; + case 2: + if (*p < 0xE0) + goto good; + break; + case 3: + if (*p < 0xF0) + goto good; + break; + default: + good: + return utf8_decode(p, pp); + } + *pp = p + 1; + return 0xFFFD; +} + +/* Scan a UTF-8 encoded buffer for content type + `buf` is a valid pointer to a UTF-8 encoded string + `len` is the number of bytes to scan + `plen` points to a `size_t` variable to receive the number of units + Return value is a mask of bits. + - `UTF8_PLAIN_ASCII`: return value for 7-bit ASCII plain text + - `UTF8_NON_ASCII`: bit for non ASCII code points (8-bit or more) + - `UTF8_HAS_16BIT`: bit for 16-bit code points + - `UTF8_HAS_NON_BMP1`: bit for non-BMP1 code points, needs UTF-16 surrogate pairs + - `UTF8_HAS_ERRORS`: bit for encoding errors + */ +int utf8_scan(const char *buf, size_t buf_len, size_t *plen) +{ + const uint8_t *p, *p_end, *p_next; + size_t i, len; + int kind; + uint8_t cbits; + + kind = UTF8_PLAIN_ASCII; + cbits = 0; + len = buf_len; + // TODO: handle more than 1 byte at a time + for (i = 0; i < buf_len; i++) + cbits |= buf[i]; + if (cbits >= 0x80) { + p = (const uint8_t *)buf; + p_end = p + buf_len; + kind = UTF8_NON_ASCII; + len = 0; + while (p < p_end) { + len++; + if (*p++ >= 0x80) { + /* parse UTF-8 sequence, check for encoding error */ + uint32_t c = utf8_decode_len(p - 1, p_end - (p - 1), &p_next); + if (p_next == p) + kind |= UTF8_HAS_ERRORS; + p = p_next; + if (c > 0xFF) { + kind |= UTF8_HAS_16BIT; + if (c > 0xFFFF) { + len++; + kind |= UTF8_HAS_NON_BMP1; + } + } + } + } + } + *plen = len; + return kind; +} + +/* Decode a string encoded in UTF-8 into an array of bytes + `src` points to the source string. It is assumed to be correctly encoded + and only contains code points below 0x800 + `src_len` is the length of the source string + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length of the destination array. A null + terminator is stored at the end of the array unless `dest_len` is `0`. + */ +size_t utf8_decode_buf8(uint8_t *dest, size_t dest_len, const char *src, size_t src_len) +{ + const uint8_t *p, *p_end; + size_t i; + + p = (const uint8_t *)src; + p_end = p + src_len; + for (i = 0; p < p_end; i++) { + uint32_t c = *p++; + if (c >= 0xC0) + c = (c << 6) + *p++ - ((0xC0 << 6) + 0x80); + if (i < dest_len) + dest[i] = c; + } + if (i < dest_len) + dest[i] = '\0'; + else if (dest_len > 0) + dest[dest_len - 1] = '\0'; + return i; +} + +/* Decode a string encoded in UTF-8 into an array of 16-bit words + `src` points to the source string. It is assumed to be correctly encoded. + `src_len` is the length of the source string + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length of the destination array. No null terminator is + stored at the end of the array. + */ +size_t utf8_decode_buf16(uint16_t *dest, size_t dest_len, const char *src, size_t src_len) +{ + const uint8_t *p, *p_end; + size_t i; + + p = (const uint8_t *)src; + p_end = p + src_len; + for (i = 0; p < p_end; i++) { + uint32_t c = *p++; + if (c >= 0x80) { + /* parse utf-8 sequence */ + c = utf8_decode_len(p - 1, p_end - (p - 1), &p); + /* encoding errors are converted as 0xFFFD and use a single byte */ + if (c > 0xFFFF) { + if (i < dest_len) + dest[i] = get_hi_surrogate(c); + i++; + c = get_lo_surrogate(c); + } + } + if (i < dest_len) + dest[i] = c; + } + return i; +} + +/* Encode a buffer of 8-bit bytes as a UTF-8 encoded string + `src` points to the source buffer. + `src_len` is the length of the source buffer + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length in bytes of the destination array. A null + terminator is stored at the end of the array unless `dest_len` is `0`. + */ +size_t utf8_encode_buf8(char *dest, size_t dest_len, const uint8_t *src, size_t src_len) +{ + size_t i, j; + uint32_t c; + + for (i = j = 0; i < src_len; i++) { + c = src[i]; + if (c < 0x80) { + if (j + 1 >= dest_len) + goto overflow; + dest[j++] = c; + } else { + if (j + 2 >= dest_len) + goto overflow; + dest[j++] = (c >> 6) | 0xC0; + dest[j++] = (c & 0x3F) | 0x80; + } + } + if (j < dest_len) + dest[j] = '\0'; + return j; + +overflow: + if (j < dest_len) + dest[j] = '\0'; + while (i < src_len) + j += 1 + (src[i++] >= 0x80); + return j; +} + +/* Encode a buffer of 16-bit code points as a UTF-8 encoded string + `src` points to the source buffer. + `src_len` is the length of the source buffer + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length in bytes of the destination array. A null + terminator is stored at the end of the array unless `dest_len` is `0`. + */ +size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_t src_len) +{ + size_t i, j; + uint32_t c; + + for (i = j = 0; i < src_len;) { + c = src[i++]; + if (c < 0x80) { + if (j + 1 >= dest_len) + goto overflow; + dest[j++] = c; + } else { + if (is_hi_surrogate(c) && i < src_len && is_lo_surrogate(src[i])) + c = from_surrogate(c, src[i++]); + if (j + utf8_encode_len(c) >= dest_len) + goto overflow; + j += utf8_encode((uint8_t *)dest + j, c); + } + } + if (j < dest_len) + dest[j] = '\0'; + return j; + +overflow: + i -= 1 + (c > 0xFFFF); + if (j < dest_len) + dest[j] = '\0'; + while (i < src_len) { + c = src[i++]; + if (c < 0x80) { + j++; + } else { + if (is_hi_surrogate(c) && i < src_len && is_lo_surrogate(src[i])) + c = from_surrogate(c, src[i++]); + j += utf8_encode_len(c); + } + } + return j; +} + +/*---- sorting with opaque argument ----*/ + +typedef void (*exchange_f)(void *a, void *b, size_t size); +typedef int (*cmp_f)(const void *, const void *, void *opaque); + +static void exchange_bytes(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + + while (size-- != 0) { + uint8_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_byte(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + uint8_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int16s(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + + for (size /= sizeof(uint16_t); size-- != 0;) { + uint16_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int16(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + uint16_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int32s(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + + for (size /= sizeof(uint32_t); size-- != 0;) { + uint32_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int32(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + uint32_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int64s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t); size-- != 0;) { + uint64_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int64(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int128s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t) * 2; size-- != 0; ap += 2, bp += 2) { + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; + } +} + +static void exchange_one_int128(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; +} + +static inline exchange_f exchange_func(const void *base, size_t size) { + switch (((uintptr_t)base | (uintptr_t)size) & 15) { + case 0: + if (size == sizeof(uint64_t) * 2) + return exchange_one_int128; + else + return exchange_int128s; + case 8: + if (size == sizeof(uint64_t)) + return exchange_one_int64; + else + return exchange_int64s; + case 4: + case 12: + if (size == sizeof(uint32_t)) + return exchange_one_int32; + else + return exchange_int32s; + case 2: + case 6: + case 10: + case 14: + if (size == sizeof(uint16_t)) + return exchange_one_int16; + else + return exchange_int16s; + default: + if (size == 1) + return exchange_one_byte; + else + return exchange_bytes; + } +} + +static void heapsortx(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + uint8_t *basep = (uint8_t *)base; + size_t i, n, c, r; + exchange_f swap = exchange_func(base, size); + + if (nmemb > 1) { + i = (nmemb / 2) * size; + n = nmemb * size; + + while (i > 0) { + i -= size; + for (r = i; (c = r * 2 + size) < n; r = c) { + if (c < n - size && cmp(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + for (i = n - size; i > 0; i -= size) { + swap(basep, basep + i, size); + + for (r = 0; (c = r * 2 + size) < i; r = c) { + if (c < i - size && cmp(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + } +} + +static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque) +{ + return cmp(a, b, opaque) < 0 ? + (cmp(b, c, opaque) < 0 ? b : (cmp(a, c, opaque) < 0 ? c : a )) : + (cmp(b, c, opaque) > 0 ? b : (cmp(a, c, opaque) < 0 ? a : c )); +} + +/* pointer based version with local stack and insertion sort threshhold */ +void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack; + uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m; + size_t m4, i, lt, gt, span, span2; + int c, depth; + exchange_f swap = exchange_func(base, size); + exchange_f swap_block = exchange_func(base, size | 128); + + if (nmemb < 2 || size <= 0) + return; + + sp->base = (uint8_t *)base; + sp->count = nmemb; + sp->depth = 0; + sp++; + + while (sp > stack) { + sp--; + ptr = sp->base; + nmemb = sp->count; + depth = sp->depth; + + while (nmemb > 6) { + if (++depth > 50) { + /* depth check to ensure worst case logarithmic time */ + heapsortx(ptr, nmemb, size, cmp, opaque); + nmemb = 0; + break; + } + /* select median of 3 from 1/4, 1/2, 3/4 positions */ + /* should use median of 5 or 9? */ + m4 = (nmemb >> 2) * size; + m = med3(ptr + m4, ptr + 2 * m4, ptr + 3 * m4, cmp, opaque); + swap(ptr, m, size); /* move the pivot to the start or the array */ + i = lt = 1; + pi = plt = ptr + size; + gt = nmemb; + pj = pgt = top = ptr + nmemb * size; + for (;;) { + while (pi < pj && (c = cmp(ptr, pi, opaque)) >= 0) { + if (c == 0) { + swap(plt, pi, size); + lt++; + plt += size; + } + i++; + pi += size; + } + while (pi < (pj -= size) && (c = cmp(ptr, pj, opaque)) <= 0) { + if (c == 0) { + gt--; + pgt -= size; + swap(pgt, pj, size); + } + } + if (pi >= pj) + break; + swap(pi, pj, size); + i++; + pi += size; + } + /* array has 4 parts: + * from 0 to lt excluded: elements identical to pivot + * from lt to pi excluded: elements smaller than pivot + * from pi to gt excluded: elements greater than pivot + * from gt to n excluded: elements identical to pivot + */ + /* move elements identical to pivot in the middle of the array: */ + /* swap values in ranges [0..lt[ and [i-lt..i[ + swapping the smallest span between lt and i-lt is sufficient + */ + span = plt - ptr; + span2 = pi - plt; + lt = i - lt; + if (span > span2) + span = span2; + swap_block(ptr, pi - span, span); + /* swap values in ranges [gt..top[ and [i..top-(top-gt)[ + swapping the smallest span between top-gt and gt-i is sufficient + */ + span = top - pgt; + span2 = pgt - pi; + pgt = top - span2; + gt = nmemb - (gt - i); + if (span > span2) + span = span2; + swap_block(pi, top - span, span); + + /* now array has 3 parts: + * from 0 to lt excluded: elements smaller than pivot + * from lt to gt excluded: elements identical to pivot + * from gt to n excluded: elements greater than pivot + */ + /* stack the larger segment and keep processing the smaller one + to minimize stack use for pathological distributions */ + if (lt > nmemb - gt) { + sp->base = ptr; + sp->count = lt; + sp->depth = depth; + sp++; + ptr = pgt; + nmemb -= gt; + } else { + sp->base = pgt; + sp->count = nmemb - gt; + sp->depth = depth; + sp++; + nmemb = lt; + } + } + /* Use insertion sort for small fragments */ + for (pi = ptr + size, top = ptr + nmemb * size; pi < top; pi += size) { + for (pj = pi; pj > ptr && cmp(pj - size, pj, opaque) > 0; pj -= size) + swap(pj, pj - size, size); + } + } +} + +/*---- Portable time functions ----*/ + +#ifdef _WIN32 + // From: https://stackoverflow.com/a/26085827 +static int gettimeofday_msvc(struct timeval *tp) +{ + static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + time = ((uint64_t)file_time.dwLowDateTime); + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long)((time - EPOCH) / 10000000L); + tp->tv_usec = (long)(system_time.wMilliseconds * 1000); + + return 0; +} + +uint64_t js__hrtime_ns(void) { + LARGE_INTEGER counter, frequency; + double scaled_freq; + double result; + + if (!QueryPerformanceFrequency(&frequency)) + abort(); + assert(frequency.QuadPart != 0); + + if (!QueryPerformanceCounter(&counter)) + abort(); + assert(counter.QuadPart != 0); + + /* Because we have no guarantee about the order of magnitude of the + * performance counter interval, integer math could cause this computation + * to overflow. Therefore we resort to floating point math. + */ + scaled_freq = (double) frequency.QuadPart / NANOSEC; + result = (double) counter.QuadPart / scaled_freq; + return (uint64_t) result; +} +#else +uint64_t js__hrtime_ns(void) { + struct timespec t; + + if (clock_gettime(CLOCK_MONOTONIC, &t)) + abort(); + + return t.tv_sec * NANOSEC + t.tv_nsec; +} +#endif + +int64_t js__gettimeofday_us(void) { + struct timeval tv; +#ifdef _WIN32 + gettimeofday_msvc(&tv); +#else + gettimeofday(&tv, NULL); +#endif + return ((int64_t)tv.tv_sec * 1000000) + tv.tv_usec; +} + +#if defined(_WIN32) +int js_exepath(char *buffer, size_t *size_ptr) { + int utf8_len, utf16_buffer_len, utf16_len; + WCHAR* utf16_buffer; + + if (buffer == NULL || size_ptr == NULL || *size_ptr == 0) + return -1; + + if (*size_ptr > 32768) { + /* Windows paths can never be longer than this. */ + utf16_buffer_len = 32768; + } else { + utf16_buffer_len = (int)*size_ptr; + } + + utf16_buffer = malloc(sizeof(WCHAR) * utf16_buffer_len); + if (!utf16_buffer) + return -1; + + /* Get the path as UTF-16. */ + utf16_len = GetModuleFileNameW(NULL, utf16_buffer, utf16_buffer_len); + if (utf16_len <= 0) + goto error; + + /* Convert to UTF-8 */ + utf8_len = WideCharToMultiByte(CP_UTF8, + 0, + utf16_buffer, + -1, + buffer, + (int)*size_ptr, + NULL, + NULL); + if (utf8_len == 0) + goto error; + + free(utf16_buffer); + + /* utf8_len *does* include the terminating null at this point, but the + * returned size shouldn't. */ + *size_ptr = utf8_len - 1; + return 0; + +error: + free(utf16_buffer); + return -1; +} +#elif defined(__APPLE__) +int js_exepath(char *buffer, size_t *size) { + /* realpath(exepath) may be > PATH_MAX so double it to be on the safe side. */ + char abspath[PATH_MAX * 2 + 1]; + char exepath[PATH_MAX + 1]; + uint32_t exepath_size; + size_t abspath_size; + + if (buffer == NULL || size == NULL || *size == 0) + return -1; + + exepath_size = sizeof(exepath); + if (_NSGetExecutablePath(exepath, &exepath_size)) + return -1; + + if (realpath(exepath, abspath) != abspath) + return -1; + + abspath_size = strlen(abspath); + if (abspath_size == 0) + return -1; + + *size -= 1; + if (*size > abspath_size) + *size = abspath_size; + + memcpy(buffer, abspath, *size); + buffer[*size] = '\0'; + + return 0; +} +#elif defined(__linux__) || defined(__GNU__) +int js_exepath(char *buffer, size_t *size) { + ssize_t n; + + if (buffer == NULL || size == NULL || *size == 0) + return -1; + + n = *size - 1; + if (n > 0) + n = readlink("/proc/self/exe", buffer, n); + + if (n == -1) + return n; + + buffer[n] = '\0'; + *size = n; + + return 0; +} +#else +int js_exepath(char* buffer, size_t* size_ptr) { + return -1; +} +#endif + +/*--- Cross-platform threading APIs. ----*/ + +#if JS_HAVE_THREADS +#if defined(_WIN32) +typedef void (*js__once_cb)(void); + +typedef struct { + js__once_cb callback; +} js__once_data_t; + +static int WINAPI js__once_inner(INIT_ONCE *once, void *param, void **context) { + js__once_data_t *data = param; + + data->callback(); + + return 1; +} + +void js_once(js_once_t *guard, js__once_cb callback) { + js__once_data_t data = { .callback = callback }; + InitOnceExecuteOnce(guard, js__once_inner, (void*) &data, NULL); +} + +void js_mutex_init(js_mutex_t *mutex) { + InitializeCriticalSection(mutex); +} + +void js_mutex_destroy(js_mutex_t *mutex) { + DeleteCriticalSection(mutex); +} + +void js_mutex_lock(js_mutex_t *mutex) { + EnterCriticalSection(mutex); +} + +void js_mutex_unlock(js_mutex_t *mutex) { + LeaveCriticalSection(mutex); +} + +void js_cond_init(js_cond_t *cond) { + InitializeConditionVariable(cond); +} + +void js_cond_destroy(js_cond_t *cond) { + /* nothing to do */ + (void) cond; +} + +void js_cond_signal(js_cond_t *cond) { + WakeConditionVariable(cond); +} + +void js_cond_broadcast(js_cond_t *cond) { + WakeAllConditionVariable(cond); +} + +void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { + if (!SleepConditionVariableCS(cond, mutex, INFINITE)) + abort(); +} + +int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { + if (SleepConditionVariableCS(cond, mutex, (DWORD)(timeout / 1e6))) + return 0; + if (GetLastError() != ERROR_TIMEOUT) + abort(); + return -1; +} + +int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg, + int flags) +{ + HANDLE h, cp; + + *thrd = INVALID_HANDLE_VALUE; + if (flags & ~JS_THREAD_CREATE_DETACHED) + return -1; + h = (HANDLE)_beginthread(start, /*stacksize*/2<<20, arg); + if (!h) + return -1; + if (flags & JS_THREAD_CREATE_DETACHED) + return 0; + // _endthread() automatically closes the handle but we want to wait on + // it so make a copy. Race-y for very short-lived threads. Can be solved + // by switching to _beginthreadex(CREATE_SUSPENDED) but means changing + // |start| from __cdecl to __stdcall. + cp = GetCurrentProcess(); + if (DuplicateHandle(cp, h, cp, thrd, 0, FALSE, DUPLICATE_SAME_ACCESS)) + return 0; + return -1; +} + +int js_thread_join(js_thread_t thrd) +{ + if (WaitForSingleObject(thrd, INFINITE)) + return -1; + CloseHandle(thrd); + return 0; +} + +#else /* !defined(_WIN32) */ + +void js_once(js_once_t *guard, void (*callback)(void)) { + if (pthread_once(guard, callback)) + abort(); +} + +void js_mutex_init(js_mutex_t *mutex) { + if (pthread_mutex_init(mutex, NULL)) + abort(); +} + +void js_mutex_destroy(js_mutex_t *mutex) { + if (pthread_mutex_destroy(mutex)) + abort(); +} + +void js_mutex_lock(js_mutex_t *mutex) { + if (pthread_mutex_lock(mutex)) + abort(); +} + +void js_mutex_unlock(js_mutex_t *mutex) { + if (pthread_mutex_unlock(mutex)) + abort(); +} + +void js_cond_init(js_cond_t *cond) { +#if defined(__APPLE__) && defined(__MACH__) + if (pthread_cond_init(cond, NULL)) + abort(); +#else + pthread_condattr_t attr; + + if (pthread_condattr_init(&attr)) + abort(); + + if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) + abort(); + + if (pthread_cond_init(cond, &attr)) + abort(); + + if (pthread_condattr_destroy(&attr)) + abort(); +#endif +} + +void js_cond_destroy(js_cond_t *cond) { +#if defined(__APPLE__) && defined(__MACH__) + /* It has been reported that destroying condition variables that have been + * signalled but not waited on can sometimes result in application crashes. + * See https://codereview.chromium.org/1323293005. + */ + pthread_mutex_t mutex; + struct timespec ts; + int err; + + if (pthread_mutex_init(&mutex, NULL)) + abort(); + + if (pthread_mutex_lock(&mutex)) + abort(); + + ts.tv_sec = 0; + ts.tv_nsec = 1; + + err = pthread_cond_timedwait_relative_np(cond, &mutex, &ts); + if (err != 0 && err != ETIMEDOUT) + abort(); + + if (pthread_mutex_unlock(&mutex)) + abort(); + + if (pthread_mutex_destroy(&mutex)) + abort(); +#endif /* defined(__APPLE__) && defined(__MACH__) */ + + if (pthread_cond_destroy(cond)) + abort(); +} + +void js_cond_signal(js_cond_t *cond) { + if (pthread_cond_signal(cond)) + abort(); +} + +void js_cond_broadcast(js_cond_t *cond) { + if (pthread_cond_broadcast(cond)) + abort(); +} + +void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { +#if defined(__APPLE__) && defined(__MACH__) + int r; + + errno = 0; + r = pthread_cond_wait(cond, mutex); + + /* Workaround for a bug in OS X at least up to 13.6 + * See https://github.com/libuv/libuv/issues/4165 + */ + if (r == EINVAL && errno == EBUSY) + return; + if (r) + abort(); +#else + if (pthread_cond_wait(cond, mutex)) + abort(); +#endif +} + +int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { + int r; + struct timespec ts; + +#if !defined(__APPLE__) + timeout += js__hrtime_ns(); +#endif + + ts.tv_sec = timeout / NANOSEC; + ts.tv_nsec = timeout % NANOSEC; +#if defined(__APPLE__) && defined(__MACH__) + r = pthread_cond_timedwait_relative_np(cond, mutex, &ts); +#else + r = pthread_cond_timedwait(cond, mutex, &ts); +#endif + + if (r == 0) + return 0; + + if (r == ETIMEDOUT) + return -1; + + abort(); + + /* Pacify some compilers. */ + return -1; +} + +int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg, + int flags) +{ + union { + void (*x)(void *); + void *(*f)(void *); + } u = {start}; + pthread_attr_t attr; + int ret; + + if (flags & ~JS_THREAD_CREATE_DETACHED) + return -1; + if (pthread_attr_init(&attr)) + return -1; + ret = -1; + if (pthread_attr_setstacksize(&attr, 2<<20)) + goto fail; + if (flags & JS_THREAD_CREATE_DETACHED) + if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) + goto fail; + if (pthread_create(thrd, &attr, u.f, arg)) + goto fail; + ret = 0; +fail: + pthread_attr_destroy(&attr); + return ret; +} + +int js_thread_join(js_thread_t thrd) +{ + if (pthread_join(thrd, NULL)) + return -1; + return 0; +} + +#endif /* !defined(_WIN32) */ +#endif /* JS_HAVE_THREADS */ + +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif diff --git a/NativeScript/napi/android/quickjs/source_ng/cutils.h b/NativeScript/napi/android/quickjs/source_ng/cutils.h new file mode 100755 index 000000000..f5d2a8bce --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/cutils.h @@ -0,0 +1,660 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef CUTILS_H +#define CUTILS_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) +#include +#include +#define alloca _alloca +#define ssize_t ptrdiff_t +#endif +#if defined(__APPLE__) +#include +#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) || defined(__GLIBC__) +#include +#elif defined(__FreeBSD__) +#include +#elif defined(_WIN32) +#include +#endif +#if !defined(_WIN32) && !defined(EMSCRIPTEN) && !defined(__wasi__) +#include +#include +#endif +#if !defined(_WIN32) +#include +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +# define likely(x) (x) +# define unlikely(x) (x) +# define force_inline __forceinline +# define no_inline __declspec(noinline) +# define __maybe_unused +# define __attribute__(x) +# define __attribute(x) +#else +# define likely(x) __builtin_expect(!!(x), 1) +# define unlikely(x) __builtin_expect(!!(x), 0) +# define force_inline inline __attribute__((always_inline)) +# define no_inline __attribute__((noinline)) +# define __maybe_unused __attribute__((unused)) +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#include +#define INF INFINITY +#define NEG_INF -INFINITY +#else +#define INF (1.0/0.0) +#define NEG_INF (-1.0/0.0) +#endif + +#ifndef offsetof +#define offsetof(type, field) ((size_t) &((type *)0)->field) +#endif +#ifndef countof +#define countof(x) (sizeof(x) / sizeof((x)[0])) +#ifndef endof +#define endof(x) ((x) + countof(x)) +#endif +#endif +#ifndef container_of +/* return the pointer of type 'type *' containing 'ptr' as field 'member' */ +#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member))) +#endif + +#if defined(_MSC_VER) || defined(__cplusplus) +#define minimum_length(n) n +#else +#define minimum_length(n) static n +#endif + +/* Borrowed from Folly */ +#ifndef JS_PRINTF_FORMAT +#ifdef _MSC_VER +#include +#define JS_PRINTF_FORMAT _Printf_format_string_ +#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) +#else +#define JS_PRINTF_FORMAT +#if !defined(__clang__) && defined(__GNUC__) +#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) \ + __attribute__((format(gnu_printf, format_param, dots_param))) +#else +#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) \ + __attribute__((format(printf, format_param, dots_param))) +#endif +#endif +#endif + +#if defined(PATH_MAX) +# define JS__PATH_MAX PATH_MAX +#elif defined(_WIN32) +# define JS__PATH_MAX 32767 +#else +# define JS__PATH_MAX 8192 +#endif + +void js__pstrcpy(char *buf, int buf_size, const char *str); +char *js__pstrcat(char *buf, int buf_size, const char *s); +int js__strstart(const char *str, const char *val, const char **ptr); +int js__has_suffix(const char *str, const char *suffix); + +static inline uint8_t is_be(void) { + union { + uint16_t a; + uint8_t b; + } u = { 0x100 }; + return u.b; +} + +static inline int max_int(int a, int b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int min_int(int a, int b) +{ + if (a < b) + return a; + else + return b; +} + +static inline uint32_t max_uint32(uint32_t a, uint32_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline uint32_t min_uint32(uint32_t a, uint32_t b) +{ + if (a < b) + return a; + else + return b; +} + +static inline int64_t max_int64(int64_t a, int64_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int64_t min_int64(int64_t a, int64_t b) +{ + if (a < b) + return a; + else + return b; +} + +/* WARNING: undefined if a = 0 */ +static inline int clz32(unsigned int a) +{ +#if defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + _BitScanReverse(&index, a); + return 31 - index; +#else + return __builtin_clz(a); +#endif +} + +/* WARNING: undefined if a = 0 */ +static inline int clz64(uint64_t a) +{ +#if defined(_MSC_VER) && !defined(__clang__) +#if INTPTR_MAX == INT64_MAX + unsigned long index; + _BitScanReverse64(&index, a); + return 63 - index; +#else + if (a >> 32) + return clz32((unsigned)(a >> 32)); + else + return clz32((unsigned)a) + 32; +#endif +#else + return __builtin_clzll(a); +#endif +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz32(unsigned int a) +{ +#if defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + _BitScanForward(&index, a); + return index; +#else + return __builtin_ctz(a); +#endif +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz64(uint64_t a) +{ +#if defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + _BitScanForward64(&index, a); + return index; +#else + return __builtin_ctzll(a); +#endif +} + +static inline uint64_t get_u64(const uint8_t *tab) +{ + uint64_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline int64_t get_i64(const uint8_t *tab) +{ + int64_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline void put_u64(uint8_t *tab, uint64_t val) +{ + memcpy(tab, &val, sizeof(val)); +} + +static inline uint32_t get_u32(const uint8_t *tab) +{ + uint32_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline int32_t get_i32(const uint8_t *tab) +{ + int32_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline void put_u32(uint8_t *tab, uint32_t val) +{ + memcpy(tab, &val, sizeof(val)); +} + +static inline uint32_t get_u16(const uint8_t *tab) +{ + uint16_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline int32_t get_i16(const uint8_t *tab) +{ + int16_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline void put_u16(uint8_t *tab, uint16_t val) +{ + memcpy(tab, &val, sizeof(val)); +} + +static inline uint32_t get_u8(const uint8_t *tab) +{ + return *tab; +} + +static inline int32_t get_i8(const uint8_t *tab) +{ + return (int8_t)*tab; +} + +static inline void put_u8(uint8_t *tab, uint8_t val) +{ + *tab = val; +} + +#ifndef bswap16 +static inline uint16_t bswap16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} +#endif + +#ifndef bswap32 +static inline uint32_t bswap32(uint32_t v) +{ + return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | + ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); +} +#endif + +#ifndef bswap64 +static inline uint64_t bswap64(uint64_t v) +{ + return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | + ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | + ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | + ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | + ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | + ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | + ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | + ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); +} +#endif + +static inline void inplace_bswap16(uint8_t *tab) { + put_u16(tab, bswap16(get_u16(tab))); +} + +static inline void inplace_bswap32(uint8_t *tab) { + put_u32(tab, bswap32(get_u32(tab))); +} + +static inline double fromfp16(uint16_t v) { + double d, s; + int e; + if ((v & 0x7C00) == 0x7C00) { + d = (v & 0x3FF) ? NAN : INFINITY; + } else { + d = (v & 0x3FF) / 1024.; + e = (v & 0x7C00) >> 10; + if (e == 0) { + e = -14; + } else { + d += 1; + e -= 15; + } + d = scalbn(d, e); + } + s = (v & 0x8000) ? -1.0 : 1.0; + return d * s; +} + +static inline uint16_t tofp16(double d) { + uint16_t f, s; + double t; + int e; + s = 0; + if (copysign(1, d) < 0) { // preserve sign when |d| is negative zero + d = -d; + s = 0x8000; + } + if (isinf(d)) + return s | 0x7C00; + if (isnan(d)) + return s | 0x7C01; + if (d == 0) + return s | 0; + d = 2 * frexp(d, &e); + e--; + if (e > 15) + return s | 0x7C00; // out of range, return +/-infinity + if (e < -25) { + d = 0; + e = 0; + } else if (e < -14) { + d = scalbn(d, e + 14); + e = 0; + } else { + d -= 1; + e += 15; + } + d *= 1024.; + f = (uint16_t)d; + t = d - f; + if (t < 0.5) + goto done; + if (t == 0.5) + if ((f & 1) == 0) + goto done; + // adjust for rounding + if (++f == 1024) { + f = 0; + if (++e == 31) + return s | 0x7C00; // out of range, return +/-infinity + } +done: + return s | (e << 10) | f; +} + +static inline int isfp16nan(uint16_t v) { + return (v & 0x7FFF) > 0x7C00; +} + +static inline int isfp16zero(uint16_t v) { + return (v & 0x7FFF) == 0; +} + +/* XXX: should take an extra argument to pass slack information to the caller */ +typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size); + +typedef struct DynBuf { + uint8_t *buf; + size_t size; + size_t allocated_size; + bool error; /* true if a memory allocation error occurred */ + DynBufReallocFunc *realloc_func; + void *opaque; /* for realloc_func */ +} DynBuf; + +void dbuf_init(DynBuf *s); +void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func); +int dbuf_realloc(DynBuf *s, size_t new_size); +int dbuf_write(DynBuf *s, size_t offset, const void *data, size_t len); +int dbuf_put(DynBuf *s, const void *data, size_t len); +int dbuf_put_self(DynBuf *s, size_t offset, size_t len); +int dbuf_putc(DynBuf *s, uint8_t c); +int dbuf_putstr(DynBuf *s, const char *str); +static inline int dbuf_put_u16(DynBuf *s, uint16_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 2); +} +static inline int dbuf_put_u32(DynBuf *s, uint32_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 4); +} +static inline int dbuf_put_u64(DynBuf *s, uint64_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 8); +} +int JS_PRINTF_FORMAT_ATTR(2, 3) dbuf_printf(DynBuf *s, JS_PRINTF_FORMAT const char *fmt, ...); +void dbuf_free(DynBuf *s); +static inline bool dbuf_error(DynBuf *s) { + return s->error; +} +static inline void dbuf_set_error(DynBuf *s) +{ + s->error = true; +} + +/*---- UTF-8 and UTF-16 handling ----*/ + +#define UTF8_CHAR_LEN_MAX 4 + +enum { + UTF8_PLAIN_ASCII = 0, // 7-bit ASCII plain text + UTF8_NON_ASCII = 1, // has non ASCII code points (8-bit or more) + UTF8_HAS_16BIT = 2, // has 16-bit code points + UTF8_HAS_NON_BMP1 = 4, // has non-BMP1 code points, needs UTF-16 surrogate pairs + UTF8_HAS_ERRORS = 8, // has encoding errors +}; +int utf8_scan(const char *buf, size_t len, size_t *plen); +size_t utf8_encode_len(uint32_t c); +size_t utf8_encode(uint8_t buf[minimum_length(UTF8_CHAR_LEN_MAX)], uint32_t c); +uint32_t utf8_decode_len(const uint8_t *p, size_t max_len, const uint8_t **pp); +uint32_t utf8_decode(const uint8_t *p, const uint8_t **pp); +size_t utf8_decode_buf8(uint8_t *dest, size_t dest_len, const char *src, size_t src_len); +size_t utf8_decode_buf16(uint16_t *dest, size_t dest_len, const char *src, size_t src_len); +size_t utf8_encode_buf8(char *dest, size_t dest_len, const uint8_t *src, size_t src_len); +size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_t src_len); + +static inline bool is_surrogate(uint32_t c) +{ + return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF +} + +static inline bool is_hi_surrogate(uint32_t c) +{ + return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF +} + +static inline bool is_lo_surrogate(uint32_t c) +{ + return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF +} + +static inline uint32_t get_hi_surrogate(uint32_t c) +{ + return (c >> 10) - (0x10000 >> 10) + 0xD800; +} + +static inline uint32_t get_lo_surrogate(uint32_t c) +{ + return (c & 0x3FF) | 0xDC00; +} + +static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo) +{ + return 65536 + 1024 * (hi & 1023) + (lo & 1023); +} + +static inline int from_hex(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +static inline uint8_t is_upper_ascii(uint8_t c) { + return c >= 'A' && c <= 'Z'; +} + +static inline uint8_t to_upper_ascii(uint8_t c) { + return c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c; +} + +void rqsort(void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *, void *), + void *arg); + +static inline uint64_t float64_as_uint64(double d) +{ + union { + double d; + uint64_t u64; + } u; + u.d = d; + return u.u64; +} + +static inline double uint64_as_float64(uint64_t u64) +{ + union { + double d; + uint64_t u64; + } u; + u.u64 = u64; + return u.d; +} + +int64_t js__gettimeofday_us(void); +uint64_t js__hrtime_ns(void); + +static inline size_t js__malloc_usable_size(const void *ptr) +{ +#if defined(__APPLE__) + return malloc_size(ptr); +#elif defined(_WIN32) + return _msize((void *)ptr); +#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__GLIBC__) + return malloc_usable_size((void *)ptr); +#else + return 0; +#endif +} + +int js_exepath(char* buffer, size_t* size); + +/* Cross-platform threading APIs. */ + +#if defined(EMSCRIPTEN) || defined(__wasi__) + +#define JS_HAVE_THREADS 0 + +#else + +#define JS_HAVE_THREADS 1 + +#if defined(_WIN32) +#define JS_ONCE_INIT INIT_ONCE_STATIC_INIT +typedef INIT_ONCE js_once_t; +typedef CRITICAL_SECTION js_mutex_t; +typedef CONDITION_VARIABLE js_cond_t; +typedef HANDLE js_thread_t; +#else +#define JS_ONCE_INIT PTHREAD_ONCE_INIT +typedef pthread_once_t js_once_t; +typedef pthread_mutex_t js_mutex_t; +typedef pthread_cond_t js_cond_t; +typedef pthread_t js_thread_t; +#endif + +void js_once(js_once_t *guard, void (*callback)(void)); + +void js_mutex_init(js_mutex_t *mutex); +void js_mutex_destroy(js_mutex_t *mutex); +void js_mutex_lock(js_mutex_t *mutex); +void js_mutex_unlock(js_mutex_t *mutex); + +void js_cond_init(js_cond_t *cond); +void js_cond_destroy(js_cond_t *cond); +void js_cond_signal(js_cond_t *cond); +void js_cond_broadcast(js_cond_t *cond); +void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex); +int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout); + +enum { + JS_THREAD_CREATE_DETACHED = 1, +}; + +// creates threads with 2 MB stacks (glibc default) +int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg, + int flags); +int js_thread_join(js_thread_t thrd); + +#endif /* !defined(EMSCRIPTEN) && !defined(__wasi__) */ + +// JS requires strict rounding behavior. Turn on 64-bits double precision +// and disable x87 80-bits extended precision for intermediate floating-point +// results. 0x300 is extended precision, 0x200 is double precision. +// Note that `*&cw` in the asm constraints looks redundant but isn't. +#if defined(__i386__) && !defined(_MSC_VER) +#define JS_X87_FPCW_SAVE_AND_ADJUST(cw) \ + unsigned short cw; \ + __asm__ __volatile__("fnstcw %0" : "=m"(*&cw)); \ + do { \ + unsigned short t = 0x200 | (cw & ~0x300); \ + __asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&t)); \ + } while (0) +#define JS_X87_FPCW_RESTORE(cw) \ + __asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&cw)) +#else +#define JS_X87_FPCW_SAVE_AND_ADJUST(cw) +#define JS_X87_FPCW_RESTORE(cw) +#endif + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* CUTILS_H */ diff --git a/NativeScript/napi/android/quickjs/source_ng/cxxtest.cc b/NativeScript/napi/android/quickjs/source_ng/cxxtest.cc new file mode 100755 index 000000000..b6151009a --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/cxxtest.cc @@ -0,0 +1,2 @@ +// note: file is not actually compiled, only checked for C++ syntax errors +#include "ctest.c" diff --git a/NativeScript/napi/android/quickjs/source_ng/dtoa.c b/NativeScript/napi/android/quickjs/source_ng/dtoa.c new file mode 100755 index 000000000..a89e824f9 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/dtoa.c @@ -0,0 +1,1619 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +// #include +#include +// #include + +#include "cutils.h" +#include "dtoa.h" + +/* + TODO: + - simplify subnormal handling + - reduce max memory usage + - free format: could add shortcut if exact result + - use 64 bit limb_t when possible + - use another algorithm for free format dtoa in base 10 (ryu ?) +*/ + +#define USE_POW5_TABLE +/* use fast path to print small integers in free format */ +#define USE_FAST_INT + +#define LIMB_LOG2_BITS 5 + +#define LIMB_BITS (1 << LIMB_LOG2_BITS) + +typedef int32_t slimb_t; +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; + +#define LIMB_DIGITS 9 + +#define JS_RADIX_MAX 36 + +#define DBIGNUM_LEN_MAX 52 /* ~ 2^(1072+53)*36^100 (dtoa) */ +#define MANT_LEN_MAX 18 /* < 36^100 */ + +typedef intptr_t mp_size_t; + +/* the represented number is sum(i, tab[i]*2^(LIMB_BITS * i)) */ +typedef struct { + int len; /* >= 1 */ + limb_t tab[]; +} mpb_t; + +static limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n) +{ + size_t i; + limb_t k, a; + + k=b; + for(i=0;i> LIMB_BITS; + } + return l; +} + +/* WARNING: d must be >= 2^(LIMB_BITS-1) */ +static inline limb_t udiv1norm_init(limb_t d) +{ + limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((dlimb_t)a1 << LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0, + limb_t d, limb_t d_inv) +{ + limb_t n1m, n_adj, q, r, ah; + dlimb_t a; + n1m = ((slimb_t)a0 >> (LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is between + 0 and d - 1 */ + a = ((dlimb_t)a1 << LIMB_BITS) | a0; + a = a - (dlimb_t)q * d - d; + ah = a >> LIMB_BITS; + q += 1 + ah; + r = (limb_t)a + (ah & d); + *pr = r; + return q; +} + +static limb_t mp_div1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r) +{ + slimb_t i; + dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((dlimb_t)r << LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + return r; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t high) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + tab_r[i] = (a >> shift) | (l << (LIMB_BITS - shift)); + l = a; + } + return l & (((limb_t)1 << shift) - 1); +} + +/* r = (a << shift) + low. 1 <= shift <= LIMB_BITS - 1, 0 <= low < + 2^shift. */ +static limb_t mp_shl(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t low) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = low; + for(i = 0; i < n; i++) { + a = tab[i]; + tab_r[i] = (a << shift) | l; + l = (a >> (LIMB_BITS - shift)); + } + return l; +} + +static no_inline limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r, limb_t b_inv, int shift) +{ + slimb_t i; + + if (shift != 0) { + r = (r << shift) | mp_shl(tabr, taba, n, shift, 0); + } + for(i = n - 1; i >= 0; i--) { + tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); + } + r >>= shift; + return r; +} + +static __maybe_unused void mpb_dump(const char *str, const mpb_t *a) +{ + int i; + + printf("%s= 0x", str); + for(i = a->len - 1; i >= 0; i--) { + printf("%08x", a->tab[i]); + if (i != 0) + printf("_"); + } + printf("\n"); +} + +static void mpb_renorm(mpb_t *r) +{ + while (r->len > 1 && r->tab[r->len - 1] == 0) + r->len--; +} + +#ifdef USE_POW5_TABLE +static const uint32_t pow5_table[17] = { + 0x00000005, 0x00000019, 0x0000007d, 0x00000271, + 0x00000c35, 0x00003d09, 0x0001312d, 0x0005f5e1, + 0x001dcd65, 0x009502f9, 0x02e90edd, 0x0e8d4a51, + 0x48c27395, 0x6bcc41e9, 0x1afd498d, 0x86f26fc1, + 0xa2bc2ec5, +}; + +static const uint8_t pow5h_table[4] = { + 0x00000001, 0x00000007, 0x00000023, 0x000000b1, +}; + +static const uint32_t pow5_inv_table[13] = { + 0x99999999, 0x47ae147a, 0x0624dd2f, 0xa36e2eb1, + 0x4f8b588e, 0x0c6f7a0b, 0xad7f29ab, 0x5798ee23, + 0x12e0be82, 0xb7cdfd9d, 0x5fd7fe17, 0x19799812, + 0xc25c2684, +}; +#endif + +/* return a^b */ +static uint64_t pow_ui(uint32_t a, uint32_t b) +{ + int i, n_bits; + uint64_t r; + if (b == 0) + return 1; + if (b == 1) + return a; +#ifdef USE_POW5_TABLE + if ((a == 5 || a == 10) && b <= 17) { + r = pow5_table[b - 1]; + if (b >= 14) { + r |= (uint64_t)pow5h_table[b - 14] << 32; + } + if (a == 10) + r <<= b; + return r; + } +#endif + r = a; + n_bits = 32 - clz32(b); + for(i = n_bits - 2; i >= 0; i--) { + r *= r; + if ((b >> i) & 1) + r *= a; + } + return r; +} + +static uint32_t pow_ui_inv(uint32_t *pr_inv, int *pshift, uint32_t a, uint32_t b) +{ + uint32_t r_inv, r; + int shift; +#ifdef USE_POW5_TABLE + if (a == 5 && b >= 1 && b <= 13) { + r = pow5_table[b - 1]; + shift = clz32(r); + r <<= shift; + r_inv = pow5_inv_table[b - 1]; + } else +#endif + { + r = pow_ui(a, b); + shift = clz32(r); + r <<= shift; + r_inv = udiv1norm_init(r); + } + *pshift = shift; + *pr_inv = r_inv; + return r; +} + +enum { + JS_RNDN, /* round to nearest, ties to even */ + JS_RNDNA, /* round to nearest, ties away from zero */ + JS_RNDZ, +}; + +static int mpb_get_bit(const mpb_t *r, int k) +{ + int l; + + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + if (l >= r->len) + return 0; + else + return (r->tab[l] >> k) & 1; +} + +/* compute round(r / 2^shift). 'shift' can be negative */ +static void mpb_shr_round(mpb_t *r, int shift, int rnd_mode) +{ + int l, i; + + if (shift == 0) + return; + if (shift < 0) { + shift = -shift; + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (shift != 0) { + r->tab[r->len] = mp_shl(r->tab, r->tab, r->len, shift, 0); + r->len++; + mpb_renorm(r); + } + if (l > 0) { + for(i = r->len - 1; i >= 0; i--) + r->tab[i + l] = r->tab[i]; + for(i = 0; i < l; i++) + r->tab[i] = 0; + r->len += l; + } + } else { + limb_t bit1, bit2; + int k, add_one; + + switch(rnd_mode) { + default: + case JS_RNDZ: + add_one = 0; + break; + case JS_RNDN: + case JS_RNDNA: + bit1 = mpb_get_bit(r, shift - 1); + if (bit1) { + if (rnd_mode == JS_RNDNA) { + bit2 = 1; + } else { + /* bit2 = oring of all the bits after bit1 */ + bit2 = 0; + if (shift >= 2) { + k = shift - 1; + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + for(i = 0; i < min_int(l, r->len); i++) + bit2 |= r->tab[i]; + if (l < r->len) + bit2 |= r->tab[l] & (((limb_t)1 << k) - 1); + } + } + if (bit2) { + add_one = 1; + } else { + /* round to even */ + add_one = mpb_get_bit(r, shift); + } + } else { + add_one = 0; + } + break; + } + + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (l >= r->len) { + r->len = 1; + r->tab[0] = add_one; + } else { + if (l > 0) { + r->len -= l; + for(i = 0; i < r->len; i++) + r->tab[i] = r->tab[i + l]; + } + if (shift != 0) { + mp_shr(r->tab, r->tab, r->len, shift, 0); + mpb_renorm(r); + } + if (add_one) { + limb_t a; + a = mp_add_ui(r->tab, 1, r->len); + if (a) + r->tab[r->len++] = a; + } + } + } +} + +/* return -1, 0 or 1 */ +static int mpb_cmp(const mpb_t *a, const mpb_t *b) +{ + mp_size_t i; + if (a->len < b->len) + return -1; + else if (a->len > b->len) + return 1; + for(i = a->len - 1; i >= 0; i--) { + if (a->tab[i] != b->tab[i]) { + if (a->tab[i] < b->tab[i]) + return -1; + else + return 1; + } + } + return 0; +} + +static void mpb_set_u64(mpb_t *r, uint64_t m) +{ +#if LIMB_BITS == 64 + r->tab[0] = m; + r->len = 1; +#else + r->tab[0] = m; + r->tab[1] = m >> LIMB_BITS; + if (r->tab[1] == 0) + r->len = 1; + else + r->len = 2; +#endif +} + +static uint64_t mpb_get_u64(mpb_t *r) +{ +#if LIMB_BITS == 64 + return r->tab[0]; +#else + if (r->len == 1) { + return r->tab[0]; + } else { + return r->tab[0] | ((uint64_t)r->tab[1] << LIMB_BITS); + } +#endif +} + +/* floor_log2() = position of the first non zero bit or -1 if zero. */ +static int mpb_floor_log2(mpb_t *a) +{ + limb_t v; + v = a->tab[a->len - 1]; + if (v == 0) + return -1; + else + return a->len * LIMB_BITS - 1 - clz32(v); +} + +#define MUL_LOG2_RADIX_BASE_LOG2 24 + +/* round((1 << MUL_LOG2_RADIX_BASE_LOG2)/log2(i + 2)) */ +static const uint32_t mul_log2_radix_table[JS_RADIX_MAX - 1] = { + 0x000000, 0xa1849d, 0x000000, 0x6e40d2, + 0x6308c9, 0x5b3065, 0x000000, 0x50c24e, + 0x4d104d, 0x4a0027, 0x4768ce, 0x452e54, + 0x433d00, 0x418677, 0x000000, 0x3ea16b, + 0x3d645a, 0x3c43c2, 0x3b3b9a, 0x3a4899, + 0x39680b, 0x3897b3, 0x37d5af, 0x372069, + 0x367686, 0x35d6df, 0x354072, 0x34b261, + 0x342bea, 0x33ac62, 0x000000, 0x32bfd9, + 0x3251dd, 0x31e8d6, 0x318465, +}; + +/* return floor(a / log2(radix)) for -2048 <= a <= 2047 */ +static int mul_log2_radix(int a, int radix) +{ + int radix_bits, mult; + + if ((radix & (radix - 1)) == 0) { + /* if the radix is a power of two better to do it exactly */ + radix_bits = 31 - clz32(radix); + if (a < 0) + a -= radix_bits - 1; + return a / radix_bits; + } else { + mult = mul_log2_radix_table[radix - 2]; + return ((int64_t)a * mult) >> MUL_LOG2_RADIX_BASE_LOG2; + } +} + +#if 0 +static void build_mul_log2_radix_table(void) +{ + int base, radix, mult, col, base_log2; + + base_log2 = 24; + base = 1 << base_log2; + col = 0; + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) + mult = 0; + else + mult = lrint((double)base / log2(radix)); + printf("0x%06x, ", mult); + if (++col == 4) { + printf("\n"); + col = 0; + } + } + printf("\n"); +} + +static void mul_log2_radix_test(void) +{ + int radix, i, ref, r; + + for(radix = 2; radix <= 36; radix++) { + for(i = -2048; i <= 2047; i++) { + ref = (int)floor((double)i / log2(radix)); + r = mul_log2_radix(i, radix); + if (ref != r) { + printf("ERROR: radix=%d i=%d r=%d ref=%d\n", + radix, i, r, ref); + exit(1); + } + } + } + if (0) + build_mul_log2_radix_table(); +} +#endif + +static void u32toa_len(char *buf, uint32_t n, size_t len) +{ + int digit, i; + for(i = len - 1; i >= 0; i--) { + digit = n % 10; + n = n / 10; + buf[i] = digit + '0'; + } +} + +/* for power of 2 radixes. len >= 1 */ +static void u64toa_bin_len(char *buf, uint64_t n, unsigned int radix_bits, int len) +{ + int digit, i; + unsigned int mask; + + mask = (1 << radix_bits) - 1; + for(i = len - 1; i >= 0; i--) { + digit = n & mask; + n >>= radix_bits; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } +} + +/* len >= 1. 2 <= radix <= 36 */ +static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ +#if LIMB_BITS == 32 + u32toa_len(buf, n, len); +#else + /* XXX: optimize */ + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % 10; + n = (limb_t)n / 10; + buf[i] = digit + '0'; + } +#endif + } else { + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % radix; + n = (limb_t)n / radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } + } +} + +size_t u32toa(char *buf, uint32_t n) +{ + char buf1[10], *q; + size_t len; + + q = buf1 + sizeof(buf1); + do { + *--q = n % 10 + '0'; + n /= 10; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; +} + +size_t i32toa(char *buf, int32_t n) +{ + if (n >= 0) { + return u32toa(buf, n); + } else { + buf[0] = '-'; + return u32toa(buf + 1, -(uint32_t)n) + 1; + } +} + +#ifdef USE_FAST_INT +size_t u64toa(char *buf, uint64_t n) +{ + if (n < 0x100000000) { + return u32toa(buf, n); + } else { + uint64_t n1; + char *q = buf; + uint32_t n2; + + n1 = n / 1000000000; + n %= 1000000000; + if (n1 >= 0x100000000) { + n2 = n1 / 1000000000; + n1 = n1 % 1000000000; + /* at most two digits */ + if (n2 >= 10) { + *q++ = n2 / 10 + '0'; + n2 %= 10; + } + *q++ = n2 + '0'; + u32toa_len(q, n1, 9); + q += 9; + } else { + q += u32toa(q, n1); + } + u32toa_len(q, n, 9); + q += 9; + return q - buf; + } +} + +size_t i64toa(char *buf, int64_t n) +{ + if (n >= 0) { + return u64toa(buf, n); + } else { + buf[0] = '-'; + return u64toa(buf + 1, -(uint64_t)n) + 1; + } +} + +/* XXX: only tested for 1 <= n < 2^53 */ +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix) +{ + int radix_bits, l; + if (likely(radix == 10)) + return u64toa(buf, n); + if ((radix & (radix - 1)) == 0) { + radix_bits = 31 - clz32(radix); + if (n == 0) + l = 1; + else + l = (64 - clz64(n) + radix_bits - 1) / radix_bits; + u64toa_bin_len(buf, n, radix_bits, l); + return l; + } else { + char buf1[41], *q; /* maximum length for radix = 3 */ + size_t len; + int digit; + q = buf1 + sizeof(buf1); + do { + digit = n % radix; + n /= radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + *--q = digit; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; + } +} + +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix) +{ + if (n >= 0) { + return u64toa_radix(buf, n, radix); + } else { + buf[0] = '-'; + return u64toa_radix(buf + 1, -(uint64_t)n, radix) + 1; + } +} +#endif /* USE_FAST_INT */ + +static const uint8_t digits_per_limb_table[JS_RADIX_MAX - 1] = { +#if LIMB_BITS == 32 +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +#else +64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, +#endif +}; + +static const uint32_t radix_base_table[JS_RADIX_MAX - 1] = { + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +}; + +/* XXX: remove the table ? */ +static uint8_t dtoa_max_digits_table[JS_RADIX_MAX - 1] = { + 54, 35, 28, 24, 22, 20, 19, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12, +}; + +/* we limit the maximum number of significant digits for atod to about + 128 bits of precision for non power of two bases. The only + requirement for Javascript is at least 20 digits in base 10. For + power of two bases, we do an exact rounding in all the cases. */ +static uint8_t atod_max_digits_table[JS_RADIX_MAX - 1] = { + 64, 80, 32, 55, 49, 45, 21, 40, 38, 37, 35, 34, 33, 32, 16, 31, 30, 30, 29, 29, 28, 28, 27, 27, 27, 26, 26, 26, 26, 25, 12, 25, 25, 24, 24, +}; + +/* if abs(d) >= B^max_exponent, it is an overflow */ +static const int16_t max_exponent[JS_RADIX_MAX - 1] = { + 1024, 647, 512, 442, 397, 365, 342, 324, + 309, 297, 286, 277, 269, 263, 256, 251, + 246, 242, 237, 234, 230, 227, 224, 221, + 218, 216, 214, 211, 209, 207, 205, 203, + 202, 200, 199, +}; + +/* if abs(d) <= B^min_exponent, it is an underflow */ +static const int16_t min_exponent[JS_RADIX_MAX - 1] = { +-1075, -679, -538, -463, -416, -383, -359, -340, + -324, -311, -300, -291, -283, -276, -269, -263, + -258, -254, -249, -245, -242, -238, -235, -232, + -229, -227, -224, -222, -220, -217, -215, -214, + -212, -210, -208, +}; + +#if 0 +void build_tables(void) +{ + int r, j, radix, n, col, i; + + /* radix_base_table */ + for(radix = 2; radix <= 36; radix++) { + r = 1; + for(j = 0; j < digits_per_limb_table[radix - 2]; j++) { + r *= radix; + } + printf(" 0x%08x,", r); + if ((radix % 4) == 1) + printf("\n"); + } + printf("\n"); + + /* dtoa_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + /* Note: over estimated when the radix is a power of two */ + printf(" %d,", 1 + (int)ceil(53.0 / log2(radix))); + } + printf("\n"); + + /* atod_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) { + /* 64 bits is more than enough */ + n = (int)floor(64.0 / log2(radix)); + } else { + n = (int)floor(128.0 / log2(radix)); + } + printf(" %d,", n); + } + printf("\n"); + + printf("static const int16_t max_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)ceil(1024 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const int16_t min_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)floor(-1075 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const uint32_t pow5_table[16] = {\n"); + col = 0; + for(i = 2; i <= 17; i++) { + r = 1; + for(j = 0; j < i; j++) { + r *= 5; + } + printf("0x%08x, ", r); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + /* high part */ + printf("static const uint8_t pow5h_table[4] = {\n"); + col = 0; + for(i = 14; i <= 17; i++) { + uint64_t r1; + r1 = 1; + for(j = 0; j < i; j++) { + r1 *= 5; + } + printf("0x%08x, ", (uint32_t)(r1 >> 32)); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); +} +#endif + +/* n_digits >= 1. 0 <= dot_pos <= n_digits. If dot_pos == n_digits, + the dot is not displayed. 'a' is modified. */ +static int output_digits(char *buf, + mpb_t *a, int radix, int n_digits1, + int dot_pos) +{ + int n_digits, digits_per_limb, radix_bits, n, len; + + n_digits = n_digits1; + if ((radix & (radix - 1)) == 0) { + /* radix = 2^radix_bits */ + radix_bits = 31 - clz32(radix); + } else { + radix_bits = 0; + } + digits_per_limb = digits_per_limb_table[radix - 2]; + if (radix_bits != 0) { + for(;;) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + u64toa_bin_len(buf + n_digits, a->tab[0], radix_bits, n); + if (n_digits == 0) + break; + mpb_shr_round(a, digits_per_limb * radix_bits, JS_RNDZ); + } + } else { + limb_t r; + while (n_digits != 0) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + r = mp_div1(a->tab, a->tab, a->len, radix_base_table[radix - 2], 0); + mpb_renorm(a); + limb_to_a(buf + n_digits, r, radix, n); + } + } + + /* add the dot */ + len = n_digits1; + if (dot_pos != n_digits1) { + memmove(buf + dot_pos + 1, buf + dot_pos, n_digits1 - dot_pos); + buf[dot_pos] = '.'; + len++; + } + return len; +} + +/* return (a, e_offset) such that a = a * (radix1*2^radix_shift)^f * + 2^-e_offset. 'f' can be negative. */ +static int mul_pow(mpb_t *a, int radix1, int radix_shift, int f, bool is_int, int e) +{ + int e_offset, d, n, n0; + + e_offset = -f * radix_shift; + if (radix1 != 1) { + d = digits_per_limb_table[radix1 - 2]; + if (f >= 0) { + limb_t h, b; + + b = 0; + n0 = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui(radix1, n); + n0 = n; + } + h = mp_mul1(a->tab, a->tab, a->len, b, 0); + if (h != 0) { + a->tab[a->len++] = h; + } + f -= n; + } + } else { + int extra_bits, l, shift; + limb_t r, rem, b, b_inv; + + f = -f; + l = (f + d - 1) / d; /* high bound for the number of limbs (XXX: make it better) */ + e_offset += l * LIMB_BITS; + if (!is_int) { + /* at least 'e' bits are needed in the final result for rounding */ + extra_bits = max_int(e - mpb_floor_log2(a), 0); + } else { + /* at least two extra bits are needed in the final result + for rounding */ + extra_bits = max_int(2 + e - e_offset, 0); + } + e_offset += extra_bits; + mpb_shr_round(a, -(l * LIMB_BITS + extra_bits), JS_RNDZ); + + b = 0; + b_inv = 0; + shift = 0; + n0 = 0; + rem = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui_inv(&b_inv, &shift, radix1, n); + n0 = n; + } + r = mp_div1norm(a->tab, a->tab, a->len, b, 0, b_inv, shift); + rem |= r; + mpb_renorm(a); + f -= n; + } + /* if the remainder is non zero, use it for rounding */ + a->tab[0] |= (rem != 0); + } + } + return e_offset; +} + +/* tmp1 = round(m*2^e*radix^f). 'tmp0' is a temporary storage */ +static void mul_pow_round(mpb_t *tmp1, uint64_t m, int e, int radix1, int radix_shift, int f, + int rnd_mode) +{ + int e_offset; + + mpb_set_u64(tmp1, m); + e_offset = mul_pow(tmp1, radix1, radix_shift, f, true, e); + mpb_shr_round(tmp1, -e + e_offset, rnd_mode); +} + +/* return round(a*2^e_offset) rounded as a float64. 'a' is modified */ +static uint64_t round_to_d(int *pe, mpb_t *a, int e_offset, int rnd_mode) +{ + int e; + uint64_t m; + + if (a->tab[0] == 0 && a->len == 1) { + /* zero result */ + m = 0; + e = 0; /* don't care */ + } else { + int prec, prec1, e_min; + e = mpb_floor_log2(a) + 1 - e_offset; + prec1 = 53; + e_min = -1021; + if (e < e_min) { + /* subnormal result or zero */ + prec = prec1 - (e_min - e); + } else { + prec = prec1; + } + mpb_shr_round(a, e + e_offset - prec, rnd_mode); + m = mpb_get_u64(a); + m <<= (53 - prec); + /* mantissa overflow due to rounding */ + if (m >= (uint64_t)1 << 53) { + m >>= 1; + e++; + } + } + *pe = e; + return m; +} + +/* return (m, e) such that m*2^(e-53) = round(a * radix^f) with 2^52 + <= m < 2^53 or m = 0. + 'a' is modified. */ +static uint64_t mul_pow_round_to_d(int *pe, mpb_t *a, + int radix1, int radix_shift, int f, int rnd_mode) +{ + int e_offset; + + e_offset = mul_pow(a, radix1, radix_shift, f, false, 55); + return round_to_d(pe, a, e_offset, rnd_mode); +} + +#ifdef JS_DTOA_DUMP_STATS +static int out_len_count[17]; + +void js_dtoa_dump_stats(void) +{ + int i, sum; + sum = 0; + for(i = 0; i < 17; i++) + sum += out_len_count[i]; + for(i = 0; i < 17; i++) { + printf("%2d %8d %5.2f%%\n", + i + 1, out_len_count[i], (double)out_len_count[i] / sum * 100); + } +} +#endif + +/* return a maximum bound of the string length. The bound depends on + 'd' only if format = JS_DTOA_FORMAT_FRAC or if JS_DTOA_EXP_DISABLED + is enabled. */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags) +{ + int fmt = flags & JS_DTOA_FORMAT_MASK; + int n, e; + uint64_t a; + + if (fmt != JS_DTOA_FORMAT_FRAC) { + if (fmt == JS_DTOA_FORMAT_FREE) { + n = dtoa_max_digits_table[radix - 2]; + } else { + n = n_digits; + } + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_DISABLED) { + /* no exponential */ + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + e -= 1023; + /* XXX: adjust */ + n += 10 + abs(mul_log2_radix(e - 1, radix)); + } + } else { + /* extra: sign, 1 dot and exponent "e-1000" */ + n += 1 + 1 + 6; + } + } else { + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + /* high bound for the integer part */ + e -= 1023; + /* x < 2^(e + 1) */ + if (e < 0) { + n = 1; + } else { + n = 2 + mul_log2_radix(e - 1, radix); + } + /* sign, extra digit, 1 dot */ + n += 1 + 1 + 1 + n_digits; + } + } + return max_int(n, 9); /* also include NaN and [-]Infinity */ +} + +#if defined(__SANITIZE_ADDRESS__) && 0 +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + return malloc(size); +} +static void dtoa_free(void *ptr) +{ + free(ptr); +} +#else +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + void *ret; + ret = *pptr; + *pptr += (size + 7) / 8; + return ret; +} + +static void dtoa_free(void *ptr) +{ +} +#endif + +/* return the length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem) +{ + uint64_t a, m, *mptr = tmp_mem->mem; + int e, sgn, l, E, P, i, E_max, radix1, radix_shift; + char *q; + mpb_t *tmp1, *mant_max; + int fmt = flags & JS_DTOA_FORMAT_MASK; + + tmp1 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + mant_max = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * MANT_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSDTOATempMem) / sizeof(mptr[0])); + + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + a = float64_as_uint64(d); + sgn = a >> 63; + e = (a >> 52) & 0x7ff; + m = a & (((uint64_t)1 << 52) - 1); + q = buf; + if (e == 0x7ff) { + if (m == 0) { + if (sgn) + *q++ = '-'; + memcpy(q, "Infinity", 8); + q += 8; + } else { + memcpy(q, "NaN", 3); + q += 3; + } + goto done; + } else if (e == 0) { + if (m == 0) { + tmp1->len = 1; + tmp1->tab[0] = 0; + E = 1; + if (fmt == JS_DTOA_FORMAT_FREE) + P = 1; + else if (fmt == JS_DTOA_FORMAT_FRAC) + P = n_digits + 1; + else + P = n_digits; + /* "-0" is displayed as "0" if JS_DTOA_MINUS_ZERO is not present */ + if (sgn && (flags & JS_DTOA_MINUS_ZERO)) + *q++ = '-'; + goto output; + } + /* denormal number: convert to a normal number */ + l = clz64(m) - 11; + e -= l - 1; + m <<= l; + } else { + m |= (uint64_t)1 << 52; + } + if (sgn) + *q++ = '-'; + /* remove the bias */ + e -= 1022; + /* d = 2^(e-53)*m */ + // printf("m=0x%016" PRIx64 " e=%d\n", m, e); +#ifdef USE_FAST_INT + if (fmt == JS_DTOA_FORMAT_FREE && + e >= 1 && e <= 53 && + (m & (((uint64_t)1 << (53 - e)) - 1)) == 0 && + (flags & JS_DTOA_EXP_MASK) != JS_DTOA_EXP_ENABLED) { + m >>= 53 - e; + /* 'm' is never zero */ + q += u64toa_radix(q, m, radix); + goto done; + } +#endif + + /* this choice of E implies F=round(x*B^(P-E) is such as: + B^(P-1) <= F < 2.B^P. */ + E = 1 + mul_log2_radix(e - 1, radix); + + if (fmt == JS_DTOA_FORMAT_FREE) { + int P_max, E0, e1, E_found, P_found; + uint64_t m1, mant_found, mant, mant_max1; + /* P_max is guarranteed to work by construction */ + P_max = dtoa_max_digits_table[radix - 2]; + E0 = E; + E_found = 0; + P_found = 0; + mant_found = 0; + /* find the minimum number of digits by successive tries */ + P = P_max; /* P_max is guarateed to work */ + for(;;) { + /* mant_max always fits on 64 bits */ + mant_max1 = pow_ui(radix, P); + /* compute the mantissa in base B */ + E = E0; + for(;;) { + /* XXX: add inexact flag */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDN); + mant = mpb_get_u64(tmp1); + if (mant < mant_max1) + break; + E++; /* at most one iteration is possible */ + } + /* remove useless trailing zero digits */ + while ((mant % radix) == 0) { + mant /= radix; + P--; + } + /* garanteed to work for P = P_max */ + if (P_found == 0) + goto prec_found; + /* convert back to base 2 */ + mpb_set_u64(tmp1, mant); + m1 = mul_pow_round_to_d(&e1, tmp1, radix1, radix_shift, E - P, JS_RNDN); + // printf("P=%2d: m=0x%016" PRIx64 " e=%d m1=0x%016" PRIx64 " e1=%d\n", P, m, e, m1, e1); + /* Note: (m, e) is never zero here, so the exponent for m1 + = 0 does not matter */ + if (m1 == m && e1 == e) { + prec_found: + P_found = P; + E_found = E; + mant_found = mant; + if (P == 1) + break; + P--; /* try lower exponent */ + } else { + break; + } + } + P = P_found; + E = E_found; + mpb_set_u64(tmp1, mant_found); +#ifdef JS_DTOA_DUMP_STATS + if (radix == 10) { + out_len_count[P - 1]++; + } +#endif + } else if (fmt == JS_DTOA_FORMAT_FRAC) { + int len; + + assert(n_digits >= 0 && n_digits <= JS_DTOA_MAX_DIGITS); + /* P = max_int(E, 1) + n_digits; */ + /* frac is rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, n_digits, JS_RNDNA); + + /* we add one extra digit on the left and remove it if needed + to avoid testing if the result is < radix^P */ + len = output_digits(q, tmp1, radix, max_int(E + 1, 1) + n_digits, + max_int(E + 1, 1)); + if (q[0] == '0' && len >= 2 && q[1] != '.') { + len--; + memmove(q, q + 1, len); + } + q += len; + goto done; + } else { + int pow_shift; + assert(n_digits >= 1 && n_digits <= JS_DTOA_MAX_DIGITS); + P = n_digits; + /* mant_max = radix^P */ + mant_max->len = 1; + mant_max->tab[0] = 1; + pow_shift = mul_pow(mant_max, radix1, radix_shift, P, false, 0); + mpb_shr_round(mant_max, pow_shift, JS_RNDZ); + + for(;;) { + /* fixed and frac are rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDNA); + if (mpb_cmp(tmp1, mant_max) < 0) + break; + E++; /* at most one iteration is possible */ + } + } + output: + if (fmt == JS_DTOA_FORMAT_FIXED) + E_max = n_digits; + else + E_max = dtoa_max_digits_table[radix - 2] + 4; + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_ENABLED || + ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_AUTO && (E <= -6 || E > E_max))) { + q += output_digits(q, tmp1, radix, P, 1); + E--; + if (radix == 10) { + *q++ = 'e'; + } else if (radix1 == 1 && radix_shift <= 4) { + E *= radix_shift; + *q++ = 'p'; + } else { + *q++ = '@'; + } + if (E < 0) { + *q++ = '-'; + E = -E; + } else { + *q++ = '+'; + } + q += u32toa(q, E); + } else if (E <= 0) { + *q++ = '0'; + *q++ = '.'; + for(i = 0; i < -E; i++) + *q++ = '0'; + q += output_digits(q, tmp1, radix, P, P); + } else { + q += output_digits(q, tmp1, radix, P, min_int(P, E)); + for(i = 0; i < E - P; i++) + *q++ = '0'; + } + done: + *q = '\0'; + dtoa_free(mant_max); + dtoa_free(tmp1); + return q - buf; +} + +static inline int to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* r = r * radix_base + a. radix_base = 0 means radix_base = 2^32 */ +static void mpb_mul1_base(mpb_t *r, limb_t radix_base, limb_t a) +{ + int i; + if (r->tab[0] == 0 && r->len == 1) { + r->tab[0] = a; + } else { + if (radix_base == 0) { + for(i = r->len; i >= 0; i--) { + r->tab[i + 1] = r->tab[i]; + } + r->tab[0] = a; + } else { + r->tab[r->len] = mp_mul1(r->tab, r->tab, r->len, + radix_base, a); + } + r->len++; + mpb_renorm(r); + } +} + +/* XXX: add fast path for small integers */ +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem) +{ + uint64_t *mptr = tmp_mem->mem; + const char *p, *p_start; + limb_t cur_limb, radix_base, extra_digits; + int is_neg, digit_count, limb_digit_count, digits_per_limb, sep, radix1, radix_shift; + int radix_bits, expn, e, max_digits, expn_offset, dot_pos, sig_pos, pos; + mpb_t *tmp0; + double dval; + bool is_bin_exp, is_zero, expn_overflow; + uint64_t m, a; + + tmp0 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSATODTempMem) / sizeof(mptr[0])); + /* optional separator between digits */ + sep = (flags & JS_ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + + p = str; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start = p; + } else if (p[0] == '-') { + is_neg = 1; + p++; + p_start = p; + } else { + p_start = p; + } + + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & JS_ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + if (!(flags & JS_ATOD_INT_ONLY) && js__strstart(p, "Infinity", &p)) + goto overflow; + } + if (radix == 0) + radix = 10; + + cur_limb = 0; + expn_offset = 0; + digit_count = 0; + limb_digit_count = 0; + max_digits = atod_max_digits_table[radix - 2]; + digits_per_limb = digits_per_limb_table[radix - 2]; + radix_base = radix_base_table[radix - 2]; + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + if (radix1 == 1) { + /* radix = 2^radix_bits */ + radix_bits = radix_shift; + } else { + radix_bits = 0; + } + tmp0->len = 1; + tmp0->tab[0] = 0; + extra_digits = 0; + pos = 0; + dot_pos = -1; + /* skip leading zeros */ + for(;;) { + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && p[1] == '0') + p++; + if (*p != '0') + break; + p++; + pos++; + } + + sig_pos = pos; + for(;;) { + limb_t c; + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && to_digit(p[1]) < radix) + p++; + c = to_digit(*p); + if (c >= radix) + break; + p++; + pos++; + if (digit_count < max_digits) { + /* XXX: could be faster when radix_bits != 0 */ + cur_limb = cur_limb * radix + c; + limb_digit_count++; + if (limb_digit_count == digits_per_limb) { + mpb_mul1_base(tmp0, radix_base, cur_limb); + cur_limb = 0; + limb_digit_count = 0; + } + digit_count++; + } else { + extra_digits |= c; + } + } + if (limb_digit_count != 0) { + mpb_mul1_base(tmp0, pow_ui(radix, limb_digit_count), cur_limb); + } + if (digit_count == 0) { + is_zero = true; + expn_offset = 0; + } else { + is_zero = false; + if (dot_pos < 0) + dot_pos = pos; + expn_offset = sig_pos + digit_count - dot_pos; + } + + /* Use the extra digits for rounding if the base is a power of + two. Otherwise they are just truncated. */ + if (radix_bits != 0 && extra_digits != 0) { + tmp0->tab[0] |= 1; + } + + /* parse the exponent, if any */ + expn = 0; + expn_overflow = false; + is_bin_exp = false; + if (!(flags & JS_ATOD_INT_ONLY) && + ((radix == 10 && (*p == 'e' || *p == 'E')) || + (radix != 10 && (*p == '@' || + (radix_bits >= 1 && radix_bits <= 4 && (*p == 'p' || *p == 'P'))))) && + p > p_start) { + bool exp_is_neg; + int c; + is_bin_exp = (*p == 'p' || *p == 'P'); + p++; + exp_is_neg = false; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_is_neg = true; + p++; + } + c = to_digit(*p); + if (c >= 10) + goto fail; /* XXX: could stop before the exponent part */ + expn = c; + p++; + for(;;) { + if (*p == sep && to_digit(p[1]) < 10) + p++; + c = to_digit(*p); + if (c >= 10) + break; + if (!expn_overflow) { + if (unlikely(expn > ((INT32_MAX - 2 - 9) / 10))) { + expn_overflow = true; + } else { + expn = expn * 10 + c; + } + } + p++; + } + if (exp_is_neg) + expn = -expn; + /* if zero result, the exponent can be arbitrarily large */ + if (!is_zero && expn_overflow) { + if (exp_is_neg) + a = 0; + else + a = (uint64_t)0x7ff << 52; /* infinity */ + goto done; + } + } + + if (p == p_start) + goto fail; + + if (is_zero) { + a = 0; + } else { + int expn1; + if (radix_bits != 0) { + if (!is_bin_exp) + expn *= radix_bits; + expn -= expn_offset * radix_bits; + expn1 = expn + digit_count * radix_bits; + if (expn1 >= 1024 + radix_bits) + goto overflow; + else if (expn1 <= -1075) + goto underflow; + m = round_to_d(&e, tmp0, -expn, JS_RNDN); + } else { + expn -= expn_offset; + expn1 = expn + digit_count; + if (expn1 >= max_exponent[radix - 2] + 1) + goto overflow; + else if (expn1 <= min_exponent[radix - 2]) + goto underflow; + m = mul_pow_round_to_d(&e, tmp0, radix1, radix_shift, expn, JS_RNDN); + } + if (m == 0) { + underflow: + a = 0; + } else if (e > 1024) { + overflow: + /* overflow */ + a = (uint64_t)0x7ff << 52; + } else if (e < -1073) { + /* underflow */ + /* XXX: check rounding */ + a = 0; + } else if (e < -1021) { + /* subnormal */ + a = m >> (-e - 1021); + } else { + a = ((uint64_t)(e + 1022) << 52) | (m & (((uint64_t)1 << 52) - 1)); + } + } + done: + a |= (uint64_t)is_neg << 63; + dval = uint64_as_float64(a); + done1: + if (pnext) + *pnext = p; + dtoa_free(tmp0); + return dval; + fail: + dval = NAN; + goto done1; +} diff --git a/NativeScript/napi/android/quickjs/source_ng/dtoa.h b/NativeScript/napi/android/quickjs/source_ng/dtoa.h new file mode 100755 index 000000000..85be04069 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/dtoa.h @@ -0,0 +1,87 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef DTOA_H +#define DTOA_H + +//#define JS_DTOA_DUMP_STATS + +/* maximum number of digits for fixed and frac formats */ +#define JS_DTOA_MAX_DIGITS 101 + +/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */ +/* use as many digits as necessary */ +#define JS_DTOA_FORMAT_FREE (0 << 0) +/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */ +#define JS_DTOA_FORMAT_FIXED (1 << 0) +/* force fractional format: [-]dd.dd with n_digits fractional digits. + 0 <= n_digits <= JS_DTOA_MAX_DIGITS */ +#define JS_DTOA_FORMAT_FRAC (2 << 0) +#define JS_DTOA_FORMAT_MASK (3 << 0) + +/* select exponential notation either in fixed or free format */ +#define JS_DTOA_EXP_AUTO (0 << 2) +#define JS_DTOA_EXP_ENABLED (1 << 2) +#define JS_DTOA_EXP_DISABLED (2 << 2) +#define JS_DTOA_EXP_MASK (3 << 2) + +#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */ + +/* only accepts integers (no dot, no exponent) */ +#define JS_ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2) +/* accept _ between digits as a digit separator */ +#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3) + +typedef struct { + uint64_t mem[37]; +} JSDTOATempMem; + +typedef struct { + uint64_t mem[27]; +} JSATODTempMem; + +/* return a maximum bound of the string length */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags); +/* return the string length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem); +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem); + +#ifdef JS_DTOA_DUMP_STATS +void js_dtoa_dump_stats(void); +#endif + +/* additional exported functions */ +size_t u32toa(char *buf, uint32_t n); +size_t i32toa(char *buf, int32_t n); +size_t u64toa(char *buf, uint64_t n); +size_t i64toa(char *buf, int64_t n); +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix); +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix); + +#endif /* DTOA_H */ diff --git a/NativeScript/napi/android/quickjs/source_ng/fuzz.c b/NativeScript/napi/android/quickjs/source_ng/fuzz.c new file mode 100755 index 000000000..fca8fa413 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/fuzz.c @@ -0,0 +1,22 @@ +// clang -g -O1 -fsanitize=fuzzer -o fuzz fuzz.c +#include "quickjs.h" +#include "quickjs.c" +#include "cutils.c" +#include "libregexp.c" +#include "libunicode.c" +#include + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + JSRuntime *rt = JS_NewRuntime(); + if (!rt) + exit(1); + JSContext *ctx = JS_NewContext(rt); + if (!ctx) + exit(1); + JSValue val = JS_ReadObject(ctx, buf, len, /*flags*/0); + JS_FreeValue(ctx, val); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source_ng/libregexp-opcode.h b/NativeScript/napi/android/quickjs/source_ng/libregexp-opcode.h new file mode 100755 index 000000000..5c1714ab0 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/libregexp-opcode.h @@ -0,0 +1,58 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef DEF + +DEF(invalid, 1) /* never used */ +DEF(char8, 2) /* 7 bits in fact */ +DEF(char16, 3) +DEF(char32, 5) +DEF(dot, 1) +DEF(any, 1) /* same as dot but match any character including line terminator */ +DEF(line_start, 1) +DEF(line_end, 1) +DEF(goto, 5) +DEF(split_goto_first, 5) +DEF(split_next_first, 5) +DEF(match, 1) +DEF(save_start, 2) /* save start position */ +DEF(save_end, 2) /* save end position, must come after saved_start */ +DEF(save_reset, 3) /* reset save positions */ +DEF(loop, 5) /* decrement the top the stack and goto if != 0 */ +DEF(push_i32, 5) /* push integer on the stack */ +DEF(drop, 1) +DEF(word_boundary, 1) +DEF(not_word_boundary, 1) +DEF(back_reference, 2) +DEF(backward_back_reference, 2) /* must come after back_reference */ +DEF(range, 3) /* variable length */ +DEF(range32, 3) /* variable length */ +DEF(lookahead, 5) +DEF(negative_lookahead, 5) +DEF(push_char_pos, 1) /* push the character position on the stack */ +DEF(check_advance, 1) /* pop one stack element and check that it is different from the character position */ +DEF(prev, 1) /* go to the previous char */ +DEF(simple_greedy_quant, 17) + +#endif /* DEF */ diff --git a/NativeScript/napi/android/quickjs/source_ng/libregexp.c b/NativeScript/napi/android/quickjs/source_ng/libregexp.c new file mode 100755 index 000000000..ac60650c9 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/libregexp.c @@ -0,0 +1,2676 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "libregexp.h" + +/* + TODO: + + - Add a lock step execution mode (=linear time execution guaranteed) + when the regular expression is "simple" i.e. no backreference nor + complicated lookahead. The opcodes are designed for this execution + model. +*/ + +#if defined(TEST) +#define DUMP_REOP +#endif + +typedef enum { +#define DEF(id, size) REOP_ ## id, +#include "libregexp-opcode.h" +#undef DEF + REOP_COUNT, +} REOPCodeEnum; + +#define CAPTURE_COUNT_MAX 255 +#define STACK_SIZE_MAX 255 +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define INTERRUPT_COUNTER_INIT 10000 + +/* unicode code points */ +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +#define TMP_BUF_SIZE 128 + +// invariant: is_unicode ^ unicode_sets (or neither, but not both) +typedef struct { + DynBuf byte_code; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + const uint8_t *buf_start; + int re_flags; + bool is_unicode; + bool unicode_sets; + bool ignore_case; + bool dotall; + int capture_count; + int total_capture_count; /* -1 = not computed yet */ + int has_named_captures; /* -1 = don't know, 0 = no, 1 = yes */ + void *opaque; + DynBuf group_names; + union { + char error_msg[TMP_BUF_SIZE]; + char tmp_buf[TMP_BUF_SIZE]; + } u; +} REParseState; + +typedef struct { +#ifdef DUMP_REOP + const char *name; +#endif + uint8_t size; +} REOpCode; + +static const REOpCode reopcode_info[REOP_COUNT] = { +#ifdef DUMP_REOP +#define DEF(id, size) { #id, size }, +#else +#define DEF(id, size) { size }, +#endif +#include "libregexp-opcode.h" +#undef DEF +}; + +#define RE_HEADER_FLAGS 0 +#define RE_HEADER_CAPTURE_COUNT 2 +#define RE_HEADER_STACK_SIZE 3 +#define RE_HEADER_BYTECODE_LEN 4 + +#define RE_HEADER_LEN 8 + +static inline int lre_is_digit(int c) { + return c >= '0' && c <= '9'; +} + +/* insert 'len' bytes at position 'pos'. Return < 0 if error. */ +static int dbuf_insert(DynBuf *s, int pos, int len) +{ + if (dbuf_realloc(s, s->size + len)) + return -1; + memmove(s->buf + pos + len, s->buf + pos, s->size - pos); + s->size += len; + return 0; +} + +static const uint16_t char_range_d[] = { + 1, + 0x0030, 0x0039 + 1, +}; + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_s[] = { + 10, + 0x0009, 0x000D + 1, + 0x0020, 0x0020 + 1, + 0x00A0, 0x00A0 + 1, + 0x1680, 0x1680 + 1, + 0x2000, 0x200A + 1, + /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ + /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ + 0x2028, 0x2029 + 1, + 0x202F, 0x202F + 1, + 0x205F, 0x205F + 1, + 0x3000, 0x3000 + 1, + /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ + 0xFEFF, 0xFEFF + 1, +}; + +bool lre_is_space(int c) +{ + int i, n, low, high; + n = (countof(char_range_s) - 1) / 2; + for(i = 0; i < n; i++) { + low = char_range_s[2 * i + 1]; + if (c < low) + return false; + high = char_range_s[2 * i + 2]; + if (c < high) + return true; + } + return false; +} + +uint32_t const lre_id_start_table_ascii[4] = { + /* $ A-Z _ a-z */ + 0x00000000, 0x00000010, 0x87FFFFFE, 0x07FFFFFE +}; + +uint32_t const lre_id_continue_table_ascii[4] = { + /* $ 0-9 A-Z _ a-z */ + 0x00000000, 0x03FF0010, 0x87FFFFFE, 0x07FFFFFE +}; + + +static const uint16_t char_range_w[] = { + 4, + 0x0030, 0x0039 + 1, + 0x0041, 0x005A + 1, + 0x005F, 0x005F + 1, + 0x0061, 0x007A + 1, +}; + +#define CLASS_RANGE_BASE 0x40000000 + +typedef enum { + CHAR_RANGE_d, + CHAR_RANGE_D, + CHAR_RANGE_s, + CHAR_RANGE_S, + CHAR_RANGE_w, + CHAR_RANGE_W, +} CharRangeEnum; + +static const uint16_t *char_range_table[] = { + char_range_d, + char_range_s, + char_range_w, +}; + +static int cr_init_char_range(REParseState *s, CharRange *cr, uint32_t c) +{ + bool invert; + const uint16_t *c_pt; + int len, i; + + invert = c & 1; + c_pt = char_range_table[c >> 1]; + len = *c_pt++; + cr_init(cr, s->opaque, lre_realloc); + for(i = 0; i < len * 2; i++) { + if (cr_add_point(cr, c_pt[i])) + goto fail; + } + if (invert) { + if (cr_invert(cr)) + goto fail; + } + return 0; + fail: + cr_free(cr); + return -1; +} + +#ifdef DUMP_REOP +static __maybe_unused void lre_dump_bytecode(const uint8_t *buf, + int buf_len) +{ + int pos, len, opcode, bc_len, re_flags, i; + uint32_t val; + + assert(buf_len >= RE_HEADER_LEN); + + re_flags = lre_get_flags(buf); + bc_len = get_u32(buf + RE_HEADER_BYTECODE_LEN); + assert(bc_len + RE_HEADER_LEN <= buf_len); + printf("flags: 0x%x capture_count=%d stack_size=%d\n", + re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_STACK_SIZE]); + if (re_flags & LRE_FLAG_NAMED_GROUPS) { + const char *p; + p = (char *)buf + RE_HEADER_LEN + bc_len; + printf("named groups: "); + for(i = 1; i < buf[RE_HEADER_CAPTURE_COUNT]; i++) { + if (i != 1) + printf(","); + printf("<%s>", p); + p += strlen(p) + 1; + } + printf("\n"); + assert(p == (char *)(buf + buf_len)); + } + printf("bytecode_len=%d\n", bc_len); + + buf += RE_HEADER_LEN; + pos = 0; + while (pos < bc_len) { + printf("%5u: ", pos); + opcode = buf[pos]; + len = reopcode_info[opcode].size; + if (opcode >= REOP_COUNT) { + printf(" invalid opcode=0x%02x\n", opcode); + break; + } + if ((pos + len) > bc_len) { + printf(" buffer overflow (opcode=0x%02x)\n", opcode); + break; + } + printf("%s", reopcode_info[opcode].name); + switch(opcode) { + case REOP_char8: + val = get_u8(buf + pos + 1); + goto printchar; + case REOP_char16: + val = get_u16(buf + pos + 1); + goto printchar; + case REOP_char32: + val = get_u32(buf + pos + 1); + printchar: + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%08x", val); + break; + case REOP_goto: + case REOP_split_goto_first: + case REOP_split_next_first: + case REOP_loop: + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(buf + pos + 1); + val += (pos + 5); + printf(" %u", val); + break; + case REOP_simple_greedy_quant: + printf(" %u %u %u %u", + get_u32(buf + pos + 1) + (pos + 17), + get_u32(buf + pos + 1 + 4), + get_u32(buf + pos + 1 + 8), + get_u32(buf + pos + 1 + 12)); + break; + case REOP_save_start: + case REOP_save_end: + case REOP_back_reference: + case REOP_backward_back_reference: + printf(" %u", buf[pos + 1]); + break; + case REOP_save_reset: + printf(" %u %u", buf[pos + 1], buf[pos + 2]); + break; + case REOP_push_i32: + val = get_u32(buf + pos + 1); + printf(" %d", val); + break; + case REOP_range: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 4; + for(i = 0; i < n * 2; i++) { + val = get_u16(buf + pos + 3 + i * 2); + printf(" 0x%04x", val); + } + } + break; + case REOP_range32: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 8; + for(i = 0; i < n * 2; i++) { + val = get_u32(buf + pos + 3 + i * 4); + printf(" 0x%08x", val); + } + } + break; + default: + break; + } + printf("\n"); + pos += len; + } +} +#endif + +static void re_emit_op(REParseState *s, int op) +{ + dbuf_putc(&s->byte_code, op); +} + +/* return the offset of the u32 value */ +static int re_emit_op_u32(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val); + return pos; +} + +static int re_emit_goto(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val - (pos + 4)); + return pos; +} + +static void re_emit_op_u8(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_putc(&s->byte_code, val); +} + +static void re_emit_op_u16(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_put_u16(&s->byte_code, val); +} + +static int JS_PRINTF_FORMAT_ATTR(2, 3) re_parse_error(REParseState *s, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(s->u.error_msg, sizeof(s->u.error_msg), fmt, ap); + va_end(ap); + return -1; +} + +static int re_parse_out_of_memory(REParseState *s) +{ + return re_parse_error(s, "out of memory"); +} + +static int lre_check_size(REParseState *s) +{ + if (s->byte_code.size < 64*1024*1024) + return 0; + return re_parse_out_of_memory(s); +} + +/* If allow_overflow is false, return -1 in case of + overflow. Otherwise return INT32_MAX. */ +static int parse_digits(const uint8_t **pp, bool allow_overflow) +{ + const uint8_t *p; + uint64_t v; + int c; + + p = *pp; + v = 0; + for(;;) { + c = *p; + if (c < '0' || c > '9') + break; + v = v * 10 + c - '0'; + if (v >= INT32_MAX) { + if (allow_overflow) + v = INT32_MAX; + else + return -1; + } + p++; + } + *pp = p; + return v; +} + +static int re_parse_expect(REParseState *s, const uint8_t **pp, int c) +{ + const uint8_t *p; + p = *pp; + if (*p != c) + return re_parse_error(s, "expecting '%c'", c); + p++; + *pp = p; + return 0; +} + +/* Parse an escape sequence, *pp points after the '\': + allow_utf16 value: + 0 : no UTF-16 escapes allowed + 1 : UTF-16 escapes allowed + 2 : UTF-16 escapes allowed and escapes of surrogate pairs are + converted to a unicode character (unicode regexp case). + + Return the unicode char and update *pp if recognized, + return -1 if malformed escape, + return -2 otherwise. */ +int lre_parse_escape(const uint8_t **pp, int allow_utf16) +{ + const uint8_t *p; + uint32_t c; + + p = *pp; + c = *p++; + switch(c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case 'x': + case 'u': + { + int h, n, i; + uint32_t c1; + + if (*p == '{' && allow_utf16) { + p++; + c = 0; + for(;;) { + h = from_hex(*p++); + if (h < 0) + return -1; + c = (c << 4) | h; + if (c > 0x10FFFF) + return -1; + if (*p == '}') + break; + } + p++; + } else { + if (c == 'x') { + n = 2; + } else { + n = 4; + } + + c = 0; + for(i = 0; i < n; i++) { + h = from_hex(*p++); + if (h < 0) { + return -1; + } + c = (c << 4) | h; + } + if (is_hi_surrogate(c) && + allow_utf16 == 2 && p[0] == '\\' && p[1] == 'u') { + /* convert an escaped surrogate pair into a + unicode char */ + c1 = 0; + for(i = 0; i < 4; i++) { + h = from_hex(p[2 + i]); + if (h < 0) + break; + c1 = (c1 << 4) | h; + } + if (i == 4 && is_lo_surrogate(c1)) { + p += 6; + c = from_surrogate(c, c1); + } + } + } + } + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c -= '0'; + if (allow_utf16 == 2) { + /* only accept \0 not followed by digit */ + if (c != 0 || lre_is_digit(*p)) + return -1; + } else { + /* parse a legacy octal sequence */ + uint32_t v; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + if (c >= 32) + break; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + } + break; + default: + return -2; + } + *pp = p; + return c; +} + +/* XXX: we use the same chars for name and value */ +static bool is_unicode_char(int c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_')); +} + +static int parse_unicode_property(REParseState *s, CharRange *cr, + const uint8_t **pp, bool is_inv) +{ + const uint8_t *p; + char name[64], value[64]; + char *q; + bool script_ext; + int ret; + + p = *pp; + if (*p != '{') + return re_parse_error(s, "expecting '{' after \\p"); + p++; + q = name; + while (is_unicode_char(*p)) { + if ((q - name) >= sizeof(name) - 1) + goto unknown_property_name; + *q++ = *p++; + } + *q = '\0'; + q = value; + if (*p == '=') { + p++; + while (is_unicode_char(*p)) { + if ((q - value) >= sizeof(value) - 1) + return re_parse_error(s, "unknown unicode property value"); + *q++ = *p++; + } + } + *q = '\0'; + if (*p != '}') + return re_parse_error(s, "expecting '}'"); + p++; + // printf("name=%s value=%s\n", name, value); + + if (!strcmp(name, "Script") || !strcmp(name, "sc")) { + script_ext = false; + goto do_script; + } else if (!strcmp(name, "Script_Extensions") || !strcmp(name, "scx")) { + script_ext = true; + do_script: + cr_init(cr, s->opaque, lre_realloc); + ret = unicode_script(cr, value, script_ext); + if (ret) { + cr_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode script"); + else + goto out_of_memory; + } + } else if (!strcmp(name, "General_Category") || !strcmp(name, "gc")) { + cr_init(cr, s->opaque, lre_realloc); + ret = unicode_general_category(cr, value); + if (ret) { + cr_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode general category"); + else + goto out_of_memory; + } + } else if (value[0] == '\0') { + cr_init(cr, s->opaque, lre_realloc); + ret = unicode_general_category(cr, name); + if (ret == -1) { + cr_free(cr); + goto out_of_memory; + } + if (ret < 0) { + ret = unicode_prop(cr, name); + if (ret) { + cr_free(cr); + if (ret == -2) + goto unknown_property_name; + else + goto out_of_memory; + } + } + } else { + unknown_property_name: + return re_parse_error(s, "unknown unicode property name"); + } + + if (is_inv) { + if (cr_invert(cr)) { + cr_free(cr); + return -1; + } + } + *pp = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} + +/* return -1 if error otherwise the character or a class range + (CLASS_RANGE_BASE). In case of class range, 'cr' is + initialized. Otherwise, it is ignored. */ +static int get_class_atom(REParseState *s, CharRange *cr, + const uint8_t **pp, bool inclass) +{ + const uint8_t *p, *p_next; + uint32_t c; + int ret; + + p = *pp; + + c = *p; + switch(c) { + case '\\': + p++; + if (p >= s->buf_end) + goto unexpected_end; + c = *p++; + switch(c) { + case 'd': + c = CHAR_RANGE_d; + goto class_range; + case 'D': + c = CHAR_RANGE_D; + goto class_range; + case 's': + c = CHAR_RANGE_s; + goto class_range; + case 'S': + c = CHAR_RANGE_S; + goto class_range; + case 'w': + c = CHAR_RANGE_w; + goto class_range; + case 'W': + c = CHAR_RANGE_W; + class_range: + if (cr_init_char_range(s, cr, c)) + return -1; + c = CLASS_RANGE_BASE; + break; + case 'c': + c = *p; + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (((c >= '0' && c <= '9') || c == '_') && + inclass && !s->is_unicode)) { /* Annex B.1.4 */ + c &= 0x1f; + p++; + } else if (s->is_unicode) { + goto invalid_escape; + } else { + /* otherwise return '\' and 'c' */ + p--; + c = '\\'; + } + break; + case 'p': + case 'P': + if (s->is_unicode) { + if (parse_unicode_property(s, cr, &p, (c == 'P'))) + return -1; + c = CLASS_RANGE_BASE; + break; + } + /* fall thru */ + default: + p--; + ret = lre_parse_escape(&p, s->is_unicode * 2); + if (ret >= 0) { + c = ret; + } else { + if (ret == -2 && *p != '\0' && strchr("^$\\.*+?()[]{}|/", *p)) { + /* always valid to escape these characters */ + goto normal_char; + } else if (s->is_unicode) { + // special case: allowed inside [] but not outside + if (ret == -2 && *p == '-' && inclass) + goto normal_char; + invalid_escape: + return re_parse_error(s, "invalid escape sequence in regular expression"); + } else { + /* just ignore the '\' */ + goto normal_char; + } + } + break; + } + break; + case '\0': + if (p >= s->buf_end) { + unexpected_end: + return re_parse_error(s, "unexpected end"); + } + /* fall thru */ + default: + normal_char: + p++; + if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) + return re_parse_error(s, "invalid UTF-8 sequence"); + p = p_next; + if (c > 0xFFFF && !s->is_unicode) { + // TODO(chqrlie): should handle non BMP-1 code points in + // the calling function and no require the source string + // to be CESU-8 encoded if not s->is_unicode + return re_parse_error(s, "malformed unicode char"); + } + } + break; + } + *pp = p; + return c; +} + +static int re_emit_range(REParseState *s, const CharRange *cr) +{ + int len, i; + uint32_t high; + + len = (unsigned)cr->len / 2; + if (len >= 65535) + return re_parse_error(s, "too many ranges"); + if (len == 0) { + /* not sure it can really happen. Emit a match that is always + false */ + re_emit_op_u32(s, REOP_char32, -1); + } else { + high = cr->points[cr->len - 1]; + if (high == UINT32_MAX) + high = cr->points[cr->len - 2]; + if (high <= 0xffff) { + /* can use 16 bit ranges with the conversion that 0xffff = + infinity */ + re_emit_op_u16(s, REOP_range, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u16(&s->byte_code, cr->points[i]); + high = cr->points[i + 1] - 1; + if (high == UINT32_MAX - 1) + high = 0xffff; + dbuf_put_u16(&s->byte_code, high); + } + } else { + re_emit_op_u16(s, REOP_range32, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u32(&s->byte_code, cr->points[i]); + dbuf_put_u32(&s->byte_code, cr->points[i + 1] - 1); + } + } + } + return 0; +} + +// s->unicode turns patterns like []] into syntax errors +// s->unicode_sets turns more patterns into errors, like [a-] or [[] +static int re_parse_char_class(REParseState *s, const uint8_t **pp) +{ + const uint8_t *p; + uint32_t c1, c2; + CharRange cr_s, *cr = &cr_s; + CharRange cr1_s, *cr1 = &cr1_s; + bool invert; + + cr_init(cr, s->opaque, lre_realloc); + p = *pp; + p++; /* skip '[' */ + + if (s->unicode_sets) { + static const char verboten[] = + "()[{}/-|" "\0" + "&&!!##$$%%**++,,..::;;<<==>>??@@``~~" "\0" + "^^^_^^"; + const char *s = verboten; + int n = 1; + do { + if (!memcmp(s, p, n)) + if (p[n] == ']') + goto invalid_class_range; + s += n; + if (!*s) { + s++; + n++; + } + } while (n < 4); + } + + invert = false; + if (*p == '^') { + p++; + invert = true; + } + + for(;;) { + if (*p == ']') + break; + c1 = get_class_atom(s, cr1, &p, true); + if ((int)c1 < 0) + goto fail; + if (*p == '-' && p[1] == ']' && s->unicode_sets) { + if (c1 >= CLASS_RANGE_BASE) + cr_free(cr1); + goto invalid_class_range; + } + if (*p == '-' && p[1] != ']') { + const uint8_t *p0 = p + 1; + if (c1 >= CLASS_RANGE_BASE) { + if (s->is_unicode) { + cr_free(cr1); + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + c2 = get_class_atom(s, cr1, &p0, true); + if ((int)c2 < 0) + goto fail; + if (c2 >= CLASS_RANGE_BASE) { + cr_free(cr1); + if (s->is_unicode) { + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + p = p0; + if (c2 < c1) { + invalid_class_range: + re_parse_error(s, "invalid class range"); + goto fail; + } + if (cr_union_interval(cr, c1, c2)) + goto memory_error; + } else { + class_atom: + if (c1 >= CLASS_RANGE_BASE) { + int ret; + ret = cr_union1(cr, cr1->points, cr1->len); + cr_free(cr1); + if (ret) + goto memory_error; + } else { + if (cr_union_interval(cr, c1, c1)) + goto memory_error; + } + } + } + if (s->ignore_case) { + if (cr_regexp_canonicalize(cr, s->is_unicode)) + goto memory_error; + } + if (invert) { + if (cr_invert(cr)) + goto memory_error; + } + if (re_emit_range(s, cr)) + goto fail; + cr_free(cr); + p++; /* skip ']' */ + *pp = p; + return 0; + memory_error: + re_parse_out_of_memory(s); + fail: + cr_free(cr); + return -1; +} + +/* Return: + - true if the opcodes may not advance the char pointer + - false if the opcodes always advance the char pointer +*/ +static bool re_need_check_advance(const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len; + uint32_t val; + bool ret; + + ret = true; + pos = 0; + + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + goto simple_char; + case REOP_range32: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + goto simple_char; + case REOP_char32: + case REOP_char16: + case REOP_char8: + case REOP_dot: + case REOP_any: + simple_char: + ret = false; + break; + case REOP_line_start: + case REOP_line_end: + case REOP_push_i32: + case REOP_push_char_pos: + case REOP_drop: + case REOP_word_boundary: + case REOP_not_word_boundary: + case REOP_prev: + /* no effect */ + break; + case REOP_save_start: + case REOP_save_end: + case REOP_save_reset: + case REOP_back_reference: + case REOP_backward_back_reference: + break; + default: + /* safe behvior: we cannot predict the outcome */ + return true; + } + pos += len; + } + return ret; +} + +/* return -1 if a simple quantifier cannot be used. Otherwise return + the number of characters in the atom. */ +static int re_is_simple_quantifier(const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len, count; + uint32_t val; + + count = 0; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + goto simple_char; + case REOP_range32: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + goto simple_char; + case REOP_char32: + case REOP_char16: + case REOP_char8: + case REOP_dot: + case REOP_any: + simple_char: + count++; + break; + case REOP_line_start: + case REOP_line_end: + case REOP_word_boundary: + case REOP_not_word_boundary: + break; + default: + return -1; + } + pos += len; + } + return count; +} + +/* '*pp' is the first char after '<' */ +static int re_parse_group_name(char *buf, int buf_size, const uint8_t **pp) +{ + const uint8_t *p, *p_next; + uint32_t c, d; + char *q; + + p = *pp; + q = buf; + for(;;) { + c = *p++; + if (c == '\\') { + if (*p != 'u') + return -1; + c = lre_parse_escape(&p, 2); // accept surrogate pairs + if ((int)c < 0) + return -1; + } else if (c == '>') { + break; + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) + return -1; + p = p_next; + if (is_hi_surrogate(c)) { + d = utf8_decode(p, &p_next); + if (is_lo_surrogate(d)) { + c = from_surrogate(c, d); + p = p_next; + } + } + } + if (q == buf) { + if (!lre_js_is_ident_first(c)) + return -1; + } else { + if (!lre_js_is_ident_next(c)) + return -1; + } + if ((q - buf + UTF8_CHAR_LEN_MAX + 1) > buf_size) + return -1; + if (c < 0x80) { + *q++ = c; + } else { + q += utf8_encode((uint8_t*)q, c); + } + } + if (q == buf) + return -1; + *q = '\0'; + *pp = p; + return 0; +} + +/* if capture_name = NULL: return the number of captures + 1. + Otherwise, return the capture index corresponding to capture_name + or -1 if none */ +static int re_parse_captures(REParseState *s, int *phas_named_captures, + const char *capture_name) +{ + const uint8_t *p; + int capture_index; + char name[TMP_BUF_SIZE]; + + capture_index = 1; + *phas_named_captures = 0; + for (p = s->buf_start; p < s->buf_end; p++) { + switch (*p) { + case '(': + if (p[1] == '?') { + if (p[2] == '<' && p[3] != '=' && p[3] != '!') { + *phas_named_captures = 1; + /* potential named capture */ + if (capture_name) { + p += 3; + if (re_parse_group_name(name, sizeof(name), &p) == 0) { + if (!strcmp(name, capture_name)) + return capture_index; + } + } + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + } else { + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + break; + case '\\': + p++; + break; + case '[': + for (p += 1 + (*p == ']'); p < s->buf_end && *p != ']'; p++) { + if (*p == '\\') + p++; + } + break; + } + } + done: + if (capture_name) + return -1; + else + return capture_index; +} + +static int re_count_captures(REParseState *s) +{ + if (s->total_capture_count < 0) { + s->total_capture_count = re_parse_captures(s, &s->has_named_captures, + NULL); + } + return s->total_capture_count; +} + +static bool re_has_named_captures(REParseState *s) +{ + if (s->has_named_captures < 0) + re_count_captures(s); + return s->has_named_captures; +} + +static int find_group_name(REParseState *s, const char *name) +{ + const char *p, *buf_end; + size_t len, name_len; + int capture_index; + + p = (char *)s->group_names.buf; + if (!p) return -1; + buf_end = (char *)s->group_names.buf + s->group_names.size; + name_len = strlen(name); + capture_index = 1; + while (p < buf_end) { + len = strlen(p); + if (len == name_len && memcmp(name, p, name_len) == 0) + return capture_index; + p += len + 1; + capture_index++; + } + return -1; +} + +static int re_parse_disjunction(REParseState *s, bool is_backward_dir); + +static int re_parse_term(REParseState *s, bool is_backward_dir) +{ + const uint8_t *p; + int c, last_atom_start, quant_min, quant_max, last_capture_count; + bool greedy, add_zero_advance_check, is_neg, is_backward_lookahead; + CharRange cr_s, *cr = &cr_s; + + if (lre_check_size(s)) + return -1; + last_atom_start = -1; + last_capture_count = 0; + p = s->buf_ptr; + c = *p; + switch(c) { + case '^': + p++; + re_emit_op(s, REOP_line_start); + break; + case '$': + p++; + re_emit_op(s, REOP_line_end); + break; + case '.': + p++; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + re_emit_op(s, s->dotall ? REOP_any : REOP_dot); + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case '{': + if (s->is_unicode) { + return re_parse_error(s, "syntax error"); + } else if (!lre_is_digit(p[1])) { + /* Annex B: we accept '{' not followed by digits as a + normal atom */ + goto parse_class_atom; + } else { + const uint8_t *p1 = p + 1; + /* Annex B: error if it is like a repetition count */ + parse_digits(&p1, true); + if (*p1 == ',') { + p1++; + if (lre_is_digit(*p1)) { + parse_digits(&p1, true); + } + } + if (*p1 != '}') { + goto parse_class_atom; + } + } + /* fall thru */ + case '*': + case '+': + case '?': + return re_parse_error(s, "nothing to repeat"); + case '(': + if (p[1] == '?') { + if (p[2] == ':') { + p += 3; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + } else if ((p[2] == '=' || p[2] == '!')) { + is_neg = (p[2] == '!'); + is_backward_lookahead = false; + p += 3; + goto lookahead; + } else if (p[2] == '<' && + (p[3] == '=' || p[3] == '!')) { + int pos; + is_neg = (p[3] == '!'); + is_backward_lookahead = true; + p += 4; + /* lookahead */ + lookahead: + /* Annex B allows lookahead to be used as an atom for + the quantifiers */ + if (!s->is_unicode && !is_backward_lookahead) { + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + } + pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0); + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_lookahead)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + re_emit_op(s, REOP_match); + /* jump after the 'match' after the lookahead is successful */ + if (dbuf_error(&s->byte_code)) + return -1; + put_u32(s->byte_code.buf + pos, s->byte_code.size - (pos + 4)); + } else if (p[2] == '<') { + p += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p)) { + return re_parse_error(s, "invalid group name"); + } + if (find_group_name(s, s->u.tmp_buf) > 0) { + return re_parse_error(s, "duplicate group name"); + } + /* group name with a trailing zero */ + dbuf_put(&s->group_names, (uint8_t *)s->u.tmp_buf, + strlen(s->u.tmp_buf) + 1); + s->has_named_captures = 1; + goto parse_capture; + } else { + return re_parse_error(s, "invalid group"); + } + } else { + int capture_index; + p++; + /* capture without group name */ + dbuf_putc(&s->group_names, 0); + parse_capture: + if (s->capture_count >= CAPTURE_COUNT_MAX) + return re_parse_error(s, "too many captures"); + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + capture_index = s->capture_count++; + re_emit_op_u8(s, REOP_save_start + is_backward_dir, + capture_index); + + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + + re_emit_op_u8(s, REOP_save_start + 1 - is_backward_dir, + capture_index); + + if (re_parse_expect(s, &p, ')')) + return -1; + } + break; + case '\\': + switch(p[1]) { + case 'b': + case 'B': + re_emit_op(s, REOP_word_boundary + (p[1] != 'b')); + p += 2; + break; + case 'k': + { + const uint8_t *p1; + int dummy_res; + + p1 = p; + if (p1[2] != '<') { + /* annex B: we tolerate invalid group names in non + unicode mode if there is no named capture + definition */ + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "expecting group name"); + else + goto parse_class_atom; + } + p1 += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p1)) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "invalid group name"); + else + goto parse_class_atom; + } + c = find_group_name(s, s->u.tmp_buf); + if (c < 0) { + /* no capture name parsed before, try to look + after (inefficient, but hopefully not common */ + c = re_parse_captures(s, &dummy_res, s->u.tmp_buf); + if (c < 0) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "group name not defined"); + else + goto parse_class_atom; + } + } + p = p1; + } + goto emit_back_reference; + case '0': + p += 2; + c = 0; + if (s->is_unicode) { + if (lre_is_digit(*p)) { + return re_parse_error(s, "invalid decimal escape in regular expression"); + } + } else { + /* Annex B.1.4: accept legacy octal */ + if (*p >= '0' && *p <= '7') { + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } + goto normal_char; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + { + const uint8_t *q = ++p; + + c = parse_digits(&p, false); + if (c < 0 || (c >= s->capture_count && c >= re_count_captures(s))) { + if (!s->is_unicode) { + /* Annex B.1.4: accept legacy octal */ + p = q; + if (*p <= '7') { + c = 0; + if (*p <= '3') + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } else { + c = *p++; + } + goto normal_char; + } + return re_parse_error(s, "back reference out of range in regular expression"); + } + emit_back_reference: + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + re_emit_op_u8(s, REOP_back_reference + is_backward_dir, c); + } + break; + default: + goto parse_class_atom; + } + break; + case '[': + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (re_parse_char_class(s, &p)) + return -1; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case ']': + case '}': + if (s->is_unicode) + return re_parse_error(s, "syntax error"); + goto parse_class_atom; + default: + parse_class_atom: + c = get_class_atom(s, cr, &p, false); + if ((int)c < 0) + return -1; + normal_char: + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (c >= CLASS_RANGE_BASE) { + int ret; + /* Note: canonicalization is not needed */ + ret = re_emit_range(s, cr); + cr_free(cr); + if (ret) + return -1; + } else { + if (s->ignore_case) + c = lre_canonicalize(c, s->is_unicode); + if (c <= 0x7f) + re_emit_op_u8(s, REOP_char8, c); + else if (c <= 0xffff) + re_emit_op_u16(s, REOP_char16, c); + else + re_emit_op_u32(s, REOP_char32, c); + } + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + } + + /* quantifier */ + if (last_atom_start >= 0) { + c = *p; + switch(c) { + case '*': + p++; + quant_min = 0; + quant_max = INT32_MAX; + goto quantifier; + case '+': + p++; + quant_min = 1; + quant_max = INT32_MAX; + goto quantifier; + case '?': + p++; + quant_min = 0; + quant_max = 1; + goto quantifier; + case '{': + { + const uint8_t *p1 = p; + /* As an extension (see ES6 annex B), we accept '{' not + followed by digits as a normal atom */ + if (!lre_is_digit(p[1])) { + if (s->is_unicode) + goto invalid_quant_count; + break; + } + p++; + quant_min = parse_digits(&p, true); + quant_max = quant_min; + if (*p == ',') { + p++; + if (lre_is_digit(*p)) { + quant_max = parse_digits(&p, true); + if (quant_max < quant_min) { + invalid_quant_count: + return re_parse_error(s, "invalid repetition count"); + } + } else { + quant_max = INT32_MAX; /* infinity */ + } + } + if (*p != '}' && !s->is_unicode) { + /* Annex B: normal atom if invalid '{' syntax */ + p = p1; + break; + } + if (re_parse_expect(s, &p, '}')) + return -1; + } + quantifier: + greedy = true; + if (*p == '?') { + p++; + greedy = false; + } + if (last_atom_start < 0) { + return re_parse_error(s, "nothing to repeat"); + } + if (greedy) { + int len, pos; + + if (quant_max > 0) { + /* specific optimization for simple quantifiers */ + if (dbuf_error(&s->byte_code)) + goto out_of_memory; + len = re_is_simple_quantifier(s->byte_code.buf + last_atom_start, + s->byte_code.size - last_atom_start); + if (len > 0) { + re_emit_op(s, REOP_match); + + if (dbuf_insert(&s->byte_code, last_atom_start, 17)) + goto out_of_memory; + pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_simple_greedy_quant; + put_u32(&s->byte_code.buf[pos], + s->byte_code.size - last_atom_start - 17); + pos += 4; + put_u32(&s->byte_code.buf[pos], quant_min); + pos += 4; + put_u32(&s->byte_code.buf[pos], quant_max); + pos += 4; + put_u32(&s->byte_code.buf[pos], len); + pos += 4; + goto done; + } + } + + if (dbuf_error(&s->byte_code)) + goto out_of_memory; + } + /* the spec tells that if there is no advance when + running the atom after the first quant_min times, + then there is no match. We remove this test when we + are sure the atom always advances the position. */ + add_zero_advance_check = re_need_check_advance(s->byte_code.buf + last_atom_start, + s->byte_code.size - last_atom_start); + + { + int len, pos; + len = s->byte_code.size - last_atom_start; + if (quant_min == 0) { + /* need to reset the capture in case the atom is + not executed */ + if (last_capture_count != s->capture_count) { + if (dbuf_insert(&s->byte_code, last_atom_start, 3)) + goto out_of_memory; + s->byte_code.buf[last_atom_start++] = REOP_save_reset; + s->byte_code.buf[last_atom_start++] = last_capture_count; + s->byte_code.buf[last_atom_start++] = s->capture_count - 1; + } + if (quant_max == 0) { + s->byte_code.size = last_atom_start; + } else if (quant_max == 1 || quant_max == INT32_MAX) { + bool has_goto = (quant_max == INT32_MAX); + if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check)) + goto out_of_memory; + s->byte_code.buf[last_atom_start] = REOP_split_goto_first + + greedy; + put_u32(s->byte_code.buf + last_atom_start + 1, + len + 5 * has_goto + add_zero_advance_check * 2); + if (add_zero_advance_check) { + s->byte_code.buf[last_atom_start + 1 + 4] = REOP_push_char_pos; + re_emit_op(s, REOP_check_advance); + } + if (has_goto) + re_emit_goto(s, REOP_goto, last_atom_start); + } else { + if (dbuf_insert(&s->byte_code, last_atom_start, 10 + add_zero_advance_check)) + goto out_of_memory; + pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_push_i32; + put_u32(s->byte_code.buf + pos, quant_max); + pos += 4; + s->byte_code.buf[pos++] = REOP_split_goto_first + greedy; + put_u32(s->byte_code.buf + pos, len + 5 + add_zero_advance_check * 2); + pos += 4; + if (add_zero_advance_check) { + s->byte_code.buf[pos++] = REOP_push_char_pos; + re_emit_op(s, REOP_check_advance); + } + re_emit_goto(s, REOP_loop, last_atom_start + 5); + re_emit_op(s, REOP_drop); + } + } else if (quant_min == 1 && quant_max == INT32_MAX && + !add_zero_advance_check) { + re_emit_goto(s, REOP_split_next_first - greedy, + last_atom_start); + } else { + if (quant_min == 1) { + /* nothing to add */ + } else { + if (dbuf_insert(&s->byte_code, last_atom_start, 5)) + goto out_of_memory; + s->byte_code.buf[last_atom_start] = REOP_push_i32; + put_u32(s->byte_code.buf + last_atom_start + 1, + quant_min); + last_atom_start += 5; + re_emit_goto(s, REOP_loop, last_atom_start); + re_emit_op(s, REOP_drop); + } + if (quant_max == INT32_MAX) { + pos = s->byte_code.size; + re_emit_op_u32(s, REOP_split_goto_first + greedy, + len + 5 + add_zero_advance_check * 2); + if (add_zero_advance_check) + re_emit_op(s, REOP_push_char_pos); + /* copy the atom */ + dbuf_put_self(&s->byte_code, last_atom_start, len); + if (add_zero_advance_check) + re_emit_op(s, REOP_check_advance); + re_emit_goto(s, REOP_goto, pos); + } else if (quant_max > quant_min) { + re_emit_op_u32(s, REOP_push_i32, quant_max - quant_min); + pos = s->byte_code.size; + re_emit_op_u32(s, REOP_split_goto_first + greedy, + len + 5 + add_zero_advance_check * 2); + if (add_zero_advance_check) + re_emit_op(s, REOP_push_char_pos); + /* copy the atom */ + dbuf_put_self(&s->byte_code, last_atom_start, len); + if (add_zero_advance_check) + re_emit_op(s, REOP_check_advance); + re_emit_goto(s, REOP_loop, pos); + re_emit_op(s, REOP_drop); + } + } + last_atom_start = -1; + } + break; + default: + break; + } + } + done: + s->buf_ptr = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} + +static int re_parse_alternative(REParseState *s, bool is_backward_dir) +{ + const uint8_t *p; + int ret; + size_t start, term_start, end, term_size; + + if (lre_check_size(s)) + return -1; + start = s->byte_code.size; + for(;;) { + p = s->buf_ptr; + if (p >= s->buf_end) + break; + if (*p == '|' || *p == ')') + break; + term_start = s->byte_code.size; + ret = re_parse_term(s, is_backward_dir); + if (ret) + return ret; + if (is_backward_dir) { + /* reverse the order of the terms (XXX: inefficient, but + speed is not really critical here) */ + end = s->byte_code.size; + term_size = end - term_start; + if (dbuf_realloc(&s->byte_code, end + term_size)) + return -1; + memmove(s->byte_code.buf + start + term_size, + s->byte_code.buf + start, + end - start); + memcpy(s->byte_code.buf + start, s->byte_code.buf + end, + term_size); + } + } + return 0; +} + +static int re_parse_disjunction(REParseState *s, bool is_backward_dir) +{ + int start, len, pos; + + if (lre_check_stack_overflow(s->opaque, 0)) + return re_parse_error(s, "stack overflow"); + if (lre_check_size(s)) + return -1; + start = s->byte_code.size; + if (re_parse_alternative(s, is_backward_dir)) + return -1; + while (*s->buf_ptr == '|') { + s->buf_ptr++; + + len = s->byte_code.size - start; + + /* insert a split before the first alternative */ + if (dbuf_insert(&s->byte_code, start, 5)) { + return re_parse_out_of_memory(s); + } + s->byte_code.buf[start] = REOP_split_next_first; + put_u32(s->byte_code.buf + start + 1, len + 5); + + pos = re_emit_op_u32(s, REOP_goto, 0); + + if (re_parse_alternative(s, is_backward_dir)) + return -1; + + /* patch the goto */ + len = s->byte_code.size - (pos + 4); + put_u32(s->byte_code.buf + pos, len); + } + return 0; +} + +/* the control flow is recursive so the analysis can be linear */ +static int lre_compute_stack_size(const uint8_t *bc_buf, int bc_buf_len) +{ + int stack_size, stack_size_max, pos, opcode, len; + uint32_t val; + + stack_size = 0; + stack_size_max = 0; + bc_buf += RE_HEADER_LEN; + bc_buf_len -= RE_HEADER_LEN; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + assert(opcode < REOP_COUNT); + assert((pos + len) <= bc_buf_len); + switch(opcode) { + case REOP_push_i32: + case REOP_push_char_pos: + stack_size++; + if (stack_size > stack_size_max) { + if (stack_size > STACK_SIZE_MAX) + return -1; + stack_size_max = stack_size; + } + break; + case REOP_drop: + case REOP_check_advance: + assert(stack_size > 0); + stack_size--; + break; + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + break; + case REOP_range32: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + break; + } + pos += len; + } + return stack_size_max; +} + +/* 'buf' must be a zero terminated UTF-8 string of length buf_len. + Return NULL if error and allocate an error message in *perror_msg, + otherwise the compiled bytecode and its length in plen. +*/ +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque) +{ + REParseState s_s, *s = &s_s; + int stack_size; + bool is_sticky; + + memset(s, 0, sizeof(*s)); + s->opaque = opaque; + s->buf_ptr = (const uint8_t *)buf; + s->buf_end = s->buf_ptr + buf_len; + s->buf_start = s->buf_ptr; + s->re_flags = re_flags; + s->is_unicode = ((re_flags & LRE_FLAG_UNICODE) != 0); + is_sticky = ((re_flags & LRE_FLAG_STICKY) != 0); + s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0); + s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0); + s->unicode_sets = ((re_flags & LRE_FLAG_UNICODE_SETS) != 0); + s->capture_count = 1; + s->total_capture_count = -1; + s->has_named_captures = -1; + + dbuf_init2(&s->byte_code, opaque, lre_realloc); + dbuf_init2(&s->group_names, opaque, lre_realloc); + + dbuf_put_u16(&s->byte_code, re_flags); /* first element is the flags */ + dbuf_putc(&s->byte_code, 0); /* second element is the number of captures */ + dbuf_putc(&s->byte_code, 0); /* stack size */ + dbuf_put_u32(&s->byte_code, 0); /* bytecode length */ + + if (!is_sticky) { + /* iterate thru all positions (about the same as .*?( ... ) ) + . We do it without an explicit loop so that lock step + thread execution will be possible in an optimized + implementation */ + re_emit_op_u32(s, REOP_split_goto_first, 1 + 5); + re_emit_op(s, REOP_any); + re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5)); + } + re_emit_op_u8(s, REOP_save_start, 0); + + if (re_parse_disjunction(s, false)) { + error: + dbuf_free(&s->byte_code); + dbuf_free(&s->group_names); + js__pstrcpy(error_msg, error_msg_size, s->u.error_msg); + *plen = 0; + return NULL; + } + + re_emit_op_u8(s, REOP_save_end, 0); + + re_emit_op(s, REOP_match); + + if (*s->buf_ptr != '\0') { + re_parse_error(s, "extraneous characters at the end"); + goto error; + } + + if (dbuf_error(&s->byte_code)) { + re_parse_out_of_memory(s); + goto error; + } + + stack_size = lre_compute_stack_size(s->byte_code.buf, s->byte_code.size); + if (stack_size < 0) { + re_parse_error(s, "too many imbricated quantifiers"); + goto error; + } + + s->byte_code.buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count; + s->byte_code.buf[RE_HEADER_STACK_SIZE] = stack_size; + put_u32(s->byte_code.buf + RE_HEADER_BYTECODE_LEN, + s->byte_code.size - RE_HEADER_LEN); + + /* add the named groups if needed */ + if (s->group_names.size > (s->capture_count - 1)) { + dbuf_put(&s->byte_code, s->group_names.buf, s->group_names.size); + put_u16(s->byte_code.buf + RE_HEADER_FLAGS, + LRE_FLAG_NAMED_GROUPS | lre_get_flags(s->byte_code.buf)); + } + dbuf_free(&s->group_names); + +#ifdef DUMP_REOP + lre_dump_bytecode(s->byte_code.buf, s->byte_code.size); +#endif + + error_msg[0] = '\0'; + *plen = s->byte_code.size; + return s->byte_code.buf; +} + +static bool is_line_terminator(uint32_t c) +{ + return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS); +} + +static bool is_word_char(uint32_t c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c == '_')); +} + +#define GET_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = *cptr++; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c)) \ + if (cbuf_type == 2) \ + if (_p < _end) \ + if (is_lo_surrogate(*_p)) \ + c = from_surrogate(c, *_p++); \ + cptr = (const void *)_p; \ + } \ + } while (0) + +#define PEEK_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[0]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c)) \ + if (cbuf_type == 2) \ + if (_p < _end) \ + if (is_lo_surrogate(*_p)) \ + c = from_surrogate(c, *_p); \ + } \ + } while (0) + +#define PEEK_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[-1]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c)) \ + if (cbuf_type == 2) \ + if (_p > _start) \ + if (is_hi_surrogate(_p[-1])) \ + c = from_surrogate(*--_p, c); \ + } \ + } while (0) + +#define GET_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + c = cptr[0]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c)) \ + if (cbuf_type == 2) \ + if (_p > _start) \ + if (is_hi_surrogate(_p[-1])) \ + c = from_surrogate(*--_p, c); \ + cptr = (const void *)_p; \ + } \ + } while (0) + +#define PREV_CHAR(cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + if (is_lo_surrogate(*_p)) \ + if (cbuf_type == 2) \ + if (_p > _start) \ + if (is_hi_surrogate(_p[-1])) \ + _p--; \ + cptr = (const void *)_p; \ + } \ + } while (0) + +typedef uintptr_t StackInt; + +typedef enum { + RE_EXEC_STATE_SPLIT, + RE_EXEC_STATE_LOOKAHEAD, + RE_EXEC_STATE_NEGATIVE_LOOKAHEAD, + RE_EXEC_STATE_GREEDY_QUANT, +} REExecStateEnum; + +typedef struct REExecState { + REExecStateEnum type : 8; + uint8_t stack_len; + size_t count; /* only used for RE_EXEC_STATE_GREEDY_QUANT */ + const uint8_t *cptr; + const uint8_t *pc; + void *buf[]; +} REExecState; + +typedef struct { + const uint8_t *cbuf; + const uint8_t *cbuf_end; + /* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16 */ + int cbuf_type; + int capture_count; + int stack_size_max; + bool multi_line; + bool ignore_case; + bool is_unicode; + int interrupt_counter; + void *opaque; /* used for stack overflow check */ + + size_t state_size; + uint8_t *state_stack; + size_t state_stack_size; + size_t state_stack_len; +} REExecContext; + +static int push_state(REExecContext *s, + uint8_t **capture, + StackInt *stack, size_t stack_len, + const uint8_t *pc, const uint8_t *cptr, + REExecStateEnum type, size_t count) +{ + REExecState *rs; + uint8_t *new_stack; + size_t new_size, i, n; + StackInt *stack_buf; + + if (unlikely((s->state_stack_len + 1) > s->state_stack_size)) { + /* reallocate the stack */ + new_size = s->state_stack_size * 3 / 2; + if (new_size < 8) + new_size = 8; + new_stack = lre_realloc(s->opaque, s->state_stack, new_size * s->state_size); + if (!new_stack) + return -1; + s->state_stack_size = new_size; + s->state_stack = new_stack; + } + rs = (REExecState *)(s->state_stack + s->state_stack_len * s->state_size); + s->state_stack_len++; + rs->type = type; + rs->count = count; + rs->stack_len = stack_len; + rs->cptr = cptr; + rs->pc = pc; + n = 2 * s->capture_count; + for(i = 0; i < n; i++) + rs->buf[i] = capture[i]; + stack_buf = (StackInt *)(rs->buf + n); + for(i = 0; i < stack_len; i++) + stack_buf[i] = stack[i]; + return 0; +} + +static int lre_poll_timeout(REExecContext *s) +{ + if (unlikely(--s->interrupt_counter <= 0)) { + s->interrupt_counter = INTERRUPT_COUNTER_INIT; + if (lre_check_timeout(s->opaque)) + return LRE_RET_TIMEOUT; + } + return 0; +} + +/* return 1 if match, 0 if not match or < 0 if error. */ +static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture, + StackInt *stack, int stack_len, + const uint8_t *pc, const uint8_t *cptr, + bool no_recurse) +{ + int opcode, ret; + int cbuf_type; + uint32_t val, c; + const uint8_t *cbuf_end; + + cbuf_type = s->cbuf_type; + cbuf_end = s->cbuf_end; + + for(;;) { + // printf("top=%p: pc=%d\n", th_list.top, (int)(pc - (bc_buf + RE_HEADER_LEN))); + opcode = *pc++; + switch(opcode) { + case REOP_match: + { + REExecState *rs; + if (no_recurse) + return (intptr_t)cptr; + ret = 1; + goto recurse; + no_match: + if (no_recurse) + return 0; + ret = 0; + recurse: + for(;;) { + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + if (s->state_stack_len == 0) + return ret; + rs = (REExecState *)(s->state_stack + + (s->state_stack_len - 1) * s->state_size); + if (rs->type == RE_EXEC_STATE_SPLIT) { + if (!ret) { + pop_state: + memcpy(capture, rs->buf, + sizeof(capture[0]) * 2 * s->capture_count); + pop_state1: + pc = rs->pc; + cptr = rs->cptr; + stack_len = rs->stack_len; + memcpy(stack, rs->buf + 2 * s->capture_count, + stack_len * sizeof(stack[0])); + s->state_stack_len--; + break; + } + } else if (rs->type == RE_EXEC_STATE_GREEDY_QUANT) { + if (!ret) { + uint32_t char_count, i; + memcpy(capture, rs->buf, + sizeof(capture[0]) * 2 * s->capture_count); + stack_len = rs->stack_len; + memcpy(stack, rs->buf + 2 * s->capture_count, + stack_len * sizeof(stack[0])); + pc = rs->pc; + cptr = rs->cptr; + /* go backward */ + char_count = get_u32(pc + 12); + for(i = 0; i < char_count; i++) { + PREV_CHAR(cptr, s->cbuf, cbuf_type); + } + pc = (pc + 16) + (int)get_u32(pc); + rs->cptr = cptr; + rs->count--; + if (rs->count == 0) { + s->state_stack_len--; + } + break; + } + } else { + ret = ((rs->type == RE_EXEC_STATE_LOOKAHEAD && ret) || + (rs->type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD && !ret)); + if (ret) { + /* keep the capture in case of positive lookahead */ + if (rs->type == RE_EXEC_STATE_LOOKAHEAD) + goto pop_state1; + else + goto pop_state; + } + } + s->state_stack_len--; + } + } + break; + case REOP_char32: + val = get_u32(pc); + pc += 4; + goto test_char; + case REOP_char16: + val = get_u16(pc); + pc += 2; + goto test_char; + case REOP_char8: + val = get_u8(pc); + pc += 1; + test_char: + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c = lre_canonicalize(c, s->is_unicode); + } + if (val != c) + goto no_match; + break; + case REOP_split_goto_first: + case REOP_split_next_first: + { + const uint8_t *pc1; + + val = get_u32(pc); + pc += 4; + if (opcode == REOP_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + ret = push_state(s, capture, stack, stack_len, + pc1, cptr, RE_EXEC_STATE_SPLIT, 0); + if (ret < 0) + return LRE_RET_MEMORY_ERROR; + break; + } + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(pc); + pc += 4; + ret = push_state(s, capture, stack, stack_len, + pc + (int)val, cptr, + RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead, + 0); + if (ret < 0) + return LRE_RET_MEMORY_ERROR; + break; + + case REOP_goto: + val = get_u32(pc); + pc += 4 + (int)val; + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + break; + case REOP_line_start: + if (cptr == s->cbuf) + break; + if (!s->multi_line) + goto no_match; + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_line_end: + if (cptr == cbuf_end) + break; + if (!s->multi_line) + goto no_match; + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_dot: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (is_line_terminator(c)) + goto no_match; + break; + case REOP_any: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + break; + case REOP_save_start: + case REOP_save_end: + val = *pc++; + assert(val < s->capture_count); + capture[2 * val + opcode - REOP_save_start] = (uint8_t *)cptr; + break; + case REOP_save_reset: + { + uint32_t val2; + val = pc[0]; + val2 = pc[1]; + pc += 2; + assert(val2 < s->capture_count); + while (val <= val2) { + capture[2 * val] = NULL; + capture[2 * val + 1] = NULL; + val++; + } + } + break; + case REOP_push_i32: + val = get_u32(pc); + pc += 4; + stack[stack_len++] = val; + break; + case REOP_drop: + stack_len--; + break; + case REOP_loop: + val = get_u32(pc); + pc += 4; + if (--stack[stack_len - 1] != 0) { + pc += (int)val; + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + } + break; + case REOP_push_char_pos: + stack[stack_len++] = (uintptr_t)cptr; + break; + case REOP_check_advance: + if (stack[--stack_len] == (uintptr_t)cptr) + goto no_match; + break; + case REOP_word_boundary: + case REOP_not_word_boundary: + { + bool v1, v2; + /* char before */ + if (cptr == s->cbuf) { + v1 = false; + } else { + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + v1 = is_word_char(c); + } + /* current char */ + if (cptr >= cbuf_end) { + v2 = false; + } else { + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + v2 = is_word_char(c); + } + if (v1 ^ v2 ^ (REOP_not_word_boundary - opcode)) + goto no_match; + } + break; + case REOP_back_reference: + case REOP_backward_back_reference: + { + const uint8_t *cptr1, *cptr1_end, *cptr1_start; + uint32_t c1, c2; + + val = *pc++; + if (val >= s->capture_count) + goto no_match; + cptr1_start = capture[2 * val]; + cptr1_end = capture[2 * val + 1]; + if (!cptr1_start || !cptr1_end) + break; + if (opcode == REOP_back_reference) { + cptr1 = cptr1_start; + while (cptr1 < cptr1_end) { + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c1, cptr1, cptr1_end, cbuf_type); + GET_CHAR(c2, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } else { + cptr1 = cptr1_end; + while (cptr1 > cptr1_start) { + if (cptr == s->cbuf) + goto no_match; + GET_PREV_CHAR(c1, cptr1, cptr1_start, cbuf_type); + GET_PREV_CHAR(c2, cptr, s->cbuf, cbuf_type); + if (s->ignore_case) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } + } + break; + case REOP_range: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c = lre_canonicalize(c, s->is_unicode); + } + idx_min = 0; + low = get_u16(pc + 0 * 4); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u16(pc + idx_max * 4 + 2); + /* 0xffff in for last value means +infinity */ + if (unlikely(c >= 0xffff) && high == 0xffff) + goto range_match; + if (c > high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u16(pc + idx * 4); + high = get_u16(pc + idx * 4 + 2); + if (c < low) + idx_max = idx - 1; + else if (c > high) + idx_min = idx + 1; + else + goto range_match; + } + goto no_match; + range_match: + pc += 4 * n; + } + break; + case REOP_range32: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c = lre_canonicalize(c, s->is_unicode); + } + idx_min = 0; + low = get_u32(pc + 0 * 8); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u32(pc + idx_max * 8 + 4); + if (c > high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u32(pc + idx * 8); + high = get_u32(pc + idx * 8 + 4); + if (c < low) + idx_max = idx - 1; + else if (c > high) + idx_min = idx + 1; + else + goto range32_match; + } + goto no_match; + range32_match: + pc += 8 * n; + } + break; + case REOP_prev: + /* go to the previous char */ + if (cptr == s->cbuf) + goto no_match; + PREV_CHAR(cptr, s->cbuf, cbuf_type); + break; + case REOP_simple_greedy_quant: + { + uint32_t next_pos, quant_min, quant_max; + size_t q; + intptr_t res; + const uint8_t *pc1; + + next_pos = get_u32(pc); + quant_min = get_u32(pc + 4); + quant_max = get_u32(pc + 8); + pc += 16; + pc1 = pc; + pc += (int)next_pos; + + q = 0; + for(;;) { + if (lre_poll_timeout(s)) + return LRE_RET_TIMEOUT; + res = lre_exec_backtrack(s, capture, stack, stack_len, + pc1, cptr, true); + if (res == LRE_RET_MEMORY_ERROR || + res == LRE_RET_TIMEOUT) + return res; + if (!res) + break; + cptr = (uint8_t *)res; + q++; + if (q >= quant_max && quant_max != INT32_MAX) + break; + } + if (q < quant_min) + goto no_match; + if (q > quant_min) { + /* will examine all matches down to quant_min */ + ret = push_state(s, capture, stack, stack_len, + pc1 - 16, cptr, + RE_EXEC_STATE_GREEDY_QUANT, + q - quant_min); + if (ret < 0) + return LRE_RET_MEMORY_ERROR; + } + } + break; + default: + abort(); + } + } +} + +/* Return 1 if match, 0 if not match or < 0 if error (see LRE_RET_x). cindex is the + starting position of the match and must be such as 0 <= cindex <= + clen. */ +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque) +{ + REExecContext s_s, *s = &s_s; + int re_flags, i, alloca_size, ret; + StackInt *stack_buf; + + re_flags = lre_get_flags(bc_buf); + s->multi_line = (re_flags & LRE_FLAG_MULTILINE) != 0; + s->ignore_case = (re_flags & LRE_FLAG_IGNORECASE) != 0; + s->is_unicode = (re_flags & LRE_FLAG_UNICODE) != 0; + s->capture_count = bc_buf[RE_HEADER_CAPTURE_COUNT]; + s->stack_size_max = bc_buf[RE_HEADER_STACK_SIZE]; + s->cbuf = cbuf; + s->cbuf_end = cbuf + (clen << cbuf_type); + s->cbuf_type = cbuf_type; + if (s->cbuf_type == 1 && s->is_unicode) + s->cbuf_type = 2; + s->interrupt_counter = INTERRUPT_COUNTER_INIT; + s->opaque = opaque; + + s->state_size = sizeof(REExecState) + + s->capture_count * sizeof(capture[0]) * 2 + + s->stack_size_max * sizeof(stack_buf[0]); + s->state_stack = NULL; + s->state_stack_len = 0; + s->state_stack_size = 0; + + for(i = 0; i < s->capture_count * 2; i++) + capture[i] = NULL; + alloca_size = s->stack_size_max * sizeof(stack_buf[0]); + stack_buf = alloca(alloca_size); + ret = lre_exec_backtrack(s, capture, stack_buf, 0, bc_buf + RE_HEADER_LEN, + cbuf + (cindex << cbuf_type), false); + lre_realloc(s->opaque, s->state_stack, 0); + return ret; +} + +int lre_get_capture_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT]; +} + +int lre_get_flags(const uint8_t *bc_buf) +{ + return get_u16(bc_buf + RE_HEADER_FLAGS); +} + +/* Return NULL if no group names. Otherwise, return a pointer to + 'capture_count - 1' zero terminated UTF-8 strings. */ +const char *lre_get_groupnames(const uint8_t *bc_buf) +{ + uint32_t re_bytecode_len; + if ((lre_get_flags(bc_buf) & LRE_FLAG_NAMED_GROUPS) == 0) + return NULL; + re_bytecode_len = get_u32(bc_buf + RE_HEADER_BYTECODE_LEN); + return (const char *)(bc_buf + RE_HEADER_LEN + re_bytecode_len); +} + +void lre_byte_swap(uint8_t *buf, size_t len, bool is_byte_swapped) +{ + uint8_t *p, *pe; + uint32_t n, r, nw; + + p = buf; + if (len < RE_HEADER_LEN) + abort(); + + // format is: + //
+ // + // + // + // etc. + inplace_bswap16(&p[RE_HEADER_FLAGS]); + + n = get_u32(&p[RE_HEADER_BYTECODE_LEN]); + inplace_bswap32(&p[RE_HEADER_BYTECODE_LEN]); + if (is_byte_swapped) + n = bswap32(n); + if (n > len - RE_HEADER_LEN) + abort(); + + p = &buf[RE_HEADER_LEN]; + pe = &p[n]; + + while (p < pe) { + n = reopcode_info[*p].size; + switch (n) { + case 1: + case 2: + break; + case 3: + switch (*p) { + case REOP_save_reset: // has two 8 bit arguments + break; + case REOP_range32: // variable length + nw = get_u16(&p[1]); // number of pairs of uint32_t + if (is_byte_swapped) + n = bswap16(n); + for (r = 3 + 8 * nw; n < r; n += 4) + inplace_bswap32(&p[n]); + goto doswap16; + case REOP_range: // variable length + nw = get_u16(&p[1]); // number of pairs of uint16_t + if (is_byte_swapped) + n = bswap16(n); + for (r = 3 + 4 * nw; n < r; n += 2) + inplace_bswap16(&p[n]); + goto doswap16; + default: + doswap16: + inplace_bswap16(&p[1]); + break; + } + break; + case 5: + inplace_bswap32(&p[1]); + break; + case 17: + assert(*p == REOP_simple_greedy_quant); + inplace_bswap32(&p[1]); + inplace_bswap32(&p[5]); + inplace_bswap32(&p[9]); + inplace_bswap32(&p[13]); + break; + default: + abort(); + } + p = &p[n]; + } +} + +#ifdef TEST + +bool lre_check_stack_overflow(void *opaque, size_t alloca_size) +{ + return false; +} + +void *lre_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +int main(int argc, char **argv) +{ + int len, flags, ret, i; + uint8_t *bc; + char error_msg[64]; + uint8_t *capture[CAPTURE_COUNT_MAX * 2]; + const char *input; + int input_len, capture_count; + + if (argc < 4) { + printf("usage: %s regexp flags input\n", argv[0]); + exit(1); + } + flags = atoi(argv[2]); + bc = lre_compile(&len, error_msg, sizeof(error_msg), argv[1], + strlen(argv[1]), flags, NULL); + if (!bc) { + fprintf(stderr, "error: %s\n", error_msg); + exit(1); + } + + input = argv[3]; + input_len = strlen(input); + + ret = lre_exec(capture, bc, (uint8_t *)input, 0, input_len, 0, NULL); + printf("ret=%d\n", ret); + if (ret == 1) { + capture_count = lre_get_capture_count(bc); + for(i = 0; i < 2 * capture_count; i++) { + uint8_t *ptr; + ptr = capture[i]; + printf("%d: ", i); + if (!ptr) + printf(""); + else + printf("%u", (int)(ptr - (uint8_t *)input)); + printf("\n"); + } + } + return 0; +} +#endif diff --git a/NativeScript/napi/android/quickjs/source_ng/libregexp.h b/NativeScript/napi/android/quickjs/source_ng/libregexp.h new file mode 100755 index 000000000..898e9a7a3 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/libregexp.h @@ -0,0 +1,97 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LIBREGEXP_H +#define LIBREGEXP_H + +#include +#include + +#include "libunicode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define LRE_FLAG_GLOBAL (1 << 0) +#define LRE_FLAG_IGNORECASE (1 << 1) +#define LRE_FLAG_MULTILINE (1 << 2) +#define LRE_FLAG_DOTALL (1 << 3) +#define LRE_FLAG_UNICODE (1 << 4) +#define LRE_FLAG_STICKY (1 << 5) +#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */ +#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ +#define LRE_FLAG_UNICODE_SETS (1 << 8) + +#define LRE_RET_MEMORY_ERROR (-1) +#define LRE_RET_TIMEOUT (-2) + +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque); +int lre_get_capture_count(const uint8_t *bc_buf); +int lre_get_flags(const uint8_t *bc_buf); +const char *lre_get_groupnames(const uint8_t *bc_buf); +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque); + +int lre_parse_escape(const uint8_t **pp, int allow_utf16); +bool lre_is_space(int c); + +void lre_byte_swap(uint8_t *buf, size_t len, bool is_byte_swapped); + +/* must be provided by the user */ +bool lre_check_stack_overflow(void *opaque, size_t alloca_size); +/* must be provided by the user, return non zero if time out */ +int lre_check_timeout(void *opaque); +void *lre_realloc(void *opaque, void *ptr, size_t size); + +/* JS identifier test */ +extern uint32_t const lre_id_start_table_ascii[4]; +extern uint32_t const lre_id_continue_table_ascii[4]; + +static inline int lre_js_is_ident_first(int c) +{ + if ((uint32_t)c < 128) { + return (lre_id_start_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { + return lre_is_id_start(c); + } +} + +static inline int lre_js_is_ident_next(int c) +{ + if ((uint32_t)c < 128) { + return (lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { + /* ZWNJ and ZWJ are accepted in identifiers */ + return lre_is_id_continue(c) || c == 0x200C || c == 0x200D; + } +} + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIBREGEXP_H */ diff --git a/NativeScript/napi/android/quickjs/source_ng/libunicode-table.h b/NativeScript/napi/android/quickjs/source_ng/libunicode-table.h new file mode 100755 index 000000000..b48a3a746 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/libunicode-table.h @@ -0,0 +1,4658 @@ +/* Compressed unicode tables */ +/* Automatically generated file - do not edit */ + +#include + +static const uint32_t case_conv_table1[378] = { + 0x00209a30, 0x00309a00, 0x005a8173, 0x00601730, + 0x006c0730, 0x006f81b3, 0x00701700, 0x007c0700, + 0x007f8100, 0x00803040, 0x009801c3, 0x00988190, + 0x00990640, 0x009c9040, 0x00a481b4, 0x00a52e40, + 0x00bc0130, 0x00bc8640, 0x00bf8170, 0x00c00100, + 0x00c08130, 0x00c10440, 0x00c30130, 0x00c38240, + 0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130, + 0x00c80130, 0x00c88240, 0x00c98130, 0x00ca0130, + 0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240, + 0x00cd0100, 0x00cd8101, 0x00ce0130, 0x00ce8130, + 0x00cf0100, 0x00cf8130, 0x00d00640, 0x00d30130, + 0x00d38240, 0x00d48130, 0x00d60240, 0x00d70130, + 0x00d78240, 0x00d88230, 0x00d98440, 0x00db8130, + 0x00dc0240, 0x00de0240, 0x00df8100, 0x00e20350, + 0x00e38350, 0x00e50350, 0x00e69040, 0x00ee8100, + 0x00ef1240, 0x00f801b4, 0x00f88350, 0x00fa0240, + 0x00fb0130, 0x00fb8130, 0x00fc2840, 0x01100130, + 0x01111240, 0x011d0131, 0x011d8240, 0x011e8130, + 0x011f0131, 0x011f8201, 0x01208240, 0x01218130, + 0x01220130, 0x01228130, 0x01230a40, 0x01280101, + 0x01288101, 0x01290101, 0x01298100, 0x012a0100, + 0x012b0200, 0x012c8100, 0x012d8100, 0x012e0101, + 0x01300100, 0x01308101, 0x01318100, 0x01320101, + 0x01328101, 0x01330101, 0x01340100, 0x01348100, + 0x01350101, 0x01358101, 0x01360101, 0x01378100, + 0x01388101, 0x01390100, 0x013a8100, 0x013e8101, + 0x01400100, 0x01410101, 0x01418100, 0x01438101, + 0x01440100, 0x01448100, 0x01450200, 0x01460100, + 0x01490100, 0x014e8101, 0x014f0101, 0x01a28173, + 0x01b80440, 0x01bb0240, 0x01bd8300, 0x01bf8130, + 0x01c30130, 0x01c40330, 0x01c60130, 0x01c70230, + 0x01c801d0, 0x01c89130, 0x01d18930, 0x01d60100, + 0x01d68300, 0x01d801d3, 0x01d89100, 0x01e10173, + 0x01e18900, 0x01e60100, 0x01e68200, 0x01e78130, + 0x01e80173, 0x01e88173, 0x01ea8173, 0x01eb0173, + 0x01eb8100, 0x01ec1840, 0x01f80173, 0x01f88173, + 0x01f90100, 0x01f98100, 0x01fa01a0, 0x01fa8173, + 0x01fb8240, 0x01fc8130, 0x01fd0240, 0x01fe8330, + 0x02001030, 0x02082030, 0x02182000, 0x02281000, + 0x02302240, 0x02453640, 0x02600130, 0x02608e40, + 0x02678100, 0x02686040, 0x0298a630, 0x02b0a600, + 0x02c381b5, 0x08502631, 0x08638131, 0x08668131, + 0x08682b00, 0x087e8300, 0x09d05011, 0x09f80610, + 0x09fc0620, 0x0e400174, 0x0e408174, 0x0e410174, + 0x0e418174, 0x0e420174, 0x0e428174, 0x0e430174, + 0x0e438180, 0x0e440180, 0x0e448240, 0x0e482b30, + 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, 0x0ec70101, + 0x0f007e40, 0x0f3f1840, 0x0f4b01b5, 0x0f4b81b6, + 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, 0x0f4d8180, + 0x0f4f0130, 0x0f506040, 0x0f800800, 0x0f840830, + 0x0f880600, 0x0f8c0630, 0x0f900800, 0x0f940830, + 0x0f980800, 0x0f9c0830, 0x0fa00600, 0x0fa40630, + 0x0fa801b0, 0x0fa88100, 0x0fa901d3, 0x0fa98100, + 0x0faa01d3, 0x0faa8100, 0x0fab01d3, 0x0fab8100, + 0x0fac8130, 0x0fad8130, 0x0fae8130, 0x0faf8130, + 0x0fb00800, 0x0fb40830, 0x0fb80200, 0x0fb90400, + 0x0fbb0201, 0x0fbc0201, 0x0fbd0201, 0x0fbe0201, + 0x0fc008b7, 0x0fc40867, 0x0fc808b8, 0x0fcc0868, + 0x0fd008b8, 0x0fd40868, 0x0fd80200, 0x0fd901b9, + 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1, 0x0fdb81d7, + 0x0fdc0230, 0x0fdd0230, 0x0fde0161, 0x0fdf0173, + 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, 0x0fe301b2, + 0x0fe381d8, 0x0fe40430, 0x0fe60162, 0x0fe80201, + 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, 0x0feb81d0, + 0x0fec0230, 0x0fed0230, 0x0ff00201, 0x0ff101d3, + 0x0ff181d3, 0x0ff201ba, 0x0ff28101, 0x0ff301b0, + 0x0ff381d3, 0x0ff40231, 0x0ff50230, 0x0ff60131, + 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, 0x0ffb01b2, + 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230, 0x0ffe0162, + 0x109301a0, 0x109501a0, 0x109581a0, 0x10990131, + 0x10a70101, 0x10b01031, 0x10b81001, 0x10c18240, + 0x125b1a31, 0x12681a01, 0x16003031, 0x16183001, + 0x16300240, 0x16310130, 0x16318130, 0x16320130, + 0x16328100, 0x16330100, 0x16338640, 0x16368130, + 0x16370130, 0x16378130, 0x16380130, 0x16390240, + 0x163a8240, 0x163f0230, 0x16406440, 0x16758440, + 0x16790240, 0x16802600, 0x16938100, 0x16968100, + 0x53202e40, 0x53401c40, 0x53910e40, 0x53993e40, + 0x53bc8440, 0x53be8130, 0x53bf0a40, 0x53c58240, + 0x53c68130, 0x53c80440, 0x53ca0101, 0x53cb1440, + 0x53d50130, 0x53d58130, 0x53d60130, 0x53d68130, + 0x53d70130, 0x53d80130, 0x53d88130, 0x53d90130, + 0x53d98131, 0x53da1040, 0x53e20131, 0x53e28130, + 0x53e30130, 0x53e38440, 0x53e58130, 0x53e60240, + 0x53e80240, 0x53eb0640, 0x53ee0130, 0x53fa8240, + 0x55a98101, 0x55b85020, 0x7d8001b2, 0x7d8081b2, + 0x7d8101b2, 0x7d8181da, 0x7d8201da, 0x7d8281b3, + 0x7d8301b3, 0x7d8981bb, 0x7d8a01bb, 0x7d8a81bb, + 0x7d8b01bc, 0x7d8b81bb, 0x7f909a31, 0x7fa09a01, + 0x82002831, 0x82142801, 0x82582431, 0x826c2401, + 0x82b80b31, 0x82be0f31, 0x82c60731, 0x82ca0231, + 0x82cb8b01, 0x82d18f01, 0x82d98701, 0x82dd8201, + 0x86403331, 0x86603301, 0x86a81631, 0x86b81601, + 0x8c502031, 0x8c602001, 0xb7202031, 0xb7302001, + 0xf4802231, 0xf4912201, +}; + +static const uint8_t case_conv_table2[378] = { + 0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04, + 0x10, 0x00, 0x8f, 0x0b, 0x00, 0x00, 0x11, 0x00, + 0x08, 0x00, 0x53, 0x4b, 0x52, 0x00, 0x53, 0x00, + 0x54, 0x00, 0x3b, 0x55, 0x56, 0x00, 0x58, 0x5a, + 0x40, 0x5f, 0x5e, 0x00, 0x47, 0x52, 0x63, 0x65, + 0x43, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, 0x6c, + 0x00, 0x6e, 0x00, 0x70, 0x00, 0x00, 0x41, 0x00, + 0x00, 0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, + 0x20, 0x36, 0x00, 0x28, 0x00, 0x24, 0x00, 0x24, + 0x25, 0x2d, 0x00, 0x13, 0x6d, 0x6f, 0x00, 0x29, + 0x27, 0x2a, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x41, + 0x1e, 0x42, 0x1f, 0x4e, 0x3c, 0x40, 0x22, 0x21, + 0x44, 0x21, 0x43, 0x26, 0x28, 0x27, 0x29, 0x23, + 0x2b, 0x4b, 0x2d, 0x46, 0x2f, 0x4c, 0x31, 0x4d, + 0x33, 0x47, 0x45, 0x99, 0x00, 0x00, 0x97, 0x91, + 0x7f, 0x80, 0x85, 0x86, 0x12, 0x82, 0x84, 0x78, + 0x79, 0x12, 0x7d, 0xa3, 0x7e, 0x7a, 0x7b, 0x8c, + 0x92, 0x98, 0xa6, 0xa0, 0x87, 0x00, 0x9a, 0xa1, + 0x95, 0x77, 0x33, 0x95, 0x00, 0x90, 0x00, 0x76, + 0x9b, 0x9a, 0x99, 0x98, 0x00, 0x00, 0xa0, 0x00, + 0x9e, 0x00, 0xa3, 0xa2, 0x15, 0x31, 0x32, 0x33, + 0xb7, 0xb8, 0x55, 0xac, 0xab, 0x12, 0x14, 0x1e, + 0x21, 0x22, 0x22, 0x2a, 0x34, 0x35, 0x00, 0xa8, + 0xa9, 0x39, 0x22, 0x4c, 0x00, 0x00, 0x97, 0x01, + 0x5a, 0xda, 0x1d, 0x36, 0x05, 0x00, 0xc7, 0xc6, + 0xc9, 0xc8, 0xcb, 0xca, 0xcd, 0xcc, 0xcf, 0xce, + 0xc4, 0xd8, 0x45, 0xd9, 0x42, 0xda, 0x46, 0xdb, + 0xd1, 0xd3, 0xd5, 0xd7, 0xdd, 0xdc, 0xf1, 0xf9, + 0x01, 0x11, 0x0a, 0x12, 0x80, 0x9f, 0x00, 0x21, + 0x80, 0xa3, 0xf0, 0x00, 0xc0, 0x40, 0xc6, 0x60, + 0xea, 0xde, 0xe6, 0x99, 0xc0, 0x00, 0x00, 0x06, + 0x60, 0xdf, 0x29, 0x00, 0x15, 0x12, 0x06, 0x16, + 0xfb, 0xe0, 0x09, 0x15, 0x12, 0x84, 0x0b, 0xc6, + 0x16, 0x02, 0xe2, 0x06, 0xc0, 0x40, 0x00, 0x46, + 0x60, 0xe1, 0xe3, 0x6d, 0x37, 0x38, 0x39, 0x18, + 0x17, 0x1a, 0x19, 0x00, 0x1d, 0x1c, 0x1f, 0x1e, + 0x00, 0x61, 0xba, 0x67, 0x45, 0x48, 0x00, 0x50, + 0x64, 0x4f, 0x51, 0x00, 0x00, 0x49, 0x00, 0x00, + 0x00, 0xa5, 0xa6, 0xa7, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x00, 0x00, 0x5c, 0x00, 0x4a, 0x00, + 0x5d, 0x57, 0x59, 0x62, 0x60, 0x72, 0x6b, 0x71, + 0x54, 0x00, 0x3e, 0x69, 0xbb, 0x00, 0x5b, 0x00, + 0x00, 0x00, 0x25, 0x00, 0x48, 0xaa, 0x8a, 0x8b, + 0x8c, 0xab, 0xac, 0x58, 0x58, 0xaf, 0x94, 0xb0, + 0x6f, 0xb2, 0x63, 0x62, 0x65, 0x64, 0x67, 0x66, + 0x6c, 0x6d, 0x6e, 0x6f, 0x68, 0x69, 0x6a, 0x6b, + 0x71, 0x70, 0x73, 0x72, 0x75, 0x74, 0x77, 0x76, + 0x79, 0x78, +}; + +static const uint16_t case_conv_ext[58] = { + 0x0399, 0x0308, 0x0301, 0x03a5, 0x0313, 0x0300, 0x0342, 0x0391, + 0x0397, 0x03a9, 0x0046, 0x0049, 0x004c, 0x0053, 0x0069, 0x0307, + 0x02bc, 0x004e, 0x004a, 0x030c, 0x0535, 0x0552, 0x0048, 0x0331, + 0x0054, 0x0057, 0x030a, 0x0059, 0x0041, 0x02be, 0x1f08, 0x1f80, + 0x1f28, 0x1f90, 0x1f68, 0x1fa0, 0x1fba, 0x0386, 0x1fb3, 0x1fca, + 0x0389, 0x1fc3, 0x03a1, 0x1ffa, 0x038f, 0x1ff3, 0x0544, 0x0546, + 0x053b, 0x054e, 0x053d, 0x03b8, 0x0462, 0xa64a, 0x1e60, 0x03c9, + 0x006b, 0x00e5, +}; + +static const uint8_t unicode_prop_Cased1_table[193] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3, + 0x80, 0x9b, 0x81, 0x8d, 0x02, 0x80, 0xe1, 0x80, + 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, 0x11, 0x03, + 0x04, 0x08, 0x01, 0x08, 0x30, 0x08, 0x01, 0x15, + 0x20, 0x00, 0x39, 0x99, 0x31, 0x9d, 0x84, 0x40, + 0x94, 0x80, 0xd6, 0x82, 0xa6, 0x80, 0x41, 0x62, + 0x80, 0xa6, 0x80, 0x4b, 0x72, 0x80, 0x4c, 0x02, + 0xf8, 0x02, 0x80, 0x8f, 0x80, 0xb0, 0x40, 0xdb, + 0x08, 0x80, 0x41, 0xd0, 0x80, 0x8c, 0x80, 0x8f, + 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28, + 0x10, 0x11, 0x02, 0x01, 0x18, 0x0b, 0x24, 0x4b, + 0x26, 0x01, 0x01, 0x86, 0xe5, 0x80, 0x60, 0x79, + 0xb6, 0x81, 0x40, 0x91, 0x81, 0xbd, 0x88, 0x94, + 0x05, 0x80, 0x98, 0x80, 0xa2, 0x00, 0x80, 0x9b, + 0x12, 0x82, 0x43, 0x34, 0xa2, 0x06, 0x80, 0x8d, + 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, 0x88, + 0x60, 0xcc, 0x44, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x07, 0x47, 0x33, 0x89, 0x80, 0x93, 0x2d, 0x41, + 0x04, 0xbd, 0x50, 0xc1, 0x99, 0x85, 0x99, 0x85, + 0x99, +}; + +static const uint8_t unicode_prop_Cased1_index[18] = { + 0xb9, 0x02, 0x80, 0xa0, 0x1e, 0x40, 0x9e, 0xa6, + 0x40, 0xbb, 0x07, 0x01, 0xdb, 0xd6, 0x01, 0x8a, + 0xf1, 0x01, +}; + +static const uint8_t unicode_prop_Case_Ignorable_table[764] = { + 0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, + 0xc6, 0x03, 0x00, 0x03, 0x01, 0x81, 0x41, 0xf6, + 0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40, + 0xfa, 0x86, 0x40, 0xce, 0x04, 0x80, 0xb0, 0xac, + 0x00, 0x01, 0x01, 0x00, 0xab, 0x80, 0x8a, 0x85, + 0x89, 0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, + 0x80, 0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, + 0x9d, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0x18, 0x08, + 0x97, 0x97, 0xaa, 0x82, 0xab, 0x06, 0x0c, 0x88, + 0xa8, 0xb9, 0xb6, 0x00, 0x03, 0x3b, 0x02, 0x86, + 0x89, 0x81, 0x8c, 0x80, 0x8e, 0x80, 0xb9, 0x03, + 0x1f, 0x80, 0x93, 0x81, 0x99, 0x01, 0x81, 0xb8, + 0x03, 0x0b, 0x09, 0x12, 0x80, 0x9d, 0x0a, 0x80, + 0x8a, 0x81, 0xb8, 0x03, 0x20, 0x0b, 0x80, 0x93, + 0x81, 0x95, 0x28, 0x80, 0xb9, 0x01, 0x00, 0x1f, + 0x06, 0x81, 0x8a, 0x81, 0x9d, 0x80, 0xbc, 0x80, + 0x8b, 0x80, 0xb1, 0x02, 0x80, 0xb6, 0x00, 0x14, + 0x10, 0x1e, 0x81, 0x8a, 0x81, 0x9c, 0x80, 0xb9, + 0x01, 0x05, 0x04, 0x81, 0x93, 0x81, 0x9b, 0x81, + 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81, 0x9c, 0x80, + 0xc7, 0x06, 0x10, 0x80, 0xd9, 0x01, 0x86, 0x8a, + 0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x86, 0xc8, + 0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04, + 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe5, + 0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f, + 0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd, + 0x80, 0x42, 0x5f, 0x82, 0x43, 0xb1, 0x82, 0x9c, + 0x81, 0x9d, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37, + 0x01, 0x8a, 0x10, 0x20, 0xac, 0x84, 0xb2, 0x80, + 0xc0, 0x81, 0xa1, 0x80, 0xf5, 0x13, 0x81, 0x88, + 0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00, + 0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07, + 0x9e, 0xb0, 0x83, 0xaf, 0x00, 0x20, 0x04, 0x80, + 0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, 0x08, 0x82, + 0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, 0x39, 0x81, + 0xbf, 0x85, 0xd1, 0x10, 0x8c, 0x06, 0x18, 0x28, + 0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, 0xe4, 0x41, + 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, + 0x82, 0x8c, 0x81, 0x8b, 0x27, 0x81, 0x89, 0x01, + 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, 0x8c, 0x80, + 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, 0x81, 0xf0, + 0x82, 0xfc, 0x80, 0x8e, 0x80, 0xdf, 0x9f, 0xae, + 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, 0x24, 0x80, + 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, + 0x44, 0xe1, 0x85, 0x41, 0x0d, 0x80, 0xe1, 0x18, + 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, 0x8d, 0xa1, + 0xcd, 0x80, 0x96, 0x82, 0xe6, 0x12, 0x0f, 0x02, + 0x03, 0x80, 0x98, 0x0c, 0x80, 0x40, 0x96, 0x81, + 0x99, 0x91, 0x8c, 0x80, 0xa5, 0x87, 0x98, 0x8a, + 0xad, 0x82, 0xaf, 0x01, 0x19, 0x81, 0x90, 0x80, + 0x94, 0x81, 0xc1, 0x29, 0x09, 0x81, 0x8b, 0x07, + 0x80, 0xa2, 0x80, 0x8a, 0x80, 0xb2, 0x00, 0x11, + 0x0c, 0x08, 0x80, 0x9a, 0x80, 0x8d, 0x0c, 0x08, + 0x80, 0xe3, 0x84, 0x88, 0x82, 0xf8, 0x01, 0x03, + 0x80, 0x60, 0x4f, 0x2f, 0x80, 0x40, 0x92, 0x90, + 0x42, 0x3c, 0x8f, 0x10, 0x8b, 0x8f, 0xa1, 0x01, + 0x80, 0x40, 0xa8, 0x06, 0x05, 0x80, 0x8a, 0x80, + 0xa2, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, + 0x80, 0x94, 0x82, 0x42, 0x00, 0x80, 0x40, 0xe1, + 0x80, 0x40, 0x94, 0x84, 0x44, 0x04, 0x28, 0xa9, + 0x80, 0x88, 0x42, 0x45, 0x10, 0x0c, 0x83, 0xa7, + 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x83, + 0xa5, 0x80, 0x99, 0x20, 0x80, 0x41, 0x3a, 0x81, + 0xce, 0x83, 0xc5, 0x8a, 0xb0, 0x83, 0xfa, 0x80, + 0xb5, 0x8e, 0xa8, 0x01, 0x81, 0x89, 0x82, 0xb0, + 0x19, 0x09, 0x03, 0x80, 0x89, 0x80, 0xb1, 0x82, + 0xa3, 0x20, 0x87, 0xbd, 0x80, 0x8b, 0x81, 0xb3, + 0x88, 0x89, 0x19, 0x80, 0xde, 0x11, 0x00, 0x0d, + 0x01, 0x80, 0x40, 0x9c, 0x02, 0x87, 0x94, 0x81, + 0xb8, 0x0a, 0x80, 0xa4, 0x32, 0x84, 0xc5, 0x85, + 0x8c, 0x00, 0x00, 0x80, 0x8d, 0x81, 0xd4, 0x39, + 0x10, 0x80, 0x96, 0x80, 0xd3, 0x28, 0x03, 0x08, + 0x81, 0x40, 0xed, 0x1d, 0x08, 0x81, 0x9a, 0x81, + 0xd4, 0x39, 0x00, 0x81, 0xe9, 0x00, 0x01, 0x28, + 0x80, 0xe4, 0x00, 0x01, 0x18, 0x84, 0x41, 0x02, + 0x88, 0x01, 0x40, 0xff, 0x08, 0x03, 0x80, 0x40, + 0x8f, 0x19, 0x0b, 0x80, 0x9f, 0x89, 0xa7, 0x29, + 0x1f, 0x80, 0x88, 0x29, 0x82, 0xad, 0x8c, 0x01, + 0x41, 0x95, 0x30, 0x28, 0x80, 0xd1, 0x95, 0x0e, + 0x01, 0x01, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x80, + 0xc7, 0x0a, 0x00, 0x80, 0x41, 0x5a, 0x81, 0x8a, + 0x81, 0xb3, 0x24, 0x00, 0x80, 0x96, 0x80, 0x54, + 0xd4, 0x90, 0x85, 0x8e, 0x60, 0x2c, 0xc7, 0x8b, + 0x12, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x88, 0x83, + 0x41, 0xfb, 0x82, 0xa7, 0x81, 0x41, 0xe1, 0x80, + 0xbe, 0x90, 0xbf, 0x08, 0x81, 0x60, 0x40, 0x0a, + 0x18, 0x30, 0x81, 0x4c, 0x9d, 0x08, 0x83, 0x52, + 0x5b, 0xad, 0x81, 0x96, 0x42, 0x1f, 0x82, 0x88, + 0x8f, 0x0e, 0x9d, 0x83, 0x40, 0x93, 0x82, 0x47, + 0xba, 0xb6, 0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, + 0x20, 0x8e, 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, + 0x04, 0x84, 0xbd, 0xa0, 0x80, 0x40, 0x9f, 0x8d, + 0x41, 0x6f, 0x80, 0xbc, 0x83, 0x41, 0xfa, 0x84, + 0x40, 0xfd, 0x81, 0x42, 0xdf, 0x86, 0xec, 0x87, + 0x4a, 0xae, 0x84, 0x6c, 0x0c, 0x00, 0x80, 0x9d, + 0xdf, 0xff, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_Case_Ignorable_index[72] = { + 0xbe, 0x05, 0x00, 0xfe, 0x07, 0x00, 0x52, 0x0a, + 0xa0, 0xc1, 0x0b, 0x00, 0x82, 0x0d, 0x00, 0x3f, + 0x10, 0x80, 0xd4, 0x17, 0x40, 0xcf, 0x1a, 0x20, + 0xf5, 0x1c, 0x00, 0x80, 0x20, 0x00, 0x16, 0xa0, + 0x00, 0xc6, 0xa8, 0x00, 0xc2, 0xaa, 0x60, 0x56, + 0xfe, 0x20, 0xb1, 0x07, 0x01, 0x02, 0x10, 0x01, + 0x42, 0x12, 0x41, 0xc4, 0x14, 0x21, 0xe1, 0x19, + 0x81, 0x48, 0x1d, 0x01, 0x44, 0x6b, 0x01, 0x83, + 0xd1, 0x21, 0x3e, 0xe1, 0x01, 0xf0, 0x01, 0x0e, +}; + +static const uint8_t unicode_prop_ID_Start_table[1133] = { + 0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03, + 0x04, 0x96, 0x80, 0x9e, 0x80, 0x41, 0xc9, 0x83, + 0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20, + 0x09, 0x18, 0x05, 0x00, 0x10, 0x00, 0x93, 0x80, + 0xd2, 0x80, 0x40, 0x8a, 0x87, 0x40, 0xa5, 0x80, + 0xa5, 0x08, 0x85, 0xa8, 0xc6, 0x9a, 0x1b, 0xac, + 0xaa, 0xa2, 0x08, 0xe2, 0x00, 0x8e, 0x0e, 0x81, + 0x89, 0x11, 0x80, 0x8f, 0x00, 0x9d, 0x9c, 0xd8, + 0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95, + 0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a, + 0x84, 0x97, 0x05, 0x90, 0xa9, 0xb9, 0xb5, 0x10, + 0x91, 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, + 0x95, 0x06, 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, + 0x08, 0x82, 0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, + 0x95, 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, + 0x92, 0x82, 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, + 0x01, 0x04, 0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, + 0x80, 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, + 0x10, 0x9d, 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, + 0x2a, 0x10, 0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, + 0x8b, 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, + 0x8f, 0x10, 0x99, 0x11, 0x01, 0x81, 0x9d, 0x03, + 0x38, 0x10, 0x96, 0x80, 0x89, 0x04, 0x10, 0x9e, + 0x08, 0x81, 0x8e, 0x81, 0x90, 0x88, 0x02, 0x80, + 0xa8, 0x08, 0x8f, 0x04, 0x17, 0x82, 0x97, 0x2c, + 0x91, 0x82, 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, + 0xaf, 0x01, 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, + 0x97, 0x00, 0x80, 0x89, 0x01, 0x88, 0x01, 0x20, + 0x80, 0x94, 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, + 0x9a, 0x84, 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, + 0x1a, 0x02, 0x0e, 0x13, 0x8c, 0x8b, 0x80, 0x90, + 0xa5, 0x00, 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, + 0x03, 0x0e, 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, + 0xa0, 0x03, 0x0e, 0x00, 0x03, 0x81, 0x8e, 0x80, + 0xb8, 0x03, 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, + 0x0d, 0x82, 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, + 0x84, 0xca, 0x82, 0x8a, 0x86, 0x91, 0x8c, 0x92, + 0x8d, 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, 0xa2, + 0x03, 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, + 0xc5, 0x89, 0x9e, 0xb0, 0x9d, 0x0c, 0x8a, 0xab, + 0x83, 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, 0x80, + 0xdc, 0xae, 0x90, 0x87, 0xb5, 0x9d, 0x8c, 0x81, + 0x89, 0xab, 0x99, 0xa3, 0xa8, 0x82, 0x89, 0xa3, + 0x81, 0x8a, 0x84, 0xaa, 0x0a, 0xa8, 0x18, 0x28, + 0x0a, 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, + 0x81, 0xa5, 0x0d, 0x0f, 0x00, 0x00, 0x00, 0x80, + 0x9e, 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, 0x13, + 0x0d, 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, + 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, + 0x0d, 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, 0x24, + 0x18, 0x90, 0xa8, 0x4a, 0x76, 0x40, 0xe4, 0x2b, + 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7, 0x30, + 0x8f, 0x96, 0x88, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98, 0x88, + 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9, 0x03, + 0x84, 0xaa, 0x80, 0xdd, 0x90, 0x9f, 0xaf, 0x8f, + 0x41, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x56, 0x8c, + 0xc2, 0xad, 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, + 0x81, 0x93, 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, + 0x88, 0x81, 0xe6, 0x81, 0xc2, 0x09, 0x00, 0x07, + 0x94, 0x8f, 0x02, 0x03, 0x80, 0x96, 0x9c, 0xb3, + 0x8d, 0xb1, 0xbd, 0x2a, 0x00, 0x81, 0x8a, 0x9b, + 0x89, 0x96, 0x98, 0x9c, 0x86, 0xae, 0x9b, 0x80, + 0x8f, 0x20, 0x89, 0x89, 0x20, 0xa8, 0x96, 0x10, + 0x87, 0x93, 0x96, 0x10, 0x82, 0xb1, 0x00, 0x11, + 0x0c, 0x08, 0x00, 0x97, 0x11, 0x8a, 0x32, 0x8b, + 0x29, 0x29, 0x85, 0x88, 0x30, 0x30, 0xaa, 0x80, + 0x8d, 0x85, 0xf2, 0x9c, 0x60, 0x2b, 0xa3, 0x8b, + 0x96, 0x83, 0xb0, 0x60, 0x21, 0x03, 0x41, 0x6d, + 0x81, 0xe9, 0xa5, 0x86, 0x8b, 0x24, 0x00, 0x89, + 0x80, 0x8c, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8b, 0xf3, 0x20, 0x40, 0x86, 0xa3, 0x99, 0x85, + 0x99, 0x8a, 0xd8, 0x15, 0x0d, 0x0d, 0x0a, 0xa2, + 0x8b, 0x80, 0x99, 0x80, 0x92, 0x01, 0x80, 0x8e, + 0x81, 0x8d, 0xa1, 0xfa, 0xc4, 0xb4, 0x41, 0x0a, + 0x9c, 0x82, 0xb0, 0xae, 0x9f, 0x8c, 0x9d, 0x84, + 0xa5, 0x89, 0x9d, 0x81, 0xa3, 0x1f, 0x04, 0xa9, + 0x40, 0x9d, 0x91, 0xa3, 0x83, 0xa3, 0x83, 0xa7, + 0x87, 0xb3, 0x8b, 0x8a, 0x80, 0x8e, 0x06, 0x01, + 0x80, 0x8a, 0x80, 0x8e, 0x06, 0x01, 0x82, 0xb3, + 0x8b, 0x41, 0x36, 0x88, 0x95, 0x89, 0x87, 0x97, + 0x28, 0xa9, 0x80, 0x88, 0xc4, 0x29, 0x00, 0xab, + 0x01, 0x10, 0x81, 0x96, 0x89, 0x96, 0x88, 0x9e, + 0xc0, 0x92, 0x01, 0x89, 0x95, 0x89, 0x99, 0xc5, + 0xb7, 0x29, 0xbf, 0x80, 0x8e, 0x18, 0x10, 0x9c, + 0xa9, 0x9c, 0x82, 0x9c, 0xa2, 0x38, 0x9b, 0x9a, + 0xb5, 0x89, 0x95, 0x89, 0x92, 0x8c, 0x91, 0xed, + 0xc8, 0xb6, 0xb2, 0x8c, 0xb2, 0x8c, 0xa3, 0xa5, + 0x9b, 0x88, 0x96, 0x40, 0xf9, 0xa9, 0x29, 0x8f, + 0x82, 0xba, 0x9c, 0x89, 0x07, 0x95, 0xa9, 0x91, + 0xad, 0x94, 0x9a, 0x96, 0x8b, 0xb4, 0xb8, 0x09, + 0x80, 0x8c, 0xac, 0x9f, 0x98, 0x99, 0xa3, 0x9c, + 0x01, 0x07, 0xa2, 0x10, 0x8b, 0xaf, 0x8d, 0x83, + 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, 0x92, + 0x81, 0xbe, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89, + 0x86, 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, + 0x04, 0x10, 0x91, 0x80, 0x8b, 0x84, 0x9d, 0x89, + 0x00, 0x08, 0x80, 0xa5, 0x00, 0x98, 0x00, 0x80, + 0xab, 0xb4, 0x91, 0x83, 0x93, 0x82, 0x9d, 0xaf, + 0x93, 0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, + 0xa3, 0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, + 0xc6, 0x9a, 0xa4, 0x86, 0x40, 0xb8, 0xab, 0xf3, + 0xbf, 0x9e, 0x39, 0x01, 0x38, 0x08, 0x97, 0x8e, + 0x00, 0x80, 0xdd, 0x39, 0xa6, 0x8f, 0x00, 0x80, + 0x9b, 0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, + 0xad, 0x92, 0x80, 0x91, 0xc8, 0x40, 0xc6, 0xa0, + 0x9e, 0x88, 0x80, 0xa4, 0x90, 0x80, 0xb0, 0x9d, + 0xef, 0x30, 0x08, 0xa5, 0x94, 0x80, 0x98, 0x28, + 0x08, 0x9f, 0x8d, 0x80, 0x41, 0x46, 0x92, 0x8e, + 0x00, 0x8c, 0x80, 0xa1, 0xfb, 0x80, 0xce, 0x43, + 0x99, 0xe5, 0xee, 0x90, 0x40, 0xc3, 0x4a, 0x4b, + 0xe0, 0x8e, 0x44, 0x2f, 0x90, 0x85, 0x98, 0x4f, + 0x9a, 0x84, 0x42, 0x46, 0x5a, 0xb8, 0x9d, 0x46, + 0xe1, 0x42, 0x38, 0x86, 0x9e, 0x90, 0xce, 0x90, + 0x9d, 0x91, 0xaf, 0x8f, 0x83, 0x9e, 0x94, 0x84, + 0x92, 0x41, 0xaf, 0xac, 0x40, 0xd2, 0xbf, 0xff, + 0xca, 0x20, 0xc1, 0x8c, 0xbf, 0x08, 0x80, 0x9b, + 0x57, 0xf7, 0x87, 0x44, 0xd5, 0xa8, 0x89, 0x60, + 0x22, 0xe6, 0x18, 0x30, 0x08, 0x41, 0x22, 0x8e, + 0x80, 0x9c, 0x11, 0x80, 0x8d, 0x1f, 0x41, 0x8b, + 0x49, 0x03, 0xea, 0x84, 0x8c, 0x82, 0x88, 0x86, + 0x89, 0x57, 0x65, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x07, 0x47, 0x33, 0x9e, 0x2d, 0x41, 0x04, 0xbd, + 0x40, 0x91, 0xac, 0x89, 0x86, 0x8f, 0x80, 0x41, + 0x40, 0x9d, 0x91, 0xab, 0x41, 0xe3, 0x9b, 0x40, + 0xe3, 0x9d, 0x08, 0x41, 0xee, 0x30, 0x18, 0x08, + 0x8e, 0x80, 0x40, 0xc4, 0xba, 0xc3, 0x30, 0x44, + 0xb3, 0x18, 0x9a, 0x01, 0x00, 0x08, 0x80, 0x89, + 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, + 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, 0x80, 0x89, + 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, 0x51, 0x43, + 0x60, 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, + 0xdd, 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, + 0x42, 0x6d, 0x49, 0xa1, 0x42, 0x1d, 0x45, 0xe1, + 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_ID_Start_index[108] = { + 0xf6, 0x03, 0x20, 0xa6, 0x07, 0x00, 0xa9, 0x09, + 0x20, 0xb1, 0x0a, 0x00, 0xba, 0x0b, 0x20, 0x3b, + 0x0d, 0x20, 0xc7, 0x0e, 0x20, 0x49, 0x12, 0x00, + 0x9b, 0x16, 0x00, 0xac, 0x19, 0x00, 0xc0, 0x1d, + 0x80, 0x80, 0x20, 0x20, 0x70, 0x2d, 0x00, 0x00, + 0x32, 0x00, 0xdd, 0xa7, 0x00, 0x4c, 0xaa, 0x20, + 0xc7, 0xd7, 0x20, 0xfc, 0xfd, 0x20, 0x9d, 0x02, + 0x21, 0x96, 0x05, 0x01, 0x9f, 0x08, 0x01, 0x49, + 0x0c, 0x21, 0x76, 0x10, 0x21, 0xa9, 0x12, 0x01, + 0xb0, 0x14, 0x01, 0x42, 0x19, 0x41, 0x90, 0x1c, + 0x01, 0xf1, 0x2f, 0x21, 0x90, 0x6b, 0x21, 0x33, + 0xb1, 0x21, 0x06, 0xd5, 0x01, 0xc3, 0xd7, 0x01, + 0xff, 0xe7, 0x21, 0x63, 0xee, 0x01, 0x5e, 0xee, + 0x42, 0xb0, 0x23, 0x03, +}; + +static const uint8_t unicode_prop_ID_Continue1_table[695] = { + 0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47, + 0xef, 0x96, 0x80, 0x40, 0xfa, 0x84, 0x41, 0x08, + 0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, + 0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89, + 0x96, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0x8e, 0x89, + 0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02, + 0x04, 0xaa, 0x82, 0xba, 0x88, 0xa9, 0x97, 0x80, + 0xa0, 0xb5, 0x10, 0x91, 0x06, 0x89, 0x09, 0x89, + 0x90, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, + 0x80, 0x89, 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, + 0x00, 0x23, 0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, + 0x8a, 0x82, 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, + 0x09, 0x89, 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, + 0x09, 0x16, 0x82, 0x89, 0x09, 0x89, 0x91, 0x80, + 0xba, 0x22, 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, + 0x8f, 0x84, 0xb6, 0x00, 0x30, 0x10, 0x1e, 0x81, + 0x8a, 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, 0x30, + 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x10, 0x8b, + 0x83, 0xb6, 0x08, 0x30, 0x10, 0x83, 0x88, 0x80, + 0x89, 0x09, 0x89, 0x90, 0x82, 0xc5, 0x03, 0x28, + 0x00, 0x3d, 0x89, 0x09, 0xbc, 0x01, 0x86, 0x8b, + 0x38, 0x89, 0xd6, 0x01, 0x88, 0x8a, 0x30, 0x89, + 0xbd, 0x0d, 0x89, 0x8a, 0x00, 0x00, 0x03, 0x81, + 0xb0, 0x93, 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, + 0x80, 0xe3, 0x93, 0x80, 0x89, 0x8b, 0x1b, 0x10, + 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x80, 0x8e, 0x42, + 0xbe, 0x82, 0x88, 0x88, 0x43, 0x9f, 0x83, 0x9b, + 0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x9f, 0x88, + 0x01, 0x89, 0xa0, 0x10, 0x8a, 0x40, 0x8e, 0x80, + 0xf5, 0x8b, 0x83, 0x8b, 0x89, 0x89, 0xff, 0x8a, + 0xbb, 0x84, 0xb8, 0x89, 0x80, 0x9c, 0x81, 0x8a, + 0x85, 0x89, 0x95, 0x8d, 0x80, 0x8f, 0xb0, 0x84, + 0xae, 0x90, 0x8a, 0x89, 0x90, 0x88, 0x8b, 0x82, + 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d, 0xaf, 0x93, + 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10, 0x94, 0x18, + 0x28, 0x0a, 0x40, 0xc5, 0xbf, 0x42, 0x0b, 0x81, + 0xb0, 0x81, 0x92, 0x80, 0xfa, 0x8c, 0x18, 0x82, + 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, 0x80, 0xdf, + 0x9f, 0x42, 0x29, 0x85, 0xe8, 0x81, 0xdf, 0x80, + 0x60, 0x75, 0x23, 0x89, 0xc4, 0x03, 0x89, 0x9f, + 0x81, 0xcf, 0x81, 0x41, 0x0f, 0x02, 0x03, 0x80, + 0x96, 0x23, 0x80, 0xd2, 0x81, 0xb1, 0x91, 0x89, + 0x89, 0x85, 0x91, 0x8c, 0x8a, 0x9b, 0x87, 0x98, + 0x8c, 0xab, 0x83, 0xae, 0x8d, 0x8e, 0x89, 0x8a, + 0x80, 0x89, 0x89, 0xae, 0x8d, 0x8b, 0x07, 0x09, + 0x89, 0xa0, 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, + 0x80, 0xa8, 0x24, 0x81, 0x40, 0xeb, 0x38, 0x09, + 0x89, 0x60, 0x4f, 0x23, 0x80, 0x42, 0xe0, 0x8f, + 0x8f, 0x8f, 0x11, 0x97, 0x82, 0x40, 0xbf, 0x89, + 0xa4, 0x80, 0xa4, 0x80, 0x42, 0x96, 0x80, 0x40, + 0xe1, 0x80, 0x40, 0x94, 0x84, 0x41, 0x24, 0x89, + 0x45, 0x56, 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, + 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x1f, 0x89, 0x85, + 0x89, 0x9e, 0x84, 0x41, 0x3c, 0x81, 0xce, 0x83, + 0xc5, 0x8a, 0xb0, 0x83, 0xf9, 0x82, 0xb4, 0x8e, + 0x9e, 0x8a, 0x09, 0x89, 0x83, 0xac, 0x8a, 0x30, + 0xac, 0x89, 0x2a, 0xa3, 0x8d, 0x80, 0x89, 0x21, + 0xab, 0x80, 0x8b, 0x82, 0xaf, 0x8d, 0x3b, 0x80, + 0x8b, 0xd1, 0x8b, 0x28, 0x08, 0x40, 0x9c, 0x8b, + 0x84, 0x89, 0x2b, 0xb6, 0x08, 0x31, 0x09, 0x82, + 0x88, 0x80, 0x89, 0x09, 0x32, 0x84, 0xc2, 0x88, + 0x00, 0x08, 0x03, 0x04, 0x00, 0x8d, 0x81, 0xd1, + 0x91, 0x88, 0x89, 0x18, 0xd0, 0x93, 0x8b, 0x89, + 0x40, 0xd4, 0x31, 0x88, 0x9a, 0x81, 0xd1, 0x90, + 0x8e, 0x89, 0xd0, 0x8c, 0x87, 0x89, 0x85, 0x93, + 0xb8, 0x8e, 0x83, 0x89, 0x40, 0xf1, 0x8e, 0x40, + 0xa4, 0x89, 0xc5, 0x28, 0x09, 0x18, 0x00, 0x81, + 0x8b, 0x89, 0xf6, 0x31, 0x32, 0x80, 0x9b, 0x89, + 0xa7, 0x30, 0x1f, 0x80, 0x88, 0x8a, 0xad, 0x8f, + 0x41, 0x55, 0x89, 0xb4, 0x38, 0x87, 0x8f, 0x89, + 0xb7, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, + 0x30, 0x07, 0x89, 0xaf, 0x20, 0x08, 0x27, 0x89, + 0x41, 0x48, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, + 0x84, 0x8c, 0x8a, 0x54, 0xe4, 0x05, 0x8e, 0x60, + 0x2c, 0xc7, 0x9b, 0x49, 0x25, 0x89, 0xd5, 0x89, + 0xa5, 0x84, 0xba, 0x86, 0x98, 0x89, 0x42, 0x15, + 0x89, 0x41, 0xd4, 0x00, 0xb6, 0x33, 0xd0, 0x80, + 0x8a, 0x81, 0x60, 0x4c, 0xaa, 0x81, 0x50, 0x50, + 0x89, 0x42, 0x05, 0xad, 0x81, 0x96, 0x42, 0x1d, + 0x22, 0x2f, 0x39, 0x86, 0x9d, 0x83, 0x40, 0x93, + 0x82, 0x45, 0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, + 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, + 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, 0x80, + 0x40, 0x9f, 0x86, 0x88, 0x89, 0x41, 0x63, 0x80, + 0xbc, 0x8d, 0x41, 0xf1, 0x8d, 0x40, 0xf3, 0x08, + 0x89, 0x42, 0xd4, 0x86, 0xec, 0x34, 0x89, 0x52, + 0x95, 0x89, 0x6c, 0x05, 0x05, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_ID_Continue1_index[66] = { + 0xfa, 0x06, 0x00, 0x70, 0x09, 0x00, 0xf0, 0x0a, + 0x40, 0x57, 0x0c, 0x00, 0xf0, 0x0d, 0x60, 0xc7, + 0x0f, 0x20, 0xea, 0x17, 0x40, 0x05, 0x1b, 0x00, + 0x0e, 0x20, 0x00, 0xa0, 0xa6, 0x20, 0xe6, 0xa9, + 0x20, 0x10, 0xfe, 0x00, 0x40, 0x0a, 0x01, 0xc3, + 0x10, 0x01, 0x4e, 0x13, 0x01, 0x41, 0x16, 0x01, + 0x0b, 0x1a, 0x01, 0xaa, 0x1d, 0x01, 0x7a, 0x6d, + 0x21, 0x45, 0xd2, 0x21, 0xaf, 0xe2, 0x01, 0xf0, + 0x01, 0x0e, +}; + +static const uint8_t unicode_prop_White_Space_table[22] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x99, 0x80, + 0x55, 0xde, 0x80, 0x49, 0x7e, 0x8a, 0x9c, 0x0c, + 0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80, +}; + +static const uint8_t unicode_prop_White_Space_index[3] = { + 0x01, 0x30, 0x00, +}; + +static const uint8_t unicode_cc_table[916] = { + 0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00, + 0xe8, 0x00, 0xd8, 0x04, 0xdc, 0x01, 0xca, 0x03, + 0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03, + 0xdc, 0xc7, 0x00, 0xf0, 0xc0, 0x02, 0xdc, 0xc2, + 0x01, 0xdc, 0x80, 0xc2, 0x03, 0xdc, 0xc0, 0x00, + 0xe8, 0x01, 0xdc, 0xc0, 0x41, 0xe9, 0x00, 0xea, + 0x41, 0xe9, 0x00, 0xea, 0x00, 0xe9, 0xcc, 0xb0, + 0xe2, 0xc4, 0xb0, 0xd8, 0x00, 0xdc, 0xc3, 0x00, + 0xdc, 0xc2, 0x00, 0xde, 0x00, 0xdc, 0xc5, 0x05, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xde, 0x00, + 0xe4, 0xc0, 0x49, 0x0a, 0x43, 0x13, 0x80, 0x00, + 0x17, 0x80, 0x41, 0x18, 0x80, 0xc0, 0x00, 0xdc, + 0x80, 0x00, 0x12, 0xb0, 0x17, 0xc7, 0x42, 0x1e, + 0xaf, 0x47, 0x1b, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0x8f, 0x00, 0x23, 0xb0, + 0x34, 0xc6, 0x81, 0xc3, 0x00, 0xdc, 0xc0, 0x81, + 0xc1, 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xa2, + 0x00, 0x24, 0x9d, 0xc0, 0x00, 0xdc, 0xc1, 0x00, + 0xdc, 0xc1, 0x02, 0xdc, 0xc0, 0x01, 0xdc, 0xc0, + 0x00, 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x00, 0xdc, + 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc, + 0x97, 0xc3, 0x80, 0xc8, 0x80, 0xc2, 0x80, 0xc4, + 0xaa, 0x02, 0xdc, 0xb0, 0x0a, 0xc1, 0x02, 0xdc, + 0xc3, 0xa9, 0xc4, 0x04, 0xdc, 0xcd, 0x80, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc2, + 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00, 0xdc, 0xc1, + 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, 0x07, 0x8f, + 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xaf, 0xc0, + 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, + 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3d, + 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x4e, 0x00, + 0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, + 0x86, 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, + 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, + 0x8f, 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, + 0x3c, 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, + 0xb0, 0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, + 0x7a, 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, + 0x80, 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, + 0x41, 0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, + 0x81, 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, + 0xc1, 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, + 0x07, 0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, + 0xb2, 0x9e, 0xc2, 0xb3, 0x83, 0x01, 0x09, 0x9d, + 0x00, 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, + 0xb0, 0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, + 0xc0, 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, + 0xb0, 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, + 0xdc, 0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, + 0x80, 0x01, 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc3, 0xb0, 0x34, 0x00, 0x07, 0x8e, 0x00, + 0x09, 0xa5, 0xc0, 0x00, 0xdc, 0xc6, 0xb0, 0x05, + 0x01, 0x09, 0xb0, 0x09, 0x00, 0x07, 0x8a, 0x01, + 0x09, 0xb0, 0x12, 0x00, 0x07, 0xb0, 0x67, 0xc2, + 0x41, 0x00, 0x04, 0xdc, 0xc1, 0x03, 0xdc, 0xc0, + 0x41, 0x00, 0x05, 0x01, 0x83, 0x00, 0xdc, 0x85, + 0xc0, 0x82, 0xc1, 0xb0, 0x95, 0xc1, 0x00, 0xdc, + 0xc6, 0x00, 0xdc, 0xc1, 0x00, 0xea, 0x00, 0xd6, + 0x00, 0xdc, 0x00, 0xca, 0xe4, 0x00, 0xe8, 0x01, + 0xe4, 0x00, 0xdc, 0x00, 0xda, 0xc0, 0x00, 0xe9, + 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xb2, 0x9f, 0xc1, + 0x01, 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, 0xc0, + 0x82, 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, 0x01, + 0x01, 0x03, 0xdc, 0xc0, 0xb8, 0x03, 0xcd, 0xc2, + 0xb0, 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, 0xb1, + 0xf9, 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, 0x00, + 0xde, 0x01, 0xe0, 0xb0, 0x38, 0x01, 0x08, 0xb8, + 0x6d, 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, 0xb0, + 0x1f, 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xa4, 0x00, + 0x09, 0xb0, 0x66, 0x00, 0x09, 0x9a, 0xd1, 0xb0, + 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, 0x2e, + 0x00, 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe, 0xc0, + 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, 0xc1, + 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, 0xc5, + 0x00, 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a, 0xb2, + 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, 0x00, + 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, 0xc4, + 0xb6, 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7, 0xc0, + 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, 0xb0, + 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, 0xb0, + 0x10, 0xc4, 0xb1, 0x0c, 0xc1, 0xb0, 0x1f, 0x02, + 0xdc, 0xb0, 0x15, 0x01, 0xdc, 0xc2, 0x00, 0xdc, + 0xc0, 0x03, 0xdc, 0xb0, 0x00, 0xc0, 0x00, 0xdc, + 0xc0, 0x00, 0xdc, 0xb0, 0x8f, 0x00, 0x09, 0xa8, + 0x00, 0x09, 0x8d, 0x00, 0x09, 0xb0, 0x08, 0x00, + 0x09, 0x00, 0x07, 0xb0, 0x14, 0xc2, 0xaf, 0x01, + 0x09, 0xb0, 0x0d, 0x00, 0x07, 0xb0, 0x1b, 0x00, + 0x09, 0x88, 0x00, 0x07, 0xb0, 0x39, 0x00, 0x09, + 0x00, 0x07, 0xb0, 0x81, 0x00, 0x07, 0x00, 0x09, + 0xb0, 0x1f, 0x01, 0x07, 0x8f, 0x00, 0x09, 0x97, + 0xc6, 0x82, 0xc4, 0xb0, 0x28, 0x02, 0x09, 0xb0, + 0x40, 0x00, 0x09, 0x82, 0x00, 0x07, 0x96, 0xc0, + 0xb0, 0x32, 0x00, 0x09, 0x00, 0x07, 0xb0, 0xca, + 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, 0x00, 0x09, + 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x42, + 0x00, 0x09, 0xb0, 0xdc, 0x00, 0x09, 0x00, 0x07, + 0xb0, 0xd1, 0x01, 0x09, 0x83, 0x00, 0x07, 0xb0, + 0x6b, 0x00, 0x09, 0xb0, 0x22, 0x00, 0x09, 0x91, + 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x74, + 0x00, 0x09, 0xb0, 0xd1, 0x00, 0x07, 0x80, 0x01, + 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x78, 0x01, + 0x09, 0xb8, 0x39, 0xbb, 0x00, 0x09, 0xb8, 0x01, + 0x8f, 0x04, 0x01, 0xb0, 0x0a, 0xc6, 0xb4, 0x88, + 0x01, 0x06, 0xb8, 0x44, 0x7b, 0x00, 0x01, 0xb8, + 0x0c, 0x95, 0x01, 0xd8, 0x02, 0x01, 0x82, 0x00, + 0xe2, 0x04, 0xd8, 0x87, 0x07, 0xdc, 0x81, 0xc4, + 0x01, 0xdc, 0x9d, 0xc3, 0xb0, 0x63, 0xc2, 0xb8, + 0x05, 0x8a, 0xc6, 0x80, 0xd0, 0x81, 0xc6, 0x80, + 0xc1, 0x80, 0xc4, 0xb0, 0x33, 0xc0, 0xb0, 0x6f, + 0xc6, 0xb1, 0x46, 0xc0, 0xb0, 0x0c, 0xc3, 0xb1, + 0xcb, 0x01, 0xe8, 0x00, 0xdc, 0xc0, 0xb0, 0xcd, + 0xc0, 0x00, 0xdc, 0xb2, 0xaf, 0x06, 0xdc, 0xb0, + 0x3c, 0xc5, 0x00, 0x07, +}; + +static const uint8_t unicode_cc_index[87] = { + 0x4d, 0x03, 0x00, 0x97, 0x05, 0x20, 0xc6, 0x05, + 0x00, 0xe7, 0x06, 0x00, 0x45, 0x07, 0x00, 0x9c, + 0x08, 0x00, 0x4d, 0x09, 0x00, 0x3c, 0x0b, 0x00, + 0x3d, 0x0d, 0x00, 0x36, 0x0f, 0x00, 0x38, 0x10, + 0x20, 0x3a, 0x19, 0x00, 0xcb, 0x1a, 0x20, 0xd3, + 0x1c, 0x00, 0xcf, 0x1d, 0x00, 0xe2, 0x20, 0x00, + 0x2e, 0x30, 0x20, 0x2b, 0xa9, 0x20, 0xed, 0xab, + 0x00, 0x39, 0x0a, 0x01, 0x4c, 0x0f, 0x01, 0x35, + 0x11, 0x21, 0x66, 0x13, 0x01, 0x40, 0x16, 0x01, + 0x47, 0x1a, 0x01, 0xf0, 0x6a, 0x21, 0x8a, 0xd1, + 0x01, 0xec, 0xe4, 0x21, 0x4b, 0xe9, 0x01, +}; + +static const uint32_t unicode_decomp_table1[709] = { + 0x00280081, 0x002a0097, 0x002a8081, 0x002bc097, + 0x002c8115, 0x002d0097, 0x002d4081, 0x002e0097, + 0x002e4115, 0x002f0199, 0x00302016, 0x00400842, + 0x00448a42, 0x004a0442, 0x004c0096, 0x004c8117, + 0x004d0242, 0x004e4342, 0x004fc12f, 0x0050c342, + 0x005240bf, 0x00530342, 0x00550942, 0x005a0842, + 0x005e0096, 0x005e4342, 0x005fc081, 0x00680142, + 0x006bc142, 0x00710185, 0x0071c317, 0x00734844, + 0x00778344, 0x00798342, 0x007b02be, 0x007c4197, + 0x007d0142, 0x007e0444, 0x00800e42, 0x00878142, + 0x00898744, 0x00ac0483, 0x00b60317, 0x00b80283, + 0x00d00214, 0x00d10096, 0x00dd0080, 0x00de8097, + 0x00df8080, 0x00e10097, 0x00e1413e, 0x00e1c080, + 0x00e204be, 0x00ea83ae, 0x00f282ae, 0x00f401ad, + 0x00f4c12e, 0x00f54103, 0x00fc0303, 0x00fe4081, + 0x0100023e, 0x0101c0be, 0x010301be, 0x010640be, + 0x010e40be, 0x0114023e, 0x0115c0be, 0x011701be, + 0x011d8144, 0x01304144, 0x01340244, 0x01358144, + 0x01368344, 0x01388344, 0x013a8644, 0x013e0144, + 0x0161c085, 0x018882ae, 0x019d422f, 0x01b00184, + 0x01b4c084, 0x024a4084, 0x024c4084, 0x024d0084, + 0x0256042e, 0x0272c12e, 0x02770120, 0x0277c084, + 0x028cc084, 0x028d8084, 0x029641ae, 0x02978084, + 0x02d20084, 0x02d2c12e, 0x02d70120, 0x02e50084, + 0x02f281ae, 0x03120084, 0x03300084, 0x0331c122, + 0x0332812e, 0x035281ae, 0x03768084, 0x037701ae, + 0x038cc085, 0x03acc085, 0x03b7012f, 0x03c30081, + 0x03d0c084, 0x03d34084, 0x03d48084, 0x03d5c084, + 0x03d70084, 0x03da4084, 0x03dcc084, 0x03dd412e, + 0x03ddc085, 0x03de0084, 0x03de4085, 0x03e04084, + 0x03e4c084, 0x03e74084, 0x03e88084, 0x03e9c084, + 0x03eb0084, 0x03ee4084, 0x04098084, 0x043f0081, + 0x06c18484, 0x06c48084, 0x06cec184, 0x06d00120, + 0x06d0c084, 0x074b0383, 0x074cc41f, 0x074f1783, + 0x075e0081, 0x0766d283, 0x07801d44, 0x078e8942, + 0x07931844, 0x079f0d42, 0x07a58216, 0x07a68085, + 0x07a6c0be, 0x07a80d44, 0x07aea044, 0x07c00122, + 0x07c08344, 0x07c20122, 0x07c28344, 0x07c40122, + 0x07c48244, 0x07c60122, 0x07c68244, 0x07c8113e, + 0x07d08244, 0x07d20122, 0x07d28244, 0x07d40122, + 0x07d48344, 0x07d64c3e, 0x07dc4080, 0x07dc80be, + 0x07dcc080, 0x07dd00be, 0x07dd4080, 0x07dd80be, + 0x07ddc080, 0x07de00be, 0x07de4080, 0x07de80be, + 0x07dec080, 0x07df00be, 0x07df4080, 0x07e00820, + 0x07e40820, 0x07e80820, 0x07ec05be, 0x07eec080, + 0x07ef00be, 0x07ef4097, 0x07ef8080, 0x07efc117, + 0x07f0443e, 0x07f24080, 0x07f280be, 0x07f2c080, + 0x07f303be, 0x07f4c080, 0x07f582ae, 0x07f6c080, + 0x07f7433e, 0x07f8c080, 0x07f903ae, 0x07fac080, + 0x07fb013e, 0x07fb8102, 0x07fc83be, 0x07fe4080, + 0x07fe80be, 0x07fec080, 0x07ff00be, 0x07ff4080, + 0x07ff8097, 0x0800011e, 0x08008495, 0x08044081, + 0x0805c097, 0x08090081, 0x08094097, 0x08098099, + 0x080bc081, 0x080cc085, 0x080d00b1, 0x080d8085, + 0x080dc0b1, 0x080f0197, 0x0811c197, 0x0815c0b3, + 0x0817c081, 0x081c0595, 0x081ec081, 0x081f0215, + 0x0820051f, 0x08228583, 0x08254415, 0x082a0097, + 0x08400119, 0x08408081, 0x0840c0bf, 0x08414119, + 0x0841c081, 0x084240bf, 0x0842852d, 0x08454081, + 0x08458097, 0x08464295, 0x08480097, 0x08484099, + 0x08488097, 0x08490081, 0x08498080, 0x084a0081, + 0x084a8102, 0x084b0495, 0x084d421f, 0x084e4081, + 0x084ec099, 0x084f0283, 0x08514295, 0x08540119, + 0x0854809b, 0x0854c619, 0x0857c097, 0x08580081, + 0x08584097, 0x08588099, 0x0858c097, 0x08590081, + 0x08594097, 0x08598099, 0x0859c09b, 0x085a0097, + 0x085a4081, 0x085a8097, 0x085ac099, 0x085b0295, + 0x085c4097, 0x085c8099, 0x085cc097, 0x085d0081, + 0x085d4097, 0x085d8099, 0x085dc09b, 0x085e0097, + 0x085e4081, 0x085e8097, 0x085ec099, 0x085f0215, + 0x08624099, 0x0866813e, 0x086b80be, 0x087341be, + 0x088100be, 0x088240be, 0x088300be, 0x088901be, + 0x088b0085, 0x088b40b1, 0x088bc085, 0x088c00b1, + 0x089040be, 0x089100be, 0x0891c1be, 0x089801be, + 0x089b42be, 0x089d0144, 0x089e0144, 0x08a00144, + 0x08a10144, 0x08a20144, 0x08ab023e, 0x08b80244, + 0x08ba8220, 0x08ca411e, 0x0918049f, 0x091a4523, + 0x091cc097, 0x091d04a5, 0x091f452b, 0x0921c09b, + 0x092204a1, 0x09244525, 0x0926c099, 0x09270d25, + 0x092d8d1f, 0x09340d1f, 0x093a8081, 0x0a8300b3, + 0x0a9d0099, 0x0a9d4097, 0x0a9d8099, 0x0ab700be, + 0x0b1f0115, 0x0b5bc081, 0x0ba7c081, 0x0bbcc081, + 0x0bc004ad, 0x0bc244ad, 0x0bc484ad, 0x0bc6f383, + 0x0be0852d, 0x0be31d03, 0x0bf1882d, 0x0c000081, + 0x0c0d8283, 0x0c130b84, 0x0c194284, 0x0c1c0122, + 0x0c1cc122, 0x0c1d8122, 0x0c1e4122, 0x0c1f0122, + 0x0c250084, 0x0c26c123, 0x0c278084, 0x0c27c085, + 0x0c2b0b84, 0x0c314284, 0x0c340122, 0x0c34c122, + 0x0c358122, 0x0c364122, 0x0c370122, 0x0c3d0084, + 0x0c3dc220, 0x0c3f8084, 0x0c3fc085, 0x0c4c4a2d, + 0x0c51451f, 0x0c53ca9f, 0x0c5915ad, 0x0c648703, + 0x0c800741, 0x0c838089, 0x0c83c129, 0x0c8441a9, + 0x0c850089, 0x0c854129, 0x0c85c2a9, 0x0c870089, + 0x0c87408f, 0x0c87808d, 0x0c881241, 0x0c910203, + 0x0c940099, 0x0c9444a3, 0x0c968323, 0x0c98072d, + 0x0c9b84af, 0x0c9dc2a1, 0x0c9f00b5, 0x0c9f40b3, + 0x0c9f8085, 0x0ca01883, 0x0cac4223, 0x0cad4523, + 0x0cafc097, 0x0cb004a1, 0x0cb241a5, 0x0cb30097, + 0x0cb34099, 0x0cb38097, 0x0cb3c099, 0x0cb417ad, + 0x0cbfc085, 0x0cc001b3, 0x0cc0c0b1, 0x0cc100b3, + 0x0cc14131, 0x0cc1c0b5, 0x0cc200b3, 0x0cc241b1, + 0x0cc30133, 0x0cc38131, 0x0cc40085, 0x0cc440b1, + 0x0cc48133, 0x0cc50085, 0x0cc540b5, 0x0cc580b7, + 0x0cc5c0b5, 0x0cc600b1, 0x0cc64135, 0x0cc6c0b3, + 0x0cc701b1, 0x0cc7c0b3, 0x0cc800b5, 0x0cc840b3, + 0x0cc881b1, 0x0cc9422f, 0x0cca4131, 0x0ccac0b5, + 0x0ccb00b1, 0x0ccb40b3, 0x0ccb80b5, 0x0ccbc0b1, + 0x0ccc012f, 0x0ccc80b5, 0x0cccc0b3, 0x0ccd00b5, + 0x0ccd40b1, 0x0ccd80b5, 0x0ccdc085, 0x0cce02b1, + 0x0ccf40b3, 0x0ccf80b1, 0x0ccfc085, 0x0cd001b1, + 0x0cd0c0b3, 0x0cd101b1, 0x0cd1c0b5, 0x0cd200b3, + 0x0cd24085, 0x0cd280b5, 0x0cd2c085, 0x0cd30133, + 0x0cd381b1, 0x0cd440b3, 0x0cd48085, 0x0cd4c0b1, + 0x0cd500b3, 0x0cd54085, 0x0cd580b5, 0x0cd5c0b1, + 0x0cd60521, 0x0cd88525, 0x0cdb02a5, 0x0cdc4099, + 0x0cdc8117, 0x0cdd0099, 0x0cdd4197, 0x0cde0127, + 0x0cde8285, 0x0cdfc089, 0x0ce0043f, 0x0ce20099, + 0x0ce2409b, 0x0ce283bf, 0x0ce44219, 0x0ce54205, + 0x0ce6433f, 0x0ce7c131, 0x0ce84085, 0x0ce881b1, + 0x0ce94085, 0x0ce98107, 0x0cea0089, 0x0cea4097, + 0x0cea8219, 0x0ceb809d, 0x0cebc08d, 0x0cec083f, + 0x0cf00105, 0x0cf0809b, 0x0cf0c197, 0x0cf1809b, + 0x0cf1c099, 0x0cf20517, 0x0cf48099, 0x0cf4c117, + 0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099, + 0x0cf68217, 0x0cf78119, 0x0cf804a1, 0x0cfa4525, + 0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103, + 0x29dc0081, 0x29fc8195, 0x29fe0103, 0x2ad70203, + 0x2ada4081, 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, + 0x3e8aa102, 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, + 0x3ec00197, 0x3ec0c119, 0x3ec1413f, 0x3ec4c2af, + 0x3ec74184, 0x3ec804ad, 0x3eca4081, 0x3eca8304, + 0x3ecc03a0, 0x3ece02a0, 0x3ecf8084, 0x3ed00120, + 0x3ed0c120, 0x3ed184ae, 0x3ed3c085, 0x3ed4312d, + 0x3ef4cbad, 0x3efa892f, 0x3eff022d, 0x3f002f2f, + 0x3f1782a5, 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, + 0x3f3c81a5, 0x3f3d64af, 0x3f542031, 0x3f649b31, + 0x3f7c0131, 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, + 0x3f7ec0bb, 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, + 0x3f8cc315, 0x3f8e462d, 0x3f91cc03, 0x3f97c695, + 0x3f9c01af, 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, + 0x3fbd442f, 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, + 0x3fe80081, 0x3fe84f1f, 0x3ff0831f, 0x3ff2831f, + 0x3ff4831f, 0x3ff6819f, 0x3ff80783, 0x41724092, + 0x41790092, 0x41e04d83, 0x41e70f91, 0x44268192, + 0x442ac092, 0x444b8112, 0x44d2c112, 0x44e0c192, + 0x44e38092, 0x44e44092, 0x44f14212, 0x452ec212, + 0x456e8112, 0x464e0092, 0x58484412, 0x5b5a0192, + 0x73358d1f, 0x733c051f, 0x74578392, 0x746ec312, + 0x75000d1f, 0x75068d1f, 0x750d0d1f, 0x7513839f, + 0x7515891f, 0x751a0d1f, 0x75208d1f, 0x75271015, + 0x752f439f, 0x7531459f, 0x75340d1f, 0x753a8d1f, + 0x75410395, 0x7543441f, 0x7545839f, 0x75478d1f, + 0x754e0795, 0x7552839f, 0x75548d1f, 0x755b0d1f, + 0x75618d1f, 0x75680d1f, 0x756e8d1f, 0x75750d1f, + 0x757b8d1f, 0x75820d1f, 0x75888d1f, 0x758f0d1f, + 0x75958d1f, 0x759c0d1f, 0x75a28d1f, 0x75a90103, + 0x75aa089f, 0x75ae4081, 0x75ae839f, 0x75b04081, + 0x75b08c9f, 0x75b6c081, 0x75b7032d, 0x75b8889f, + 0x75bcc081, 0x75bd039f, 0x75bec081, 0x75bf0c9f, + 0x75c54081, 0x75c5832d, 0x75c7089f, 0x75cb4081, + 0x75cb839f, 0x75cd4081, 0x75cd8c9f, 0x75d3c081, + 0x75d4032d, 0x75d5889f, 0x75d9c081, 0x75da039f, + 0x75dbc081, 0x75dc0c9f, 0x75e24081, 0x75e2832d, + 0x75e4089f, 0x75e84081, 0x75e8839f, 0x75ea4081, + 0x75ea8c9f, 0x75f0c081, 0x75f1042d, 0x75f3851f, + 0x75f6051f, 0x75f8851f, 0x75fb051f, 0x75fd851f, + 0x780c049f, 0x780e419f, 0x780f059f, 0x7811c203, + 0x7812d0ad, 0x781b0103, 0x7b80022d, 0x7b814dad, + 0x7b884203, 0x7b89c081, 0x7b8a452d, 0x7b8d0403, + 0x7b908081, 0x7b91dc03, 0x7ba0052d, 0x7ba2c8ad, + 0x7ba84483, 0x7baac8ad, 0x7c400097, 0x7c404521, + 0x7c440d25, 0x7c4a8087, 0x7c4ac115, 0x7c4b4117, + 0x7c4c0d1f, 0x7c528217, 0x7c538099, 0x7c53c097, + 0x7c5a8197, 0x7c640097, 0x7c80012f, 0x7c808081, + 0x7c841603, 0x7c9004c1, 0x7c940103, 0x7efc051f, + 0xbe0001ac, 0xbe00d110, 0xbe0947ac, 0xbe0d3910, + 0xbe29872c, 0xbe2d022c, 0xbe2e3790, 0xbe49ff90, + 0xbe69bc10, +}; + +static const uint16_t unicode_decomp_table2[709] = { + 0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008, + 0x000a, 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3, + 0x00c7, 0x00cb, 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8, + 0x0108, 0x010a, 0x0073, 0x0110, 0x0112, 0x0114, 0x0120, 0x012c, + 0x0144, 0x014d, 0x0153, 0x0162, 0x0168, 0x016a, 0x0176, 0x0192, + 0x0194, 0x01a9, 0x01bb, 0x01c7, 0x01d1, 0x01d5, 0x02b9, 0x01d7, + 0x003b, 0x01d9, 0x01db, 0x00b7, 0x01e1, 0x01fc, 0x020c, 0x0218, + 0x021d, 0x0223, 0x0227, 0x03a3, 0x0233, 0x023f, 0x0242, 0x024b, + 0x024e, 0x0251, 0x025d, 0x0260, 0x0269, 0x026c, 0x026f, 0x0275, + 0x0278, 0x0281, 0x028a, 0x029c, 0x029f, 0x02a3, 0x02af, 0x02b9, + 0x02c5, 0x02c9, 0x02cd, 0x02d1, 0x02d5, 0x02e7, 0x02ed, 0x02f1, + 0x02f5, 0x02f9, 0x02fd, 0x0305, 0x0309, 0x030d, 0x0313, 0x0317, + 0x031b, 0x0323, 0x0327, 0x032b, 0x032f, 0x0335, 0x033d, 0x0341, + 0x0349, 0x034d, 0x0351, 0x0f0b, 0x0357, 0x035b, 0x035f, 0x0363, + 0x0367, 0x036b, 0x036f, 0x0373, 0x0379, 0x037d, 0x0381, 0x0385, + 0x0389, 0x038d, 0x0391, 0x0395, 0x0399, 0x039d, 0x03a1, 0x10dc, + 0x03a5, 0x03c9, 0x03cd, 0x03d9, 0x03dd, 0x03e1, 0x03ef, 0x03f1, + 0x043d, 0x044f, 0x0499, 0x04f0, 0x0502, 0x054a, 0x0564, 0x056c, + 0x0570, 0x0573, 0x059a, 0x05fa, 0x05fe, 0x0607, 0x060b, 0x0614, + 0x0618, 0x061e, 0x0622, 0x0628, 0x068e, 0x0694, 0x0698, 0x069e, + 0x06a2, 0x06ab, 0x03ac, 0x06f3, 0x03ad, 0x06f6, 0x03ae, 0x06f9, + 0x03af, 0x06fc, 0x03cc, 0x06ff, 0x03cd, 0x0702, 0x03ce, 0x0705, + 0x0709, 0x070d, 0x0711, 0x0386, 0x0732, 0x0735, 0x03b9, 0x0737, + 0x073b, 0x0388, 0x0753, 0x0389, 0x0756, 0x0390, 0x076b, 0x038a, + 0x0777, 0x03b0, 0x0789, 0x038e, 0x0799, 0x079f, 0x07a3, 0x038c, + 0x07b8, 0x038f, 0x07bb, 0x00b4, 0x07be, 0x07c0, 0x07c2, 0x2010, + 0x07cb, 0x002e, 0x07cd, 0x07cf, 0x0020, 0x07d2, 0x07d6, 0x07db, + 0x07df, 0x07e4, 0x07ea, 0x07f0, 0x0020, 0x07f6, 0x2212, 0x0801, + 0x0805, 0x0807, 0x081d, 0x0825, 0x0827, 0x0043, 0x082d, 0x0830, + 0x0190, 0x0836, 0x0839, 0x004e, 0x0845, 0x0847, 0x084c, 0x084e, + 0x0851, 0x005a, 0x03a9, 0x005a, 0x0853, 0x0857, 0x0860, 0x0069, + 0x0862, 0x0865, 0x086f, 0x0874, 0x087a, 0x087e, 0x08a2, 0x0049, + 0x08a4, 0x08a6, 0x08a9, 0x0056, 0x08ab, 0x08ad, 0x08b0, 0x08b4, + 0x0058, 0x08b6, 0x08b8, 0x08bb, 0x08c0, 0x08c2, 0x08c5, 0x0076, + 0x08c7, 0x08c9, 0x08cc, 0x08d0, 0x0078, 0x08d2, 0x08d4, 0x08d7, + 0x08db, 0x08de, 0x08e4, 0x08e7, 0x08f0, 0x08f3, 0x08f6, 0x08f9, + 0x0902, 0x0906, 0x090b, 0x090f, 0x0914, 0x0917, 0x091a, 0x0923, + 0x092c, 0x093b, 0x093e, 0x0941, 0x0944, 0x0947, 0x094a, 0x0956, + 0x095c, 0x0960, 0x0962, 0x0964, 0x0968, 0x096a, 0x0970, 0x0978, + 0x097c, 0x0980, 0x0986, 0x0989, 0x098f, 0x0991, 0x0030, 0x0993, + 0x0999, 0x099c, 0x099e, 0x09a1, 0x09a4, 0x2d61, 0x6bcd, 0x9f9f, + 0x09a6, 0x09b1, 0x09bc, 0x09c7, 0x0a95, 0x0aa1, 0x0b15, 0x0020, + 0x0b27, 0x0b31, 0x0b8d, 0x0ba1, 0x0ba5, 0x0ba9, 0x0bad, 0x0bb1, + 0x0bb5, 0x0bb9, 0x0bbd, 0x0bc1, 0x0bc5, 0x0c21, 0x0c35, 0x0c39, + 0x0c3d, 0x0c41, 0x0c45, 0x0c49, 0x0c4d, 0x0c51, 0x0c55, 0x0c59, + 0x0c6f, 0x0c71, 0x0c73, 0x0ca0, 0x0cbc, 0x0cdc, 0x0ce4, 0x0cec, + 0x0cf4, 0x0cfc, 0x0d04, 0x0d0c, 0x0d14, 0x0d22, 0x0d2e, 0x0d7a, + 0x0d82, 0x0d85, 0x0d89, 0x0d8d, 0x0d9d, 0x0db1, 0x0db5, 0x0dbc, + 0x0dc2, 0x0dc6, 0x0e28, 0x0e2c, 0x0e30, 0x0e32, 0x0e36, 0x0e3c, + 0x0e3e, 0x0e41, 0x0e43, 0x0e46, 0x0e77, 0x0e7b, 0x0e89, 0x0e8e, + 0x0e94, 0x0e9c, 0x0ea3, 0x0ea9, 0x0eb4, 0x0ebe, 0x0ec6, 0x0eca, + 0x0ecf, 0x0ed9, 0x0edd, 0x0ee4, 0x0eec, 0x0ef3, 0x0ef8, 0x0f04, + 0x0f0a, 0x0f15, 0x0f1b, 0x0f22, 0x0f28, 0x0f33, 0x0f3d, 0x0f45, + 0x0f4c, 0x0f51, 0x0f57, 0x0f5e, 0x0f63, 0x0f69, 0x0f70, 0x0f76, + 0x0f7d, 0x0f82, 0x0f89, 0x0f8d, 0x0f9e, 0x0fa4, 0x0fa9, 0x0fad, + 0x0fb8, 0x0fbe, 0x0fc9, 0x0fd0, 0x0fd6, 0x0fda, 0x0fe1, 0x0fe5, + 0x0fef, 0x0ffa, 0x1000, 0x1004, 0x1009, 0x100f, 0x1013, 0x101a, + 0x101f, 0x1023, 0x1029, 0x102f, 0x1032, 0x1036, 0x1039, 0x103f, + 0x1045, 0x1059, 0x1061, 0x1079, 0x107c, 0x1080, 0x1095, 0x10a1, + 0x10b1, 0x10c3, 0x10cb, 0x10cf, 0x10da, 0x10de, 0x10ea, 0x10f2, + 0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, 0x114d, 0x1153, + 0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, 0x1181, + 0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab, + 0xa76f, 0x11af, 0x11b2, 0x11b6, 0x028d, 0x11be, 0x1210, 0x130e, + 0x140c, 0x1490, 0x1495, 0x1553, 0x156c, 0x1572, 0x1578, 0x157e, + 0x158a, 0x1596, 0x002b, 0x15a1, 0x15b9, 0x15bd, 0x15c1, 0x15c5, + 0x15c9, 0x15cd, 0x15e1, 0x15e5, 0x1649, 0x1662, 0x1688, 0x168e, + 0x174c, 0x1752, 0x1757, 0x1777, 0x1877, 0x187d, 0x1911, 0x19d3, + 0x1a77, 0x1a7f, 0x1a9d, 0x1aa2, 0x1ab6, 0x1ac0, 0x1ac6, 0x1ada, + 0x1adf, 0x1ae5, 0x1af3, 0x1b23, 0x1b30, 0x1b38, 0x1b3c, 0x1b52, + 0x1bc9, 0x1bdb, 0x1bdd, 0x1bdf, 0x3164, 0x1c20, 0x1c22, 0x1c24, + 0x1c26, 0x1c28, 0x1c2a, 0x1c48, 0x1c4d, 0x1c52, 0x1c88, 0x1cce, + 0x1cdc, 0x1ce1, 0x1cea, 0x1cf3, 0x1d01, 0x1d06, 0x1d0b, 0x1d1d, + 0x1d2f, 0x1d38, 0x1d3d, 0x1d61, 0x1d6f, 0x1d71, 0x1d73, 0x1d93, + 0x1dae, 0x1db0, 0x1db2, 0x1db4, 0x1db6, 0x1db8, 0x1dba, 0x1dbc, + 0x1ddc, 0x1dde, 0x1de0, 0x1de2, 0x1de4, 0x1deb, 0x1ded, 0x1def, + 0x1df1, 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, 0x1e0c, + 0x1e0e, 0x1e10, 0x1e12, 0x1e14, 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, + 0x1e20, 0x03f4, 0x1e22, 0x2207, 0x1e24, 0x2202, 0x1e26, 0x1e2e, + 0x03f4, 0x1e30, 0x2207, 0x1e32, 0x2202, 0x1e34, 0x1e3c, 0x03f4, + 0x1e3e, 0x2207, 0x1e40, 0x2202, 0x1e42, 0x1e4a, 0x03f4, 0x1e4c, + 0x2207, 0x1e4e, 0x2202, 0x1e50, 0x1e58, 0x03f4, 0x1e5a, 0x2207, + 0x1e5c, 0x2202, 0x1e5e, 0x1e68, 0x1e6a, 0x1e6c, 0x1e6e, 0x1e70, + 0x1e72, 0x1e74, 0x1e76, 0x1e78, 0x1e80, 0x1ea3, 0x1ea7, 0x1ead, + 0x1eca, 0x062d, 0x1ed2, 0x1ede, 0x062c, 0x1eee, 0x1f5e, 0x1f6a, + 0x1f7d, 0x1f8f, 0x1fa2, 0x1fa4, 0x1fa8, 0x1fae, 0x1fb4, 0x1fb6, + 0x1fba, 0x1fbc, 0x1fc4, 0x1fc7, 0x1fc9, 0x1fcf, 0x1fd1, 0x30b5, + 0x1fd7, 0x202f, 0x2045, 0x2049, 0x204b, 0x2050, 0x209d, 0x20ae, + 0x21af, 0x21bf, 0x21c5, 0x22bf, 0x23dd, +}; + +static const uint8_t unicode_decomp_data[9451] = { + 0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81, + 0x20, 0xa7, 0x31, 0x6f, 0x31, 0xd0, 0x34, 0x31, + 0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41, + 0x81, 0x41, 0x82, 0x41, 0x83, 0x41, 0x88, 0x41, + 0x8a, 0x00, 0x00, 0x43, 0xa7, 0x45, 0x80, 0x45, + 0x81, 0x45, 0x82, 0x45, 0x88, 0x49, 0x80, 0x49, + 0x81, 0x49, 0x82, 0x49, 0x88, 0x00, 0x00, 0x4e, + 0x83, 0x4f, 0x80, 0x4f, 0x81, 0x4f, 0x82, 0x4f, + 0x83, 0x4f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x55, + 0x80, 0x55, 0x81, 0x55, 0x82, 0x55, 0x88, 0x59, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x61, + 0x81, 0x61, 0x82, 0x61, 0x83, 0x61, 0x88, 0x61, + 0x8a, 0x00, 0x00, 0x63, 0xa7, 0x65, 0x80, 0x65, + 0x81, 0x65, 0x82, 0x65, 0x88, 0x69, 0x80, 0x69, + 0x81, 0x69, 0x82, 0x69, 0x88, 0x00, 0x00, 0x6e, + 0x83, 0x6f, 0x80, 0x6f, 0x81, 0x6f, 0x82, 0x6f, + 0x83, 0x6f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x75, + 0x80, 0x75, 0x81, 0x75, 0x82, 0x75, 0x88, 0x79, + 0x81, 0x00, 0x00, 0x79, 0x88, 0x41, 0x84, 0x41, + 0x86, 0x41, 0xa8, 0x43, 0x81, 0x43, 0x82, 0x43, + 0x87, 0x43, 0x8c, 0x44, 0x8c, 0x45, 0x84, 0x45, + 0x86, 0x45, 0x87, 0x45, 0xa8, 0x45, 0x8c, 0x47, + 0x82, 0x47, 0x86, 0x47, 0x87, 0x47, 0xa7, 0x48, + 0x82, 0x49, 0x83, 0x49, 0x84, 0x49, 0x86, 0x49, + 0xa8, 0x49, 0x87, 0x49, 0x4a, 0x69, 0x6a, 0x4a, + 0x82, 0x4b, 0xa7, 0x4c, 0x81, 0x4c, 0xa7, 0x4c, + 0x8c, 0x4c, 0x00, 0x00, 0x6b, 0x20, 0x6b, 0x4e, + 0x81, 0x4e, 0xa7, 0x4e, 0x8c, 0xbc, 0x02, 0x6e, + 0x4f, 0x84, 0x4f, 0x86, 0x4f, 0x8b, 0x52, 0x81, + 0x52, 0xa7, 0x52, 0x8c, 0x53, 0x81, 0x53, 0x82, + 0x53, 0xa7, 0x53, 0x8c, 0x54, 0xa7, 0x54, 0x8c, + 0x55, 0x83, 0x55, 0x84, 0x55, 0x86, 0x55, 0x8a, + 0x55, 0x8b, 0x55, 0xa8, 0x57, 0x82, 0x59, 0x82, + 0x59, 0x88, 0x5a, 0x81, 0x5a, 0x87, 0x5a, 0x8c, + 0x4f, 0x9b, 0x55, 0x9b, 0x44, 0x00, 0x7d, 0x01, + 0x44, 0x00, 0x7e, 0x01, 0x64, 0x00, 0x7e, 0x01, + 0x4c, 0x4a, 0x4c, 0x6a, 0x6c, 0x6a, 0x4e, 0x4a, + 0x4e, 0x6a, 0x6e, 0x6a, 0x41, 0x00, 0x8c, 0x49, + 0x00, 0x8c, 0x4f, 0x00, 0x8c, 0x55, 0x00, 0x8c, + 0xdc, 0x00, 0x84, 0xdc, 0x00, 0x81, 0xdc, 0x00, + 0x8c, 0xdc, 0x00, 0x80, 0xc4, 0x00, 0x84, 0x26, + 0x02, 0x84, 0xc6, 0x00, 0x84, 0x47, 0x8c, 0x4b, + 0x8c, 0x4f, 0xa8, 0xea, 0x01, 0x84, 0xeb, 0x01, + 0x84, 0xb7, 0x01, 0x8c, 0x92, 0x02, 0x8c, 0x6a, + 0x00, 0x8c, 0x44, 0x5a, 0x44, 0x7a, 0x64, 0x7a, + 0x47, 0x81, 0x4e, 0x00, 0x80, 0xc5, 0x00, 0x81, + 0xc6, 0x00, 0x81, 0xd8, 0x00, 0x81, 0x41, 0x8f, + 0x41, 0x91, 0x45, 0x8f, 0x45, 0x91, 0x49, 0x8f, + 0x49, 0x91, 0x4f, 0x8f, 0x4f, 0x91, 0x52, 0x8f, + 0x52, 0x91, 0x55, 0x8f, 0x55, 0x91, 0x53, 0xa6, + 0x54, 0xa6, 0x48, 0x8c, 0x41, 0x00, 0x87, 0x45, + 0x00, 0xa7, 0xd6, 0x00, 0x84, 0xd5, 0x00, 0x84, + 0x4f, 0x00, 0x87, 0x2e, 0x02, 0x84, 0x59, 0x00, + 0x84, 0x68, 0x00, 0x66, 0x02, 0x6a, 0x00, 0x72, + 0x00, 0x79, 0x02, 0x7b, 0x02, 0x81, 0x02, 0x77, + 0x00, 0x79, 0x00, 0x20, 0x86, 0x20, 0x87, 0x20, + 0x8a, 0x20, 0xa8, 0x20, 0x83, 0x20, 0x8b, 0x63, + 0x02, 0x6c, 0x00, 0x73, 0x00, 0x78, 0x00, 0x95, + 0x02, 0x80, 0x81, 0x00, 0x93, 0x88, 0x81, 0x20, + 0xc5, 0x20, 0x81, 0xa8, 0x00, 0x81, 0x91, 0x03, + 0x81, 0x95, 0x03, 0x81, 0x97, 0x03, 0x81, 0x99, + 0x03, 0x81, 0x00, 0x00, 0x00, 0x9f, 0x03, 0x81, + 0x00, 0x00, 0x00, 0xa5, 0x03, 0x81, 0xa9, 0x03, + 0x81, 0xca, 0x03, 0x81, 0x01, 0x03, 0x98, 0x07, + 0xa4, 0x07, 0xb0, 0x00, 0xb4, 0x00, 0xb6, 0x00, + 0xb8, 0x00, 0xca, 0x00, 0x01, 0x03, 0xb8, 0x07, + 0xc4, 0x07, 0xbe, 0x00, 0xc4, 0x00, 0xc8, 0x00, + 0xa5, 0x03, 0x0d, 0x13, 0x00, 0x01, 0x03, 0xd1, + 0x00, 0xd1, 0x07, 0xc6, 0x03, 0xc0, 0x03, 0xba, + 0x03, 0xc1, 0x03, 0xc2, 0x03, 0x00, 0x00, 0x98, + 0x03, 0xb5, 0x03, 0x15, 0x04, 0x80, 0x15, 0x04, + 0x88, 0x00, 0x00, 0x00, 0x13, 0x04, 0x81, 0x06, + 0x04, 0x88, 0x1a, 0x04, 0x81, 0x18, 0x04, 0x80, + 0x23, 0x04, 0x86, 0x18, 0x04, 0x86, 0x38, 0x04, + 0x86, 0x35, 0x04, 0x80, 0x35, 0x04, 0x88, 0x00, + 0x00, 0x00, 0x33, 0x04, 0x81, 0x56, 0x04, 0x88, + 0x3a, 0x04, 0x81, 0x38, 0x04, 0x80, 0x43, 0x04, + 0x86, 0x74, 0x04, 0x8f, 0x16, 0x04, 0x86, 0x10, + 0x04, 0x86, 0x10, 0x04, 0x88, 0x15, 0x04, 0x86, + 0xd8, 0x04, 0x88, 0x16, 0x04, 0x88, 0x17, 0x04, + 0x88, 0x18, 0x04, 0x84, 0x18, 0x04, 0x88, 0x1e, + 0x04, 0x88, 0xe8, 0x04, 0x88, 0x2d, 0x04, 0x88, + 0x23, 0x04, 0x84, 0x23, 0x04, 0x88, 0x23, 0x04, + 0x8b, 0x27, 0x04, 0x88, 0x2b, 0x04, 0x88, 0x65, + 0x05, 0x82, 0x05, 0x27, 0x06, 0x00, 0x2c, 0x00, + 0x2d, 0x21, 0x2d, 0x00, 0x2e, 0x23, 0x2d, 0x27, + 0x06, 0x00, 0x4d, 0x21, 0x4d, 0xa0, 0x4d, 0x23, + 0x4d, 0xd5, 0x06, 0x54, 0x06, 0x00, 0x00, 0x00, + 0x00, 0xc1, 0x06, 0x54, 0x06, 0xd2, 0x06, 0x54, + 0x06, 0x28, 0x09, 0x3c, 0x09, 0x30, 0x09, 0x3c, + 0x09, 0x33, 0x09, 0x3c, 0x09, 0x15, 0x09, 0x00, + 0x27, 0x01, 0x27, 0x02, 0x27, 0x07, 0x27, 0x0c, + 0x27, 0x0d, 0x27, 0x16, 0x27, 0x1a, 0x27, 0xbe, + 0x09, 0x09, 0x00, 0x09, 0x19, 0xa1, 0x09, 0xbc, + 0x09, 0xaf, 0x09, 0xbc, 0x09, 0x32, 0x0a, 0x3c, + 0x0a, 0x38, 0x0a, 0x3c, 0x0a, 0x16, 0x0a, 0x00, + 0x26, 0x01, 0x26, 0x06, 0x26, 0x2b, 0x0a, 0x3c, + 0x0a, 0x47, 0x0b, 0x56, 0x0b, 0x3e, 0x0b, 0x09, + 0x00, 0x09, 0x19, 0x21, 0x0b, 0x3c, 0x0b, 0x92, + 0x0b, 0xd7, 0x0b, 0xbe, 0x0b, 0x08, 0x00, 0x09, + 0x00, 0x08, 0x19, 0x46, 0x0c, 0x56, 0x0c, 0xbf, + 0x0c, 0xd5, 0x0c, 0xc6, 0x0c, 0xd5, 0x0c, 0xc2, + 0x0c, 0x04, 0x00, 0x08, 0x13, 0x3e, 0x0d, 0x08, + 0x00, 0x09, 0x00, 0x08, 0x19, 0xd9, 0x0d, 0xca, + 0x0d, 0xca, 0x0d, 0x0f, 0x05, 0x12, 0x00, 0x0f, + 0x15, 0x4d, 0x0e, 0x32, 0x0e, 0xcd, 0x0e, 0xb2, + 0x0e, 0x99, 0x0e, 0x12, 0x00, 0x12, 0x08, 0x42, + 0x0f, 0xb7, 0x0f, 0x4c, 0x0f, 0xb7, 0x0f, 0x51, + 0x0f, 0xb7, 0x0f, 0x56, 0x0f, 0xb7, 0x0f, 0x5b, + 0x0f, 0xb7, 0x0f, 0x40, 0x0f, 0xb5, 0x0f, 0x71, + 0x0f, 0x72, 0x0f, 0x71, 0x0f, 0x00, 0x03, 0x41, + 0x0f, 0xb2, 0x0f, 0x81, 0x0f, 0xb3, 0x0f, 0x80, + 0x0f, 0xb3, 0x0f, 0x81, 0x0f, 0x71, 0x0f, 0x80, + 0x0f, 0x92, 0x0f, 0xb7, 0x0f, 0x9c, 0x0f, 0xb7, + 0x0f, 0xa1, 0x0f, 0xb7, 0x0f, 0xa6, 0x0f, 0xb7, + 0x0f, 0xab, 0x0f, 0xb7, 0x0f, 0x90, 0x0f, 0xb5, + 0x0f, 0x25, 0x10, 0x2e, 0x10, 0x05, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x09, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1b, 0x35, + 0x1b, 0x11, 0x1b, 0x35, 0x1b, 0x3a, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1b, 0x35, + 0x1b, 0x3e, 0x1b, 0x35, 0x1b, 0x42, 0x1b, 0x35, + 0x1b, 0x41, 0x00, 0xc6, 0x00, 0x42, 0x00, 0x00, + 0x00, 0x44, 0x00, 0x45, 0x00, 0x8e, 0x01, 0x47, + 0x00, 0x4f, 0x00, 0x22, 0x02, 0x50, 0x00, 0x52, + 0x00, 0x54, 0x00, 0x55, 0x00, 0x57, 0x00, 0x61, + 0x00, 0x50, 0x02, 0x51, 0x02, 0x02, 0x1d, 0x62, + 0x00, 0x64, 0x00, 0x65, 0x00, 0x59, 0x02, 0x5b, + 0x02, 0x5c, 0x02, 0x67, 0x00, 0x00, 0x00, 0x6b, + 0x00, 0x6d, 0x00, 0x4b, 0x01, 0x6f, 0x00, 0x54, + 0x02, 0x16, 0x1d, 0x17, 0x1d, 0x70, 0x00, 0x74, + 0x00, 0x75, 0x00, 0x1d, 0x1d, 0x6f, 0x02, 0x76, + 0x00, 0x25, 0x1d, 0xb2, 0x03, 0xb3, 0x03, 0xb4, + 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x69, 0x00, 0x72, + 0x00, 0x75, 0x00, 0x76, 0x00, 0xb2, 0x03, 0xb3, + 0x03, 0xc1, 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x52, + 0x02, 0x63, 0x00, 0x55, 0x02, 0xf0, 0x00, 0x5c, + 0x02, 0x66, 0x00, 0x5f, 0x02, 0x61, 0x02, 0x65, + 0x02, 0x68, 0x02, 0x69, 0x02, 0x6a, 0x02, 0x7b, + 0x1d, 0x9d, 0x02, 0x6d, 0x02, 0x85, 0x1d, 0x9f, + 0x02, 0x71, 0x02, 0x70, 0x02, 0x72, 0x02, 0x73, + 0x02, 0x74, 0x02, 0x75, 0x02, 0x78, 0x02, 0x82, + 0x02, 0x83, 0x02, 0xab, 0x01, 0x89, 0x02, 0x8a, + 0x02, 0x1c, 0x1d, 0x8b, 0x02, 0x8c, 0x02, 0x7a, + 0x00, 0x90, 0x02, 0x91, 0x02, 0x92, 0x02, 0xb8, + 0x03, 0x41, 0x00, 0xa5, 0x42, 0x00, 0x87, 0x42, + 0x00, 0xa3, 0x42, 0x00, 0xb1, 0xc7, 0x00, 0x81, + 0x44, 0x00, 0x87, 0x44, 0x00, 0xa3, 0x44, 0x00, + 0xb1, 0x44, 0x00, 0xa7, 0x44, 0x00, 0xad, 0x12, + 0x01, 0x80, 0x12, 0x01, 0x81, 0x45, 0x00, 0xad, + 0x45, 0x00, 0xb0, 0x28, 0x02, 0x86, 0x46, 0x00, + 0x87, 0x47, 0x00, 0x84, 0x48, 0x00, 0x87, 0x48, + 0x00, 0xa3, 0x48, 0x00, 0x88, 0x48, 0x00, 0xa7, + 0x48, 0x00, 0xae, 0x49, 0x00, 0xb0, 0xcf, 0x00, + 0x81, 0x4b, 0x00, 0x81, 0x4b, 0x00, 0xa3, 0x4b, + 0x00, 0xb1, 0x4c, 0x00, 0xa3, 0x36, 0x1e, 0x84, + 0x4c, 0xb1, 0x4c, 0xad, 0x4d, 0x81, 0x4d, 0x87, + 0x4d, 0xa3, 0x4e, 0x87, 0x4e, 0xa3, 0x4e, 0xb1, + 0x4e, 0xad, 0xd5, 0x00, 0x81, 0xd5, 0x00, 0x88, + 0x4c, 0x01, 0x80, 0x4c, 0x01, 0x81, 0x50, 0x00, + 0x81, 0x50, 0x00, 0x87, 0x52, 0x00, 0x87, 0x52, + 0x00, 0xa3, 0x5a, 0x1e, 0x84, 0x52, 0x00, 0xb1, + 0x53, 0x00, 0x87, 0x53, 0x00, 0xa3, 0x5a, 0x01, + 0x87, 0x60, 0x01, 0x87, 0x62, 0x1e, 0x87, 0x54, + 0x00, 0x87, 0x54, 0x00, 0xa3, 0x54, 0x00, 0xb1, + 0x54, 0x00, 0xad, 0x55, 0x00, 0xa4, 0x55, 0x00, + 0xb0, 0x55, 0x00, 0xad, 0x68, 0x01, 0x81, 0x6a, + 0x01, 0x88, 0x56, 0x83, 0x56, 0xa3, 0x57, 0x80, + 0x57, 0x81, 0x57, 0x88, 0x57, 0x87, 0x57, 0xa3, + 0x58, 0x87, 0x58, 0x88, 0x59, 0x87, 0x5a, 0x82, + 0x5a, 0xa3, 0x5a, 0xb1, 0x68, 0xb1, 0x74, 0x88, + 0x77, 0x8a, 0x79, 0x8a, 0x61, 0x00, 0xbe, 0x02, + 0x7f, 0x01, 0x87, 0x41, 0x00, 0xa3, 0x41, 0x00, + 0x89, 0xc2, 0x00, 0x81, 0xc2, 0x00, 0x80, 0xc2, + 0x00, 0x89, 0xc2, 0x00, 0x83, 0xa0, 0x1e, 0x82, + 0x02, 0x01, 0x81, 0x02, 0x01, 0x80, 0x02, 0x01, + 0x89, 0x02, 0x01, 0x83, 0xa0, 0x1e, 0x86, 0x45, + 0x00, 0xa3, 0x45, 0x00, 0x89, 0x45, 0x00, 0x83, + 0xca, 0x00, 0x81, 0xca, 0x00, 0x80, 0xca, 0x00, + 0x89, 0xca, 0x00, 0x83, 0xb8, 0x1e, 0x82, 0x49, + 0x00, 0x89, 0x49, 0x00, 0xa3, 0x4f, 0x00, 0xa3, + 0x4f, 0x00, 0x89, 0xd4, 0x00, 0x81, 0xd4, 0x00, + 0x80, 0xd4, 0x00, 0x89, 0xd4, 0x00, 0x83, 0xcc, + 0x1e, 0x82, 0xa0, 0x01, 0x81, 0xa0, 0x01, 0x80, + 0xa0, 0x01, 0x89, 0xa0, 0x01, 0x83, 0xa0, 0x01, + 0xa3, 0x55, 0x00, 0xa3, 0x55, 0x00, 0x89, 0xaf, + 0x01, 0x81, 0xaf, 0x01, 0x80, 0xaf, 0x01, 0x89, + 0xaf, 0x01, 0x83, 0xaf, 0x01, 0xa3, 0x59, 0x00, + 0x80, 0x59, 0x00, 0xa3, 0x59, 0x00, 0x89, 0x59, + 0x00, 0x83, 0xb1, 0x03, 0x13, 0x03, 0x00, 0x1f, + 0x80, 0x00, 0x1f, 0x81, 0x00, 0x1f, 0xc2, 0x91, + 0x03, 0x13, 0x03, 0x08, 0x1f, 0x80, 0x08, 0x1f, + 0x81, 0x08, 0x1f, 0xc2, 0xb5, 0x03, 0x13, 0x03, + 0x10, 0x1f, 0x80, 0x10, 0x1f, 0x81, 0x95, 0x03, + 0x13, 0x03, 0x18, 0x1f, 0x80, 0x18, 0x1f, 0x81, + 0xb7, 0x03, 0x93, 0xb7, 0x03, 0x94, 0x20, 0x1f, + 0x80, 0x21, 0x1f, 0x80, 0x20, 0x1f, 0x81, 0x21, + 0x1f, 0x81, 0x20, 0x1f, 0xc2, 0x21, 0x1f, 0xc2, + 0x97, 0x03, 0x93, 0x97, 0x03, 0x94, 0x28, 0x1f, + 0x80, 0x29, 0x1f, 0x80, 0x28, 0x1f, 0x81, 0x29, + 0x1f, 0x81, 0x28, 0x1f, 0xc2, 0x29, 0x1f, 0xc2, + 0xb9, 0x03, 0x93, 0xb9, 0x03, 0x94, 0x30, 0x1f, + 0x80, 0x31, 0x1f, 0x80, 0x30, 0x1f, 0x81, 0x31, + 0x1f, 0x81, 0x30, 0x1f, 0xc2, 0x31, 0x1f, 0xc2, + 0x99, 0x03, 0x93, 0x99, 0x03, 0x94, 0x38, 0x1f, + 0x80, 0x39, 0x1f, 0x80, 0x38, 0x1f, 0x81, 0x39, + 0x1f, 0x81, 0x38, 0x1f, 0xc2, 0x39, 0x1f, 0xc2, + 0xbf, 0x03, 0x93, 0xbf, 0x03, 0x94, 0x40, 0x1f, + 0x80, 0x40, 0x1f, 0x81, 0x9f, 0x03, 0x13, 0x03, + 0x48, 0x1f, 0x80, 0x48, 0x1f, 0x81, 0xc5, 0x03, + 0x13, 0x03, 0x50, 0x1f, 0x80, 0x50, 0x1f, 0x81, + 0x50, 0x1f, 0xc2, 0xa5, 0x03, 0x94, 0x00, 0x00, + 0x00, 0x59, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x59, + 0x1f, 0x81, 0x00, 0x00, 0x00, 0x59, 0x1f, 0xc2, + 0xc9, 0x03, 0x93, 0xc9, 0x03, 0x94, 0x60, 0x1f, + 0x80, 0x61, 0x1f, 0x80, 0x60, 0x1f, 0x81, 0x61, + 0x1f, 0x81, 0x60, 0x1f, 0xc2, 0x61, 0x1f, 0xc2, + 0xa9, 0x03, 0x93, 0xa9, 0x03, 0x94, 0x68, 0x1f, + 0x80, 0x69, 0x1f, 0x80, 0x68, 0x1f, 0x81, 0x69, + 0x1f, 0x81, 0x68, 0x1f, 0xc2, 0x69, 0x1f, 0xc2, + 0xb1, 0x03, 0x80, 0xb5, 0x03, 0x80, 0xb7, 0x03, + 0x80, 0xb9, 0x03, 0x80, 0xbf, 0x03, 0x80, 0xc5, + 0x03, 0x80, 0xc9, 0x03, 0x80, 0x00, 0x1f, 0x45, + 0x03, 0x20, 0x1f, 0x45, 0x03, 0x60, 0x1f, 0x45, + 0x03, 0xb1, 0x03, 0x86, 0xb1, 0x03, 0x84, 0x70, + 0x1f, 0xc5, 0xb1, 0x03, 0xc5, 0xac, 0x03, 0xc5, + 0x00, 0x00, 0x00, 0xb1, 0x03, 0xc2, 0xb6, 0x1f, + 0xc5, 0x91, 0x03, 0x86, 0x91, 0x03, 0x84, 0x91, + 0x03, 0x80, 0x91, 0x03, 0xc5, 0x20, 0x93, 0x20, + 0x93, 0x20, 0xc2, 0xa8, 0x00, 0xc2, 0x74, 0x1f, + 0xc5, 0xb7, 0x03, 0xc5, 0xae, 0x03, 0xc5, 0x00, + 0x00, 0x00, 0xb7, 0x03, 0xc2, 0xc6, 0x1f, 0xc5, + 0x95, 0x03, 0x80, 0x97, 0x03, 0x80, 0x97, 0x03, + 0xc5, 0xbf, 0x1f, 0x80, 0xbf, 0x1f, 0x81, 0xbf, + 0x1f, 0xc2, 0xb9, 0x03, 0x86, 0xb9, 0x03, 0x84, + 0xca, 0x03, 0x80, 0x00, 0x03, 0xb9, 0x42, 0xca, + 0x42, 0x99, 0x06, 0x99, 0x04, 0x99, 0x00, 0xfe, + 0x1f, 0x80, 0xfe, 0x1f, 0x81, 0xfe, 0x1f, 0xc2, + 0xc5, 0x03, 0x86, 0xc5, 0x03, 0x84, 0xcb, 0x03, + 0x80, 0x00, 0x03, 0xc1, 0x13, 0xc1, 0x14, 0xc5, + 0x42, 0xcb, 0x42, 0xa5, 0x06, 0xa5, 0x04, 0xa5, + 0x00, 0xa1, 0x03, 0x94, 0xa8, 0x00, 0x80, 0x85, + 0x03, 0x60, 0x00, 0x7c, 0x1f, 0xc5, 0xc9, 0x03, + 0xc5, 0xce, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xc9, + 0x03, 0xc2, 0xf6, 0x1f, 0xc5, 0x9f, 0x03, 0x80, + 0xa9, 0x03, 0x80, 0xa9, 0x03, 0xc5, 0x20, 0x94, + 0x02, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0xb3, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, + 0x00, 0x00, 0x00, 0x35, 0x20, 0x35, 0x20, 0x35, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x21, 0x00, 0x00, + 0x20, 0x85, 0x3f, 0x3f, 0x3f, 0x21, 0x21, 0x3f, + 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30, 0x69, + 0x00, 0x00, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x2b, 0x3d, 0x28, 0x29, 0x6e, 0x30, 0x00, 0x2b, + 0x00, 0x12, 0x22, 0x3d, 0x00, 0x28, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x61, 0x00, 0x65, 0x00, 0x6f, + 0x00, 0x78, 0x00, 0x59, 0x02, 0x68, 0x6b, 0x6c, + 0x6d, 0x6e, 0x70, 0x73, 0x74, 0x52, 0x73, 0x61, + 0x2f, 0x63, 0x61, 0x2f, 0x73, 0xb0, 0x00, 0x43, + 0x63, 0x2f, 0x6f, 0x63, 0x2f, 0x75, 0xb0, 0x00, + 0x46, 0x48, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, + 0xdf, 0x01, 0x01, 0x04, 0x24, 0x4e, 0x6f, 0x50, + 0x51, 0x52, 0x52, 0x52, 0x53, 0x4d, 0x54, 0x45, + 0x4c, 0x54, 0x4d, 0x4b, 0x00, 0xc5, 0x00, 0x42, + 0x43, 0x00, 0x65, 0x45, 0x46, 0x00, 0x4d, 0x6f, + 0xd0, 0x05, 0x46, 0x41, 0x58, 0xc0, 0x03, 0xb3, + 0x03, 0x93, 0x03, 0xa0, 0x03, 0x11, 0x22, 0x44, + 0x64, 0x65, 0x69, 0x6a, 0x31, 0xd0, 0x37, 0x31, + 0xd0, 0x39, 0x31, 0xd0, 0x31, 0x30, 0x31, 0xd0, + 0x33, 0x32, 0xd0, 0x33, 0x31, 0xd0, 0x35, 0x32, + 0xd0, 0x35, 0x33, 0xd0, 0x35, 0x34, 0xd0, 0x35, + 0x31, 0xd0, 0x36, 0x35, 0xd0, 0x36, 0x31, 0xd0, + 0x38, 0x33, 0xd0, 0x38, 0x35, 0xd0, 0x38, 0x37, + 0xd0, 0x38, 0x31, 0xd0, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x56, 0x56, 0x49, 0x56, 0x49, 0x49, + 0x56, 0x49, 0x49, 0x49, 0x49, 0x58, 0x58, 0x49, + 0x58, 0x49, 0x49, 0x4c, 0x43, 0x44, 0x4d, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x76, 0x76, + 0x69, 0x76, 0x69, 0x69, 0x76, 0x69, 0x69, 0x69, + 0x69, 0x78, 0x78, 0x69, 0x78, 0x69, 0x69, 0x6c, + 0x63, 0x64, 0x6d, 0x30, 0xd0, 0x33, 0x90, 0x21, + 0xb8, 0x92, 0x21, 0xb8, 0x94, 0x21, 0xb8, 0xd0, + 0x21, 0xb8, 0xd4, 0x21, 0xb8, 0xd2, 0x21, 0xb8, + 0x03, 0x22, 0xb8, 0x08, 0x22, 0xb8, 0x0b, 0x22, + 0xb8, 0x23, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x25, + 0x22, 0xb8, 0x2b, 0x22, 0x2b, 0x22, 0x2b, 0x22, + 0x00, 0x00, 0x00, 0x2e, 0x22, 0x2e, 0x22, 0x2e, + 0x22, 0x00, 0x00, 0x00, 0x3c, 0x22, 0xb8, 0x43, + 0x22, 0xb8, 0x45, 0x22, 0xb8, 0x00, 0x00, 0x00, + 0x48, 0x22, 0xb8, 0x3d, 0x00, 0xb8, 0x00, 0x00, + 0x00, 0x61, 0x22, 0xb8, 0x4d, 0x22, 0xb8, 0x3c, + 0x00, 0xb8, 0x3e, 0x00, 0xb8, 0x64, 0x22, 0xb8, + 0x65, 0x22, 0xb8, 0x72, 0x22, 0xb8, 0x76, 0x22, + 0xb8, 0x7a, 0x22, 0xb8, 0x82, 0x22, 0xb8, 0x86, + 0x22, 0xb8, 0xa2, 0x22, 0xb8, 0xa8, 0x22, 0xb8, + 0xa9, 0x22, 0xb8, 0xab, 0x22, 0xb8, 0x7c, 0x22, + 0xb8, 0x91, 0x22, 0xb8, 0xb2, 0x22, 0x38, 0x03, + 0x08, 0x30, 0x31, 0x00, 0x31, 0x00, 0x30, 0x00, + 0x32, 0x30, 0x28, 0x00, 0x31, 0x00, 0x29, 0x00, + 0x28, 0x00, 0x31, 0x00, 0x30, 0x00, 0x29, 0x00, + 0x28, 0x32, 0x30, 0x29, 0x31, 0x00, 0x2e, 0x00, + 0x31, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x32, 0x30, + 0x2e, 0x28, 0x00, 0x61, 0x00, 0x29, 0x00, 0x41, + 0x00, 0x61, 0x00, 0x2b, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0xdd, 0x2a, 0xb8, 0x6a, 0x56, 0x00, 0x4e, + 0x00, 0x28, 0x36, 0x3f, 0x59, 0x85, 0x8c, 0xa0, + 0xba, 0x3f, 0x51, 0x00, 0x26, 0x2c, 0x43, 0x57, + 0x6c, 0xa1, 0xb6, 0xc1, 0x9b, 0x52, 0x00, 0x5e, + 0x7a, 0x7f, 0x9d, 0xa6, 0xc1, 0xce, 0xe7, 0xb6, + 0x53, 0xc8, 0x53, 0xe3, 0x53, 0xd7, 0x56, 0x1f, + 0x57, 0xeb, 0x58, 0x02, 0x59, 0x0a, 0x59, 0x15, + 0x59, 0x27, 0x59, 0x73, 0x59, 0x50, 0x5b, 0x80, + 0x5b, 0xf8, 0x5b, 0x0f, 0x5c, 0x22, 0x5c, 0x38, + 0x5c, 0x6e, 0x5c, 0x71, 0x5c, 0xdb, 0x5d, 0xe5, + 0x5d, 0xf1, 0x5d, 0xfe, 0x5d, 0x72, 0x5e, 0x7a, + 0x5e, 0x7f, 0x5e, 0xf4, 0x5e, 0xfe, 0x5e, 0x0b, + 0x5f, 0x13, 0x5f, 0x50, 0x5f, 0x61, 0x5f, 0x73, + 0x5f, 0xc3, 0x5f, 0x08, 0x62, 0x36, 0x62, 0x4b, + 0x62, 0x2f, 0x65, 0x34, 0x65, 0x87, 0x65, 0x97, + 0x65, 0xa4, 0x65, 0xb9, 0x65, 0xe0, 0x65, 0xe5, + 0x65, 0xf0, 0x66, 0x08, 0x67, 0x28, 0x67, 0x20, + 0x6b, 0x62, 0x6b, 0x79, 0x6b, 0xb3, 0x6b, 0xcb, + 0x6b, 0xd4, 0x6b, 0xdb, 0x6b, 0x0f, 0x6c, 0x14, + 0x6c, 0x34, 0x6c, 0x6b, 0x70, 0x2a, 0x72, 0x36, + 0x72, 0x3b, 0x72, 0x3f, 0x72, 0x47, 0x72, 0x59, + 0x72, 0x5b, 0x72, 0xac, 0x72, 0x84, 0x73, 0x89, + 0x73, 0xdc, 0x74, 0xe6, 0x74, 0x18, 0x75, 0x1f, + 0x75, 0x28, 0x75, 0x30, 0x75, 0x8b, 0x75, 0x92, + 0x75, 0x76, 0x76, 0x7d, 0x76, 0xae, 0x76, 0xbf, + 0x76, 0xee, 0x76, 0xdb, 0x77, 0xe2, 0x77, 0xf3, + 0x77, 0x3a, 0x79, 0xb8, 0x79, 0xbe, 0x79, 0x74, + 0x7a, 0xcb, 0x7a, 0xf9, 0x7a, 0x73, 0x7c, 0xf8, + 0x7c, 0x36, 0x7f, 0x51, 0x7f, 0x8a, 0x7f, 0xbd, + 0x7f, 0x01, 0x80, 0x0c, 0x80, 0x12, 0x80, 0x33, + 0x80, 0x7f, 0x80, 0x89, 0x80, 0xe3, 0x81, 0x00, + 0x07, 0x10, 0x19, 0x29, 0x38, 0x3c, 0x8b, 0x8f, + 0x95, 0x4d, 0x86, 0x6b, 0x86, 0x40, 0x88, 0x4c, + 0x88, 0x63, 0x88, 0x7e, 0x89, 0x8b, 0x89, 0xd2, + 0x89, 0x00, 0x8a, 0x37, 0x8c, 0x46, 0x8c, 0x55, + 0x8c, 0x78, 0x8c, 0x9d, 0x8c, 0x64, 0x8d, 0x70, + 0x8d, 0xb3, 0x8d, 0xab, 0x8e, 0xca, 0x8e, 0x9b, + 0x8f, 0xb0, 0x8f, 0xb5, 0x8f, 0x91, 0x90, 0x49, + 0x91, 0xc6, 0x91, 0xcc, 0x91, 0xd1, 0x91, 0x77, + 0x95, 0x80, 0x95, 0x1c, 0x96, 0xb6, 0x96, 0xb9, + 0x96, 0xe8, 0x96, 0x51, 0x97, 0x5e, 0x97, 0x62, + 0x97, 0x69, 0x97, 0xcb, 0x97, 0xed, 0x97, 0xf3, + 0x97, 0x01, 0x98, 0xa8, 0x98, 0xdb, 0x98, 0xdf, + 0x98, 0x96, 0x99, 0x99, 0x99, 0xac, 0x99, 0xa8, + 0x9a, 0xd8, 0x9a, 0xdf, 0x9a, 0x25, 0x9b, 0x2f, + 0x9b, 0x32, 0x9b, 0x3c, 0x9b, 0x5a, 0x9b, 0xe5, + 0x9c, 0x75, 0x9e, 0x7f, 0x9e, 0xa5, 0x9e, 0x00, + 0x16, 0x1e, 0x28, 0x2c, 0x54, 0x58, 0x69, 0x6e, + 0x7b, 0x96, 0xa5, 0xad, 0xe8, 0xf7, 0xfb, 0x12, + 0x30, 0x00, 0x00, 0x41, 0x53, 0x44, 0x53, 0x45, + 0x53, 0x4b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x51, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x53, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x55, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x59, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x61, 0x30, 0x99, 0x30, 0x64, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x66, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0x99, + 0x30, 0x6f, 0x30, 0x99, 0x30, 0x72, 0x30, 0x99, + 0x30, 0x75, 0x30, 0x99, 0x30, 0x78, 0x30, 0x99, + 0x30, 0x7b, 0x30, 0x99, 0x30, 0x46, 0x30, 0x99, + 0x30, 0x20, 0x00, 0x99, 0x30, 0x9d, 0x30, 0x99, + 0x30, 0x88, 0x30, 0x8a, 0x30, 0xab, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xad, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x30, 0x99, + 0x30, 0xc4, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc6, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc8, 0x30, 0x99, 0x30, 0xcf, 0x30, 0x99, + 0x30, 0xd2, 0x30, 0x99, 0x30, 0xd5, 0x30, 0x99, + 0x30, 0xd8, 0x30, 0x99, 0x30, 0xdb, 0x30, 0x99, + 0x30, 0xa6, 0x30, 0x99, 0x30, 0xef, 0x30, 0x99, + 0x30, 0xfd, 0x30, 0x99, 0x30, 0xb3, 0x30, 0xc8, + 0x30, 0x00, 0x11, 0x00, 0x01, 0xaa, 0x02, 0xac, + 0xad, 0x03, 0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0x1a, 0x06, 0x07, 0x08, 0x21, 0x09, + 0x11, 0x61, 0x11, 0x14, 0x11, 0x4c, 0x00, 0x01, + 0xb3, 0xb4, 0xb8, 0xba, 0xbf, 0xc3, 0xc5, 0x08, + 0xc9, 0xcb, 0x09, 0x0a, 0x0c, 0x0e, 0x0f, 0x13, + 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x22, + 0x2c, 0x33, 0x38, 0xdd, 0xde, 0x43, 0x44, 0x45, + 0x70, 0x71, 0x74, 0x7d, 0x7e, 0x80, 0x8a, 0x8d, + 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, + 0x0a, 0x4e, 0x2d, 0x4e, 0x0b, 0x4e, 0x32, 0x75, + 0x59, 0x4e, 0x19, 0x4e, 0x01, 0x4e, 0x29, 0x59, + 0x30, 0x57, 0xba, 0x4e, 0x28, 0x00, 0x29, 0x00, + 0x00, 0x11, 0x02, 0x11, 0x03, 0x11, 0x05, 0x11, + 0x06, 0x11, 0x07, 0x11, 0x09, 0x11, 0x0b, 0x11, + 0x0c, 0x11, 0x0e, 0x11, 0x0f, 0x11, 0x10, 0x11, + 0x11, 0x11, 0x12, 0x11, 0x28, 0x00, 0x00, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x02, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x05, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x09, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0e, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0c, 0x11, + 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x69, 0x11, 0x0c, 0x11, 0x65, 0x11, 0xab, 0x11, + 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11, + 0x12, 0x11, 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, + 0x29, 0x00, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, + 0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e, + 0x6b, 0x51, 0x5d, 0x4e, 0x41, 0x53, 0x08, 0x67, + 0x6b, 0x70, 0x34, 0x6c, 0x28, 0x67, 0xd1, 0x91, + 0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67, + 0x3e, 0x79, 0x0d, 0x54, 0x79, 0x72, 0xa1, 0x8c, + 0x5d, 0x79, 0xb4, 0x52, 0xe3, 0x4e, 0x7c, 0x54, + 0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c, + 0x54, 0x53, 0x6d, 0x79, 0x11, 0x4f, 0xea, 0x81, + 0xf3, 0x81, 0x4f, 0x55, 0x7c, 0x5e, 0x87, 0x65, + 0x8f, 0x7b, 0x50, 0x54, 0x45, 0x32, 0x00, 0x31, + 0x00, 0x33, 0x00, 0x30, 0x00, 0x00, 0x11, 0x00, + 0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x0c, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x00, 0x11, 0x00, + 0x61, 0x02, 0x61, 0x03, 0x61, 0x05, 0x61, 0x06, + 0x61, 0x07, 0x61, 0x09, 0x61, 0x0b, 0x61, 0x0c, + 0x61, 0x0e, 0x11, 0x61, 0x11, 0x00, 0x11, 0x0e, + 0x61, 0xb7, 0x00, 0x69, 0x0b, 0x11, 0x01, 0x63, + 0x00, 0x69, 0x0b, 0x11, 0x6e, 0x11, 0x00, 0x4e, + 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, 0x94, 0x4e, + 0x6d, 0x51, 0x03, 0x4e, 0x6b, 0x51, 0x5d, 0x4e, + 0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c, + 0x28, 0x67, 0xd1, 0x91, 0x1f, 0x57, 0xe5, 0x65, + 0x2a, 0x68, 0x09, 0x67, 0x3e, 0x79, 0x0d, 0x54, + 0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52, + 0xd8, 0x79, 0x37, 0x75, 0x73, 0x59, 0x69, 0x90, + 0x2a, 0x51, 0x70, 0x53, 0xe8, 0x6c, 0x05, 0x98, + 0x11, 0x4f, 0x99, 0x51, 0x63, 0x6b, 0x0a, 0x4e, + 0x2d, 0x4e, 0x0b, 0x4e, 0xe6, 0x5d, 0xf3, 0x53, + 0x3b, 0x53, 0x97, 0x5b, 0x66, 0x5b, 0xe3, 0x76, + 0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x1c, 0x59, + 0x33, 0x00, 0x36, 0x00, 0x34, 0x00, 0x30, 0x00, + 0x35, 0x30, 0x31, 0x00, 0x08, 0x67, 0x31, 0x00, + 0x30, 0x00, 0x08, 0x67, 0x48, 0x67, 0x65, 0x72, + 0x67, 0x65, 0x56, 0x4c, 0x54, 0x44, 0xa2, 0x30, + 0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0b, 0x0d, + 0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, + 0x1f, 0x22, 0x24, 0x26, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x30, 0x33, 0x36, 0x39, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x42, 0x44, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0xe4, + 0x4e, 0x8c, 0x54, 0xa1, 0x30, 0x01, 0x30, 0x5b, + 0x27, 0x01, 0x4a, 0x34, 0x00, 0x01, 0x52, 0x39, + 0x01, 0xa2, 0x30, 0x00, 0x5a, 0x49, 0xa4, 0x30, + 0x00, 0x27, 0x4f, 0x0c, 0xa4, 0x30, 0x00, 0x4f, + 0x1d, 0x02, 0x05, 0x4f, 0xa8, 0x30, 0x00, 0x11, + 0x07, 0x54, 0x21, 0xa8, 0x30, 0x00, 0x54, 0x03, + 0x54, 0xa4, 0x30, 0x06, 0x4f, 0x15, 0x06, 0x58, + 0x3c, 0x07, 0x00, 0x46, 0xab, 0x30, 0x00, 0x3e, + 0x18, 0x1d, 0x00, 0x42, 0x3f, 0x51, 0xac, 0x30, + 0x00, 0x41, 0x47, 0x00, 0x47, 0x32, 0xae, 0x30, + 0xac, 0x30, 0xae, 0x30, 0x00, 0x1d, 0x4e, 0xad, + 0x30, 0x00, 0x38, 0x3d, 0x4f, 0x01, 0x3e, 0x13, + 0x4f, 0xad, 0x30, 0xed, 0x30, 0xad, 0x30, 0x00, + 0x40, 0x03, 0x3c, 0x33, 0xad, 0x30, 0x00, 0x40, + 0x34, 0x4f, 0x1b, 0x3e, 0xad, 0x30, 0x00, 0x40, + 0x42, 0x16, 0x1b, 0xb0, 0x30, 0x00, 0x39, 0x30, + 0xa4, 0x30, 0x0c, 0x45, 0x3c, 0x24, 0x4f, 0x0b, + 0x47, 0x18, 0x00, 0x49, 0xaf, 0x30, 0x00, 0x3e, + 0x4d, 0x1e, 0xb1, 0x30, 0x00, 0x4b, 0x08, 0x02, + 0x3a, 0x19, 0x02, 0x4b, 0x2c, 0xa4, 0x30, 0x11, + 0x00, 0x0b, 0x47, 0xb5, 0x30, 0x00, 0x3e, 0x0c, + 0x47, 0x2b, 0xb0, 0x30, 0x07, 0x3a, 0x43, 0x00, + 0xb9, 0x30, 0x02, 0x3a, 0x08, 0x02, 0x3a, 0x0f, + 0x07, 0x43, 0x00, 0xb7, 0x30, 0x10, 0x00, 0x12, + 0x34, 0x11, 0x3c, 0x13, 0x17, 0xa4, 0x30, 0x2a, + 0x1f, 0x24, 0x2b, 0x00, 0x20, 0xbb, 0x30, 0x16, + 0x41, 0x00, 0x38, 0x0d, 0xc4, 0x30, 0x0d, 0x38, + 0x00, 0xd0, 0x30, 0x00, 0x2c, 0x1c, 0x1b, 0xa2, + 0x30, 0x32, 0x00, 0x17, 0x26, 0x49, 0xaf, 0x30, + 0x25, 0x00, 0x3c, 0xb3, 0x30, 0x21, 0x00, 0x20, + 0x38, 0xa1, 0x30, 0x34, 0x00, 0x48, 0x22, 0x28, + 0xa3, 0x30, 0x32, 0x00, 0x59, 0x25, 0xa7, 0x30, + 0x2f, 0x1c, 0x10, 0x00, 0x44, 0xd5, 0x30, 0x00, + 0x14, 0x1e, 0xaf, 0x30, 0x29, 0x00, 0x10, 0x4d, + 0x3c, 0xda, 0x30, 0xbd, 0x30, 0xb8, 0x30, 0x22, + 0x13, 0x1a, 0x20, 0x33, 0x0c, 0x22, 0x3b, 0x01, + 0x22, 0x44, 0x00, 0x21, 0x44, 0x07, 0xa4, 0x30, + 0x39, 0x00, 0x4f, 0x24, 0xc8, 0x30, 0x14, 0x23, + 0x00, 0xdb, 0x30, 0xf3, 0x30, 0xc9, 0x30, 0x14, + 0x2a, 0x00, 0x12, 0x33, 0x22, 0x12, 0x33, 0x2a, + 0xa4, 0x30, 0x3a, 0x00, 0x0b, 0x49, 0xa4, 0x30, + 0x3a, 0x00, 0x47, 0x3a, 0x1f, 0x2b, 0x3a, 0x47, + 0x0b, 0xb7, 0x30, 0x27, 0x3c, 0x00, 0x30, 0x3c, + 0xaf, 0x30, 0x30, 0x00, 0x3e, 0x44, 0xdf, 0x30, + 0xea, 0x30, 0xd0, 0x30, 0x0f, 0x1a, 0x00, 0x2c, + 0x1b, 0xe1, 0x30, 0xac, 0x30, 0xac, 0x30, 0x35, + 0x00, 0x1c, 0x47, 0x35, 0x50, 0x1c, 0x3f, 0xa2, + 0x30, 0x42, 0x5a, 0x27, 0x42, 0x5a, 0x49, 0x44, + 0x00, 0x51, 0xc3, 0x30, 0x27, 0x00, 0x05, 0x28, + 0xea, 0x30, 0xe9, 0x30, 0xd4, 0x30, 0x17, 0x00, + 0x28, 0xd6, 0x30, 0x15, 0x26, 0x00, 0x15, 0xec, + 0x30, 0xe0, 0x30, 0xb2, 0x30, 0x3a, 0x41, 0x16, + 0x00, 0x41, 0xc3, 0x30, 0x2c, 0x00, 0x05, 0x30, + 0x00, 0xb9, 0x70, 0x31, 0x00, 0x30, 0x00, 0xb9, + 0x70, 0x32, 0x00, 0x30, 0x00, 0xb9, 0x70, 0x68, + 0x50, 0x61, 0x64, 0x61, 0x41, 0x55, 0x62, 0x61, + 0x72, 0x6f, 0x56, 0x70, 0x63, 0x64, 0x6d, 0x64, + 0x00, 0x6d, 0x00, 0xb2, 0x00, 0x49, 0x00, 0x55, + 0x00, 0x73, 0x5e, 0x10, 0x62, 0x2d, 0x66, 0x8c, + 0x54, 0x27, 0x59, 0x63, 0x6b, 0x0e, 0x66, 0xbb, + 0x6c, 0x2a, 0x68, 0x0f, 0x5f, 0x1a, 0x4f, 0x3e, + 0x79, 0x70, 0x00, 0x41, 0x6e, 0x00, 0x41, 0xbc, + 0x03, 0x41, 0x6d, 0x00, 0x41, 0x6b, 0x00, 0x41, + 0x4b, 0x00, 0x42, 0x4d, 0x00, 0x42, 0x47, 0x00, + 0x42, 0x63, 0x61, 0x6c, 0x6b, 0x63, 0x61, 0x6c, + 0x70, 0x00, 0x46, 0x6e, 0x00, 0x46, 0xbc, 0x03, + 0x46, 0xbc, 0x03, 0x67, 0x6d, 0x00, 0x67, 0x6b, + 0x00, 0x67, 0x48, 0x00, 0x7a, 0x6b, 0x48, 0x7a, + 0x4d, 0x48, 0x7a, 0x47, 0x48, 0x7a, 0x54, 0x48, + 0x7a, 0xbc, 0x03, 0x13, 0x21, 0x6d, 0x00, 0x13, + 0x21, 0x64, 0x00, 0x13, 0x21, 0x6b, 0x00, 0x13, + 0x21, 0x66, 0x00, 0x6d, 0x6e, 0x00, 0x6d, 0xbc, + 0x03, 0x6d, 0x6d, 0x00, 0x6d, 0x63, 0x00, 0x6d, + 0x6b, 0x00, 0x6d, 0x63, 0x00, 0x0a, 0x0a, 0x4f, + 0x00, 0x0a, 0x4f, 0x6d, 0x00, 0xb2, 0x00, 0x63, + 0x00, 0x08, 0x0a, 0x4f, 0x0a, 0x0a, 0x50, 0x00, + 0x0a, 0x50, 0x6d, 0x00, 0xb3, 0x00, 0x6b, 0x00, + 0x6d, 0x00, 0xb3, 0x00, 0x6d, 0x00, 0x15, 0x22, + 0x73, 0x00, 0x6d, 0x00, 0x15, 0x22, 0x73, 0x00, + 0xb2, 0x00, 0x50, 0x61, 0x6b, 0x50, 0x61, 0x4d, + 0x50, 0x61, 0x47, 0x50, 0x61, 0x72, 0x61, 0x64, + 0x72, 0x61, 0x64, 0xd1, 0x73, 0x72, 0x00, 0x61, + 0x00, 0x64, 0x00, 0x15, 0x22, 0x73, 0x00, 0xb2, + 0x00, 0x70, 0x00, 0x73, 0x6e, 0x00, 0x73, 0xbc, + 0x03, 0x73, 0x6d, 0x00, 0x73, 0x70, 0x00, 0x56, + 0x6e, 0x00, 0x56, 0xbc, 0x03, 0x56, 0x6d, 0x00, + 0x56, 0x6b, 0x00, 0x56, 0x4d, 0x00, 0x56, 0x70, + 0x00, 0x57, 0x6e, 0x00, 0x57, 0xbc, 0x03, 0x57, + 0x6d, 0x00, 0x57, 0x6b, 0x00, 0x57, 0x4d, 0x00, + 0x57, 0x6b, 0x00, 0xa9, 0x03, 0x4d, 0x00, 0xa9, + 0x03, 0x61, 0x2e, 0x6d, 0x2e, 0x42, 0x71, 0x63, + 0x63, 0x63, 0x64, 0x43, 0xd1, 0x6b, 0x67, 0x43, + 0x6f, 0x2e, 0x64, 0x42, 0x47, 0x79, 0x68, 0x61, + 0x48, 0x50, 0x69, 0x6e, 0x4b, 0x4b, 0x4b, 0x4d, + 0x6b, 0x74, 0x6c, 0x6d, 0x6c, 0x6e, 0x6c, 0x6f, + 0x67, 0x6c, 0x78, 0x6d, 0x62, 0x6d, 0x69, 0x6c, + 0x6d, 0x6f, 0x6c, 0x50, 0x48, 0x70, 0x2e, 0x6d, + 0x2e, 0x50, 0x50, 0x4d, 0x50, 0x52, 0x73, 0x72, + 0x53, 0x76, 0x57, 0x62, 0x56, 0xd1, 0x6d, 0x41, + 0xd1, 0x6d, 0x31, 0x00, 0xe5, 0x65, 0x31, 0x00, + 0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00, + 0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65, + 0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x43, + 0x46, 0x51, 0x26, 0x01, 0x53, 0x01, 0x27, 0xa7, + 0x37, 0xab, 0x6b, 0x02, 0x52, 0xab, 0x48, 0x8c, + 0xf4, 0x66, 0xca, 0x8e, 0xc8, 0x8c, 0xd1, 0x6e, + 0x32, 0x4e, 0xe5, 0x53, 0x9c, 0x9f, 0x9c, 0x9f, + 0x51, 0x59, 0xd1, 0x91, 0x87, 0x55, 0x48, 0x59, + 0xf6, 0x61, 0x69, 0x76, 0x85, 0x7f, 0x3f, 0x86, + 0xba, 0x87, 0xf8, 0x88, 0x8f, 0x90, 0x02, 0x6a, + 0x1b, 0x6d, 0xd9, 0x70, 0xde, 0x73, 0x3d, 0x84, + 0x6a, 0x91, 0xf1, 0x99, 0x82, 0x4e, 0x75, 0x53, + 0x04, 0x6b, 0x1b, 0x72, 0x2d, 0x86, 0x1e, 0x9e, + 0x50, 0x5d, 0xeb, 0x6f, 0xcd, 0x85, 0x64, 0x89, + 0xc9, 0x62, 0xd8, 0x81, 0x1f, 0x88, 0xca, 0x5e, + 0x17, 0x67, 0x6a, 0x6d, 0xfc, 0x72, 0xce, 0x90, + 0x86, 0x4f, 0xb7, 0x51, 0xde, 0x52, 0xc4, 0x64, + 0xd3, 0x6a, 0x10, 0x72, 0xe7, 0x76, 0x01, 0x80, + 0x06, 0x86, 0x5c, 0x86, 0xef, 0x8d, 0x32, 0x97, + 0x6f, 0x9b, 0xfa, 0x9d, 0x8c, 0x78, 0x7f, 0x79, + 0xa0, 0x7d, 0xc9, 0x83, 0x04, 0x93, 0x7f, 0x9e, + 0xd6, 0x8a, 0xdf, 0x58, 0x04, 0x5f, 0x60, 0x7c, + 0x7e, 0x80, 0x62, 0x72, 0xca, 0x78, 0xc2, 0x8c, + 0xf7, 0x96, 0xd8, 0x58, 0x62, 0x5c, 0x13, 0x6a, + 0xda, 0x6d, 0x0f, 0x6f, 0x2f, 0x7d, 0x37, 0x7e, + 0x4b, 0x96, 0xd2, 0x52, 0x8b, 0x80, 0xdc, 0x51, + 0xcc, 0x51, 0x1c, 0x7a, 0xbe, 0x7d, 0xf1, 0x83, + 0x75, 0x96, 0x80, 0x8b, 0xcf, 0x62, 0x02, 0x6a, + 0xfe, 0x8a, 0x39, 0x4e, 0xe7, 0x5b, 0x12, 0x60, + 0x87, 0x73, 0x70, 0x75, 0x17, 0x53, 0xfb, 0x78, + 0xbf, 0x4f, 0xa9, 0x5f, 0x0d, 0x4e, 0xcc, 0x6c, + 0x78, 0x65, 0x22, 0x7d, 0xc3, 0x53, 0x5e, 0x58, + 0x01, 0x77, 0x49, 0x84, 0xaa, 0x8a, 0xba, 0x6b, + 0xb0, 0x8f, 0x88, 0x6c, 0xfe, 0x62, 0xe5, 0x82, + 0xa0, 0x63, 0x65, 0x75, 0xae, 0x4e, 0x69, 0x51, + 0xc9, 0x51, 0x81, 0x68, 0xe7, 0x7c, 0x6f, 0x82, + 0xd2, 0x8a, 0xcf, 0x91, 0xf5, 0x52, 0x42, 0x54, + 0x73, 0x59, 0xec, 0x5e, 0xc5, 0x65, 0xfe, 0x6f, + 0x2a, 0x79, 0xad, 0x95, 0x6a, 0x9a, 0x97, 0x9e, + 0xce, 0x9e, 0x9b, 0x52, 0xc6, 0x66, 0x77, 0x6b, + 0x62, 0x8f, 0x74, 0x5e, 0x90, 0x61, 0x00, 0x62, + 0x9a, 0x64, 0x23, 0x6f, 0x49, 0x71, 0x89, 0x74, + 0xca, 0x79, 0xf4, 0x7d, 0x6f, 0x80, 0x26, 0x8f, + 0xee, 0x84, 0x23, 0x90, 0x4a, 0x93, 0x17, 0x52, + 0xa3, 0x52, 0xbd, 0x54, 0xc8, 0x70, 0xc2, 0x88, + 0xaa, 0x8a, 0xc9, 0x5e, 0xf5, 0x5f, 0x7b, 0x63, + 0xae, 0x6b, 0x3e, 0x7c, 0x75, 0x73, 0xe4, 0x4e, + 0xf9, 0x56, 0xe7, 0x5b, 0xba, 0x5d, 0x1c, 0x60, + 0xb2, 0x73, 0x69, 0x74, 0x9a, 0x7f, 0x46, 0x80, + 0x34, 0x92, 0xf6, 0x96, 0x48, 0x97, 0x18, 0x98, + 0x8b, 0x4f, 0xae, 0x79, 0xb4, 0x91, 0xb8, 0x96, + 0xe1, 0x60, 0x86, 0x4e, 0xda, 0x50, 0xee, 0x5b, + 0x3f, 0x5c, 0x99, 0x65, 0x02, 0x6a, 0xce, 0x71, + 0x42, 0x76, 0xfc, 0x84, 0x7c, 0x90, 0x8d, 0x9f, + 0x88, 0x66, 0x2e, 0x96, 0x89, 0x52, 0x7b, 0x67, + 0xf3, 0x67, 0x41, 0x6d, 0x9c, 0x6e, 0x09, 0x74, + 0x59, 0x75, 0x6b, 0x78, 0x10, 0x7d, 0x5e, 0x98, + 0x6d, 0x51, 0x2e, 0x62, 0x78, 0x96, 0x2b, 0x50, + 0x19, 0x5d, 0xea, 0x6d, 0x2a, 0x8f, 0x8b, 0x5f, + 0x44, 0x61, 0x17, 0x68, 0x87, 0x73, 0x86, 0x96, + 0x29, 0x52, 0x0f, 0x54, 0x65, 0x5c, 0x13, 0x66, + 0x4e, 0x67, 0xa8, 0x68, 0xe5, 0x6c, 0x06, 0x74, + 0xe2, 0x75, 0x79, 0x7f, 0xcf, 0x88, 0xe1, 0x88, + 0xcc, 0x91, 0xe2, 0x96, 0x3f, 0x53, 0xba, 0x6e, + 0x1d, 0x54, 0xd0, 0x71, 0x98, 0x74, 0xfa, 0x85, + 0xa3, 0x96, 0x57, 0x9c, 0x9f, 0x9e, 0x97, 0x67, + 0xcb, 0x6d, 0xe8, 0x81, 0xcb, 0x7a, 0x20, 0x7b, + 0x92, 0x7c, 0xc0, 0x72, 0x99, 0x70, 0x58, 0x8b, + 0xc0, 0x4e, 0x36, 0x83, 0x3a, 0x52, 0x07, 0x52, + 0xa6, 0x5e, 0xd3, 0x62, 0xd6, 0x7c, 0x85, 0x5b, + 0x1e, 0x6d, 0xb4, 0x66, 0x3b, 0x8f, 0x4c, 0x88, + 0x4d, 0x96, 0x8b, 0x89, 0xd3, 0x5e, 0x40, 0x51, + 0xc0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x58, + 0x00, 0x00, 0x74, 0x66, 0x00, 0x00, 0x00, 0x00, + 0xde, 0x51, 0x2a, 0x73, 0xca, 0x76, 0x3c, 0x79, + 0x5e, 0x79, 0x65, 0x79, 0x8f, 0x79, 0x56, 0x97, + 0xbe, 0x7c, 0xbd, 0x7f, 0x00, 0x00, 0x12, 0x86, + 0x00, 0x00, 0xf8, 0x8a, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x90, 0xfd, 0x90, 0xef, 0x98, 0xfc, 0x98, + 0x28, 0x99, 0xb4, 0x9d, 0xde, 0x90, 0xb7, 0x96, + 0xae, 0x4f, 0xe7, 0x50, 0x4d, 0x51, 0xc9, 0x52, + 0xe4, 0x52, 0x51, 0x53, 0x9d, 0x55, 0x06, 0x56, + 0x68, 0x56, 0x40, 0x58, 0xa8, 0x58, 0x64, 0x5c, + 0x6e, 0x5c, 0x94, 0x60, 0x68, 0x61, 0x8e, 0x61, + 0xf2, 0x61, 0x4f, 0x65, 0xe2, 0x65, 0x91, 0x66, + 0x85, 0x68, 0x77, 0x6d, 0x1a, 0x6e, 0x22, 0x6f, + 0x6e, 0x71, 0x2b, 0x72, 0x22, 0x74, 0x91, 0x78, + 0x3e, 0x79, 0x49, 0x79, 0x48, 0x79, 0x50, 0x79, + 0x56, 0x79, 0x5d, 0x79, 0x8d, 0x79, 0x8e, 0x79, + 0x40, 0x7a, 0x81, 0x7a, 0xc0, 0x7b, 0xf4, 0x7d, + 0x09, 0x7e, 0x41, 0x7e, 0x72, 0x7f, 0x05, 0x80, + 0xed, 0x81, 0x79, 0x82, 0x79, 0x82, 0x57, 0x84, + 0x10, 0x89, 0x96, 0x89, 0x01, 0x8b, 0x39, 0x8b, + 0xd3, 0x8c, 0x08, 0x8d, 0xb6, 0x8f, 0x38, 0x90, + 0xe3, 0x96, 0xff, 0x97, 0x3b, 0x98, 0x75, 0x60, + 0xee, 0x42, 0x18, 0x82, 0x02, 0x26, 0x4e, 0xb5, + 0x51, 0x68, 0x51, 0x80, 0x4f, 0x45, 0x51, 0x80, + 0x51, 0xc7, 0x52, 0xfa, 0x52, 0x9d, 0x55, 0x55, + 0x55, 0x99, 0x55, 0xe2, 0x55, 0x5a, 0x58, 0xb3, + 0x58, 0x44, 0x59, 0x54, 0x59, 0x62, 0x5a, 0x28, + 0x5b, 0xd2, 0x5e, 0xd9, 0x5e, 0x69, 0x5f, 0xad, + 0x5f, 0xd8, 0x60, 0x4e, 0x61, 0x08, 0x61, 0x8e, + 0x61, 0x60, 0x61, 0xf2, 0x61, 0x34, 0x62, 0xc4, + 0x63, 0x1c, 0x64, 0x52, 0x64, 0x56, 0x65, 0x74, + 0x66, 0x17, 0x67, 0x1b, 0x67, 0x56, 0x67, 0x79, + 0x6b, 0xba, 0x6b, 0x41, 0x6d, 0xdb, 0x6e, 0xcb, + 0x6e, 0x22, 0x6f, 0x1e, 0x70, 0x6e, 0x71, 0xa7, + 0x77, 0x35, 0x72, 0xaf, 0x72, 0x2a, 0x73, 0x71, + 0x74, 0x06, 0x75, 0x3b, 0x75, 0x1d, 0x76, 0x1f, + 0x76, 0xca, 0x76, 0xdb, 0x76, 0xf4, 0x76, 0x4a, + 0x77, 0x40, 0x77, 0xcc, 0x78, 0xb1, 0x7a, 0xc0, + 0x7b, 0x7b, 0x7c, 0x5b, 0x7d, 0xf4, 0x7d, 0x3e, + 0x7f, 0x05, 0x80, 0x52, 0x83, 0xef, 0x83, 0x79, + 0x87, 0x41, 0x89, 0x86, 0x89, 0x96, 0x89, 0xbf, + 0x8a, 0xf8, 0x8a, 0xcb, 0x8a, 0x01, 0x8b, 0xfe, + 0x8a, 0xed, 0x8a, 0x39, 0x8b, 0x8a, 0x8b, 0x08, + 0x8d, 0x38, 0x8f, 0x72, 0x90, 0x99, 0x91, 0x76, + 0x92, 0x7c, 0x96, 0xe3, 0x96, 0x56, 0x97, 0xdb, + 0x97, 0xff, 0x97, 0x0b, 0x98, 0x3b, 0x98, 0x12, + 0x9b, 0x9c, 0x9f, 0x4a, 0x28, 0x44, 0x28, 0xd5, + 0x33, 0x9d, 0x3b, 0x18, 0x40, 0x39, 0x40, 0x49, + 0x52, 0xd0, 0x5c, 0xd3, 0x7e, 0x43, 0x9f, 0x8e, + 0x9f, 0x2a, 0xa0, 0x02, 0x66, 0x66, 0x66, 0x69, + 0x66, 0x6c, 0x66, 0x66, 0x69, 0x66, 0x66, 0x6c, + 0x7f, 0x01, 0x74, 0x73, 0x00, 0x74, 0x65, 0x05, + 0x0f, 0x11, 0x0f, 0x00, 0x0f, 0x06, 0x19, 0x11, + 0x0f, 0x08, 0xd9, 0x05, 0xb4, 0x05, 0x00, 0x00, + 0x00, 0x00, 0xf2, 0x05, 0xb7, 0x05, 0xd0, 0x05, + 0x12, 0x00, 0x03, 0x04, 0x0b, 0x0c, 0x0d, 0x18, + 0x1a, 0xe9, 0x05, 0xc1, 0x05, 0xe9, 0x05, 0xc2, + 0x05, 0x49, 0xfb, 0xc1, 0x05, 0x49, 0xfb, 0xc2, + 0x05, 0xd0, 0x05, 0xb7, 0x05, 0xd0, 0x05, 0xb8, + 0x05, 0xd0, 0x05, 0xbc, 0x05, 0xd8, 0x05, 0xbc, + 0x05, 0xde, 0x05, 0xbc, 0x05, 0xe0, 0x05, 0xbc, + 0x05, 0xe3, 0x05, 0xbc, 0x05, 0xb9, 0x05, 0x2d, + 0x03, 0x2e, 0x03, 0x2f, 0x03, 0x30, 0x03, 0x31, + 0x03, 0x1c, 0x00, 0x18, 0x06, 0x22, 0x06, 0x2b, + 0x06, 0xd0, 0x05, 0xdc, 0x05, 0x71, 0x06, 0x00, + 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0f, 0x0f, 0x0f, 0x0f, 0x09, 0x09, 0x09, + 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x08, 0x08, + 0x08, 0x33, 0x33, 0x33, 0x33, 0x35, 0x35, 0x35, + 0x35, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, + 0x12, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x1c, 0x1c, 0x1b, 0x1b, 0x1d, 0x1d, 0x17, + 0x17, 0x27, 0x27, 0x20, 0x20, 0x38, 0x38, 0x38, + 0x38, 0x3e, 0x3e, 0x3e, 0x3e, 0x42, 0x42, 0x42, + 0x42, 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x4a, + 0x4a, 0x4a, 0x4a, 0x4f, 0x4f, 0x50, 0x50, 0x50, + 0x50, 0x4d, 0x4d, 0x4d, 0x4d, 0x61, 0x61, 0x62, + 0x62, 0x49, 0x06, 0x64, 0x64, 0x64, 0x64, 0x7e, + 0x7e, 0x7d, 0x7d, 0x7f, 0x7f, 0x2e, 0x82, 0x82, + 0x7c, 0x7c, 0x80, 0x80, 0x87, 0x87, 0x87, 0x87, + 0x00, 0x00, 0x26, 0x06, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xaf, 0x00, 0xaf, 0x00, 0x22, 0x00, 0x22, + 0x00, 0xa1, 0x00, 0xa1, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa2, 0x00, 0xa2, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0x23, 0x00, 0x23, 0x00, 0x23, + 0xcc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x26, 0x06, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x1f, 0x00, 0x23, + 0x00, 0x24, 0x02, 0x06, 0x02, 0x07, 0x02, 0x08, + 0x02, 0x1f, 0x02, 0x23, 0x02, 0x24, 0x04, 0x06, + 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x23, + 0x04, 0x24, 0x05, 0x06, 0x05, 0x1f, 0x05, 0x23, + 0x05, 0x24, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, + 0x07, 0x1f, 0x08, 0x06, 0x08, 0x07, 0x08, 0x1f, + 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, + 0x0f, 0x07, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, + 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x11, 0x1f, + 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, + 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, 0x1b, 0x08, + 0x1b, 0x1f, 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x07, + 0x1c, 0x1f, 0x1c, 0x23, 0x1c, 0x24, 0x1d, 0x01, + 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e, + 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x06, + 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x23, + 0x1e, 0x24, 0x1f, 0x06, 0x1f, 0x07, 0x1f, 0x08, + 0x1f, 0x1f, 0x1f, 0x23, 0x1f, 0x24, 0x20, 0x06, + 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, 0x23, + 0x20, 0x24, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x23, + 0x21, 0x24, 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, + 0x24, 0x1f, 0x24, 0x23, 0x24, 0x24, 0x0a, 0x4a, + 0x0b, 0x4a, 0x23, 0x4a, 0x20, 0x00, 0x4c, 0x06, + 0x51, 0x06, 0x51, 0x06, 0xff, 0x00, 0x1f, 0x26, + 0x06, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x1f, 0x00, + 0x20, 0x00, 0x23, 0x00, 0x24, 0x02, 0x0b, 0x02, + 0x0c, 0x02, 0x1f, 0x02, 0x20, 0x02, 0x23, 0x02, + 0x24, 0x04, 0x0b, 0x04, 0x0c, 0x04, 0x1f, 0x26, + 0x06, 0x04, 0x20, 0x04, 0x23, 0x04, 0x24, 0x05, + 0x0b, 0x05, 0x0c, 0x05, 0x1f, 0x05, 0x20, 0x05, + 0x23, 0x05, 0x24, 0x1b, 0x23, 0x1b, 0x24, 0x1c, + 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x1e, 0x1d, + 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x1f, 0x1e, + 0x23, 0x1e, 0x24, 0x1f, 0x01, 0x1f, 0x1f, 0x20, + 0x0b, 0x20, 0x0c, 0x20, 0x1f, 0x20, 0x20, 0x20, + 0x23, 0x20, 0x24, 0x23, 0x4a, 0x24, 0x0b, 0x24, + 0x0c, 0x24, 0x1f, 0x24, 0x20, 0x24, 0x23, 0x24, + 0x24, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x1f, 0x00, 0x21, 0x02, 0x06, 0x02, 0x07, 0x02, + 0x08, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x06, 0x04, + 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x21, 0x05, + 0x1f, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, + 0x1f, 0x08, 0x06, 0x08, 0x1f, 0x0d, 0x06, 0x0d, + 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, 0x0f, + 0x08, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, + 0x08, 0x10, 0x1f, 0x11, 0x07, 0x12, 0x1f, 0x13, + 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, 0x1b, + 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1c, + 0x07, 0x1c, 0x1f, 0x1d, 0x06, 0x1d, 0x07, 0x1d, + 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x06, 0x1e, + 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x21, 0x1f, + 0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, 0x20, + 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, + 0x21, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x4a, 0x24, + 0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, 0x24, + 0x21, 0x00, 0x1f, 0x00, 0x21, 0x02, 0x1f, 0x02, + 0x21, 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x05, + 0x21, 0x0d, 0x1f, 0x0d, 0x21, 0x0e, 0x1f, 0x0e, + 0x21, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x1f, 0x20, + 0x1f, 0x20, 0x21, 0x24, 0x1f, 0x24, 0x21, 0x40, + 0x06, 0x4e, 0x06, 0x51, 0x06, 0x27, 0x06, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0c, + 0x20, 0x0d, 0x20, 0x10, 0x1e, 0x0c, 0x05, 0x0c, + 0x06, 0x0c, 0x07, 0x0d, 0x05, 0x0d, 0x06, 0x0d, + 0x07, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x24, 0x00, + 0x24, 0x2a, 0x06, 0x00, 0x02, 0x1b, 0x00, 0x03, + 0x02, 0x00, 0x03, 0x02, 0x00, 0x03, 0x1b, 0x00, + 0x04, 0x1b, 0x00, 0x1b, 0x02, 0x00, 0x1b, 0x03, + 0x00, 0x1b, 0x04, 0x02, 0x1b, 0x03, 0x02, 0x1b, + 0x03, 0x03, 0x1b, 0x20, 0x03, 0x1b, 0x1f, 0x09, + 0x03, 0x02, 0x09, 0x02, 0x03, 0x09, 0x02, 0x1f, + 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x03, 0x09, 0x1b, + 0x02, 0x09, 0x1b, 0x1b, 0x09, 0x1b, 0x1b, 0x0b, + 0x03, 0x03, 0x0b, 0x03, 0x03, 0x0b, 0x1b, 0x1b, + 0x0a, 0x03, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, 0x02, + 0x20, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x04, 0x0a, + 0x1b, 0x1b, 0x0a, 0x1b, 0x1b, 0x0c, 0x03, 0x1f, + 0x0c, 0x04, 0x1b, 0x0c, 0x04, 0x1b, 0x0d, 0x1b, + 0x03, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, 0x1b, 0x0d, + 0x1b, 0x20, 0x0f, 0x02, 0x1b, 0x0f, 0x1b, 0x1b, + 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1f, 0x10, 0x1b, + 0x1b, 0x10, 0x1b, 0x20, 0x10, 0x1b, 0x1f, 0x17, + 0x04, 0x1b, 0x17, 0x04, 0x1b, 0x18, 0x1b, 0x03, + 0x18, 0x1b, 0x1b, 0x1a, 0x03, 0x1b, 0x1a, 0x03, + 0x20, 0x1a, 0x03, 0x1f, 0x1a, 0x02, 0x02, 0x1a, + 0x02, 0x02, 0x1a, 0x04, 0x1b, 0x1a, 0x04, 0x1b, + 0x1a, 0x1b, 0x03, 0x1a, 0x1b, 0x03, 0x1b, 0x03, + 0x02, 0x1b, 0x03, 0x1b, 0x1b, 0x03, 0x20, 0x1b, + 0x02, 0x03, 0x1b, 0x02, 0x1b, 0x1b, 0x04, 0x02, + 0x1b, 0x04, 0x1b, 0x28, 0x06, 0x1d, 0x04, 0x06, + 0x1f, 0x1d, 0x04, 0x1f, 0x1d, 0x1d, 0x1e, 0x05, + 0x1d, 0x1e, 0x05, 0x21, 0x1e, 0x04, 0x1d, 0x1e, + 0x04, 0x1d, 0x1e, 0x04, 0x21, 0x1e, 0x1d, 0x22, + 0x1e, 0x1d, 0x21, 0x22, 0x1d, 0x1d, 0x22, 0x1d, + 0x1d, 0x00, 0x06, 0x22, 0x02, 0x04, 0x22, 0x02, + 0x04, 0x21, 0x02, 0x06, 0x22, 0x02, 0x06, 0x21, + 0x02, 0x1d, 0x22, 0x02, 0x1d, 0x21, 0x04, 0x1d, + 0x22, 0x04, 0x05, 0x21, 0x04, 0x1d, 0x21, 0x0b, + 0x06, 0x21, 0x0d, 0x05, 0x22, 0x0c, 0x05, 0x22, + 0x0e, 0x05, 0x22, 0x1c, 0x04, 0x22, 0x1c, 0x1d, + 0x22, 0x22, 0x05, 0x22, 0x22, 0x04, 0x22, 0x22, + 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1a, 0x1d, 0x22, + 0x1e, 0x05, 0x22, 0x1a, 0x1d, 0x05, 0x1c, 0x05, + 0x1d, 0x11, 0x1d, 0x22, 0x1b, 0x1d, 0x22, 0x1e, + 0x04, 0x05, 0x1d, 0x06, 0x22, 0x1c, 0x04, 0x1d, + 0x1b, 0x1d, 0x1d, 0x1c, 0x04, 0x1d, 0x1e, 0x04, + 0x05, 0x04, 0x05, 0x22, 0x05, 0x04, 0x22, 0x1d, + 0x04, 0x22, 0x19, 0x1d, 0x22, 0x00, 0x05, 0x22, + 0x1b, 0x1d, 0x1d, 0x11, 0x04, 0x1d, 0x0d, 0x1d, + 0x1d, 0x0b, 0x06, 0x22, 0x1e, 0x04, 0x22, 0x35, + 0x06, 0x00, 0x0f, 0x9d, 0x0d, 0x0f, 0x9d, 0x27, + 0x06, 0x00, 0x1d, 0x1d, 0x20, 0x00, 0x1c, 0x01, + 0x0a, 0x1e, 0x06, 0x1e, 0x08, 0x0e, 0x1d, 0x12, + 0x1e, 0x0a, 0x0c, 0x21, 0x1d, 0x12, 0x1d, 0x23, + 0x20, 0x21, 0x0c, 0x1d, 0x1e, 0x35, 0x06, 0x00, + 0x0f, 0x14, 0x27, 0x06, 0x0e, 0x1d, 0x22, 0xff, + 0x00, 0x1d, 0x1d, 0x20, 0xff, 0x12, 0x1d, 0x23, + 0x20, 0xff, 0x21, 0x0c, 0x1d, 0x1e, 0x27, 0x06, + 0x05, 0x1d, 0xff, 0x05, 0x1d, 0x00, 0x1d, 0x20, + 0x27, 0x06, 0x0a, 0xa5, 0x00, 0x1d, 0x2c, 0x00, + 0x01, 0x30, 0x02, 0x30, 0x3a, 0x00, 0x3b, 0x00, + 0x21, 0x00, 0x3f, 0x00, 0x16, 0x30, 0x17, 0x30, + 0x26, 0x20, 0x13, 0x20, 0x12, 0x01, 0x00, 0x5f, + 0x5f, 0x28, 0x29, 0x7b, 0x7d, 0x08, 0x30, 0x0c, + 0x0d, 0x08, 0x09, 0x02, 0x03, 0x00, 0x01, 0x04, + 0x05, 0x06, 0x07, 0x5b, 0x00, 0x5d, 0x00, 0x3e, + 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x5f, + 0x00, 0x5f, 0x00, 0x5f, 0x00, 0x2c, 0x00, 0x01, + 0x30, 0x2e, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x3a, + 0x00, 0x3f, 0x00, 0x21, 0x00, 0x14, 0x20, 0x28, + 0x00, 0x29, 0x00, 0x7b, 0x00, 0x7d, 0x00, 0x14, + 0x30, 0x15, 0x30, 0x23, 0x26, 0x2a, 0x2b, 0x2d, + 0x3c, 0x3e, 0x3d, 0x00, 0x5c, 0x24, 0x25, 0x40, + 0x40, 0x06, 0xff, 0x0b, 0x00, 0x0b, 0xff, 0x0c, + 0x20, 0x00, 0x4d, 0x06, 0x40, 0x06, 0xff, 0x0e, + 0x00, 0x0e, 0xff, 0x0f, 0x00, 0x0f, 0xff, 0x10, + 0x00, 0x10, 0xff, 0x11, 0x00, 0x11, 0xff, 0x12, + 0x00, 0x12, 0x21, 0x06, 0x00, 0x01, 0x01, 0x02, + 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x05, + 0x05, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, + 0x08, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, + 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x12, + 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, + 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, + 0x18, 0x19, 0x19, 0x19, 0x19, 0x20, 0x20, 0x20, + 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, + 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, + 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, + 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x29, + 0x29, 0x22, 0x06, 0x22, 0x00, 0x22, 0x00, 0x22, + 0x01, 0x22, 0x01, 0x22, 0x03, 0x22, 0x03, 0x22, + 0x05, 0x22, 0x05, 0x21, 0x00, 0x85, 0x29, 0x01, + 0x30, 0x01, 0x0b, 0x0c, 0x00, 0xfa, 0xf1, 0xa0, + 0xa2, 0xa4, 0xa6, 0xa8, 0xe2, 0xe4, 0xe6, 0xc2, + 0xfb, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, 0xac, + 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, + 0xbe, 0xc0, 0xc3, 0xc5, 0xc7, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xd1, 0xd4, 0xd7, 0xda, 0xdd, + 0xde, 0xdf, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, 0xe8, + 0xe9, 0xea, 0xeb, 0xec, 0xee, 0xf2, 0x98, 0x99, + 0x31, 0x31, 0x4f, 0x31, 0x55, 0x31, 0x5b, 0x31, + 0x61, 0x31, 0xa2, 0x00, 0xa3, 0x00, 0xac, 0x00, + 0xaf, 0x00, 0xa6, 0x00, 0xa5, 0x00, 0xa9, 0x20, + 0x00, 0x00, 0x02, 0x25, 0x90, 0x21, 0x91, 0x21, + 0x92, 0x21, 0x93, 0x21, 0xa0, 0x25, 0xcb, 0x25, + 0xd2, 0x05, 0x07, 0x03, 0x01, 0xda, 0x05, 0x07, + 0x03, 0x01, 0xd0, 0x02, 0xd1, 0x02, 0xe6, 0x00, + 0x99, 0x02, 0x53, 0x02, 0x00, 0x00, 0xa3, 0x02, + 0x66, 0xab, 0xa5, 0x02, 0xa4, 0x02, 0x56, 0x02, + 0x57, 0x02, 0x91, 0x1d, 0x58, 0x02, 0x5e, 0x02, + 0xa9, 0x02, 0x64, 0x02, 0x62, 0x02, 0x60, 0x02, + 0x9b, 0x02, 0x27, 0x01, 0x9c, 0x02, 0x67, 0x02, + 0x84, 0x02, 0xaa, 0x02, 0xab, 0x02, 0x6c, 0x02, + 0x04, 0xdf, 0x8e, 0xa7, 0x6e, 0x02, 0x05, 0xdf, + 0x8e, 0x02, 0x06, 0xdf, 0xf8, 0x00, 0x76, 0x02, + 0x77, 0x02, 0x71, 0x00, 0x7a, 0x02, 0x08, 0xdf, + 0x7d, 0x02, 0x7e, 0x02, 0x80, 0x02, 0xa8, 0x02, + 0xa6, 0x02, 0x67, 0xab, 0xa7, 0x02, 0x88, 0x02, + 0x71, 0x2c, 0x00, 0x00, 0x8f, 0x02, 0xa1, 0x02, + 0xa2, 0x02, 0x98, 0x02, 0xc0, 0x01, 0xc1, 0x01, + 0xc2, 0x01, 0x0a, 0xdf, 0x1e, 0xdf, 0x41, 0x04, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x14, 0x99, 0x10, + 0xba, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, + 0xba, 0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, + 0x05, 0x31, 0x11, 0x27, 0x11, 0x32, 0x11, 0x27, + 0x11, 0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, + 0x57, 0x13, 0x55, 0x82, 0x13, 0xc9, 0x13, 0x00, + 0x00, 0x00, 0x00, 0x84, 0x13, 0xbb, 0x13, 0x05, + 0x05, 0x8b, 0x13, 0xc2, 0x13, 0x05, 0x90, 0x13, + 0xc9, 0x13, 0x05, 0xc2, 0x13, 0xc2, 0x13, 0x00, + 0x00, 0x00, 0x00, 0xc2, 0x13, 0xb8, 0x13, 0xc2, + 0x13, 0xc9, 0x13, 0x05, 0x55, 0xb9, 0x14, 0xba, + 0x14, 0xb9, 0x14, 0xb0, 0x14, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x14, 0xbd, 0x14, 0x55, 0x50, 0xb8, + 0x15, 0xaf, 0x15, 0xb9, 0x15, 0xaf, 0x15, 0x55, + 0x35, 0x19, 0x30, 0x19, 0x05, 0x1e, 0x61, 0x1e, + 0x61, 0x1e, 0x61, 0x29, 0x61, 0x1e, 0x61, 0x1f, + 0x61, 0x29, 0x61, 0x1f, 0x61, 0x1e, 0x61, 0x20, + 0x61, 0x21, 0x61, 0x1f, 0x61, 0x22, 0x61, 0x1f, + 0x61, 0x21, 0x61, 0x20, 0x61, 0x55, 0x55, 0x55, + 0x55, 0x67, 0x6d, 0x67, 0x6d, 0x63, 0x6d, 0x67, + 0x6d, 0x69, 0x6d, 0x67, 0x6d, 0x55, 0x05, 0x41, + 0x00, 0x30, 0x00, 0x57, 0xd1, 0x65, 0xd1, 0x58, + 0xd1, 0x65, 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, 0x5f, + 0xd1, 0x6f, 0xd1, 0x5f, 0xd1, 0x70, 0xd1, 0x5f, + 0xd1, 0x71, 0xd1, 0x5f, 0xd1, 0x72, 0xd1, 0x55, + 0x55, 0x55, 0x05, 0xb9, 0xd1, 0x65, 0xd1, 0xba, + 0xd1, 0x65, 0xd1, 0xbb, 0xd1, 0x6e, 0xd1, 0xbc, + 0xd1, 0x6e, 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, 0xbc, + 0xd1, 0x6f, 0xd1, 0x55, 0x55, 0x55, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x69, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x43, 0x44, + 0x00, 0x00, 0x47, 0x00, 0x00, 0x4a, 0x4b, 0x00, + 0x00, 0x4e, 0x4f, 0x50, 0x51, 0x00, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, + 0x63, 0x64, 0x00, 0x66, 0x68, 0x00, 0x70, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, + 0x45, 0x46, 0x47, 0x4a, 0x00, 0x53, 0x00, 0x61, + 0x00, 0x41, 0x42, 0x00, 0x44, 0x45, 0x46, 0x47, + 0x00, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x00, 0x4f, + 0x53, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x31, 0x01, 0x37, 0x02, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, + 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, + 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, + 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, + 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, + 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x0b, 0x0c, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x04, 0x3a, 0x04, 0x3e, 0x04, + 0x4b, 0x04, 0x4d, 0x04, 0x4e, 0x04, 0x89, 0xa6, + 0x30, 0x04, 0xa9, 0x26, 0x28, 0xb9, 0x7f, 0x9f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x0a, 0x0b, 0x0e, 0x0f, 0x11, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x61, 0x26, + 0x25, 0x2f, 0x7b, 0x51, 0xa6, 0xb1, 0x04, 0x27, + 0x06, 0x00, 0x01, 0x05, 0x08, 0x2a, 0x06, 0x1e, + 0x08, 0x03, 0x0d, 0x20, 0x19, 0x1a, 0x1b, 0x1c, + 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, + 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x44, 0x90, + 0x77, 0x45, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, + 0x47, 0x06, 0x33, 0x06, 0x17, 0x10, 0x11, 0x12, + 0x13, 0x00, 0x06, 0x0e, 0x02, 0x0f, 0x34, 0x06, + 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, + 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, 0x2d, 0x06, + 0x00, 0x00, 0x4a, 0x06, 0x00, 0x00, 0x44, 0x06, + 0x00, 0x00, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, + 0x00, 0x00, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, + 0x34, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x06, + 0x00, 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, + 0x00, 0x00, 0xba, 0x06, 0x00, 0x00, 0x6f, 0x06, + 0x00, 0x00, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, + 0x47, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x06, + 0x37, 0x06, 0x4a, 0x06, 0x43, 0x06, 0x00, 0x00, + 0x45, 0x06, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, + 0x41, 0x06, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, + 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, + 0x00, 0x00, 0x36, 0x06, 0x38, 0x06, 0x3a, 0x06, + 0x6e, 0x06, 0x00, 0x00, 0xa1, 0x06, 0x27, 0x06, + 0x00, 0x01, 0x05, 0x08, 0x20, 0x21, 0x0b, 0x06, + 0x10, 0x23, 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, + 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, + 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x28, 0x06, 0x2c, + 0x06, 0x2f, 0x06, 0x00, 0x00, 0x48, 0x06, 0x32, + 0x06, 0x2d, 0x06, 0x37, 0x06, 0x4a, 0x06, 0x2a, + 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, + 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, 0x0c, + 0x0e, 0x10, 0x30, 0x2e, 0x30, 0x00, 0x2c, 0x00, + 0x28, 0x00, 0x41, 0x00, 0x29, 0x00, 0x14, 0x30, + 0x53, 0x00, 0x15, 0x30, 0x43, 0x52, 0x43, 0x44, + 0x57, 0x5a, 0x41, 0x00, 0x48, 0x56, 0x4d, 0x56, + 0x53, 0x44, 0x53, 0x53, 0x50, 0x50, 0x56, 0x57, + 0x43, 0x4d, 0x43, 0x4d, 0x44, 0x4d, 0x52, 0x44, + 0x4a, 0x4b, 0x30, 0x30, 0x00, 0x68, 0x68, 0x4b, + 0x62, 0x57, 0x5b, 0xcc, 0x53, 0xc7, 0x30, 0x8c, + 0x4e, 0x1a, 0x59, 0xe3, 0x89, 0x29, 0x59, 0xa4, + 0x4e, 0x20, 0x66, 0x21, 0x71, 0x99, 0x65, 0x4d, + 0x52, 0x8c, 0x5f, 0x8d, 0x51, 0xb0, 0x65, 0x1d, + 0x52, 0x42, 0x7d, 0x1f, 0x75, 0xa9, 0x8c, 0xf0, + 0x58, 0x39, 0x54, 0x14, 0x6f, 0x95, 0x62, 0x55, + 0x63, 0x00, 0x4e, 0x09, 0x4e, 0x4a, 0x90, 0xe6, + 0x5d, 0x2d, 0x4e, 0xf3, 0x53, 0x07, 0x63, 0x70, + 0x8d, 0x53, 0x62, 0x81, 0x79, 0x7a, 0x7a, 0x08, + 0x54, 0x80, 0x6e, 0x09, 0x67, 0x08, 0x67, 0x33, + 0x75, 0x72, 0x52, 0xb6, 0x55, 0x4d, 0x91, 0x14, + 0x30, 0x15, 0x30, 0x2c, 0x67, 0x09, 0x4e, 0x8c, + 0x4e, 0x89, 0x5b, 0xb9, 0x70, 0x53, 0x62, 0xd7, + 0x76, 0xdd, 0x52, 0x57, 0x65, 0x97, 0x5f, 0xef, + 0x53, 0x30, 0x00, 0x38, 0x4e, 0x05, 0x00, 0x09, + 0x22, 0x01, 0x60, 0x4f, 0xae, 0x4f, 0xbb, 0x4f, + 0x02, 0x50, 0x7a, 0x50, 0x99, 0x50, 0xe7, 0x50, + 0xcf, 0x50, 0x9e, 0x34, 0x3a, 0x06, 0x4d, 0x51, + 0x54, 0x51, 0x64, 0x51, 0x77, 0x51, 0x1c, 0x05, + 0xb9, 0x34, 0x67, 0x51, 0x8d, 0x51, 0x4b, 0x05, + 0x97, 0x51, 0xa4, 0x51, 0xcc, 0x4e, 0xac, 0x51, + 0xb5, 0x51, 0xdf, 0x91, 0xf5, 0x51, 0x03, 0x52, + 0xdf, 0x34, 0x3b, 0x52, 0x46, 0x52, 0x72, 0x52, + 0x77, 0x52, 0x15, 0x35, 0x02, 0x00, 0x20, 0x80, + 0x80, 0x00, 0x08, 0x00, 0x00, 0xc7, 0x52, 0x00, + 0x02, 0x1d, 0x33, 0x3e, 0x3f, 0x50, 0x82, 0x8a, + 0x93, 0xac, 0xb6, 0xb8, 0xb8, 0xb8, 0x2c, 0x0a, + 0x70, 0x70, 0xca, 0x53, 0xdf, 0x53, 0x63, 0x0b, + 0xeb, 0x53, 0xf1, 0x53, 0x06, 0x54, 0x9e, 0x54, + 0x38, 0x54, 0x48, 0x54, 0x68, 0x54, 0xa2, 0x54, + 0xf6, 0x54, 0x10, 0x55, 0x53, 0x55, 0x63, 0x55, + 0x84, 0x55, 0x84, 0x55, 0x99, 0x55, 0xab, 0x55, + 0xb3, 0x55, 0xc2, 0x55, 0x16, 0x57, 0x06, 0x56, + 0x17, 0x57, 0x51, 0x56, 0x74, 0x56, 0x07, 0x52, + 0xee, 0x58, 0xce, 0x57, 0xf4, 0x57, 0x0d, 0x58, + 0x8b, 0x57, 0x32, 0x58, 0x31, 0x58, 0xac, 0x58, + 0xe4, 0x14, 0xf2, 0x58, 0xf7, 0x58, 0x06, 0x59, + 0x1a, 0x59, 0x22, 0x59, 0x62, 0x59, 0xa8, 0x16, + 0xea, 0x16, 0xec, 0x59, 0x1b, 0x5a, 0x27, 0x5a, + 0xd8, 0x59, 0x66, 0x5a, 0xee, 0x36, 0xfc, 0x36, + 0x08, 0x5b, 0x3e, 0x5b, 0x3e, 0x5b, 0xc8, 0x19, + 0xc3, 0x5b, 0xd8, 0x5b, 0xe7, 0x5b, 0xf3, 0x5b, + 0x18, 0x1b, 0xff, 0x5b, 0x06, 0x5c, 0x53, 0x5f, + 0x22, 0x5c, 0x81, 0x37, 0x60, 0x5c, 0x6e, 0x5c, + 0xc0, 0x5c, 0x8d, 0x5c, 0xe4, 0x1d, 0x43, 0x5d, + 0xe6, 0x1d, 0x6e, 0x5d, 0x6b, 0x5d, 0x7c, 0x5d, + 0xe1, 0x5d, 0xe2, 0x5d, 0x2f, 0x38, 0xfd, 0x5d, + 0x28, 0x5e, 0x3d, 0x5e, 0x69, 0x5e, 0x62, 0x38, + 0x83, 0x21, 0x7c, 0x38, 0xb0, 0x5e, 0xb3, 0x5e, + 0xb6, 0x5e, 0xca, 0x5e, 0x92, 0xa3, 0xfe, 0x5e, + 0x31, 0x23, 0x31, 0x23, 0x01, 0x82, 0x22, 0x5f, + 0x22, 0x5f, 0xc7, 0x38, 0xb8, 0x32, 0xda, 0x61, + 0x62, 0x5f, 0x6b, 0x5f, 0xe3, 0x38, 0x9a, 0x5f, + 0xcd, 0x5f, 0xd7, 0x5f, 0xf9, 0x5f, 0x81, 0x60, + 0x3a, 0x39, 0x1c, 0x39, 0x94, 0x60, 0xd4, 0x26, + 0xc7, 0x60, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, + 0x02, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x08, + 0x80, 0x28, 0x80, 0x02, 0x00, 0x00, 0x02, 0x48, + 0x61, 0x00, 0x04, 0x06, 0x04, 0x32, 0x46, 0x6a, + 0x5c, 0x67, 0x96, 0xaa, 0xae, 0xc8, 0xd3, 0x5d, + 0x62, 0x00, 0x54, 0x77, 0xf3, 0x0c, 0x2b, 0x3d, + 0x63, 0xfc, 0x62, 0x68, 0x63, 0x83, 0x63, 0xe4, + 0x63, 0xf1, 0x2b, 0x22, 0x64, 0xc5, 0x63, 0xa9, + 0x63, 0x2e, 0x3a, 0x69, 0x64, 0x7e, 0x64, 0x9d, + 0x64, 0x77, 0x64, 0x6c, 0x3a, 0x4f, 0x65, 0x6c, + 0x65, 0x0a, 0x30, 0xe3, 0x65, 0xf8, 0x66, 0x49, + 0x66, 0x19, 0x3b, 0x91, 0x66, 0x08, 0x3b, 0xe4, + 0x3a, 0x92, 0x51, 0x95, 0x51, 0x00, 0x67, 0x9c, + 0x66, 0xad, 0x80, 0xd9, 0x43, 0x17, 0x67, 0x1b, + 0x67, 0x21, 0x67, 0x5e, 0x67, 0x53, 0x67, 0xc3, + 0x33, 0x49, 0x3b, 0xfa, 0x67, 0x85, 0x67, 0x52, + 0x68, 0x85, 0x68, 0x6d, 0x34, 0x8e, 0x68, 0x1f, + 0x68, 0x14, 0x69, 0x9d, 0x3b, 0x42, 0x69, 0xa3, + 0x69, 0xea, 0x69, 0xa8, 0x6a, 0xa3, 0x36, 0xdb, + 0x6a, 0x18, 0x3c, 0x21, 0x6b, 0xa7, 0x38, 0x54, + 0x6b, 0x4e, 0x3c, 0x72, 0x6b, 0x9f, 0x6b, 0xba, + 0x6b, 0xbb, 0x6b, 0x8d, 0x3a, 0x0b, 0x1d, 0xfa, + 0x3a, 0x4e, 0x6c, 0xbc, 0x3c, 0xbf, 0x6c, 0xcd, + 0x6c, 0x67, 0x6c, 0x16, 0x6d, 0x3e, 0x6d, 0x77, + 0x6d, 0x41, 0x6d, 0x69, 0x6d, 0x78, 0x6d, 0x85, + 0x6d, 0x1e, 0x3d, 0x34, 0x6d, 0x2f, 0x6e, 0x6e, + 0x6e, 0x33, 0x3d, 0xcb, 0x6e, 0xc7, 0x6e, 0xd1, + 0x3e, 0xf9, 0x6d, 0x6e, 0x6f, 0x5e, 0x3f, 0x8e, + 0x3f, 0xc6, 0x6f, 0x39, 0x70, 0x1e, 0x70, 0x1b, + 0x70, 0x96, 0x3d, 0x4a, 0x70, 0x7d, 0x70, 0x77, + 0x70, 0xad, 0x70, 0x25, 0x05, 0x45, 0x71, 0x63, + 0x42, 0x9c, 0x71, 0xab, 0x43, 0x28, 0x72, 0x35, + 0x72, 0x50, 0x72, 0x08, 0x46, 0x80, 0x72, 0x95, + 0x72, 0x35, 0x47, 0x02, 0x20, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, + 0x02, 0x02, 0x80, 0x8a, 0x00, 0x00, 0x20, 0x00, + 0x08, 0x0a, 0x00, 0x80, 0x88, 0x80, 0x20, 0x14, + 0x48, 0x7a, 0x73, 0x8b, 0x73, 0xac, 0x3e, 0xa5, + 0x73, 0xb8, 0x3e, 0xb8, 0x3e, 0x47, 0x74, 0x5c, + 0x74, 0x71, 0x74, 0x85, 0x74, 0xca, 0x74, 0x1b, + 0x3f, 0x24, 0x75, 0x36, 0x4c, 0x3e, 0x75, 0x92, + 0x4c, 0x70, 0x75, 0x9f, 0x21, 0x10, 0x76, 0xa1, + 0x4f, 0xb8, 0x4f, 0x44, 0x50, 0xfc, 0x3f, 0x08, + 0x40, 0xf4, 0x76, 0xf3, 0x50, 0xf2, 0x50, 0x19, + 0x51, 0x33, 0x51, 0x1e, 0x77, 0x1f, 0x77, 0x1f, + 0x77, 0x4a, 0x77, 0x39, 0x40, 0x8b, 0x77, 0x46, + 0x40, 0x96, 0x40, 0x1d, 0x54, 0x4e, 0x78, 0x8c, + 0x78, 0xcc, 0x78, 0xe3, 0x40, 0x26, 0x56, 0x56, + 0x79, 0x9a, 0x56, 0xc5, 0x56, 0x8f, 0x79, 0xeb, + 0x79, 0x2f, 0x41, 0x40, 0x7a, 0x4a, 0x7a, 0x4f, + 0x7a, 0x7c, 0x59, 0xa7, 0x5a, 0xa7, 0x5a, 0xee, + 0x7a, 0x02, 0x42, 0xab, 0x5b, 0xc6, 0x7b, 0xc9, + 0x7b, 0x27, 0x42, 0x80, 0x5c, 0xd2, 0x7c, 0xa0, + 0x42, 0xe8, 0x7c, 0xe3, 0x7c, 0x00, 0x7d, 0x86, + 0x5f, 0x63, 0x7d, 0x01, 0x43, 0xc7, 0x7d, 0x02, + 0x7e, 0x45, 0x7e, 0x34, 0x43, 0x28, 0x62, 0x47, + 0x62, 0x59, 0x43, 0xd9, 0x62, 0x7a, 0x7f, 0x3e, + 0x63, 0x95, 0x7f, 0xfa, 0x7f, 0x05, 0x80, 0xda, + 0x64, 0x23, 0x65, 0x60, 0x80, 0xa8, 0x65, 0x70, + 0x80, 0x5f, 0x33, 0xd5, 0x43, 0xb2, 0x80, 0x03, + 0x81, 0x0b, 0x44, 0x3e, 0x81, 0xb5, 0x5a, 0xa7, + 0x67, 0xb5, 0x67, 0x93, 0x33, 0x9c, 0x33, 0x01, + 0x82, 0x04, 0x82, 0x9e, 0x8f, 0x6b, 0x44, 0x91, + 0x82, 0x8b, 0x82, 0x9d, 0x82, 0xb3, 0x52, 0xb1, + 0x82, 0xb3, 0x82, 0xbd, 0x82, 0xe6, 0x82, 0x3c, + 0x6b, 0xe5, 0x82, 0x1d, 0x83, 0x63, 0x83, 0xad, + 0x83, 0x23, 0x83, 0xbd, 0x83, 0xe7, 0x83, 0x57, + 0x84, 0x53, 0x83, 0xca, 0x83, 0xcc, 0x83, 0xdc, + 0x83, 0x36, 0x6c, 0x6b, 0x6d, 0x02, 0x00, 0x00, + 0x20, 0x22, 0x2a, 0xa0, 0x0a, 0x00, 0x20, 0x80, + 0x28, 0x00, 0xa8, 0x20, 0x20, 0x00, 0x02, 0x80, + 0x22, 0x02, 0x8a, 0x08, 0x00, 0xaa, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x28, 0xd5, 0x6c, 0x2b, + 0x45, 0xf1, 0x84, 0xf3, 0x84, 0x16, 0x85, 0xca, + 0x73, 0x64, 0x85, 0x2c, 0x6f, 0x5d, 0x45, 0x61, + 0x45, 0xb1, 0x6f, 0xd2, 0x70, 0x6b, 0x45, 0x50, + 0x86, 0x5c, 0x86, 0x67, 0x86, 0x69, 0x86, 0xa9, + 0x86, 0x88, 0x86, 0x0e, 0x87, 0xe2, 0x86, 0x79, + 0x87, 0x28, 0x87, 0x6b, 0x87, 0x86, 0x87, 0xd7, + 0x45, 0xe1, 0x87, 0x01, 0x88, 0xf9, 0x45, 0x60, + 0x88, 0x63, 0x88, 0x67, 0x76, 0xd7, 0x88, 0xde, + 0x88, 0x35, 0x46, 0xfa, 0x88, 0xbb, 0x34, 0xae, + 0x78, 0x66, 0x79, 0xbe, 0x46, 0xc7, 0x46, 0xa0, + 0x8a, 0xed, 0x8a, 0x8a, 0x8b, 0x55, 0x8c, 0xa8, + 0x7c, 0xab, 0x8c, 0xc1, 0x8c, 0x1b, 0x8d, 0x77, + 0x8d, 0x2f, 0x7f, 0x04, 0x08, 0xcb, 0x8d, 0xbc, + 0x8d, 0xf0, 0x8d, 0xde, 0x08, 0xd4, 0x8e, 0x38, + 0x8f, 0xd2, 0x85, 0xed, 0x85, 0x94, 0x90, 0xf1, + 0x90, 0x11, 0x91, 0x2e, 0x87, 0x1b, 0x91, 0x38, + 0x92, 0xd7, 0x92, 0xd8, 0x92, 0x7c, 0x92, 0xf9, + 0x93, 0x15, 0x94, 0xfa, 0x8b, 0x8b, 0x95, 0x95, + 0x49, 0xb7, 0x95, 0x77, 0x8d, 0xe6, 0x49, 0xc3, + 0x96, 0xb2, 0x5d, 0x23, 0x97, 0x45, 0x91, 0x1a, + 0x92, 0x6e, 0x4a, 0x76, 0x4a, 0xe0, 0x97, 0x0a, + 0x94, 0xb2, 0x4a, 0x96, 0x94, 0x0b, 0x98, 0x0b, + 0x98, 0x29, 0x98, 0xb6, 0x95, 0xe2, 0x98, 0x33, + 0x4b, 0x29, 0x99, 0xa7, 0x99, 0xc2, 0x99, 0xfe, + 0x99, 0xce, 0x4b, 0x30, 0x9b, 0x12, 0x9b, 0x40, + 0x9c, 0xfd, 0x9c, 0xce, 0x4c, 0xed, 0x4c, 0x67, + 0x9d, 0xce, 0xa0, 0xf8, 0x4c, 0x05, 0xa1, 0x0e, + 0xa2, 0x91, 0xa2, 0xbb, 0x9e, 0x56, 0x4d, 0xf9, + 0x9e, 0xfe, 0x9e, 0x05, 0x9f, 0x0f, 0x9f, 0x16, + 0x9f, 0x3b, 0x9f, 0x00, 0xa6, 0x02, 0x88, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x28, 0x00, + 0x08, 0xa0, 0x80, 0xa0, 0x80, 0x00, 0x80, 0x80, + 0x00, 0x0a, 0x88, 0x80, 0x00, 0x80, 0x00, 0x20, + 0x2a, 0x00, 0x80, +}; + +static const uint16_t unicode_comp_table[965] = { + 0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0, + 0x02c2, 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982, + 0x2440, 0x2280, 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8, + 0x02ca, 0x02cc, 0x0287, 0x228a, 0x02ce, 0x228c, 0x2290, 0x2292, + 0x228e, 0x0288, 0x0289, 0x028a, 0x2482, 0x0300, 0x0302, 0x0304, + 0x028b, 0x2480, 0x0308, 0x0984, 0x0986, 0x2458, 0x0a02, 0x0306, + 0x2298, 0x229a, 0x229e, 0x0900, 0x030a, 0x22a0, 0x030c, 0x030e, + 0x0840, 0x0310, 0x0312, 0x22a2, 0x22a6, 0x09c0, 0x22a4, 0x22a8, + 0x22aa, 0x028c, 0x028d, 0x028e, 0x0340, 0x0342, 0x0344, 0x0380, + 0x028f, 0x248e, 0x07c2, 0x0988, 0x098a, 0x2490, 0x0346, 0x22ac, + 0x0400, 0x22b0, 0x0842, 0x22b2, 0x0402, 0x22b4, 0x0440, 0x0444, + 0x22b6, 0x0442, 0x22c2, 0x22c0, 0x22c4, 0x22c6, 0x22c8, 0x0940, + 0x04c0, 0x0291, 0x22ca, 0x04c4, 0x22cc, 0x04c2, 0x22d0, 0x22ce, + 0x0292, 0x0293, 0x0294, 0x0295, 0x0540, 0x0542, 0x0a08, 0x0296, + 0x2494, 0x0544, 0x07c4, 0x098c, 0x098e, 0x06c0, 0x2492, 0x0844, + 0x2308, 0x230a, 0x0580, 0x230c, 0x0584, 0x0990, 0x0992, 0x230e, + 0x0582, 0x2312, 0x0586, 0x0588, 0x2314, 0x058c, 0x2316, 0x0998, + 0x058a, 0x231e, 0x0590, 0x2320, 0x099a, 0x058e, 0x2324, 0x2322, + 0x0299, 0x029a, 0x029b, 0x05c0, 0x05c2, 0x05c4, 0x029c, 0x24ac, + 0x05c6, 0x05c8, 0x07c6, 0x0994, 0x0996, 0x0700, 0x24aa, 0x2326, + 0x05ca, 0x232a, 0x2328, 0x2340, 0x2342, 0x2344, 0x2346, 0x05cc, + 0x234a, 0x2348, 0x234c, 0x234e, 0x2350, 0x24b8, 0x029d, 0x05ce, + 0x24be, 0x0a0c, 0x2352, 0x0600, 0x24bc, 0x24ba, 0x0640, 0x2354, + 0x0642, 0x0644, 0x2356, 0x2358, 0x02a0, 0x02a1, 0x02a2, 0x02a3, + 0x02c1, 0x02c3, 0x0a01, 0x02a4, 0x2443, 0x02a5, 0x07c1, 0x0981, + 0x0983, 0x2441, 0x2281, 0x02c5, 0x2283, 0x2285, 0x2287, 0x02c7, + 0x02c9, 0x02cb, 0x02cd, 0x02a7, 0x228b, 0x02cf, 0x228d, 0x2291, + 0x2293, 0x228f, 0x02a8, 0x02a9, 0x02aa, 0x2483, 0x0301, 0x0303, + 0x0305, 0x02ab, 0x2481, 0x0309, 0x0985, 0x0987, 0x2459, 0x0a03, + 0x0307, 0x2299, 0x229b, 0x229f, 0x0901, 0x030b, 0x22a1, 0x030d, + 0x030f, 0x0841, 0x0311, 0x0313, 0x22a3, 0x22a7, 0x09c1, 0x22a5, + 0x22a9, 0x22ab, 0x2380, 0x02ac, 0x02ad, 0x02ae, 0x0341, 0x0343, + 0x0345, 0x02af, 0x248f, 0x07c3, 0x0989, 0x098b, 0x2491, 0x0347, + 0x22ad, 0x0401, 0x0884, 0x22b1, 0x0843, 0x22b3, 0x0403, 0x22b5, + 0x0441, 0x0445, 0x22b7, 0x0443, 0x22c3, 0x22c1, 0x22c5, 0x22c7, + 0x22c9, 0x0941, 0x04c1, 0x02b1, 0x22cb, 0x04c5, 0x22cd, 0x04c3, + 0x22d1, 0x22cf, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x0541, 0x0543, + 0x0a09, 0x02b6, 0x2495, 0x0545, 0x07c5, 0x098d, 0x098f, 0x06c1, + 0x2493, 0x0845, 0x2309, 0x230b, 0x0581, 0x230d, 0x0585, 0x0991, + 0x0993, 0x230f, 0x0583, 0x2313, 0x0587, 0x0589, 0x2315, 0x058d, + 0x2317, 0x0999, 0x058b, 0x231f, 0x2381, 0x0591, 0x2321, 0x099b, + 0x058f, 0x2325, 0x2323, 0x02b9, 0x02ba, 0x02bb, 0x05c1, 0x05c3, + 0x05c5, 0x02bc, 0x24ad, 0x05c7, 0x05c9, 0x07c7, 0x0995, 0x0997, + 0x0701, 0x24ab, 0x2327, 0x05cb, 0x232b, 0x2329, 0x2341, 0x2343, + 0x2345, 0x2347, 0x05cd, 0x234b, 0x2349, 0x2382, 0x234d, 0x234f, + 0x2351, 0x24b9, 0x02bd, 0x05cf, 0x24bf, 0x0a0d, 0x2353, 0x02bf, + 0x24bd, 0x2383, 0x24bb, 0x0641, 0x2355, 0x0643, 0x0645, 0x2357, + 0x2359, 0x3101, 0x0c80, 0x2e00, 0x2446, 0x2444, 0x244a, 0x2448, + 0x0800, 0x0942, 0x0944, 0x0804, 0x2288, 0x2486, 0x2484, 0x248a, + 0x2488, 0x22ae, 0x2498, 0x2496, 0x249c, 0x249a, 0x2300, 0x0a06, + 0x2302, 0x0a04, 0x0946, 0x07ce, 0x07ca, 0x07c8, 0x07cc, 0x2447, + 0x2445, 0x244b, 0x2449, 0x0801, 0x0943, 0x0945, 0x0805, 0x2289, + 0x2487, 0x2485, 0x248b, 0x2489, 0x22af, 0x2499, 0x2497, 0x249d, + 0x249b, 0x2301, 0x0a07, 0x2303, 0x0a05, 0x0947, 0x07cf, 0x07cb, + 0x07c9, 0x07cd, 0x2450, 0x244e, 0x2454, 0x2452, 0x2451, 0x244f, + 0x2455, 0x2453, 0x2294, 0x2296, 0x2295, 0x2297, 0x2304, 0x2306, + 0x2305, 0x2307, 0x2318, 0x2319, 0x231a, 0x231b, 0x232c, 0x232d, + 0x232e, 0x232f, 0x2400, 0x24a2, 0x24a0, 0x24a6, 0x24a4, 0x24a8, + 0x24a3, 0x24a1, 0x24a7, 0x24a5, 0x24a9, 0x24b0, 0x24ae, 0x24b4, + 0x24b2, 0x24b6, 0x24b1, 0x24af, 0x24b5, 0x24b3, 0x24b7, 0x0882, + 0x0880, 0x0881, 0x0802, 0x0803, 0x229c, 0x229d, 0x0a0a, 0x0a0b, + 0x0883, 0x0b40, 0x2c8a, 0x0c81, 0x2c89, 0x2c88, 0x2540, 0x2541, + 0x2d00, 0x2e07, 0x0d00, 0x2640, 0x2641, 0x2e80, 0x0d01, 0x26c8, + 0x26c9, 0x2f00, 0x2f84, 0x0d02, 0x2f83, 0x2f82, 0x0d40, 0x26d8, + 0x26d9, 0x3186, 0x0d04, 0x2740, 0x2741, 0x3100, 0x3086, 0x0d06, + 0x3085, 0x3084, 0x0d41, 0x2840, 0x3200, 0x0d07, 0x284f, 0x2850, + 0x3280, 0x2c84, 0x2e03, 0x2857, 0x0d42, 0x2c81, 0x2c80, 0x24c0, + 0x24c1, 0x2c86, 0x2c83, 0x28c0, 0x0d43, 0x25c0, 0x25c1, 0x2940, + 0x0d44, 0x26c0, 0x26c1, 0x2e05, 0x2e02, 0x29c0, 0x0d45, 0x2f05, + 0x2f04, 0x0d80, 0x26d0, 0x26d1, 0x2f80, 0x2a40, 0x0d82, 0x26e0, + 0x26e1, 0x3080, 0x3081, 0x2ac0, 0x0d83, 0x3004, 0x3003, 0x0d81, + 0x27c0, 0x27c1, 0x3082, 0x2b40, 0x0d84, 0x2847, 0x2848, 0x3184, + 0x3181, 0x2f06, 0x0d08, 0x2f81, 0x3005, 0x0d46, 0x3083, 0x3182, + 0x0e00, 0x0e01, 0x0f40, 0x1180, 0x1182, 0x0f03, 0x0f00, 0x11c0, + 0x0f01, 0x1140, 0x1202, 0x1204, 0x0f81, 0x1240, 0x0fc0, 0x1242, + 0x0f80, 0x1244, 0x1284, 0x0f82, 0x1286, 0x1288, 0x128a, 0x12c0, + 0x1282, 0x1181, 0x1183, 0x1043, 0x1040, 0x11c1, 0x1041, 0x1141, + 0x1203, 0x1205, 0x10c1, 0x1241, 0x1000, 0x1243, 0x10c0, 0x1245, + 0x1285, 0x10c2, 0x1287, 0x1289, 0x128b, 0x12c1, 0x1283, 0x1080, + 0x1100, 0x1101, 0x1200, 0x1201, 0x1280, 0x1281, 0x1340, 0x1341, + 0x1343, 0x1342, 0x1344, 0x13c2, 0x1400, 0x13c0, 0x1440, 0x1480, + 0x14c0, 0x1540, 0x1541, 0x1740, 0x1700, 0x1741, 0x17c0, 0x1800, + 0x1802, 0x1801, 0x1840, 0x1880, 0x1900, 0x18c0, 0x18c1, 0x1901, + 0x1940, 0x1942, 0x1941, 0x1980, 0x19c0, 0x19c2, 0x19c1, 0x1c80, + 0x1cc0, 0x1dc0, 0x1f80, 0x2000, 0x2002, 0x2004, 0x2006, 0x2008, + 0x2040, 0x2080, 0x2082, 0x20c0, 0x20c1, 0x2100, 0x22b8, 0x22b9, + 0x2310, 0x2311, 0x231c, 0x231d, 0x244c, 0x2456, 0x244d, 0x2457, + 0x248c, 0x248d, 0x249e, 0x249f, 0x2500, 0x2502, 0x2504, 0x2bc0, + 0x2501, 0x2503, 0x2505, 0x2bc1, 0x2bc2, 0x2bc3, 0x2bc4, 0x2bc5, + 0x2bc6, 0x2bc7, 0x2580, 0x2582, 0x2584, 0x2bc8, 0x2581, 0x2583, + 0x2585, 0x2bc9, 0x2bca, 0x2bcb, 0x2bcc, 0x2bcd, 0x2bce, 0x2bcf, + 0x2600, 0x2602, 0x2601, 0x2603, 0x2680, 0x2682, 0x2681, 0x2683, + 0x26c2, 0x26c4, 0x26c6, 0x2c00, 0x26c3, 0x26c5, 0x26c7, 0x2c01, + 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x26ca, 0x26cc, + 0x26ce, 0x2c08, 0x26cb, 0x26cd, 0x26cf, 0x2c09, 0x2c0a, 0x2c0b, + 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x26d2, 0x26d4, 0x26d6, 0x26d3, + 0x26d5, 0x26d7, 0x26da, 0x26dc, 0x26de, 0x26db, 0x26dd, 0x26df, + 0x2700, 0x2702, 0x2701, 0x2703, 0x2780, 0x2782, 0x2781, 0x2783, + 0x2800, 0x2802, 0x2804, 0x2801, 0x2803, 0x2805, 0x2842, 0x2844, + 0x2846, 0x2849, 0x284b, 0x284d, 0x2c40, 0x284a, 0x284c, 0x284e, + 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2851, + 0x2853, 0x2855, 0x2c48, 0x2852, 0x2854, 0x2856, 0x2c49, 0x2c4a, + 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c82, 0x2e01, 0x3180, + 0x2c87, 0x2f01, 0x2f02, 0x2f03, 0x2e06, 0x3185, 0x3000, 0x3001, + 0x3002, 0x4640, 0x4641, 0x4680, 0x46c0, 0x46c2, 0x46c1, 0x4700, + 0x4740, 0x4780, 0x47c0, 0x47c2, 0x4900, 0x4940, 0x4980, 0x4982, + 0x4a00, 0x49c2, 0x4a03, 0x4a04, 0x4a40, 0x4a41, 0x4a80, 0x4a81, + 0x4ac0, 0x4ac1, 0x4bc0, 0x4bc1, 0x4b00, 0x4b01, 0x4b40, 0x4b41, + 0x4bc2, 0x4bc3, 0x4b80, 0x4b81, 0x4b82, 0x4b83, 0x4c00, 0x4c01, + 0x4c02, 0x4c03, 0x5600, 0x5440, 0x5442, 0x5444, 0x5446, 0x5448, + 0x544a, 0x544c, 0x544e, 0x5450, 0x5452, 0x5454, 0x5456, 0x5480, + 0x5482, 0x5484, 0x54c0, 0x54c1, 0x5500, 0x5501, 0x5540, 0x5541, + 0x5580, 0x5581, 0x55c0, 0x55c1, 0x5680, 0x58c0, 0x5700, 0x5702, + 0x5704, 0x5706, 0x5708, 0x570a, 0x570c, 0x570e, 0x5710, 0x5712, + 0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, 0x5781, 0x57c0, + 0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, 0x5900, + 0x5901, 0x5902, 0x5903, 0x5940, 0x8ec0, 0x8f00, 0x8fc0, 0x8fc2, + 0x9000, 0x9040, 0x9041, 0x9080, 0x9081, 0x90c0, 0x90c2, 0x9100, + 0x9140, 0x9182, 0x9180, 0x9183, 0x91c1, 0x91c0, 0x91c3, 0x9200, + 0x9201, 0x9240, 0x9280, 0x9282, 0x9284, 0x9281, 0x9285, 0x9287, + 0x9286, 0x9283, 0x92c1, 0x92c0, 0x92c2, +}; + +typedef enum { + UNICODE_GC_Cn, + UNICODE_GC_Lu, + UNICODE_GC_Ll, + UNICODE_GC_Lt, + UNICODE_GC_Lm, + UNICODE_GC_Lo, + UNICODE_GC_Mn, + UNICODE_GC_Mc, + UNICODE_GC_Me, + UNICODE_GC_Nd, + UNICODE_GC_Nl, + UNICODE_GC_No, + UNICODE_GC_Sm, + UNICODE_GC_Sc, + UNICODE_GC_Sk, + UNICODE_GC_So, + UNICODE_GC_Pc, + UNICODE_GC_Pd, + UNICODE_GC_Ps, + UNICODE_GC_Pe, + UNICODE_GC_Pi, + UNICODE_GC_Pf, + UNICODE_GC_Po, + UNICODE_GC_Zs, + UNICODE_GC_Zl, + UNICODE_GC_Zp, + UNICODE_GC_Cc, + UNICODE_GC_Cf, + UNICODE_GC_Cs, + UNICODE_GC_Co, + UNICODE_GC_LC, + UNICODE_GC_L, + UNICODE_GC_M, + UNICODE_GC_N, + UNICODE_GC_S, + UNICODE_GC_P, + UNICODE_GC_Z, + UNICODE_GC_C, + UNICODE_GC_COUNT, +} UnicodeGCEnum; + +static const char unicode_gc_name_table[] = + "Cn,Unassigned" "\0" + "Lu,Uppercase_Letter" "\0" + "Ll,Lowercase_Letter" "\0" + "Lt,Titlecase_Letter" "\0" + "Lm,Modifier_Letter" "\0" + "Lo,Other_Letter" "\0" + "Mn,Nonspacing_Mark" "\0" + "Mc,Spacing_Mark" "\0" + "Me,Enclosing_Mark" "\0" + "Nd,Decimal_Number,digit" "\0" + "Nl,Letter_Number" "\0" + "No,Other_Number" "\0" + "Sm,Math_Symbol" "\0" + "Sc,Currency_Symbol" "\0" + "Sk,Modifier_Symbol" "\0" + "So,Other_Symbol" "\0" + "Pc,Connector_Punctuation" "\0" + "Pd,Dash_Punctuation" "\0" + "Ps,Open_Punctuation" "\0" + "Pe,Close_Punctuation" "\0" + "Pi,Initial_Punctuation" "\0" + "Pf,Final_Punctuation" "\0" + "Po,Other_Punctuation" "\0" + "Zs,Space_Separator" "\0" + "Zl,Line_Separator" "\0" + "Zp,Paragraph_Separator" "\0" + "Cc,Control,cntrl" "\0" + "Cf,Format" "\0" + "Cs,Surrogate" "\0" + "Co,Private_Use" "\0" + "LC,Cased_Letter" "\0" + "L,Letter" "\0" + "M,Mark,Combining_Mark" "\0" + "N,Number" "\0" + "S,Symbol" "\0" + "P,Punctuation,punct" "\0" + "Z,Separator" "\0" + "C,Other" "\0" +; + +static const uint8_t unicode_gc_table[4070] = { + 0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13, + 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, + 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, + 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, + 0xfa, 0x19, 0x17, 0x16, 0x6d, 0x0f, 0x16, 0x0e, + 0x0f, 0x05, 0x14, 0x0c, 0x1b, 0x0f, 0x0e, 0x0f, + 0x0c, 0x2b, 0x0e, 0x02, 0x36, 0x0e, 0x0b, 0x05, + 0x15, 0x4b, 0x16, 0xe1, 0x0f, 0x0c, 0xc1, 0xe2, + 0x10, 0x0c, 0xe2, 0x00, 0xff, 0x30, 0x02, 0xff, + 0x08, 0x02, 0xff, 0x27, 0xbf, 0x22, 0x21, 0x02, + 0x5f, 0x5f, 0x21, 0x22, 0x61, 0x02, 0x21, 0x02, + 0x41, 0x42, 0x21, 0x02, 0x21, 0x02, 0x9f, 0x7f, + 0x02, 0x5f, 0x5f, 0x21, 0x02, 0x5f, 0x3f, 0x02, + 0x05, 0x3f, 0x22, 0x65, 0x01, 0x03, 0x02, 0x01, + 0x03, 0x02, 0x01, 0x03, 0x02, 0xff, 0x08, 0x02, + 0xff, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x5f, 0x21, + 0x02, 0xff, 0x32, 0xa2, 0x21, 0x02, 0x21, 0x22, + 0x5f, 0x41, 0x02, 0xff, 0x00, 0xe2, 0x3c, 0x05, + 0xe2, 0x13, 0xe4, 0x0a, 0x6e, 0xe4, 0x04, 0xee, + 0x06, 0x84, 0xce, 0x04, 0x0e, 0x04, 0xee, 0x09, + 0xe6, 0x68, 0x7f, 0x04, 0x0e, 0x3f, 0x20, 0x04, + 0x42, 0x16, 0x01, 0x60, 0x2e, 0x01, 0x16, 0x41, + 0x00, 0x01, 0x00, 0x21, 0x02, 0xe1, 0x09, 0x00, + 0xe1, 0x01, 0xe2, 0x1b, 0x3f, 0x02, 0x41, 0x42, + 0xff, 0x10, 0x62, 0x3f, 0x0c, 0x5f, 0x3f, 0x02, + 0xe1, 0x2b, 0xe2, 0x28, 0xff, 0x1a, 0x0f, 0x86, + 0x28, 0xff, 0x2f, 0xff, 0x06, 0x02, 0xff, 0x58, + 0x00, 0xe1, 0x1e, 0x20, 0x04, 0xb6, 0xe2, 0x21, + 0x16, 0x11, 0x20, 0x2f, 0x0d, 0x00, 0xe6, 0x25, + 0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06, + 0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0, + 0x03, 0xbb, 0x4c, 0x36, 0x0d, 0x36, 0x2f, 0xe6, + 0x03, 0x16, 0x1b, 0x56, 0xe5, 0x18, 0x04, 0xe5, + 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, 0x06, + 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f, 0xa6, + 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, 0x45, + 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, 0x06, + 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51, 0xe6, + 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, 0x19, + 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, 0x06, + 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01, 0x04, + 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, 0xe5, + 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, 0x80, + 0xe5, 0x10, 0x0e, 0xa5, 0x00, 0x3b, 0x80, 0xe6, + 0x01, 0xe5, 0x21, 0x04, 0xe6, 0x10, 0x1b, 0xe6, + 0x18, 0x07, 0xe5, 0x2e, 0x06, 0x07, 0x06, 0x05, + 0x47, 0xe6, 0x00, 0x67, 0x06, 0x27, 0x05, 0xc6, + 0xe5, 0x02, 0x26, 0x36, 0xe9, 0x02, 0x16, 0x04, + 0xe5, 0x07, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, + 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x05, + 0x40, 0x65, 0x20, 0x06, 0x05, 0x47, 0x66, 0x20, + 0x27, 0x20, 0x27, 0x06, 0x05, 0xe0, 0x00, 0x07, + 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, 0x02, + 0x25, 0x2d, 0xab, 0x0f, 0x0d, 0x05, 0x16, 0x06, + 0x20, 0x26, 0x07, 0x00, 0xa5, 0x60, 0x25, 0x20, + 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x25, + 0x00, 0x25, 0x20, 0x06, 0x00, 0x47, 0x26, 0x60, + 0x26, 0x20, 0x46, 0x40, 0x06, 0xc0, 0x65, 0x00, + 0x05, 0xc0, 0xe9, 0x02, 0x26, 0x45, 0x06, 0x16, + 0xe0, 0x02, 0x26, 0x07, 0x00, 0xe5, 0x01, 0x00, + 0x45, 0x00, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, + 0x00, 0x85, 0x20, 0x06, 0x05, 0x47, 0x86, 0x00, + 0x26, 0x07, 0x00, 0x27, 0x06, 0x20, 0x05, 0xe0, + 0x07, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x16, 0x0d, + 0xc0, 0x05, 0xa6, 0x00, 0x06, 0x27, 0x00, 0xe5, + 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, + 0x00, 0x25, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, + 0x06, 0x07, 0x66, 0x20, 0x27, 0x20, 0x27, 0x06, + 0xc0, 0x26, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, + 0x20, 0xe9, 0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, + 0x06, 0x05, 0x00, 0xa5, 0x40, 0x45, 0x00, 0x65, + 0x40, 0x25, 0x00, 0x05, 0x00, 0x25, 0x40, 0x25, + 0x40, 0x45, 0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, + 0x27, 0x40, 0x47, 0x00, 0x47, 0x06, 0x20, 0x05, + 0xa0, 0x07, 0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, + 0x0d, 0x0f, 0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, + 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x08, + 0x20, 0x06, 0x05, 0x46, 0x67, 0x00, 0x46, 0x00, + 0x66, 0xc0, 0x26, 0x00, 0x45, 0x20, 0x05, 0x20, + 0x25, 0x26, 0x20, 0xe9, 0x02, 0xc0, 0x16, 0xcb, + 0x0f, 0x05, 0x06, 0x27, 0x16, 0xe5, 0x00, 0x00, + 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x02, 0x00, + 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x87, 0x00, + 0x06, 0x27, 0x00, 0x27, 0x26, 0xc0, 0x27, 0xa0, + 0x25, 0x00, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x00, + 0x25, 0x07, 0xe0, 0x04, 0x26, 0x27, 0xe5, 0x01, + 0x00, 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, 0x47, + 0x66, 0x00, 0x47, 0x00, 0x47, 0x06, 0x05, 0x0f, + 0x60, 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, 0xe9, + 0x02, 0xeb, 0x01, 0x0f, 0xa5, 0x00, 0x06, 0x27, + 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10, 0x00, 0xe5, + 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, 0x60, + 0x47, 0x46, 0x00, 0x06, 0x00, 0xe7, 0x00, 0xa0, + 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0, 0x04, 0xe5, + 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, 0x04, + 0xe6, 0x00, 0x16, 0xe9, 0x02, 0x36, 0xe0, 0x1d, + 0x25, 0x00, 0x05, 0x00, 0x85, 0x00, 0xe5, 0x10, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, 0xe6, + 0x01, 0x05, 0x20, 0x85, 0x00, 0x04, 0x00, 0xc6, + 0x00, 0xe9, 0x02, 0x20, 0x65, 0xe0, 0x18, 0x05, + 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, 0xaf, + 0xe9, 0x02, 0xeb, 0x02, 0x0f, 0x06, 0x0f, 0x06, + 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13, 0x27, 0xe5, + 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, 0x07, + 0x86, 0x16, 0x26, 0x85, 0xe6, 0x03, 0x00, 0xe6, + 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf, 0x00, 0x2f, + 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, 0x27, + 0x66, 0x07, 0xa6, 0x07, 0x26, 0x27, 0x26, 0x05, + 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26, 0x65, 0x46, + 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, 0x05, + 0x06, 0x27, 0x26, 0xa7, 0x06, 0x05, 0x07, 0xe9, + 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e, 0x00, 0x01, + 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, 0x42, + 0xe5, 0x80, 0xc1, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21, 0x00, 0x65, + 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x07, 0x00, 0xe5, + 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b, 0x20, 0x46, + 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, 0xef, + 0x02, 0xa0, 0xe1, 0x4e, 0x20, 0xa2, 0x20, 0x11, + 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5, 0x09, 0x17, + 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, 0x56, + 0x4a, 0xe5, 0x00, 0xc0, 0xe5, 0x0a, 0x46, 0x07, + 0xe0, 0x01, 0xe5, 0x0b, 0x26, 0x07, 0x36, 0xe0, + 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, 0x05, + 0x00, 0x45, 0x00, 0x26, 0xe0, 0x04, 0xe5, 0x2c, + 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06, 0x27, 0xe6, + 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, 0x20, + 0xe9, 0x02, 0xa0, 0xeb, 0x02, 0xa0, 0xb6, 0x11, + 0x76, 0x46, 0x1b, 0x06, 0xe9, 0x02, 0xa0, 0xe5, + 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, 0xe5, + 0x1a, 0x06, 0x05, 0x80, 0xe5, 0x3e, 0xe0, 0x02, + 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26, 0x47, 0x60, + 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, 0x36, + 0xe9, 0x02, 0xe5, 0x16, 0x20, 0x85, 0xe0, 0x03, + 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0, 0xe9, 0x02, + 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, 0x27, + 0x06, 0x20, 0x36, 0xe5, 0x2d, 0x07, 0x06, 0x07, + 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27, 0xe6, 0x00, + 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, 0xa0, + 0xe9, 0x02, 0xa0, 0xd6, 0x04, 0xb6, 0x20, 0xe6, + 0x06, 0x08, 0xe6, 0x08, 0xe0, 0x29, 0x66, 0x07, + 0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, 0x06, 0x87, + 0x06, 0x27, 0xe5, 0x00, 0x00, 0x36, 0xe9, 0x02, + 0xd6, 0xef, 0x02, 0xe6, 0x01, 0xef, 0x01, 0x56, + 0x26, 0x07, 0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, + 0x07, 0x46, 0x25, 0xe9, 0x02, 0xe5, 0x24, 0x06, + 0x07, 0x26, 0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, + 0x00, 0x76, 0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, + 0x27, 0x26, 0x40, 0x96, 0xe9, 0x02, 0x40, 0x45, + 0xe9, 0x02, 0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, + 0x3f, 0x80, 0xe1, 0x23, 0x20, 0x41, 0xf6, 0x00, + 0xe0, 0x00, 0x46, 0x16, 0xe6, 0x05, 0x07, 0xc6, + 0x65, 0x06, 0xa5, 0x06, 0x25, 0x07, 0x26, 0x05, + 0x80, 0xe2, 0x24, 0xe4, 0x37, 0xe2, 0x05, 0x04, + 0xe2, 0x1a, 0xe4, 0x1d, 0xe6, 0x38, 0xff, 0x80, + 0x0e, 0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, + 0x00, 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, + 0x00, 0xe2, 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, + 0x20, 0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, + 0x20, 0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, + 0x00, 0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, + 0x61, 0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, + 0x61, 0x03, 0x4e, 0x62, 0x20, 0x22, 0x61, 0x00, + 0x4e, 0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, + 0x22, 0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, + 0xb1, 0x36, 0x14, 0x15, 0x12, 0x34, 0x15, 0x12, + 0x14, 0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, + 0x01, 0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, + 0x13, 0xf6, 0x03, 0x0c, 0x16, 0x10, 0xf6, 0x02, + 0x17, 0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, + 0xab, 0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, + 0x12, 0x13, 0x00, 0xe4, 0x05, 0x40, 0xed, 0x19, + 0xe0, 0x07, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, + 0x04, 0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, + 0x02, 0x41, 0x22, 0x41, 0x02, 0x0f, 0x01, 0x2f, + 0x0c, 0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, + 0x2f, 0x22, 0x21, 0x8c, 0x3f, 0x42, 0x0f, 0x0c, + 0x2f, 0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, + 0x6a, 0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, + 0x0c, 0x2f, 0x0c, 0x2f, 0x0c, 0xcf, 0x0c, 0xef, + 0x17, 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, + 0xec, 0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, + 0x13, 0xef, 0x0c, 0x2c, 0xcf, 0x12, 0x13, 0xef, + 0x49, 0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, + 0xac, 0xef, 0x40, 0xe0, 0x0e, 0xef, 0x03, 0xe0, + 0x0d, 0xeb, 0x34, 0xef, 0x46, 0xeb, 0x0e, 0xef, + 0x80, 0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, + 0xec, 0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, + 0xef, 0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, + 0x12, 0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, + 0xec, 0x80, 0x7a, 0xef, 0x28, 0xec, 0x0d, 0x2f, + 0xac, 0xef, 0x1f, 0x20, 0xef, 0x18, 0x00, 0xef, + 0x61, 0xe1, 0x28, 0xe2, 0x28, 0x5f, 0x21, 0x22, + 0xdf, 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82, 0x24, + 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, 0x46, + 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, 0x00, + 0x02, 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0, 0x04, + 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, 0x01, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, 0x56, + 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, 0x11, + 0x36, 0x11, 0x16, 0x14, 0x15, 0x36, 0x14, 0x15, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, 0x16, + 0x12, 0xf6, 0x05, 0x2f, 0x56, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, 0xe0, 0x1a, + 0xef, 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, + 0x80, 0x4e, 0xe0, 0x12, 0xef, 0x08, 0x17, 0x56, + 0x0f, 0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, + 0x12, 0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, 0x11, + 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, + 0xe5, 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, + 0xe5, 0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, 0x23, + 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, + 0xe5, 0x18, 0xef, 0x1e, 0xe0, 0x01, 0x0f, 0xe5, + 0x08, 0xef, 0x17, 0x00, 0xeb, 0x02, 0xef, 0x16, + 0xeb, 0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, + 0x02, 0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, + 0xe5, 0x99, 0x38, 0xef, 0x38, 0xe5, 0xc0, 0x11, + 0x8d, 0x04, 0xe5, 0x83, 0xef, 0x40, 0xef, 0x2f, + 0xe0, 0x01, 0xe5, 0x20, 0xa4, 0x36, 0xe5, 0x80, + 0x84, 0x04, 0x56, 0xe5, 0x08, 0xe9, 0x02, 0x25, + 0xe0, 0x0c, 0xff, 0x26, 0x05, 0x06, 0x48, 0x16, + 0xe6, 0x02, 0x16, 0x04, 0xff, 0x14, 0x24, 0x26, + 0xe5, 0x3e, 0xea, 0x02, 0x26, 0xb6, 0xe0, 0x00, + 0xee, 0x0f, 0xe4, 0x01, 0x2e, 0xff, 0x06, 0x22, + 0xff, 0x36, 0x04, 0xe2, 0x00, 0x9f, 0xff, 0x02, + 0x04, 0x2e, 0x7f, 0x05, 0x7f, 0x22, 0xff, 0x0d, + 0x61, 0x02, 0x81, 0x02, 0xff, 0x07, 0x41, 0x02, + 0x5f, 0x3f, 0x20, 0x3f, 0x00, 0x02, 0x00, 0x02, + 0xdf, 0xe0, 0x0d, 0x44, 0x3f, 0x05, 0x24, 0x02, + 0xc5, 0x06, 0x45, 0x06, 0x65, 0x06, 0xe5, 0x0f, + 0x27, 0x26, 0x07, 0x6f, 0x06, 0x40, 0xab, 0x2f, + 0x0d, 0x0f, 0xa0, 0xe5, 0x2c, 0x76, 0xe0, 0x00, + 0x27, 0xe5, 0x2a, 0xe7, 0x08, 0x26, 0xe0, 0x00, + 0x36, 0xe9, 0x02, 0xa0, 0xe6, 0x0a, 0xa5, 0x56, + 0x05, 0x16, 0x25, 0x06, 0xe9, 0x02, 0xe5, 0x14, + 0xe6, 0x00, 0x36, 0xe5, 0x0f, 0xe6, 0x03, 0x27, + 0xe0, 0x03, 0x16, 0xe5, 0x15, 0x40, 0x46, 0x07, + 0xe5, 0x27, 0x06, 0x27, 0x66, 0x27, 0x26, 0x47, + 0xf6, 0x05, 0x00, 0x04, 0xe9, 0x02, 0x60, 0x36, + 0x85, 0x06, 0x04, 0xe5, 0x01, 0xe9, 0x02, 0x85, + 0x00, 0xe5, 0x21, 0xa6, 0x27, 0x26, 0x27, 0x26, + 0xe0, 0x01, 0x45, 0x06, 0xe5, 0x00, 0x06, 0x07, + 0x20, 0xe9, 0x02, 0x20, 0x76, 0xe5, 0x08, 0x04, + 0xa5, 0x4f, 0x05, 0x07, 0x06, 0x07, 0xe5, 0x2a, + 0x06, 0x05, 0x46, 0x25, 0x26, 0x85, 0x26, 0x05, + 0x06, 0x05, 0xe0, 0x10, 0x25, 0x04, 0x36, 0xe5, + 0x03, 0x07, 0x26, 0x27, 0x36, 0x05, 0x24, 0x07, + 0x06, 0xe0, 0x02, 0xa5, 0x20, 0xa5, 0x20, 0xa5, + 0xe0, 0x01, 0xc5, 0x00, 0xc5, 0x00, 0xe2, 0x23, + 0x0e, 0x64, 0xe2, 0x01, 0x04, 0x2e, 0x60, 0xe2, + 0x48, 0xe5, 0x1b, 0x27, 0x06, 0x27, 0x06, 0x27, + 0x16, 0x07, 0x06, 0x20, 0xe9, 0x02, 0xa0, 0xe5, + 0xab, 0x1c, 0xe0, 0x04, 0xe5, 0x0f, 0x60, 0xe5, + 0x29, 0x60, 0xfc, 0x87, 0x78, 0xfd, 0x98, 0x78, + 0xe5, 0x80, 0xe6, 0x20, 0xe5, 0x62, 0xe0, 0x1e, + 0xc2, 0xe0, 0x04, 0x82, 0x80, 0x05, 0x06, 0xe5, + 0x02, 0x0c, 0xe5, 0x05, 0x00, 0x85, 0x00, 0x05, + 0x00, 0x25, 0x00, 0x25, 0x00, 0xe5, 0x64, 0xee, + 0x09, 0xe0, 0x08, 0xe5, 0x80, 0xe3, 0x13, 0x12, + 0xef, 0x08, 0xe5, 0x38, 0x20, 0xe5, 0x2e, 0xc0, + 0x0f, 0xe0, 0x18, 0xe5, 0x04, 0x0d, 0x4f, 0xe6, + 0x08, 0xd6, 0x12, 0x13, 0x16, 0xa0, 0xe6, 0x08, + 0x16, 0x31, 0x30, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x36, 0x12, 0x13, 0x76, 0x50, + 0x56, 0x00, 0x76, 0x11, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x56, 0x0c, 0x11, 0x4c, 0x00, 0x16, + 0x0d, 0x36, 0x60, 0x85, 0x00, 0xe5, 0x7f, 0x20, + 0x1b, 0x00, 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16, + 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c, + 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, 0x10, + 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, 0x12, + 0x13, 0x16, 0x12, 0x13, 0x36, 0xe5, 0x02, 0x04, + 0xe5, 0x25, 0x24, 0xe5, 0x17, 0x40, 0xa5, 0x20, + 0xa5, 0x20, 0xa5, 0x20, 0x45, 0x40, 0x2d, 0x0c, + 0x0e, 0x0f, 0x2d, 0x00, 0x0f, 0x6c, 0x2f, 0xe0, + 0x02, 0x5b, 0x2f, 0x20, 0xe5, 0x04, 0x00, 0xe5, + 0x12, 0x00, 0xe5, 0x0b, 0x00, 0x25, 0x00, 0xe5, + 0x07, 0x20, 0xe5, 0x06, 0xe0, 0x1a, 0xe5, 0x73, + 0x80, 0x56, 0x60, 0xeb, 0x25, 0x40, 0xef, 0x01, + 0xea, 0x2d, 0x6b, 0xef, 0x09, 0x2b, 0x4f, 0x00, + 0xef, 0x05, 0x40, 0x0f, 0xe0, 0x27, 0xef, 0x25, + 0x06, 0xe0, 0x7a, 0xe5, 0x15, 0x40, 0xe5, 0x29, + 0xe0, 0x07, 0x06, 0xeb, 0x13, 0x60, 0xe5, 0x18, + 0x6b, 0xe0, 0x01, 0xe5, 0x0c, 0x0a, 0xe5, 0x00, + 0x0a, 0x80, 0xe5, 0x1e, 0x86, 0x80, 0xe5, 0x16, + 0x00, 0x16, 0xe5, 0x1c, 0x60, 0xe5, 0x00, 0x16, + 0x8a, 0xe0, 0x22, 0xe1, 0x20, 0xe2, 0x20, 0xe5, + 0x46, 0x20, 0xe9, 0x02, 0xa0, 0xe1, 0x1c, 0x60, + 0xe2, 0x1c, 0x60, 0xe5, 0x20, 0xe0, 0x00, 0xe5, + 0x2c, 0xe0, 0x03, 0x16, 0xe1, 0x03, 0x00, 0xe1, + 0x07, 0x00, 0xc1, 0x00, 0x21, 0x00, 0xe2, 0x03, + 0x00, 0xe2, 0x07, 0x00, 0xc2, 0x00, 0x22, 0x40, + 0xe5, 0x2c, 0xe0, 0x04, 0xe5, 0x80, 0xaf, 0xe0, + 0x01, 0xe5, 0x0e, 0xe0, 0x02, 0xe5, 0x00, 0xe0, + 0x10, 0xa4, 0x00, 0xe4, 0x22, 0x00, 0xe4, 0x01, + 0xe0, 0x3d, 0xa5, 0x20, 0x05, 0x00, 0xe5, 0x24, + 0x00, 0x25, 0x40, 0x05, 0x20, 0xe5, 0x0f, 0x00, + 0x16, 0xeb, 0x00, 0xe5, 0x0f, 0x2f, 0xcb, 0xe5, + 0x17, 0xe0, 0x00, 0xeb, 0x01, 0xe0, 0x28, 0xe5, + 0x0b, 0x00, 0x25, 0x80, 0x8b, 0xe5, 0x0e, 0xab, + 0x40, 0x16, 0xe5, 0x12, 0x80, 0x16, 0xe0, 0x38, + 0xe5, 0x30, 0x60, 0x2b, 0x25, 0xeb, 0x08, 0x20, + 0xeb, 0x26, 0x05, 0x46, 0x00, 0x26, 0x80, 0x66, + 0x65, 0x00, 0x45, 0x00, 0xe5, 0x15, 0x20, 0x46, + 0x60, 0x06, 0xeb, 0x01, 0xc0, 0xf6, 0x01, 0xc0, + 0xe5, 0x15, 0x2b, 0x16, 0xe5, 0x15, 0x4b, 0xe0, + 0x18, 0xe5, 0x00, 0x0f, 0xe5, 0x14, 0x26, 0x60, + 0x8b, 0xd6, 0xe0, 0x01, 0xe5, 0x2e, 0x40, 0xd6, + 0xe5, 0x0e, 0x20, 0xeb, 0x00, 0xe5, 0x0b, 0x80, + 0xeb, 0x00, 0xe5, 0x0a, 0xc0, 0x76, 0xe0, 0x04, + 0xcb, 0xe0, 0x48, 0xe5, 0x41, 0xe0, 0x2f, 0xe1, + 0x2b, 0xe0, 0x05, 0xe2, 0x2b, 0xc0, 0xab, 0xe5, + 0x1c, 0x66, 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xe9, + 0x02, 0x65, 0x04, 0x05, 0xe1, 0x0e, 0x40, 0x86, + 0x11, 0x04, 0xe2, 0x0e, 0xe0, 0x00, 0x2c, 0xe0, + 0x80, 0x48, 0xeb, 0x17, 0x00, 0xe5, 0x22, 0x00, + 0x26, 0x11, 0x20, 0x25, 0xe0, 0x08, 0x45, 0xe0, + 0x2f, 0x66, 0xe5, 0x15, 0xeb, 0x02, 0x05, 0xe0, + 0x00, 0xe5, 0x0e, 0xe6, 0x03, 0x6b, 0x96, 0xe0, + 0x0e, 0xe5, 0x0a, 0x66, 0x76, 0xe0, 0x1e, 0xe5, + 0x0d, 0xcb, 0xe0, 0x0c, 0xe5, 0x0f, 0xe0, 0x01, + 0x07, 0x06, 0x07, 0xe5, 0x2d, 0xe6, 0x07, 0xd6, + 0x60, 0xeb, 0x0c, 0xe9, 0x02, 0x06, 0x25, 0x26, + 0x05, 0xe0, 0x01, 0x46, 0x07, 0xe5, 0x25, 0x47, + 0x66, 0x27, 0x26, 0x36, 0x1b, 0x76, 0x06, 0xe0, + 0x02, 0x1b, 0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, + 0xa0, 0x46, 0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, + 0x00, 0xe9, 0x02, 0x76, 0x05, 0x27, 0x05, 0xe0, + 0x00, 0xe5, 0x1b, 0x06, 0x36, 0x05, 0xe0, 0x01, + 0x26, 0x07, 0xe5, 0x28, 0x47, 0xe6, 0x01, 0x27, + 0x65, 0x76, 0x66, 0x16, 0x07, 0x06, 0xe9, 0x02, + 0x05, 0x16, 0x05, 0x56, 0x00, 0xeb, 0x0c, 0xe0, + 0x03, 0xe5, 0x0a, 0x00, 0xe5, 0x11, 0x47, 0x46, + 0x27, 0x06, 0x07, 0x26, 0xb6, 0x06, 0x25, 0x06, + 0xe0, 0x36, 0xc5, 0x00, 0x05, 0x00, 0x65, 0x00, + 0xe5, 0x07, 0x00, 0xe5, 0x02, 0x16, 0xa0, 0xe5, + 0x27, 0x06, 0x47, 0xe6, 0x00, 0x80, 0xe9, 0x02, + 0xa0, 0x26, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, + 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, + 0x85, 0x00, 0x26, 0x05, 0x27, 0x06, 0x67, 0x20, + 0x27, 0x20, 0x47, 0x20, 0x05, 0xa0, 0x07, 0x80, + 0x85, 0x27, 0x20, 0xc6, 0x40, 0x86, 0xe0, 0x03, + 0xe5, 0x02, 0x00, 0x05, 0x20, 0x05, 0x00, 0xe5, + 0x1e, 0x00, 0x05, 0x47, 0xa6, 0x00, 0x07, 0x20, + 0x07, 0x00, 0x67, 0x00, 0x27, 0x06, 0x07, 0x06, + 0x05, 0x06, 0x05, 0x36, 0x00, 0x36, 0xe0, 0x00, + 0x26, 0xe0, 0x15, 0xe5, 0x2d, 0x47, 0xe6, 0x00, + 0x27, 0x46, 0x07, 0x06, 0x65, 0x96, 0xe9, 0x02, + 0x36, 0x00, 0x16, 0x06, 0x45, 0xe0, 0x16, 0xe5, + 0x28, 0x47, 0xa6, 0x07, 0x06, 0x67, 0x26, 0x07, + 0x26, 0x25, 0x16, 0x05, 0xe0, 0x00, 0xe9, 0x02, + 0xe0, 0x80, 0x1e, 0xe5, 0x27, 0x47, 0x66, 0x20, + 0x67, 0x26, 0x07, 0x26, 0xf6, 0x0f, 0x65, 0x26, + 0xe0, 0x1a, 0xe5, 0x28, 0x47, 0xe6, 0x00, 0x27, + 0x06, 0x07, 0x26, 0x56, 0x05, 0xe0, 0x03, 0xe9, + 0x02, 0xa0, 0xf6, 0x05, 0xe0, 0x0b, 0xe5, 0x23, + 0x06, 0x07, 0x06, 0x27, 0xa6, 0x07, 0x06, 0x05, + 0x16, 0xa0, 0xe9, 0x02, 0xa0, 0xe9, 0x0c, 0xe0, + 0x14, 0xe5, 0x13, 0x20, 0x06, 0x07, 0x06, 0x27, + 0x66, 0x07, 0x86, 0x60, 0xe9, 0x02, 0x2b, 0x56, + 0x0f, 0xc5, 0xe0, 0x80, 0x31, 0xe5, 0x24, 0x47, + 0xe6, 0x01, 0x07, 0x26, 0x16, 0xe0, 0x5c, 0xe1, + 0x18, 0xe2, 0x18, 0xe9, 0x02, 0xeb, 0x01, 0xe0, + 0x04, 0xe5, 0x00, 0x20, 0x05, 0x20, 0xe5, 0x00, + 0x00, 0x25, 0x00, 0xe5, 0x10, 0xa7, 0x00, 0x27, + 0x20, 0x26, 0x07, 0x06, 0x05, 0x07, 0x05, 0x07, + 0x06, 0x56, 0xe0, 0x01, 0xe9, 0x02, 0xe0, 0x3e, + 0xe5, 0x00, 0x20, 0xe5, 0x1f, 0x47, 0x66, 0x20, + 0x26, 0x67, 0x06, 0x05, 0x16, 0x05, 0x07, 0xe0, + 0x13, 0x05, 0xe6, 0x02, 0xe5, 0x20, 0xa6, 0x07, + 0x05, 0x66, 0xf6, 0x00, 0x06, 0xe0, 0x00, 0x05, + 0xa6, 0x27, 0x46, 0xe5, 0x26, 0xe6, 0x05, 0x07, + 0x26, 0x56, 0x05, 0x96, 0xe0, 0x05, 0xe5, 0x41, + 0xc0, 0xf6, 0x02, 0xe0, 0x80, 0x2e, 0xe5, 0x19, + 0x16, 0xe0, 0x06, 0xe9, 0x02, 0xa0, 0xe5, 0x01, + 0x00, 0xe5, 0x1d, 0x07, 0xc6, 0x00, 0xa6, 0x07, + 0x06, 0x05, 0x96, 0xe0, 0x02, 0xe9, 0x02, 0xeb, + 0x0b, 0x40, 0x36, 0xe5, 0x16, 0x20, 0xe6, 0x0e, + 0x00, 0x07, 0xc6, 0x07, 0x26, 0x07, 0x26, 0xe0, + 0x41, 0xc5, 0x00, 0x25, 0x00, 0xe5, 0x1e, 0xa6, + 0x40, 0x06, 0x00, 0x26, 0x00, 0xc6, 0x05, 0x06, + 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xa5, 0x00, 0x25, + 0x00, 0xe5, 0x18, 0x87, 0x00, 0x26, 0x00, 0x27, + 0x06, 0x07, 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, + 0x80, 0xae, 0xe5, 0x0b, 0x26, 0x27, 0x36, 0xc0, + 0x26, 0x05, 0x07, 0xe5, 0x05, 0x00, 0xe5, 0x1a, + 0x27, 0x86, 0x40, 0x27, 0x06, 0x07, 0x06, 0xf6, + 0x05, 0xe9, 0x02, 0x06, 0xe0, 0x4d, 0x05, 0xe0, + 0x07, 0xeb, 0x0d, 0xef, 0x00, 0x6d, 0xef, 0x09, + 0xe0, 0x05, 0x16, 0xe5, 0x83, 0x12, 0xe0, 0x5e, + 0xea, 0x67, 0x00, 0x96, 0xe0, 0x03, 0xe5, 0x80, + 0x3c, 0xe0, 0x89, 0xc4, 0xe5, 0x59, 0x36, 0xe0, + 0x05, 0xe5, 0x83, 0xa8, 0xfb, 0x08, 0x06, 0xa5, + 0xe6, 0x07, 0xe0, 0x02, 0xe5, 0x8f, 0x13, 0x80, + 0xe5, 0x81, 0xbf, 0xe0, 0x9a, 0x31, 0xe5, 0x16, + 0xe6, 0x04, 0x47, 0x46, 0xe9, 0x02, 0xe0, 0x86, + 0x3e, 0xe5, 0x81, 0xb1, 0xc0, 0xe5, 0x17, 0x00, + 0xe9, 0x02, 0x60, 0x36, 0xe5, 0x47, 0x00, 0xe9, + 0x02, 0xa0, 0xe5, 0x16, 0x20, 0x86, 0x16, 0xe0, + 0x02, 0xe5, 0x28, 0xc6, 0x96, 0x6f, 0x64, 0x16, + 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, 0xcb, 0x00, + 0xe5, 0x0d, 0x80, 0xe5, 0x0b, 0xe0, 0x81, 0x28, + 0x44, 0xe5, 0x20, 0x24, 0x56, 0xe9, 0x02, 0xe0, + 0x80, 0x3e, 0xe1, 0x18, 0xe2, 0x18, 0xeb, 0x0f, + 0x76, 0xe0, 0x5d, 0xe5, 0x43, 0x60, 0x06, 0x05, + 0xe7, 0x2f, 0xc0, 0x66, 0xe4, 0x05, 0xe0, 0x38, + 0x24, 0x16, 0x04, 0x06, 0xe0, 0x03, 0x27, 0xe0, + 0x06, 0xe5, 0x97, 0x70, 0xe0, 0x00, 0xe5, 0x84, + 0x4e, 0xe0, 0x21, 0xe5, 0x02, 0xe0, 0xa2, 0x5f, + 0x64, 0x00, 0xc4, 0x00, 0x24, 0x00, 0xe5, 0x80, + 0x9b, 0xe0, 0x07, 0x05, 0xe0, 0x15, 0x45, 0x20, + 0x05, 0xe0, 0x06, 0x65, 0xe0, 0x00, 0xe5, 0x81, + 0x04, 0xe0, 0x88, 0x7c, 0xe5, 0x63, 0x80, 0xe5, + 0x05, 0x40, 0xe5, 0x01, 0xc0, 0xe5, 0x02, 0x20, + 0x0f, 0x26, 0x16, 0x7b, 0xe0, 0x8e, 0xd4, 0xef, + 0x80, 0x68, 0xe9, 0x02, 0xa0, 0xef, 0x81, 0x2c, + 0xe0, 0x44, 0xe6, 0x26, 0x20, 0xe6, 0x0f, 0xe0, + 0x01, 0xef, 0x6c, 0xe0, 0x34, 0xef, 0x80, 0x6e, + 0xe0, 0x02, 0xef, 0x1f, 0x20, 0xef, 0x34, 0x27, + 0x46, 0x4f, 0xa7, 0xfb, 0x00, 0xe6, 0x00, 0x2f, + 0xc6, 0xef, 0x16, 0x66, 0xef, 0x35, 0xe0, 0x0d, + 0xef, 0x3a, 0x46, 0x0f, 0xe0, 0x72, 0xeb, 0x0c, + 0xe0, 0x04, 0xeb, 0x0c, 0xe0, 0x04, 0xef, 0x4f, + 0xe0, 0x01, 0xeb, 0x11, 0xe0, 0x7f, 0xe1, 0x12, + 0xe2, 0x12, 0xe1, 0x12, 0xc2, 0x00, 0xe2, 0x0a, + 0xe1, 0x12, 0xe2, 0x12, 0x01, 0x00, 0x21, 0x20, + 0x01, 0x20, 0x21, 0x20, 0x61, 0x00, 0xe1, 0x00, + 0x62, 0x00, 0x02, 0x00, 0xc2, 0x00, 0xe2, 0x03, + 0xe1, 0x12, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x20, + 0xe1, 0x00, 0x00, 0xc1, 0x00, 0xe2, 0x12, 0x21, + 0x00, 0x61, 0x00, 0x81, 0x00, 0x01, 0x40, 0xc1, + 0x00, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x14, 0x20, 0xe1, 0x11, 0x0c, 0xe2, + 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, + 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, + 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, + 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0x3f, + 0x20, 0xe9, 0x2a, 0xef, 0x81, 0x78, 0xe6, 0x2f, + 0x6f, 0xe6, 0x2a, 0xef, 0x00, 0x06, 0xef, 0x06, + 0x06, 0x2f, 0x96, 0xe0, 0x07, 0x86, 0x00, 0xe6, + 0x07, 0xe0, 0x83, 0xc8, 0xe2, 0x02, 0x05, 0xe2, + 0x0c, 0xa0, 0xa2, 0xe0, 0x80, 0x4d, 0xc6, 0x00, + 0xe6, 0x09, 0x20, 0xc6, 0x00, 0x26, 0x00, 0x86, + 0x80, 0xe4, 0x36, 0xe0, 0x19, 0x06, 0xe0, 0x68, + 0xe5, 0x25, 0x40, 0xc6, 0xc4, 0x20, 0xe9, 0x02, + 0x60, 0x05, 0x0f, 0xe0, 0x80, 0xb8, 0xe5, 0x16, + 0x06, 0xe0, 0x09, 0xe5, 0x24, 0x66, 0xe9, 0x02, + 0x80, 0x0d, 0xe0, 0x81, 0x48, 0xe5, 0x13, 0x04, + 0x66, 0xe9, 0x02, 0xe0, 0x80, 0x4e, 0xe5, 0x16, + 0x26, 0x05, 0xe9, 0x02, 0x60, 0x16, 0xe0, 0x81, + 0x58, 0xc5, 0x00, 0x65, 0x00, 0x25, 0x00, 0xe5, + 0x07, 0x00, 0xe5, 0x80, 0x3d, 0x20, 0xeb, 0x01, + 0xc6, 0xe0, 0x21, 0xe1, 0x1a, 0xe2, 0x1a, 0xc6, + 0x04, 0x60, 0xe9, 0x02, 0x60, 0x36, 0xe0, 0x82, + 0x89, 0xeb, 0x33, 0x0f, 0x4b, 0x0d, 0x6b, 0xe0, + 0x44, 0xeb, 0x25, 0x0f, 0xeb, 0x07, 0xe0, 0x80, + 0x3a, 0x65, 0x00, 0xe5, 0x13, 0x00, 0x25, 0x00, + 0x05, 0x20, 0x05, 0x00, 0xe5, 0x02, 0x00, 0x65, + 0x00, 0x05, 0x00, 0x05, 0xa0, 0x05, 0x60, 0x05, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x45, 0x00, 0x25, + 0x00, 0x05, 0x20, 0x05, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x25, 0x00, 0x05, + 0x20, 0x65, 0x00, 0xc5, 0x00, 0x65, 0x00, 0x65, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x00, 0xe5, 0x09, + 0x80, 0x45, 0x00, 0x85, 0x00, 0xe5, 0x09, 0xe0, + 0x2c, 0x2c, 0xe0, 0x80, 0x86, 0xef, 0x24, 0x60, + 0xef, 0x5c, 0xe0, 0x04, 0xef, 0x07, 0x20, 0xef, + 0x07, 0x00, 0xef, 0x07, 0x00, 0xef, 0x1d, 0xe0, + 0x02, 0xeb, 0x05, 0xef, 0x80, 0x19, 0xe0, 0x30, + 0xef, 0x15, 0xe0, 0x05, 0xef, 0x24, 0x60, 0xef, + 0x01, 0xc0, 0x2f, 0xe0, 0x06, 0xaf, 0xe0, 0x80, + 0x12, 0xef, 0x80, 0x73, 0x8e, 0xef, 0x82, 0x50, + 0x60, 0xef, 0x09, 0x40, 0xef, 0x05, 0x40, 0xef, + 0x6f, 0x60, 0xef, 0x57, 0xa0, 0xef, 0x04, 0x60, + 0x0f, 0xe0, 0x07, 0xef, 0x04, 0x60, 0xef, 0x30, + 0xe0, 0x00, 0xef, 0x02, 0xa0, 0xef, 0x20, 0xe0, + 0x00, 0xef, 0x16, 0x20, 0xef, 0x04, 0x60, 0x2f, + 0xe0, 0x36, 0xef, 0x80, 0xcc, 0xe0, 0x04, 0xef, + 0x06, 0x20, 0xef, 0x05, 0x40, 0xef, 0x02, 0x80, + 0xef, 0x30, 0xc0, 0xef, 0x07, 0x20, 0xef, 0x03, + 0xa0, 0xef, 0x01, 0xc0, 0xef, 0x80, 0x0b, 0x00, + 0xef, 0x54, 0xe9, 0x02, 0xe0, 0x83, 0x7e, 0xe5, + 0xc0, 0x66, 0x58, 0xe0, 0x18, 0xe5, 0x8f, 0xb2, + 0xa0, 0xe5, 0x80, 0x56, 0x20, 0xe5, 0x95, 0xfa, + 0xe0, 0x06, 0xe5, 0x9c, 0xa9, 0xe0, 0x07, 0xe5, + 0x81, 0xe6, 0xe0, 0x89, 0x1a, 0xe5, 0x81, 0x96, + 0xe0, 0x85, 0x5a, 0xe5, 0x92, 0xc3, 0x80, 0xe5, + 0x8f, 0xd8, 0xe0, 0xca, 0x9b, 0xc9, 0x1b, 0xe0, + 0x16, 0xfb, 0x58, 0xe0, 0x78, 0xe6, 0x80, 0x68, + 0xe0, 0xc0, 0xbd, 0x88, 0xfd, 0xc0, 0xbf, 0x76, + 0x20, 0xfd, 0xc0, 0xbf, 0x76, 0x20, +}; + +typedef enum { + UNICODE_SCRIPT_Unknown, + UNICODE_SCRIPT_Adlam, + UNICODE_SCRIPT_Ahom, + UNICODE_SCRIPT_Anatolian_Hieroglyphs, + UNICODE_SCRIPT_Arabic, + UNICODE_SCRIPT_Armenian, + UNICODE_SCRIPT_Avestan, + UNICODE_SCRIPT_Balinese, + UNICODE_SCRIPT_Bamum, + UNICODE_SCRIPT_Bassa_Vah, + UNICODE_SCRIPT_Batak, + UNICODE_SCRIPT_Bengali, + UNICODE_SCRIPT_Bhaiksuki, + UNICODE_SCRIPT_Bopomofo, + UNICODE_SCRIPT_Brahmi, + UNICODE_SCRIPT_Braille, + UNICODE_SCRIPT_Buginese, + UNICODE_SCRIPT_Buhid, + UNICODE_SCRIPT_Canadian_Aboriginal, + UNICODE_SCRIPT_Carian, + UNICODE_SCRIPT_Caucasian_Albanian, + UNICODE_SCRIPT_Chakma, + UNICODE_SCRIPT_Cham, + UNICODE_SCRIPT_Cherokee, + UNICODE_SCRIPT_Chorasmian, + UNICODE_SCRIPT_Common, + UNICODE_SCRIPT_Coptic, + UNICODE_SCRIPT_Cuneiform, + UNICODE_SCRIPT_Cypriot, + UNICODE_SCRIPT_Cyrillic, + UNICODE_SCRIPT_Cypro_Minoan, + UNICODE_SCRIPT_Deseret, + UNICODE_SCRIPT_Devanagari, + UNICODE_SCRIPT_Dives_Akuru, + UNICODE_SCRIPT_Dogra, + UNICODE_SCRIPT_Duployan, + UNICODE_SCRIPT_Egyptian_Hieroglyphs, + UNICODE_SCRIPT_Elbasan, + UNICODE_SCRIPT_Elymaic, + UNICODE_SCRIPT_Ethiopic, + UNICODE_SCRIPT_Georgian, + UNICODE_SCRIPT_Glagolitic, + UNICODE_SCRIPT_Gothic, + UNICODE_SCRIPT_Garay, + UNICODE_SCRIPT_Grantha, + UNICODE_SCRIPT_Greek, + UNICODE_SCRIPT_Gujarati, + UNICODE_SCRIPT_Gunjala_Gondi, + UNICODE_SCRIPT_Gurmukhi, + UNICODE_SCRIPT_Gurung_Khema, + UNICODE_SCRIPT_Han, + UNICODE_SCRIPT_Hangul, + UNICODE_SCRIPT_Hanifi_Rohingya, + UNICODE_SCRIPT_Hanunoo, + UNICODE_SCRIPT_Hatran, + UNICODE_SCRIPT_Hebrew, + UNICODE_SCRIPT_Hiragana, + UNICODE_SCRIPT_Imperial_Aramaic, + UNICODE_SCRIPT_Inherited, + UNICODE_SCRIPT_Inscriptional_Pahlavi, + UNICODE_SCRIPT_Inscriptional_Parthian, + UNICODE_SCRIPT_Javanese, + UNICODE_SCRIPT_Kaithi, + UNICODE_SCRIPT_Kannada, + UNICODE_SCRIPT_Katakana, + UNICODE_SCRIPT_Kawi, + UNICODE_SCRIPT_Kayah_Li, + UNICODE_SCRIPT_Kharoshthi, + UNICODE_SCRIPT_Khmer, + UNICODE_SCRIPT_Khojki, + UNICODE_SCRIPT_Khitan_Small_Script, + UNICODE_SCRIPT_Khudawadi, + UNICODE_SCRIPT_Kirat_Rai, + UNICODE_SCRIPT_Lao, + UNICODE_SCRIPT_Latin, + UNICODE_SCRIPT_Lepcha, + UNICODE_SCRIPT_Limbu, + UNICODE_SCRIPT_Linear_A, + UNICODE_SCRIPT_Linear_B, + UNICODE_SCRIPT_Lisu, + UNICODE_SCRIPT_Lycian, + UNICODE_SCRIPT_Lydian, + UNICODE_SCRIPT_Makasar, + UNICODE_SCRIPT_Mahajani, + UNICODE_SCRIPT_Malayalam, + UNICODE_SCRIPT_Mandaic, + UNICODE_SCRIPT_Manichaean, + UNICODE_SCRIPT_Marchen, + UNICODE_SCRIPT_Masaram_Gondi, + UNICODE_SCRIPT_Medefaidrin, + UNICODE_SCRIPT_Meetei_Mayek, + UNICODE_SCRIPT_Mende_Kikakui, + UNICODE_SCRIPT_Meroitic_Cursive, + UNICODE_SCRIPT_Meroitic_Hieroglyphs, + UNICODE_SCRIPT_Miao, + UNICODE_SCRIPT_Modi, + UNICODE_SCRIPT_Mongolian, + UNICODE_SCRIPT_Mro, + UNICODE_SCRIPT_Multani, + UNICODE_SCRIPT_Myanmar, + UNICODE_SCRIPT_Nabataean, + UNICODE_SCRIPT_Nag_Mundari, + UNICODE_SCRIPT_Nandinagari, + UNICODE_SCRIPT_New_Tai_Lue, + UNICODE_SCRIPT_Newa, + UNICODE_SCRIPT_Nko, + UNICODE_SCRIPT_Nushu, + UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong, + UNICODE_SCRIPT_Ogham, + UNICODE_SCRIPT_Ol_Chiki, + UNICODE_SCRIPT_Ol_Onal, + UNICODE_SCRIPT_Old_Hungarian, + UNICODE_SCRIPT_Old_Italic, + UNICODE_SCRIPT_Old_North_Arabian, + UNICODE_SCRIPT_Old_Permic, + UNICODE_SCRIPT_Old_Persian, + UNICODE_SCRIPT_Old_Sogdian, + UNICODE_SCRIPT_Old_South_Arabian, + UNICODE_SCRIPT_Old_Turkic, + UNICODE_SCRIPT_Old_Uyghur, + UNICODE_SCRIPT_Oriya, + UNICODE_SCRIPT_Osage, + UNICODE_SCRIPT_Osmanya, + UNICODE_SCRIPT_Pahawh_Hmong, + UNICODE_SCRIPT_Palmyrene, + UNICODE_SCRIPT_Pau_Cin_Hau, + UNICODE_SCRIPT_Phags_Pa, + UNICODE_SCRIPT_Phoenician, + UNICODE_SCRIPT_Psalter_Pahlavi, + UNICODE_SCRIPT_Rejang, + UNICODE_SCRIPT_Runic, + UNICODE_SCRIPT_Samaritan, + UNICODE_SCRIPT_Saurashtra, + UNICODE_SCRIPT_Sharada, + UNICODE_SCRIPT_Shavian, + UNICODE_SCRIPT_Siddham, + UNICODE_SCRIPT_SignWriting, + UNICODE_SCRIPT_Sinhala, + UNICODE_SCRIPT_Sogdian, + UNICODE_SCRIPT_Sora_Sompeng, + UNICODE_SCRIPT_Soyombo, + UNICODE_SCRIPT_Sundanese, + UNICODE_SCRIPT_Sunuwar, + UNICODE_SCRIPT_Syloti_Nagri, + UNICODE_SCRIPT_Syriac, + UNICODE_SCRIPT_Tagalog, + UNICODE_SCRIPT_Tagbanwa, + UNICODE_SCRIPT_Tai_Le, + UNICODE_SCRIPT_Tai_Tham, + UNICODE_SCRIPT_Tai_Viet, + UNICODE_SCRIPT_Takri, + UNICODE_SCRIPT_Tamil, + UNICODE_SCRIPT_Tangut, + UNICODE_SCRIPT_Telugu, + UNICODE_SCRIPT_Thaana, + UNICODE_SCRIPT_Thai, + UNICODE_SCRIPT_Tibetan, + UNICODE_SCRIPT_Tifinagh, + UNICODE_SCRIPT_Tirhuta, + UNICODE_SCRIPT_Tangsa, + UNICODE_SCRIPT_Todhri, + UNICODE_SCRIPT_Toto, + UNICODE_SCRIPT_Tulu_Tigalari, + UNICODE_SCRIPT_Ugaritic, + UNICODE_SCRIPT_Vai, + UNICODE_SCRIPT_Vithkuqi, + UNICODE_SCRIPT_Wancho, + UNICODE_SCRIPT_Warang_Citi, + UNICODE_SCRIPT_Yezidi, + UNICODE_SCRIPT_Yi, + UNICODE_SCRIPT_Zanabazar_Square, + UNICODE_SCRIPT_COUNT, +} UnicodeScriptEnum; + +static const char unicode_script_name_table[] = + "Adlam,Adlm" "\0" + "Ahom,Ahom" "\0" + "Anatolian_Hieroglyphs,Hluw" "\0" + "Arabic,Arab" "\0" + "Armenian,Armn" "\0" + "Avestan,Avst" "\0" + "Balinese,Bali" "\0" + "Bamum,Bamu" "\0" + "Bassa_Vah,Bass" "\0" + "Batak,Batk" "\0" + "Bengali,Beng" "\0" + "Bhaiksuki,Bhks" "\0" + "Bopomofo,Bopo" "\0" + "Brahmi,Brah" "\0" + "Braille,Brai" "\0" + "Buginese,Bugi" "\0" + "Buhid,Buhd" "\0" + "Canadian_Aboriginal,Cans" "\0" + "Carian,Cari" "\0" + "Caucasian_Albanian,Aghb" "\0" + "Chakma,Cakm" "\0" + "Cham,Cham" "\0" + "Cherokee,Cher" "\0" + "Chorasmian,Chrs" "\0" + "Common,Zyyy" "\0" + "Coptic,Copt,Qaac" "\0" + "Cuneiform,Xsux" "\0" + "Cypriot,Cprt" "\0" + "Cyrillic,Cyrl" "\0" + "Cypro_Minoan,Cpmn" "\0" + "Deseret,Dsrt" "\0" + "Devanagari,Deva" "\0" + "Dives_Akuru,Diak" "\0" + "Dogra,Dogr" "\0" + "Duployan,Dupl" "\0" + "Egyptian_Hieroglyphs,Egyp" "\0" + "Elbasan,Elba" "\0" + "Elymaic,Elym" "\0" + "Ethiopic,Ethi" "\0" + "Georgian,Geor" "\0" + "Glagolitic,Glag" "\0" + "Gothic,Goth" "\0" + "Garay,Gara" "\0" + "Grantha,Gran" "\0" + "Greek,Grek" "\0" + "Gujarati,Gujr" "\0" + "Gunjala_Gondi,Gong" "\0" + "Gurmukhi,Guru" "\0" + "Gurung_Khema,Gukh" "\0" + "Han,Hani" "\0" + "Hangul,Hang" "\0" + "Hanifi_Rohingya,Rohg" "\0" + "Hanunoo,Hano" "\0" + "Hatran,Hatr" "\0" + "Hebrew,Hebr" "\0" + "Hiragana,Hira" "\0" + "Imperial_Aramaic,Armi" "\0" + "Inherited,Zinh,Qaai" "\0" + "Inscriptional_Pahlavi,Phli" "\0" + "Inscriptional_Parthian,Prti" "\0" + "Javanese,Java" "\0" + "Kaithi,Kthi" "\0" + "Kannada,Knda" "\0" + "Katakana,Kana" "\0" + "Kawi,Kawi" "\0" + "Kayah_Li,Kali" "\0" + "Kharoshthi,Khar" "\0" + "Khmer,Khmr" "\0" + "Khojki,Khoj" "\0" + "Khitan_Small_Script,Kits" "\0" + "Khudawadi,Sind" "\0" + "Kirat_Rai,Krai" "\0" + "Lao,Laoo" "\0" + "Latin,Latn" "\0" + "Lepcha,Lepc" "\0" + "Limbu,Limb" "\0" + "Linear_A,Lina" "\0" + "Linear_B,Linb" "\0" + "Lisu,Lisu" "\0" + "Lycian,Lyci" "\0" + "Lydian,Lydi" "\0" + "Makasar,Maka" "\0" + "Mahajani,Mahj" "\0" + "Malayalam,Mlym" "\0" + "Mandaic,Mand" "\0" + "Manichaean,Mani" "\0" + "Marchen,Marc" "\0" + "Masaram_Gondi,Gonm" "\0" + "Medefaidrin,Medf" "\0" + "Meetei_Mayek,Mtei" "\0" + "Mende_Kikakui,Mend" "\0" + "Meroitic_Cursive,Merc" "\0" + "Meroitic_Hieroglyphs,Mero" "\0" + "Miao,Plrd" "\0" + "Modi,Modi" "\0" + "Mongolian,Mong" "\0" + "Mro,Mroo" "\0" + "Multani,Mult" "\0" + "Myanmar,Mymr" "\0" + "Nabataean,Nbat" "\0" + "Nag_Mundari,Nagm" "\0" + "Nandinagari,Nand" "\0" + "New_Tai_Lue,Talu" "\0" + "Newa,Newa" "\0" + "Nko,Nkoo" "\0" + "Nushu,Nshu" "\0" + "Nyiakeng_Puachue_Hmong,Hmnp" "\0" + "Ogham,Ogam" "\0" + "Ol_Chiki,Olck" "\0" + "Ol_Onal,Onao" "\0" + "Old_Hungarian,Hung" "\0" + "Old_Italic,Ital" "\0" + "Old_North_Arabian,Narb" "\0" + "Old_Permic,Perm" "\0" + "Old_Persian,Xpeo" "\0" + "Old_Sogdian,Sogo" "\0" + "Old_South_Arabian,Sarb" "\0" + "Old_Turkic,Orkh" "\0" + "Old_Uyghur,Ougr" "\0" + "Oriya,Orya" "\0" + "Osage,Osge" "\0" + "Osmanya,Osma" "\0" + "Pahawh_Hmong,Hmng" "\0" + "Palmyrene,Palm" "\0" + "Pau_Cin_Hau,Pauc" "\0" + "Phags_Pa,Phag" "\0" + "Phoenician,Phnx" "\0" + "Psalter_Pahlavi,Phlp" "\0" + "Rejang,Rjng" "\0" + "Runic,Runr" "\0" + "Samaritan,Samr" "\0" + "Saurashtra,Saur" "\0" + "Sharada,Shrd" "\0" + "Shavian,Shaw" "\0" + "Siddham,Sidd" "\0" + "SignWriting,Sgnw" "\0" + "Sinhala,Sinh" "\0" + "Sogdian,Sogd" "\0" + "Sora_Sompeng,Sora" "\0" + "Soyombo,Soyo" "\0" + "Sundanese,Sund" "\0" + "Sunuwar,Sunu" "\0" + "Syloti_Nagri,Sylo" "\0" + "Syriac,Syrc" "\0" + "Tagalog,Tglg" "\0" + "Tagbanwa,Tagb" "\0" + "Tai_Le,Tale" "\0" + "Tai_Tham,Lana" "\0" + "Tai_Viet,Tavt" "\0" + "Takri,Takr" "\0" + "Tamil,Taml" "\0" + "Tangut,Tang" "\0" + "Telugu,Telu" "\0" + "Thaana,Thaa" "\0" + "Thai,Thai" "\0" + "Tibetan,Tibt" "\0" + "Tifinagh,Tfng" "\0" + "Tirhuta,Tirh" "\0" + "Tangsa,Tnsa" "\0" + "Todhri,Todr" "\0" + "Toto,Toto" "\0" + "Tulu_Tigalari,Tutg" "\0" + "Ugaritic,Ugar" "\0" + "Vai,Vaii" "\0" + "Vithkuqi,Vith" "\0" + "Wancho,Wcho" "\0" + "Warang_Citi,Wara" "\0" + "Yezidi,Yezi" "\0" + "Yi,Yiii" "\0" + "Zanabazar_Square,Zanb" "\0" +; + +static const uint8_t unicode_script_table[2803] = { + 0xc0, 0x19, 0x99, 0x4a, 0x85, 0x19, 0x99, 0x4a, + 0xae, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x80, 0x4a, + 0x84, 0x19, 0x96, 0x4a, 0x80, 0x19, 0x9e, 0x4a, + 0x80, 0x19, 0xe1, 0x60, 0x4a, 0xa6, 0x19, 0x84, + 0x4a, 0x84, 0x19, 0x81, 0x0d, 0x93, 0x19, 0xe0, + 0x0f, 0x3a, 0x83, 0x2d, 0x80, 0x19, 0x82, 0x2d, + 0x01, 0x83, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x03, + 0x80, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x80, 0x19, + 0x82, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x93, 0x2d, + 0x00, 0xbe, 0x2d, 0x8d, 0x1a, 0x8f, 0x2d, 0xe0, + 0x24, 0x1d, 0x81, 0x3a, 0xe0, 0x48, 0x1d, 0x00, + 0xa5, 0x05, 0x01, 0xb1, 0x05, 0x01, 0x82, 0x05, + 0x00, 0xb6, 0x37, 0x07, 0x9a, 0x37, 0x03, 0x85, + 0x37, 0x0a, 0x84, 0x04, 0x80, 0x19, 0x85, 0x04, + 0x80, 0x19, 0x8d, 0x04, 0x80, 0x19, 0x82, 0x04, + 0x80, 0x19, 0x9f, 0x04, 0x80, 0x19, 0x89, 0x04, + 0x8a, 0x3a, 0x99, 0x04, 0x80, 0x3a, 0xe0, 0x0b, + 0x04, 0x80, 0x19, 0xa1, 0x04, 0x8d, 0x90, 0x00, + 0xbb, 0x90, 0x01, 0x82, 0x90, 0xaf, 0x04, 0xb1, + 0x9a, 0x0d, 0xba, 0x69, 0x01, 0x82, 0x69, 0xad, + 0x83, 0x01, 0x8e, 0x83, 0x00, 0x9b, 0x55, 0x01, + 0x80, 0x55, 0x00, 0x8a, 0x90, 0x04, 0x9e, 0x04, + 0x00, 0x81, 0x04, 0x04, 0xca, 0x04, 0x80, 0x19, + 0x9c, 0x04, 0xd0, 0x20, 0x83, 0x3a, 0x8e, 0x20, + 0x81, 0x19, 0x99, 0x20, 0x83, 0x0b, 0x00, 0x87, + 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x95, 0x0b, 0x00, + 0x86, 0x0b, 0x00, 0x80, 0x0b, 0x02, 0x83, 0x0b, + 0x01, 0x88, 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x83, + 0x0b, 0x07, 0x80, 0x0b, 0x03, 0x81, 0x0b, 0x00, + 0x84, 0x0b, 0x01, 0x98, 0x0b, 0x01, 0x82, 0x30, + 0x00, 0x85, 0x30, 0x03, 0x81, 0x30, 0x01, 0x95, + 0x30, 0x00, 0x86, 0x30, 0x00, 0x81, 0x30, 0x00, + 0x81, 0x30, 0x00, 0x81, 0x30, 0x01, 0x80, 0x30, + 0x00, 0x84, 0x30, 0x03, 0x81, 0x30, 0x01, 0x82, + 0x30, 0x02, 0x80, 0x30, 0x06, 0x83, 0x30, 0x00, + 0x80, 0x30, 0x06, 0x90, 0x30, 0x09, 0x82, 0x2e, + 0x00, 0x88, 0x2e, 0x00, 0x82, 0x2e, 0x00, 0x95, + 0x2e, 0x00, 0x86, 0x2e, 0x00, 0x81, 0x2e, 0x00, + 0x84, 0x2e, 0x01, 0x89, 0x2e, 0x00, 0x82, 0x2e, + 0x00, 0x82, 0x2e, 0x01, 0x80, 0x2e, 0x0e, 0x83, + 0x2e, 0x01, 0x8b, 0x2e, 0x06, 0x86, 0x2e, 0x00, + 0x82, 0x78, 0x00, 0x87, 0x78, 0x01, 0x81, 0x78, + 0x01, 0x95, 0x78, 0x00, 0x86, 0x78, 0x00, 0x81, + 0x78, 0x00, 0x84, 0x78, 0x01, 0x88, 0x78, 0x01, + 0x81, 0x78, 0x01, 0x82, 0x78, 0x06, 0x82, 0x78, + 0x03, 0x81, 0x78, 0x00, 0x84, 0x78, 0x01, 0x91, + 0x78, 0x09, 0x81, 0x97, 0x00, 0x85, 0x97, 0x02, + 0x82, 0x97, 0x00, 0x83, 0x97, 0x02, 0x81, 0x97, + 0x00, 0x80, 0x97, 0x00, 0x81, 0x97, 0x02, 0x81, + 0x97, 0x02, 0x82, 0x97, 0x02, 0x8b, 0x97, 0x03, + 0x84, 0x97, 0x02, 0x82, 0x97, 0x00, 0x83, 0x97, + 0x01, 0x80, 0x97, 0x05, 0x80, 0x97, 0x0d, 0x94, + 0x97, 0x04, 0x8c, 0x99, 0x00, 0x82, 0x99, 0x00, + 0x96, 0x99, 0x00, 0x8f, 0x99, 0x01, 0x88, 0x99, + 0x00, 0x82, 0x99, 0x00, 0x83, 0x99, 0x06, 0x81, + 0x99, 0x00, 0x82, 0x99, 0x01, 0x80, 0x99, 0x01, + 0x83, 0x99, 0x01, 0x89, 0x99, 0x06, 0x88, 0x99, + 0x8c, 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x96, 0x3f, + 0x00, 0x89, 0x3f, 0x00, 0x84, 0x3f, 0x01, 0x88, + 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x83, 0x3f, 0x06, + 0x81, 0x3f, 0x05, 0x81, 0x3f, 0x00, 0x83, 0x3f, + 0x01, 0x89, 0x3f, 0x00, 0x82, 0x3f, 0x0b, 0x8c, + 0x54, 0x00, 0x82, 0x54, 0x00, 0xb2, 0x54, 0x00, + 0x82, 0x54, 0x00, 0x85, 0x54, 0x03, 0x8f, 0x54, + 0x01, 0x99, 0x54, 0x00, 0x82, 0x89, 0x00, 0x91, + 0x89, 0x02, 0x97, 0x89, 0x00, 0x88, 0x89, 0x00, + 0x80, 0x89, 0x01, 0x86, 0x89, 0x02, 0x80, 0x89, + 0x03, 0x85, 0x89, 0x00, 0x80, 0x89, 0x00, 0x87, + 0x89, 0x05, 0x89, 0x89, 0x01, 0x82, 0x89, 0x0b, + 0xb9, 0x9b, 0x03, 0x80, 0x19, 0x9b, 0x9b, 0x24, + 0x81, 0x49, 0x00, 0x80, 0x49, 0x00, 0x84, 0x49, + 0x00, 0x97, 0x49, 0x00, 0x80, 0x49, 0x00, 0x96, + 0x49, 0x01, 0x84, 0x49, 0x00, 0x80, 0x49, 0x00, + 0x86, 0x49, 0x00, 0x89, 0x49, 0x01, 0x83, 0x49, + 0x1f, 0xc7, 0x9c, 0x00, 0xa3, 0x9c, 0x03, 0xa6, + 0x9c, 0x00, 0xa3, 0x9c, 0x00, 0x8e, 0x9c, 0x00, + 0x86, 0x9c, 0x83, 0x19, 0x81, 0x9c, 0x24, 0xe0, + 0x3f, 0x63, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, + 0x80, 0x28, 0x01, 0xaa, 0x28, 0x80, 0x19, 0x83, + 0x28, 0xe0, 0x9f, 0x33, 0xc8, 0x27, 0x00, 0x83, + 0x27, 0x01, 0x86, 0x27, 0x00, 0x80, 0x27, 0x00, + 0x83, 0x27, 0x01, 0xa8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xa0, 0x27, 0x00, 0x83, 0x27, 0x01, 0x86, + 0x27, 0x00, 0x80, 0x27, 0x00, 0x83, 0x27, 0x01, + 0x8e, 0x27, 0x00, 0xb8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xc2, 0x27, 0x01, 0x9f, 0x27, 0x02, 0x99, + 0x27, 0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, + 0xe2, 0x1f, 0x12, 0x9c, 0x6c, 0x02, 0xca, 0x82, + 0x82, 0x19, 0x8a, 0x82, 0x06, 0x95, 0x91, 0x08, + 0x80, 0x91, 0x94, 0x35, 0x81, 0x19, 0x08, 0x93, + 0x11, 0x0b, 0x8c, 0x92, 0x00, 0x82, 0x92, 0x00, + 0x81, 0x92, 0x0b, 0xdd, 0x44, 0x01, 0x89, 0x44, + 0x05, 0x89, 0x44, 0x05, 0x81, 0x60, 0x81, 0x19, + 0x80, 0x60, 0x80, 0x19, 0x93, 0x60, 0x05, 0xd8, + 0x60, 0x06, 0xaa, 0x60, 0x04, 0xc5, 0x12, 0x09, + 0x9e, 0x4c, 0x00, 0x8b, 0x4c, 0x03, 0x8b, 0x4c, + 0x03, 0x80, 0x4c, 0x02, 0x8b, 0x4c, 0x9d, 0x93, + 0x01, 0x84, 0x93, 0x0a, 0xab, 0x67, 0x03, 0x99, + 0x67, 0x05, 0x8a, 0x67, 0x02, 0x81, 0x67, 0x9f, + 0x44, 0x9b, 0x10, 0x01, 0x81, 0x10, 0xbe, 0x94, + 0x00, 0x9c, 0x94, 0x01, 0x8a, 0x94, 0x05, 0x89, + 0x94, 0x05, 0x8d, 0x94, 0x01, 0x9e, 0x3a, 0x30, + 0xcc, 0x07, 0x00, 0xb1, 0x07, 0xbf, 0x8d, 0xb3, + 0x0a, 0x07, 0x83, 0x0a, 0xb7, 0x4b, 0x02, 0x8e, + 0x4b, 0x02, 0x82, 0x4b, 0xaf, 0x6d, 0x8a, 0x1d, + 0x04, 0xaa, 0x28, 0x01, 0x82, 0x28, 0x87, 0x8d, + 0x07, 0x82, 0x3a, 0x80, 0x19, 0x8c, 0x3a, 0x80, + 0x19, 0x86, 0x3a, 0x83, 0x19, 0x80, 0x3a, 0x85, + 0x19, 0x80, 0x3a, 0x82, 0x19, 0x81, 0x3a, 0x80, + 0x19, 0x04, 0xa5, 0x4a, 0x84, 0x2d, 0x80, 0x1d, + 0xb0, 0x4a, 0x84, 0x2d, 0x83, 0x4a, 0x84, 0x2d, + 0x8c, 0x4a, 0x80, 0x1d, 0xc5, 0x4a, 0x80, 0x2d, + 0xbf, 0x3a, 0xe0, 0x9f, 0x4a, 0x95, 0x2d, 0x01, + 0x85, 0x2d, 0x01, 0xa5, 0x2d, 0x01, 0x85, 0x2d, + 0x01, 0x87, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x80, + 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x9e, 0x2d, 0x01, + 0xb4, 0x2d, 0x00, 0x8e, 0x2d, 0x00, 0x8d, 0x2d, + 0x01, 0x85, 0x2d, 0x00, 0x92, 0x2d, 0x01, 0x82, + 0x2d, 0x00, 0x88, 0x2d, 0x00, 0x8b, 0x19, 0x81, + 0x3a, 0xd6, 0x19, 0x00, 0x8a, 0x19, 0x80, 0x4a, + 0x01, 0x8a, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x00, + 0x8c, 0x4a, 0x02, 0xa0, 0x19, 0x0e, 0xa0, 0x3a, + 0x0e, 0xa5, 0x19, 0x80, 0x2d, 0x82, 0x19, 0x81, + 0x4a, 0x85, 0x19, 0x80, 0x4a, 0x9a, 0x19, 0x80, + 0x4a, 0x90, 0x19, 0xa8, 0x4a, 0x82, 0x19, 0x03, + 0xe2, 0x39, 0x19, 0x15, 0x8a, 0x19, 0x14, 0xe3, + 0x3f, 0x19, 0xe0, 0x9f, 0x0f, 0xe2, 0x13, 0x19, + 0x01, 0x9f, 0x19, 0x00, 0xe0, 0x08, 0x19, 0xdf, + 0x29, 0x9f, 0x4a, 0xe0, 0x13, 0x1a, 0x04, 0x86, + 0x1a, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, 0x80, + 0x28, 0x01, 0xb7, 0x9d, 0x06, 0x81, 0x9d, 0x0d, + 0x80, 0x9d, 0x96, 0x27, 0x08, 0x86, 0x27, 0x00, + 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, + 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, + 0x27, 0x00, 0x86, 0x27, 0x00, 0x9f, 0x1d, 0xdd, + 0x19, 0x21, 0x99, 0x32, 0x00, 0xd8, 0x32, 0x0b, + 0xe0, 0x75, 0x32, 0x19, 0x94, 0x19, 0x80, 0x32, + 0x80, 0x19, 0x80, 0x32, 0x98, 0x19, 0x88, 0x32, + 0x83, 0x3a, 0x81, 0x33, 0x87, 0x19, 0x83, 0x32, + 0x83, 0x19, 0x00, 0xd5, 0x38, 0x01, 0x81, 0x3a, + 0x81, 0x19, 0x82, 0x38, 0x80, 0x19, 0xd9, 0x40, + 0x81, 0x19, 0x82, 0x40, 0x04, 0xaa, 0x0d, 0x00, + 0xdd, 0x33, 0x00, 0x8f, 0x19, 0x9f, 0x0d, 0xa5, + 0x19, 0x08, 0x80, 0x19, 0x8f, 0x40, 0x9e, 0x33, + 0x00, 0xbf, 0x19, 0x9e, 0x33, 0xd0, 0x19, 0xae, + 0x40, 0x80, 0x19, 0xd7, 0x40, 0xe0, 0x47, 0x19, + 0xf0, 0x09, 0x5f, 0x32, 0xbf, 0x19, 0xf0, 0x41, + 0x9f, 0x32, 0xe4, 0x2c, 0xa9, 0x02, 0xb6, 0xa9, + 0x08, 0xaf, 0x4f, 0xe0, 0xcb, 0xa4, 0x13, 0xdf, + 0x1d, 0xd7, 0x08, 0x07, 0xa1, 0x19, 0xe0, 0x05, + 0x4a, 0x82, 0x19, 0xc2, 0x4a, 0x01, 0x81, 0x4a, + 0x00, 0x80, 0x4a, 0x00, 0x87, 0x4a, 0x14, 0x8d, + 0x4a, 0xac, 0x8f, 0x02, 0x89, 0x19, 0x05, 0xb7, + 0x7e, 0x07, 0xc5, 0x84, 0x07, 0x8b, 0x84, 0x05, + 0x9f, 0x20, 0xad, 0x42, 0x80, 0x19, 0x80, 0x42, + 0xa3, 0x81, 0x0a, 0x80, 0x81, 0x9c, 0x33, 0x02, + 0xcd, 0x3d, 0x00, 0x80, 0x19, 0x89, 0x3d, 0x03, + 0x81, 0x3d, 0x9e, 0x63, 0x00, 0xb6, 0x16, 0x08, + 0x8d, 0x16, 0x01, 0x89, 0x16, 0x01, 0x83, 0x16, + 0x9f, 0x63, 0xc2, 0x95, 0x17, 0x84, 0x95, 0x96, + 0x5a, 0x09, 0x85, 0x27, 0x01, 0x85, 0x27, 0x01, + 0x85, 0x27, 0x08, 0x86, 0x27, 0x00, 0x86, 0x27, + 0x00, 0xaa, 0x4a, 0x80, 0x19, 0x88, 0x4a, 0x80, + 0x2d, 0x83, 0x4a, 0x81, 0x19, 0x03, 0xcf, 0x17, + 0xad, 0x5a, 0x01, 0x89, 0x5a, 0x05, 0xf0, 0x1b, + 0x43, 0x33, 0x0b, 0x96, 0x33, 0x03, 0xb0, 0x33, + 0x70, 0x10, 0xa3, 0xe1, 0x0d, 0x32, 0x01, 0xe0, + 0x09, 0x32, 0x25, 0x86, 0x4a, 0x0b, 0x84, 0x05, + 0x04, 0x99, 0x37, 0x00, 0x84, 0x37, 0x00, 0x80, + 0x37, 0x00, 0x81, 0x37, 0x00, 0x81, 0x37, 0x00, + 0x89, 0x37, 0xe0, 0x12, 0x04, 0x0f, 0xe1, 0x0a, + 0x04, 0x81, 0x19, 0xcf, 0x04, 0x01, 0xb5, 0x04, + 0x06, 0x80, 0x04, 0x1f, 0x8f, 0x04, 0x8f, 0x3a, + 0x89, 0x19, 0x05, 0x8d, 0x3a, 0x81, 0x1d, 0xa2, + 0x19, 0x00, 0x92, 0x19, 0x00, 0x83, 0x19, 0x03, + 0x84, 0x04, 0x00, 0xe0, 0x26, 0x04, 0x01, 0x80, + 0x19, 0x00, 0x9f, 0x19, 0x99, 0x4a, 0x85, 0x19, + 0x99, 0x4a, 0x8a, 0x19, 0x89, 0x40, 0x80, 0x19, + 0xac, 0x40, 0x81, 0x19, 0x9e, 0x33, 0x02, 0x85, + 0x33, 0x01, 0x85, 0x33, 0x01, 0x85, 0x33, 0x01, + 0x82, 0x33, 0x02, 0x86, 0x19, 0x00, 0x86, 0x19, + 0x09, 0x84, 0x19, 0x01, 0x8b, 0x4e, 0x00, 0x99, + 0x4e, 0x00, 0x92, 0x4e, 0x00, 0x81, 0x4e, 0x00, + 0x8e, 0x4e, 0x01, 0x8d, 0x4e, 0x21, 0xe0, 0x1a, + 0x4e, 0x04, 0x82, 0x19, 0x03, 0xac, 0x19, 0x02, + 0x88, 0x19, 0xce, 0x2d, 0x00, 0x8c, 0x19, 0x02, + 0x80, 0x2d, 0x2e, 0xac, 0x19, 0x80, 0x3a, 0x60, + 0x21, 0x9c, 0x50, 0x02, 0xb0, 0x13, 0x0e, 0x80, + 0x3a, 0x9a, 0x19, 0x03, 0xa3, 0x70, 0x08, 0x82, + 0x70, 0x9a, 0x2a, 0x04, 0xaa, 0x72, 0x04, 0x9d, + 0xa3, 0x00, 0x80, 0xa3, 0xa3, 0x73, 0x03, 0x8d, + 0x73, 0x29, 0xcf, 0x1f, 0xaf, 0x86, 0x9d, 0x7a, + 0x01, 0x89, 0x7a, 0x05, 0xa3, 0x79, 0x03, 0xa3, + 0x79, 0x03, 0xa7, 0x25, 0x07, 0xb3, 0x14, 0x0a, + 0x80, 0x14, 0x8a, 0xa5, 0x00, 0x8e, 0xa5, 0x00, + 0x86, 0xa5, 0x00, 0x81, 0xa5, 0x00, 0x8a, 0xa5, + 0x00, 0x8e, 0xa5, 0x00, 0x86, 0xa5, 0x00, 0x81, + 0xa5, 0x02, 0xb3, 0xa0, 0x0b, 0xe0, 0xd6, 0x4d, + 0x08, 0x95, 0x4d, 0x09, 0x87, 0x4d, 0x17, 0x85, + 0x4a, 0x00, 0xa9, 0x4a, 0x00, 0x88, 0x4a, 0x44, + 0x85, 0x1c, 0x01, 0x80, 0x1c, 0x00, 0xab, 0x1c, + 0x00, 0x81, 0x1c, 0x02, 0x80, 0x1c, 0x01, 0x80, + 0x1c, 0x95, 0x39, 0x00, 0x88, 0x39, 0x9f, 0x7c, + 0x9e, 0x64, 0x07, 0x88, 0x64, 0x2f, 0x92, 0x36, + 0x00, 0x81, 0x36, 0x04, 0x84, 0x36, 0x9b, 0x7f, + 0x02, 0x80, 0x7f, 0x99, 0x51, 0x04, 0x80, 0x51, + 0x3f, 0x9f, 0x5d, 0x97, 0x5c, 0x03, 0x93, 0x5c, + 0x01, 0xad, 0x5c, 0x83, 0x43, 0x00, 0x81, 0x43, + 0x04, 0x87, 0x43, 0x00, 0x82, 0x43, 0x00, 0x9c, + 0x43, 0x01, 0x82, 0x43, 0x03, 0x89, 0x43, 0x06, + 0x88, 0x43, 0x06, 0x9f, 0x75, 0x9f, 0x71, 0x1f, + 0xa6, 0x56, 0x03, 0x8b, 0x56, 0x08, 0xb5, 0x06, + 0x02, 0x86, 0x06, 0x95, 0x3c, 0x01, 0x87, 0x3c, + 0x92, 0x3b, 0x04, 0x87, 0x3b, 0x91, 0x80, 0x06, + 0x83, 0x80, 0x0b, 0x86, 0x80, 0x4f, 0xc8, 0x76, + 0x36, 0xb2, 0x6f, 0x0c, 0xb2, 0x6f, 0x06, 0x85, + 0x6f, 0xa7, 0x34, 0x07, 0x89, 0x34, 0x05, 0xa5, + 0x2b, 0x02, 0x9c, 0x2b, 0x07, 0x81, 0x2b, 0x60, + 0x6f, 0x9e, 0x04, 0x00, 0xa9, 0xa8, 0x00, 0x82, + 0xa8, 0x01, 0x81, 0xa8, 0x0f, 0x82, 0x04, 0x36, + 0x83, 0x04, 0xa7, 0x74, 0x07, 0xa9, 0x8a, 0x15, + 0x99, 0x77, 0x25, 0x9b, 0x18, 0x13, 0x96, 0x26, + 0x08, 0xcd, 0x0e, 0x03, 0xa3, 0x0e, 0x08, 0x80, + 0x0e, 0xc2, 0x3e, 0x09, 0x80, 0x3e, 0x01, 0x98, + 0x8b, 0x06, 0x89, 0x8b, 0x05, 0xb4, 0x15, 0x00, + 0x91, 0x15, 0x07, 0xa6, 0x53, 0x08, 0xdf, 0x85, + 0x00, 0x93, 0x89, 0x0a, 0x91, 0x45, 0x00, 0xae, + 0x45, 0x3d, 0x86, 0x62, 0x00, 0x80, 0x62, 0x00, + 0x83, 0x62, 0x00, 0x8e, 0x62, 0x00, 0x8a, 0x62, + 0x05, 0xba, 0x47, 0x04, 0x89, 0x47, 0x05, 0x83, + 0x2c, 0x00, 0x87, 0x2c, 0x01, 0x81, 0x2c, 0x01, + 0x95, 0x2c, 0x00, 0x86, 0x2c, 0x00, 0x81, 0x2c, + 0x00, 0x84, 0x2c, 0x00, 0x80, 0x3a, 0x88, 0x2c, + 0x01, 0x81, 0x2c, 0x01, 0x82, 0x2c, 0x01, 0x80, + 0x2c, 0x05, 0x80, 0x2c, 0x04, 0x86, 0x2c, 0x01, + 0x86, 0x2c, 0x02, 0x84, 0x2c, 0x0a, 0x89, 0xa2, + 0x00, 0x80, 0xa2, 0x01, 0x80, 0xa2, 0x00, 0xa5, + 0xa2, 0x00, 0x89, 0xa2, 0x00, 0x80, 0xa2, 0x01, + 0x80, 0xa2, 0x00, 0x83, 0xa2, 0x00, 0x89, 0xa2, + 0x00, 0x81, 0xa2, 0x07, 0x81, 0xa2, 0x1c, 0xdb, + 0x68, 0x00, 0x84, 0x68, 0x1d, 0xc7, 0x9e, 0x07, + 0x89, 0x9e, 0x60, 0x45, 0xb5, 0x87, 0x01, 0xa5, + 0x87, 0x21, 0xc4, 0x5f, 0x0a, 0x89, 0x5f, 0x05, + 0x8c, 0x60, 0x12, 0xb9, 0x96, 0x05, 0x89, 0x96, + 0x05, 0x93, 0x63, 0x1b, 0x9a, 0x02, 0x01, 0x8e, + 0x02, 0x03, 0x96, 0x02, 0x60, 0x58, 0xbb, 0x22, + 0x60, 0x03, 0xd2, 0xa7, 0x0b, 0x80, 0xa7, 0x86, + 0x21, 0x01, 0x80, 0x21, 0x01, 0x87, 0x21, 0x00, + 0x81, 0x21, 0x00, 0x9d, 0x21, 0x00, 0x81, 0x21, + 0x01, 0x8b, 0x21, 0x08, 0x89, 0x21, 0x45, 0x87, + 0x66, 0x01, 0xad, 0x66, 0x01, 0x8a, 0x66, 0x1a, + 0xc7, 0xaa, 0x07, 0xd2, 0x8c, 0x0c, 0x8f, 0x12, + 0xb8, 0x7d, 0x06, 0x89, 0x20, 0x60, 0x55, 0xa1, + 0x8e, 0x0d, 0x89, 0x8e, 0x05, 0x88, 0x0c, 0x00, + 0xac, 0x0c, 0x00, 0x8d, 0x0c, 0x09, 0x9c, 0x0c, + 0x02, 0x9f, 0x57, 0x01, 0x95, 0x57, 0x00, 0x8d, + 0x57, 0x48, 0x86, 0x58, 0x00, 0x81, 0x58, 0x00, + 0xab, 0x58, 0x02, 0x80, 0x58, 0x00, 0x81, 0x58, + 0x00, 0x88, 0x58, 0x07, 0x89, 0x58, 0x05, 0x85, + 0x2f, 0x00, 0x81, 0x2f, 0x00, 0xa4, 0x2f, 0x00, + 0x81, 0x2f, 0x00, 0x85, 0x2f, 0x06, 0x89, 0x2f, + 0x60, 0xd5, 0x98, 0x52, 0x06, 0x90, 0x41, 0x00, + 0xa8, 0x41, 0x02, 0x9c, 0x41, 0x54, 0x80, 0x4f, + 0x0e, 0xb1, 0x97, 0x0c, 0x80, 0x97, 0xe3, 0x39, + 0x1b, 0x60, 0x05, 0xe0, 0x0e, 0x1b, 0x00, 0x84, + 0x1b, 0x0a, 0xe0, 0x63, 0x1b, 0x69, 0xeb, 0xe0, + 0x02, 0x1e, 0x0c, 0xe3, 0xf5, 0x24, 0x09, 0xef, + 0x3a, 0x24, 0x04, 0xe1, 0xe6, 0x03, 0x70, 0x0a, + 0x58, 0xb9, 0x31, 0x66, 0x65, 0xe1, 0xd8, 0x08, + 0x06, 0x9e, 0x61, 0x00, 0x89, 0x61, 0x03, 0x81, + 0x61, 0xce, 0x9f, 0x00, 0x89, 0x9f, 0x05, 0x9d, + 0x09, 0x01, 0x85, 0x09, 0x09, 0xc5, 0x7b, 0x09, + 0x89, 0x7b, 0x00, 0x86, 0x7b, 0x00, 0x94, 0x7b, + 0x04, 0x92, 0x7b, 0x61, 0x4f, 0xb9, 0x48, 0x60, + 0x65, 0xda, 0x59, 0x60, 0x04, 0xca, 0x5e, 0x03, + 0xb8, 0x5e, 0x06, 0x90, 0x5e, 0x3f, 0x80, 0x98, + 0x80, 0x6a, 0x81, 0x32, 0x80, 0x46, 0x0a, 0x81, + 0x32, 0x0d, 0xf0, 0x07, 0x97, 0x98, 0x07, 0xe2, + 0x9f, 0x98, 0xe1, 0x75, 0x46, 0x28, 0x80, 0x46, + 0x88, 0x98, 0x70, 0x12, 0x86, 0x83, 0x40, 0x00, + 0x86, 0x40, 0x00, 0x81, 0x40, 0x00, 0x80, 0x40, + 0xe0, 0xbe, 0x38, 0x82, 0x40, 0x0e, 0x80, 0x38, + 0x1c, 0x82, 0x38, 0x01, 0x80, 0x40, 0x0d, 0x83, + 0x40, 0x07, 0xe1, 0x2b, 0x6a, 0x68, 0xa3, 0xe0, + 0x0a, 0x23, 0x04, 0x8c, 0x23, 0x02, 0x88, 0x23, + 0x06, 0x89, 0x23, 0x01, 0x83, 0x23, 0x83, 0x19, + 0x6e, 0xfb, 0xe0, 0x99, 0x19, 0x05, 0xe1, 0x53, + 0x19, 0x4b, 0xad, 0x3a, 0x01, 0x96, 0x3a, 0x08, + 0xe0, 0x13, 0x19, 0x3b, 0xe0, 0x95, 0x19, 0x09, + 0xa6, 0x19, 0x01, 0xbd, 0x19, 0x82, 0x3a, 0x90, + 0x19, 0x87, 0x3a, 0x81, 0x19, 0x86, 0x3a, 0x9d, + 0x19, 0x83, 0x3a, 0xbc, 0x19, 0x14, 0xc5, 0x2d, + 0x60, 0x19, 0x93, 0x19, 0x0b, 0x93, 0x19, 0x0b, + 0xd6, 0x19, 0x08, 0x98, 0x19, 0x60, 0x26, 0xd4, + 0x19, 0x00, 0xc6, 0x19, 0x00, 0x81, 0x19, 0x01, + 0x80, 0x19, 0x01, 0x81, 0x19, 0x01, 0x83, 0x19, + 0x00, 0x8b, 0x19, 0x00, 0x80, 0x19, 0x00, 0x86, + 0x19, 0x00, 0xc0, 0x19, 0x00, 0x83, 0x19, 0x01, + 0x87, 0x19, 0x00, 0x86, 0x19, 0x00, 0x9b, 0x19, + 0x00, 0x83, 0x19, 0x00, 0x84, 0x19, 0x00, 0x80, + 0x19, 0x02, 0x86, 0x19, 0x00, 0xe0, 0xf3, 0x19, + 0x01, 0xe0, 0xc3, 0x19, 0x01, 0xb1, 0x19, 0xe2, + 0x2b, 0x88, 0x0e, 0x84, 0x88, 0x00, 0x8e, 0x88, + 0x63, 0xef, 0x9e, 0x4a, 0x05, 0x85, 0x4a, 0x60, + 0x74, 0x86, 0x29, 0x00, 0x90, 0x29, 0x01, 0x86, + 0x29, 0x00, 0x81, 0x29, 0x00, 0x84, 0x29, 0x04, + 0xbd, 0x1d, 0x20, 0x80, 0x1d, 0x60, 0x0f, 0xac, + 0x6b, 0x02, 0x8d, 0x6b, 0x01, 0x89, 0x6b, 0x03, + 0x81, 0x6b, 0x60, 0xdf, 0x9e, 0xa1, 0x10, 0xb9, + 0xa6, 0x04, 0x80, 0xa6, 0x61, 0x6f, 0xa9, 0x65, + 0x60, 0x75, 0xaa, 0x6e, 0x03, 0x80, 0x6e, 0x61, + 0x7f, 0x86, 0x27, 0x00, 0x83, 0x27, 0x00, 0x81, + 0x27, 0x00, 0x8e, 0x27, 0x00, 0xe0, 0x64, 0x5b, + 0x01, 0x8f, 0x5b, 0x28, 0xcb, 0x01, 0x03, 0x89, + 0x01, 0x03, 0x81, 0x01, 0x62, 0xb0, 0xc3, 0x19, + 0x4b, 0xbc, 0x19, 0x60, 0x61, 0x83, 0x04, 0x00, + 0x9a, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, + 0x01, 0x80, 0x04, 0x00, 0x89, 0x04, 0x00, 0x83, + 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x05, + 0x80, 0x04, 0x03, 0x80, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x80, 0x04, 0x00, 0x82, 0x04, 0x00, 0x81, + 0x04, 0x00, 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, + 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x80, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, + 0x04, 0x01, 0x83, 0x04, 0x00, 0x86, 0x04, 0x00, + 0x83, 0x04, 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x89, 0x04, 0x00, 0x90, 0x04, 0x04, 0x82, + 0x04, 0x00, 0x84, 0x04, 0x00, 0x90, 0x04, 0x33, + 0x81, 0x04, 0x60, 0xad, 0xab, 0x19, 0x03, 0xe0, + 0x03, 0x19, 0x0b, 0x8e, 0x19, 0x01, 0x8e, 0x19, + 0x00, 0x8e, 0x19, 0x00, 0xa4, 0x19, 0x09, 0xe0, + 0x4d, 0x19, 0x37, 0x99, 0x19, 0x80, 0x38, 0x81, + 0x19, 0x0c, 0xab, 0x19, 0x03, 0x88, 0x19, 0x06, + 0x81, 0x19, 0x0d, 0x85, 0x19, 0x60, 0x39, 0xe3, + 0x77, 0x19, 0x03, 0x90, 0x19, 0x02, 0x8c, 0x19, + 0x02, 0xe0, 0x16, 0x19, 0x03, 0xde, 0x19, 0x05, + 0x8b, 0x19, 0x03, 0x80, 0x19, 0x0e, 0x8b, 0x19, + 0x03, 0xb7, 0x19, 0x07, 0x89, 0x19, 0x05, 0xa7, + 0x19, 0x07, 0x9d, 0x19, 0x01, 0x8b, 0x19, 0x03, + 0x81, 0x19, 0x3d, 0xe0, 0xf3, 0x19, 0x0b, 0x8d, + 0x19, 0x01, 0x8c, 0x19, 0x02, 0x89, 0x19, 0x04, + 0xb7, 0x19, 0x06, 0x8e, 0x19, 0x01, 0x8a, 0x19, + 0x05, 0x88, 0x19, 0x06, 0xe0, 0x32, 0x19, 0x00, + 0xe0, 0x05, 0x19, 0x63, 0xa5, 0xf0, 0x96, 0x7f, + 0x32, 0x1f, 0xef, 0xd9, 0x32, 0x05, 0xe0, 0x7d, + 0x32, 0x01, 0xf0, 0x06, 0x21, 0x32, 0x0d, 0xf0, + 0x0c, 0xd0, 0x32, 0x0e, 0xe2, 0x0d, 0x32, 0x69, + 0x41, 0xe1, 0xbd, 0x32, 0x65, 0x81, 0xf0, 0x02, + 0xea, 0x32, 0x04, 0xef, 0xff, 0x32, 0x7a, 0xcb, + 0xf0, 0x80, 0x19, 0x1d, 0xdf, 0x19, 0x60, 0x1f, + 0xe0, 0x8f, 0x3a, +}; + +static const uint8_t unicode_script_ext_table[1253] = { + 0x80, 0x36, 0x00, 0x00, 0x10, 0x06, 0x13, 0x1a, + 0x23, 0x25, 0x28, 0x29, 0x2f, 0x2a, 0x2d, 0x32, + 0x4a, 0x51, 0x53, 0x72, 0x86, 0x81, 0x83, 0x00, + 0x00, 0x07, 0x0b, 0x1d, 0x20, 0x4a, 0x4f, 0x9b, + 0xa1, 0x09, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x00, + 0x00, 0x02, 0x02, 0x0d, 0x4a, 0x00, 0x00, 0x00, + 0x02, 0x4a, 0x4f, 0x08, 0x00, 0x00, 0x02, 0x4a, + 0x9b, 0x00, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x25, + 0x00, 0x00, 0x08, 0x17, 0x1a, 0x1d, 0x2d, 0x4a, + 0x72, 0x8e, 0x93, 0x00, 0x08, 0x17, 0x1d, 0x2d, + 0x4a, 0x79, 0x8e, 0x93, 0xa0, 0x00, 0x04, 0x17, + 0x1d, 0x4a, 0x9d, 0x00, 0x05, 0x29, 0x4a, 0x8e, + 0x90, 0x9b, 0x00, 0x0b, 0x14, 0x17, 0x1a, 0x1d, + 0x2a, 0x2d, 0x4a, 0x79, 0x90, 0x9d, 0xa0, 0x00, + 0x06, 0x1a, 0x25, 0x29, 0x2a, 0x40, 0x4a, 0x00, + 0x04, 0x1d, 0x2d, 0x4a, 0x72, 0x00, 0x09, 0x1a, + 0x23, 0x37, 0x4a, 0x72, 0x90, 0x93, 0x9d, 0xa0, + 0x00, 0x0a, 0x05, 0x1d, 0x23, 0x2a, 0x2d, 0x37, + 0x4a, 0x72, 0x90, 0x93, 0x00, 0x02, 0x4a, 0x9d, + 0x00, 0x03, 0x23, 0x4a, 0x90, 0x00, 0x04, 0x17, + 0x1d, 0x4a, 0x79, 0x00, 0x03, 0x17, 0x4a, 0x93, + 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x02, 0x27, 0x4a, + 0x00, 0x00, 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x03, + 0x1d, 0x4a, 0xa0, 0x00, 0x00, 0x00, 0x04, 0x2d, + 0x4a, 0x72, 0xa0, 0x0b, 0x00, 0x00, 0x02, 0x4a, + 0x90, 0x01, 0x00, 0x00, 0x05, 0x17, 0x23, 0x40, + 0x4a, 0x90, 0x00, 0x04, 0x17, 0x23, 0x4a, 0x90, + 0x00, 0x02, 0x4a, 0x90, 0x06, 0x00, 0x00, 0x03, + 0x4a, 0x8e, 0x90, 0x00, 0x02, 0x4a, 0x90, 0x00, + 0x00, 0x00, 0x03, 0x17, 0x4a, 0x90, 0x00, 0x06, + 0x14, 0x17, 0x2a, 0x4a, 0x8e, 0x9b, 0x0f, 0x00, + 0x00, 0x01, 0x2d, 0x01, 0x00, 0x00, 0x01, 0x2d, + 0x11, 0x00, 0x00, 0x02, 0x4a, 0x79, 0x04, 0x00, + 0x00, 0x03, 0x14, 0x4a, 0xa0, 0x03, 0x00, 0x0c, + 0x01, 0x4a, 0x03, 0x00, 0x01, 0x02, 0x1a, 0x2d, + 0x80, 0x8c, 0x00, 0x00, 0x02, 0x1d, 0x72, 0x00, + 0x02, 0x1d, 0x29, 0x01, 0x02, 0x1d, 0x4a, 0x00, + 0x02, 0x1d, 0x29, 0x80, 0x80, 0x00, 0x00, 0x03, + 0x05, 0x28, 0x29, 0x80, 0x01, 0x00, 0x00, 0x07, + 0x04, 0x2b, 0x69, 0x34, 0x90, 0x9a, 0xa8, 0x0d, + 0x00, 0x00, 0x07, 0x04, 0x2b, 0x69, 0x34, 0x90, + 0x9a, 0xa8, 0x00, 0x03, 0x04, 0x90, 0x9a, 0x01, + 0x00, 0x00, 0x08, 0x01, 0x04, 0x2b, 0x69, 0x34, + 0x90, 0x9a, 0xa8, 0x1f, 0x00, 0x00, 0x09, 0x01, + 0x04, 0x55, 0x56, 0x77, 0x80, 0x34, 0x8a, 0x90, + 0x09, 0x00, 0x0a, 0x02, 0x04, 0x90, 0x09, 0x00, + 0x09, 0x03, 0x04, 0x9a, 0xa8, 0x05, 0x00, 0x00, + 0x02, 0x04, 0x90, 0x62, 0x00, 0x00, 0x02, 0x04, + 0x34, 0x81, 0xfb, 0x00, 0x00, 0x0d, 0x0b, 0x20, + 0x2c, 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x85, + 0x97, 0x99, 0x9e, 0x00, 0x0c, 0x0b, 0x20, 0x2c, + 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x97, 0x99, + 0x9e, 0x10, 0x00, 0x00, 0x15, 0x0b, 0x20, 0x22, + 0x2f, 0x58, 0x2c, 0x2e, 0x30, 0x3f, 0x53, 0x54, + 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, 0x97, + 0x99, 0x9e, 0x00, 0x17, 0x0b, 0x20, 0x22, 0x2f, + 0x58, 0x2c, 0x2e, 0x31, 0x30, 0x3f, 0x4c, 0x53, + 0x54, 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, + 0x97, 0x99, 0x9e, 0x09, 0x04, 0x20, 0x22, 0x3e, + 0x53, 0x75, 0x00, 0x09, 0x03, 0x0b, 0x15, 0x8f, + 0x75, 0x00, 0x09, 0x02, 0x30, 0x62, 0x75, 0x00, + 0x09, 0x02, 0x2e, 0x45, 0x80, 0x75, 0x00, 0x0d, + 0x02, 0x2c, 0x97, 0x80, 0x71, 0x00, 0x09, 0x03, + 0x3f, 0x66, 0xa2, 0x82, 0xcf, 0x00, 0x09, 0x03, + 0x15, 0x63, 0x93, 0x80, 0x30, 0x00, 0x00, 0x03, + 0x28, 0x29, 0x4a, 0x85, 0x6e, 0x00, 0x02, 0x01, + 0x82, 0x46, 0x00, 0x01, 0x04, 0x11, 0x35, 0x92, + 0x91, 0x80, 0x4a, 0x00, 0x01, 0x02, 0x60, 0x7e, + 0x00, 0x00, 0x00, 0x02, 0x60, 0x7e, 0x84, 0x49, + 0x00, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, 0x00, + 0x01, 0x20, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, + 0x00, 0x03, 0x20, 0x2c, 0x3f, 0x00, 0x01, 0x20, + 0x01, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, + 0x00, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, + 0x00, 0x06, 0x20, 0x3f, 0x54, 0x78, 0x97, 0x99, + 0x00, 0x01, 0x20, 0x01, 0x02, 0x20, 0x85, 0x01, + 0x01, 0x20, 0x00, 0x02, 0x20, 0x85, 0x00, 0x02, + 0x0b, 0x20, 0x06, 0x01, 0x20, 0x00, 0x02, 0x20, + 0x66, 0x00, 0x02, 0x0b, 0x20, 0x01, 0x01, 0x20, + 0x00, 0x02, 0x0b, 0x20, 0x03, 0x01, 0x20, 0x00, + 0x0b, 0x0b, 0x20, 0x2c, 0x3f, 0x54, 0x66, 0x78, + 0x89, 0x99, 0x9e, 0xa2, 0x00, 0x02, 0x20, 0x2c, + 0x00, 0x04, 0x20, 0x2c, 0x3f, 0xa2, 0x01, 0x02, + 0x0b, 0x20, 0x00, 0x01, 0x0b, 0x01, 0x02, 0x20, + 0x2c, 0x00, 0x01, 0x66, 0x80, 0x44, 0x00, 0x01, + 0x01, 0x2d, 0x35, 0x00, 0x00, 0x03, 0x1d, 0x4a, + 0x90, 0x00, 0x00, 0x00, 0x01, 0x90, 0x81, 0xb3, + 0x00, 0x00, 0x03, 0x4a, 0x60, 0x7e, 0x1e, 0x00, + 0x00, 0x02, 0x01, 0x04, 0x09, 0x00, 0x00, 0x06, + 0x13, 0x28, 0x29, 0x6f, 0x50, 0x76, 0x01, 0x00, + 0x00, 0x04, 0x13, 0x2d, 0x6f, 0x5d, 0x80, 0x11, + 0x00, 0x00, 0x03, 0x20, 0x2c, 0x4a, 0x8c, 0xa5, + 0x00, 0x00, 0x02, 0x1a, 0x4a, 0x17, 0x00, 0x00, + 0x02, 0x06, 0x76, 0x00, 0x07, 0x06, 0x13, 0x28, + 0x6f, 0x3e, 0x51, 0x83, 0x09, 0x00, 0x00, 0x01, + 0x23, 0x03, 0x00, 0x00, 0x03, 0x01, 0x04, 0x6f, + 0x00, 0x00, 0x00, 0x02, 0x1d, 0x29, 0x81, 0x2b, + 0x00, 0x0f, 0x02, 0x32, 0x98, 0x00, 0x00, 0x00, + 0x07, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, 0xa9, + 0x00, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, + 0x7e, 0xa9, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x01, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, + 0x01, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, + 0x9c, 0xa9, 0x01, 0x09, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x4f, 0x60, 0x9c, 0xa9, 0x05, 0x06, 0x0d, + 0x33, 0x32, 0x38, 0x40, 0xa9, 0x00, 0x00, 0x00, + 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x07, 0x06, + 0x0d, 0x33, 0x32, 0x38, 0x40, 0xa9, 0x03, 0x05, + 0x0d, 0x33, 0x32, 0x38, 0x40, 0x09, 0x00, 0x03, + 0x02, 0x0d, 0x32, 0x01, 0x00, 0x00, 0x05, 0x0d, + 0x33, 0x32, 0x38, 0x40, 0x04, 0x02, 0x38, 0x40, + 0x00, 0x00, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x03, 0x00, 0x01, 0x03, 0x32, 0x38, 0x40, + 0x01, 0x01, 0x32, 0x58, 0x00, 0x03, 0x02, 0x38, + 0x40, 0x02, 0x00, 0x00, 0x02, 0x38, 0x40, 0x59, + 0x00, 0x00, 0x06, 0x0d, 0x33, 0x32, 0x38, 0x40, + 0xa9, 0x00, 0x02, 0x38, 0x40, 0x80, 0x12, 0x00, + 0x0f, 0x01, 0x32, 0x1f, 0x00, 0x25, 0x01, 0x32, + 0x08, 0x00, 0x00, 0x02, 0x32, 0x98, 0x2f, 0x00, + 0x27, 0x01, 0x32, 0x37, 0x00, 0x30, 0x01, 0x32, + 0x0e, 0x00, 0x0b, 0x01, 0x32, 0x32, 0x00, 0x00, + 0x01, 0x32, 0x57, 0x00, 0x18, 0x01, 0x32, 0x09, + 0x00, 0x04, 0x01, 0x32, 0x5f, 0x00, 0x1e, 0x01, + 0x32, 0xc0, 0x31, 0xef, 0x00, 0x00, 0x02, 0x1d, + 0x29, 0x80, 0x0f, 0x00, 0x07, 0x02, 0x32, 0x4a, + 0x80, 0xa7, 0x00, 0x02, 0x10, 0x20, 0x22, 0x2e, + 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x54, 0x5f, 0x66, + 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x02, 0x0f, 0x20, + 0x22, 0x2e, 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x5f, + 0x66, 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x01, 0x0b, + 0x20, 0x22, 0x2e, 0x30, 0x45, 0x3e, 0x53, 0x5f, + 0x47, 0x96, 0x9e, 0x00, 0x0c, 0x20, 0x22, 0x2e, + 0x30, 0x45, 0x3e, 0x53, 0x5f, 0x85, 0x47, 0x96, + 0x9e, 0x00, 0x0b, 0x20, 0x22, 0x2e, 0x30, 0x45, + 0x3e, 0x53, 0x5f, 0x47, 0x96, 0x9e, 0x80, 0x36, + 0x00, 0x00, 0x03, 0x0b, 0x20, 0xa2, 0x00, 0x00, + 0x00, 0x02, 0x20, 0x97, 0x39, 0x00, 0x00, 0x03, + 0x42, 0x4a, 0x63, 0x80, 0x1f, 0x00, 0x00, 0x02, + 0x10, 0x3d, 0xc0, 0x12, 0xed, 0x00, 0x01, 0x02, + 0x04, 0x69, 0x80, 0x31, 0x00, 0x00, 0x02, 0x04, + 0x9a, 0x09, 0x00, 0x00, 0x02, 0x04, 0x9a, 0x46, + 0x00, 0x01, 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, + 0x80, 0x99, 0x00, 0x04, 0x06, 0x0d, 0x33, 0x32, + 0x38, 0x40, 0xa9, 0x09, 0x00, 0x00, 0x02, 0x38, + 0x40, 0x2c, 0x00, 0x01, 0x02, 0x38, 0x40, 0x80, + 0xdf, 0x00, 0x01, 0x03, 0x1e, 0x1c, 0x4e, 0x00, + 0x02, 0x1c, 0x4e, 0x03, 0x00, 0x2c, 0x03, 0x1c, + 0x4d, 0x4e, 0x02, 0x00, 0x08, 0x02, 0x1c, 0x4e, + 0x81, 0x1f, 0x00, 0x1b, 0x02, 0x04, 0x1a, 0x87, + 0x75, 0x00, 0x00, 0x02, 0x56, 0x77, 0x87, 0x8d, + 0x00, 0x00, 0x02, 0x2c, 0x97, 0x00, 0x00, 0x00, + 0x02, 0x2c, 0x97, 0x36, 0x00, 0x01, 0x02, 0x2c, + 0x97, 0x8c, 0x12, 0x00, 0x01, 0x02, 0x2c, 0x97, + 0x00, 0x00, 0x00, 0x02, 0x2c, 0x97, 0xc0, 0x5c, + 0x4b, 0x00, 0x03, 0x01, 0x23, 0x96, 0x3b, 0x00, + 0x11, 0x01, 0x32, 0x9e, 0x5d, 0x00, 0x01, 0x01, + 0x32, 0xce, 0xcd, 0x2d, 0x00, +}; + +static const uint8_t unicode_prop_Hyphen_table[28] = { + 0xac, 0x80, 0xfe, 0x80, 0x44, 0xdb, 0x80, 0x52, + 0x7a, 0x80, 0x48, 0x08, 0x81, 0x4e, 0x04, 0x80, + 0x42, 0xe2, 0x80, 0x60, 0xcd, 0x66, 0x80, 0x40, + 0xa8, 0x80, 0xd6, 0x80, +}; + +static const uint8_t unicode_prop_Other_Math_table[200] = { + 0xdd, 0x80, 0x43, 0x70, 0x11, 0x80, 0x99, 0x09, + 0x81, 0x5c, 0x1f, 0x80, 0x9a, 0x82, 0x8a, 0x80, + 0x9f, 0x83, 0x97, 0x81, 0x8d, 0x81, 0xc0, 0x8c, + 0x18, 0x11, 0x1c, 0x91, 0x03, 0x01, 0x89, 0x00, + 0x14, 0x28, 0x11, 0x09, 0x02, 0x05, 0x13, 0x24, + 0xca, 0x21, 0x18, 0x08, 0x08, 0x00, 0x21, 0x0b, + 0x0b, 0x91, 0x09, 0x00, 0x06, 0x00, 0x29, 0x41, + 0x21, 0x83, 0x40, 0xa7, 0x08, 0x80, 0x97, 0x80, + 0x90, 0x80, 0x41, 0xbc, 0x81, 0x8b, 0x88, 0x24, + 0x21, 0x09, 0x14, 0x8d, 0x00, 0x01, 0x85, 0x97, + 0x81, 0xb8, 0x00, 0x80, 0x9c, 0x83, 0x88, 0x81, + 0x41, 0x55, 0x81, 0x9e, 0x89, 0x41, 0x92, 0x95, + 0xbe, 0x83, 0x9f, 0x81, 0x60, 0xd4, 0x62, 0x00, + 0x03, 0x80, 0x40, 0xd2, 0x00, 0x80, 0x60, 0xd4, + 0xc0, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, + 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, + 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, + 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x81, + 0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08, + 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, + 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, +}; + +static const uint8_t unicode_prop_Other_Alphabetic_table[443] = { + 0x43, 0x44, 0x80, 0x9c, 0x8c, 0x42, 0x3f, 0x8d, + 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, + 0x06, 0x8f, 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, + 0xa2, 0x80, 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, + 0x88, 0x02, 0x03, 0xe9, 0x80, 0xbb, 0x8b, 0x16, + 0x85, 0x93, 0xb5, 0x09, 0x8e, 0x01, 0x22, 0x89, + 0x81, 0x9c, 0x82, 0xb9, 0x31, 0x09, 0x81, 0x89, + 0x80, 0x89, 0x81, 0x9c, 0x82, 0xb9, 0x23, 0x09, + 0x0b, 0x80, 0x9d, 0x0a, 0x80, 0x8a, 0x82, 0xb9, + 0x38, 0x10, 0x81, 0x94, 0x81, 0x95, 0x13, 0x82, + 0xb9, 0x31, 0x09, 0x81, 0x88, 0x81, 0x89, 0x81, + 0x9d, 0x80, 0xba, 0x22, 0x10, 0x82, 0x89, 0x80, + 0xa7, 0x84, 0xb8, 0x30, 0x10, 0x17, 0x81, 0x8a, + 0x81, 0x9c, 0x82, 0xb9, 0x30, 0x10, 0x17, 0x81, + 0x8a, 0x81, 0x8e, 0x80, 0x8b, 0x83, 0xb9, 0x30, + 0x10, 0x82, 0x89, 0x80, 0x89, 0x81, 0x9c, 0x82, + 0xca, 0x28, 0x00, 0x87, 0x91, 0x81, 0xbc, 0x01, + 0x86, 0x91, 0x80, 0xe2, 0x01, 0x28, 0x81, 0x8f, + 0x80, 0x40, 0xa2, 0x92, 0x88, 0x8a, 0x80, 0xa3, + 0xed, 0x8b, 0x00, 0x0b, 0x96, 0x1b, 0x10, 0x11, + 0x32, 0x83, 0x8c, 0x8b, 0x00, 0x89, 0x83, 0x46, + 0x73, 0x81, 0x9d, 0x81, 0x9d, 0x81, 0x9d, 0x81, + 0xc1, 0x92, 0x40, 0xbb, 0x81, 0xa1, 0x80, 0xf5, + 0x8b, 0x83, 0x88, 0x40, 0xdd, 0x84, 0xb8, 0x89, + 0x81, 0x93, 0xc9, 0x81, 0x8a, 0x82, 0xb0, 0x84, + 0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, 0xb8, + 0x8a, 0xb1, 0x92, 0x41, 0x9b, 0xa1, 0x46, 0xc0, + 0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, 0x87, + 0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, 0x84, + 0xd7, 0x81, 0xb1, 0x8f, 0x00, 0xb8, 0x80, 0xa5, + 0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, 0xa4, + 0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, 0x82, + 0xb1, 0x00, 0x11, 0x0c, 0x80, 0xab, 0x24, 0x80, + 0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, 0x48, + 0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0x43, + 0x13, 0x83, 0xc0, 0x80, 0x41, 0x40, 0x81, 0xce, + 0x80, 0x41, 0x02, 0x82, 0xb4, 0x8d, 0xac, 0x81, + 0x8a, 0x82, 0xac, 0x88, 0x88, 0x80, 0xbc, 0x82, + 0xa3, 0x8b, 0x91, 0x81, 0xb8, 0x82, 0xaf, 0x8c, + 0x8d, 0x81, 0xdb, 0x88, 0x08, 0x28, 0x08, 0x40, + 0x9c, 0x89, 0x96, 0x83, 0xb9, 0x31, 0x09, 0x81, + 0x89, 0x80, 0x89, 0x81, 0xd3, 0x88, 0x00, 0x08, + 0x03, 0x01, 0xe6, 0x8c, 0x02, 0xe9, 0x91, 0x40, + 0xec, 0x31, 0x86, 0x9c, 0x81, 0xd1, 0x8e, 0x00, + 0xe9, 0x8a, 0xe6, 0x8d, 0x41, 0x00, 0x8c, 0x40, + 0xf6, 0x28, 0x09, 0x0a, 0x00, 0x80, 0x40, 0x8d, + 0x31, 0x2b, 0x80, 0x9b, 0x89, 0xa9, 0x20, 0x83, + 0x91, 0x8a, 0xad, 0x8d, 0x41, 0x96, 0x38, 0x86, + 0xd2, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, + 0x10, 0x02, 0x80, 0xc1, 0x20, 0x08, 0x83, 0x41, + 0x5b, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, 0x82, + 0x60, 0x41, 0xdc, 0x90, 0x4e, 0x1f, 0x00, 0xb6, + 0x33, 0xdc, 0x81, 0x60, 0x4c, 0xab, 0x80, 0x60, + 0x23, 0x60, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, + 0x80, 0x48, 0xb6, 0x80, 0x47, 0xe7, 0x99, 0x85, + 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Lowercase_table[69] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88, + 0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x4d, + 0x80, 0x80, 0x4c, 0x2e, 0xbe, 0x8c, 0x80, 0xa1, + 0xa4, 0x42, 0xb0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, + 0x40, 0xd2, 0x8f, 0x43, 0x4f, 0x99, 0x47, 0x91, + 0x81, 0x60, 0x7a, 0x1d, 0x81, 0x40, 0xd1, 0x80, + 0x40, 0x80, 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, + 0x80, 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, + 0x88, 0x60, 0xd8, 0x74, 0xbd, +}; + +static const uint8_t unicode_prop_Other_Uppercase_table[15] = { + 0x60, 0x21, 0x5f, 0x8f, 0x43, 0x45, 0x99, 0x61, + 0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Grapheme_Extend_table[112] = { + 0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80, + 0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe7, + 0x00, 0x03, 0x08, 0x81, 0x88, 0x81, 0xe6, 0x80, + 0x97, 0x80, 0xf6, 0x80, 0x8e, 0x80, 0x49, 0x34, + 0x80, 0x9d, 0x80, 0x43, 0xff, 0x04, 0x00, 0x04, + 0x81, 0xe4, 0x80, 0xc6, 0x81, 0x44, 0x17, 0x80, + 0x50, 0x20, 0x81, 0x60, 0x79, 0x22, 0x80, 0xeb, + 0x80, 0x60, 0x55, 0xdc, 0x81, 0x52, 0x1f, 0x80, + 0xf3, 0x80, 0x41, 0x07, 0x80, 0x8d, 0x80, 0x88, + 0x80, 0xdf, 0x80, 0x88, 0x01, 0x00, 0x14, 0x80, + 0x40, 0xdf, 0x80, 0x8b, 0x80, 0x40, 0xf0, 0x80, + 0x41, 0x05, 0x80, 0x42, 0x78, 0x80, 0x8b, 0x80, + 0x46, 0x02, 0x80, 0x60, 0x50, 0xad, 0x81, 0x60, + 0x61, 0x72, 0x0d, 0x85, 0x6c, 0x2e, 0xac, 0xdf, +}; + +static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table[32] = { + 0x43, 0x4e, 0x80, 0x4e, 0x0e, 0x81, 0x46, 0x52, + 0x81, 0x48, 0xae, 0x80, 0x50, 0xfd, 0x80, 0x60, + 0xce, 0x3a, 0x80, 0xce, 0x88, 0x6d, 0x00, 0x06, + 0x00, 0x9d, 0xdf, 0xff, 0x40, 0xef, 0x4e, 0x0f, +}; + +static const uint8_t unicode_prop_Other_ID_Start_table[11] = { + 0x58, 0x84, 0x81, 0x48, 0x90, 0x80, 0x94, 0x80, + 0x4f, 0x6b, 0x81, +}; + +static const uint8_t unicode_prop_Other_ID_Continue_table[22] = { + 0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0, + 0x88, 0x46, 0x67, 0x80, 0x46, 0x30, 0x81, 0x50, + 0xec, 0x80, 0x60, 0xce, 0x68, 0x80, +}; + +static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table[19] = { + 0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80, + 0x41, 0x7f, 0x81, 0xcf, 0x80, 0x61, 0x07, 0xd9, + 0x80, 0x8e, 0x80, +}; + +static const uint8_t unicode_prop_XID_Start1_table[31] = { + 0x43, 0x79, 0x80, 0x4a, 0xb7, 0x80, 0xfe, 0x80, + 0x60, 0x21, 0xe6, 0x81, 0x60, 0xcb, 0xc0, 0x85, + 0x41, 0x95, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x41, 0x1e, 0x81, +}; + +static const uint8_t unicode_prop_XID_Continue1_table[23] = { + 0x43, 0x79, 0x80, 0x60, 0x2d, 0x1f, 0x81, 0x60, + 0xcb, 0xc0, 0x85, 0x41, 0x95, 0x81, 0xf3, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Titlecased1_table[22] = { + 0x41, 0xc3, 0x08, 0x08, 0x81, 0xa4, 0x81, 0x4e, + 0xdc, 0xaa, 0x0a, 0x4e, 0x87, 0x3f, 0x3f, 0x87, + 0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Casefolded1_table[29] = { + 0x41, 0xef, 0x80, 0x41, 0x9e, 0x80, 0x9e, 0x80, + 0x5a, 0xe4, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, + 0x80, 0xde, 0x06, 0x06, 0x80, 0x8a, 0x09, 0x81, + 0x89, 0x10, 0x81, 0x8d, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table[450] = { + 0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12, + 0x10, 0x82, 0xf3, 0x80, 0x8b, 0x80, 0x40, 0x84, + 0x01, 0x01, 0x80, 0xa2, 0x01, 0x80, 0x40, 0xbb, + 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, 0x81, 0x89, + 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08, 0x07, 0x80, + 0x9e, 0x80, 0xa0, 0x82, 0x9c, 0x80, 0x42, 0x28, + 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, 0xfb, 0x08, + 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11, 0x80, 0x40, + 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, 0x80, 0xa7, + 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, 0x03, 0x03, + 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00, 0x26, 0x80, + 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, 0x80, 0x8b, + 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, 0x46, 0x52, + 0x81, 0xd4, 0x84, 0x45, 0x1b, 0x10, 0x8a, 0x80, + 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, 0xa4, 0x40, + 0xd5, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, 0x80, + 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xb7, 0x05, 0x00, 0x13, 0x05, 0x11, 0x02, 0x0c, + 0x11, 0x00, 0x00, 0x0c, 0x15, 0x05, 0x08, 0x8f, + 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b, 0x00, + 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, 0x80, + 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, 0x01, + 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06, 0x05, + 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, 0x40, + 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, 0x34, + 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6, 0x82, + 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, 0x80, + 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, 0xd5, + 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09, 0x80, + 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, 0x9e, + 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, 0x60, + 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0x40, 0x80, + 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, 0x60, + 0x4d, 0x95, 0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, + 0x00, 0x00, 0x09, 0x82, 0xc3, 0x81, 0xe9, 0xc2, + 0x00, 0x97, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8c, 0x82, 0x99, 0x95, 0x94, 0x81, 0x8b, 0x80, + 0x92, 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86, 0x08, + 0x80, 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d, 0x0d, + 0x0a, 0x16, 0x06, 0x80, 0x88, 0x47, 0x87, 0x20, + 0xa9, 0x80, 0x88, 0x60, 0xb4, 0xe4, 0x83, 0x50, + 0x31, 0xa3, 0x44, 0x63, 0x86, 0x8d, 0x87, 0xbf, + 0x85, 0x42, 0x3e, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x41, 0x23, 0x81, + 0xb1, 0x48, 0x2f, 0xbd, 0x4d, 0x91, 0x18, 0x9a, + 0x01, 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00, + 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, + 0x03, 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22, + 0x04, 0x80, 0x90, 0x42, 0x43, 0x8a, 0x84, 0x9e, + 0x80, 0x9f, 0x99, 0x82, 0xa2, 0x80, 0xee, 0x82, + 0x8c, 0xab, 0x83, 0x88, 0x31, 0x49, 0x9d, 0x89, + 0x60, 0xfc, 0x05, 0x42, 0x1d, 0x6b, 0x05, 0xe1, + 0x4f, 0xff, +}; + +static const uint8_t unicode_prop_ASCII_Hex_Digit_table[5] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_Bidi_Control_table[10] = { + 0x46, 0x1b, 0x80, 0x59, 0xf0, 0x81, 0x99, 0x84, + 0xb6, 0x83, +}; + +static const uint8_t unicode_prop_Dash_table[58] = { + 0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e, + 0x40, 0x80, 0x44, 0x04, 0x80, 0x48, 0x08, 0x85, + 0xbc, 0x80, 0xa6, 0x80, 0x8e, 0x80, 0x41, 0x85, + 0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, 0x0b, 0x80, + 0x9b, 0x80, 0x41, 0xbd, 0x80, 0x92, 0x80, 0xee, + 0x80, 0x60, 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, + 0x80, 0x40, 0xa8, 0x80, 0x4e, 0x5f, 0x80, 0x41, + 0x3d, 0x80, +}; + +static const uint8_t unicode_prop_Deprecated_table[23] = { + 0x41, 0x48, 0x80, 0x45, 0x28, 0x80, 0x49, 0x02, + 0x00, 0x80, 0x48, 0x28, 0x81, 0x48, 0xc4, 0x85, + 0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80, +}; + +static const uint8_t unicode_prop_Diacritic_table[438] = { + 0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81, + 0x41, 0xf6, 0x40, 0x9e, 0x07, 0x25, 0x90, 0x0b, + 0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0, + 0x80, 0xb6, 0x90, 0x80, 0x9a, 0x00, 0x01, 0x00, + 0x40, 0x85, 0x3b, 0x81, 0x40, 0x85, 0x0b, 0x0a, + 0x82, 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1, + 0x81, 0xfd, 0x87, 0xa8, 0x89, 0x8f, 0x9b, 0xbc, + 0x80, 0x8f, 0x02, 0x83, 0x9b, 0x80, 0xc9, 0x80, + 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, + 0x8f, 0x80, 0xae, 0x82, 0xbb, 0x80, 0x8f, 0x06, + 0x80, 0xf6, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, + 0x80, 0x8f, 0x80, 0xec, 0x81, 0x8f, 0x80, 0xfb, + 0x80, 0xee, 0x80, 0x8b, 0x28, 0x80, 0xea, 0x80, + 0x8c, 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03, + 0x81, 0xc1, 0x10, 0x81, 0xbd, 0x80, 0xef, 0x00, + 0x81, 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89, + 0x81, 0x42, 0xc0, 0x82, 0x43, 0xb3, 0x81, 0x9d, + 0x80, 0x40, 0x93, 0x8a, 0x88, 0x80, 0x41, 0x5a, + 0x82, 0x41, 0x23, 0x80, 0x93, 0x39, 0x80, 0xaf, + 0x8e, 0x81, 0x8a, 0xe7, 0x80, 0x8e, 0x80, 0xa5, + 0x88, 0xb5, 0x81, 0xb9, 0x80, 0x8a, 0x81, 0xc1, + 0x81, 0xbf, 0x85, 0xd1, 0x98, 0x18, 0x28, 0x0a, + 0xb1, 0xbe, 0xd8, 0x8b, 0xa4, 0x8a, 0x41, 0xbc, + 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, 0x82, + 0x8c, 0x81, 0x4c, 0xef, 0x82, 0x41, 0x3c, 0x80, + 0x41, 0xf9, 0x85, 0xe8, 0x83, 0xde, 0x80, 0x60, + 0x75, 0x71, 0x80, 0x8b, 0x08, 0x80, 0x9b, 0x81, + 0xd1, 0x81, 0x8d, 0xa1, 0xe5, 0x82, 0xec, 0x81, + 0x8b, 0x80, 0xa4, 0x80, 0x40, 0x96, 0x80, 0x9a, + 0x91, 0xb8, 0x83, 0xa3, 0x80, 0xde, 0x80, 0x8b, + 0x80, 0xa3, 0x80, 0x40, 0x94, 0x82, 0xc0, 0x83, + 0xb2, 0x80, 0xe3, 0x84, 0x88, 0x82, 0xff, 0x81, + 0x60, 0x4f, 0x2f, 0x80, 0x43, 0x00, 0x8f, 0x41, + 0x0d, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, + 0x80, 0x42, 0xfb, 0x80, 0x44, 0x9e, 0x28, 0xa9, + 0x80, 0x88, 0x42, 0x7c, 0x13, 0x80, 0x40, 0xa4, + 0x81, 0x42, 0x3a, 0x85, 0xa5, 0x80, 0x99, 0x84, + 0x41, 0x8e, 0x82, 0xc5, 0x8a, 0xb0, 0x83, 0x40, + 0xbf, 0x80, 0xa8, 0x80, 0xc7, 0x81, 0xf7, 0x81, + 0xbd, 0x80, 0xcb, 0x80, 0x88, 0x82, 0xe7, 0x81, + 0x40, 0xb1, 0x81, 0xcf, 0x81, 0x8f, 0x80, 0x97, + 0x32, 0x84, 0xd8, 0x10, 0x81, 0x8c, 0x81, 0xde, + 0x02, 0x80, 0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, + 0x80, 0xf5, 0x81, 0xf2, 0x80, 0x41, 0x0c, 0x81, + 0x41, 0x01, 0x0b, 0x80, 0x40, 0x9b, 0x80, 0xd2, + 0x80, 0x91, 0x80, 0xd0, 0x80, 0x41, 0xa4, 0x80, + 0x41, 0x01, 0x00, 0x81, 0xd0, 0x80, 0x41, 0xa8, + 0x81, 0x96, 0x80, 0x54, 0xeb, 0x8e, 0x60, 0x2c, + 0xd8, 0x80, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x42, + 0x33, 0x81, 0x42, 0x21, 0x90, 0xcf, 0x81, 0x60, + 0x3f, 0xfd, 0x18, 0x30, 0x81, 0x5f, 0x00, 0xad, + 0x81, 0x96, 0x42, 0x1f, 0x12, 0x2f, 0x39, 0x86, + 0x9d, 0x83, 0x4e, 0x81, 0xbd, 0x40, 0xc1, 0x86, + 0x41, 0x76, 0x80, 0xbc, 0x83, 0x42, 0xfd, 0x81, + 0x42, 0xdf, 0x86, 0xec, 0x10, 0x82, +}; + +static const uint8_t unicode_prop_Extender_table[111] = { + 0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d, + 0x80, 0x41, 0xb8, 0x80, 0x42, 0x75, 0x80, 0x40, + 0x88, 0x80, 0xd8, 0x80, 0x42, 0xef, 0x80, 0xfe, + 0x80, 0x49, 0x42, 0x80, 0xb7, 0x80, 0x42, 0x62, + 0x80, 0x41, 0x8d, 0x80, 0xc3, 0x80, 0x53, 0x88, + 0x80, 0xaa, 0x84, 0xe6, 0x81, 0xdc, 0x82, 0x60, + 0x6f, 0x15, 0x80, 0x45, 0xf5, 0x80, 0x43, 0xc1, + 0x80, 0x95, 0x80, 0x40, 0x88, 0x80, 0xeb, 0x80, + 0x94, 0x81, 0x60, 0x54, 0x7a, 0x80, 0x48, 0x0f, + 0x81, 0x45, 0xca, 0x80, 0x9a, 0x03, 0x80, 0x44, + 0xc6, 0x80, 0x41, 0x24, 0x80, 0xf3, 0x81, 0x41, + 0xf1, 0x82, 0x44, 0xce, 0x80, 0x60, 0x50, 0xa8, + 0x81, 0x44, 0x9b, 0x08, 0x80, 0x60, 0x71, 0x57, + 0x81, 0x44, 0xb0, 0x80, 0x43, 0x53, 0x82, +}; + +static const uint8_t unicode_prop_Hex_Digit_table[12] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, 0x60, 0xfe, 0xa8, + 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_IDS_Unary_Operator_table[4] = { + 0x60, 0x2f, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_IDS_Binary_Operator_table[8] = { + 0x60, 0x2f, 0xef, 0x09, 0x89, 0x41, 0xf0, 0x80, +}; + +static const uint8_t unicode_prop_IDS_Trinary_Operator_table[4] = { + 0x60, 0x2f, 0xf1, 0x81, +}; + +static const uint8_t unicode_prop_Ideographic_table[72] = { + 0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82, + 0x43, 0xc4, 0x59, 0xbf, 0xbf, 0x60, 0x51, 0xff, + 0x60, 0x58, 0xff, 0x41, 0x6d, 0x81, 0xe9, 0x60, + 0x75, 0x09, 0x80, 0x9a, 0x57, 0xf7, 0x87, 0x44, + 0xd5, 0xa8, 0x89, 0x60, 0x24, 0x66, 0x41, 0x8b, + 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xdf, 0x9f, 0x50, + 0x39, 0x85, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, + 0x5d, 0x30, 0x8e, 0x42, 0x6d, 0x49, 0xa1, 0x42, + 0x1d, 0x45, 0xe1, 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_Join_Control_table[4] = { + 0x60, 0x20, 0x0b, 0x81, +}; + +static const uint8_t unicode_prop_Logical_Order_Exception_table[15] = { + 0x4e, 0x3f, 0x84, 0xfa, 0x84, 0x4a, 0xef, 0x11, + 0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81, +}; + +static const uint8_t unicode_prop_Modifier_Combining_Mark_table[16] = { + 0x46, 0x53, 0x09, 0x80, 0x40, 0x82, 0x05, 0x02, + 0x81, 0x41, 0xe0, 0x08, 0x12, 0x80, 0x9e, 0x80, +}; + +static const uint8_t unicode_prop_Noncharacter_Code_Point_table[71] = { + 0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_Syntax_table[58] = { + 0xa0, 0x8e, 0x89, 0x86, 0x99, 0x18, 0x80, 0x99, + 0x83, 0xa1, 0x30, 0x00, 0x08, 0x00, 0x0b, 0x03, + 0x02, 0x80, 0x96, 0x80, 0x9e, 0x80, 0x5f, 0x17, + 0x97, 0x87, 0x8e, 0x81, 0x92, 0x80, 0x89, 0x41, + 0x30, 0x42, 0xcf, 0x40, 0x9f, 0x42, 0x75, 0x9d, + 0x44, 0x6b, 0x41, 0xff, 0xff, 0x41, 0x80, 0x13, + 0x98, 0x8e, 0x80, 0x60, 0xcd, 0x0c, 0x81, 0x41, + 0x04, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_White_Space_table[11] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x5f, 0x87, + 0x81, 0x97, 0x81, +}; + +static const uint8_t unicode_prop_Quotation_Mark_table[31] = { + 0xa1, 0x03, 0x80, 0x40, 0x82, 0x80, 0x8e, 0x80, + 0x5f, 0x5b, 0x87, 0x98, 0x81, 0x4e, 0x06, 0x80, + 0x41, 0xc8, 0x83, 0x8c, 0x82, 0x60, 0xce, 0x20, + 0x83, 0x40, 0xbc, 0x03, 0x80, 0xd9, 0x81, +}; + +static const uint8_t unicode_prop_Radical_table[9] = { + 0x60, 0x2e, 0x7f, 0x99, 0x80, 0xd8, 0x8b, 0x40, + 0xd5, +}; + +static const uint8_t unicode_prop_Regional_Indicator_table[4] = { + 0x61, 0xf1, 0xe5, 0x99, +}; + +static const uint8_t unicode_prop_Sentence_Terminal_table[213] = { + 0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48, + 0x80, 0x40, 0x92, 0x82, 0x40, 0xb3, 0x80, 0xaa, + 0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81, + 0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15, + 0x03, 0x81, 0x43, 0x04, 0x80, 0x40, 0xc5, 0x81, + 0x40, 0x9c, 0x81, 0xac, 0x04, 0x80, 0x41, 0x39, + 0x81, 0x41, 0x61, 0x83, 0x40, 0xa1, 0x81, 0x89, + 0x09, 0x81, 0x9c, 0x82, 0x40, 0xba, 0x81, 0xc0, + 0x81, 0x43, 0xa3, 0x80, 0x96, 0x81, 0x88, 0x82, + 0x4c, 0xae, 0x82, 0x41, 0x31, 0x80, 0x8c, 0x80, + 0x95, 0x81, 0x41, 0xac, 0x80, 0x60, 0x74, 0xfb, + 0x80, 0x41, 0x0d, 0x81, 0x40, 0xe2, 0x02, 0x80, + 0x41, 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, + 0x97, 0x81, 0x40, 0x92, 0x82, 0x40, 0x8f, 0x81, + 0x40, 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, + 0xba, 0x02, 0x81, 0x40, 0xa8, 0x80, 0x8b, 0x80, + 0x8f, 0x80, 0xc0, 0x80, 0x4a, 0xf3, 0x81, 0x44, + 0xfc, 0x84, 0xab, 0x83, 0x40, 0xbc, 0x81, 0xf4, + 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, + 0x81, 0xd7, 0x08, 0x81, 0xeb, 0x80, 0x41, 0x29, + 0x81, 0xf4, 0x81, 0x41, 0x74, 0x0c, 0x8e, 0xe8, + 0x81, 0x40, 0xf8, 0x82, 0x42, 0x04, 0x00, 0x80, + 0x40, 0xfa, 0x81, 0xd6, 0x81, 0x41, 0xa3, 0x81, + 0x42, 0xb3, 0x81, 0xc9, 0x81, 0x60, 0x4b, 0x28, + 0x81, 0x40, 0x84, 0x80, 0xc0, 0x81, 0x8a, 0x80, + 0x42, 0x28, 0x81, 0x41, 0x27, 0x80, 0x60, 0x4e, + 0x05, 0x80, 0x5d, 0xe7, 0x80, +}; + +static const uint8_t unicode_prop_Soft_Dotted_table[79] = { + 0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80, + 0x9d, 0x80, 0xb3, 0x80, 0x93, 0x80, 0x41, 0x3f, + 0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2, + 0x80, 0x8c, 0x02, 0x80, 0x40, 0x83, 0x80, 0x40, + 0x9c, 0x80, 0x41, 0xa4, 0x80, 0x40, 0xd5, 0x81, + 0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0x48, + 0x85, 0x80, 0x41, 0x30, 0x81, 0x99, 0x80, +}; + +static const uint8_t unicode_prop_Terminal_Punctuation_table[264] = { + 0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, + 0x43, 0x3d, 0x07, 0x80, 0x42, 0x00, 0x80, 0xb8, + 0x80, 0xc7, 0x80, 0x8d, 0x00, 0x82, 0x40, 0xb3, + 0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5, + 0x28, 0x87, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, + 0xf3, 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, + 0x81, 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, + 0x82, 0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, + 0x19, 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, + 0x40, 0xa1, 0x81, 0x89, 0x08, 0x82, 0x9c, 0x82, + 0x40, 0xba, 0x84, 0xbd, 0x81, 0x43, 0xa3, 0x80, + 0x96, 0x81, 0x88, 0x82, 0x4c, 0xae, 0x82, 0x41, + 0x31, 0x80, 0x8c, 0x03, 0x80, 0x89, 0x00, 0x0a, + 0x81, 0x41, 0xab, 0x81, 0x60, 0x74, 0xfa, 0x81, + 0x41, 0x0c, 0x82, 0x40, 0xe2, 0x84, 0x41, 0x7d, + 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x96, 0x82, + 0x40, 0x92, 0x82, 0xfe, 0x80, 0x8f, 0x81, 0x40, + 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, 0xb8, + 0x10, 0x83, 0x40, 0xa8, 0x80, 0x89, 0x00, 0x80, + 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80, 0x44, 0x39, + 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80, 0x40, 0xc6, + 0x80, 0x41, 0x35, 0x81, 0x40, 0x97, 0x85, 0xc3, + 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84, 0xab, 0x83, + 0x40, 0xbc, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40, + 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, 0x84, 0xeb, + 0x80, 0x41, 0x29, 0x81, 0xf4, 0x82, 0x8b, 0x81, + 0x41, 0x65, 0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, + 0x82, 0x42, 0x04, 0x00, 0x80, 0x40, 0xfa, 0x81, + 0xd6, 0x0b, 0x81, 0x41, 0x9d, 0x82, 0xac, 0x80, + 0x42, 0x84, 0x81, 0xc9, 0x81, 0x45, 0x2a, 0x84, + 0x60, 0x45, 0xf8, 0x81, 0x40, 0x84, 0x80, 0xc0, + 0x82, 0x89, 0x80, 0x42, 0x28, 0x81, 0x41, 0x26, + 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, 0x83, +}; + +static const uint8_t unicode_prop_Unified_Ideograph_table[48] = { + 0x60, 0x33, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x51, + 0xff, 0x60, 0x5a, 0x0d, 0x08, 0x00, 0x81, 0x89, + 0x00, 0x00, 0x09, 0x82, 0x61, 0x05, 0xd5, 0x60, + 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, 0xdd, + 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, 0x42, + 0x6d, 0x51, 0xa1, 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_Variation_Selector_table[13] = { + 0x58, 0x0a, 0x10, 0x80, 0x60, 0xe5, 0xef, 0x8f, + 0x6d, 0x02, 0xef, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_Bidi_Mirrored_table[173] = { + 0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, + 0x9c, 0x00, 0x80, 0xac, 0x80, 0x8e, 0x80, 0x4e, + 0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81, + 0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0, + 0x80, 0x40, 0xbf, 0x1a, 0x2a, 0x02, 0x0a, 0x18, + 0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23, + 0x88, 0x08, 0x00, 0x38, 0x9f, 0x0b, 0x20, 0x88, + 0x09, 0x92, 0x21, 0x88, 0x21, 0x0b, 0x97, 0x81, + 0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d, + 0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d, + 0x41, 0x92, 0x95, 0x0d, 0x80, 0x8d, 0x38, 0x35, + 0x10, 0x1c, 0x01, 0x0c, 0x18, 0x02, 0x09, 0x89, + 0x29, 0x81, 0x8b, 0x92, 0x03, 0x08, 0x00, 0x08, + 0x03, 0x21, 0x2a, 0x97, 0x81, 0x8a, 0x0b, 0x18, + 0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00, + 0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80, + 0x42, 0x02, 0x1a, 0x08, 0x81, 0x8d, 0x09, 0x89, + 0xaa, 0x87, 0x41, 0xaa, 0x89, 0x0f, 0x60, 0xce, + 0x3c, 0x2c, 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, + 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, + 0x81, 0x60, 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, + 0x80, 0xb8, 0x80, 0xb8, 0x80, +}; + +static const uint8_t unicode_prop_Emoji_table[238] = { + 0xa2, 0x05, 0x04, 0x89, 0xee, 0x03, 0x80, 0x5f, + 0x8c, 0x80, 0x8b, 0x80, 0x40, 0xd7, 0x80, 0x95, + 0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81, + 0x8b, 0x80, 0x40, 0xa5, 0x80, 0x98, 0x8a, 0x1a, + 0x40, 0xc6, 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, + 0x88, 0x80, 0xb9, 0x18, 0x84, 0x88, 0x01, 0x01, + 0x09, 0x03, 0x01, 0x00, 0x09, 0x02, 0x02, 0x0f, + 0x14, 0x00, 0x04, 0x8b, 0x8a, 0x09, 0x00, 0x08, + 0x80, 0x91, 0x01, 0x81, 0x91, 0x28, 0x00, 0x0a, + 0x0c, 0x01, 0x0b, 0x81, 0x8a, 0x0c, 0x09, 0x04, + 0x08, 0x00, 0x81, 0x93, 0x0c, 0x28, 0x19, 0x03, + 0x01, 0x01, 0x28, 0x01, 0x00, 0x00, 0x05, 0x02, + 0x05, 0x80, 0x89, 0x81, 0x8e, 0x01, 0x03, 0x00, + 0x03, 0x10, 0x80, 0x8a, 0x81, 0xaf, 0x82, 0x88, + 0x80, 0x8d, 0x80, 0x8d, 0x80, 0x41, 0x73, 0x81, + 0x41, 0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, 0x80, + 0x44, 0xd9, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, + 0x80, 0x61, 0xbd, 0x69, 0x80, 0x40, 0xc9, 0x80, + 0x40, 0x9f, 0x81, 0x8b, 0x81, 0x8d, 0x01, 0x89, + 0xca, 0x99, 0x01, 0x96, 0x80, 0x93, 0x01, 0x88, + 0x94, 0x81, 0x40, 0xad, 0xa1, 0x81, 0xef, 0x09, + 0x02, 0x81, 0xd2, 0x0a, 0x80, 0x41, 0x06, 0x80, + 0xbe, 0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, + 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, + 0x04, 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, + 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x0a, 0x83, + 0x89, 0x10, 0x01, 0x10, 0x81, 0x89, 0x40, 0xe2, + 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, 0x80, + 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, 0x84, 0xb7, + 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, +}; + +static const uint8_t unicode_prop_Emoji_Component_table[28] = { + 0xa2, 0x05, 0x04, 0x89, 0x5f, 0xd2, 0x80, 0x40, + 0xd4, 0x80, 0x60, 0xdd, 0x2a, 0x80, 0x60, 0xf3, + 0xd5, 0x99, 0x41, 0xfa, 0x84, 0x45, 0xaf, 0x83, + 0x6c, 0x06, 0x6b, 0xdf, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_table[4] = { + 0x61, 0xf3, 0xfa, 0x84, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_Base_table[71] = { + 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, + 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, + 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, 0x10, 0x1a, + 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, 0xc8, 0x0b, + 0x80, 0x94, 0x03, 0x81, 0x40, 0xad, 0x12, 0x84, + 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, 0x8a, 0x80, + 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, 0x88, 0x89, + 0x0a, 0xb7, 0x80, 0xbc, 0x08, 0x08, 0x80, 0x90, + 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, 0x88, +}; + +static const uint8_t unicode_prop_Emoji_Presentation_table[144] = { + 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, + 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, + 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, + 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, + 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, + 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, + 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, + 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, + 0x01, 0x89, 0xca, 0x99, 0x00, 0x97, 0x80, 0x93, + 0x01, 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, + 0x8b, 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, + 0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, + 0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, + 0x80, 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, + 0xc5, 0x28, 0x12, 0x0a, 0x1b, 0x8a, 0x0e, 0x88, + 0x40, 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, + 0x89, 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, + 0x84, 0xb7, 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, +}; + +static const uint8_t unicode_prop_Extended_Pictographic_table[156] = { + 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, + 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, + 0x8e, 0x81, 0x41, 0x6e, 0x81, 0x8b, 0x80, 0xde, + 0x80, 0xc5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6, + 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, + 0xb9, 0x18, 0x28, 0x8b, 0x80, 0xf1, 0x89, 0xf5, + 0x81, 0x8a, 0x00, 0x00, 0x28, 0x10, 0x28, 0x89, + 0x81, 0x8e, 0x01, 0x03, 0x00, 0x03, 0x10, 0x80, + 0x8a, 0x84, 0xac, 0x82, 0x88, 0x80, 0x8d, 0x80, + 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, 0xce, 0x82, + 0x92, 0x81, 0xb2, 0x03, 0x80, 0x44, 0xd9, 0x80, + 0x8b, 0x80, 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd, + 0x65, 0x40, 0xff, 0x8c, 0x82, 0x9e, 0x80, 0xbb, + 0x85, 0x8b, 0x81, 0x8d, 0x01, 0x89, 0x91, 0xb8, + 0x9a, 0x8e, 0x89, 0x80, 0x93, 0x01, 0x88, 0x03, + 0x88, 0x41, 0xb1, 0x84, 0x41, 0x3d, 0x87, 0x41, + 0x09, 0xaf, 0xff, 0xf3, 0x8b, 0xd4, 0xaa, 0x8b, + 0x83, 0xb7, 0x87, 0x89, 0x85, 0xa7, 0x87, 0x9d, + 0xd1, 0x8b, 0xae, 0x80, 0x89, 0x80, 0x41, 0xb8, + 0x40, 0xff, 0x43, 0xfd, +}; + +static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table[51] = { + 0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb, + 0x80, 0x4b, 0x41, 0x81, 0x46, 0x52, 0x81, 0xd4, + 0x84, 0x47, 0xfa, 0x84, 0x99, 0x84, 0xb0, 0x8f, + 0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40, + 0xee, 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60, + 0xbc, 0xa6, 0x83, 0x54, 0xce, 0x87, 0x6c, 0x2e, + 0x84, 0x4f, 0xff, +}; + +typedef enum { + UNICODE_PROP_Hyphen, + UNICODE_PROP_Other_Math, + UNICODE_PROP_Other_Alphabetic, + UNICODE_PROP_Other_Lowercase, + UNICODE_PROP_Other_Uppercase, + UNICODE_PROP_Other_Grapheme_Extend, + UNICODE_PROP_Other_Default_Ignorable_Code_Point, + UNICODE_PROP_Other_ID_Start, + UNICODE_PROP_Other_ID_Continue, + UNICODE_PROP_Prepended_Concatenation_Mark, + UNICODE_PROP_ID_Continue1, + UNICODE_PROP_XID_Start1, + UNICODE_PROP_XID_Continue1, + UNICODE_PROP_Changes_When_Titlecased1, + UNICODE_PROP_Changes_When_Casefolded1, + UNICODE_PROP_Changes_When_NFKC_Casefolded1, + UNICODE_PROP_ASCII_Hex_Digit, + UNICODE_PROP_Bidi_Control, + UNICODE_PROP_Dash, + UNICODE_PROP_Deprecated, + UNICODE_PROP_Diacritic, + UNICODE_PROP_Extender, + UNICODE_PROP_Hex_Digit, + UNICODE_PROP_IDS_Unary_Operator, + UNICODE_PROP_IDS_Binary_Operator, + UNICODE_PROP_IDS_Trinary_Operator, + UNICODE_PROP_Ideographic, + UNICODE_PROP_Join_Control, + UNICODE_PROP_Logical_Order_Exception, + UNICODE_PROP_Modifier_Combining_Mark, + UNICODE_PROP_Noncharacter_Code_Point, + UNICODE_PROP_Pattern_Syntax, + UNICODE_PROP_Pattern_White_Space, + UNICODE_PROP_Quotation_Mark, + UNICODE_PROP_Radical, + UNICODE_PROP_Regional_Indicator, + UNICODE_PROP_Sentence_Terminal, + UNICODE_PROP_Soft_Dotted, + UNICODE_PROP_Terminal_Punctuation, + UNICODE_PROP_Unified_Ideograph, + UNICODE_PROP_Variation_Selector, + UNICODE_PROP_White_Space, + UNICODE_PROP_Bidi_Mirrored, + UNICODE_PROP_Emoji, + UNICODE_PROP_Emoji_Component, + UNICODE_PROP_Emoji_Modifier, + UNICODE_PROP_Emoji_Modifier_Base, + UNICODE_PROP_Emoji_Presentation, + UNICODE_PROP_Extended_Pictographic, + UNICODE_PROP_Default_Ignorable_Code_Point, + UNICODE_PROP_ID_Start, + UNICODE_PROP_Case_Ignorable, + UNICODE_PROP_ASCII, + UNICODE_PROP_Alphabetic, + UNICODE_PROP_Any, + UNICODE_PROP_Assigned, + UNICODE_PROP_Cased, + UNICODE_PROP_Changes_When_Casefolded, + UNICODE_PROP_Changes_When_Casemapped, + UNICODE_PROP_Changes_When_Lowercased, + UNICODE_PROP_Changes_When_NFKC_Casefolded, + UNICODE_PROP_Changes_When_Titlecased, + UNICODE_PROP_Changes_When_Uppercased, + UNICODE_PROP_Grapheme_Base, + UNICODE_PROP_Grapheme_Extend, + UNICODE_PROP_ID_Continue, + UNICODE_PROP_ID_Compat_Math_Start, + UNICODE_PROP_ID_Compat_Math_Continue, + UNICODE_PROP_Lowercase, + UNICODE_PROP_Math, + UNICODE_PROP_Uppercase, + UNICODE_PROP_XID_Continue, + UNICODE_PROP_XID_Start, + UNICODE_PROP_Cased1, + UNICODE_PROP_InCB, + UNICODE_PROP_COUNT, +} UnicodePropertyEnum; + +static const char unicode_prop_name_table[] = + "ASCII_Hex_Digit,AHex" "\0" + "Bidi_Control,Bidi_C" "\0" + "Dash" "\0" + "Deprecated,Dep" "\0" + "Diacritic,Dia" "\0" + "Extender,Ext" "\0" + "Hex_Digit,Hex" "\0" + "IDS_Unary_Operator,IDSU" "\0" + "IDS_Binary_Operator,IDSB" "\0" + "IDS_Trinary_Operator,IDST" "\0" + "Ideographic,Ideo" "\0" + "Join_Control,Join_C" "\0" + "Logical_Order_Exception,LOE" "\0" + "Modifier_Combining_Mark,MCM" "\0" + "Noncharacter_Code_Point,NChar" "\0" + "Pattern_Syntax,Pat_Syn" "\0" + "Pattern_White_Space,Pat_WS" "\0" + "Quotation_Mark,QMark" "\0" + "Radical" "\0" + "Regional_Indicator,RI" "\0" + "Sentence_Terminal,STerm" "\0" + "Soft_Dotted,SD" "\0" + "Terminal_Punctuation,Term" "\0" + "Unified_Ideograph,UIdeo" "\0" + "Variation_Selector,VS" "\0" + "White_Space,space" "\0" + "Bidi_Mirrored,Bidi_M" "\0" + "Emoji" "\0" + "Emoji_Component,EComp" "\0" + "Emoji_Modifier,EMod" "\0" + "Emoji_Modifier_Base,EBase" "\0" + "Emoji_Presentation,EPres" "\0" + "Extended_Pictographic,ExtPict" "\0" + "Default_Ignorable_Code_Point,DI" "\0" + "ID_Start,IDS" "\0" + "Case_Ignorable,CI" "\0" + "ASCII" "\0" + "Alphabetic,Alpha" "\0" + "Any" "\0" + "Assigned" "\0" + "Cased" "\0" + "Changes_When_Casefolded,CWCF" "\0" + "Changes_When_Casemapped,CWCM" "\0" + "Changes_When_Lowercased,CWL" "\0" + "Changes_When_NFKC_Casefolded,CWKCF" "\0" + "Changes_When_Titlecased,CWT" "\0" + "Changes_When_Uppercased,CWU" "\0" + "Grapheme_Base,Gr_Base" "\0" + "Grapheme_Extend,Gr_Ext" "\0" + "ID_Continue,IDC" "\0" + "ID_Compat_Math_Start" "\0" + "ID_Compat_Math_Continue" "\0" + "Lowercase,Lower" "\0" + "Math" "\0" + "Uppercase,Upper" "\0" + "XID_Continue,XIDC" "\0" + "XID_Start,XIDS" "\0" +; + +static const uint8_t * const unicode_prop_table[] = { + unicode_prop_Hyphen_table, + unicode_prop_Other_Math_table, + unicode_prop_Other_Alphabetic_table, + unicode_prop_Other_Lowercase_table, + unicode_prop_Other_Uppercase_table, + unicode_prop_Other_Grapheme_Extend_table, + unicode_prop_Other_Default_Ignorable_Code_Point_table, + unicode_prop_Other_ID_Start_table, + unicode_prop_Other_ID_Continue_table, + unicode_prop_Prepended_Concatenation_Mark_table, + unicode_prop_ID_Continue1_table, + unicode_prop_XID_Start1_table, + unicode_prop_XID_Continue1_table, + unicode_prop_Changes_When_Titlecased1_table, + unicode_prop_Changes_When_Casefolded1_table, + unicode_prop_Changes_When_NFKC_Casefolded1_table, + unicode_prop_ASCII_Hex_Digit_table, + unicode_prop_Bidi_Control_table, + unicode_prop_Dash_table, + unicode_prop_Deprecated_table, + unicode_prop_Diacritic_table, + unicode_prop_Extender_table, + unicode_prop_Hex_Digit_table, + unicode_prop_IDS_Unary_Operator_table, + unicode_prop_IDS_Binary_Operator_table, + unicode_prop_IDS_Trinary_Operator_table, + unicode_prop_Ideographic_table, + unicode_prop_Join_Control_table, + unicode_prop_Logical_Order_Exception_table, + unicode_prop_Modifier_Combining_Mark_table, + unicode_prop_Noncharacter_Code_Point_table, + unicode_prop_Pattern_Syntax_table, + unicode_prop_Pattern_White_Space_table, + unicode_prop_Quotation_Mark_table, + unicode_prop_Radical_table, + unicode_prop_Regional_Indicator_table, + unicode_prop_Sentence_Terminal_table, + unicode_prop_Soft_Dotted_table, + unicode_prop_Terminal_Punctuation_table, + unicode_prop_Unified_Ideograph_table, + unicode_prop_Variation_Selector_table, + unicode_prop_White_Space_table, + unicode_prop_Bidi_Mirrored_table, + unicode_prop_Emoji_table, + unicode_prop_Emoji_Component_table, + unicode_prop_Emoji_Modifier_table, + unicode_prop_Emoji_Modifier_Base_table, + unicode_prop_Emoji_Presentation_table, + unicode_prop_Extended_Pictographic_table, + unicode_prop_Default_Ignorable_Code_Point_table, + unicode_prop_ID_Start_table, + unicode_prop_Case_Ignorable_table, +}; + +static const uint16_t unicode_prop_len_table[] = { + countof(unicode_prop_Hyphen_table), + countof(unicode_prop_Other_Math_table), + countof(unicode_prop_Other_Alphabetic_table), + countof(unicode_prop_Other_Lowercase_table), + countof(unicode_prop_Other_Uppercase_table), + countof(unicode_prop_Other_Grapheme_Extend_table), + countof(unicode_prop_Other_Default_Ignorable_Code_Point_table), + countof(unicode_prop_Other_ID_Start_table), + countof(unicode_prop_Other_ID_Continue_table), + countof(unicode_prop_Prepended_Concatenation_Mark_table), + countof(unicode_prop_ID_Continue1_table), + countof(unicode_prop_XID_Start1_table), + countof(unicode_prop_XID_Continue1_table), + countof(unicode_prop_Changes_When_Titlecased1_table), + countof(unicode_prop_Changes_When_Casefolded1_table), + countof(unicode_prop_Changes_When_NFKC_Casefolded1_table), + countof(unicode_prop_ASCII_Hex_Digit_table), + countof(unicode_prop_Bidi_Control_table), + countof(unicode_prop_Dash_table), + countof(unicode_prop_Deprecated_table), + countof(unicode_prop_Diacritic_table), + countof(unicode_prop_Extender_table), + countof(unicode_prop_Hex_Digit_table), + countof(unicode_prop_IDS_Unary_Operator_table), + countof(unicode_prop_IDS_Binary_Operator_table), + countof(unicode_prop_IDS_Trinary_Operator_table), + countof(unicode_prop_Ideographic_table), + countof(unicode_prop_Join_Control_table), + countof(unicode_prop_Logical_Order_Exception_table), + countof(unicode_prop_Modifier_Combining_Mark_table), + countof(unicode_prop_Noncharacter_Code_Point_table), + countof(unicode_prop_Pattern_Syntax_table), + countof(unicode_prop_Pattern_White_Space_table), + countof(unicode_prop_Quotation_Mark_table), + countof(unicode_prop_Radical_table), + countof(unicode_prop_Regional_Indicator_table), + countof(unicode_prop_Sentence_Terminal_table), + countof(unicode_prop_Soft_Dotted_table), + countof(unicode_prop_Terminal_Punctuation_table), + countof(unicode_prop_Unified_Ideograph_table), + countof(unicode_prop_Variation_Selector_table), + countof(unicode_prop_White_Space_table), + countof(unicode_prop_Bidi_Mirrored_table), + countof(unicode_prop_Emoji_table), + countof(unicode_prop_Emoji_Component_table), + countof(unicode_prop_Emoji_Modifier_table), + countof(unicode_prop_Emoji_Modifier_Base_table), + countof(unicode_prop_Emoji_Presentation_table), + countof(unicode_prop_Extended_Pictographic_table), + countof(unicode_prop_Default_Ignorable_Code_Point_table), + countof(unicode_prop_ID_Start_table), + countof(unicode_prop_Case_Ignorable_table), +}; + diff --git a/NativeScript/napi/android/quickjs/source_ng/libunicode.c b/NativeScript/napi/android/quickjs/source_ng/libunicode.c new file mode 100755 index 000000000..a621c5235 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/libunicode.c @@ -0,0 +1,1746 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "libunicode.h" +#include "libunicode-table.h" + +// note: stored as 4 bit tag, not much room left +enum { + RUN_TYPE_U, + RUN_TYPE_L, + RUN_TYPE_UF, + RUN_TYPE_LF, + RUN_TYPE_UL, + RUN_TYPE_LSU, + RUN_TYPE_U2L_399_EXT2, + RUN_TYPE_UF_D20, + RUN_TYPE_UF_D1_EXT, + RUN_TYPE_U_EXT, + RUN_TYPE_LF_EXT, + RUN_TYPE_UF_EXT2, + RUN_TYPE_LF_EXT2, + RUN_TYPE_UF_EXT3, +}; + +static int lre_case_conv1(uint32_t c, int conv_type) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + lre_case_conv(res, c, conv_type); + return res[0]; +} + +/* case conversion using the table entry 'idx' with value 'v' */ +static int lre_case_conv_entry(uint32_t *res, uint32_t c, int conv_type, uint32_t idx, uint32_t v) +{ + uint32_t code, data, type, a, is_lower; + is_lower = (conv_type != 0); + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + data = ((v & 0xf) << 8) | case_conv_table2[idx]; + code = v >> (32 - 17); + switch(type) { + case RUN_TYPE_U: + case RUN_TYPE_L: + case RUN_TYPE_UF: + case RUN_TYPE_LF: + if (conv_type == (type & 1) || + (type >= RUN_TYPE_UF && conv_type == 2)) { + c = c - code + (case_conv_table1[data] >> (32 - 17)); + } + break; + case RUN_TYPE_UL: + a = c - code; + if ((a & 1) != (1 - is_lower)) + break; + c = (a ^ 1) + code; + break; + case RUN_TYPE_LSU: + a = c - code; + if (a == 1) { + c += 2 * is_lower - 1; + } else if (a == (1 - is_lower) * 2) { + c += (2 * is_lower - 1) * 2; + } + break; + case RUN_TYPE_U2L_399_EXT2: + if (!is_lower) { + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = 0x399; + return 2; + } else { + c = c - code + case_conv_ext[data & 0x3f]; + } + break; + case RUN_TYPE_UF_D20: + if (conv_type == 1) + break; + c = data + (conv_type == 2) * 0x20; + break; + case RUN_TYPE_UF_D1_EXT: + if (conv_type == 1) + break; + c = case_conv_ext[data] + (conv_type == 2); + break; + case RUN_TYPE_U_EXT: + case RUN_TYPE_LF_EXT: + if (is_lower != (type - RUN_TYPE_U_EXT)) + break; + c = case_conv_ext[data]; + break; + case RUN_TYPE_LF_EXT2: + if (!is_lower) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + return 2; + case RUN_TYPE_UF_EXT2: + if (conv_type == 1) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + } + return 2; + default: + case RUN_TYPE_UF_EXT3: + if (conv_type == 1) + break; + res[0] = case_conv_ext[data >> 8]; + res[1] = case_conv_ext[(data >> 4) & 0xf]; + res[2] = case_conv_ext[data & 0xf]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + res[2] = lre_case_conv1(res[2], 1); + } + return 3; + } + res[0] = c; + return 1; +} + +/* conv_type: + 0 = to upper + 1 = to lower + 2 = case folding (= to lower with modifications) +*/ +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type) +{ + if (c < 128) { + if (conv_type) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_conv_entry(res, c, conv_type, idx, v); + } + } + } + res[0] = c; + return 1; +} + +static int lre_case_folding_entry(uint32_t c, uint32_t idx, uint32_t v, bool is_unicode) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + int len; + + if (is_unicode) { + len = lre_case_conv_entry(res, c, 2, idx, v); + if (len == 1) { + c = res[0]; + } else { + /* handle the few specific multi-character cases (see + unicode_gen.c:dump_case_folding_special_cases()) */ + if (c == 0xfb06) { + c = 0xfb05; + } else if (c == 0x01fd3) { + c = 0x390; + } else if (c == 0x01fe3) { + c = 0x3b0; + } + } + } else { + if (likely(c < 128)) { + if (c >= 'a' && c <= 'z') + c = c - 'a' + 'A'; + } else { + /* legacy regexp: to upper case if single char >= 128 */ + len = lre_case_conv_entry(res, c, false, idx, v); + if (len == 1 && res[0] >= 128) + c = res[0]; + } + } + return c; +} + +/* JS regexp specific rules for case folding */ +int lre_canonicalize(uint32_t c, bool is_unicode) +{ + if (c < 128) { + /* fast case */ + if (is_unicode) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_folding_entry(c, idx, v, is_unicode); + } + } + } + return c; +} + +static uint32_t get_le24(const uint8_t *ptr) +{ + return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16); +} + +#define UNICODE_INDEX_BLOCK_LEN 32 + +/* return -1 if not in table, otherwise the offset in the block */ +static int get_index_pos(uint32_t *pcode, uint32_t c, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, v; + int idx_min, idx_max, idx; + + idx_min = 0; + v = get_le24(index_table); + code = v & ((1 << 21) - 1); + if (c < code) { + *pcode = 0; + return 0; + } + idx_max = index_table_len - 1; + code = get_le24(index_table + idx_max * 3); + if (c >= code) + return -1; + /* invariant: tab[idx_min] <= c < tab2[idx_max] */ + while ((idx_max - idx_min) > 1) { + idx = (idx_max + idx_min) / 2; + v = get_le24(index_table + idx * 3); + code = v & ((1 << 21) - 1); + if (c < code) { + idx_max = idx; + } else { + idx_min = idx; + } + } + v = get_le24(index_table + idx_min * 3); + *pcode = v & ((1 << 21) - 1); + return (idx_min + 1) * UNICODE_INDEX_BLOCK_LEN + (v >> 21); +} + +static bool lre_is_in_table(uint32_t c, const uint8_t *table, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, b, bit; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, index_table, index_table_len); + if (pos < 0) + return false; /* outside the table */ + p = table + pos; + bit = 0; + for(;;) { + b = *p++; + if (b < 64) { + code += (b >> 3) + 1; + if (c < code) + return bit; + bit ^= 1; + code += (b & 7) + 1; + } else if (b >= 0x80) { + code += b - 0x80 + 1; + } else if (b < 0x60) { + code += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + code += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (c < code) + return bit; + bit ^= 1; + } +} + +bool lre_is_cased(uint32_t c) +{ + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return true; + } + } + return lre_is_in_table(c, unicode_prop_Cased1_table, + unicode_prop_Cased1_index, + sizeof(unicode_prop_Cased1_index) / 3); +} + +bool lre_is_case_ignorable(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_Case_Ignorable_table, + unicode_prop_Case_Ignorable_index, + sizeof(unicode_prop_Case_Ignorable_index) / 3); +} + +/* character range */ + +static __maybe_unused void cr_dump(CharRange *cr) +{ + int i; + for(i = 0; i < cr->len; i++) + printf("%d: 0x%04x\n", i, cr->points[i]); +} + +static void *cr_default_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void cr_init(CharRange *cr, void *mem_opaque, DynBufReallocFunc *realloc_func) +{ + cr->len = cr->size = 0; + cr->points = NULL; + cr->mem_opaque = mem_opaque; + cr->realloc_func = realloc_func ? realloc_func : cr_default_realloc; +} + +void cr_free(CharRange *cr) +{ + cr->realloc_func(cr->mem_opaque, cr->points, 0); +} + +int cr_realloc(CharRange *cr, int size) +{ + int new_size; + uint32_t *new_buf; + + if (size > cr->size) { + new_size = max_int(size, cr->size * 3 / 2); + new_buf = cr->realloc_func(cr->mem_opaque, cr->points, + new_size * sizeof(cr->points[0])); + if (!new_buf) + return -1; + cr->points = new_buf; + cr->size = new_size; + } + return 0; +} + +int cr_copy(CharRange *cr, const CharRange *cr1) +{ + if (cr_realloc(cr, cr1->len)) + return -1; + memcpy(cr->points, cr1->points, sizeof(cr->points[0]) * cr1->len); + cr->len = cr1->len; + return 0; +} + +/* merge consecutive intervals and remove empty intervals */ +static void cr_compress(CharRange *cr) +{ + int i, j, k, len; + uint32_t *pt; + + pt = cr->points; + len = cr->len; + i = 0; + j = 0; + k = 0; + while ((i + 1) < len) { + if (pt[i] == pt[i + 1]) { + /* empty interval */ + i += 2; + } else { + j = i; + while ((j + 3) < len && pt[j + 1] == pt[j + 2]) + j += 2; + /* just copy */ + pt[k] = pt[i]; + pt[k + 1] = pt[j + 1]; + k += 2; + i = j + 2; + } + } + cr->len = k; +} + +/* union or intersection */ +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op) +{ + int a_idx, b_idx, is_in; + uint32_t v; + + a_idx = 0; + b_idx = 0; + for(;;) { + /* get one more point from a or b in increasing order */ + if (a_idx < a_len && b_idx < b_len) { + if (a_pt[a_idx] < b_pt[b_idx]) { + goto a_add; + } else if (a_pt[a_idx] == b_pt[b_idx]) { + v = a_pt[a_idx]; + a_idx++; + b_idx++; + } else { + goto b_add; + } + } else if (a_idx < a_len) { + a_add: + v = a_pt[a_idx++]; + } else if (b_idx < b_len) { + b_add: + v = b_pt[b_idx++]; + } else { + break; + } + /* add the point if the in/out status changes */ + switch(op) { + case CR_OP_UNION: + is_in = (a_idx & 1) | (b_idx & 1); + break; + case CR_OP_INTER: + is_in = (a_idx & 1) & (b_idx & 1); + break; + case CR_OP_XOR: + is_in = (a_idx & 1) ^ (b_idx & 1); + break; + default: + abort(); + } + if (is_in != (cr->len & 1)) { + if (cr_add_point(cr, v)) + return -1; + } + } + cr_compress(cr); + return 0; +} + +int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len) +{ + CharRange a = *cr; + int ret; + cr->len = 0; + cr->size = 0; + cr->points = NULL; + ret = cr_op(cr, a.points, a.len, b_pt, b_len, CR_OP_UNION); + cr_free(&a); + return ret; +} + +int cr_invert(CharRange *cr) +{ + int len; + len = cr->len; + if (cr_realloc(cr, len + 2)) + return -1; + memmove(cr->points + 1, cr->points, len * sizeof(cr->points[0])); + cr->points[0] = 0; + cr->points[len + 1] = UINT32_MAX; + cr->len = len + 2; + cr_compress(cr); + return 0; +} + +bool lre_is_id_start(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_ID_Start_table, + unicode_prop_ID_Start_index, + sizeof(unicode_prop_ID_Start_index) / 3); +} + +bool lre_is_id_continue(uint32_t c) +{ + return lre_is_id_start(c) || + lre_is_in_table(c, unicode_prop_ID_Continue1_table, + unicode_prop_ID_Continue1_index, + sizeof(unicode_prop_ID_Continue1_index) / 3); +} + +bool lre_is_white_space(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_White_Space_table, + unicode_prop_White_Space_index, + sizeof(unicode_prop_White_Space_index) / 3); +} + +#define UNICODE_DECOMP_LEN_MAX 18 + +typedef enum { + DECOMP_TYPE_C1, /* 16 bit char */ + DECOMP_TYPE_L1, /* 16 bit char table */ + DECOMP_TYPE_L2, + DECOMP_TYPE_L3, + DECOMP_TYPE_L4, + DECOMP_TYPE_L5, /* XXX: not used */ + DECOMP_TYPE_L6, /* XXX: could remove */ + DECOMP_TYPE_L7, /* XXX: could remove */ + DECOMP_TYPE_LL1, /* 18 bit char table */ + DECOMP_TYPE_LL2, + DECOMP_TYPE_S1, /* 8 bit char table */ + DECOMP_TYPE_S2, + DECOMP_TYPE_S3, + DECOMP_TYPE_S4, + DECOMP_TYPE_S5, + DECOMP_TYPE_I1, /* increment 16 bit char value */ + DECOMP_TYPE_I2_0, + DECOMP_TYPE_I2_1, + DECOMP_TYPE_I3_1, + DECOMP_TYPE_I3_2, + DECOMP_TYPE_I4_1, + DECOMP_TYPE_I4_2, + DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */ + DECOMP_TYPE_B2, + DECOMP_TYPE_B3, + DECOMP_TYPE_B4, + DECOMP_TYPE_B5, + DECOMP_TYPE_B6, + DECOMP_TYPE_B7, + DECOMP_TYPE_B8, + DECOMP_TYPE_B18, + DECOMP_TYPE_LS2, + DECOMP_TYPE_PAT3, + DECOMP_TYPE_S2_UL, + DECOMP_TYPE_LS2_UL, +} DecompTypeEnum; + +static uint32_t unicode_get_short_code(uint32_t c) +{ + static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 }; + + if (c < 0x80) + return c; + else if (c < 0x80 + 0x50) + return c - 0x80 + 0x300; + else + return unicode_short_table[c - 0x80 - 0x50]; +} + +static uint32_t unicode_get_lower_simple(uint32_t c) +{ + if (c < 0x100 || (c >= 0x410 && c <= 0x42f)) + c += 0x20; + else + c++; + return c; +} + +static uint16_t unicode_get16(const uint8_t *p) +{ + return p[0] | (p[1] << 8); +} + +static int unicode_decomp_entry(uint32_t *res, uint32_t c, + int idx, uint32_t code, uint32_t len, + uint32_t type) +{ + uint32_t c1; + int l, i, p; + const uint8_t *d; + + if (type == DECOMP_TYPE_C1) { + res[0] = unicode_decomp_table2[idx]; + return 1; + } else { + d = unicode_decomp_data + unicode_decomp_table2[idx]; + switch(type) { + case DECOMP_TYPE_L1: + case DECOMP_TYPE_L2: + case DECOMP_TYPE_L3: + case DECOMP_TYPE_L4: + case DECOMP_TYPE_L5: + case DECOMP_TYPE_L6: + case DECOMP_TYPE_L7: + l = type - DECOMP_TYPE_L1 + 1; + d += (c - code) * l * 2; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get16(d + 2 * i)) == 0) + return 0; + } + return l; + case DECOMP_TYPE_LL1: + case DECOMP_TYPE_LL2: + { + uint32_t k, p; + l = type - DECOMP_TYPE_LL1 + 1; + k = (c - code) * l; + p = len * l * 2; + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * k) | + (((d[p + (k / 4)] >> ((k % 4) * 2)) & 3) << 16); + if (!c1) + return 0; + res[i] = c1; + k++; + } + } + return l; + case DECOMP_TYPE_S1: + case DECOMP_TYPE_S2: + case DECOMP_TYPE_S3: + case DECOMP_TYPE_S4: + case DECOMP_TYPE_S5: + l = type - DECOMP_TYPE_S1 + 1; + d += (c - code) * l; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get_short_code(d[i])) == 0) + return 0; + } + return l; + case DECOMP_TYPE_I1: + l = 1; + p = 0; + goto decomp_type_i; + case DECOMP_TYPE_I2_0: + case DECOMP_TYPE_I2_1: + case DECOMP_TYPE_I3_1: + case DECOMP_TYPE_I3_2: + case DECOMP_TYPE_I4_1: + case DECOMP_TYPE_I4_2: + l = 2 + ((type - DECOMP_TYPE_I2_0) >> 1); + p = ((type - DECOMP_TYPE_I2_0) & 1) + (l > 2); + decomp_type_i: + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * i); + if (i == p) + c1 += c - code; + res[i] = c1; + } + return l; + case DECOMP_TYPE_B18: + l = 18; + goto decomp_type_b; + case DECOMP_TYPE_B1: + case DECOMP_TYPE_B2: + case DECOMP_TYPE_B3: + case DECOMP_TYPE_B4: + case DECOMP_TYPE_B5: + case DECOMP_TYPE_B6: + case DECOMP_TYPE_B7: + case DECOMP_TYPE_B8: + l = type - DECOMP_TYPE_B1 + 1; + decomp_type_b: + { + uint32_t c_min; + c_min = unicode_get16(d); + d += 2 + (c - code) * l; + for(i = 0; i < l; i++) { + c1 = d[i]; + if (c1 == 0xff) + c1 = 0x20; + else + c1 += c_min; + res[i] = c1; + } + } + return l; + case DECOMP_TYPE_LS2: + d += (c - code) * 3; + if (!(res[0] = unicode_get16(d))) + return 0; + res[1] = unicode_get_short_code(d[2]); + return 2; + case DECOMP_TYPE_PAT3: + res[0] = unicode_get16(d); + res[2] = unicode_get16(d + 2); + d += 4 + (c - code) * 2; + res[1] = unicode_get16(d); + return 3; + case DECOMP_TYPE_S2_UL: + case DECOMP_TYPE_LS2_UL: + c1 = c - code; + if (type == DECOMP_TYPE_S2_UL) { + d += c1 & ~1; + c = unicode_get_short_code(*d); + d++; + } else { + d += (c1 >> 1) * 3; + c = unicode_get16(d); + d += 2; + } + if (c1 & 1) + c = unicode_get_lower_simple(c); + res[0] = c; + res[1] = unicode_get_short_code(*d); + return 2; + } + } + return 0; +} + + +/* return the length of the decomposition (length <= + UNICODE_DECOMP_LEN_MAX) or 0 if no decomposition */ +static int unicode_decomp_char(uint32_t *res, uint32_t c, bool is_compat1) +{ + uint32_t v, type, is_compat, code, len; + int idx_min, idx_max, idx; + + idx_min = 0; + idx_max = countof(unicode_decomp_table1) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + v = unicode_decomp_table1[idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + // printf("idx=%d code=%05x len=%d\n", idx, code, len); + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + is_compat = v & 1; + if (is_compat1 < is_compat) + break; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + return unicode_decomp_entry(res, c, idx, code, len, type); + } + } + return 0; +} + +/* return 0 if no pair found */ +static int unicode_compose_pair(uint32_t c0, uint32_t c1) +{ + uint32_t code, len, type, v, idx1, d_idx, d_offset, ch; + int idx_min, idx_max, idx, d; + uint32_t pair[2]; + + idx_min = 0; + idx_max = countof(unicode_comp_table) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + idx1 = unicode_comp_table[idx]; + + /* idx1 represent an entry of the decomposition table */ + d_idx = idx1 >> 6; + d_offset = idx1 & 0x3f; + v = unicode_decomp_table1[d_idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + ch = code + d_offset; + unicode_decomp_entry(pair, ch, d_idx, code, len, type); + d = c0 - pair[0]; + if (d == 0) + d = c1 - pair[1]; + if (d < 0) { + idx_max = idx - 1; + } else if (d > 0) { + idx_min = idx + 1; + } else { + return ch; + } + } + return 0; +} + +/* return the combining class of character c (between 0 and 255) */ +static int unicode_get_cc(uint32_t c) +{ + uint32_t code, n, type, cc, c1, b; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, + unicode_cc_index, sizeof(unicode_cc_index) / 3); + if (pos < 0) + return 0; + p = unicode_cc_table + pos; + for(;;) { + b = *p++; + type = b >> 6; + n = b & 0x3f; + if (n < 48) { + } else if (n < 56) { + n = (n - 48) << 8; + n |= *p++; + n += 48; + } else { + n = (n - 56) << 8; + n |= *p++ << 8; + n |= *p++; + n += 48 + (1 << 11); + } + if (type <= 1) + p++; + c1 = code + n + 1; + if (c < c1) { + switch(type) { + case 0: + cc = p[-1]; + break; + case 1: + cc = p[-1] + c - code; + break; + case 2: + cc = 0; + break; + default: + case 3: + cc = 230; + break; + } + return cc; + } + code = c1; + } +} + +static void sort_cc(int *buf, int len) +{ + int i, j, k, cc, cc1, start, ch1; + + for(i = 0; i < len; i++) { + cc = unicode_get_cc(buf[i]); + if (cc != 0) { + start = i; + j = i + 1; + while (j < len) { + ch1 = buf[j]; + cc1 = unicode_get_cc(ch1); + if (cc1 == 0) + break; + k = j - 1; + while (k >= start) { + if (unicode_get_cc(buf[k]) <= cc1) + break; + buf[k + 1] = buf[k]; + k--; + } + buf[k + 1] = ch1; + j++; + } + i = j; + } + } +} + +static void to_nfd_rec(DynBuf *dbuf, + const int *src, int src_len, int is_compat) +{ + uint32_t c, v; + int i, l; + uint32_t res[UNICODE_DECOMP_LEN_MAX]; + + for(i = 0; i < src_len; i++) { + c = src[i]; + if (c >= 0xac00 && c < 0xd7a4) { + /* Hangul decomposition */ + c -= 0xac00; + dbuf_put_u32(dbuf, 0x1100 + c / 588); + dbuf_put_u32(dbuf, 0x1161 + (c % 588) / 28); + v = c % 28; + if (v != 0) + dbuf_put_u32(dbuf, 0x11a7 + v); + } else { + l = unicode_decomp_char(res, c, is_compat); + if (l) { + to_nfd_rec(dbuf, (int *)res, l, is_compat); + } else { + dbuf_put_u32(dbuf, c); + } + } + } +} + +/* return 0 if not found */ +static int compose_pair(uint32_t c0, uint32_t c1) +{ + /* Hangul composition */ + if (c0 >= 0x1100 && c0 < 0x1100 + 19 && + c1 >= 0x1161 && c1 < 0x1161 + 21) { + return 0xac00 + (c0 - 0x1100) * 588 + (c1 - 0x1161) * 28; + } else if (c0 >= 0xac00 && c0 < 0xac00 + 11172 && + (c0 - 0xac00) % 28 == 0 && + c1 >= 0x11a7 && c1 < 0x11a7 + 28) { + return c0 + c1 - 0x11a7; + } else { + return unicode_compose_pair(c0, c1); + } +} + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, DynBufReallocFunc *realloc_func) +{ + int *buf, buf_len, i, p, starter_pos, cc, last_cc, out_len; + bool is_compat; + DynBuf dbuf_s, *dbuf = &dbuf_s; + + is_compat = n_type >> 1; + + dbuf_init2(dbuf, opaque, realloc_func); + if (dbuf_realloc(dbuf, sizeof(int) * src_len)) + goto fail; + + /* common case: latin1 is unaffected by NFC */ + if (n_type == UNICODE_NFC) { + for(i = 0; i < src_len; i++) { + if (src[i] >= 0x100) + goto not_latin1; + } + buf = (int *)dbuf->buf; + memcpy(buf, src, src_len * sizeof(int)); + *pdst = (uint32_t *)buf; + return src_len; + not_latin1: ; + } + + to_nfd_rec(dbuf, (const int *)src, src_len, is_compat); + if (dbuf_error(dbuf)) { + fail: + *pdst = NULL; + return -1; + } + buf = (int *)dbuf->buf; + buf_len = dbuf->size / sizeof(int); + + sort_cc(buf, buf_len); + + if (buf_len <= 1 || (n_type & 1) != 0) { + /* NFD / NFKD */ + *pdst = (uint32_t *)buf; + return buf_len; + } + + i = 1; + out_len = 1; + while (i < buf_len) { + /* find the starter character and test if it is blocked from + the character at 'i' */ + last_cc = unicode_get_cc(buf[i]); + starter_pos = out_len - 1; + while (starter_pos >= 0) { + cc = unicode_get_cc(buf[starter_pos]); + if (cc == 0) + break; + if (cc >= last_cc) + goto next; + last_cc = 256; + starter_pos--; + } + if (starter_pos >= 0 && + (p = compose_pair(buf[starter_pos], buf[i])) != 0) { + buf[starter_pos] = p; + i++; + } else { + next: + buf[out_len++] = buf[i++]; + } + } + *pdst = (uint32_t *)buf; + return out_len; +} + +/* char ranges for various unicode properties */ + +static int unicode_find_name(const char *name_table, const char *name) +{ + const char *p, *r; + int pos; + size_t name_len, len; + + p = name_table; + pos = 0; + name_len = strlen(name); + while (*p) { + for(;;) { + r = strchr(p, ','); + if (!r) + len = strlen(p); + else + len = r - p; + if (len == name_len && !memcmp(p, name, name_len)) + return pos; + p += len + 1; + if (!r) + break; + } + pos++; + } + return -1; +} + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_script(CharRange *cr, + const char *script_name, bool is_ext) +{ + int script_idx; + const uint8_t *p, *p_end; + uint32_t c, c1, b, n, v, v_len, i, type; + CharRange cr1_s = { 0 }, *cr1 = NULL; + CharRange cr2_s = { 0 }, *cr2 = &cr2_s; + bool is_common; + + script_idx = unicode_find_name(unicode_script_name_table, script_name); + if (script_idx < 0) + return -2; + /* Note: we remove the "Unknown" Script */ + script_idx += UNICODE_SCRIPT_Unknown + 1; + + is_common = (script_idx == UNICODE_SCRIPT_Common || + script_idx == UNICODE_SCRIPT_Inherited); + if (is_ext) { + cr1 = &cr1_s; + cr_init(cr1, cr->mem_opaque, cr->realloc_func); + cr_init(cr2, cr->mem_opaque, cr->realloc_func); + } else { + cr1 = cr; + } + + p = unicode_script_table; + p_end = unicode_script_table + countof(unicode_script_table); + c = 0; + while (p < p_end) { + b = *p++; + type = b >> 7; + n = b & 0x7f; + if (n < 96) { + } else if (n < 112) { + n = (n - 96) << 8; + n |= *p++; + n += 96; + } else { + n = (n - 112) << 16; + n |= *p++ << 8; + n |= *p++; + n += 96 + (1 << 12); + } + if (type == 0) + v = 0; + else + v = *p++; + c1 = c + n + 1; + if (v == script_idx) { + if (cr_add_interval(cr1, c, c1)) + goto fail; + } + c = c1; + } + + if (is_ext) { + /* add the script extensions */ + p = unicode_script_ext_table; + p_end = unicode_script_ext_table + countof(unicode_script_ext_table); + c = 0; + while (p < p_end) { + b = *p++; + if (b < 128) { + n = b; + } else if (b < 128 + 64) { + n = (b - 128) << 8; + n |= *p++; + n += 128; + } else { + n = (b - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 128 + (1 << 14); + } + c1 = c + n + 1; + v_len = *p++; + if (is_common) { + if (v_len != 0) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + } + } else { + for(i = 0; i < v_len; i++) { + if (p[i] == script_idx) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + break; + } + } + } + p += v_len; + c = c1; + } + if (is_common) { + /* remove all the characters with script extensions */ + if (cr_invert(cr2)) + goto fail; + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_INTER)) + goto fail; + } else { + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_UNION)) + goto fail; + } + cr_free(cr1); + cr_free(cr2); + } + return 0; + fail: + if (is_ext) { + cr_free(cr1); + cr_free(cr2); + } + goto fail; +} + +#define M(id) (1U << UNICODE_GC_ ## id) + +static int unicode_general_category1(CharRange *cr, uint32_t gc_mask) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, n, v; + + p = unicode_gc_table; + p_end = unicode_gc_table + countof(unicode_gc_table); + c = 0; + while (p < p_end) { + b = *p++; + n = b >> 5; + v = b & 0x1f; + if (n == 7) { + n = *p++; + if (n < 128) { + n += 7; + } else if (n < 128 + 64) { + n = (n - 128) << 8; + n |= *p++; + n += 7 + 128; + } else { + n = (n - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 7 + 128 + (1 << 14); + } + } + c0 = c; + c += n + 1; + if (v == 31) { + /* run of Lu / Ll */ + b = gc_mask & (M(Lu) | M(Ll)); + if (b != 0) { + if (b == (M(Lu) | M(Ll))) { + goto add_range; + } else { + c0 += ((gc_mask & M(Ll)) != 0); + for(; c0 < c; c0 += 2) { + if (cr_add_interval(cr, c0, c0 + 1)) + return -1; + } + } + } + } else if ((gc_mask >> v) & 1) { + add_range: + if (cr_add_interval(cr, c0, c)) + return -1; + } + } + return 0; +} + +static int unicode_prop1(CharRange *cr, int prop_idx) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, bit; + + p = unicode_prop_table[prop_idx]; + p_end = p + unicode_prop_len_table[prop_idx]; + c = 0; + bit = 0; + while (p < p_end) { + c0 = c; + b = *p++; + if (b < 64) { + c += (b >> 3) + 1; + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + c0 = c; + c += (b & 7) + 1; + } else if (b >= 0x80) { + c += b - 0x80 + 1; + } else if (b < 0x60) { + c += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + c += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + } + return 0; +} + +#define CASE_U (1 << 0) +#define CASE_L (1 << 1) +#define CASE_F (1 << 2) + +/* use the case conversion table to generate range of characters. + CASE_U: set char if modified by uppercasing, + CASE_L: set char if modified by lowercasing, + CASE_F: set char if modified by case folding, + */ +static int unicode_case1(CharRange *cr, int case_mask) +{ +#define MR(x) (1 << RUN_TYPE_ ## x) + const uint32_t tab_run_mask[3] = { + MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | + MR(UF_D1_EXT) | MR(U_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + + MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2), + + MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + }; +#undef MR + uint32_t mask, v, code, type, len, i, idx; + + if (case_mask == 0) + return 0; + mask = 0; + for(i = 0; i < 3; i++) { + if ((case_mask >> i) & 1) + mask |= tab_run_mask[i]; + } + for(idx = 0; idx < countof(case_conv_table1); idx++) { + v = case_conv_table1[idx]; + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if ((mask >> type) & 1) { + // printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1); + switch(type) { + case RUN_TYPE_UL: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + code += ((case_mask & CASE_U) != 0); + for(i = 0; i < len; i += 2) { + if (cr_add_interval(cr, code + i, code + i + 1)) + return -1; + } + break; + case RUN_TYPE_LSU: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + if (!(case_mask & CASE_U)) { + if (cr_add_interval(cr, code, code + 1)) + return -1; + } + if (cr_add_interval(cr, code + 1, code + 2)) + return -1; + if (case_mask & CASE_U) { + if (cr_add_interval(cr, code + 2, code + 3)) + return -1; + } + break; + default: + def_case: + if (cr_add_interval(cr, code, code + len)) + return -1; + break; + } + } + } + return 0; +} + +static int point_cmp(const void *p1, const void *p2, void *arg) +{ + uint32_t v1 = *(uint32_t *)p1; + uint32_t v2 = *(uint32_t *)p2; + return (v1 > v2) - (v1 < v2); +} + +static void cr_sort_and_remove_overlap(CharRange *cr) +{ + uint32_t start, end, start1, end1, i, j; + + /* the resulting ranges are not necessarily sorted and may overlap */ + rqsort(cr->points, cr->len / 2, sizeof(cr->points[0]) * 2, point_cmp, NULL); + j = 0; + for(i = 0; i < cr->len; ) { + start = cr->points[i]; + end = cr->points[i + 1]; + i += 2; + while (i < cr->len) { + start1 = cr->points[i]; + end1 = cr->points[i + 1]; + if (start1 > end) { + /* |------| + * |-------| */ + break; + } else if (end1 <= end) { + /* |------| + * |--| */ + i += 2; + } else { + /* |------| + * |-------| */ + end = end1; + i += 2; + } + } + cr->points[j] = start; + cr->points[j + 1] = end; + j += 2; + } + cr->len = j; +} + +/* canonicalize a character set using the JS regex case folding rules + (see lre_canonicalize()) */ +int cr_regexp_canonicalize(CharRange *cr, bool is_unicode) +{ + CharRange cr_inter, cr_mask, cr_result, cr_sub; + uint32_t v, code, len, i, idx, start, end, c, d_start, d_end, d; + + cr_init(&cr_mask, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_inter, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_result, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_sub, cr->mem_opaque, cr->realloc_func); + + if (unicode_case1(&cr_mask, is_unicode ? CASE_F : CASE_U)) + goto fail; + if (cr_op(&cr_inter, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + if (cr_invert(&cr_mask)) + goto fail; + if (cr_op(&cr_sub, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + /* cr_inter = cr & cr_mask */ + /* cr_sub = cr & ~cr_mask */ + + /* use the case conversion table to compute the result */ + d_start = -1; + d_end = -1; + idx = 0; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + for(i = 0; i < cr_inter.len; i += 2) { + start = cr_inter.points[i]; + end = cr_inter.points[i + 1]; + + for(c = start; c < end; c++) { + for(;;) { + if (c >= code && c < code + len) + break; + idx++; + assert(idx < countof(case_conv_table1)); + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + } + d = lre_case_folding_entry(c, idx, v, is_unicode); + /* try to merge with the current interval */ + if (d_start == -1) { + d_start = d; + d_end = d + 1; + } else if (d_end == d) { + d_end++; + } else { + cr_add_interval(&cr_result, d_start, d_end); + d_start = d; + d_end = d + 1; + } + } + } + if (d_start != -1) { + if (cr_add_interval(&cr_result, d_start, d_end)) + goto fail; + } + + /* the resulting ranges are not necessarily sorted and may overlap */ + cr_sort_and_remove_overlap(&cr_result); + + /* or with the character not affected by the case folding */ + cr->len = 0; + if (cr_op(cr, cr_result.points, cr_result.len, cr_sub.points, cr_sub.len, CR_OP_UNION)) + goto fail; + + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return 0; + fail: + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return -1; +} + +typedef enum { + POP_GC, + POP_PROP, + POP_CASE, + POP_UNION, + POP_INTER, + POP_XOR, + POP_INVERT, + POP_END, +} PropOPEnum; + +#define POP_STACK_LEN_MAX 4 + +static int unicode_prop_ops(CharRange *cr, ...) +{ + va_list ap; + CharRange stack[POP_STACK_LEN_MAX]; + int stack_len, op, ret, i; + uint32_t a; + + va_start(ap, cr); + stack_len = 0; + for(;;) { + op = va_arg(ap, int); + switch(op) { + case POP_GC: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_general_category1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_PROP: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_prop1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_CASE: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_case1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_UNION: + case POP_INTER: + case POP_XOR: + { + CharRange *cr1, *cr2, *cr3; + assert(stack_len >= 2); + assert(stack_len < POP_STACK_LEN_MAX); + cr1 = &stack[stack_len - 2]; + cr2 = &stack[stack_len - 1]; + cr3 = &stack[stack_len++]; + cr_init(cr3, cr->mem_opaque, cr->realloc_func); + if (cr_op(cr3, cr1->points, cr1->len, + cr2->points, cr2->len, op - POP_UNION + CR_OP_UNION)) + goto fail; + cr_free(cr1); + cr_free(cr2); + *cr1 = *cr3; + stack_len -= 2; + } + break; + case POP_INVERT: + assert(stack_len >= 1); + if (cr_invert(&stack[stack_len - 1])) + goto fail; + break; + case POP_END: + goto done; + default: + abort(); + } + } + done: + va_end(ap); + assert(stack_len == 1); + ret = cr_copy(cr, &stack[0]); + cr_free(&stack[0]); + return ret; + fail: + va_end(ap); + for(i = 0; i < stack_len; i++) + cr_free(&stack[i]); + return -1; +} + +static const uint32_t unicode_gc_mask_table[] = { + M(Lu) | M(Ll) | M(Lt), /* LC */ + M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo), /* L */ + M(Mn) | M(Mc) | M(Me), /* M */ + M(Nd) | M(Nl) | M(No), /* N */ + M(Sm) | M(Sc) | M(Sk) | M(So), /* S */ + M(Pc) | M(Pd) | M(Ps) | M(Pe) | M(Pi) | M(Pf) | M(Po), /* P */ + M(Zs) | M(Zl) | M(Zp), /* Z */ + M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn), /* C */ +}; + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_general_category(CharRange *cr, const char *gc_name) +{ + int gc_idx; + uint32_t gc_mask; + + gc_idx = unicode_find_name(unicode_gc_name_table, gc_name); + if (gc_idx < 0) + return -2; + if (gc_idx <= UNICODE_GC_Co) { + gc_mask = (uint64_t)1 << gc_idx; + } else { + gc_mask = unicode_gc_mask_table[gc_idx - UNICODE_GC_LC]; + } + return unicode_general_category1(cr, gc_mask); +} + + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_prop(CharRange *cr, const char *prop_name) +{ + int prop_idx, ret; + + prop_idx = unicode_find_name(unicode_prop_name_table, prop_name); + if (prop_idx < 0) + return -2; + prop_idx += UNICODE_PROP_ASCII_Hex_Digit; + + ret = 0; + switch(prop_idx) { + case UNICODE_PROP_ASCII: + if (cr_add_interval(cr, 0x00, 0x7f + 1)) + return -1; + break; + case UNICODE_PROP_Any: + if (cr_add_interval(cr, 0x00000, 0x10ffff + 1)) + return -1; + break; + case UNICODE_PROP_Assigned: + ret = unicode_prop_ops(cr, + POP_GC, M(Cn), + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Math: + ret = unicode_prop_ops(cr, + POP_GC, M(Sm), + POP_PROP, UNICODE_PROP_Other_Math, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Lowercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Ll), + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Uppercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Cased: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Alphabetic: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Alphabetic, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Grapheme_Base: + ret = unicode_prop_ops(cr, + POP_GC, M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn) | M(Zl) | M(Zp) | M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Grapheme_Extend: + ret = unicode_prop_ops(cr, + POP_GC, M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_XID_Start: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Start1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_XID_Continue: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) | + M(Mn) | M(Mc) | M(Nd) | M(Pc), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_ID_Continue, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Continue1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_Changes_When_Uppercased: + ret = unicode_case1(cr, CASE_U); + break; + case UNICODE_PROP_Changes_When_Lowercased: + ret = unicode_case1(cr, CASE_L); + break; + case UNICODE_PROP_Changes_When_Casemapped: + ret = unicode_case1(cr, CASE_U | CASE_L | CASE_F); + break; + case UNICODE_PROP_Changes_When_Titlecased: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_U, + POP_PROP, UNICODE_PROP_Changes_When_Titlecased1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_Casefolded1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_NFKC_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_NFKC_Casefolded1, + POP_XOR, + POP_END); + break; + /* we use the existing tables */ + case UNICODE_PROP_ID_Continue: + ret = unicode_prop_ops(cr, + POP_PROP, UNICODE_PROP_ID_Start, + POP_PROP, UNICODE_PROP_ID_Continue1, + POP_XOR, + POP_END); + break; + default: + if (prop_idx >= countof(unicode_prop_table)) + return -2; + ret = unicode_prop1(cr, prop_idx); + break; + } + return ret; +} diff --git a/NativeScript/napi/android/quickjs/source_ng/libunicode.h b/NativeScript/napi/android/quickjs/source_ng/libunicode.h new file mode 100755 index 000000000..8e6f2a01d --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/libunicode.h @@ -0,0 +1,126 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LIBUNICODE_H +#define LIBUNICODE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LRE_CC_RES_LEN_MAX 3 + +typedef enum { + UNICODE_NFC, + UNICODE_NFD, + UNICODE_NFKC, + UNICODE_NFKD, +} UnicodeNormalizationEnum; + +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); +int lre_canonicalize(uint32_t c, bool is_unicode); +bool lre_is_cased(uint32_t c); +bool lre_is_case_ignorable(uint32_t c); + +/* char ranges */ + +typedef struct { + int len; /* in points, always even */ + int size; + uint32_t *points; /* points sorted by increasing value */ + void *mem_opaque; + void *(*realloc_func)(void *opaque, void *ptr, size_t size); +} CharRange; + +typedef enum { + CR_OP_UNION, + CR_OP_INTER, + CR_OP_XOR, +} CharRangeOpEnum; + +void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); +void cr_free(CharRange *cr); +int cr_realloc(CharRange *cr, int size); +int cr_copy(CharRange *cr, const CharRange *cr1); + +static inline int cr_add_point(CharRange *cr, uint32_t v) +{ + if (cr->len >= cr->size) { + if (cr_realloc(cr, cr->len + 1)) + return -1; + } + cr->points[cr->len++] = v; + return 0; +} + +static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + if ((cr->len + 2) > cr->size) { + if (cr_realloc(cr, cr->len + 2)) + return -1; + } + cr->points[cr->len++] = c1; + cr->points[cr->len++] = c2; + return 0; +} + +int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len); + +static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + uint32_t b_pt[2]; + b_pt[0] = c1; + b_pt[1] = c2 + 1; + return cr_union1(cr, b_pt, 2); +} + +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op); + +int cr_invert(CharRange *cr); +int cr_regexp_canonicalize(CharRange *cr, bool is_unicode); + +bool lre_is_id_start(uint32_t c); +bool lre_is_id_continue(uint32_t c); +bool lre_is_white_space(uint32_t c); + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); + +/* Unicode character range functions */ + +int unicode_script(CharRange *cr, + const char *script_name, bool is_ext); +int unicode_general_category(CharRange *cr, const char *gc_name); +int unicode_prop(CharRange *cr, const char *prop_name); + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIBUNICODE_H */ diff --git a/NativeScript/napi/android/quickjs/source_ng/list.h b/NativeScript/napi/android/quickjs/source_ng/list.h new file mode 100755 index 000000000..b8dd71681 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/list.h @@ -0,0 +1,107 @@ +/* + * Linux klist like system + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LIST_H +#define LIST_H + +#ifndef NULL +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +#define LIST_HEAD_INIT(el) { &(el), &(el) } + +/* return the pointer of type 'type *' containing 'el' as field 'member' */ +#define list_entry(el, type, member) container_of(el, type, member) + +static inline void init_list_head(struct list_head *head) +{ + head->prev = head; + head->next = head; +} + +/* insert 'el' between 'prev' and 'next' */ +static inline void __list_add(struct list_head *el, + struct list_head *prev, struct list_head *next) +{ + prev->next = el; + el->prev = prev; + el->next = next; + next->prev = el; +} + +/* add 'el' at the head of the list 'head' (= after element head) */ +static inline void list_add(struct list_head *el, struct list_head *head) +{ + __list_add(el, head, head->next); +} + +/* add 'el' at the end of the list 'head' (= before element head) */ +static inline void list_add_tail(struct list_head *el, struct list_head *head) +{ + __list_add(el, head->prev, head); +} + +static inline void list_del(struct list_head *el) +{ + struct list_head *prev, *next; + prev = el->prev; + next = el->next; + prev->next = next; + next->prev = prev; + el->prev = NULL; /* fail safe */ + el->next = NULL; /* fail safe */ +} + +static inline int list_empty(struct list_head *el) +{ + return el->next == el; +} + +#define list_for_each(el, head) \ + for(el = (head)->next; el != (head); el = el->next) + +#define list_for_each_safe(el, el1, head) \ + for(el = (head)->next, el1 = el->next; el != (head); \ + el = el1, el1 = el->next) + +#define list_for_each_prev(el, head) \ + for(el = (head)->prev; el != (head); el = el->prev) + +#define list_for_each_prev_safe(el, el1, head) \ + for(el = (head)->prev, el1 = el->prev; el != (head); \ + el = el1, el1 = el->prev) + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIST_H */ diff --git a/NativeScript/napi/android/quickjs/source_ng/meson.build b/NativeScript/napi/android/quickjs/source_ng/meson.build new file mode 100755 index 000000000..1c178513c --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/meson.build @@ -0,0 +1,635 @@ +project( + 'quickjs-ng', + 'c', + version: '0.11.0', + default_options: [ + 'c_std=gnu11,c11', + 'warning_level=3', + 'default_library=static', + ], + license: 'MIT', + license_files: 'LICENSE', + meson_version: '>=1.3.0', +) + +host_system = host_machine.system() +cc = meson.get_compiler('c') + +qjs_gcc_warning_args = [ + '-Wno-unsafe-buffer-usage', + '-Wno-sign-conversion', + '-Wno-nonportable-system-include-path', + '-Wno-implicit-int-conversion', + '-Wno-shorten-64-to-32', + '-Wno-reserved-macro-identifier', + '-Wno-reserved-identifier', + '-Wdeprecated-declarations', + + '-Wno-implicit-fallthrough', + '-Wno-sign-compare', + '-Wno-missing-field-initializers', + '-Wno-unused-parameter', + '-Wno-unused-but-set-variable', + '-Wno-array-bounds', + '-Wno-format-truncation', +] +qjs_gcc_args = [ + '-funsigned-char', +] + +if host_system == 'darwin' + # https://github.com/quickjs-ng/quickjs/issues/453 + qjs_gcc_warning_args += '-Wno-maybe-uninitialized' +endif + +# https://github.com/microsoft/cpp-docs/tree/main/docs/error-messages/compiler-warnings +qjs_msvc_warning_args = [ + '/wd4018', # -Wno-sign-conversion + '/wd4061', # -Wno-implicit-fallthrough + '/wd4100', # -Wno-unused-parameter + '/wd4200', # -Wno-zero-length-array + '/wd4242', # -Wno-shorten-64-to-32 + '/wd4244', # -Wno-shorten-64-to-32 + '/wd4245', # -Wno-sign-compare + '/wd4267', # -Wno-shorten-64-to-32 + '/wd4388', # -Wno-sign-compare + '/wd4389', # -Wno-sign-compare + '/wd4710', # Function not inlined + '/wd4711', # Function was inlined + '/wd4820', # Padding added after construct + '/wd4996', # -Wdeprecated-declarations + '/wd5045', # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified +] +qjs_msvc_args = [ + '/experimental:c11atomics', + '/J', # -funsigned-char +] + +if cc.get_argument_syntax() == 'msvc' + add_project_arguments( + cc.get_supported_arguments(qjs_msvc_warning_args), + cc.get_id().contains('clang') ? cc.get_supported_arguments(qjs_gcc_warning_args) : [], + qjs_msvc_args, + language: 'c', + ) +else + add_project_arguments( + cc.get_supported_arguments(qjs_gcc_warning_args), + qjs_gcc_args, + language: 'c', + ) +endif + +if host_system == 'windows' + # Set a 8MB default stack size on Windows. + # It defaults to 1MB on MSVC, which is the same as our current JS stack size, + # so it will overflow and crash otherwise. + # On MinGW it defaults to 2MB. + stack_size = 8 * 1024 * 1024 + if cc.get_argument_syntax() == 'msvc' + add_project_link_arguments(f'/STACK:@stack_size@', language: 'c') + else + add_project_link_arguments(f'-Wl,--stack,@stack_size@', language: 'c') + endif +endif + +if meson.is_cross_build() + native_cc = meson.get_compiler('c', native: true) + + if native_cc.get_argument_syntax() == 'msvc' + # https://github.com/microsoft/cpp-docs/tree/main/docs/error-messages/compiler-warnings + add_project_arguments( + native_cc.get_supported_arguments(qjs_msvc_warning_args), + native_cc.get_id().contains('clang') ? native_cc.get_supported_arguments(qjs_gcc_warning_args) : [], + qjs_msvc_args, + + language: 'c', + native: true, + ) + else + add_project_arguments( + native_cc.get_supported_arguments(qjs_gcc_warning_args), + qjs_gcc_args, + + language: 'c', + native: true, + ) + endif +endif +if get_option('debug') + add_project_arguments( + cc.get_supported_arguments('-fno-omit-frame-pointer'), + language: 'c', + ) +endif + +qjs_sys_deps = [] + +m_dep = cc.find_library('m', required: false) +qjs_sys_deps += m_dep +qjs_sys_deps += dependency('threads', required: false) +qjs_sys_deps += dependency('dl', required: false) + +qjs_srcs = files( + 'cutils.c', + 'dtoa.c', + 'libregexp.c', + 'libunicode.c', + 'quickjs.c', +) +qjs_hdrs = files( + 'quickjs.h', +) + +qjs_libc = get_option('libc') +qjs_libc_srcs = files('quickjs-libc.c') +qjs_libc_hdrs = files('quickjs-libc.h') + +if qjs_libc + qjs_hdrs += qjs_libc_hdrs +endif + +qjs_parser = get_option('parser') + +qjs_c_args = ['-D_GNU_SOURCE'] + +if host_system == 'windows' + qjs_c_args += ['-DWIN32_LEAN_AND_MEAN', '-D_WIN32_WINNT=0x0601'] +endif + +if not qjs_parser + qjs_c_args += ['-DQJS_DISABLE_PARSER'] +endif + +qjs_libc_lib = static_library( + 'quickjs-libc', + qjs_libc_srcs, + + dependencies: qjs_sys_deps, + c_args: qjs_c_args, + gnu_symbol_visibility: 'hidden', +) + +qjs_lib = library( + 'qjs', + qjs_srcs, + + # export public headers + generator( + find_program('cp', 'xcopy'), + output: ['@PLAINNAME@'], + arguments: ['@INPUT@', '@OUTPUT@'], + ).process(qjs_hdrs), + + dependencies: qjs_sys_deps, + link_whole: qjs_libc ? qjs_libc_lib : [], + c_args: qjs_c_args, + gnu_symbol_visibility: 'hidden', + + install: true, + version: meson.project_version(), +) + +qjs_export_variables = [ + f'have_parser=@qjs_parser@' +] + +qjs_dep = declare_dependency( + link_with: qjs_lib, + dependencies: qjs_sys_deps, + include_directories: qjs_lib.private_dir_include(), + variables: qjs_export_variables, +) + +if host_system == 'emscripten' + qjs_wasm_export_name = 'getQuickJs' + executable( + 'qjs_wasm', + qjs_srcs, + link_args: cc.get_supported_link_arguments( + # in emscripten 3.x, this will be set to 16k which is too small for quickjs. + '-sSTACK_SIZE=@0@'.format(2 * 1024 * 1024), # let it be 2m = 2 * 1024 * 1024, otherwise, stack overflow may be occured at bootstrap + '-sNO_INVOKE_RUN', + '-sNO_EXIT_RUNTIME', + '-sMODULARIZE', # do not mess the global + '-sEXPORT_ES6', # export js file to morden es module + '-sEXPORT_NAME=@0@'.format(qjs_wasm_export_name), # give a name + '-sTEXTDECODER=1', # it will be 2 if we use -Oz, and that will cause js -> c string convertion fail + '-sNO_DEFAULT_TO_CXX', # this project is pure c project, no need for c plus plus handle + '-sEXPORTED_RUNTIME_METHODS=ccall,cwrap', + ), + dependencies: m_dep, + c_args: qjs_c_args, + ) +endif + +install_headers(qjs_hdrs, subdir: 'quickjs-ng') + +if not meson.is_subproject() + docdir = get_option('docdir') + datadir = get_option('datadir') + + if docdir == '' + docdir = datadir / 'doc' / meson.project_name() + endif + install_data( + 'LICENSE', + install_dir: docdir, + + install_tag: 'doc', + ) + install_subdir( + 'examples', + install_dir: docdir, + + strip_directory: false, + install_tag: 'doc', + ) +endif + +meson.override_dependency('quickjs-ng', qjs_dep) + +import('pkgconfig').generate( + qjs_lib, + subdirs: 'quickjs-ng', + name: 'quickjs-ng', + description: 'QuickJS, the Next Generation: a mighty JavaScript engine', + url: 'https://github.com/quickjs-ng/quickjs', + version: meson.project_version(), + variables: qjs_export_variables, +) + +if not qjs_parser + subdir_done() +endif + +# QuickJS bytecode compiler +qjsc_srcs = files( + 'qjsc.c', +) +qjsc_exe = executable( + 'qjsc', + qjsc_srcs, + + c_args: qjs_c_args, + link_with: qjs_libc ? [] : qjs_libc_lib, + dependencies: qjs_dep, + + install: true, +) + +mimalloc_dep = [] +mimalloc_sys_dep = dependency('mimalloc', required: get_option('cli_mimalloc')) +if mimalloc_sys_dep.found() + mimalloc_dep = declare_dependency( + dependencies: mimalloc_sys_dep, + compile_args: '-DQJS_USE_MIMALLOC', + ) +endif + +# QuickJS CLI +qjs_exe_srcs = files( + 'gen/repl.c', + 'gen/standalone.c', + 'qjs.c', +) +qjs_exe = executable( + 'qjs', + qjs_exe_srcs, + + c_args: qjs_c_args, + link_with: qjs_libc ? [] : qjs_libc_lib, + dependencies: [qjs_dep, mimalloc_dep], + export_dynamic: true, + + install: true, +) + +if meson.is_cross_build() + mimalloc_native_dep = [] + mimalloc_sys_native_dep = dependency('mimalloc', required: get_option('cli_mimalloc'), native: true) + if mimalloc_sys_dep.found() + mimalloc_native_dep = declare_dependency( + dependencies: mimalloc_sys_native_dep, + compile_args: '-DQJS_USE_MIMALLOC', + ) + endif + + qjs_sys_native_deps = [ + native_cc.find_library('m', required: false), + dependency('threads', required: false, native: true), + dependency('dl', required: false, native: true), + ] + qjs_native_lib = static_library( + 'qjs_native', + qjs_srcs, + qjs_libc_srcs, + + dependencies: qjs_sys_native_deps, + c_args: qjs_c_args, + gnu_symbol_visibility: 'hidden', + + build_by_default: false, + native: true, + install: false, + ) + + meson.override_find_program( + 'qjsc', + executable( + 'qjsc_native', + qjsc_srcs, + + c_args: qjs_c_args, + link_with: qjs_native_lib, + dependencies: qjs_sys_native_deps, + + build_by_default: false, + native: true, + install: false, + ), + ) + meson.override_find_program( + 'qjs', + executable( + 'qjs_native', + qjs_exe_srcs, + + c_args: qjs_c_args, + link_with: qjs_native_lib, + dependencies: [qjs_sys_native_deps, mimalloc_native_dep], + export_dynamic: true, + + build_by_default: false, + native: true, + install: false, + ), + ) +else + meson.override_find_program('qjsc', qjsc_exe) + meson.override_find_program('qjs', qjs_exe) +endif + +tests = get_option('tests').disable_auto_if(meson.is_subproject()) +examples = get_option('examples').disable_auto_if(meson.is_subproject()) + +if tests.allowed() + if host_system != 'emscripten' + # Test262 runner + run262_exe = executable( + 'run-test262', + 'run-test262.c', + qjs_libc_srcs, + + c_args: qjs_c_args, + dependencies: qjs_dep, + ) + + test( + 'test', + run262_exe, + args: ['-c', files('tests.conf')], + workdir: meson.current_source_dir(), + ) + + foreach bench : [ + 'empty_loop', + + 'date_now', + + 'prop_read', + 'prop_write', + 'prop_create', + 'prop_delete', + + 'array_read', + 'array_write', + 'array_prop_create', + 'array_length_decr', + 'array_hole_length_decr', + 'array_push', + 'array_pop', + + 'typed_array_read', + 'typed_array_write', + + 'global_read', + 'global_write', + 'global_write_strict', + + 'local_destruct', + 'global_destruct', + 'global_destruct_strict', + + 'func_call', + 'closure_var', + + 'int_arith', + 'float_arith', + + 'map_set', + 'map_delete', + 'weak_map_set', + 'weak_map_delete', + + 'array_for', + 'array_for_in', + 'array_for_of', + + 'math_min', + + 'object_null', + + 'regexp_ascii', + 'regexp_utf16', + + 'string_build1', + 'string_build2', + + 'string_slice1', + 'string_slice2', + 'string_slice3', + + 'sort_bench', + + 'int_to_string', + 'int_toString', + + 'float_to_string', + 'float_toString', + 'float_toFixed', + 'float_toPrecision', + 'float_toExponential', + + 'string_to_int', + 'string_to_float', + + 'bigint64_arith', + 'bigint256_arith', + ] + benchmark( + bench, + qjs_exe, + args: [files('tests/microbench.js'), bench], + ) + endforeach + endif + + # API test + test( + 'api', + executable( + 'api-test', + 'api-test.c', + + c_args: qjs_c_args, + dependencies: qjs_dep, + build_by_default: false, + ), + ) + + # Function.toString() test + test( + 'function_source', + executable( + 'function_source', + 'gen/function_source.c', + qjs_libc_srcs, + + c_args: qjs_c_args, + dependencies: qjs_dep, + build_by_default: false, + ), + ) +endif + +# Unicode generator +unicode_gen = executable( + 'unicode_gen', + 'cutils.c', + 'unicode_gen.c', + + c_args: qjs_c_args, + build_by_default: false, +) + +run_target( + 'libunicode-table.h', + command: [ + unicode_gen, + meson.current_source_dir() / 'unicode', + files('libunicode-table.h'), + ], +) + +# bytecode to c source code for builtin and examples +alias_target('codegen', + run_target( + 'codegen_repl.c', + command: [ + qjsc_exe, + '-ss', + '-o', files('gen/repl.c'), + '-m', + '-n', 'repl.js', + files('repl.js'), + ], + ), + run_target( + 'codegen_standalone.c', + command: [ + qjsc_exe, + '-ss', + '-o', files('gen/standalone.c'), + '-m', + '-n', 'standalone.js', + files('standalone.js'), + ], + ), + run_target( + 'codegen_function_source.c', + command: [ + qjsc_exe, + '-e', + '-o', files('gen/function_source.c'), + '-n', 'tests/function_source.js', + files('tests/function_source.js'), + ], + ), + run_target( + 'codegen_hello.c', + command: [ + qjsc_exe, + '-e', + '-o', files('gen/hello.c'), + '-n', 'examples/hello.js', + files('examples/hello.js'), + ], + ), + run_target( + 'codegen_hello_module.c', + command: [ + qjsc_exe, + '-e', + '-o', files('gen/hello_module.c'), + '-m', + '-n', 'examples/hello_module.js', + files('examples/hello_module.js'), + ], + ), + run_target( + 'codegen_test_fib.c', + command: [ + qjsc_exe, + '-e', + '-o', files('gen/test_fib.c'), + '-m', + '-n', 'examples/test_fib.js', + files('examples/test_fib.js'), + ], + ), + run_target( + 'codegen_builtin-array-fromasync.h', + command: [ + qjsc_exe, + '-C', + '-ss', + '-o', files('builtin-array-fromasync.h'), + '-n', 'builtin-array-fromasync.js', + files('builtin-array-fromasync.js'), + ], + ), +) + +if examples.allowed() + executable( + 'hello', + 'gen/hello.c', + qjs_libc_srcs, + + c_args: qjs_c_args, + dependencies: qjs_dep, + ) + + executable( + 'hello_module', + 'gen/hello_module.c', + qjs_libc_srcs, + + c_args: qjs_c_args, + dependencies: qjs_dep, + ) + + subdir('examples') + + executable( + 'test_fib', + 'examples/fib.c', + 'gen/test_fib.c', + qjs_libc_srcs, + + c_args: qjs_c_args, + dependencies: qjs_dep, + export_dynamic: true, + ) +endif diff --git a/NativeScript/napi/android/quickjs/source_ng/meson_options.txt b/NativeScript/napi/android/quickjs/source_ng/meson_options.txt new file mode 100755 index 000000000..20e661e1a --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/meson_options.txt @@ -0,0 +1,6 @@ +option('tests', type: 'feature', description: 'build tests') +option('examples', type: 'feature', description: 'build examples') +option('libc', type: 'boolean', value: false, description: 'build qjs standard library modules as part of the library') +option('cli_mimalloc', type: 'feature', value: 'disabled', description: 'build qjs cli with mimalloc') +option('docdir', type: 'string', description: 'documentation directory') +option('parser', type: 'boolean', value: true, description: 'Enable JS source code parser') diff --git a/NativeScript/napi/android/quickjs/source_ng/qjs.c b/NativeScript/napi/android/quickjs/source_ng/qjs.c new file mode 100755 index 000000000..4aed6837e --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/qjs.c @@ -0,0 +1,748 @@ +/* + * QuickJS stand alone interpreter + * + * Copyright (c) 2017-2021 Fabrice Bellard + * Copyright (c) 2017-2021 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "quickjs.h" +#include "quickjs-libc.h" + +#ifdef QJS_USE_MIMALLOC +#include +#endif + +extern const uint8_t qjsc_repl[]; +extern const uint32_t qjsc_repl_size; +extern const uint8_t qjsc_standalone[]; +extern const uint32_t qjsc_standalone_size; + +// Must match standalone.js +#define TRAILER_SIZE 12 +static const char trailer_magic[] = "quickjs2"; +static const int trailer_magic_size = sizeof(trailer_magic) - 1; +static const int trailer_size = TRAILER_SIZE; + +static int qjs__argc; +static char **qjs__argv; + + +static bool is_standalone(const char *exe) +{ + FILE *exe_f = fopen(exe, "rb"); + if (!exe_f) + return false; + if (fseek(exe_f, -trailer_size, SEEK_END) < 0) + goto fail; + uint8_t buf[TRAILER_SIZE]; + if (fread(buf, 1, trailer_size, exe_f) != trailer_size) + goto fail; + fclose(exe_f); + return !memcmp(buf, trailer_magic, trailer_magic_size); +fail: + fclose(exe_f); + return false; +} + +static JSValue load_standalone_module(JSContext *ctx) +{ + JSModuleDef *m; + JSValue obj, val; + obj = JS_ReadObject(ctx, qjsc_standalone, qjsc_standalone_size, JS_READ_OBJ_BYTECODE); + if (JS_IsException(obj)) + goto exception; + assert(JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE); + if (JS_ResolveModule(ctx, obj) < 0) { + JS_FreeValue(ctx, obj); + goto exception; + } + if (js_module_set_import_meta(ctx, obj, false, true) < 0) { + JS_FreeValue(ctx, obj); + goto exception; + } + val = JS_EvalFunction(ctx, JS_DupValue(ctx, obj)); + val = js_std_await(ctx, val); + + if (JS_IsException(val)) { + JS_FreeValue(ctx, obj); + exception: + js_std_dump_error(ctx); + exit(1); + } + JS_FreeValue(ctx, val); + + m = JS_VALUE_GET_PTR(obj); + JS_FreeValue(ctx, obj); + return JS_GetModuleNamespace(ctx, m); +} + +static int eval_buf(JSContext *ctx, const void *buf, int buf_len, + const char *filename, int eval_flags) +{ + bool use_realpath; + JSValue val; + int ret; + + if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) { + /* for the modules, we compile then run to be able to set + import.meta */ + val = JS_Eval(ctx, buf, buf_len, filename, + eval_flags | JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(val)) { + // ex. "" pr "/dev/stdin" + use_realpath = + !(*filename == '<' || !strncmp(filename, "/dev/", 5)); + if (js_module_set_import_meta(ctx, val, use_realpath, true) < 0) { + js_std_dump_error(ctx); + ret = -1; + goto end; + } + val = JS_EvalFunction(ctx, val); + } + val = js_std_await(ctx, val); + } else { + val = JS_Eval(ctx, buf, buf_len, filename, eval_flags); + } + if (JS_IsException(val)) { + js_std_dump_error(ctx); + ret = -1; + } else { + ret = 0; + } +end: + JS_FreeValue(ctx, val); + return ret; +} + +static int eval_file(JSContext *ctx, const char *filename, int module) +{ + uint8_t *buf; + int ret, eval_flags; + size_t buf_len; + + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + perror(filename); + exit(1); + } + + if (module < 0) { + module = (js__has_suffix(filename, ".mjs") || + JS_DetectModule((const char *)buf, buf_len)); + } + if (module) + eval_flags = JS_EVAL_TYPE_MODULE; + else + eval_flags = JS_EVAL_TYPE_GLOBAL; + ret = eval_buf(ctx, buf, buf_len, filename, eval_flags); + js_free(ctx, buf); + return ret; +} + +static int64_t parse_limit(const char *arg) { + char *p; + unsigned long unit = 1024; /* default to traditional KB */ + double d = strtod(arg, &p); + + if (p == arg) { + fprintf(stderr, "Invalid limit: %s\n", arg); + return -1; + } + + if (*p) { + switch (*p++) { + case 'b': case 'B': unit = 1UL << 0; break; + case 'k': case 'K': unit = 1UL << 10; break; /* IEC kibibytes */ + case 'm': case 'M': unit = 1UL << 20; break; /* IEC mebibytes */ + case 'g': case 'G': unit = 1UL << 30; break; /* IEC gigibytes */ + default: + fprintf(stderr, "Invalid limit: %s, unrecognized suffix, only k,m,g are allowed\n", arg); + return -1; + } + if (*p) { + fprintf(stderr, "Invalid limit: %s, only one suffix allowed\n", arg); + return -1; + } + } + + return (int64_t)(d * unit); +} + +static JSValue js_gc(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JS_RunGC(JS_GetRuntime(ctx)); + return JS_UNDEFINED; +} + +static JSValue js_navigator_get_userAgent(JSContext *ctx, JSValueConst this_val) +{ + char version[32]; + snprintf(version, sizeof(version), "quickjs-ng/%s", JS_GetVersion()); + return JS_NewString(ctx, version); +} + +static const JSCFunctionListEntry navigator_proto_funcs[] = { + JS_CGETSET_DEF2("userAgent", js_navigator_get_userAgent, NULL, JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Navigator", JS_PROP_CONFIGURABLE), +}; + +static const JSCFunctionListEntry global_obj[] = { + JS_CFUNC_DEF("gc", 0, js_gc), +}; + +/* also used to initialize the worker context */ +static JSContext *JS_NewCustomContext(JSRuntime *rt) +{ + JSContext *ctx; + ctx = JS_NewContext(rt); + if (!ctx) + return NULL; + /* system modules */ + js_init_module_std(ctx, "qjs:std"); + js_init_module_os(ctx, "qjs:os"); + js_init_module_bjson(ctx, "qjs:bjson"); + + JSValue global = JS_GetGlobalObject(ctx); + JS_SetPropertyFunctionList(ctx, global, global_obj, countof(global_obj)); + JSValue args = JS_NewArray(ctx); + int i; + for(i = 0; i < qjs__argc; i++) { + JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, qjs__argv[i])); + } + JS_SetPropertyStr(ctx, global, "execArgv", args); + JS_SetPropertyStr(ctx, global, "argv0", JS_NewString(ctx, qjs__argv[0])); + JSValue navigator_proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, navigator_proto, navigator_proto_funcs, countof(navigator_proto_funcs)); + JSValue navigator = JS_NewObjectProto(ctx, navigator_proto); + JS_DefinePropertyValueStr(ctx, global, "navigator", navigator, JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE); + JS_FreeValue(ctx, global); + JS_FreeValue(ctx, navigator_proto); + + return ctx; +} + +struct trace_malloc_data { + uint8_t *base; +}; + +static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr, + struct trace_malloc_data *dp) +{ + return ptr - dp->base; +} + +static void JS_PRINTF_FORMAT_ATTR(2, 3) js_trace_malloc_printf(void *opaque, JS_PRINTF_FORMAT const char *fmt, ...) +{ + va_list ap; + int c; + + va_start(ap, fmt); + while ((c = *fmt++) != '\0') { + if (c == '%') { + /* only handle %p and %zd */ + if (*fmt == 'p') { + uint8_t *ptr = va_arg(ap, void *); + if (ptr == NULL) { + printf("NULL"); + } else { + printf("H%+06lld.%zd", + js_trace_malloc_ptr_offset(ptr, opaque), + js__malloc_usable_size(ptr)); + } + fmt++; + continue; + } + if (fmt[0] == 'z' && fmt[1] == 'd') { + size_t sz = va_arg(ap, size_t); + printf("%zd", sz); + fmt += 2; + continue; + } + } + putc(c, stdout); + } + va_end(ap); +} + +static void js_trace_malloc_init(struct trace_malloc_data *s) +{ + free(s->base = malloc(8)); +} + +static void *js_trace_calloc(void *opaque, size_t count, size_t size) +{ + void *ptr; + ptr = calloc(count, size); + js_trace_malloc_printf(opaque, "C %zd %zd -> %p\n", count, size, ptr); + return ptr; +} + +static void *js_trace_malloc(void *opaque, size_t size) +{ + void *ptr; + ptr = malloc(size); + js_trace_malloc_printf(opaque, "A %zd -> %p\n", size, ptr); + return ptr; +} + +static void js_trace_free(void *opaque, void *ptr) +{ + if (!ptr) + return; + js_trace_malloc_printf(opaque, "F %p\n", ptr); + free(ptr); +} + +static void *js_trace_realloc(void *opaque, void *ptr, size_t size) +{ + js_trace_malloc_printf(opaque, "R %zd %p", size, ptr); + ptr = realloc(ptr, size); + js_trace_malloc_printf(opaque, " -> %p\n", ptr); + return ptr; +} + +static const JSMallocFunctions trace_mf = { + js_trace_calloc, + js_trace_malloc, + js_trace_free, + js_trace_realloc, + js__malloc_usable_size +}; + +#ifdef QJS_USE_MIMALLOC +static void *js_mi_calloc(void *opaque, size_t count, size_t size) +{ + return mi_calloc(count, size); +} + +static void *js_mi_malloc(void *opaque, size_t size) +{ + return mi_malloc(size); +} + +static void js_mi_free(void *opaque, void *ptr) +{ + if (!ptr) + return; + mi_free(ptr); +} + +static void *js_mi_realloc(void *opaque, void *ptr, size_t size) +{ + return mi_realloc(ptr, size); +} + +static const JSMallocFunctions mi_mf = { + js_mi_calloc, + js_mi_malloc, + js_mi_free, + js_mi_realloc, + mi_malloc_usable_size +}; +#endif + +#define PROG_NAME "qjs" + +void help(void) +{ + printf("QuickJS-ng version %s\n" + "usage: " PROG_NAME " [options] [file [args]]\n" + "-h --help list options\n" + "-e --eval EXPR evaluate EXPR\n" + "-i --interactive go to interactive mode\n" + "-C --script load as JS classic script (default=autodetect)\n" + "-m --module load as ES module (default=autodetect)\n" + "-I --include file include an additional file\n" + " --std make 'std', 'os' and 'bjson' available to script\n" + "-T --trace trace memory allocation\n" + "-d --dump dump the memory usage stats\n" + "-D --dump-flags flags for dumping debug data (see DUMP_* defines)\n" + "-c --compile FILE compile the given JS file as a standalone executable\n" + "-o --out FILE output file for standalone executables\n" + " --exe select the executable to use as the base, defaults to the current one\n" + " --memory-limit n limit the memory usage to 'n' Kbytes\n" + " --stack-size n limit the stack size to 'n' Kbytes\n" + "-q --quit just instantiate the interpreter and quit\n", JS_GetVersion()); + exit(1); +} + +int main(int argc, char **argv) +{ + JSRuntime *rt; + JSContext *ctx; + JSValue ret = JS_UNDEFINED; + struct trace_malloc_data trace_data = { NULL }; + int r = 0; + int optind = 1; + char exebuf[JS__PATH_MAX]; + size_t exebuf_size = sizeof(exebuf); + char *compile_file = NULL; + char *exe = NULL; + char *expr = NULL; + char *dump_flags_str = NULL; + char *out = NULL; + int standalone = 0; + int interactive = 0; + int dump_memory = 0; + int dump_flags = 0; + int trace_memory = 0; + int empty_run = 0; + int module = -1; + int load_std = 0; + char *include_list[32]; + int i, include_count = 0; + int64_t memory_limit = -1; + int64_t stack_size = -1; + + /* save for later */ + qjs__argc = argc; + qjs__argv = argv; + + /* check if this is a standalone executable */ + + if (!js_exepath(exebuf, &exebuf_size) && is_standalone(exebuf)) { + standalone = 1; + goto start; + } + + dump_flags_str = getenv("QJS_DUMP_FLAGS"); + dump_flags = dump_flags_str ? strtol(dump_flags_str, NULL, 16) : 0; + + /* cannot use getopt because we want to pass the command line to + the script */ + while (optind < argc && *argv[optind] == '-') { + char *arg = argv[optind] + 1; + const char *longopt = ""; + char *optarg = NULL; + /* a single - is not an option, it also stops argument scanning */ + if (!*arg) + break; + optind++; + if (*arg == '-') { + longopt = arg + 1; + optarg = strchr(longopt, '='); + if (optarg) + *optarg++ = '\0'; + arg += strlen(arg); + /* -- stops argument scanning */ + if (!*longopt) + break; + } + for (; *arg || *longopt; longopt = "") { + char opt = *arg; + if (opt) { + arg++; + if (!optarg && *arg) + optarg = arg; + } + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { + help(); + continue; + } + if (opt == 'e' || !strcmp(longopt, "eval")) { + if (!optarg) { + if (optind >= argc) { + fprintf(stderr, "qjs: missing expression for -e\n"); + exit(1); + } + optarg = argv[optind++]; + } + expr = optarg; + break; + } + if (opt == 'I' || !strcmp(longopt, "include")) { + if (optind >= argc) { + fprintf(stderr, "expecting filename"); + exit(1); + } + if (include_count >= countof(include_list)) { + fprintf(stderr, "too many included files"); + exit(1); + } + include_list[include_count++] = argv[optind++]; + continue; + } + if (opt == 'i' || !strcmp(longopt, "interactive")) { + interactive++; + continue; + } + if (opt == 'm' || !strcmp(longopt, "module")) { + module = 1; + continue; + } + if (opt == 'C' || !strcmp(longopt, "script")) { + module = 0; + continue; + } + if (opt == 'd' || !strcmp(longopt, "dump")) { + dump_memory++; + continue; + } + if (opt == 'D' || !strcmp(longopt, "dump-flags")) { + dump_flags = optarg ? strtol(optarg, NULL, 16) : 0; + break; + } + if (opt == 'T' || !strcmp(longopt, "trace")) { + trace_memory++; + continue; + } + if (!strcmp(longopt, "std")) { + load_std = 1; + continue; + } + if (opt == 'q' || !strcmp(longopt, "quit")) { + empty_run++; + continue; + } + if (!strcmp(longopt, "memory-limit")) { + if (!optarg) { + if (optind >= argc) { + fprintf(stderr, "expecting memory limit"); + exit(1); + } + optarg = argv[optind++]; + } + memory_limit = parse_limit(optarg); + break; + } + if (!strcmp(longopt, "stack-size")) { + if (!optarg) { + if (optind >= argc) { + fprintf(stderr, "expecting stack size"); + exit(1); + } + optarg = argv[optind++]; + } + stack_size = parse_limit(optarg); + break; + } + if (opt == 'c' || !strcmp(longopt, "compile")) { + if (!optarg) { + if (optind >= argc) { + fprintf(stderr, "qjs: missing file for -c\n"); + exit(1); + } + optarg = argv[optind++]; + } + compile_file = optarg; + break; + } + if (opt == 'o' || !strcmp(longopt, "out")) { + if (!optarg) { + if (optind >= argc) { + fprintf(stderr, "qjs: missing file for -o\n"); + exit(1); + } + optarg = argv[optind++]; + } + out = optarg; + break; + } + if (!strcmp(longopt, "exe")) { + if (!optarg) { + if (optind >= argc) { + fprintf(stderr, "qjs: missing file for --exe\n"); + exit(1); + } + optarg = argv[optind++]; + } + exe = optarg; + break; + } + if (opt) { + fprintf(stderr, "qjs: unknown option '-%c'\n", opt); + } else { + fprintf(stderr, "qjs: unknown option '--%s'\n", longopt); + } + help(); + } + } + + if (compile_file && !out) + help(); + +start: + + if (trace_memory) { + js_trace_malloc_init(&trace_data); + rt = JS_NewRuntime2(&trace_mf, &trace_data); + } else { +#ifdef QJS_USE_MIMALLOC + rt = JS_NewRuntime2(&mi_mf, NULL); +#else + rt = JS_NewRuntime(); +#endif + } + if (!rt) { + fprintf(stderr, "qjs: cannot allocate JS runtime\n"); + exit(2); + } + if (memory_limit >= 0) + JS_SetMemoryLimit(rt, (size_t)memory_limit); + if (stack_size >= 0) + JS_SetMaxStackSize(rt, (size_t)stack_size); + if (dump_flags != 0) + JS_SetDumpFlags(rt, dump_flags); + js_std_set_worker_new_context_func(JS_NewCustomContext); + js_std_init_handlers(rt); + ctx = JS_NewCustomContext(rt); + if (!ctx) { + fprintf(stderr, "qjs: cannot allocate JS context\n"); + exit(2); + } + + /* loader for ES6 modules */ + JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); + + /* exit on unhandled promise rejections */ + JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker, NULL); + + if (!empty_run) { + js_std_add_helpers(ctx, argc - optind, argv + optind); + + /* make 'std' and 'os' visible to non module code */ + if (load_std) { + const char *str = + "import * as bjson from 'qjs:bjson';\n" + "import * as std from 'qjs:std';\n" + "import * as os from 'qjs:os';\n" + "globalThis.bjson = bjson;\n" + "globalThis.std = std;\n" + "globalThis.os = os;\n"; + eval_buf(ctx, str, strlen(str), "", JS_EVAL_TYPE_MODULE); + } + + for(i = 0; i < include_count; i++) { + if (eval_file(ctx, include_list[i], 0)) + goto fail; + } + + if (standalone) { + JSValue ns = load_standalone_module(ctx); + if (JS_IsException(ns)) + goto fail; + JSValue func = JS_GetPropertyStr(ctx, ns, "runStandalone"); + JS_FreeValue(ctx, ns); + if (JS_IsException(func)) + goto fail; + ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL); + JS_FreeValue(ctx, func); + } else if (compile_file) { + JSValue ns = load_standalone_module(ctx); + if (JS_IsException(ns)) + goto fail; + JSValue func = JS_GetPropertyStr(ctx, ns, "compileStandalone"); + JS_FreeValue(ctx, ns); + if (JS_IsException(func)) + goto fail; + JSValue args[3]; + args[0] = JS_NewString(ctx, compile_file); + args[1] = JS_NewString(ctx, out); + args[2] = exe != NULL ? JS_NewString(ctx, exe) : JS_UNDEFINED; + ret = JS_Call(ctx, func, JS_UNDEFINED, 3, (JSValueConst *)args); + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, args[0]); + JS_FreeValue(ctx, args[1]); + JS_FreeValue(ctx, args[2]); + } else if (expr) { + int flags = module ? JS_EVAL_TYPE_MODULE : 0; + if (eval_buf(ctx, expr, strlen(expr), "", flags)) + goto fail; + } else if (optind >= argc) { + /* interactive mode */ + interactive = 1; + } else { + const char *filename; + filename = argv[optind]; + if (eval_file(ctx, filename, module)) + goto fail; + } + if (interactive) { + JS_SetHostPromiseRejectionTracker(rt, NULL, NULL); + js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0); + } + if (standalone || compile_file) { + if (JS_IsException(ret)) { + r = 1; + } else { + JS_FreeValue(ctx, ret); + r = js_std_loop(ctx); + } + } else { + r = js_std_loop(ctx); + } + if (r) { + js_std_dump_error(ctx); + goto fail; + } + } + + if (dump_memory) { + JSMemoryUsage stats; + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(stdout, &stats, rt); + } + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + if (empty_run && dump_memory) { + clock_t t[5]; + double best[5] = {0}; + int i, j; + for (i = 0; i < 100; i++) { + t[0] = clock(); + rt = JS_NewRuntime(); + t[1] = clock(); + ctx = JS_NewContext(rt); + t[2] = clock(); + JS_FreeContext(ctx); + t[3] = clock(); + JS_FreeRuntime(rt); + t[4] = clock(); + for (j = 4; j > 0; j--) { + double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC; + if (i == 0 || best[j] > ms) + best[j] = ms; + } + } + printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n", + best[1] + best[2] + best[3] + best[4], + best[1], best[2], best[3], best[4]); + } + return 0; + fail: + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 1; +} diff --git a/NativeScript/napi/android/quickjs/source_ng/qjsc.c b/NativeScript/napi/android/quickjs/source_ng/qjsc.c new file mode 100755 index 000000000..395bcb34d --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/qjsc.c @@ -0,0 +1,673 @@ +/* + * QuickJS command line compiler + * + * Copyright (c) 2018-2024 Fabrice Bellard + * Copyright (c) 2023-2025 Ben Noordhuis + * Copyright (c) 2023-2025 Saúl Ibarra Corretgé + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "quickjs-libc.h" + +typedef enum { + OUTPUT_C, + OUTPUT_C_MAIN, + OUTPUT_RAW, +} OutputTypeEnum; + +typedef struct { + char *name; + char *short_name; + int flags; +} namelist_entry_t; + +typedef struct namelist_t { + namelist_entry_t *array; + int count; + int size; +} namelist_t; + +static namelist_t cname_list; +static namelist_t cmodule_list; +static namelist_t init_module_list; +static OutputTypeEnum output_type; +static FILE *outfile; +static const char *c_ident_prefix = "qjsc_"; +static int strip; + +void namelist_add(namelist_t *lp, const char *name, const char *short_name, + int flags) +{ + namelist_entry_t *e; + if (lp->count == lp->size) { + size_t newsize = lp->size + (lp->size >> 1) + 4; + namelist_entry_t *a = + realloc(lp->array, sizeof(lp->array[0]) * newsize); + /* XXX: check for realloc failure */ + lp->array = a; + lp->size = newsize; + } + e = &lp->array[lp->count++]; + e->name = strdup(name); + if (short_name) + e->short_name = strdup(short_name); + else + e->short_name = NULL; + e->flags = flags; +} + +void namelist_free(namelist_t *lp) +{ + while (lp->count > 0) { + namelist_entry_t *e = &lp->array[--lp->count]; + free(e->name); + free(e->short_name); + } + free(lp->array); + lp->array = NULL; + lp->size = 0; +} + +namelist_entry_t *namelist_find(namelist_t *lp, const char *name) +{ + int i; + for(i = 0; i < lp->count; i++) { + namelist_entry_t *e = &lp->array[i]; + if (!strcmp(e->name, name)) + return e; + } + return NULL; +} + +static void get_c_name(char *buf, size_t buf_size, const char *file) +{ + const char *p, *r; + size_t len, i; + int c; + char *q; + + p = strrchr(file, '/'); + if (!p) + p = file; + else + p++; + r = strrchr(p, '.'); + if (!r) + len = strlen(p); + else + len = r - p; + js__pstrcpy(buf, buf_size, c_ident_prefix); + q = buf + strlen(buf); + for(i = 0; i < len; i++) { + c = p[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'))) { + c = '_'; + } + if ((q - buf) < buf_size - 1) + *q++ = c; + } + *q = '\0'; +} + +static void dump_hex(FILE *f, const uint8_t *buf, size_t len) +{ + size_t i, col; + col = 0; + for(i = 0; i < len; i++) { + fprintf(f, " 0x%02x,", buf[i]); + if (++col == 8) { + fprintf(f, "\n"); + col = 0; + } + } + if (col != 0) + fprintf(f, "\n"); +} + +static void output_object_code(JSContext *ctx, + FILE *fo, JSValue obj, const char *c_name, + bool load_only) +{ + uint8_t *out_buf; + size_t out_buf_len; + int flags = JS_WRITE_OBJ_BYTECODE; + + if (strip) { + flags |= JS_WRITE_OBJ_STRIP_SOURCE; + if (strip > 1) + flags |= JS_WRITE_OBJ_STRIP_DEBUG; + } + + out_buf = JS_WriteObject(ctx, &out_buf_len, obj, flags); + if (!out_buf) { + js_std_dump_error(ctx); + exit(1); + } + + namelist_add(&cname_list, c_name, NULL, load_only); + + if (output_type == OUTPUT_RAW) { + fwrite(out_buf, 1, out_buf_len, fo); + } else { + fprintf(fo, "const uint32_t %s_size = %u;\n\n", + c_name, (unsigned int)out_buf_len); + fprintf(fo, "const uint8_t %s[%u] = {\n", + c_name, (unsigned int)out_buf_len); + dump_hex(fo, out_buf, out_buf_len); + fprintf(fo, "};\n\n"); + } + + js_free(ctx, out_buf); +} + +static int js_module_dummy_init(JSContext *ctx, JSModuleDef *m) +{ + /* should never be called when compiling JS code */ + abort(); + return -1; // pacify compiler +} + +static void find_unique_cname(char *cname, size_t cname_size) +{ + char cname1[1024]; + int suffix_num; + size_t len, max_len; + assert(cname_size >= 32); + /* find a C name not matching an existing module C name by + adding a numeric suffix */ + len = strlen(cname); + max_len = cname_size - 16; + if (len > max_len) + cname[max_len] = '\0'; + suffix_num = 1; + for(;;) { + snprintf(cname1, sizeof(cname1), "%s_%d", cname, suffix_num); + if (!namelist_find(&cname_list, cname1)) + break; + suffix_num++; + } + js__pstrcpy(cname, cname_size, cname1); +} + +JSModuleDef *jsc_module_loader(JSContext *ctx, + const char *module_name, void *opaque) +{ + JSModuleDef *m; + namelist_entry_t *e; + + /* check if it is a declared C or system module */ + e = namelist_find(&cmodule_list, module_name); + if (e) { + /* add in the static init module list */ + namelist_add(&init_module_list, e->name, e->short_name, 0); + /* create a dummy module */ + m = JS_NewCModule(ctx, module_name, js_module_dummy_init); + } else if (js__has_suffix(module_name, ".so")) { + JS_ThrowReferenceError(ctx, "%s: dynamically linking to shared libraries not supported", + module_name); + return NULL; + } else { + size_t buf_len; + uint8_t *buf; + JSValue func_val; + char cname[1000]; + + buf = js_load_file(ctx, &buf_len, module_name); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s'", + module_name); + return NULL; + } + + /* compile the module */ + func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + js_free(ctx, buf); + if (JS_IsException(func_val)) + return NULL; + get_c_name(cname, sizeof(cname), module_name); + if (namelist_find(&cname_list, cname)) { + find_unique_cname(cname, sizeof(cname)); + } + output_object_code(ctx, outfile, func_val, cname, true); + + /* the module is already referenced, so we must free it */ + m = JS_VALUE_GET_PTR(func_val); + JS_FreeValue(ctx, func_val); + } + return m; +} + +static void compile_file(JSContext *ctx, FILE *fo, + const char *filename, + const char *script_name, + const char *c_name1, + int module) +{ + uint8_t *buf; + char c_name[1024]; + int eval_flags; + JSValue obj; + size_t buf_len; + + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + fprintf(stderr, "Could not load '%s'\n", filename); + exit(1); + } + eval_flags = JS_EVAL_FLAG_COMPILE_ONLY; + if (module < 0) { + module = (js__has_suffix(filename, ".mjs") || + JS_DetectModule((const char *)buf, buf_len)); + } + if (module) + eval_flags |= JS_EVAL_TYPE_MODULE; + else + eval_flags |= JS_EVAL_TYPE_GLOBAL; + obj = JS_Eval(ctx, (const char *)buf, buf_len, script_name ? script_name : filename, eval_flags); + if (JS_IsException(obj)) { + js_std_dump_error(ctx); + exit(1); + } + js_free(ctx, buf); + if (c_name1) { + js__pstrcpy(c_name, sizeof(c_name), c_name1); + } else { + get_c_name(c_name, sizeof(c_name), filename); + } + output_object_code(ctx, fo, obj, c_name, false); + JS_FreeValue(ctx, obj); +} + +static const char main_c_template1[] = + "int main(int argc, char **argv)\n" + "{\n" + " int r;\n" + " JSRuntime *rt;\n" + " JSContext *ctx;\n" + " r = 0;\n" + " rt = JS_NewRuntime();\n" + " js_std_set_worker_new_context_func(JS_NewCustomContext);\n" + " js_std_init_handlers(rt);\n" + ; + +static const char main_c_template2[] = + " r = js_std_loop(ctx);\n" + " if (r) {\n" + " js_std_dump_error(ctx);\n" + " }\n" + " js_std_free_handlers(rt);\n" + " JS_FreeContext(ctx);\n" + " JS_FreeRuntime(rt);\n" + " return r;\n" + "}\n"; + +#define PROG_NAME "qjsc" + +void help(void) +{ + printf("QuickJS-ng Compiler version %s\n" + "usage: " PROG_NAME " [options] [files]\n" + "\n" + "options are:\n" + "-b output raw bytecode instead of C code\n" + "-e output main() and bytecode in a C file\n" + "-o output set the output filename\n" + "-n script_name set the script name (as used in stack traces)\n" + "-N cname set the C name of the generated data\n" + "-C compile as JS classic script (default=autodetect)\n" + "-m compile as ES module (default=autodetect)\n" + "-D module_name compile a dynamically loaded module or worker\n" + "-M module_name[,cname] add initialization code for an external C module\n" + "-p prefix set the prefix of the generated C names\n" + "-P do not add default system modules\n" + "-s strip the source code, specify twice to also strip debug info\n" + "-S n set the maximum stack size to 'n' bytes (default=%d)\n", + JS_GetVersion(), + JS_DEFAULT_STACK_SIZE); + exit(1); +} + +// TODO(bnoordhuis) share with qjs.c maybe +static int64_t parse_limit(const char *arg) { + char *p; + unsigned long unit = 1; // bytes for backcompat; qjs defaults to kilobytes + double d = strtod(arg, &p); + + if (p == arg) { + fprintf(stderr, "qjsc: invalid limit: %s\n", arg); + return -1; + } + + if (*p) { + switch (*p++) { + case 'b': case 'B': unit = 1UL << 0; break; + case 'k': case 'K': unit = 1UL << 10; break; /* IEC kibibytes */ + case 'm': case 'M': unit = 1UL << 20; break; /* IEC mebibytes */ + case 'g': case 'G': unit = 1UL << 30; break; /* IEC gigibytes */ + default: + fprintf(stderr, "qjsc: invalid limit: %s, unrecognized suffix, only k,m,g are allowed\n", arg); + return -1; + } + if (*p) { + fprintf(stderr, "qjsc: invalid limit: %s, only one suffix allowed\n", arg); + return -1; + } + } + + return (int64_t)(d * unit); +} + +static void check_hasarg(int optind, int argc, int opt) +{ + if (optind >= argc) { + fprintf(stderr, "qjsc: missing file for -%c\n", opt); + exit(1); + } +} + +int main(int argc, char **argv) +{ + int optind = 1; + int i; + const char *out_filename, *cname, *script_name; + char cfilename[1024]; + FILE *fo; + JSRuntime *rt; + JSContext *ctx; + int module; + size_t stack_size; + namelist_t dynamic_module_list; + bool load_system_modules = true; + + out_filename = NULL; + script_name = NULL; + output_type = OUTPUT_C; + cname = NULL; + module = -1; + strip = 0; + stack_size = 0; + memset(&dynamic_module_list, 0, sizeof(dynamic_module_list)); + + + while (optind < argc && *argv[optind] == '-') { + char *arg = argv[optind] + 1; + const char *longopt = ""; + char *optarg = NULL; + /* a single - is not an option, it also stops argument scanning */ + if (!*arg) + break; + optind++; + if (*arg == '-') { + longopt = arg + 1; + optarg = strchr(longopt, '='); + if (optarg) + *optarg++ = '\0'; + arg += strlen(arg); + /* -- stops argument scanning */ + if (!*longopt) + break; + } + for (; *arg || *longopt; longopt = "") { + char opt = *arg; + if (opt) { + arg++; + if (!optarg && *arg) + optarg = arg; + } + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { + help(); + continue; + } + if (opt == 'b') { + output_type = OUTPUT_RAW; + continue; + } + if (opt == 'o') { + if (!optarg) { + check_hasarg(optind, argc, opt); + optarg = argv[optind++]; + } + out_filename = optarg; + continue; + } + if (opt == 'e') { + output_type = OUTPUT_C_MAIN; + continue; + } + if (opt == 'n') { + if (!optarg) { + check_hasarg(optind, argc, opt); + optarg = argv[optind++]; + } + script_name = optarg; + continue; + } + if (opt == 'N') { + if (!optarg) { + check_hasarg(optind, argc, opt); + optarg = argv[optind++]; + } + cname = optarg; + continue; + } + if (opt == 'C') { + module = 0; + continue; + } + if (opt == 'm') { + module = 1; + continue; + } + if (opt == 'M') { + char *p; + char path[1024]; + char cname[1024]; + if (!optarg) { + check_hasarg(optind, argc, opt); + optarg = argv[optind++]; + } + js__pstrcpy(path, sizeof(path), optarg); + p = strchr(path, ','); + if (p) { + *p = '\0'; + js__pstrcpy(cname, sizeof(cname), p + 1); + } else { + get_c_name(cname, sizeof(cname), path); + } + namelist_add(&cmodule_list, path, cname, 0); + continue; + } + if (opt == 'D') { + if (!optarg) { + check_hasarg(optind, argc, opt); + optarg = argv[optind++]; + } + namelist_add(&dynamic_module_list, optarg, NULL, 0); + continue; + } + if (opt == 'P') { + load_system_modules = false; + continue; + } + if (opt == 's') { + strip++; + continue; + } + if (opt == 'p') { + if (!optarg) { + check_hasarg(optind, argc, opt); + optarg = argv[optind++]; + } + c_ident_prefix = optarg; + continue; + } + if (opt == 'S') { + if (!optarg) { + check_hasarg(optind, argc, opt); + optarg = argv[optind++]; + } + stack_size = parse_limit(optarg); + continue; + } + help(); + } + } + + if (load_system_modules) { + /* add system modules */ + namelist_add(&cmodule_list, "qjs:std", "std", 0); + namelist_add(&cmodule_list, "qjs:os", "os", 0); + namelist_add(&cmodule_list, "qjs:bjson", "bjson", 0); + namelist_add(&cmodule_list, "std", "std", 0); + namelist_add(&cmodule_list, "os", "os", 0); + namelist_add(&cmodule_list, "bjson", "bjson", 0); + } + + if (optind >= argc) + help(); + + if (!out_filename) + out_filename = "out.c"; + + js__pstrcpy(cfilename, sizeof(cfilename), out_filename); + + if (output_type == OUTPUT_RAW) + fo = fopen(cfilename, "wb"); + else + fo = fopen(cfilename, "w"); + + if (!fo) { + perror(cfilename); + exit(1); + } + outfile = fo; + + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); + + /* loader for ES6 modules */ + JS_SetModuleLoaderFunc(rt, NULL, jsc_module_loader, NULL); + + if (output_type != OUTPUT_RAW) { + fprintf(fo, "/* File generated automatically by the QuickJS-ng compiler. */\n" + "\n" + ); + } + + if (output_type == OUTPUT_C_MAIN) { + fprintf(fo, "#include \"quickjs-libc.h\"\n" + "\n" + ); + } else if (output_type == OUTPUT_C) { + fprintf(fo, "#include \n" + "\n" + ); + } + + for(i = optind; i < argc; i++) { + const char *filename = argv[i]; + compile_file(ctx, fo, filename, script_name, cname, module); + cname = NULL; + } + + for(i = 0; i < dynamic_module_list.count; i++) { + if (!jsc_module_loader(ctx, dynamic_module_list.array[i].name, NULL)) { + fprintf(stderr, "Could not load dynamic module '%s'\n", + dynamic_module_list.array[i].name); + exit(1); + } + } + + if (output_type == OUTPUT_C_MAIN) { + fprintf(fo, + "static JSContext *JS_NewCustomContext(JSRuntime *rt)\n" + "{\n" + " JSContext *ctx = JS_NewContext(rt);\n" + " if (!ctx)\n" + " return NULL;\n"); + /* add the precompiled modules (XXX: could modify the module + loader instead) */ + for(i = 0; i < init_module_list.count; i++) { + namelist_entry_t *e = &init_module_list.array[i]; + /* initialize the static C modules */ + + fprintf(fo, + " {\n" + " extern JSModuleDef *js_init_module_%s(JSContext *ctx, const char *name);\n" + " js_init_module_%s(ctx, \"%s\");\n" + " }\n", + e->short_name, e->short_name, e->name); + } + for(i = 0; i < cname_list.count; i++) { + namelist_entry_t *e = &cname_list.array[i]; + if (e->flags) { + fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 1);\n", + e->name, e->name); + } + } + fprintf(fo, + " return ctx;\n" + "}\n\n"); + + fputs(main_c_template1, fo); + + if (stack_size != 0) { + fprintf(fo, " JS_SetMaxStackSize(rt, %u);\n", + (unsigned int)stack_size); + } + + /* add the module loader */ + fprintf(fo, " JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);\n"); + + fprintf(fo, + " ctx = JS_NewCustomContext(rt);\n" + " js_std_add_helpers(ctx, argc, argv);\n"); + + for(i = 0; i < cname_list.count; i++) { + namelist_entry_t *e = &cname_list.array[i]; + if (!e->flags) { + fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 0);\n", + e->name, e->name); + } + } + fputs(main_c_template2, fo); + } + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + fclose(fo); + + namelist_free(&cname_list); + namelist_free(&cmodule_list); + namelist_free(&init_module_list); + return 0; +} diff --git a/NativeScript/napi/android/quickjs/source_ng/quickjs-atom.h b/NativeScript/napi/android/quickjs/source_ng/quickjs-atom.h new file mode 100755 index 000000000..2a1232843 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/quickjs-atom.h @@ -0,0 +1,265 @@ +/* + * QuickJS atom definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef DEF + +/* Note: first atoms are considered as keywords in the parser */ +DEF(null, "null") /* must be first */ +DEF(false, "false") +DEF(true, "true") +DEF(if, "if") +DEF(else, "else") +DEF(return, "return") +DEF(var, "var") +DEF(this, "this") +DEF(delete, "delete") +DEF(void, "void") +DEF(typeof, "typeof") +DEF(new, "new") +DEF(in, "in") +DEF(instanceof, "instanceof") +DEF(do, "do") +DEF(while, "while") +DEF(for, "for") +DEF(break, "break") +DEF(continue, "continue") +DEF(switch, "switch") +DEF(case, "case") +DEF(default, "default") +DEF(throw, "throw") +DEF(try, "try") +DEF(catch, "catch") +DEF(finally, "finally") +DEF(function, "function") +DEF(debugger, "debugger") +DEF(with, "with") +/* FutureReservedWord */ +DEF(class, "class") +DEF(const, "const") +DEF(enum, "enum") +DEF(export, "export") +DEF(extends, "extends") +DEF(import, "import") +DEF(super, "super") +/* FutureReservedWords when parsing strict mode code */ +DEF(implements, "implements") +DEF(interface, "interface") +DEF(let, "let") +DEF(package, "package") +DEF(private, "private") +DEF(protected, "protected") +DEF(public, "public") +DEF(static, "static") +DEF(yield, "yield") +DEF(await, "await") + +/* empty string */ +DEF(empty_string, "") +/* identifiers */ +DEF(keys, "keys") +DEF(size, "size") +DEF(length, "length") +DEF(message, "message") +DEF(cause, "cause") +DEF(errors, "errors") +DEF(stack, "stack") +DEF(name, "name") +DEF(toString, "toString") +DEF(toLocaleString, "toLocaleString") +DEF(valueOf, "valueOf") +DEF(eval, "eval") +DEF(prototype, "prototype") +DEF(constructor, "constructor") +DEF(configurable, "configurable") +DEF(writable, "writable") +DEF(enumerable, "enumerable") +DEF(value, "value") +DEF(get, "get") +DEF(set, "set") +DEF(of, "of") +DEF(__proto__, "__proto__") +DEF(undefined, "undefined") +DEF(number, "number") +DEF(boolean, "boolean") +DEF(string, "string") +DEF(object, "object") +DEF(symbol, "symbol") +DEF(integer, "integer") +DEF(unknown, "unknown") +DEF(arguments, "arguments") +DEF(callee, "callee") +DEF(caller, "caller") +DEF(_eval_, "") +DEF(_ret_, "") +DEF(_var_, "") +DEF(_arg_var_, "") +DEF(_with_, "") +DEF(lastIndex, "lastIndex") +DEF(target, "target") +DEF(index, "index") +DEF(input, "input") +DEF(defineProperties, "defineProperties") +DEF(apply, "apply") +DEF(join, "join") +DEF(concat, "concat") +DEF(split, "split") +DEF(construct, "construct") +DEF(getPrototypeOf, "getPrototypeOf") +DEF(setPrototypeOf, "setPrototypeOf") +DEF(isExtensible, "isExtensible") +DEF(preventExtensions, "preventExtensions") +DEF(has, "has") +DEF(deleteProperty, "deleteProperty") +DEF(defineProperty, "defineProperty") +DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") +DEF(ownKeys, "ownKeys") +DEF(add, "add") +DEF(done, "done") +DEF(next, "next") +DEF(values, "values") +DEF(source, "source") +DEF(flags, "flags") +DEF(global, "global") +DEF(unicode, "unicode") +DEF(raw, "raw") +DEF(new_target, "new.target") +DEF(this_active_func, "this.active_func") +DEF(home_object, "") +DEF(computed_field, "") +DEF(static_computed_field, "") /* must come after computed_fields */ +DEF(class_fields_init, "") +DEF(brand, "") +DEF(hash_constructor, "#constructor") +DEF(as, "as") +DEF(from, "from") +DEF(fromAsync, "fromAsync") +DEF(meta, "meta") +DEF(_default_, "*default*") +DEF(_star_, "*") +DEF(Module, "Module") +DEF(then, "then") +DEF(resolve, "resolve") +DEF(reject, "reject") +DEF(promise, "promise") +DEF(proxy, "proxy") +DEF(revoke, "revoke") +DEF(async, "async") +DEF(exec, "exec") +DEF(groups, "groups") +DEF(indices, "indices") +DEF(status, "status") +DEF(reason, "reason") +DEF(globalThis, "globalThis") +DEF(bigint, "bigint") +DEF(not_equal, "not-equal") +DEF(timed_out, "timed-out") +DEF(ok, "ok") +DEF(toJSON, "toJSON") +DEF(maxByteLength, "maxByteLength") +/* class names */ +DEF(Object, "Object") +DEF(Array, "Array") +DEF(Error, "Error") +DEF(Number, "Number") +DEF(String, "String") +DEF(Boolean, "Boolean") +DEF(Symbol, "Symbol") +DEF(Arguments, "Arguments") +DEF(Math, "Math") +DEF(JSON, "JSON") +DEF(Date, "Date") +DEF(Function, "Function") +DEF(GeneratorFunction, "GeneratorFunction") +DEF(ForInIterator, "ForInIterator") +DEF(RegExp, "RegExp") +DEF(ArrayBuffer, "ArrayBuffer") +DEF(SharedArrayBuffer, "SharedArrayBuffer") +/* must keep same order as class IDs for typed arrays */ +DEF(Uint8ClampedArray, "Uint8ClampedArray") +DEF(Int8Array, "Int8Array") +DEF(Uint8Array, "Uint8Array") +DEF(Int16Array, "Int16Array") +DEF(Uint16Array, "Uint16Array") +DEF(Int32Array, "Int32Array") +DEF(Uint32Array, "Uint32Array") +DEF(BigInt64Array, "BigInt64Array") +DEF(BigUint64Array, "BigUint64Array") +DEF(Float16Array, "Float16Array") +DEF(Float32Array, "Float32Array") +DEF(Float64Array, "Float64Array") +DEF(DataView, "DataView") +DEF(BigInt, "BigInt") +DEF(WeakRef, "WeakRef") +DEF(FinalizationRegistry, "FinalizationRegistry") +DEF(Map, "Map") +DEF(Set, "Set") /* Map + 1 */ +DEF(WeakMap, "WeakMap") /* Map + 2 */ +DEF(WeakSet, "WeakSet") /* Map + 3 */ +DEF(Iterator, "Iterator") +DEF(IteratorConcat, "Iterator Concat") +DEF(IteratorHelper, "Iterator Helper") +DEF(IteratorWrap, "Iterator Wrap") +DEF(Map_Iterator, "Map Iterator") +DEF(Set_Iterator, "Set Iterator") +DEF(Array_Iterator, "Array Iterator") +DEF(String_Iterator, "String Iterator") +DEF(RegExp_String_Iterator, "RegExp String Iterator") +DEF(Generator, "Generator") +DEF(Proxy, "Proxy") +DEF(Promise, "Promise") +DEF(PromiseResolveFunction, "PromiseResolveFunction") +DEF(PromiseRejectFunction, "PromiseRejectFunction") +DEF(AsyncFunction, "AsyncFunction") +DEF(AsyncFunctionResolve, "AsyncFunctionResolve") +DEF(AsyncFunctionReject, "AsyncFunctionReject") +DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") +DEF(AsyncGenerator, "AsyncGenerator") +DEF(EvalError, "EvalError") +DEF(RangeError, "RangeError") +DEF(ReferenceError, "ReferenceError") +DEF(SyntaxError, "SyntaxError") +DEF(TypeError, "TypeError") +DEF(URIError, "URIError") +DEF(InternalError, "InternalError") +DEF(DOMException, "DOMException") +DEF(CallSite, "CallSite") +/* private symbols */ +DEF(Private_brand, "") +/* symbols */ +DEF(Symbol_toPrimitive, "Symbol.toPrimitive") +DEF(Symbol_iterator, "Symbol.iterator") +DEF(Symbol_match, "Symbol.match") +DEF(Symbol_matchAll, "Symbol.matchAll") +DEF(Symbol_replace, "Symbol.replace") +DEF(Symbol_search, "Symbol.search") +DEF(Symbol_split, "Symbol.split") +DEF(Symbol_toStringTag, "Symbol.toStringTag") +DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") +DEF(Symbol_hasInstance, "Symbol.hasInstance") +DEF(Symbol_species, "Symbol.species") +DEF(Symbol_unscopables, "Symbol.unscopables") +DEF(Symbol_asyncIterator, "Symbol.asyncIterator") + +#endif /* DEF */ diff --git a/NativeScript/napi/android/quickjs/source_ng/quickjs-c-atomics.h b/NativeScript/napi/android/quickjs/source_ng/quickjs-c-atomics.h new file mode 100755 index 000000000..8fc6b7203 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/quickjs-c-atomics.h @@ -0,0 +1,54 @@ +/* + * QuickJS C atomics definitions + * + * Copyright (c) 2023 Marcin Kolny + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) + // Use GCC builtins for version < 4.9 +# if((__GNUC__ << 16) + __GNUC_MINOR__ < ((4) << 16) + 9) +# define GCC_BUILTIN_ATOMICS +# endif +#endif + +#ifdef GCC_BUILTIN_ATOMICS +#define atomic_fetch_add(obj, arg) \ + __atomic_fetch_add(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_compare_exchange_strong(obj, expected, desired) \ + __atomic_compare_exchange_n(obj, expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) +#define atomic_exchange(obj, desired) \ + __atomic_exchange_n (obj, desired, __ATOMIC_SEQ_CST) +#define atomic_load(obj) \ + __atomic_load_n(obj, __ATOMIC_SEQ_CST) +#define atomic_store(obj, desired) \ + __atomic_store_n(obj, desired, __ATOMIC_SEQ_CST) +#define atomic_fetch_or(obj, arg) \ + __atomic_fetch_or(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_fetch_xor(obj, arg) \ + __atomic_fetch_xor(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_fetch_and(obj, arg) \ + __atomic_fetch_and(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_fetch_sub(obj, arg) \ + __atomic_fetch_sub(obj, arg, __ATOMIC_SEQ_CST) +#define _Atomic +#else +#include +#endif diff --git a/NativeScript/napi/android/quickjs/source_ng/quickjs-libc.c b/NativeScript/napi/android/quickjs/source_ng/quickjs-libc.c new file mode 100755 index 000000000..2c7ac7704 --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/quickjs-libc.c @@ -0,0 +1,4685 @@ +/* + * QuickJS C library + * + * Copyright (c) 2017-2021 Fabrice Bellard + * Copyright (c) 2017-2021 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "quickjs.h" +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) +#include +#include +#endif +#include +#include +#include +#include +#if !defined(_MSC_VER) +#include +#endif +#if defined(_WIN32) +#include +#include +#include +#include +#include +#include +#include +#define popen _popen +#define pclose _pclose +#define rmdir _rmdir +#define getcwd _getcwd +#define chdir _chdir +#else +#include +#include +#if !defined(__wasi__) +#include +#include +#include +#include +#include +#endif + +#if defined(__APPLE__) +typedef sig_t sighandler_t; +#include +#include +#define environ (*_NSGetEnviron()) +#endif + +#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) +typedef sig_t sighandler_t; +extern char **environ; +#endif + +#endif /* _WIN32 */ + +#include "cutils.h" +#include "list.h" +#include "quickjs-libc.h" + +#if JS_HAVE_THREADS +#include "quickjs-c-atomics.h" +#define USE_WORKER // enable os.Worker +#endif + +#ifndef S_IFBLK +#define S_IFBLK 0 +#endif + +#ifndef S_IFIFO +#define S_IFIFO 0 +#endif + +#ifndef MAX_SAFE_INTEGER // already defined in amalgamation builds +#define MAX_SAFE_INTEGER (((int64_t) 1 << 53) - 1) +#endif + +#ifndef QJS_NATIVE_MODULE_SUFFIX +#ifdef _WIN32 +#define QJS_NATIVE_MODULE_SUFFIX ".dll" +#else +#define QJS_NATIVE_MODULE_SUFFIX ".so" +#endif +#endif + +/* TODO: + - add socket calls +*/ + +typedef struct { + struct list_head link; + int fd; + JSValue rw_func[2]; +} JSOSRWHandler; + +typedef struct { + struct list_head link; + int sig_num; + JSValue func; +} JSOSSignalHandler; + +typedef struct { + struct list_head link; + int64_t timer_id; + uint8_t repeats:1; + int64_t timeout; + int64_t delay; + JSValue func; +} JSOSTimer; + +typedef struct { + struct list_head link; + JSValue promise; + JSValue reason; +} JSRejectedPromiseEntry; + +#ifdef USE_WORKER + +typedef struct { + struct list_head link; + uint8_t *data; + size_t data_len; + /* list of SharedArrayBuffers, necessary to free the message */ + uint8_t **sab_tab; + size_t sab_tab_len; +} JSWorkerMessage; + +typedef struct JSWaker { +#ifdef _WIN32 + HANDLE handle; +#else + int read_fd; + int write_fd; +#endif +} JSWaker; + +typedef struct { + int ref_count; + js_mutex_t mutex; + struct list_head msg_queue; /* list of JSWorkerMessage.link */ + JSWaker waker; +} JSWorkerMessagePipe; + +typedef struct { + struct list_head link; + JSWorkerMessagePipe *recv_pipe; + JSValue on_message_func; +} JSWorkerMessageHandler; + +#endif // USE_WORKER + +typedef struct JSThreadState { + struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */ + struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */ + struct list_head os_timers; /* list of JSOSTimer.link */ + struct list_head port_list; /* list of JSWorkerMessageHandler.link */ + struct list_head rejected_promise_list; /* list of JSRejectedPromiseEntry.link */ + int eval_script_recurse; /* only used in the main thread */ + int64_t next_timer_id; /* for setTimeout / setInterval */ + bool can_js_os_poll; + /* not used in the main thread */ +#ifdef USE_WORKER + JSWorkerMessagePipe *recv_pipe, *send_pipe; +#else + void *recv_pipe; +#endif // USE_WORKER + JSClassID std_file_class_id; + JSClassID worker_class_id; +} JSThreadState; + +static uint64_t os_pending_signals; + +static void *js_std_dbuf_realloc(void *opaque, void *ptr, size_t size) +{ + JSRuntime *rt = opaque; + return js_realloc_rt(rt, ptr, size); +} + +static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, JS_GetRuntime(ctx), js_std_dbuf_realloc); +} + +static bool my_isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +static JSThreadState *js_get_thread_state(JSRuntime *rt) +{ + return (JSThreadState *)js_std_cmd(/*GetOpaque*/0, rt); +} + +static void js_set_thread_state(JSRuntime *rt, JSThreadState *ts) +{ + js_std_cmd(/*SetOpaque*/1, rt, ts); +} + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif // __GNUC__ +static JSValue js_printf_internal(JSContext *ctx, + int argc, JSValueConst *argv, FILE *fp) +{ + char fmtbuf[32]; + uint8_t cbuf[UTF8_CHAR_LEN_MAX+1]; + JSValue res; + DynBuf dbuf; + const char *fmt_str = NULL; + const uint8_t *fmt, *fmt_end; + const uint8_t *p; + char *q; + int i, c, len, mod; + size_t fmt_len; + int32_t int32_arg; + int64_t int64_arg; + double double_arg; + const char *string_arg; + + js_std_dbuf_init(ctx, &dbuf); + + if (argc > 0) { + fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]); + if (!fmt_str) + goto fail; + + i = 1; + fmt = (const uint8_t *)fmt_str; + fmt_end = fmt + fmt_len; + while (fmt < fmt_end) { + for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++) + continue; + dbuf_put(&dbuf, p, fmt - p); + if (fmt >= fmt_end) + break; + q = fmtbuf; + *q++ = *fmt++; /* copy '%' */ + + /* flags */ + for(;;) { + c = *fmt; + if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' || + c == '\'') { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = c; + fmt++; + } else { + break; + } + } + /* width */ + if (*fmt == '*') { + if (i >= argc) + goto missing; + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); + fmt++; + } else { + while (my_isdigit(*fmt)) { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + } + } + if (*fmt == '.') { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + if (*fmt == '*') { + if (i >= argc) + goto missing; + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); + fmt++; + } else { + while (my_isdigit(*fmt)) { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + } + } + } + + /* we only support the "l" modifier for 64 bit numbers */ + mod = ' '; + if (*fmt == 'l') { + mod = *fmt++; + } + + /* type */ + c = *fmt++; + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = c; + *q = '\0'; + + switch (c) { + case 'c': + if (i >= argc) + goto missing; + if (JS_IsString(argv[i])) { + // TODO(chqrlie) need an API to wrap charCodeAt and codePointAt */ + string_arg = JS_ToCString(ctx, argv[i++]); + if (!string_arg) + goto fail; + int32_arg = utf8_decode((const uint8_t *)string_arg, &p); + JS_FreeCString(ctx, string_arg); + } else { + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + } + // XXX: throw an exception? + if ((unsigned)int32_arg > 0x10FFFF) + int32_arg = 0xFFFD; + /* ignore conversion flags, width and precision */ + len = utf8_encode(cbuf, int32_arg); + dbuf_put(&dbuf, cbuf, len); + break; + + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + if (i >= argc) + goto missing; + if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) + goto fail; + if (mod == 'l') { + /* 64 bit number */ +#if defined(_WIN32) + if (q >= fmtbuf + sizeof(fmtbuf) - 3) + goto invalid; + q[2] = q[-1]; + q[-1] = 'I'; + q[0] = '6'; + q[1] = '4'; + q[3] = '\0'; + dbuf_printf(&dbuf, fmtbuf, (int64_t)int64_arg); +#else + if (q >= fmtbuf + sizeof(fmtbuf) - 2) + goto invalid; + q[1] = q[-1]; + q[-1] = q[0] = 'l'; + q[2] = '\0'; + dbuf_printf(&dbuf, fmtbuf, (long long)int64_arg); +#endif + } else { + dbuf_printf(&dbuf, fmtbuf, (int)int64_arg); + } + break; + + case 's': + if (i >= argc) + goto missing; + /* XXX: handle strings containing null characters */ + string_arg = JS_ToCString(ctx, argv[i++]); + if (!string_arg) + goto fail; + dbuf_printf(&dbuf, fmtbuf, string_arg); + JS_FreeCString(ctx, string_arg); + break; + + case 'e': + case 'f': + case 'g': + case 'a': + case 'E': + case 'F': + case 'G': + case 'A': + if (i >= argc) + goto missing; + if (JS_ToFloat64(ctx, &double_arg, argv[i++])) + goto fail; + dbuf_printf(&dbuf, fmtbuf, double_arg); + break; + + case '%': + dbuf_putc(&dbuf, '%'); + break; + + default: + /* XXX: should support an extension mechanism */ + invalid: + JS_ThrowTypeError(ctx, "invalid conversion specifier in format string"); + goto fail; + missing: + JS_ThrowReferenceError(ctx, "missing argument for conversion specifier"); + goto fail; + } + } + JS_FreeCString(ctx, fmt_str); + } + if (dbuf.error) { + res = JS_ThrowOutOfMemory(ctx); + } else { + if (fp) { + len = fwrite(dbuf.buf, 1, dbuf.size, fp); + res = JS_NewInt32(ctx, len); + } else { + res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); + } + } + dbuf_free(&dbuf); + return res; + +fail: + JS_FreeCString(ctx, fmt_str); + dbuf_free(&dbuf); + return JS_EXCEPTION; +} +#ifdef __GNUC__ +#pragma GCC diagnostic pop // ignored "-Wformat-nonliteral" +#endif // __GNUC__ + +uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename) +{ + FILE *f; + size_t n, len; + uint8_t *p, *buf, tmp[8192]; + + f = fopen(filename, "rb"); + if (!f) + return NULL; + buf = NULL; + len = 0; + do { + n = fread(tmp, 1, sizeof(tmp), f); + if (ctx) { + p = js_realloc(ctx, buf, len + n + 1); + } else { + p = realloc(buf, len + n + 1); + } + if (!p) { + if (ctx) { + js_free(ctx, buf); + } else { + free(buf); + } + fclose(f); + return NULL; + } + memcpy(&p[len], tmp, n); + buf = p; + len += n; + buf[len] = '\0'; + } while (n == sizeof(tmp)); + fclose(f); + *pbuf_len = len; + return buf; +} + +/* load and evaluate a file */ +static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *buf; + const char *filename; + JSValue ret; + size_t buf_len; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load '%s'", filename); + JS_FreeCString(ctx, filename); + return JS_EXCEPTION; + } + ret = JS_Eval(ctx, (char *)buf, buf_len, filename, + JS_EVAL_TYPE_GLOBAL); + js_free(ctx, buf); + JS_FreeCString(ctx, filename); + return ret; +} + +static int get_bool_option(JSContext *ctx, bool *pbool, + JSValueConst obj, + const char *option) +{ + JSValue val; + val = JS_GetPropertyStr(ctx, obj, option); + if (JS_IsException(val)) + return -1; + if (!JS_IsUndefined(val)) { + *pbool = JS_ToBool(ctx, val); + } + JS_FreeValue(ctx, val); + return 0; +} + +static void free_buf(JSRuntime *rt, void *opaque, void *ptr) { + js_free_rt(rt, ptr); +} + +/* load a file as a UTF-8 encoded string or Uint8Array */ +static JSValue js_std_loadFile(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *buf; + const char *filename; + JSValueConst options_obj; + JSValue ret; + size_t buf_len; + bool binary = false; + + if (argc >= 2) { + options_obj = argv[1]; + if (get_bool_option(ctx, &binary, options_obj, + "binary")) + return JS_EXCEPTION; + } + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + buf = js_load_file(ctx, &buf_len, filename); + JS_FreeCString(ctx, filename); + if (!buf) + return JS_NULL; + if (binary) { + ret = JS_NewUint8Array(ctx, buf, buf_len, free_buf, NULL, false); + } else { + ret = JS_NewStringLen(ctx, (char *)buf, buf_len); + js_free(ctx, buf); + } + + return ret; +} + +static JSValue js_std_writeFile(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename; + const char *mode; + const void *buf; + size_t len, n; + JSValueConst data; + JSValue val, ret, unref; + bool release; + FILE *fp; + + ret = JS_EXCEPTION; + len = 0; + buf = ""; + mode = "w"; + data = argv[1]; + unref = JS_UNDEFINED; + release = false; + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + if (JS_IsObject(data)) { + val = JS_GetPropertyStr(ctx, data, "buffer"); + if (JS_IsException(val)) + goto exception; + if (JS_IsArrayBuffer(val)) { + data = unref = val; + } else { + JS_FreeValue(ctx, val); + } + } + if (JS_IsArrayBuffer(data)) { + buf = JS_GetArrayBuffer(ctx, &len, data); + mode = "wb"; + } else if (!JS_IsUndefined(data)) { + buf = JS_ToCStringLen(ctx, &len, data); + release = true; + } + if (!buf) + goto exception; + fp = fopen(filename, mode); + if (!fp) { + JS_ThrowPlainError(ctx, "error opening %s for writing", filename); + goto exception; + } + n = fwrite(buf, len, 1, fp); + fclose(fp); + if (n != 1) { + JS_ThrowPlainError(ctx, "error writing to %s", filename); + goto exception; + } + ret = JS_UNDEFINED; +exception: + JS_FreeCString(ctx, filename); + if (release) + JS_FreeCString(ctx, buf); + JS_FreeValue(ctx, unref); + return ret; +} + +typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, + const char *module_name); + + +#if defined(_WIN32) +static JSModuleDef *js_module_loader_so(JSContext *ctx, + const char *module_name) +{ + JSModuleDef *m; + HINSTANCE hd; + JSInitModuleFunc *init; + char *filename = NULL; + size_t len = strlen(module_name); + bool is_absolute = len > 2 && ((module_name[0] >= 'A' && module_name[0] <= 'Z') || + (module_name[0] >= 'a' && module_name[0] <= 'z')) && module_name[1] == ':'; + bool is_relative = len > 2 && module_name[0] == '.' && (module_name[1] == '/' || module_name[1] == '\\'); + if (is_absolute || is_relative) { + filename = (char *)module_name; + } else { + filename = js_malloc(ctx, len + 2 + 1); + if (!filename) + return NULL; + strcpy(filename, "./"); + strcpy(filename + 2, module_name); + } + hd = LoadLibraryA(filename); + if (filename != module_name) + js_free(ctx, filename); + if (hd == NULL) { + JS_ThrowReferenceError(ctx, "js_load_module '%s' error: %lu", + module_name, GetLastError()); + goto fail; + } + init = (JSInitModuleFunc *)(uintptr_t)GetProcAddress(hd, "js_init_module"); + if (!init) { + JS_ThrowReferenceError(ctx, "js_init_module '%s' not found: %lu", + module_name, GetLastError()); + goto fail; + } + m = init(ctx, module_name); + if (!m) { + JS_ThrowReferenceError(ctx, "js_call_module '%s' initialization error", + module_name); + fail: + if (hd != NULL) + FreeLibrary(hd); + return NULL; + } + return m; +} +#elif defined(__wasi__) +static JSModuleDef *js_module_loader_so(JSContext *ctx, + const char *module_name) +{ + JS_ThrowReferenceError(ctx, "shared library modules are not supported yet"); + return NULL; +} +#else +static JSModuleDef *js_module_loader_so(JSContext *ctx, + const char *module_name) +{ + JSModuleDef *m; + void *hd; + JSInitModuleFunc *init; + char *filename; + + if (!strchr(module_name, '/')) { + /* must add a '/' so that the DLL is not searched in the + system library paths */ + filename = js_malloc(ctx, strlen(module_name) + 2 + 1); + if (!filename) + return NULL; + strcpy(filename, "./"); + strcpy(filename + 2, module_name); + } else { + filename = (char *)module_name; + } + + /* C module */ + hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL); + if (filename != module_name) + js_free(ctx, filename); + if (!hd) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library: %s", + module_name, dlerror()); + goto fail; + } + + *(void **) (&init) = dlsym(hd, "js_init_module"); + if (!init) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", + module_name); + goto fail; + } + + m = init(ctx, module_name); + if (!m) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", + module_name); + fail: + if (hd) + dlclose(hd); + return NULL; + } + return m; +} +#endif /* !_WIN32 */ + +int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, + bool use_realpath, bool is_main) +{ + JSModuleDef *m; + char buf[JS__PATH_MAX + 16]; + JSValue meta_obj; + JSAtom module_name_atom; + const char *module_name; + + assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE); + m = JS_VALUE_GET_PTR(func_val); + + module_name_atom = JS_GetModuleName(ctx, m); + module_name = JS_AtomToCString(ctx, module_name_atom); + JS_FreeAtom(ctx, module_name_atom); + if (!module_name) + return -1; + if (!strchr(module_name, ':')) { + strcpy(buf, "file://"); +#if !defined(_WIN32) && !defined(__wasi__) + /* realpath() cannot be used with modules compiled with qjsc + because the corresponding module source code is not + necessarily present */ + if (use_realpath) { + char *res = realpath(module_name, buf + strlen(buf)); + if (!res) { + JS_ThrowTypeError(ctx, "realpath failure"); + JS_FreeCString(ctx, module_name); + return -1; + } + } else +#endif + { + js__pstrcat(buf, sizeof(buf), module_name); + } + } else { + js__pstrcpy(buf, sizeof(buf), module_name); + } + JS_FreeCString(ctx, module_name); + + meta_obj = JS_GetImportMeta(ctx, m); + if (JS_IsException(meta_obj)) + return -1; + JS_DefinePropertyValueStr(ctx, meta_obj, "url", + JS_NewString(ctx, buf), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, meta_obj, "main", + JS_NewBool(ctx, is_main), + JS_PROP_C_W_E); + JS_FreeValue(ctx, meta_obj); + return 0; +} + +JSModuleDef *js_module_loader(JSContext *ctx, + const char *module_name, void *opaque) +{ + JSModuleDef *m; + + if (js__has_suffix(module_name, QJS_NATIVE_MODULE_SUFFIX)) { + m = js_module_loader_so(ctx, module_name); + } else { + size_t buf_len; + uint8_t *buf; + JSValue func_val; + + buf = js_load_file(ctx, &buf_len, module_name); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s'", + module_name); + return NULL; + } + + /* compile the module */ + func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + js_free(ctx, buf); + if (JS_IsException(func_val)) + return NULL; + if (js_module_set_import_meta(ctx, func_val, true, false) < 0) { + JS_FreeValue(ctx, func_val); + return NULL; + } + /* the module is already referenced, so we must free it */ + m = JS_VALUE_GET_PTR(func_val); + JS_FreeValue(ctx, func_val); + } + return m; +} + +static JSValue js_std_exit(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int status; + if (JS_ToInt32(ctx, &status, argv[0])) + status = -1; + exit(status); + return JS_UNDEFINED; +} + +static JSValue js_std_getenv(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *name, *str; + name = JS_ToCString(ctx, argv[0]); + if (!name) + return JS_EXCEPTION; + str = getenv(name); + JS_FreeCString(ctx, name); + if (!str) + return JS_UNDEFINED; + else + return JS_NewString(ctx, str); +} + +#if defined(_WIN32) +static void setenv(const char *name, const char *value, int overwrite) +{ + char *str; + size_t name_len, value_len; + name_len = strlen(name); + value_len = strlen(value); + str = malloc(name_len + 1 + value_len + 1); + memcpy(str, name, name_len); + str[name_len] = '='; + memcpy(str + name_len + 1, value, value_len); + str[name_len + 1 + value_len] = '\0'; + _putenv(str); + free(str); +} + +static void unsetenv(const char *name) +{ + setenv(name, "", true); +} +#endif /* _WIN32 */ + +static JSValue js_std_setenv(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *name, *value; + name = JS_ToCString(ctx, argv[0]); + if (!name) + return JS_EXCEPTION; + value = JS_ToCString(ctx, argv[1]); + if (!value) { + JS_FreeCString(ctx, name); + return JS_EXCEPTION; + } + setenv(name, value, true); + JS_FreeCString(ctx, name); + JS_FreeCString(ctx, value); + return JS_UNDEFINED; +} + +static JSValue js_std_unsetenv(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *name; + name = JS_ToCString(ctx, argv[0]); + if (!name) + return JS_EXCEPTION; + unsetenv(name); + JS_FreeCString(ctx, name); + return JS_UNDEFINED; +} + +/* return an object containing the list of the available environment + variables. */ +static JSValue js_std_getenviron(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + char **envp; + const char *name, *p, *value; + JSValue obj; + uint32_t idx; + size_t name_len; + JSAtom atom; + int ret; + + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + envp = environ; + for(idx = 0; envp[idx] != NULL; idx++) { + name = envp[idx]; + p = strchr(name, '='); + name_len = p - name; + if (!p) + continue; + value = p + 1; + atom = JS_NewAtomLen(ctx, name, name_len); + if (atom == JS_ATOM_NULL) + goto fail; + ret = JS_DefinePropertyValue(ctx, obj, atom, JS_NewString(ctx, value), + JS_PROP_C_W_E); + JS_FreeAtom(ctx, atom); + if (ret < 0) + goto fail; + } + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_std_gc(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JS_RunGC(JS_GetRuntime(ctx)); + return JS_UNDEFINED; +} + +static int interrupt_handler(JSRuntime *rt, void *opaque) +{ + return (os_pending_signals >> SIGINT) & 1; +} + +static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + const char *str = NULL; + size_t len; + JSValue ret, obj; + JSValueConst options_obj, arg; + bool backtrace_barrier = false; + bool eval_function = false; + bool eval_module = false; + bool compile_only = false; + bool compile_module = false; + bool is_async = false; + int flags; + + if (argc >= 2) { + options_obj = argv[1]; + if (get_bool_option(ctx, &backtrace_barrier, options_obj, + "backtrace_barrier")) + return JS_EXCEPTION; + if (get_bool_option(ctx, &eval_function, options_obj, + "eval_function")) + return JS_EXCEPTION; + if (get_bool_option(ctx, &eval_module, options_obj, + "eval_module")) + return JS_EXCEPTION; + if (get_bool_option(ctx, &compile_only, options_obj, + "compile_only")) + return JS_EXCEPTION; + if (get_bool_option(ctx, &compile_module, options_obj, + "compile_module")) + return JS_EXCEPTION; + if (get_bool_option(ctx, &is_async, options_obj, + "async")) + return JS_EXCEPTION; + } + + if (eval_module) { + arg = argv[0]; + if (JS_VALUE_GET_TAG(arg) != JS_TAG_MODULE) + return JS_ThrowTypeError(ctx, "not a module"); + + if (JS_ResolveModule(ctx, arg) < 0) + return JS_EXCEPTION; + + if (js_module_set_import_meta(ctx, arg, false, false) < 0) + return JS_EXCEPTION; + + return JS_EvalFunction(ctx, JS_DupValue(ctx, arg)); + } + + if (!eval_function) { + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) + return JS_EXCEPTION; + } + if (!ts->recv_pipe && ++ts->eval_script_recurse == 1) { + /* install the interrupt handler */ + JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); + } + flags = compile_module ? JS_EVAL_TYPE_MODULE : JS_EVAL_TYPE_GLOBAL; + if (backtrace_barrier) + flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; + if (compile_only) + flags |= JS_EVAL_FLAG_COMPILE_ONLY; + if (is_async) + flags |= JS_EVAL_FLAG_ASYNC; + if (eval_function) { + obj = JS_DupValue(ctx, argv[0]); + ret = JS_EvalFunction(ctx, obj); // takes ownership of |obj| + } else { + ret = JS_Eval(ctx, str, len, "", flags); + } + JS_FreeCString(ctx, str); + if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { + /* remove the interrupt handler */ + JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL); + os_pending_signals &= ~((uint64_t)1 << SIGINT); + /* convert the uncatchable "interrupted" error into a normal error + so that it can be caught by the REPL */ + if (JS_IsException(ret)) + JS_ResetUncatchableError(ctx); + } + return ret; +} + +typedef struct { + FILE *f; + bool is_popen; +} JSSTDFile; + +static bool is_stdio(FILE *f) +{ + return f == stdin || f == stdout || f == stderr; +} + +static void js_std_file_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSThreadState *ts = js_get_thread_state(rt); + JSSTDFile *s = JS_GetOpaque(val, ts->std_file_class_id); + if (s) { + if (s->f && !is_stdio(s->f)) { +#if !defined(__wasi__) + if (s->is_popen) + pclose(s->f); + else +#endif + fclose(s->f); + } + js_free_rt(rt, s); + } +} + +static ssize_t js_get_errno(ssize_t ret) +{ + if (ret == -1) + ret = -errno; + return ret; +} + +static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int err; + if (JS_ToInt32(ctx, &err, argv[0])) + return JS_EXCEPTION; + return JS_NewString(ctx, strerror(err)); +} + +static JSValue js_new_std_file(JSContext *ctx, FILE *f, bool is_popen) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSSTDFile *s; + JSValue obj; + obj = JS_NewObjectClass(ctx, ts->std_file_class_id); + if (JS_IsException(obj)) + return obj; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + s->is_popen = is_popen; + s->f = f; + JS_SetOpaque(obj, s); + return obj; +} + +static void js_set_error_object(JSContext *ctx, JSValueConst obj, int err) +{ + if (!JS_IsUndefined(obj)) { + JS_SetPropertyStr(ctx, obj, "errno", JS_NewInt32(ctx, err)); + } +} + +static JSValue js_std_open(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename, *mode = NULL; + FILE *f; + int err; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; + mode = JS_ToCString(ctx, argv[1]); + if (!mode) + goto fail; + if (mode[strspn(mode, "rwa+bx")] != '\0') { + JS_ThrowTypeError(ctx, "invalid file mode"); + goto fail; + } + + f = fopen(filename, mode); + if (!f) + err = errno; + else + err = 0; + if (argc >= 3) + js_set_error_object(ctx, argv[2], err); + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, false); + fail: + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + return JS_EXCEPTION; +} + +#if !defined(__wasi__) +static JSValue js_std_popen(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename, *mode = NULL; + FILE *f; + int err; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; + mode = JS_ToCString(ctx, argv[1]); + if (!mode) + goto fail; + if (strcmp(mode, "r") && strcmp(mode, "w")) { + JS_ThrowTypeError(ctx, "invalid file mode"); + goto fail; + } + + f = popen(filename, mode); + if (!f) + err = errno; + else + err = 0; + if (argc >= 3) + js_set_error_object(ctx, argv[2], err); + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, true); + fail: + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + return JS_EXCEPTION; +} +#endif // !defined(__wasi__) + +static JSValue js_std_fdopen(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *mode; + FILE *f; + int fd, err; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + mode = JS_ToCString(ctx, argv[1]); + if (!mode) + goto fail; + if (mode[strspn(mode, "rwa+")] != '\0') { + JS_ThrowTypeError(ctx, "invalid file mode"); + goto fail; + } + + f = fdopen(fd, mode); + if (!f) + err = errno; + else + err = 0; + if (argc >= 3) + js_set_error_object(ctx, argv[2], err); + JS_FreeCString(ctx, mode); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, false); + fail: + JS_FreeCString(ctx, mode); + return JS_EXCEPTION; +} + +#if !defined(__wasi__) +static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f; + f = tmpfile(); + if (argc >= 1) + js_set_error_object(ctx, argv[0], f ? 0 : errno); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, false); +} +#endif + +static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return js_printf_internal(ctx, argc, argv, NULL); +} + +static JSValue js_std_printf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return js_printf_internal(ctx, argc, argv, stdout); +} + +static FILE *js_std_file_get(JSContext *ctx, JSValueConst obj) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSSTDFile *s = JS_GetOpaque2(ctx, obj, ts->std_file_class_id); + if (!s) + return NULL; + if (!s->f) { + JS_ThrowTypeError(ctx, "invalid file handle"); + return NULL; + } + return s->f; +} + +static JSValue js_std_file_puts(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + FILE *f; + int i; + const char *str; + size_t len; + + if (magic == 0) { + f = stdout; + } else { + f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + } + + for(i = 0; i < argc; i++) { + str = JS_ToCStringLen(ctx, &len, argv[i]); + if (!str) + return JS_EXCEPTION; + fwrite(str, 1, len, f); + JS_FreeCString(ctx, str); + } + return JS_UNDEFINED; +} + +static JSValue js_std_file_close(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSSTDFile *s = JS_GetOpaque2(ctx, this_val, ts->std_file_class_id); + int err; + if (!s) + return JS_EXCEPTION; + if (!s->f) + return JS_ThrowTypeError(ctx, "invalid file handle"); + if (is_stdio(s->f)) + return JS_ThrowTypeError(ctx, "cannot close stdio"); +#if !defined(__wasi__) + if (s->is_popen) + err = js_get_errno(pclose(s->f)); + else +#endif + err = js_get_errno(fclose(s->f)); + s->f = NULL; + return JS_NewInt32(ctx, err); +} + +static JSValue js_std_file_printf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return js_printf_internal(ctx, argc, argv, f); +} + +static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + fflush(f); + return JS_UNDEFINED; +} + +static JSValue js_std_file_tell(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_bigint) +{ + FILE *f = js_std_file_get(ctx, this_val); + int64_t pos; + if (!f) + return JS_EXCEPTION; +#if defined(__linux__) || defined(__GLIBC__) + pos = ftello(f); +#else + pos = ftell(f); +#endif + if (is_bigint) + return JS_NewBigInt64(ctx, pos); + else + return JS_NewInt64(ctx, pos); +} + +static JSValue js_std_file_seek(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int64_t pos; + int whence, ret; + if (!f) + return JS_EXCEPTION; + if (JS_ToInt64Ext(ctx, &pos, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &whence, argv[1])) + return JS_EXCEPTION; +#if defined(__linux__) || defined(__GLIBC__) + ret = fseeko(f, pos, whence); +#else + ret = fseek(f, pos, whence); +#endif + if (ret < 0) + ret = -errno; + return JS_NewInt32(ctx, ret); +} + +static JSValue js_std_file_eof(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewBool(ctx, feof(f)); +} + +static JSValue js_std_file_error(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewBool(ctx, ferror(f)); +} + +static JSValue js_std_file_clearerr(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + clearerr(f); + return JS_UNDEFINED; +} + +static JSValue js_std_file_fileno(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewInt32(ctx, fileno(f)); +} + +static JSValue js_std_file_read_write(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + FILE *f = js_std_file_get(ctx, this_val); + uint64_t pos, len; + size_t size, ret; + uint8_t *buf; + + if (!f) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &pos, argv[1])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &len, argv[2])) + return JS_EXCEPTION; + buf = JS_GetArrayBuffer(ctx, &size, argv[0]); + if (!buf) + return JS_EXCEPTION; + if (pos + len > size) + return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); + if (magic) + ret = fwrite(buf + pos, 1, len, f); + else + ret = fread(buf + pos, 1, len, f); + return JS_NewInt64(ctx, ret); +} + +/* XXX: could use less memory and go faster */ +static JSValue js_std_file_getline(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int c; + DynBuf dbuf; + JSValue obj; + + if (!f) + return JS_EXCEPTION; + + js_std_dbuf_init(ctx, &dbuf); + for(;;) { + c = fgetc(f); + if (c == EOF) { + if (dbuf.size == 0) { + /* EOF */ + dbuf_free(&dbuf); + return JS_NULL; + } else { + break; + } + } + if (c == '\n') + break; + if (dbuf_putc(&dbuf, c)) { + dbuf_free(&dbuf); + return JS_ThrowOutOfMemory(ctx); + } + } + obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); + dbuf_free(&dbuf); + return obj; +} + +/* XXX: could use less memory and go faster */ +static JSValue js_std_file_readAs(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + FILE *f = js_std_file_get(ctx, this_val); + int c; + DynBuf dbuf; + JSValue obj; + uint64_t max_size64; + size_t max_size; + JSValueConst max_size_val; + + if (!f) + return JS_EXCEPTION; + + if (argc >= 1) + max_size_val = argv[0]; + else + max_size_val = JS_UNDEFINED; + max_size = (size_t)-1; + if (!JS_IsUndefined(max_size_val)) { + if (JS_ToIndex(ctx, &max_size64, max_size_val)) + return JS_EXCEPTION; + if (max_size64 < max_size) + max_size = max_size64; + } + + js_std_dbuf_init(ctx, &dbuf); + while (max_size != 0) { + c = fgetc(f); + if (c == EOF) + break; + if (dbuf_putc(&dbuf, c)) { + dbuf_free(&dbuf); + return JS_EXCEPTION; + } + max_size--; + } + if (magic) { + obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); + } else { + obj = JS_NewArrayBufferCopy(ctx, dbuf.buf, dbuf.size); + } + dbuf_free(&dbuf); + return obj; +} + +static JSValue js_std_file_getByte(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewInt32(ctx, fgetc(f)); +} + +static JSValue js_std_file_putByte(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int c; + if (!f) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &c, argv[0])) + return JS_EXCEPTION; + c = fputc(c, f); + return JS_NewInt32(ctx, c); +} + +/* urlGet */ +#if !defined(__wasi__) + +#define URL_GET_PROGRAM "curl -s -i --" +#define URL_GET_BUF_SIZE 4096 + +static int http_get_header_line(FILE *f, char *buf, size_t buf_size, + DynBuf *dbuf) +{ + int c; + char *p; + + p = buf; + for(;;) { + c = fgetc(f); + if (c < 0) + return -1; + if ((p - buf) < buf_size - 1) + *p++ = c; + if (dbuf) + dbuf_putc(dbuf, c); + if (c == '\n') + break; + } + *p = '\0'; + return 0; +} + +static int http_get_status(const char *buf) +{ + const char *p = buf; + while (*p != ' ' && *p != '\0') + p++; + if (*p != ' ') + return 0; + while (*p == ' ') + p++; + return atoi(p); +} + +static JSValue js_std_urlGet(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *url; + DynBuf cmd_buf; + DynBuf data_buf_s, *data_buf = &data_buf_s; + DynBuf header_buf_s, *header_buf = &header_buf_s; + char *buf; + size_t i, len; + int status; + JSValue response = JS_UNDEFINED, ret_obj; + JSValueConst options_obj; + FILE *f; + bool binary_flag, full_flag; + + url = JS_ToCString(ctx, argv[0]); + if (!url) + return JS_EXCEPTION; + + binary_flag = false; + full_flag = false; + + if (argc >= 2) { + options_obj = argv[1]; + + if (get_bool_option(ctx, &binary_flag, options_obj, "binary")) + goto fail_obj; + + if (get_bool_option(ctx, &full_flag, options_obj, "full")) { + fail_obj: + JS_FreeCString(ctx, url); + return JS_EXCEPTION; + } + } + + js_std_dbuf_init(ctx, &cmd_buf); + dbuf_printf(&cmd_buf, "%s '", URL_GET_PROGRAM); + for(i = 0; url[i] != '\0'; i++) { + unsigned char c = url[i]; + switch (c) { + case '\'': + /* shell single quoted string does not support \' */ + dbuf_putstr(&cmd_buf, "'\\''"); + break; + case '[': case ']': case '{': case '}': case '\\': + /* prevent interpretation by curl as range or set specification */ + dbuf_putc(&cmd_buf, '\\'); + /* FALLTHROUGH */ + default: + dbuf_putc(&cmd_buf, c); + break; + } + } + JS_FreeCString(ctx, url); + dbuf_putstr(&cmd_buf, "'"); + dbuf_putc(&cmd_buf, '\0'); + if (dbuf_error(&cmd_buf)) { + dbuf_free(&cmd_buf); + return JS_EXCEPTION; + } + // printf("%s\n", (char *)cmd_buf.buf); + f = popen((char *)cmd_buf.buf, "r"); + dbuf_free(&cmd_buf); + if (!f) { + return JS_ThrowTypeError(ctx, "could not start curl"); + } + + js_std_dbuf_init(ctx, data_buf); + js_std_dbuf_init(ctx, header_buf); + + buf = js_malloc(ctx, URL_GET_BUF_SIZE); + if (!buf) + goto fail; + + /* get the HTTP status */ + if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, NULL) < 0) { + status = 0; + goto bad_header; + } + status = http_get_status(buf); + if (!full_flag && !(status >= 200 && status <= 299)) { + goto bad_header; + } + + /* wait until there is an empty line */ + for(;;) { + if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, header_buf) < 0) { + bad_header: + response = JS_NULL; + goto done; + } + if (!strcmp(buf, "\r\n")) + break; + } + if (dbuf_error(header_buf)) + goto fail; + header_buf->size -= 2; /* remove the trailing CRLF */ + + /* download the data */ + for(;;) { + len = fread(buf, 1, URL_GET_BUF_SIZE, f); + if (len == 0) + break; + dbuf_put(data_buf, (uint8_t *)buf, len); + } + if (dbuf_error(data_buf)) + goto fail; + if (binary_flag) { + response = JS_NewArrayBufferCopy(ctx, + data_buf->buf, data_buf->size); + } else { + response = JS_NewStringLen(ctx, (char *)data_buf->buf, data_buf->size); + } + if (JS_IsException(response)) + goto fail; + done: + js_free(ctx, buf); + buf = NULL; + pclose(f); + f = NULL; + dbuf_free(data_buf); + data_buf = NULL; + + if (full_flag) { + ret_obj = JS_NewObject(ctx); + if (JS_IsException(ret_obj)) + goto fail; + JS_DefinePropertyValueStr(ctx, ret_obj, "response", + response, + JS_PROP_C_W_E); + if (!JS_IsNull(response)) { + JS_DefinePropertyValueStr(ctx, ret_obj, "responseHeaders", + JS_NewStringLen(ctx, (char *)header_buf->buf, + header_buf->size), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, ret_obj, "status", + JS_NewInt32(ctx, status), + JS_PROP_C_W_E); + } + } else { + ret_obj = response; + } + dbuf_free(header_buf); + return ret_obj; + fail: + if (f) + pclose(f); + js_free(ctx, buf); + if (data_buf) + dbuf_free(data_buf); + if (header_buf) + dbuf_free(header_buf); + JS_FreeValue(ctx, response); + return JS_EXCEPTION; +} +#endif // !defined(__wasi__) + +static JSClassDef js_std_file_class = { + "FILE", + .finalizer = js_std_file_finalizer, +}; + +static const JSCFunctionListEntry js_std_error_props[] = { + /* various errno values */ +#define DEF(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) + DEF(EINVAL), + DEF(EIO), + DEF(EACCES), + DEF(EEXIST), + DEF(ENOSPC), + DEF(ENOSYS), + DEF(EBUSY), + DEF(ENOENT), + DEF(EPERM), + DEF(EPIPE), + DEF(EBADF), +#undef DEF +}; + +static const JSCFunctionListEntry js_std_funcs[] = { + JS_CFUNC_DEF("exit", 1, js_std_exit ), + JS_CFUNC_DEF("gc", 0, js_std_gc ), + JS_CFUNC_DEF("evalScript", 1, js_evalScript ), + JS_CFUNC_DEF("loadScript", 1, js_loadScript ), + JS_CFUNC_DEF("getenv", 1, js_std_getenv ), + JS_CFUNC_DEF("setenv", 1, js_std_setenv ), + JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), + JS_CFUNC_DEF("getenviron", 1, js_std_getenviron ), +#if !defined(__wasi__) + JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), +#endif + JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), + JS_CFUNC_DEF("writeFile", 2, js_std_writeFile ), + JS_CFUNC_DEF("strerror", 1, js_std_strerror ), + + /* FILE I/O */ + JS_CFUNC_DEF("open", 2, js_std_open ), +#if !defined(__wasi__) + JS_CFUNC_DEF("popen", 2, js_std_popen ), + JS_CFUNC_DEF("tmpfile", 0, js_std_tmpfile ), +#endif + JS_CFUNC_DEF("fdopen", 2, js_std_fdopen ), + JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 0 ), + JS_CFUNC_DEF("printf", 1, js_std_printf ), + JS_CFUNC_DEF("sprintf", 1, js_std_sprintf ), + JS_PROP_INT32_DEF("SEEK_SET", SEEK_SET, JS_PROP_CONFIGURABLE ), + JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ), + JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ), + JS_OBJECT_DEF("Error", js_std_error_props, countof(js_std_error_props), JS_PROP_CONFIGURABLE), +}; + +static const JSCFunctionListEntry js_std_file_proto_funcs[] = { + JS_CFUNC_DEF("close", 0, js_std_file_close ), + JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 1 ), + JS_CFUNC_DEF("printf", 1, js_std_file_printf ), + JS_CFUNC_DEF("flush", 0, js_std_file_flush ), + JS_CFUNC_MAGIC_DEF("tell", 0, js_std_file_tell, 0 ), + JS_CFUNC_MAGIC_DEF("tello", 0, js_std_file_tell, 1 ), + JS_CFUNC_DEF("seek", 2, js_std_file_seek ), + JS_CFUNC_DEF("eof", 0, js_std_file_eof ), + JS_CFUNC_DEF("fileno", 0, js_std_file_fileno ), + JS_CFUNC_DEF("error", 0, js_std_file_error ), + JS_CFUNC_DEF("clearerr", 0, js_std_file_clearerr ), + JS_CFUNC_MAGIC_DEF("read", 3, js_std_file_read_write, 0 ), + JS_CFUNC_MAGIC_DEF("write", 3, js_std_file_read_write, 1 ), + JS_CFUNC_DEF("getline", 0, js_std_file_getline ), + JS_CFUNC_MAGIC_DEF("readAsArrayBuffer", 0, js_std_file_readAs, 0 ), + JS_CFUNC_MAGIC_DEF("readAsString", 0, js_std_file_readAs, 1 ), + JS_CFUNC_DEF("getByte", 0, js_std_file_getByte ), + JS_CFUNC_DEF("putByte", 1, js_std_file_putByte ), + /* setvbuf, ... */ +}; + +static int js_std_init(JSContext *ctx, JSModuleDef *m) +{ + JSValue proto; + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + + /* FILE class */ + /* the class ID is created once */ + JS_NewClassID(rt, &ts->std_file_class_id); + /* the class is created once per runtime */ + JS_NewClass(rt, ts->std_file_class_id, &js_std_file_class); + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_std_file_proto_funcs, + countof(js_std_file_proto_funcs)); + JS_SetClassProto(ctx, ts->std_file_class_id, proto); + + JS_SetModuleExportList(ctx, m, js_std_funcs, + countof(js_std_funcs)); + JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, false)); + JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, false)); + JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, false)); + return 0; +} + +JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_std_init); + if (!m) + return NULL; + JS_AddModuleExportList(ctx, m, js_std_funcs, countof(js_std_funcs)); + JS_AddModuleExport(ctx, m, "in"); + JS_AddModuleExport(ctx, m, "out"); + JS_AddModuleExport(ctx, m, "err"); + return m; +} + +/**********************************************************/ +/* 'os' object */ + +static JSValue js_os_open(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename; + int flags, mode, ret; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &flags, argv[1])) + goto fail; + if (argc >= 3 && !JS_IsUndefined(argv[2])) { + if (JS_ToInt32(ctx, &mode, argv[2])) { + fail: + JS_FreeCString(ctx, filename); + return JS_EXCEPTION; + } + } else { + mode = 0666; + } +#if defined(_WIN32) + /* force binary mode by default */ + if (!(flags & O_TEXT)) + flags |= O_BINARY; +#endif + ret = js_get_errno(open(filename, flags, mode)); + JS_FreeCString(ctx, filename); + return JS_NewInt32(ctx, ret); +} + +static JSValue js_os_close(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, ret; + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + ret = js_get_errno(close(fd)); + return JS_NewInt32(ctx, ret); +} + +static JSValue js_os_seek(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, whence; + int64_t pos, ret; + bool is_bigint; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + is_bigint = JS_IsBigInt(argv[1]); + if (JS_ToInt64Ext(ctx, &pos, argv[1])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &whence, argv[2])) + return JS_EXCEPTION; + ret = lseek(fd, pos, whence); + if (ret == -1) + ret = -errno; + if (is_bigint) + return JS_NewBigInt64(ctx, ret); + else + return JS_NewInt64(ctx, ret); +} + +static JSValue js_os_read_write(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + int fd; + uint64_t pos, len; + size_t size; + ssize_t ret; + uint8_t *buf; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &pos, argv[2])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &len, argv[3])) + return JS_EXCEPTION; + buf = JS_GetArrayBuffer(ctx, &size, argv[1]); + if (!buf) + return JS_EXCEPTION; + if (pos + len > size) + return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); + if (magic) + ret = js_get_errno(write(fd, buf + pos, len)); + else + ret = js_get_errno(read(fd, buf + pos, len)); + return JS_NewInt64(ctx, ret); +} + +static JSValue js_os_isatty(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + return JS_NewBool(ctx, (isatty(fd) != 0)); +} + +#if defined(_WIN32) +static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + HANDLE handle; + CONSOLE_SCREEN_BUFFER_INFO info; + JSValue obj; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + handle = (HANDLE)_get_osfhandle(fd); + + if (!GetConsoleScreenBufferInfo(handle, &info)) + return JS_NULL; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, info.dwSize.X), JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, info.dwSize.Y), JS_PROP_C_W_E); + return obj; +} + +/* Windows 10 built-in VT100 emulation */ +#define __ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#define __ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 + +static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + HANDLE handle; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + handle = (HANDLE)_get_osfhandle(fd); + SetConsoleMode(handle, ENABLE_WINDOW_INPUT | __ENABLE_VIRTUAL_TERMINAL_INPUT); + _setmode(fd, _O_BINARY); + if (fd == 0) { + handle = (HANDLE)_get_osfhandle(1); /* corresponding output */ + SetConsoleMode(handle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | __ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + return JS_UNDEFINED; +} +#elif !defined(__wasi__) +static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + struct winsize ws; + JSValue obj; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + if (ioctl(fd, TIOCGWINSZ, &ws) == 0 && + ws.ws_col >= 4 && ws.ws_row >= 4) { + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ws.ws_col), JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, ws.ws_row), JS_PROP_C_W_E); + return obj; + } else { + return JS_NULL; + } +} + +static struct termios oldtty; + +static void term_exit(void) +{ + tcsetattr(0, TCSANOW, &oldtty); +} + +/* XXX: should add a way to go back to normal mode */ +static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + struct termios tty; + int fd; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + + memset(&tty, 0, sizeof(tty)); + tcgetattr(fd, &tty); + oldtty = tty; + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + + tcsetattr(fd, TCSANOW, &tty); + + atexit(term_exit); + return JS_UNDEFINED; +} + +#endif /* !_WIN32 && !__wasi__ */ + +static JSValue js_os_remove(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename; + int ret; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; +#if defined(_WIN32) + { + struct stat st; + if (stat(filename, &st) == 0 && (st.st_mode & _S_IFDIR)) { + ret = rmdir(filename); + } else { + ret = unlink(filename); + } + } +#else + ret = remove(filename); +#endif + ret = js_get_errno(ret); + JS_FreeCString(ctx, filename); + return JS_NewInt32(ctx, ret); +} + +static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *oldpath, *newpath; + int ret; + + oldpath = JS_ToCString(ctx, argv[0]); + if (!oldpath) + return JS_EXCEPTION; + newpath = JS_ToCString(ctx, argv[1]); + if (!newpath) { + JS_FreeCString(ctx, oldpath); + return JS_EXCEPTION; + } + ret = js_get_errno(rename(oldpath, newpath)); + JS_FreeCString(ctx, oldpath); + JS_FreeCString(ctx, newpath); + return JS_NewInt32(ctx, ret); +} + +static bool is_main_thread(JSRuntime *rt) +{ + JSThreadState *ts = js_get_thread_state(rt); + return !ts->recv_pipe; +} + +static JSOSRWHandler *find_rh(JSThreadState *ts, int fd) +{ + JSOSRWHandler *rh; + struct list_head *el; + + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (rh->fd == fd) + return rh; + } + return NULL; +} + +static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh) +{ + int i; + list_del(&rh->link); + for(i = 0; i < 2; i++) { + JS_FreeValueRT(rt, rh->rw_func[i]); + } + js_free_rt(rt, rh); +} + +static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSOSRWHandler *rh; + int fd; + JSValueConst func; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + func = argv[1]; + if (JS_IsNull(func)) { + rh = find_rh(ts, fd); + if (rh) { + JS_FreeValue(ctx, rh->rw_func[magic]); + rh->rw_func[magic] = JS_NULL; + if (JS_IsNull(rh->rw_func[0]) && + JS_IsNull(rh->rw_func[1])) { + /* remove the entry */ + free_rw_handler(JS_GetRuntime(ctx), rh); + } + } + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + rh = find_rh(ts, fd); + if (!rh) { + rh = js_mallocz(ctx, sizeof(*rh)); + if (!rh) + return JS_EXCEPTION; + rh->fd = fd; + rh->rw_func[0] = JS_NULL; + rh->rw_func[1] = JS_NULL; + list_add_tail(&rh->link, &ts->os_rw_handlers); + } + JS_FreeValue(ctx, rh->rw_func[magic]); + rh->rw_func[magic] = JS_DupValue(ctx, func); + } + return JS_UNDEFINED; +} + +static JSOSSignalHandler *find_sh(JSThreadState *ts, int sig_num) +{ + JSOSSignalHandler *sh; + struct list_head *el; + list_for_each(el, &ts->os_signal_handlers) { + sh = list_entry(el, JSOSSignalHandler, link); + if (sh->sig_num == sig_num) + return sh; + } + return NULL; +} + +static void free_sh(JSRuntime *rt, JSOSSignalHandler *sh) +{ + list_del(&sh->link); + JS_FreeValueRT(rt, sh->func); + js_free_rt(rt, sh); +} + +static void os_signal_handler(int sig_num) +{ + os_pending_signals |= ((uint64_t)1 << sig_num); +} + +#if defined(_WIN32) +typedef void (*sighandler_t)(int sig_num); +#endif + +static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSOSSignalHandler *sh; + uint32_t sig_num; + JSValueConst func; + sighandler_t handler; + + if (!is_main_thread(rt)) + return JS_ThrowTypeError(ctx, "signal handler can only be set in the main thread"); + + if (JS_ToUint32(ctx, &sig_num, argv[0])) + return JS_EXCEPTION; + if (sig_num >= 64) + return JS_ThrowRangeError(ctx, "invalid signal number"); + func = argv[1]; + /* func = null: SIG_DFL, func = undefined, SIG_IGN */ + if (JS_IsNull(func) || JS_IsUndefined(func)) { + sh = find_sh(ts, sig_num); + if (sh) { + free_sh(JS_GetRuntime(ctx), sh); + } + if (JS_IsNull(func)) + handler = SIG_DFL; + else + handler = SIG_IGN; + signal(sig_num, handler); + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + sh = find_sh(ts, sig_num); + if (!sh) { + sh = js_mallocz(ctx, sizeof(*sh)); + if (!sh) + return JS_EXCEPTION; + sh->sig_num = sig_num; + list_add_tail(&sh->link, &ts->os_signal_handlers); + } + JS_FreeValue(ctx, sh->func); + sh->func = JS_DupValue(ctx, func); + signal(sig_num, os_signal_handler); + } + return JS_UNDEFINED; +} + +#if !defined(_WIN32) && !defined(__wasi__) +static JSValue js_os_cputime(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + struct rusage ru; + int64_t cputime; + + cputime = 0; + if (!getrusage(RUSAGE_SELF, &ru)) + cputime = (int64_t)ru.ru_utime.tv_sec * 1000000 + ru.ru_utime.tv_usec; + + return JS_NewInt64(ctx, cputime); +} +#endif + +static JSValue js_os_exepath(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + char buf[JS__PATH_MAX]; + size_t len = sizeof(buf); + if (js_exepath(buf, &len)) + return JS_UNDEFINED; + return JS_NewStringLen(ctx, buf, len); +} + +static JSValue js_os_now(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewInt64(ctx, js__hrtime_ns() / 1000); +} + +static uint64_t js__hrtime_ms(void) +{ + return js__hrtime_ns() / (1000 * 1000); +} + +static void free_timer(JSRuntime *rt, JSOSTimer *th) +{ + list_del(&th->link); + JS_FreeValueRT(rt, th->func); + js_free_rt(rt, th); +} + +// TODO(bnoordhuis) accept string as first arg and eval at timer expiry +// TODO(bnoordhuis) retain argv[2..] as args for callback if argc > 2 +static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + int64_t delay; + JSValueConst func; + JSOSTimer *th; + + func = argv[0]; + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + if (JS_ToInt64(ctx, &delay, argv[1])) + return JS_EXCEPTION; + if (delay < 1) + delay = 1; + th = js_mallocz(ctx, sizeof(*th)); + if (!th) + return JS_EXCEPTION; + th->timer_id = ts->next_timer_id++; + if (ts->next_timer_id > MAX_SAFE_INTEGER) + ts->next_timer_id = 1; + th->repeats = (magic > 0); + th->timeout = js__hrtime_ms() + delay; + th->delay = delay; + th->func = JS_DupValue(ctx, func); + list_add_tail(&th->link, &ts->os_timers); + return JS_NewInt64(ctx, th->timer_id); +} + +static JSOSTimer *find_timer_by_id(JSThreadState *ts, int timer_id) +{ + struct list_head *el; + if (timer_id <= 0) + return NULL; + list_for_each(el, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + if (th->timer_id == timer_id) + return th; + } + return NULL; +} + +static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSOSTimer *th; + int64_t timer_id; + + if (JS_ToInt64(ctx, &timer_id, argv[0])) + return JS_EXCEPTION; + th = find_timer_by_id(ts, timer_id); + if (!th) + return JS_UNDEFINED; + free_timer(rt, th); + return JS_UNDEFINED; +} + +/* return a promise */ +static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + int64_t delay; + JSOSTimer *th; + JSValue promise, resolving_funcs[2]; + + if (JS_ToInt64(ctx, &delay, argv[0])) + return JS_EXCEPTION; + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + + th = js_mallocz(ctx, sizeof(*th)); + if (!th) { + JS_FreeValue(ctx, promise); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return JS_EXCEPTION; + } + th->timer_id = -1; + th->timeout = js__hrtime_ms() + delay; + th->func = JS_DupValue(ctx, resolving_funcs[0]); + list_add_tail(&th->link, &ts->os_timers); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; +} + +static int call_handler(JSContext *ctx, JSValue func) +{ + int r; + JSValue ret, func1; + /* 'func' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + func1 = JS_DupValue(ctx, func); + ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL); + JS_FreeValue(ctx, func1); + r = 0; + if (JS_IsException(ret)) + r = -1; + JS_FreeValue(ctx, ret); + return r; +} + +static int js_os_run_timers(JSRuntime *rt, JSContext *ctx, JSThreadState *ts, int *min_delay) +{ + JSValue func; + JSOSTimer *th; + int64_t cur_time, delay; + struct list_head *el; + int r; + + if (list_empty(&ts->os_timers)) { + *min_delay = -1; + return 0; + } + + cur_time = js__hrtime_ms(); + *min_delay = INT32_MAX; + + list_for_each(el, &ts->os_timers) { + th = list_entry(el, JSOSTimer, link); + delay = th->timeout - cur_time; + if (delay > 0) { + *min_delay = min_int(*min_delay, delay); + } else { + *min_delay = 0; + func = JS_DupValueRT(rt, th->func); + if (th->repeats) + th->timeout = cur_time + th->delay; + else + free_timer(rt, th); + r = call_handler(ctx, func); + JS_FreeValueRT(rt, func); + return r; + } + } + + return 0; +} + +#ifdef USE_WORKER + +#ifdef _WIN32 + +static int js_waker_init(JSWaker *w) +{ + w->handle = CreateEvent(NULL, TRUE, FALSE, NULL); + return w->handle ? 0 : -1; +} + +static void js_waker_signal(JSWaker *w) +{ + SetEvent(w->handle); +} + +static void js_waker_clear(JSWaker *w) +{ + ResetEvent(w->handle); +} + +static void js_waker_close(JSWaker *w) +{ + CloseHandle(w->handle); + w->handle = INVALID_HANDLE_VALUE; +} + +#else // !_WIN32 + +static int js_waker_init(JSWaker *w) +{ + int fds[2]; + + if (pipe(fds) < 0) + return -1; + w->read_fd = fds[0]; + w->write_fd = fds[1]; + return 0; +} + +static void js_waker_signal(JSWaker *w) +{ + int ret; + + for(;;) { + ret = write(w->write_fd, "", 1); + if (ret == 1) + break; + if (ret < 0 && (errno != EAGAIN || errno != EINTR)) + break; + } +} + +static void js_waker_clear(JSWaker *w) +{ + uint8_t buf[16]; + int ret; + + for(;;) { + ret = read(w->read_fd, buf, sizeof(buf)); + if (ret >= 0) + break; + if (errno != EAGAIN && errno != EINTR) + break; + } +} + +static void js_waker_close(JSWaker *w) +{ + close(w->read_fd); + close(w->write_fd); + w->read_fd = -1; + w->write_fd = -1; +} + +#endif // _WIN32 + +static void js_free_message(JSWorkerMessage *msg); + +/* return 1 if a message was handled, 0 if no message */ +static int handle_posted_message(JSRuntime *rt, JSContext *ctx, + JSWorkerMessageHandler *port) +{ + JSWorkerMessagePipe *ps = port->recv_pipe; + int ret; + struct list_head *el; + JSWorkerMessage *msg; + JSValue obj, data_obj, func, retval; + + js_mutex_lock(&ps->mutex); + if (!list_empty(&ps->msg_queue)) { + el = ps->msg_queue.next; + msg = list_entry(el, JSWorkerMessage, link); + + /* remove the message from the queue */ + list_del(&msg->link); + + // drain read end of pipe + if (list_empty(&ps->msg_queue)) + js_waker_clear(&ps->waker); + + js_mutex_unlock(&ps->mutex); + + data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, + JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); + + js_free_message(msg); + + if (JS_IsException(data_obj)) + goto fail; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, data_obj); + goto fail; + } + JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); + + /* 'func' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + func = JS_DupValue(ctx, port->on_message_func); + retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj); + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, func); + if (JS_IsException(retval)) { + fail: + js_std_dump_error(ctx); + } else { + JS_FreeValue(ctx, retval); + } + ret = 1; + } else { + js_mutex_unlock(&ps->mutex); + ret = 0; + } + return ret; +} + +#endif // USE_WORKER + +#if defined(_WIN32) +static int js_os_poll(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + int min_delay, count; + JSOSRWHandler *rh; + struct list_head *el; + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; // 64 + + /* XXX: handle signals if useful */ + + if (js_os_run_timers(rt, ctx, ts, &min_delay)) + return -1; + if (min_delay == 0) + return 0; // expired timer + if (min_delay < 0) + if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->port_list)) + return -1; /* no more events */ + + count = 0; + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) + handles[count++] = (HANDLE)_get_osfhandle(rh->fd); // stdin + if (count == (int)countof(handles)) + break; + } + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (JS_IsNull(port->on_message_func)) + continue; + handles[count++] = port->recv_pipe->waker.handle; + if (count == (int)countof(handles)) + break; + } + + if (count > 0) { + DWORD ret, timeout = INFINITE; + if (min_delay != -1) + timeout = min_delay; + ret = WaitForMultipleObjects(count, handles, FALSE, timeout); + if (ret < count) { + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { + return call_handler(ctx, rh->rw_func[0]); + /* must stop because the list may have been modified */ + } + } + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + if (ps->waker.handle == handles[ret]) { + if (handle_posted_message(rt, ctx, port)) + goto done; + } + } + } + } + } else { + Sleep(min_delay); + } +done: + return 0; +} +#else // !defined(_WIN32) +static int js_os_poll(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + int r, w, ret, nfds, min_delay; + JSOSRWHandler *rh; + struct list_head *el; + struct pollfd *pfd, *pfds, pfds_local[64]; + + /* only check signals in the main thread */ + if (!ts->recv_pipe && + unlikely(os_pending_signals != 0)) { + JSOSSignalHandler *sh; + uint64_t mask; + + list_for_each(el, &ts->os_signal_handlers) { + sh = list_entry(el, JSOSSignalHandler, link); + mask = (uint64_t)1 << sh->sig_num; + if (os_pending_signals & mask) { + os_pending_signals &= ~mask; + return call_handler(ctx, sh->func); + } + } + } + + if (js_os_run_timers(rt, ctx, ts, &min_delay)) + return -1; + if (min_delay == 0) + return 0; // expired timer + if (min_delay < 0) + if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->port_list)) + return -1; /* no more events */ + + nfds = 0; + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + nfds += (!JS_IsNull(rh->rw_func[0]) || !JS_IsNull(rh->rw_func[1])); + } + +#ifdef USE_WORKER + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + nfds += !JS_IsNull(port->on_message_func); + } +#endif // USE_WORKER + + pfd = pfds = pfds_local; + if (nfds > (int)countof(pfds_local)) { + pfd = pfds = js_malloc(ctx, nfds * sizeof(*pfd)); + if (!pfd) + return -1; + } + + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + r = POLLIN * !JS_IsNull(rh->rw_func[0]); + w = POLLOUT * !JS_IsNull(rh->rw_func[1]); + if (r || w) + *pfd++ = (struct pollfd){rh->fd, r|w, 0}; + } + +#ifdef USE_WORKER + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + *pfd++ = (struct pollfd){ps->waker.read_fd, POLLIN, 0}; + } + } +#endif // USE_WORKER + + // FIXME(bnoordhuis) the loop below is quadratic in theory but + // linear-ish in practice because we bail out on the first hit, + // i.e., it's probably good enough for now + ret = 0; + nfds = poll(pfds, nfds, min_delay); + for (pfd = pfds; nfds-- > 0; pfd++) { + rh = find_rh(ts, pfd->fd); + if (rh) { + r = (POLLERR|POLLHUP|POLLNVAL|POLLIN) * !JS_IsNull(rh->rw_func[0]); + w = (POLLERR|POLLHUP|POLLNVAL|POLLOUT) * !JS_IsNull(rh->rw_func[1]); + if (r & pfd->revents) { + ret = call_handler(ctx, rh->rw_func[0]); + goto done; + /* must stop because the list may have been modified */ + } + if (w & pfd->revents) { + ret = call_handler(ctx, rh->rw_func[1]); + goto done; + /* must stop because the list may have been modified */ + } + } else { +#ifdef USE_WORKER + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + if (pfd->fd == ps->waker.read_fd) { + if (handle_posted_message(rt, ctx, port)) + goto done; + } + } + } +#endif // USE_WORKER + } + } +done: + if (pfds != pfds_local) + js_free(ctx, pfds); + return ret; +} +#endif // defined(_WIN32) + + +static JSValue make_obj_error(JSContext *ctx, + JSValue obj, + int err) +{ + JSValue arr; + if (JS_IsException(obj)) + return obj; + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + return JS_EXCEPTION; + JS_DefinePropertyValueUint32(ctx, arr, 0, obj, + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, arr, 1, JS_NewInt32(ctx, err), + JS_PROP_C_W_E); + return arr; +} + +static JSValue make_string_error(JSContext *ctx, + const char *buf, + int err) +{ + return make_obj_error(ctx, JS_NewString(ctx, buf), err); +} + +/* return [cwd, errorcode] */ +static JSValue js_os_getcwd(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + char buf[JS__PATH_MAX]; + int err; + + if (!getcwd(buf, sizeof(buf))) { + buf[0] = '\0'; + err = errno; + } else { + err = 0; + } + return make_string_error(ctx, buf, err); +} + +static JSValue js_os_chdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *target; + int err; + + target = JS_ToCString(ctx, argv[0]); + if (!target) + return JS_EXCEPTION; + err = js_get_errno(chdir(target)); + JS_FreeCString(ctx, target); + return JS_NewInt32(ctx, err); +} + +static JSValue js_os_mkdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int mode, ret; + const char *path; + + if (argc >= 2) { + if (JS_ToInt32(ctx, &mode, argv[1])) + return JS_EXCEPTION; + } else { + mode = 0777; + } + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + (void)mode; + ret = js_get_errno(mkdir(path)); +#else + ret = js_get_errno(mkdir(path, mode)); +#endif + JS_FreeCString(ctx, path); + return JS_NewInt32(ctx, ret); +} + +/* return [array, errorcode] */ +static JSValue js_os_readdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ +#ifdef _WIN32 + const char *path; + JSValue obj; + int err; + uint32_t len; + HANDLE h; + WIN32_FIND_DATAA d; + char s[1024]; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) { + JS_FreeCString(ctx, path); + return JS_EXCEPTION; + } + snprintf(s, sizeof(s), "%s/*", path); + JS_FreeCString(ctx, path); + err = 0; + h = FindFirstFileA(s, &d); + if (h == INVALID_HANDLE_VALUE) + err = GetLastError(); + if (err) + goto done; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewString(ctx, "."), + JS_PROP_C_W_E); + for (len = 1; FindNextFileA(h, &d); len++) { + JS_DefinePropertyValueUint32(ctx, obj, len, + JS_NewString(ctx, d.cFileName), + JS_PROP_C_W_E); + } + FindClose(h); +done: + return make_obj_error(ctx, obj, err); +#else + const char *path; + DIR *f; + struct dirent *d; + JSValue obj; + int err; + uint32_t len; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) { + JS_FreeCString(ctx, path); + return JS_EXCEPTION; + } + f = opendir(path); + if (!f) + err = errno; + else + err = 0; + JS_FreeCString(ctx, path); + if (!f) + goto done; + len = 0; + for(;;) { + errno = 0; + d = readdir(f); + if (!d) { + err = errno; + break; + } + JS_DefinePropertyValueUint32(ctx, obj, len++, + JS_NewString(ctx, d->d_name), + JS_PROP_C_W_E); + } + closedir(f); + done: + return make_obj_error(ctx, obj, err); +#endif +} + +#if !defined(_WIN32) +static int64_t timespec_to_ms(const struct timespec *tv) +{ + return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000); +} +#endif + +/* return [obj, errcode] */ +static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_lstat) +{ + const char *path; + int err, res; + struct stat st; + JSValue obj; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + res = stat(path, &st); +#else + if (is_lstat) + res = lstat(path, &st); + else + res = stat(path, &st); +#endif + err = (res < 0) ? errno : 0; + JS_FreeCString(ctx, path); + if (res < 0) { + obj = JS_NULL; + } else { + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JS_DefinePropertyValueStr(ctx, obj, "dev", + JS_NewInt64(ctx, st.st_dev), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ino", + JS_NewInt64(ctx, st.st_ino), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mode", + JS_NewInt32(ctx, st.st_mode), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "nlink", + JS_NewInt64(ctx, st.st_nlink), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "uid", + JS_NewInt64(ctx, st.st_uid), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "gid", + JS_NewInt64(ctx, st.st_gid), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "rdev", + JS_NewInt64(ctx, st.st_rdev), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "size", + JS_NewInt64(ctx, st.st_size), + JS_PROP_C_W_E); +#if !defined(_WIN32) + JS_DefinePropertyValueStr(ctx, obj, "blocks", + JS_NewInt64(ctx, st.st_blocks), + JS_PROP_C_W_E); +#endif +#if defined(_WIN32) + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, (int64_t)st.st_atime * 1000), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, (int64_t)st.st_mtime * 1000), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, (int64_t)st.st_ctime * 1000), + JS_PROP_C_W_E); +#elif defined(__APPLE__) + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_atimespec)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_mtimespec)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_ctimespec)), + JS_PROP_C_W_E); +#else + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_atim)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_mtim)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_ctim)), + JS_PROP_C_W_E); +#endif + } + return make_obj_error(ctx, obj, err); +} + +#if !defined(_WIN32) +static void ms_to_timeval(struct timeval *tv, uint64_t v) +{ + tv->tv_sec = v / 1000; + tv->tv_usec = (v % 1000) * 1000; +} +#endif + +static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + int64_t atime, mtime; + int ret; + + if (JS_ToInt64(ctx, &atime, argv[1])) + return JS_EXCEPTION; + if (JS_ToInt64(ctx, &mtime, argv[2])) + return JS_EXCEPTION; + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + { + struct _utimbuf times; + times.actime = atime / 1000; + times.modtime = mtime / 1000; + ret = js_get_errno(_utime(path, ×)); + } +#else + { + struct timeval times[2]; + ms_to_timeval(×[0], atime); + ms_to_timeval(×[1], mtime); + ret = js_get_errno(utimes(path, times)); + } +#endif + JS_FreeCString(ctx, path); + return JS_NewInt32(ctx, ret); +} + +/* sleep(delay_ms) */ +static JSValue js_os_sleep(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int64_t delay; + int ret; + + if (JS_ToInt64(ctx, &delay, argv[0])) + return JS_EXCEPTION; + if (delay < 0) + delay = 0; +#if defined(_WIN32) + { + if (delay > INT32_MAX) + delay = INT32_MAX; + Sleep(delay); + ret = 0; + } +#else + { + struct timespec ts; + + ts.tv_sec = delay / 1000; + ts.tv_nsec = (delay % 1000) * 1000000; + ret = js_get_errno(nanosleep(&ts, NULL)); + } +#endif + return JS_NewInt32(ctx, ret); +} + +#if defined(_WIN32) +static char *realpath(const char *path, char *buf) +{ + if (!_fullpath(buf, path, JS__PATH_MAX)) { + errno = ENOENT; + return NULL; + } else { + return buf; + } +} +#endif + +#if !defined(__wasi__) +/* return [path, errorcode] */ +static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + char buf[JS__PATH_MAX], *res; + int err; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + res = realpath(path, buf); + JS_FreeCString(ctx, path); + if (!res) { + buf[0] = '\0'; + err = errno; + } else { + err = 0; + } + return make_string_error(ctx, buf, err); +} +#endif + +#if !defined(_WIN32) && !defined(__wasi__) && !(defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH)) +static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *target, *linkpath; + int err; + + target = JS_ToCString(ctx, argv[0]); + if (!target) + return JS_EXCEPTION; + linkpath = JS_ToCString(ctx, argv[1]); + if (!linkpath) { + JS_FreeCString(ctx, target); + return JS_EXCEPTION; + } + err = js_get_errno(symlink(target, linkpath)); + JS_FreeCString(ctx, target); + JS_FreeCString(ctx, linkpath); + return JS_NewInt32(ctx, err); +} + +/* return [path, errorcode] */ +static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + char buf[JS__PATH_MAX]; + int err; + ssize_t res; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + res = readlink(path, buf, sizeof(buf) - 1); + if (res < 0) { + buf[0] = '\0'; + err = errno; + } else { + buf[res] = '\0'; + err = 0; + } + JS_FreeCString(ctx, path); + return make_string_error(ctx, buf, err); +} + +static char **build_envp(JSContext *ctx, JSValue obj) +{ + uint32_t len, i; + JSPropertyEnum *tab; + char **envp, *pair; + const char *key, *str; + JSValue val; + size_t key_len, str_len; + + if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) + return NULL; + envp = js_mallocz(ctx, sizeof(envp[0]) * ((size_t)len + 1)); + if (!envp) + goto fail; + for(i = 0; i < len; i++) { + val = JS_GetProperty(ctx, obj, tab[i].atom); + if (JS_IsException(val)) + goto fail; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + goto fail; + key = JS_AtomToCString(ctx, tab[i].atom); + if (!key) { + JS_FreeCString(ctx, str); + goto fail; + } + key_len = strlen(key); + str_len = strlen(str); + pair = js_malloc(ctx, key_len + str_len + 2); + if (!pair) { + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + goto fail; + } + memcpy(pair, key, key_len); + pair[key_len] = '='; + memcpy(pair + key_len + 1, str, str_len); + pair[key_len + 1 + str_len] = '\0'; + envp[i] = pair; + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + } + done: + for(i = 0; i < len; i++) + JS_FreeAtom(ctx, tab[i].atom); + js_free(ctx, tab); + return envp; + fail: + if (envp) { + for(i = 0; i < len; i++) + js_free(ctx, envp[i]); + js_free(ctx, envp); + envp = NULL; + } + goto done; +} + +/* execvpe is not available on non GNU systems */ +static int my_execvpe(const char *filename, char **argv, char **envp) +{ + char *path, *p, *p_next, *p1; + char buf[JS__PATH_MAX]; + size_t filename_len, path_len; + bool eacces_error; + + filename_len = strlen(filename); + if (filename_len == 0) { + errno = ENOENT; + return -1; + } + if (strchr(filename, '/')) + return execve(filename, argv, envp); + + path = getenv("PATH"); + if (!path) + path = (char *)"/bin:/usr/bin"; + eacces_error = false; + p = path; + for(p = path; p != NULL; p = p_next) { + p1 = strchr(p, ':'); + if (!p1) { + p_next = NULL; + path_len = strlen(p); + } else { + p_next = p1 + 1; + path_len = p1 - p; + } + /* path too long */ + if ((path_len + 1 + filename_len + 1) > JS__PATH_MAX) + continue; + memcpy(buf, p, path_len); + buf[path_len] = '/'; + memcpy(buf + path_len + 1, filename, filename_len); + buf[path_len + 1 + filename_len] = '\0'; + + execve(buf, argv, envp); + + switch(errno) { + case EACCES: + eacces_error = true; + break; + case ENOENT: + case ENOTDIR: + break; + default: + return -1; + } + } + if (eacces_error) + errno = EACCES; + return -1; +} + +static void (*js_os_exec_closefrom)(int); + +#if !defined(EMSCRIPTEN) && !defined(__wasi__) + +static js_once_t js_os_exec_once = JS_ONCE_INIT; + +static void js_os_exec_once_init(void) +{ + *(void **) (&js_os_exec_closefrom) = dlsym(RTLD_DEFAULT, "closefrom"); +} + +#endif + +/* exec(args[, options]) -> exitcode */ +static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst options, args = argv[0]; + JSValue val, ret_val; + const char **exec_argv, *file = NULL, *str, *cwd = NULL; + char **envp = environ; + uint32_t exec_argc, i; + int ret, pid, status; + bool block_flag = true, use_path = true; + static const char *std_name[3] = { "stdin", "stdout", "stderr" }; + int std_fds[3]; + uint32_t uid = -1, gid = -1; + int ngroups = -1; + gid_t groups[64]; + + val = JS_GetPropertyStr(ctx, args, "length"); + if (JS_IsException(val)) + return JS_EXCEPTION; + ret = JS_ToUint32(ctx, &exec_argc, val); + JS_FreeValue(ctx, val); + if (ret) + return JS_EXCEPTION; + /* arbitrary limit to avoid overflow */ + if (exec_argc < 1 || exec_argc > 65535) { + return JS_ThrowTypeError(ctx, "invalid number of arguments"); + } + exec_argv = js_mallocz(ctx, sizeof(exec_argv[0]) * (exec_argc + 1)); + if (!exec_argv) + return JS_EXCEPTION; + for(i = 0; i < exec_argc; i++) { + val = JS_GetPropertyUint32(ctx, args, i); + if (JS_IsException(val)) + goto exception; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + goto exception; + exec_argv[i] = str; + } + exec_argv[exec_argc] = NULL; + + for(i = 0; i < 3; i++) + std_fds[i] = i; + + /* get the options, if any */ + if (argc >= 2) { + options = argv[1]; + + if (get_bool_option(ctx, &block_flag, options, "block")) + goto exception; + if (get_bool_option(ctx, &use_path, options, "usePath")) + goto exception; + + val = JS_GetPropertyStr(ctx, options, "file"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + file = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!file) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "cwd"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + cwd = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!cwd) + goto exception; + } + + /* stdin/stdout/stderr handles */ + for(i = 0; i < 3; i++) { + val = JS_GetPropertyStr(ctx, options, std_name[i]); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + int fd; + ret = JS_ToInt32(ctx, &fd, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + std_fds[i] = fd; + } + } + + val = JS_GetPropertyStr(ctx, options, "env"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + envp = build_envp(ctx, val); + JS_FreeValue(ctx, val); + if (!envp) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "uid"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + ret = JS_ToUint32(ctx, &uid, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "gid"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + ret = JS_ToUint32(ctx, &gid, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "groups"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + int64_t idx, len; + JSValue prop; + uint32_t id; + ngroups = 0; + if (JS_GetLength(ctx, val, &len)) { + JS_FreeValue(ctx, val); + goto exception; + } + for (idx = 0; idx < len; idx++) { + prop = JS_GetPropertyInt64(ctx, val, idx); + if (JS_IsException(prop)) + break; + if (JS_IsUndefined(prop)) + continue; + ret = JS_ToUint32(ctx, &id, prop); + JS_FreeValue(ctx, prop); + if (ret) + break; + if (ngroups == countof(groups)) { + JS_ThrowRangeError(ctx, "too many groups"); + break; + } + groups[ngroups++] = id; + } + JS_FreeValue(ctx, val); + if (idx < len) + goto exception; + } + + } + +#if !defined(EMSCRIPTEN) && !defined(__wasi__) + // should happen pre-fork because it calls dlsym() + // and that's not an async-signal-safe function + js_once(&js_os_exec_once, js_os_exec_once_init); +#endif + + pid = fork(); + if (pid < 0) { + JS_ThrowTypeError(ctx, "fork error"); + goto exception; + } + if (pid == 0) { + /* child */ + /* remap the stdin/stdout/stderr handles if necessary */ + for(i = 0; i < 3; i++) { + if (std_fds[i] != i) { + if (dup2(std_fds[i], i) < 0) + _exit(127); + } + } + + if (js_os_exec_closefrom) { + js_os_exec_closefrom(3); + } else { + int fd_max = sysconf(_SC_OPEN_MAX); + for(i = 3; i < fd_max; i++) + close(i); + } + + if (cwd) { + if (chdir(cwd) < 0) + _exit(127); + } + if (ngroups != -1) { + if (setgroups(ngroups, groups) < 0) + _exit(127); + } + if (uid != -1) { + if (setuid(uid) < 0) + _exit(127); + } + if (gid != -1) { + if (setgid(gid) < 0) + _exit(127); + } + + if (!file) + file = exec_argv[0]; + if (use_path) + ret = my_execvpe(file, (char **)exec_argv, envp); + else + ret = execve(file, (char **)exec_argv, envp); + _exit(127); + } + /* parent */ + if (block_flag) { + for(;;) { + ret = waitpid(pid, &status, 0); + if (ret == pid) { + if (WIFEXITED(status)) { + ret = WEXITSTATUS(status); + break; + } else if (WIFSIGNALED(status)) { + ret = -WTERMSIG(status); + break; + } + } + } + } else { + ret = pid; + } + ret_val = JS_NewInt32(ctx, ret); + done: + JS_FreeCString(ctx, file); + JS_FreeCString(ctx, cwd); + for(i = 0; i < exec_argc; i++) + JS_FreeCString(ctx, exec_argv[i]); + js_free(ctx, exec_argv); + if (envp != environ) { + char **p; + p = envp; + while (*p != NULL) { + js_free(ctx, *p); + p++; + } + js_free(ctx, envp); + } + return ret_val; + exception: + ret_val = JS_EXCEPTION; + goto done; +} + +/* getpid() -> pid */ +static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewInt32(ctx, getpid()); +} + +/* waitpid(pid, block) -> [pid, status] */ +static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid, status, options, ret; + JSValue obj; + + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &options, argv[1])) + return JS_EXCEPTION; + + ret = waitpid(pid, &status, options); + if (ret < 0) { + ret = -errno; + status = 0; + } + + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ret), + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, status), + JS_PROP_C_W_E); + return obj; +} + +/* pipe() -> [read_fd, write_fd] or null if error */ +static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pipe_fds[2], ret; + JSValue obj; + + ret = pipe(pipe_fds); + if (ret < 0) + return JS_NULL; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), + JS_PROP_C_W_E); + return obj; +} + +/* kill(pid, sig) */ +static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid, sig, ret; + + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &sig, argv[1])) + return JS_EXCEPTION; + ret = js_get_errno(kill(pid, sig)); + return JS_NewInt32(ctx, ret); +} + +/* dup(fd) */ +static JSValue js_os_dup(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, ret; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + ret = js_get_errno(dup(fd)); + return JS_NewInt32(ctx, ret); +} + +/* dup2(fd) */ +static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, fd2, ret; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &fd2, argv[1])) + return JS_EXCEPTION; + ret = js_get_errno(dup2(fd, fd2)); + return JS_NewInt32(ctx, ret); +} + +#endif /* !_WIN32 */ + +#ifdef USE_WORKER + +/* Worker */ + +typedef struct { + JSWorkerMessagePipe *recv_pipe; + JSWorkerMessagePipe *send_pipe; + JSWorkerMessageHandler *msg_handler; +} JSWorkerData; + +typedef struct { + char *filename; /* module filename */ + char *basename; /* module base name */ + JSWorkerMessagePipe *recv_pipe, *send_pipe; +} WorkerFuncArgs; + +typedef struct { + int ref_count; + uint64_t buf[]; +} JSSABHeader; + +static JSContext *(*js_worker_new_context_func)(JSRuntime *rt); + +static int atomic_add_int(int *ptr, int v) +{ + return atomic_fetch_add((_Atomic uint32_t*)ptr, v) + v; +} + +/* shared array buffer allocator */ +static void *js_sab_alloc(void *opaque, size_t size) +{ + JSSABHeader *sab; + sab = malloc(sizeof(JSSABHeader) + size); + if (!sab) + return NULL; + sab->ref_count = 1; + return sab->buf; +} + +static void js_sab_free(void *opaque, void *ptr) +{ + JSSABHeader *sab; + int ref_count; + sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); + ref_count = atomic_add_int(&sab->ref_count, -1); + assert(ref_count >= 0); + if (ref_count == 0) { + free(sab); + } +} + +static void js_sab_dup(void *opaque, void *ptr) +{ + JSSABHeader *sab; + sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); + atomic_add_int(&sab->ref_count, 1); +} + +static JSWorkerMessagePipe *js_new_message_pipe(void) +{ + JSWorkerMessagePipe *ps; + + ps = malloc(sizeof(*ps)); + if (!ps) + return NULL; + if (js_waker_init(&ps->waker)) { + free(ps); + return NULL; + } + ps->ref_count = 1; + init_list_head(&ps->msg_queue); + js_mutex_init(&ps->mutex); + return ps; +} + +static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps) +{ + atomic_add_int(&ps->ref_count, 1); + return ps; +} + +static void js_free_message(JSWorkerMessage *msg) +{ + size_t i; + /* free the SAB */ + for(i = 0; i < msg->sab_tab_len; i++) { + js_sab_free(NULL, msg->sab_tab[i]); + } + free(msg->sab_tab); + free(msg->data); + free(msg); +} + +static void js_free_message_pipe(JSWorkerMessagePipe *ps) +{ + struct list_head *el, *el1; + JSWorkerMessage *msg; + int ref_count; + + if (!ps) + return; + + ref_count = atomic_add_int(&ps->ref_count, -1); + assert(ref_count >= 0); + if (ref_count == 0) { + list_for_each_safe(el, el1, &ps->msg_queue) { + msg = list_entry(el, JSWorkerMessage, link); + js_free_message(msg); + } + js_mutex_destroy(&ps->mutex); + js_waker_close(&ps->waker); + free(ps); + } +} + +static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port) +{ + if (port) { + js_free_message_pipe(port->recv_pipe); + JS_FreeValueRT(rt, port->on_message_func); + list_del(&port->link); + js_free_rt(rt, port); + } +} + +static void js_worker_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSThreadState *ts = js_get_thread_state(rt); + JSWorkerData *worker = JS_GetOpaque(val, ts->worker_class_id); + if (worker) { + js_free_message_pipe(worker->recv_pipe); + js_free_message_pipe(worker->send_pipe); + js_free_port(rt, worker->msg_handler); + js_free_rt(rt, worker); + } +} + +static JSClassDef js_worker_class = { + "Worker", + .finalizer = js_worker_finalizer, +}; + +static void worker_func(void *opaque) +{ + WorkerFuncArgs *args = opaque; + JSRuntime *rt; + JSThreadState *ts; + JSContext *ctx; + JSValue val; + + rt = JS_NewRuntime(); + if (rt == NULL) { + fprintf(stderr, "JS_NewRuntime failure"); + exit(1); + } + js_std_init_handlers(rt); + + JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); + + /* set the pipe to communicate with the parent */ + ts = js_get_thread_state(rt); + ts->recv_pipe = args->recv_pipe; + ts->send_pipe = args->send_pipe; + + /* function pointer to avoid linking the whole JS_NewContext() if + not needed */ + ctx = js_worker_new_context_func(rt); + if (ctx == NULL) { + fprintf(stderr, "JS_NewContext failure"); + } + + JS_SetCanBlock(rt, true); + + js_std_add_helpers(ctx, -1, NULL); + + val = JS_LoadModule(ctx, args->basename, args->filename); + free(args->filename); + free(args->basename); + free(args); + val = js_std_await(ctx, val); + if (JS_IsException(val)) + js_std_dump_error(ctx); + JS_FreeValue(ctx, val); + + js_std_loop(ctx); + + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + +static JSValue js_worker_ctor_internal(JSContext *ctx, JSValueConst new_target, + JSWorkerMessagePipe *recv_pipe, + JSWorkerMessagePipe *send_pipe) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSValue obj = JS_UNDEFINED, proto; + JSWorkerData *s; + + /* create the object */ + if (JS_IsUndefined(new_target)) { + proto = JS_GetClassProto(ctx, ts->worker_class_id); + } else { + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) + goto fail; + } + obj = JS_NewObjectProtoClass(ctx, proto, ts->worker_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) + goto fail; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + goto fail; + s->recv_pipe = js_dup_message_pipe(recv_pipe); + s->send_pipe = js_dup_message_pipe(send_pipe); + + JS_SetOpaque(obj, s); + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + WorkerFuncArgs *args = NULL; + js_thread_t thr; + JSValue obj = JS_UNDEFINED; + int ret; + const char *filename = NULL, *basename; + JSAtom basename_atom; + + /* XXX: in order to avoid problems with resource liberation, we + don't support creating workers inside workers */ + if (!is_main_thread(rt)) + return JS_ThrowTypeError(ctx, "cannot create a worker inside a worker"); + + /* base name, assuming the calling function is a normal JS + function */ + basename_atom = JS_GetScriptOrModuleName(ctx, 1); + if (basename_atom == JS_ATOM_NULL) { + return JS_ThrowTypeError(ctx, "could not determine calling script or module name"); + } + basename = JS_AtomToCString(ctx, basename_atom); + JS_FreeAtom(ctx, basename_atom); + if (!basename) + goto fail; + + /* module name */ + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; + + args = malloc(sizeof(*args)); + if (!args) + goto oom_fail; + memset(args, 0, sizeof(*args)); + args->filename = strdup(filename); + args->basename = strdup(basename); + + /* ports */ + args->recv_pipe = js_new_message_pipe(); + if (!args->recv_pipe) + goto oom_fail; + args->send_pipe = js_new_message_pipe(); + if (!args->send_pipe) + goto oom_fail; + + obj = js_worker_ctor_internal(ctx, new_target, + args->send_pipe, args->recv_pipe); + if (JS_IsException(obj)) + goto fail; + + ret = js_thread_create(&thr, worker_func, args, JS_THREAD_CREATE_DETACHED); + if (ret != 0) { + JS_ThrowTypeError(ctx, "could not create worker"); + goto fail; + } + JS_FreeCString(ctx, basename); + JS_FreeCString(ctx, filename); + return obj; + oom_fail: + JS_ThrowOutOfMemory(ctx); + fail: + JS_FreeCString(ctx, basename); + JS_FreeCString(ctx, filename); + if (args) { + free(args->filename); + free(args->basename); + js_free_message_pipe(args->recv_pipe); + js_free_message_pipe(args->send_pipe); + free(args); + } + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, ts->worker_class_id); + JSWorkerMessagePipe *ps; + size_t data_len, i; + uint8_t *data; + JSWorkerMessage *msg; + JSSABTab sab_tab; + + if (!worker) + return JS_EXCEPTION; + + data = JS_WriteObject2(ctx, &data_len, argv[0], + JS_WRITE_OBJ_SAB | JS_WRITE_OBJ_REFERENCE, + &sab_tab); + if (!data) + return JS_EXCEPTION; + + msg = malloc(sizeof(*msg)); + if (!msg) + goto fail; + msg->data = NULL; + msg->sab_tab = NULL; + + /* must reallocate because the allocator may be different */ + msg->data = malloc(data_len); + if (!msg->data) + goto fail; + memcpy(msg->data, data, data_len); + msg->data_len = data_len; + + if (sab_tab.len > 0) { + msg->sab_tab = malloc(sizeof(msg->sab_tab[0]) * sab_tab.len); + if (!msg->sab_tab) + goto fail; + memcpy(msg->sab_tab, sab_tab.tab, sizeof(msg->sab_tab[0]) * sab_tab.len); + } + msg->sab_tab_len = sab_tab.len; + + js_free(ctx, data); + js_free(ctx, sab_tab.tab); + + /* increment the SAB reference counts */ + for(i = 0; i < msg->sab_tab_len; i++) { + js_sab_dup(NULL, msg->sab_tab[i]); + } + + ps = worker->send_pipe; + js_mutex_lock(&ps->mutex); + /* indicate that data is present */ + if (list_empty(&ps->msg_queue)) + js_waker_signal(&ps->waker); + list_add_tail(&msg->link, &ps->msg_queue); + js_mutex_unlock(&ps->mutex); + return JS_UNDEFINED; + fail: + if (msg) { + free(msg->data); + free(msg->sab_tab); + free(msg); + } + js_free(ctx, data); + js_free(ctx, sab_tab.tab); + return JS_EXCEPTION; + +} + +static JSValue js_worker_set_onmessage(JSContext *ctx, JSValueConst this_val, + JSValueConst func) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, ts->worker_class_id); + JSWorkerMessageHandler *port; + + if (!worker) + return JS_EXCEPTION; + + port = worker->msg_handler; + if (JS_IsNull(func)) { + if (port) { + js_free_port(rt, port); + worker->msg_handler = NULL; + } + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + if (!port) { + port = js_mallocz(ctx, sizeof(*port)); + if (!port) + return JS_EXCEPTION; + port->recv_pipe = js_dup_message_pipe(worker->recv_pipe); + port->on_message_func = JS_NULL; + list_add_tail(&port->link, &ts->port_list); + worker->msg_handler = port; + } + JS_FreeValue(ctx, port->on_message_func); + port->on_message_func = JS_DupValue(ctx, func); + } + return JS_UNDEFINED; +} + +static JSValue js_worker_get_onmessage(JSContext *ctx, JSValueConst this_val) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, ts->worker_class_id); + JSWorkerMessageHandler *port; + if (!worker) + return JS_EXCEPTION; + port = worker->msg_handler; + if (port) { + return JS_DupValue(ctx, port->on_message_func); + } else { + return JS_NULL; + } +} + +static const JSCFunctionListEntry js_worker_proto_funcs[] = { + JS_CFUNC_DEF("postMessage", 1, js_worker_postMessage ), + JS_CGETSET_DEF("onmessage", js_worker_get_onmessage, js_worker_set_onmessage ), +}; + +#endif /* USE_WORKER */ + +void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) +{ +#ifdef USE_WORKER + js_worker_new_context_func = func; +#endif +} + +#if defined(_WIN32) +#define OS_PLATFORM "win32" +#elif defined(__APPLE__) +#define OS_PLATFORM "darwin" +#elif defined(EMSCRIPTEN) +#define OS_PLATFORM "js" +#elif defined(__CYGWIN__) +#define OS_PLATFORM "cygwin" +#elif defined(__linux__) +#define OS_PLATFORM "linux" +#elif defined(__OpenBSD__) +#define OS_PLATFORM "openbsd" +#elif defined(__NetBSD__) +#define OS_PLATFORM "netbsd" +#elif defined(__FreeBSD__) +#define OS_PLATFORM "freebsd" +#elif defined(__wasi__) +#define OS_PLATFORM "wasi" +#elif defined(__GNU__) +#define OS_PLATFORM "hurd" +#else +#define OS_PLATFORM "unknown" +#endif + +#define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) + +static const JSCFunctionListEntry js_os_funcs[] = { + JS_CFUNC_DEF("open", 2, js_os_open ), + OS_FLAG(O_RDONLY), + OS_FLAG(O_WRONLY), + OS_FLAG(O_RDWR), + OS_FLAG(O_APPEND), + OS_FLAG(O_CREAT), + OS_FLAG(O_EXCL), + OS_FLAG(O_TRUNC), +#if defined(_WIN32) + OS_FLAG(O_BINARY), + OS_FLAG(O_TEXT), +#endif + JS_CFUNC_DEF("close", 1, js_os_close ), + JS_CFUNC_DEF("seek", 3, js_os_seek ), + JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ), + JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ), + JS_CFUNC_DEF("isatty", 1, js_os_isatty ), +#if !defined(__wasi__) + JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ), + JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ), +#endif + JS_CFUNC_DEF("remove", 1, js_os_remove ), + JS_CFUNC_DEF("rename", 2, js_os_rename ), + JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ), + JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ), + JS_CFUNC_DEF("signal", 2, js_os_signal ), + OS_FLAG(SIGINT), + OS_FLAG(SIGABRT), + OS_FLAG(SIGFPE), + OS_FLAG(SIGILL), + OS_FLAG(SIGSEGV), + OS_FLAG(SIGTERM), +#if !defined(_WIN32) && !defined(__wasi__) + OS_FLAG(SIGQUIT), + OS_FLAG(SIGPIPE), + OS_FLAG(SIGALRM), + OS_FLAG(SIGUSR1), + OS_FLAG(SIGUSR2), + OS_FLAG(SIGCHLD), + OS_FLAG(SIGCONT), + OS_FLAG(SIGSTOP), + OS_FLAG(SIGTSTP), + OS_FLAG(SIGTTIN), + OS_FLAG(SIGTTOU), + JS_CFUNC_DEF("cputime", 0, js_os_cputime ), +#endif + JS_CFUNC_DEF("exePath", 0, js_os_exepath ), + JS_CFUNC_DEF("now", 0, js_os_now ), + JS_CFUNC_MAGIC_DEF("setTimeout", 2, js_os_setTimeout, 0 ), + JS_CFUNC_MAGIC_DEF("setInterval", 2, js_os_setTimeout, 1 ), + // per spec: both functions can cancel timeouts and intervals + JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), + JS_CFUNC_DEF("clearInterval", 1, js_os_clearTimeout ), + JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ), + JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), + JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), + JS_CFUNC_DEF("chdir", 0, js_os_chdir ), + JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), + JS_CFUNC_DEF("readdir", 1, js_os_readdir ), + /* st_mode constants */ + OS_FLAG(S_IFMT), + OS_FLAG(S_IFIFO), + OS_FLAG(S_IFCHR), + OS_FLAG(S_IFDIR), + OS_FLAG(S_IFBLK), + OS_FLAG(S_IFREG), +#if !defined(_WIN32) + OS_FLAG(S_IFSOCK), + OS_FLAG(S_IFLNK), + OS_FLAG(S_ISGID), + OS_FLAG(S_ISUID), +#endif + JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), + JS_CFUNC_DEF("utimes", 3, js_os_utimes ), + JS_CFUNC_DEF("sleep", 1, js_os_sleep ), +#if !defined(__wasi__) + JS_CFUNC_DEF("realpath", 1, js_os_realpath ), +#endif +#if !defined(_WIN32) && !defined(__wasi__) && !(defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH)) + JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), + JS_CFUNC_DEF("symlink", 2, js_os_symlink ), + JS_CFUNC_DEF("readlink", 1, js_os_readlink ), + JS_CFUNC_DEF("exec", 1, js_os_exec ), + JS_CFUNC_DEF("getpid", 0, js_os_getpid ), + JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), + OS_FLAG(WNOHANG), + JS_CFUNC_DEF("pipe", 0, js_os_pipe ), + JS_CFUNC_DEF("kill", 2, js_os_kill ), + JS_CFUNC_DEF("dup", 1, js_os_dup ), + JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), +#endif +}; + +static int js_os_init(JSContext *ctx, JSModuleDef *m) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + + ts->can_js_os_poll = true; + +#ifdef USE_WORKER + { + JSValue proto, obj; + /* Worker class */ + JS_NewClassID(rt, &ts->worker_class_id); + JS_NewClass(rt, ts->worker_class_id, &js_worker_class); + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_worker_proto_funcs, countof(js_worker_proto_funcs)); + + obj = JS_NewCFunction2(ctx, js_worker_ctor, "Worker", 1, + JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, obj, proto); + + JS_SetClassProto(ctx, ts->worker_class_id, proto); + + /* set 'Worker.parent' if necessary */ + if (ts->recv_pipe && ts->send_pipe) { + JS_DefinePropertyValueStr(ctx, obj, "parent", + js_worker_ctor_internal(ctx, JS_UNDEFINED, ts->recv_pipe, ts->send_pipe), + JS_PROP_C_W_E); + } + + JS_SetModuleExport(ctx, m, "Worker", obj); + } +#endif /* USE_WORKER */ + + return JS_SetModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); +} + +JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_os_init); + if (!m) + return NULL; + JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); +#ifdef USE_WORKER + JS_AddModuleExport(ctx, m, "Worker"); +#endif + return m; +} + +/**********************************************************/ + +static JSValue js_print(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ +#ifdef _WIN32 + HANDLE handle; + DWORD mode; +#endif + const char *s; + JSValueConst v; + DynBuf b; + int i; + + dbuf_init(&b); + for(i = 0; i < argc; i++) { + v = argv[i]; + s = JS_ToCString(ctx, v); + if (!s && JS_IsObject(v)) { + JS_FreeValue(ctx, JS_GetException(ctx)); + JSValue t = JS_ToObjectString(ctx, v); + s = JS_ToCString(ctx, t); + JS_FreeValue(ctx, t); + } + if (s) { + dbuf_printf(&b, "%s%s", &" "[!i], s); + JS_FreeCString(ctx, s); + } else { + dbuf_printf(&b, "%s", &" "[!i]); + JS_FreeValue(ctx, JS_GetException(ctx)); + } + } + dbuf_putc(&b, '\n'); +#ifdef _WIN32 + // use WriteConsoleA with CP_UTF8 for better Unicode handling vis-a-vis + // the mangling that happens when going through msvcrt's stdio layer, + // *except* when stdout is redirected to something that is not a console + handle = (HANDLE)_get_osfhandle(/*STDOUT_FILENO*/1); // don't CloseHandle + if (GetFileType(handle) != FILE_TYPE_CHAR) + goto fallback; + if (!GetConsoleMode(handle, &mode)) + goto fallback; + handle = GetStdHandle(STD_OUTPUT_HANDLE); + if (handle == INVALID_HANDLE_VALUE) + goto fallback; + mode = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); + WriteConsoleA(handle, b.buf, b.size, NULL, NULL); + SetConsoleOutputCP(mode); + FlushFileBuffers(handle); + goto done; +fallback: +#endif + fwrite(b.buf, 1, b.size, stdout); + fflush(stdout); + goto done; // avoid unused label warning +done: + dbuf_free(&b); + return JS_UNDEFINED; +} + +void js_std_add_helpers(JSContext *ctx, int argc, char **argv) +{ + JSValue global_obj, console, args; + int i; + + /* XXX: should these global definitions be enumerable? */ + global_obj = JS_GetGlobalObject(ctx); + + console = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, console, "log", + JS_NewCFunction(ctx, js_print, "log", 1)); + JS_SetPropertyStr(ctx, global_obj, "console", console); + + /* same methods as the mozilla JS shell */ + if (argc >= 0) { + args = JS_NewArray(ctx); + for(i = 0; i < argc; i++) { + JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i])); + } + JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args); + } + + JS_SetPropertyStr(ctx, global_obj, "print", + JS_NewCFunction(ctx, js_print, "print", 1)); + + JS_FreeValue(ctx, global_obj); +} + +static void js_std_finalize(JSRuntime *rt, void *arg) +{ + JSThreadState *ts = arg; + js_set_thread_state(rt, NULL); + js_free_rt(rt, ts); +} + +void js_std_init_handlers(JSRuntime *rt) +{ + JSThreadState *ts; + + ts = js_mallocz_rt(rt, sizeof(*ts)); + if (!ts) { + fprintf(stderr, "Could not allocate memory for the worker"); + exit(1); + } + init_list_head(&ts->os_rw_handlers); + init_list_head(&ts->os_signal_handlers); + init_list_head(&ts->os_timers); + init_list_head(&ts->port_list); + init_list_head(&ts->rejected_promise_list); + + ts->next_timer_id = 1; + + js_set_thread_state(rt, ts); + JS_AddRuntimeFinalizer(rt, js_std_finalize, ts); + +#ifdef USE_WORKER + /* set the SharedArrayBuffer memory handlers */ + { + JSSharedArrayBufferFunctions sf; + memset(&sf, 0, sizeof(sf)); + sf.sab_alloc = js_sab_alloc; + sf.sab_free = js_sab_free; + sf.sab_dup = js_sab_dup; + JS_SetSharedArrayBufferFunctions(rt, &sf); + } +#endif +} + +static void free_rp(JSRuntime *rt, JSRejectedPromiseEntry *rp) +{ + list_del(&rp->link); + JS_FreeValueRT(rt, rp->promise); + JS_FreeValueRT(rt, rp->reason); + js_free_rt(rt, rp); +} + +void js_std_free_handlers(JSRuntime *rt) +{ + JSThreadState *ts = js_get_thread_state(rt); + struct list_head *el, *el1; + + list_for_each_safe(el, el1, &ts->os_rw_handlers) { + JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); + free_rw_handler(rt, rh); + } + + list_for_each_safe(el, el1, &ts->os_signal_handlers) { + JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link); + free_sh(rt, sh); + } + + list_for_each_safe(el, el1, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + free_timer(rt, th); + } + + list_for_each_safe(el, el1, &ts->rejected_promise_list) { + JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); + free_rp(rt, rp); + } + +#ifdef USE_WORKER + /* XXX: free port_list ? */ + js_free_message_pipe(ts->recv_pipe); + js_free_message_pipe(ts->send_pipe); +#endif +} + +static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) +{ + const char *str; + + str = JS_ToCString(ctx, val); + if (str) { + fprintf(f, "%s\n", str); + JS_FreeCString(ctx, str); + } else { + fprintf(f, "[exception]\n"); + } +} + +static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) +{ + JSValue val; + bool is_error; + + is_error = JS_IsError(exception_val); + js_dump_obj(ctx, stderr, exception_val); + if (is_error) { + val = JS_GetPropertyStr(ctx, exception_val, "stack"); + } else { + js_std_cmd(/*ErrorBackTrace*/2, ctx, &val); + } + if (!JS_IsUndefined(val)) { + js_dump_obj(ctx, stderr, val); + JS_FreeValue(ctx, val); + } +} + +void js_std_dump_error(JSContext *ctx) +{ + JSValue exception_val; + + exception_val = JS_GetException(ctx); + js_std_dump_error1(ctx, exception_val); + JS_FreeValue(ctx, exception_val); +} + +static JSRejectedPromiseEntry *find_rejected_promise(JSContext *ctx, JSThreadState *ts, + JSValueConst promise) +{ + struct list_head *el; + + list_for_each(el, &ts->rejected_promise_list) { + JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); + if (JS_IsSameValue(ctx, rp->promise, promise)) + return rp; + } + return NULL; +} + +void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, + JSValueConst reason, + bool is_handled, void *opaque) +{ + + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSRejectedPromiseEntry *rp = find_rejected_promise(ctx, ts, promise); + + if (is_handled) { + /* the rejection is handled, so the entry can be removed if present */ + if (rp) + free_rp(rt, rp); + } else { + /* add a new entry if needed */ + if (!rp) { + rp = js_malloc_rt(rt, sizeof(*rp)); + if (rp) { + rp->promise = JS_DupValue(ctx, promise); + rp->reason = JS_DupValue(ctx, reason); + list_add_tail(&rp->link, &ts->rejected_promise_list); + } + } + } +} + +/* check if there are pending promise rejections. It must be done + asynchrously in case a rejected promise is handled later. Currently + we do it once the application is about to sleep. It could be done + more often if needed. */ +static void js_std_promise_rejection_check(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + struct list_head *el; + + if (unlikely(!list_empty(&ts->rejected_promise_list))) { + list_for_each(el, &ts->rejected_promise_list) { + JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); + fprintf(stderr, "Possibly unhandled promise rejection: "); + js_std_dump_error1(ctx, rp->reason); + fflush(stderr); + } + exit(1); + } +} + +/* main loop which calls the user JS callbacks */ +int js_std_loop(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSContext *ctx1; + int err; + + for(;;) { + /* execute the pending jobs */ + for(;;) { + err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); + if (err <= 0) { + if (err < 0) + goto done; + break; + } + } + + js_std_promise_rejection_check(ctx); + + if (!ts->can_js_os_poll || js_os_poll(ctx)) + break; + } +done: + return JS_HasException(ctx); +} + +/* Wait for a promise and execute pending jobs while waiting for + it. Return the promise result or JS_EXCEPTION in case of promise + rejection. */ +JSValue js_std_await(JSContext *ctx, JSValue obj) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSValue ret; + int state; + + for(;;) { + state = JS_PromiseState(ctx, obj); + if (state == JS_PROMISE_FULFILLED) { + ret = JS_PromiseResult(ctx, obj); + JS_FreeValue(ctx, obj); + break; + } else if (state == JS_PROMISE_REJECTED) { + ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj)); + JS_FreeValue(ctx, obj); + break; + } else if (state == JS_PROMISE_PENDING) { + JSContext *ctx1; + int err; + err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); + if (err < 0) { + js_std_dump_error(ctx1); + } + if (err == 0) + js_std_promise_rejection_check(ctx); + if (ts->can_js_os_poll) + js_os_poll(ctx); + } else { + /* not a promise */ + ret = obj; + break; + } + } + return ret; +} + +void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, + int load_only) +{ + JSValue obj, val; + obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE); + if (JS_IsException(obj)) + goto exception; + if (load_only) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { + if (js_module_set_import_meta(ctx, obj, false, false) < 0) + goto exception; + } + JS_FreeValue(ctx, obj); + } else { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { + if (JS_ResolveModule(ctx, obj) < 0) { + JS_FreeValue(ctx, obj); + goto exception; + } + if (js_module_set_import_meta(ctx, obj, false, true) < 0) + goto exception; + val = JS_EvalFunction(ctx, obj); + val = js_std_await(ctx, val); + } else { + val = JS_EvalFunction(ctx, obj); + } + if (JS_IsException(val)) { + exception: + js_std_dump_error(ctx); + exit(1); + } + JS_FreeValue(ctx, val); + } +} + +static JSValue js_bjson_read(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *buf; + uint64_t pos, len; + JSValue obj; + size_t size; + int flags; + + if (JS_ToIndex(ctx, &pos, argv[1])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &len, argv[2])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &flags, argv[3])) + return JS_EXCEPTION; + buf = JS_GetArrayBuffer(ctx, &size, argv[0]); + if (!buf) + return JS_EXCEPTION; + if (pos + len > size) + return JS_ThrowRangeError(ctx, "array buffer overflow"); + obj = JS_ReadObject(ctx, buf + pos, len, flags); + return obj; +} + +static JSValue js_bjson_write(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + size_t len; + uint8_t *buf; + JSValue array; + int flags; + + if (JS_ToInt32(ctx, &flags, argv[1])) + return JS_EXCEPTION; + buf = JS_WriteObject(ctx, &len, argv[0], flags); + if (!buf) + return JS_EXCEPTION; + array = JS_NewArrayBufferCopy(ctx, buf, len); + js_free(ctx, buf); + return array; +} + + +static const JSCFunctionListEntry js_bjson_funcs[] = { + JS_CFUNC_DEF("read", 4, js_bjson_read ), + JS_CFUNC_DEF("write", 2, js_bjson_write ), +#define DEF(x) JS_PROP_INT32_DEF(#x, JS_##x, JS_PROP_CONFIGURABLE) + DEF(READ_OBJ_BYTECODE), + DEF(READ_OBJ_REFERENCE), + DEF(READ_OBJ_SAB), + DEF(WRITE_OBJ_BYTECODE), + DEF(WRITE_OBJ_REFERENCE), + DEF(WRITE_OBJ_SAB), + DEF(WRITE_OBJ_STRIP_DEBUG), + DEF(WRITE_OBJ_STRIP_SOURCE), +#undef DEF +}; + +static int js_bjson_init(JSContext *ctx, JSModuleDef *m) +{ + return JS_SetModuleExportList(ctx, m, js_bjson_funcs, + countof(js_bjson_funcs)); +} + +JSModuleDef *js_init_module_bjson(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_bjson_init); + if (!m) + return NULL; + JS_AddModuleExportList(ctx, m, js_bjson_funcs, countof(js_bjson_funcs)); + return m; +} diff --git a/NativeScript/napi/android/quickjs/source_ng/quickjs-libc.h b/NativeScript/napi/android/quickjs/source_ng/quickjs-libc.h new file mode 100755 index 000000000..6af5a639d --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/quickjs-libc.h @@ -0,0 +1,76 @@ +/* + * QuickJS C library + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef QUICKJS_LIBC_H +#define QUICKJS_LIBC_H + +#include +#include +#include + +#include "quickjs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define JS_EXTERN __attribute__((visibility("default"))) +#else +#define JS_EXTERN /* nothing */ +#endif + +JS_EXTERN JSModuleDef *js_init_module_std(JSContext *ctx, + const char *module_name); +JS_EXTERN JSModuleDef *js_init_module_os(JSContext *ctx, + const char *module_name); +JS_EXTERN JSModuleDef *js_init_module_bjson(JSContext *ctx, + const char *module_name); +JS_EXTERN void js_std_add_helpers(JSContext *ctx, int argc, char **argv); +JS_EXTERN int js_std_loop(JSContext *ctx); +JS_EXTERN JSValue js_std_await(JSContext *ctx, JSValue obj); +JS_EXTERN void js_std_init_handlers(JSRuntime *rt); +JS_EXTERN void js_std_free_handlers(JSRuntime *rt); +JS_EXTERN void js_std_dump_error(JSContext *ctx); +JS_EXTERN uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, + const char *filename); +JS_EXTERN int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, + bool use_realpath, bool is_main); +JS_EXTERN JSModuleDef *js_module_loader(JSContext *ctx, + const char *module_name, void *opaque); +JS_EXTERN void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, + size_t buf_len, int flags); +JS_EXTERN void js_std_promise_rejection_tracker(JSContext *ctx, + JSValueConst promise, + JSValueConst reason, + bool is_handled, + void *opaque); +JS_EXTERN void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); + +#undef JS_EXTERN + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* QUICKJS_LIBC_H */ diff --git a/NativeScript/napi/android/quickjs/source_ng/quickjs-opcode.h b/NativeScript/napi/android/quickjs/source_ng/quickjs-opcode.h new file mode 100755 index 000000000..bd5be754e --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/quickjs-opcode.h @@ -0,0 +1,371 @@ +/* + * QuickJS opcode definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef FMT +FMT(none) +FMT(none_int) +FMT(none_loc) +FMT(none_arg) +FMT(none_var_ref) +FMT(u8) +FMT(i8) +FMT(loc8) +FMT(const8) +FMT(label8) +FMT(u16) +FMT(i16) +FMT(label16) +FMT(npop) +FMT(npopx) +FMT(npop_u16) +FMT(loc) +FMT(arg) +FMT(var_ref) +FMT(u32) +FMT(u32x2) +FMT(i32) +FMT(const) +FMT(label) +FMT(atom) +FMT(atom_u8) +FMT(atom_u16) +FMT(atom_label_u8) +FMT(atom_label_u16) +FMT(label_u16) +#undef FMT +#endif /* FMT */ + +#ifdef DEF + +#ifndef def +#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f) +#endif + +DEF(invalid, 1, 0, 0, none) /* never emitted */ + +/* push values */ +DEF( push_i32, 5, 0, 1, i32) +DEF( push_const, 5, 0, 1, const) +DEF( fclosure, 5, 0, 1, const) /* must follow push_const */ +DEF(push_atom_value, 5, 0, 1, atom) +DEF( private_symbol, 5, 0, 1, atom) +DEF( undefined, 1, 0, 1, none) +DEF( null, 1, 0, 1, none) +DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */ +DEF( push_false, 1, 0, 1, none) +DEF( push_true, 1, 0, 1, none) +DEF( object, 1, 0, 1, none) +DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */ +DEF( rest, 3, 0, 1, u16) /* only used at the start of a function */ + +DEF( drop, 1, 1, 0, none) /* a -> */ +DEF( nip, 1, 2, 1, none) /* a b -> b */ +DEF( nip1, 1, 3, 2, none) /* a b c -> b c */ +DEF( dup, 1, 1, 2, none) /* a -> a a */ +DEF( dup1, 1, 2, 3, none) /* a b -> a a b */ +DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */ +DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */ +DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */ +DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */ +DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */ +DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */ +DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */ +DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */ +DEF( swap, 1, 2, 2, none) /* a b -> b a */ +DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */ +DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */ +DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */ +DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */ +DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */ + +DEF(call_constructor, 3, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */ +DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */ +DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */ +DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */ +DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */ +DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ +DEF( apply, 3, 3, 1, u16) +DEF( return, 1, 1, 0, none) +DEF( return_undef, 1, 0, 0, none) +DEF(check_ctor_return, 1, 1, 2, none) +DEF( check_ctor, 1, 0, 0, none) +DEF( init_ctor, 1, 0, 1, none) +DEF( check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */ +DEF( add_brand, 1, 2, 0, none) /* this_obj home_obj -> */ +DEF( return_async, 1, 1, 0, none) +DEF( throw, 1, 1, 0, none) +DEF( throw_error, 6, 0, 0, atom_u8) +DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */ +DEF( apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */ +DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a + bytecode string */ +DEF( get_super, 1, 1, 1, none) +DEF( import, 1, 1, 1, none) /* dynamic module import */ + +DEF( check_var, 5, 0, 1, atom) /* check if a variable exists */ +DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */ +DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */ +DEF( put_var, 5, 1, 0, atom) /* must come after get_var */ +DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */ +DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */ + +DEF( get_ref_value, 1, 2, 3, none) +DEF( put_ref_value, 1, 3, 0, none) + +DEF( define_var, 6, 0, 0, atom_u8) +DEF(check_define_var, 6, 0, 0, atom_u8) +DEF( define_func, 6, 1, 0, atom_u8) + +// order matters, see IC counterparts +DEF( get_field, 5, 1, 1, atom) +DEF( get_field2, 5, 1, 2, atom) +DEF( put_field, 5, 2, 0, atom) + +DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */ +DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */ +DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ +DEF( get_array_el, 1, 2, 1, none) +DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ +DEF( put_array_el, 1, 3, 0, none) +DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */ +DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */ +DEF( define_field, 5, 2, 1, atom) +DEF( set_name, 5, 1, 1, atom) +DEF(set_name_computed, 1, 2, 2, none) +DEF( set_proto, 1, 2, 1, none) +DEF(set_home_object, 1, 2, 2, none) +DEF(define_array_el, 1, 3, 2, none) +DEF( append, 1, 3, 2, none) /* append enumerated object, update length */ +DEF(copy_data_properties, 2, 3, 3, u8) +DEF( define_method, 6, 2, 1, atom_u8) +DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */ +DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */ +DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */ + +DEF( get_loc, 3, 0, 1, loc) +DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */ +DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */ +DEF( get_arg, 3, 0, 1, arg) +DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ +DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */ +DEF( get_var_ref, 3, 0, 1, var_ref) +DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ +DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ +DEF(set_loc_uninitialized, 3, 0, 0, loc) +DEF( get_loc_check, 3, 0, 1, loc) +DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ +DEF( put_loc_check_init, 3, 1, 0, loc) +DEF(get_var_ref_check, 3, 0, 1, var_ref) +DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ +DEF(put_var_ref_check_init, 3, 1, 0, var_ref) +DEF( close_loc, 3, 0, 0, loc) +DEF( if_false, 5, 1, 0, label) +DEF( if_true, 5, 1, 0, label) /* must come after if_false */ +DEF( goto, 5, 0, 0, label) /* must come after if_true */ +DEF( catch, 5, 0, 1, label) +DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ +DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ +DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */ + +DEF( to_object, 1, 1, 1, none) +//DEF( to_string, 1, 1, 1, none) +DEF( to_propkey, 1, 1, 1, none) +DEF( to_propkey2, 1, 2, 2, none) + +DEF( with_get_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_put_var, 10, 2, 1, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_delete_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_make_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_get_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_get_ref_undef, 10, 1, 0, atom_label_u8) + +DEF( make_loc_ref, 7, 0, 2, atom_u16) +DEF( make_arg_ref, 7, 0, 2, atom_u16) +DEF(make_var_ref_ref, 7, 0, 2, atom_u16) +DEF( make_var_ref, 5, 0, 2, atom) + +DEF( for_in_start, 1, 1, 1, none) +DEF( for_of_start, 1, 1, 3, none) +DEF(for_await_of_start, 1, 1, 3, none) +DEF( for_in_next, 1, 1, 3, none) +DEF( for_of_next, 2, 3, 5, u8) +DEF(iterator_check_object, 1, 1, 1, none) +DEF(iterator_get_value_done, 1, 1, 2, none) +DEF( iterator_close, 1, 3, 0, none) +DEF( iterator_next, 1, 4, 4, none) +DEF( iterator_call, 2, 4, 5, u8) +DEF( initial_yield, 1, 0, 0, none) +DEF( yield, 1, 1, 2, none) +DEF( yield_star, 1, 1, 2, none) +DEF(async_yield_star, 1, 1, 2, none) +DEF( await, 1, 1, 1, none) + +/* arithmetic/logic operations */ +DEF( neg, 1, 1, 1, none) +DEF( plus, 1, 1, 1, none) +DEF( dec, 1, 1, 1, none) +DEF( inc, 1, 1, 1, none) +DEF( post_dec, 1, 1, 2, none) +DEF( post_inc, 1, 1, 2, none) +DEF( dec_loc, 2, 0, 0, loc8) +DEF( inc_loc, 2, 0, 0, loc8) +DEF( add_loc, 2, 1, 0, loc8) +DEF( not, 1, 1, 1, none) +DEF( lnot, 1, 1, 1, none) +DEF( typeof, 1, 1, 1, none) +DEF( delete, 1, 2, 1, none) +DEF( delete_var, 5, 0, 1, atom) + +/* warning: order matters (see js_parse_assign_expr) */ +DEF( mul, 1, 2, 1, none) +DEF( div, 1, 2, 1, none) +DEF( mod, 1, 2, 1, none) +DEF( add, 1, 2, 1, none) +DEF( sub, 1, 2, 1, none) +DEF( shl, 1, 2, 1, none) +DEF( sar, 1, 2, 1, none) +DEF( shr, 1, 2, 1, none) +DEF( and, 1, 2, 1, none) +DEF( xor, 1, 2, 1, none) +DEF( or, 1, 2, 1, none) +DEF( pow, 1, 2, 1, none) + +DEF( lt, 1, 2, 1, none) +DEF( lte, 1, 2, 1, none) +DEF( gt, 1, 2, 1, none) +DEF( gte, 1, 2, 1, none) +DEF( instanceof, 1, 2, 1, none) +DEF( in, 1, 2, 1, none) +DEF( eq, 1, 2, 1, none) +DEF( neq, 1, 2, 1, none) +DEF( strict_eq, 1, 2, 1, none) +DEF( strict_neq, 1, 2, 1, none) +DEF(is_undefined_or_null, 1, 1, 1, none) +DEF( private_in, 1, 2, 1, none) +DEF(push_bigint_i32, 5, 0, 1, i32) +/* must be the last non short and non temporary opcode */ +DEF( nop, 1, 0, 0, none) + +/* temporary opcodes: never emitted in the final bytecode */ + +def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ +def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ + +def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ + +def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ +def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ +def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */ +def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */ +def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */ +def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ + +def( source_loc, 9, 0, 0, u32x2) /* emitted in phase 1, removed in phase 3 */ + +DEF( push_minus1, 1, 0, 1, none_int) +DEF( push_0, 1, 0, 1, none_int) +DEF( push_1, 1, 0, 1, none_int) +DEF( push_2, 1, 0, 1, none_int) +DEF( push_3, 1, 0, 1, none_int) +DEF( push_4, 1, 0, 1, none_int) +DEF( push_5, 1, 0, 1, none_int) +DEF( push_6, 1, 0, 1, none_int) +DEF( push_7, 1, 0, 1, none_int) +DEF( push_i8, 2, 0, 1, i8) +DEF( push_i16, 3, 0, 1, i16) +DEF( push_const8, 2, 0, 1, const8) +DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */ +DEF(push_empty_string, 1, 0, 1, none) + +DEF( get_loc8, 2, 0, 1, loc8) +DEF( put_loc8, 2, 1, 0, loc8) +DEF( set_loc8, 2, 1, 1, loc8) + +DEF( get_loc0_loc1, 1, 0, 2, none_loc) +DEF( get_loc0, 1, 0, 1, none_loc) +DEF( get_loc1, 1, 0, 1, none_loc) +DEF( get_loc2, 1, 0, 1, none_loc) +DEF( get_loc3, 1, 0, 1, none_loc) +DEF( put_loc0, 1, 1, 0, none_loc) +DEF( put_loc1, 1, 1, 0, none_loc) +DEF( put_loc2, 1, 1, 0, none_loc) +DEF( put_loc3, 1, 1, 0, none_loc) +DEF( set_loc0, 1, 1, 1, none_loc) +DEF( set_loc1, 1, 1, 1, none_loc) +DEF( set_loc2, 1, 1, 1, none_loc) +DEF( set_loc3, 1, 1, 1, none_loc) +DEF( get_arg0, 1, 0, 1, none_arg) +DEF( get_arg1, 1, 0, 1, none_arg) +DEF( get_arg2, 1, 0, 1, none_arg) +DEF( get_arg3, 1, 0, 1, none_arg) +DEF( put_arg0, 1, 1, 0, none_arg) +DEF( put_arg1, 1, 1, 0, none_arg) +DEF( put_arg2, 1, 1, 0, none_arg) +DEF( put_arg3, 1, 1, 0, none_arg) +DEF( set_arg0, 1, 1, 1, none_arg) +DEF( set_arg1, 1, 1, 1, none_arg) +DEF( set_arg2, 1, 1, 1, none_arg) +DEF( set_arg3, 1, 1, 1, none_arg) +DEF( get_var_ref0, 1, 0, 1, none_var_ref) +DEF( get_var_ref1, 1, 0, 1, none_var_ref) +DEF( get_var_ref2, 1, 0, 1, none_var_ref) +DEF( get_var_ref3, 1, 0, 1, none_var_ref) +DEF( put_var_ref0, 1, 1, 0, none_var_ref) +DEF( put_var_ref1, 1, 1, 0, none_var_ref) +DEF( put_var_ref2, 1, 1, 0, none_var_ref) +DEF( put_var_ref3, 1, 1, 0, none_var_ref) +DEF( set_var_ref0, 1, 1, 1, none_var_ref) +DEF( set_var_ref1, 1, 1, 1, none_var_ref) +DEF( set_var_ref2, 1, 1, 1, none_var_ref) +DEF( set_var_ref3, 1, 1, 1, none_var_ref) + +DEF( get_length, 1, 1, 1, none) + +DEF( if_false8, 2, 1, 0, label8) +DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */ +DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */ +DEF( goto16, 3, 0, 0, label16) + +DEF( call0, 1, 1, 1, npopx) +DEF( call1, 1, 1, 1, npopx) +DEF( call2, 1, 1, 1, npopx) +DEF( call3, 1, 1, 1, npopx) + +DEF( is_undefined, 1, 1, 1, none) +DEF( is_null, 1, 1, 1, none) +DEF(typeof_is_undefined, 1, 1, 1, none) +DEF( typeof_is_function, 1, 1, 1, none) + +#undef DEF +#undef def +#endif /* DEF */ diff --git a/NativeScript/napi/android/quickjs/source_ng/quickjs.c b/NativeScript/napi/android/quickjs/source_ng/quickjs.c new file mode 100755 index 000000000..1a3174e8f --- /dev/null +++ b/NativeScript/napi/android/quickjs/source_ng/quickjs.c @@ -0,0 +1,58355 @@ +/* + * QuickJS Javascript Engine + * + * Copyright (c) 2017-2025 Fabrice Bellard + * Copyright (c) 2017-2025 Charlie Gordon + * Copyright (c) 2023-2025 Ben Noordhuis + * Copyright (c) 2023-2025 Saúl Ibarra Corretgé + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) +#include +#if defined(_WIN32) +#include +#endif +#endif +#if defined(_WIN32) +#include +#endif +#include +#include +#include + +#include "cutils.h" +#include "list.h" +#include "quickjs.h" +#include "libregexp.h" +#include "dtoa.h" + +#if defined(EMSCRIPTEN) || defined(_MSC_VER) +#define DIRECT_DISPATCH 0 +#else +#define DIRECT_DISPATCH 1 +#endif + +#if defined(__APPLE__) +#define MALLOC_OVERHEAD 0 +#else +#define MALLOC_OVERHEAD 8 +#endif + +#if defined(__NEWLIB__) +#define NO_TM_GMTOFF +#endif + +// atomic_store etc. are completely busted in recent versions of tcc; +// somehow the compiler forgets to load |ptr| into %rdi when calling +// the __atomic_*() helpers in its lib/stdatomic.c and lib/atomic.S +#if !defined(__TINYC__) && !defined(EMSCRIPTEN) && !defined(__wasi__) && !__STDC_NO_ATOMICS__ +#include "quickjs-c-atomics.h" +#define CONFIG_ATOMICS +#endif + +#ifndef __GNUC__ +#define __extension__ +#endif + +#ifndef NDEBUG +#define ENABLE_DUMPS +#endif + +//#define FORCE_GC_AT_MALLOC /* test the GC by forcing it before each object allocation */ + +#define check_dump_flag(rt, flag) ((rt->dump_flags & (flag +0)) == (flag +0)) + +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +#define QJS_VERSION_STRING \ + STRINGIFY(QJS_VERSION_MAJOR) "." STRINGIFY(QJS_VERSION_MINOR) "." STRINGIFY(QJS_VERSION_PATCH) QJS_VERSION_SUFFIX + +const char* JS_GetVersion(void) { + return QJS_VERSION_STRING; +} + +#undef STRINFIGY_ +#undef STRINGIFY + +static inline JSValueConst *vc(JSValue *vals) +{ + return (JSValueConst *)vals; +} + +static inline JSValue unsafe_unconst(JSValueConst v) +{ +#ifdef JS_CHECK_JSVALUE + return (JSValue)v; +#else + return v; +#endif +} + +static inline JSValueConst safe_const(JSValue v) +{ +#ifdef JS_CHECK_JSVALUE + return (JSValueConst)v; +#else + return v; +#endif +} + +enum { + /* classid tag */ /* union usage | properties */ + JS_CLASS_OBJECT = 1, /* must be first */ + JS_CLASS_ARRAY, /* u.array | length */ + JS_CLASS_ERROR, + JS_CLASS_NUMBER, /* u.object_data */ + JS_CLASS_STRING, /* u.object_data */ + JS_CLASS_BOOLEAN, /* u.object_data */ + JS_CLASS_SYMBOL, /* u.object_data */ + JS_CLASS_ARGUMENTS, /* u.array | length */ + JS_CLASS_MAPPED_ARGUMENTS, /* | length */ + JS_CLASS_DATE, /* u.object_data */ + JS_CLASS_MODULE_NS, + JS_CLASS_C_FUNCTION, /* u.cfunc */ + JS_CLASS_BYTECODE_FUNCTION, /* u.func */ + JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ + JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ + JS_CLASS_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ + JS_CLASS_REGEXP, /* u.regexp */ + JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_DATAVIEW, /* u.typed_array */ + JS_CLASS_BIG_INT, /* u.object_data */ + JS_CLASS_MAP, /* u.map_state */ + JS_CLASS_SET, /* u.map_state */ + JS_CLASS_WEAKMAP, /* u.map_state */ + JS_CLASS_WEAKSET, /* u.map_state */ + JS_CLASS_ITERATOR, + JS_CLASS_ITERATOR_CONCAT, /* u.iterator_concat_data */ + JS_CLASS_ITERATOR_HELPER, /* u.iterator_helper_data */ + JS_CLASS_ITERATOR_WRAP, /* u.iterator_wrap_data */ + JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ + JS_CLASS_GENERATOR, /* u.generator_data */ + JS_CLASS_PROXY, /* u.proxy_data */ + JS_CLASS_PROMISE, /* u.promise_data */ + JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ + JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ + JS_CLASS_ASYNC_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ + JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ + JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ + JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ + JS_CLASS_WEAK_REF, + JS_CLASS_FINALIZATION_REGISTRY, + JS_CLASS_DOM_EXCEPTION, + JS_CLASS_CALL_SITE, + + JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ +}; + +/* number of typed array types */ +#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) +static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT]; +#define typed_array_size_log2(classid) (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY]) + +typedef enum JSErrorEnum { + JS_EVAL_ERROR, + JS_RANGE_ERROR, + JS_REFERENCE_ERROR, + JS_SYNTAX_ERROR, + JS_TYPE_ERROR, + JS_URI_ERROR, + JS_INTERNAL_ERROR, + JS_AGGREGATE_ERROR, + + JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ + JS_PLAIN_ERROR = JS_NATIVE_ERROR_COUNT +} JSErrorEnum; + +#define JS_MAX_LOCAL_VARS 65535 +#define JS_STACK_SIZE_MAX 65534 +#define JS_STRING_LEN_MAX ((1 << 30) - 1) +// 1,024 bytes is about the cutoff point where it starts getting +// more profitable to ref slice than to copy +#define JS_STRING_SLICE_LEN_MAX 1024 // in bytes + +#define __exception __attribute__((warn_unused_result)) + +typedef struct JSShape JSShape; +typedef struct JSString JSString; +typedef struct JSString JSAtomStruct; + +#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v)) + +typedef enum { + JS_GC_PHASE_NONE, + JS_GC_PHASE_DECREF, + JS_GC_PHASE_REMOVE_CYCLES, +} JSGCPhaseEnum; + +typedef struct JSMallocState { + size_t malloc_count; + size_t malloc_size; + size_t malloc_limit; + void *opaque; /* user opaque */ +} JSMallocState; + +typedef struct JSRuntimeFinalizerState { + struct JSRuntimeFinalizerState *next; + JSRuntimeFinalizer *finalizer; + void *arg; +} JSRuntimeFinalizerState; + +typedef struct JSValueLink { + struct JSValueLink *next; + JSValueConst value; +} JSValueLink; + +struct JSRuntime { + JSMallocFunctions mf; + JSMallocState malloc_state; + const char *rt_info; + + int atom_hash_size; /* power of two */ + int atom_count; + int atom_size; + int atom_count_resize; /* resize hash table at this count */ + uint32_t *atom_hash; + JSAtomStruct **atom_array; + int atom_free_index; /* 0 = none */ + + JSClassID js_class_id_alloc; /* counter for user defined classes */ + int class_count; /* size of class_array */ + JSClass *class_array; + + struct list_head context_list; /* list of JSContext.link */ + /* list of JSGCObjectHeader.link. List of allocated GC objects (used + by the garbage collector) */ + struct list_head gc_obj_list; + /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ + struct list_head gc_zero_ref_count_list; + struct list_head tmp_obj_list; /* used during GC */ + JSGCPhaseEnum gc_phase : 8; + size_t malloc_gc_threshold; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + struct list_head string_list; /* list of JSString.link */ +#endif + /* stack limitation */ + uintptr_t stack_size; /* in bytes, 0 if no limit */ + uintptr_t stack_top; + uintptr_t stack_limit; /* lower stack limit */ + + JSValue current_exception; + /* true if inside an out of memory error, to avoid recursing */ + bool in_out_of_memory; + /* true if inside build_backtrace, to avoid recursing */ + bool in_build_stack_trace; + /* true if inside JS_FreeRuntime */ + bool in_free; + + struct JSStackFrame *current_stack_frame; + + JSInterruptHandler *interrupt_handler; + void *interrupt_opaque; + + JSPromiseHook *promise_hook; + void *promise_hook_opaque; + // for smuggling the parent promise from js_promise_then + // to js_promise_constructor + JSValueLink *parent_promise; + + JSHostPromiseRejectionTracker *host_promise_rejection_tracker; + void *host_promise_rejection_tracker_opaque; + + struct list_head job_list; /* list of JSJobEntry.link */ + struct list_head weakref_keepalive_list; /* strong refs kept until the current host job completes */ + + JSModuleNormalizeFunc *module_normalize_func; + JSModuleLoaderFunc *module_loader_func; + void *module_loader_opaque; + /* timestamp for internal use in module evaluation */ + int64_t module_async_evaluation_next_timestamp; + + /* used to allocate, free and clone SharedArrayBuffers */ + JSSharedArrayBufferFunctions sab_funcs; + + bool can_block; /* true if Atomics.wait can block */ + uint32_t dump_flags : 24; + + /* Shape hash table */ + int shape_hash_bits; + int shape_hash_size; + int shape_hash_count; /* number of hashed shapes */ + JSShape **shape_hash; + void *user_opaque; + void *libc_opaque; + JSRuntimeFinalizerState *finalizers; +}; + +struct JSClass { + uint32_t class_id; /* 0 means free entry */ + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; + JSClassCall *call; + /* pointers for exotic behavior, can be NULL if none are present */ + const JSClassExoticMethods *exotic; +}; + +typedef struct JSStackFrame { + struct JSStackFrame *prev_frame; /* NULL if first stack frame */ + JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */ + JSValue *arg_buf; /* arguments */ + JSValue *var_buf; /* variables */ + struct list_head var_ref_list; /* list of JSVarRef.link */ + uint8_t *cur_pc; /* only used in bytecode functions : PC of the + instruction after the call */ + uint32_t arg_count : 31; + uint32_t is_strict_mode : 1; + /* only used in generators. Current stack pointer value. NULL if + the function is running. */ + JSValue *cur_sp; +} JSStackFrame; + +typedef struct JSWeakRefKeepAliveEntry { + struct list_head link; + JSValue value; +} JSWeakRefKeepAliveEntry; + +typedef enum { + JS_GC_OBJ_TYPE_JS_OBJECT, + JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, + JS_GC_OBJ_TYPE_SHAPE, + JS_GC_OBJ_TYPE_VAR_REF, + JS_GC_OBJ_TYPE_ASYNC_FUNCTION, + JS_GC_OBJ_TYPE_JS_CONTEXT, +} JSGCObjectTypeEnum; + +/* header for GC objects. GC objects are C data structures with a + reference count that can reference other GC objects. JS Objects are + a particular type of GC object. */ +struct JSGCObjectHeader { + int ref_count; /* must come first, 32-bit */ + JSGCObjectTypeEnum gc_obj_type : 4; + uint8_t mark : 4; /* used by the GC */ + uint8_t dummy1; /* not used by the GC */ + uint16_t dummy2; /* not used by the GC */ + struct list_head link; +}; + +typedef struct JSVarRef { + union { + JSGCObjectHeader header; /* must come first */ + struct { + int __gc_ref_count; /* corresponds to header.ref_count */ + uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ + uint8_t is_detached; + }; + }; + JSValue *pvalue; /* pointer to the value, either on the stack or + to 'value' */ + JSValue value; /* used when the variable is no longer on the stack */ +} JSVarRef; + +typedef struct JSRefCountHeader { + int ref_count; +} JSRefCountHeader; + +/* bigint */ +typedef int32_t js_slimb_t; +typedef uint32_t js_limb_t; +typedef int64_t js_sdlimb_t; +typedef uint64_t js_dlimb_t; + +#define JS_LIMB_DIGITS 9 + +/* Must match the size of short_big_int in JSValueUnion */ +#define JS_LIMB_BITS 32 +#define JS_SHORT_BIG_INT_BITS JS_LIMB_BITS +#define JS_BIGINT_MAX_SIZE ((1024 * 1024) / JS_LIMB_BITS) /* in limbs */ +#define JS_SHORT_BIG_INT_MIN INT32_MIN +#define JS_SHORT_BIG_INT_MAX INT32_MAX + + +typedef struct JSBigInt { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len; /* number of limbs, >= 1 */ + js_limb_t tab[]; /* two's complement representation, always + normalized so that 'len' is the minimum + possible length >= 1 */ +} JSBigInt; + +/* this bigint structure can hold a 64 bit integer */ +typedef struct { + js_limb_t big_int_buf[sizeof(JSBigInt) / sizeof(js_limb_t)]; /* for JSBigInt */ + /* must come just after */ + js_limb_t tab[(64 + JS_LIMB_BITS - 1) / JS_LIMB_BITS]; +} JSBigIntBuf; + +typedef enum { + JS_AUTOINIT_ID_PROTOTYPE, + JS_AUTOINIT_ID_MODULE_NS, + JS_AUTOINIT_ID_PROP, + JS_AUTOINIT_ID_BYTECODE, +} JSAutoInitIDEnum; + +enum { + JS_BUILTIN_ARRAY_FROMASYNC = 1, +}; + +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define JS_INTERRUPT_COUNTER_INIT 10000 + +struct JSContext { + JSGCObjectHeader header; /* must come first */ + JSRuntime *rt; + struct list_head link; + + uint16_t binary_object_count; + int binary_object_size; + + JSShape *array_shape; /* initial shape for Array objects */ + + JSValue *class_proto; + JSValue function_proto; + JSValue function_ctor; + JSValue array_ctor; + JSValue regexp_ctor; + JSValue promise_ctor; + JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; + JSValue error_ctor; + JSValue error_back_trace; + JSValue error_prepare_stack; + JSValue error_stack_trace_limit; + JSValue iterator_ctor; + JSValue iterator_ctor_getset; + JSValue iterator_proto; + JSValue async_iterator_proto; + JSValue array_proto_values; + JSValue throw_type_error; + JSValue eval_obj; + + JSValue global_obj; /* global object */ + JSValue global_var_obj; /* contains the global let/const definitions */ + + double time_origin; + + uint64_t random_state; + + /* when the counter reaches zero, JSRutime.interrupt_handler is called */ + int interrupt_counter; + + struct list_head loaded_modules; /* list of JSModuleDef.link */ + + /* if NULL, RegExp compilation is not supported */ + JSValue (*compile_regexp)(JSContext *ctx, JSValueConst pattern, + JSValueConst flags); + /* if NULL, eval is not supported */ + JSValue (*eval_internal)(JSContext *ctx, JSValueConst this_obj, + const char *input, size_t input_len, + const char *filename, int line, int flags, int scope_idx); + void *user_opaque; +}; + +typedef union JSFloat64Union { + double d; + uint64_t u64; + uint32_t u32[2]; +} JSFloat64Union; + +typedef enum { + JS_WEAK_REF_KIND_MAP, + JS_WEAK_REF_KIND_WEAK_REF, + JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY, +} JSWeakRefKindEnum; + +typedef struct JSWeakRefRecord { + JSWeakRefKindEnum kind; + struct JSWeakRefRecord *next_weak_ref; + union { + struct JSMapRecord *map_record; + struct JSWeakRefData *weak_ref_data; + struct JSFinRecEntry *fin_rec_entry; + } u; +} JSWeakRefRecord; + +typedef struct JSMapRecord { + int ref_count; /* used during enumeration to avoid freeing the record */ + bool empty; /* true if the record is deleted */ + struct JSMapState *map; + struct list_head link; + struct list_head hash_link; + JSValue key; + JSValue value; +} JSMapRecord; + +typedef struct JSMapState { + bool is_weak; /* true if WeakSet/WeakMap */ + struct list_head records; /* list of JSMapRecord.link */ + uint32_t record_count; + struct list_head *hash_table; + uint32_t hash_size; /* must be a power of two */ + uint32_t record_count_threshold; /* count at which a hash table + resize is needed */ +} JSMapState; + +enum +{ + JS_TO_STRING_IS_PROPERTY_KEY = 1 << 0, + JS_TO_STRING_NO_SIDE_EFFECTS = 1 << 1, +}; + +enum { + JS_ATOM_TYPE_STRING = 1, + JS_ATOM_TYPE_GLOBAL_SYMBOL, + JS_ATOM_TYPE_SYMBOL, + JS_ATOM_TYPE_PRIVATE, +}; + +enum { + JS_ATOM_HASH_SYMBOL, + JS_ATOM_HASH_PRIVATE, +}; + +typedef enum { + JS_ATOM_KIND_STRING, + JS_ATOM_KIND_SYMBOL, + JS_ATOM_KIND_PRIVATE, +} JSAtomKindEnum; + +typedef enum { + JS_STRING_KIND_NORMAL, + JS_STRING_KIND_SLICE, +} JSStringKind; + +#define JS_ATOM_HASH_MASK ((1 << 29) - 1) + +struct JSString { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len : 31; + uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ + /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, + for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 + XXX: could change encoding to have one more bit in hash */ + uint32_t hash : 29; + uint8_t kind : 1; + uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ + uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ + JSWeakRefRecord *first_weak_ref; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + struct list_head link; /* string list */ +#endif +}; + +typedef struct JSStringSlice { + JSString *parent; + uint32_t start; // in bytes, not characters +} JSStringSlice; + +static inline void *strv(JSString *p) +{ + JSStringSlice *slice; + + switch (p->kind) { + case JS_STRING_KIND_NORMAL: + return (void *)&p[1]; + case JS_STRING_KIND_SLICE: + slice = (void *)&p[1]; + return (char *)&slice->parent[1] + slice->start; + } + abort(); + return NULL; +} + +static inline uint8_t *str8(JSString *p) +{ + return strv(p); +} + +static inline uint16_t *str16(JSString *p) +{ + return strv(p); +} + +typedef struct JSClosureVar { + uint8_t is_local : 1; + uint8_t is_arg : 1; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* 8 bits available */ + uint16_t var_idx; /* is_local = true: index to a normal variable of the + parent function. otherwise: index to a closure + variable of the parent function */ + JSAtom var_name; +} JSClosureVar; + +#define ARG_SCOPE_INDEX 1 +#define ARG_SCOPE_END (-2) + +typedef struct JSVarScope { + int parent; /* index into fd->scopes of the enclosing scope */ + int first; /* index into fd->vars of the last variable in this scope */ +} JSVarScope; + +typedef enum { + /* XXX: add more variable kinds here instead of using bit fields */ + JS_VAR_NORMAL, + JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ + JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator + function declaration */ + JS_VAR_CATCH, + JS_VAR_FUNCTION_NAME, /* function expression name */ + JS_VAR_PRIVATE_FIELD, + JS_VAR_PRIVATE_METHOD, + JS_VAR_PRIVATE_GETTER, + JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */ + JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER */ +} JSVarKindEnum; + +/* XXX: could use a different structure in bytecode functions to save + memory */ +typedef struct JSVarDef { + JSAtom var_name; + /* index into fd->scopes of this variable lexical scope */ + int scope_level; + /* during compilation: + - if scope_level = 0: scope in which the variable is defined + - if scope_level != 0: index into fd->vars of the next + variable in the same or enclosing lexical scope + in a bytecode function: + index into fd->vars of the next + variable in the same or enclosing lexical scope + */ + int scope_next; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t is_captured : 1; + uint8_t is_static_private : 1; /* only used during private class field parsing */ + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* only used during compilation: function pool index for lexical + variables with var_kind = + JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of + the definition of the 'var' variables (they have scope_level = + 0) */ + int func_pool_idx : 24; /* only used during compilation : index in + the constant pool for hoisted function + definition */ +} JSVarDef; + +/* for the encoding of the pc2line table */ +#define PC2LINE_BASE (-1) +#define PC2LINE_RANGE 5 +#define PC2LINE_OP_FIRST 1 +#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) + +typedef enum JSFunctionKindEnum { + JS_FUNC_NORMAL = 0, + JS_FUNC_GENERATOR = (1 << 0), + JS_FUNC_ASYNC = (1 << 1), + JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), +} JSFunctionKindEnum; + +typedef struct JSFunctionBytecode { + JSGCObjectHeader header; /* must come first */ + uint8_t is_strict_mode : 1; + uint8_t has_prototype : 1; /* true if a prototype field is necessary */ + uint8_t has_simple_parameter_list : 1; + uint8_t is_derived_class_constructor : 1; + /* true if home_object needs to be initialized */ + uint8_t need_home_object : 1; + uint8_t func_kind : 2; + uint8_t new_target_allowed : 1; + uint8_t super_call_allowed : 1; + uint8_t super_allowed : 1; + uint8_t arguments_allowed : 1; + uint8_t backtrace_barrier : 1; /* stop backtrace on this function */ + /* XXX: 5 bits available */ + uint8_t *byte_code_buf; /* (self pointer) */ + int byte_code_len; + JSAtom func_name; + JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */ + JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */ + uint16_t arg_count; + uint16_t var_count; + uint16_t defined_arg_count; /* for length function property */ + uint16_t stack_size; /* maximum stack size */ + JSContext *realm; /* function realm */ + JSValue *cpool; /* constant pool (self pointer) */ + int cpool_count; + int closure_var_count; + JSAtom filename; + int line_num; + int col_num; + int source_len; + int pc2line_len; + uint8_t *pc2line_buf; + char *source; +} JSFunctionBytecode; + +typedef struct JSBoundFunction { + JSValue func_obj; + JSValue this_val; + int argc; + JSValue argv[]; +} JSBoundFunction; + +typedef enum JSIteratorKindEnum { + JS_ITERATOR_KIND_KEY, + JS_ITERATOR_KIND_VALUE, + JS_ITERATOR_KIND_KEY_AND_VALUE, +} JSIteratorKindEnum; + +typedef enum JSIteratorHelperKindEnum { + JS_ITERATOR_HELPER_KIND_DROP, + JS_ITERATOR_HELPER_KIND_EVERY, + JS_ITERATOR_HELPER_KIND_FILTER, + JS_ITERATOR_HELPER_KIND_FIND, + JS_ITERATOR_HELPER_KIND_FLAT_MAP, + JS_ITERATOR_HELPER_KIND_FOR_EACH, + JS_ITERATOR_HELPER_KIND_MAP, + JS_ITERATOR_HELPER_KIND_SOME, + JS_ITERATOR_HELPER_KIND_TAKE, +} JSIteratorHelperKindEnum; + +typedef struct JSForInIterator { + JSValue obj; + bool is_array; + uint32_t array_length; + uint32_t idx; +} JSForInIterator; + +typedef struct JSRegExp { + JSString *pattern; + JSString *bytecode; /* also contains the flags */ +} JSRegExp; + +typedef struct JSProxyData { + JSValue target; + JSValue handler; + uint8_t is_func; + uint8_t is_revoked; +} JSProxyData; + +typedef struct JSArrayBuffer { + int byte_length; /* 0 if detached */ + int max_byte_length; /* -1 if not resizable; >= byte_length otherwise */ + uint8_t detached; + uint8_t shared; /* if shared, the array buffer cannot be detached */ + uint8_t *data; /* NULL if detached */ + struct list_head array_list; + void *opaque; + JSFreeArrayBufferDataFunc *free_func; +} JSArrayBuffer; + +typedef struct JSTypedArray { + struct list_head link; /* link to arraybuffer */ + JSObject *obj; /* back pointer to the TypedArray/DataView object */ + JSObject *buffer; /* based array buffer */ + uint32_t offset; /* byte offset in the array buffer */ + uint32_t length; /* byte length in the array buffer */ + bool track_rab; /* auto-track length of backing array buffer */ +} JSTypedArray; + +typedef struct JSAsyncFunctionState { + JSValue this_val; /* 'this' generator argument */ + int argc; /* number of function arguments */ + bool throw_flag; /* used to throw an exception in JS_CallInternal() */ + JSStackFrame frame; +} JSAsyncFunctionState; + +/* XXX: could use an object instead to avoid the + JS_TAG_ASYNC_FUNCTION tag for the GC */ +typedef struct JSAsyncFunctionData { + JSGCObjectHeader header; /* must come first */ + JSValue resolving_funcs[2]; + bool is_active; /* true if the async function state is valid */ + JSAsyncFunctionState func_state; +} JSAsyncFunctionData; + +typedef struct JSReqModuleEntry { + JSAtom module_name; + JSModuleDef *module; /* used using resolution */ +} JSReqModuleEntry; + +typedef enum JSExportTypeEnum { + JS_EXPORT_TYPE_LOCAL, + JS_EXPORT_TYPE_INDIRECT, +} JSExportTypeEnum; + +typedef struct JSExportEntry { + union { + struct { + int var_idx; /* closure variable index */ + JSVarRef *var_ref; /* if != NULL, reference to the variable */ + } local; /* for local export */ + int req_module_idx; /* module for indirect export */ + } u; + JSExportTypeEnum export_type; + JSAtom local_name; /* '*' if export ns from. not used for local + export after compilation */ + JSAtom export_name; /* exported variable name */ +} JSExportEntry; + +typedef struct JSStarExportEntry { + int req_module_idx; /* in req_module_entries */ +} JSStarExportEntry; + +typedef struct JSImportEntry { + int var_idx; /* closure variable index */ + JSAtom import_name; + int req_module_idx; /* in req_module_entries */ +} JSImportEntry; + +typedef enum { + JS_MODULE_STATUS_UNLINKED, + JS_MODULE_STATUS_LINKING, + JS_MODULE_STATUS_LINKED, + JS_MODULE_STATUS_EVALUATING, + JS_MODULE_STATUS_EVALUATING_ASYNC, + JS_MODULE_STATUS_EVALUATED, +} JSModuleStatus; + +struct JSModuleDef { + JSRefCountHeader header; /* must come first, 32-bit */ + JSAtom module_name; + struct list_head link; + + JSReqModuleEntry *req_module_entries; + int req_module_entries_count; + int req_module_entries_size; + + JSExportEntry *export_entries; + int export_entries_count; + int export_entries_size; + + JSStarExportEntry *star_export_entries; + int star_export_entries_count; + int star_export_entries_size; + + JSImportEntry *import_entries; + int import_entries_count; + int import_entries_size; + + JSValue module_ns; + JSValue func_obj; /* only used for JS modules */ + JSModuleInitFunc *init_func; /* only used for C modules */ + bool has_tla; /* true if func_obj contains await */ + bool resolved; + bool func_created; + JSModuleStatus status : 8; + /* temp use during js_module_link() & js_module_evaluate() */ + int dfs_index, dfs_ancestor_index; + JSModuleDef *stack_prev; + /* temp use during js_module_evaluate() */ + JSModuleDef **async_parent_modules; + int async_parent_modules_count; + int async_parent_modules_size; + int pending_async_dependencies; + bool async_evaluation; + int64_t async_evaluation_timestamp; + JSModuleDef *cycle_root; + JSValue promise; /* corresponds to spec field: capability */ + JSValue resolving_funcs[2]; /* corresponds to spec field: capability */ + /* true if evaluation yielded an exception. It is saved in + eval_exception */ + bool eval_has_exception; + JSValue eval_exception; + JSValue meta_obj; /* for import.meta */ +}; + +typedef struct JSJobEntry { + struct list_head link; + JSContext *ctx; + JSJobFunc *job_func; + int argc; + JSValue argv[]; +} JSJobEntry; + +typedef struct JSProperty { + union { + JSValue value; /* JS_PROP_NORMAL */ + struct { /* JS_PROP_GETSET */ + JSObject *getter; /* NULL if undefined */ + JSObject *setter; /* NULL if undefined */ + } getset; + JSVarRef *var_ref; /* JS_PROP_VARREF */ + struct { /* JS_PROP_AUTOINIT */ + /* in order to use only 2 pointers, we compress the realm + and the init function pointer */ + uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x) + in the 2 low bits */ + void *opaque; + } init; + } u; +} JSProperty; + +#define JS_PROP_INITIAL_SIZE 2 +#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ +#define JS_ARRAY_INITIAL_SIZE 2 + +typedef struct JSShapeProperty { + uint32_t hash_next : 26; /* 0 if last in list */ + uint32_t flags : 6; /* JS_PROP_XXX */ + JSAtom atom; /* JS_ATOM_NULL = free property entry */ +} JSShapeProperty; + +struct JSShape { + /* hash table of size hash_mask + 1 before the start of the + structure (see prop_hash_end()). */ + JSGCObjectHeader header; + /* true if the shape is inserted in the shape hash table. If not, + JSShape.hash is not valid */ + uint8_t is_hashed; + /* If true, the shape may have small array index properties 'n' with 0 + <= n <= 2^31-1. If false, the shape is guaranteed not to have + small array index properties */ + uint8_t has_small_array_index; + uint32_t hash; /* current hash value */ + uint32_t prop_hash_mask; + int prop_size; /* allocated properties */ + int prop_count; /* include deleted properties */ + int deleted_prop_count; + JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ + JSObject *proto; + JSShapeProperty prop[]; /* prop_size elements */ +}; + +struct JSObject { + union { + JSGCObjectHeader header; + struct { + int __gc_ref_count; /* corresponds to header.ref_count */ + uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ + + uint8_t extensible : 1; + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* true if object has exotic property handlers */ + uint8_t fast_array : 1; /* true if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */ + uint8_t is_constructor : 1; /* true if object is a constructor function */ + uint8_t is_uncatchable_error : 1; /* if true, error is not catchable */ + uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ + uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ + uint16_t class_id; /* see JS_CLASS_x */ + }; + }; + /* byte offsets: 16/24 */ + JSShape *shape; /* prototype and property names + flag */ + JSProperty *prop; /* array of properties */ + /* byte offsets: 24/40 */ + JSWeakRefRecord *first_weak_ref; + /* byte offsets: 28/48 */ + union { + void *opaque; + struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ + struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ + struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ + struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ + struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ + struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ + struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ + struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ + struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ + struct JSIteratorConcatData *iterator_concat_data; /* JS_CLASS_ITERATOR_CONCAT */ + struct JSIteratorHelperData *iterator_helper_data; /* JS_CLASS_ITERATOR_HELPER */ + struct JSIteratorWrapData *iterator_wrap_data; /* JS_CLASS_ITERATOR_WRAP */ + struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ + struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ + struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ + struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ + struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ + struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ + struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ + /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ + struct JSFunctionBytecode *function_bytecode; + JSVarRef **var_refs; + JSObject *home_object; /* for 'super' access */ + } func; + struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ + JSContext *realm; + JSCFunctionType c_function; + uint8_t length; + uint8_t cproto; + int16_t magic; + } cfunc; + /* array part for fast arrays and typed arrays */ + struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + union { + uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + } u1; + union { + JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */ + uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ + int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */ + uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ + int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */ + uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ + int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ + uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ + uint16_t *fp16_ptr; /* JS_CLASS_FLOAT16_ARRAY */ + float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ + double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ + } u; + uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ + } array; /* 12/20 bytes */ + JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ + JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ + } u; + /* byte sizes: 40/48/72 */ +}; + +typedef struct JSCallSiteData { + JSValue filename; + JSValue func; + JSValue func_name; + bool native; + int line_num; + int col_num; +} JSCallSiteData; + +enum { + __JS_ATOM_NULL = JS_ATOM_NULL, +#define DEF(name, str) JS_ATOM_ ## name, +#include "quickjs-atom.h" +#undef DEF + JS_ATOM_END, +}; +#define JS_ATOM_LAST_KEYWORD JS_ATOM_super +#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield + +static const char js_atom_init[] = +#define DEF(name, str) str "\0" +#include "quickjs-atom.h" +#undef DEF +; + +typedef enum OPCodeFormat { +#define FMT(f) OP_FMT_ ## f, +#define DEF(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +} OPCodeFormat; + +typedef enum OPCodeEnum { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_COUNT, /* excluding temporary opcodes */ + /* temporary opcodes : overlap with the short opcodes */ + OP_TEMP_START = OP_nop + 1, + OP___dummy = OP_TEMP_START - 1, +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) +#define def(id, size, n_pop, n_push, f) OP_ ## id, +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_TEMP_END, +} OPCodeEnum; + +static int JS_InitAtoms(JSRuntime *rt); +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type); +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p); +static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b); +static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags); +static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags); +static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValueConst *argv, int flags); +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValueConst *argv, int flags); +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv); +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValueConst *argv); +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, bool is_array_ctor); +static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, + JSValueConst val, int flags, int scope_idx); +static __maybe_unused void JS_DumpString(JSRuntime *rt, JSString *p); +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); +static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValueConst val); +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); +static __maybe_unused void JS_DumpShapes(JSRuntime *rt); + +static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static void js_array_finalizer(JSRuntime *rt, JSValueConst val); +static void js_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_object_data_finalizer(JSRuntime *rt, JSValueConst val); +static void js_object_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_c_function_finalizer(JSRuntime *rt, JSValueConst val); +static void js_c_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValueConst val); +static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_bound_function_finalizer(JSRuntime *rt, JSValueConst val); +static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValueConst val); +static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_regexp_finalizer(JSRuntime *rt, JSValueConst val); +static void js_array_buffer_finalizer(JSRuntime *rt, JSValueConst val); +static void js_typed_array_finalizer(JSRuntime *rt, JSValueConst val); +static void js_typed_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_proxy_finalizer(JSRuntime *rt, JSValueConst val); +static void js_proxy_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_map_finalizer(JSRuntime *rt, JSValueConst val); +static void js_map_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_map_iterator_finalizer(JSRuntime *rt, JSValueConst val); +static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_array_iterator_finalizer(JSRuntime *rt, JSValueConst val); +static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_concat_finalizer(JSRuntime *rt, JSValueConst val); +static void js_iterator_concat_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_helper_finalizer(JSRuntime *rt, JSValueConst val); +static void js_iterator_helper_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_iterator_wrap_finalizer(JSRuntime *rt, JSValueConst val); +static void js_iterator_wrap_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_regexp_string_iterator_finalizer(JSRuntime *rt, + JSValueConst val); +static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_generator_finalizer(JSRuntime *rt, JSValueConst val); +static void js_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_promise_finalizer(JSRuntime *rt, JSValueConst val); +static void js_promise_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValueConst val); +static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); + +#define HINT_STRING 0 +#define HINT_NUMBER 1 +#define HINT_NONE 2 +#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint); +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); +static int JS_ToBoolFree(JSContext *ctx, JSValue val); +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); +static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val); +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val); +static JSValue JS_ToPropertyKeyInternal(JSContext *ctx, JSValueConst val, + int flags); +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len); +static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern, + JSValueConst flags); +static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor, + JSValue pattern, JSValue bc); +static void gc_decref(JSRuntime *rt); +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name); +static JSValue js_array_push(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int unshift); +static JSValue js_array_constructor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv); +static JSValue js_error_constructor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv, int magic); +static JSValue js_object_defineProperty(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); + +typedef enum JSStrictEqModeEnum { + JS_EQ_STRICT, + JS_EQ_SAME_VALUE, + JS_EQ_SAME_VALUE_ZERO, +} JSStrictEqModeEnum; + +static bool js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, + JSStrictEqModeEnum eq_mode); +static bool js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2); +static bool js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static bool js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val); +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags); +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val); +static JSValue JS_ThrowStackOverflow(JSContext *ctx); +static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); +static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj); +static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, + JSValueConst proto_val, bool throw_flag); +static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj); +static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj); +static int js_proxy_isArray(JSContext *ctx, JSValueConst obj); +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, + int flags); +static int js_string_memcmp(JSString *p1, JSString *p2, int len); +static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref); +static bool is_valid_weakref_target(JSValueConst val); +static void insert_weakref_record(JSValueConst target, + struct JSWeakRefRecord *wr); +static JSValue js_array_buffer_constructor3(JSContext *ctx, + JSValueConst new_target, + uint64_t len, uint64_t *max_len, + JSClassID class_id, + uint8_t *buf, + JSFreeArrayBufferDataFunc *free_func, + void *opaque, bool alloc_flag); +static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr); +static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj); +static bool array_buffer_is_resizable(const JSArrayBuffer *abuf); +static JSValue js_typed_array_constructor(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int classid); +static JSValue js_typed_array_constructor_ta(JSContext *ctx, + JSValueConst new_target, + JSValueConst src_obj, + int classid, uint32_t len); +static bool is_typed_array(JSClassID class_id); +static bool typed_array_is_oob(JSObject *p); +static uint32_t typed_array_length(JSObject *p); +static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); +static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx); +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, + bool is_arg); +static JSValue js_call_generator_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags); +static void js_async_function_resolve_finalizer(JSRuntime *rt, + JSValueConst val); +static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, + const char *input, size_t input_len, + const char *filename, int line, int flags, int scope_idx); +static void js_free_module_def(JSContext *ctx, JSModuleDef *m); +static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m, + JS_MarkFunc *mark_func); +static JSValue js_import_meta(JSContext *ctx); +static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier); +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); +static JSValue js_new_promise_capability(JSContext *ctx, + JSValue *resolving_funcs, + JSValueConst ctor); +static __exception int perform_promise_then(JSContext *ctx, + JSValueConst promise, + JSValueConst *resolve_reject, + JSValueConst *cap_resolving_funcs); +static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue js_promise_resolve_thenable_job(JSContext *ctx, + int argc, JSValueConst *argv); +static bool js_string_eq(JSString *p1, JSString *p2); +static int js_string_compare(JSString *p1, JSString *p2); +static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags); +static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val); +static bool JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val); +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val); +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop); +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc); +static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, + JS_MarkFunc *mark_func); +static void JS_AddIntrinsicBasicObjects(JSContext *ctx); +static void js_free_shape(JSRuntime *rt, JSShape *sh); +static void js_free_shape_null(JSRuntime *rt, JSShape *sh); +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs); +static int init_shape_hash(JSRuntime *rt); +static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, + JSValueConst obj); +static __exception int js_get_length64(JSContext *ctx, int64_t *pres, + JSValueConst obj); +static __exception int js_set_length64(JSContext *ctx, JSValueConst obj, + int64_t len); +static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len); +static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, + JSValueConst array_arg); +static JSValue js_create_array(JSContext *ctx, int len, JSValueConst *tab); +static bool js_get_fast_array(JSContext *ctx, JSValue obj, + JSValue **arrpp, uint32_t *countp); +static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len); +static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, + JSValue sync_iter); +static void js_c_function_data_finalizer(JSRuntime *rt, JSValueConst val); +static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static JSValue js_call_c_function_data(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags); +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValueConst val); +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type); +static void remove_gc_object(JSGCObjectHeader *h); +static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s); +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); +static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, + void *opaque); +static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, + JSAtom atom, void *opaque); + +static void js_set_uncatchable_error(JSContext *ctx, JSValueConst val, + bool flag); + +static JSValue js_new_callsite(JSContext *ctx, JSCallSiteData *csd); +static void js_new_callsite_data(JSContext *ctx, JSCallSiteData *csd, JSStackFrame *sf); +static void js_new_callsite_data2(JSContext *ctx, JSCallSiteData *csd, const char *filename, int line_num, int col_num); +static void _JS_AddIntrinsicCallSite(JSContext *ctx); + +static void JS_SetOpaqueInternal(JSValueConst obj, void *opaque); + +static const JSClassExoticMethods js_arguments_exotic_methods; +static const JSClassExoticMethods js_string_exotic_methods; +static const JSClassExoticMethods js_proxy_exotic_methods; +static const JSClassExoticMethods js_module_ns_exotic_methods; + +static inline bool double_is_int32(double d) +{ + uint64_t u, e; + JSFloat64Union t; + + t.d = d; + u = t.u64; + + e = ((u >> 52) & 0x7FF) - 1023; + if (e > 30) { + // accept 0, INT32_MIN, reject too large, too small, nan, inf, -0 + return !u || (u == 0xc1e0000000000000); + } else { + // shift out sign, exponent and whole part bits + // value is fractional if remaining low bits are non-zero + return !(u << 12 << e); + } +} + +static JSValue js_float64(double d) +{ + return __JS_NewFloat64(d); +} + +static int compare_u32(uint32_t a, uint32_t b) +{ + return -(a < b) + (b < a); // -1, 0 or 1 +} + +static JSValue js_int32(int32_t v) +{ + return JS_MKVAL(JS_TAG_INT, v); +} + +static JSValue js_uint32(uint32_t v) +{ + if (v <= INT32_MAX) + return js_int32(v); + else + return js_float64(v); +} + +static JSValue js_int64(int64_t v) +{ + if (v >= INT32_MIN && v <= INT32_MAX) + return js_int32(v); + else + return js_float64(v); +} + +static JSValue js_number(double d) +{ + if (double_is_int32(d)) + return js_int32((int32_t)d); + else + return js_float64(d); +} + +JSValue JS_NewNumber(JSContext *ctx, double d) +{ + return js_number(d); +} + +static JSValue js_bool(bool v) +{ + return JS_MKVAL(JS_TAG_BOOL, (v != 0)); +} + +static JSValue js_dup(JSValueConst v) +{ + if (JS_VALUE_HAS_REF_COUNT(v)) { + JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + p->ref_count++; + } + return unsafe_unconst(v); +} + +JSValue JS_DupValue(JSContext *ctx, JSValueConst v) +{ + return js_dup(v); +} + +JSValue JS_DupValueRT(JSRuntime *rt, JSValueConst v) +{ + return js_dup(v); +} + +static void js_trigger_gc(JSRuntime *rt, size_t size) +{ + bool force_gc; +#ifdef FORCE_GC_AT_MALLOC + force_gc = true; +#else + force_gc = ((rt->malloc_state.malloc_size + size) > + rt->malloc_gc_threshold); +#endif + if (force_gc) { +#ifdef ENABLE_DUMPS // JS_DUMP_GC + if (check_dump_flag(rt, JS_DUMP_GC)) { + printf("GC: size=%zd\n", rt->malloc_state.malloc_size); + } +#endif + JS_RunGC(rt); + rt->malloc_gc_threshold = rt->malloc_state.malloc_size + + (rt->malloc_state.malloc_size >> 1); + } +} + +static size_t js_malloc_usable_size_unknown(const void *ptr) +{ + return 0; +} + +void *js_calloc_rt(JSRuntime *rt, size_t count, size_t size) +{ + void *ptr; + JSMallocState *s; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(count != 0 && size != 0); + + if (size > 0) + if (unlikely(count != (count * size) / size)) + return NULL; + + s = &rt->malloc_state; + /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ + if (unlikely(s->malloc_size + (count * size) > s->malloc_limit - 1)) + return NULL; + + ptr = rt->mf.js_calloc(s->opaque, count, size); + if (!ptr) + return NULL; + + s->malloc_count++; + s->malloc_size += rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + return ptr; +} + +void *js_malloc_rt(JSRuntime *rt, size_t size) +{ + void *ptr; + JSMallocState *s; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(size != 0); + + s = &rt->malloc_state; + /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ + if (unlikely(s->malloc_size + size > s->malloc_limit - 1)) + return NULL; + + ptr = rt->mf.js_malloc(s->opaque, size); + if (!ptr) + return NULL; + + s->malloc_count++; + s->malloc_size += rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + return ptr; +} + +void js_free_rt(JSRuntime *rt, void *ptr) +{ + JSMallocState *s; + + if (!ptr) + return; + + s = &rt->malloc_state; + s->malloc_count--; + s->malloc_size -= rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + rt->mf.js_free(s->opaque, ptr); +} + +void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) +{ + size_t old_size; + JSMallocState *s; + + if (!ptr) { + if (size == 0) + return NULL; + return js_malloc_rt(rt, size); + } + if (unlikely(size == 0)) { + js_free_rt(rt, ptr); + return NULL; + } + old_size = rt->mf.js_malloc_usable_size(ptr); + s = &rt->malloc_state; + /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ + if (s->malloc_size + size - old_size > s->malloc_limit - 1) + return NULL; + + ptr = rt->mf.js_realloc(s->opaque, ptr, size); + if (!ptr) + return NULL; + + s->malloc_size += rt->mf.js_malloc_usable_size(ptr) - old_size; + return ptr; +} + +size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) +{ + return rt->mf.js_malloc_usable_size(ptr); +} + +/** + * This used to be implemented as malloc + memset, but using calloc + * yields better performance in initial, bursty allocations, something useful + * for QuickJS. + * + * More information: https://github.com/quickjs-ng/quickjs/pull/519 + */ +void *js_mallocz_rt(JSRuntime *rt, size_t size) +{ + return js_calloc_rt(rt, 1, size); +} + +/* Throw out of memory in case of error */ +void *js_calloc(JSContext *ctx, size_t count, size_t size) +{ + void *ptr; + ptr = js_calloc_rt(ctx->rt, count, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +/* Throw out of memory in case of error */ +void *js_malloc(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_malloc_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +/* Throw out of memory in case of error */ +void *js_mallocz(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_mallocz_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +void js_free(JSContext *ctx, void *ptr) +{ + js_free_rt(ctx->rt, ptr); +} + +/* Throw out of memory in case of error */ +void *js_realloc(JSContext *ctx, void *ptr, size_t size) +{ + void *ret; + ret = js_realloc_rt(ctx->rt, ptr, size); + if (unlikely(!ret && size != 0)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ret; +} + +/* store extra allocated size in *pslack if successful */ +void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack) +{ + void *ret; + ret = js_realloc_rt(ctx->rt, ptr, size); + if (unlikely(!ret && size != 0)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + if (pslack) { + size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret); + *pslack = (new_size > size) ? new_size - size : 0; + } + return ret; +} + +size_t js_malloc_usable_size(JSContext *ctx, const void *ptr) +{ + return js_malloc_usable_size_rt(ctx->rt, ptr); +} + +/* Throw out of memory exception in case of error */ +char *js_strndup(JSContext *ctx, const char *s, size_t n) +{ + char *ptr; + ptr = js_malloc(ctx, n + 1); + if (ptr) { + memcpy(ptr, s, n); + ptr[n] = '\0'; + } + return ptr; +} + +char *js_strdup(JSContext *ctx, const char *str) +{ + return js_strndup(ctx, str, strlen(str)); +} + +static no_inline int js_realloc_array(JSContext *ctx, void **parray, + int elem_size, int *psize, int req_size) +{ + int new_size; + size_t slack; + void *new_array; + /* XXX: potential arithmetic overflow */ + new_size = max_int(req_size, *psize * 3 / 2); + new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack); + if (!new_array) + return -1; + new_size += slack / elem_size; + *psize = new_size; + *parray = new_array; + return 0; +} + +/* resize the array and update its size if req_size > *psize */ +static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size, + int *psize, int req_size) +{ + if (unlikely(req_size > *psize)) + return js_realloc_array(ctx, parray, elem_size, psize, req_size); + else + return 0; +} + +static void *js_dbuf_realloc(void *ctx, void *ptr, size_t size) +{ + return js_realloc(ctx, ptr, size); +} + +static inline void js_dbuf_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, ctx, js_dbuf_realloc); +} + +static inline int is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static inline int string_get(JSString *p, int idx) { + return p->is_wide_char ? str16(p)[idx] : str8(p)[idx]; +} + +typedef struct JSClassShortDef { + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; +} JSClassShortDef; + +static JSClassShortDef const js_std_class_def[] = { + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */ + { JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */ + { JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */ + { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */ + { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */ + { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */ + { JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */ + { JS_ATOM_Arguments, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARGUMENTS */ + { JS_ATOM_Arguments, NULL, NULL }, /* JS_CLASS_MAPPED_ARGUMENTS */ + { JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */ + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_MODULE_NS */ + { JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */ + { JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */ + { JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */ + { JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */ + { JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */ + { JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */ + { JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ + { JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_ARRAY_BUFFER */ + { JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_SHARED_ARRAY_BUFFER */ + { JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */ + { JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT8_ARRAY */ + { JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8_ARRAY */ + { JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT16_ARRAY */ + { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */ + { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */ + { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */ + { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */ + { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */ + { JS_ATOM_Float16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT16_ARRAY */ + { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */ + { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */ + { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */ + { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */ + { JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */ + { JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */ + { JS_ATOM_WeakMap, js_map_finalizer, NULL }, /* JS_CLASS_WEAKMAP */ + { JS_ATOM_WeakSet, js_map_finalizer, NULL }, /* JS_CLASS_WEAKSET */ + { JS_ATOM_Iterator, NULL, NULL }, /* JS_CLASS_ITERATOR */ + { JS_ATOM_IteratorConcat, js_iterator_concat_finalizer, js_iterator_concat_mark }, /* JS_CLASS_ITERATOR_CONCAT */ + { JS_ATOM_IteratorHelper, js_iterator_helper_finalizer, js_iterator_helper_mark }, /* JS_CLASS_ITERATOR_HELPER */ + { JS_ATOM_IteratorWrap, js_iterator_wrap_finalizer, js_iterator_wrap_mark }, /* JS_CLASS_ITERATOR_WRAP */ + { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */ + { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ + { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ + { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ + { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ + { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ +}; + +static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, + int start, int count) +{ + JSClassDef cm_s, *cm = &cm_s; + int i, class_id; + + for(i = 0; i < count; i++) { + class_id = i + start; + memset(cm, 0, sizeof(*cm)); + cm->finalizer = tab[i].finalizer; + cm->gc_mark = tab[i].gc_mark; + if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0) + return -1; + } + return 0; +} + +/* Uses code from LLVM project. */ +static inline uintptr_t js_get_stack_pointer(void) +{ +#if defined(__clang__) || defined(__GNUC__) + return (uintptr_t)__builtin_frame_address(0); +#elif defined(_MSC_VER) + return (uintptr_t)_AddressOfReturnAddress(); +#else + char CharOnStack = 0; + // The volatile store here is intended to escape the local variable, to + // prevent the compiler from optimizing CharOnStack into anything other + // than a char on the stack. + // + // Tested on: MSVC 2015 - 2019, GCC 4.9 - 9, Clang 3.2 - 9, ICC 13 - 19. + char *volatile Ptr = &CharOnStack; + return (uintptr_t) Ptr; +#endif +} + +static inline bool js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) +{ + uintptr_t sp; + sp = js_get_stack_pointer() - alloca_size; + return unlikely(sp < rt->stack_limit); +} + +JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) +{ + JSRuntime *rt; + JSMallocState ms; + + memset(&ms, 0, sizeof(ms)); + ms.opaque = opaque; + ms.malloc_limit = 0; + + rt = mf->js_calloc(opaque, 1, sizeof(JSRuntime)); + if (!rt) + return NULL; + rt->mf = *mf; + if (!rt->mf.js_malloc_usable_size) { + /* use dummy function if none provided */ + rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; + } + /* Inline what js_malloc_rt does since we cannot use it here. */ + ms.malloc_count++; + ms.malloc_size += rt->mf.js_malloc_usable_size(rt) + MALLOC_OVERHEAD; + rt->malloc_state = ms; + rt->malloc_gc_threshold = 256 * 1024; + + init_list_head(&rt->context_list); + init_list_head(&rt->gc_obj_list); + init_list_head(&rt->gc_zero_ref_count_list); + rt->gc_phase = JS_GC_PHASE_NONE; + +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + init_list_head(&rt->string_list); +#endif + init_list_head(&rt->job_list); + init_list_head(&rt->weakref_keepalive_list); + + if (JS_InitAtoms(rt)) + goto fail; + + /* create the object, array and function classes */ + if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT, + countof(js_std_class_def)) < 0) + goto fail; + rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods; + rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods; + rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods; + + rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function; + rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_call_c_function_data; + rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function; + rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_call_generator_function; + if (init_shape_hash(rt)) + goto fail; + + rt->js_class_id_alloc = JS_CLASS_INIT_COUNT; + + rt->stack_size = JS_DEFAULT_STACK_SIZE; +#ifdef __wasi__ + rt->stack_size = 0; +#endif + + JS_UpdateStackTop(rt); + + rt->current_exception = JS_UNINITIALIZED; + + return rt; + fail: + JS_FreeRuntime(rt); + return NULL; +} + +void *JS_GetRuntimeOpaque(JSRuntime *rt) +{ + return rt->user_opaque; +} + +void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque) +{ + rt->user_opaque = opaque; +} + +int JS_AddRuntimeFinalizer(JSRuntime *rt, JSRuntimeFinalizer *finalizer, + void *arg) +{ + JSRuntimeFinalizerState *fs = js_malloc_rt(rt, sizeof(*fs)); + if (!fs) + return -1; + fs->next = rt->finalizers; + fs->finalizer = finalizer; + fs->arg = arg; + rt->finalizers = fs; + return 0; +} + +static void *js_def_calloc(void *opaque, size_t count, size_t size) +{ + return calloc(count, size); +} + +static void *js_def_malloc(void *opaque, size_t size) +{ + return malloc(size); +} + +static void js_def_free(void *opaque, void *ptr) +{ + free(ptr); +} + +static void *js_def_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +static const JSMallocFunctions def_malloc_funcs = { + js_def_calloc, + js_def_malloc, + js_def_free, + js_def_realloc, + js__malloc_usable_size +}; + +JSRuntime *JS_NewRuntime(void) +{ + return JS_NewRuntime2(&def_malloc_funcs, NULL); +} + +void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) +{ + rt->malloc_state.malloc_limit = limit; +} + +void JS_SetDumpFlags(JSRuntime *rt, uint64_t flags) +{ +#ifdef ENABLE_DUMPS + rt->dump_flags = flags; +#endif +} + +uint64_t JS_GetDumpFlags(JSRuntime *rt) +{ +#ifdef ENABLE_DUMPS + return rt->dump_flags; +#else + return 0; +#endif +} + +size_t JS_GetGCThreshold(JSRuntime *rt) { + return rt->malloc_gc_threshold; +} + +/* use -1 to disable automatic GC */ +void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold) +{ + rt->malloc_gc_threshold = gc_threshold; +} + +#define malloc(s) malloc_is_forbidden(s) +#define free(p) free_is_forbidden(p) +#define realloc(p,s) realloc_is_forbidden(p,s) + +void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque) +{ + rt->interrupt_handler = cb; + rt->interrupt_opaque = opaque; +} + +void JS_SetCanBlock(JSRuntime *rt, bool can_block) +{ + rt->can_block = can_block; +} + +void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, + const JSSharedArrayBufferFunctions *sf) +{ + rt->sab_funcs = *sf; +} + +/* return 0 if OK, < 0 if exception */ +int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = ctx->rt; + JSJobEntry *e; + int i; + + assert(!rt->in_free); + + e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); + if (!e) + return -1; + e->ctx = ctx; + e->job_func = job_func; + e->argc = argc; + for(i = 0; i < argc; i++) { + e->argv[i] = js_dup(argv[i]); + } + list_add_tail(&e->link, &rt->job_list); + return 0; +} + +bool JS_IsJobPending(JSRuntime *rt) +{ + return !list_empty(&rt->job_list); +} + +/* return < 0 if exception, 0 if no job pending, 1 if a job was + executed successfully. the context of the job is stored in '*pctx' */ +int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) +{ + JSContext *ctx; + JSJobEntry *e; + JSValue res; + int i, ret; + + if (list_empty(&rt->job_list)) { + *pctx = NULL; + return 0; + } + + /* get the first pending job and execute it */ + e = list_entry(rt->job_list.next, JSJobEntry, link); + list_del(&e->link); + ctx = e->ctx; + res = e->job_func(e->ctx, e->argc, vc(e->argv)); + for(i = 0; i < e->argc; i++) + JS_FreeValue(ctx, e->argv[i]); + if (JS_IsException(res)) + ret = -1; + else + ret = 1; + JS_FreeValue(ctx, res); + js_free(ctx, e); + *pctx = ctx; + return ret; +} + +static inline uint32_t atom_get_free(const JSAtomStruct *p) +{ + return (uintptr_t)p >> 1; +} + +static inline bool atom_is_free(const JSAtomStruct *p) +{ + return (uintptr_t)p & 1; +} + +static inline JSAtomStruct *atom_set_free(uint32_t v) +{ + return (JSAtomStruct *)(((uintptr_t)v << 1) | 1); +} + +/* Note: the string contents are uninitialized */ +static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char) +{ + JSString *str; + str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); + if (unlikely(!str)) + return NULL; + str->header.ref_count = 1; + str->is_wide_char = is_wide_char; + str->len = max_len; + str->kind = JS_STRING_KIND_NORMAL; + str->atom_type = 0; + str->hash = 0; /* optional but costless */ + str->hash_next = 0; /* optional */ +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&str->link, &rt->string_list); +#endif + return str; +} + +static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) +{ + JSString *p; + p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char); + if (unlikely(!p)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return p; +} + +static inline void js_free_string0(JSRuntime *rt, JSString *str); + +/* same as JS_FreeValueRT() but faster */ +static inline void js_free_string(JSRuntime *rt, JSString *str) +{ + if (--str->header.ref_count <= 0) + js_free_string0(rt, str); +} + +static inline void js_free_string0(JSRuntime *rt, JSString *str) +{ + if (str->atom_type) { + JS_FreeAtomStruct(rt, str); + } else { +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_del(&str->link); +#endif + if (str->kind == JS_STRING_KIND_SLICE) { + JSStringSlice *slice = (void *)&str[1]; + js_free_string(rt, slice->parent); // safe, recurses only 1 level + } + js_free_rt(rt, str); + } +} + +void JS_SetRuntimeInfo(JSRuntime *rt, const char *s) +{ + if (rt) + rt->rt_info = s; +} + +void JS_FreeRuntime(JSRuntime *rt) +{ + struct list_head *el, *el1; + int i; + + rt->in_free = true; + JS_FreeValueRT(rt, rt->current_exception); + + list_for_each_safe(el, el1, &rt->job_list) { + JSJobEntry *e = list_entry(el, JSJobEntry, link); + for(i = 0; i < e->argc; i++) + JS_FreeValueRT(rt, e->argv[i]); + js_free_rt(rt, e); + } + init_list_head(&rt->job_list); + JS_ClearWeakRefKeepAlives(rt); + + JS_RunGC(rt); + +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + /* leaking objects */ + if (check_dump_flag(rt, JS_DUMP_LEAKS)) { + bool header_done; + JSGCObjectHeader *p; + int count; + + /* remove the internal refcounts to display only the object + referenced externally */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + p->mark = 0; + } + gc_decref(rt); + + header_done = false; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (p->ref_count != 0) { + if (!header_done) { + printf("Object leaks:\n"); + JS_DumpObjectHeader(rt); + header_done = true; + } + JS_DumpGCObject(rt, p); + } + } + + count = 0; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (p->ref_count == 0) { + count++; + } + } + if (count != 0) + printf("Secondary object leaks: %d\n", count); + } +#endif + + assert(list_empty(&rt->gc_obj_list)); + + /* free the classes */ + for(i = 0; i < rt->class_count; i++) { + JSClass *cl = &rt->class_array[i]; + if (cl->class_id != 0) { + JS_FreeAtomRT(rt, cl->class_name); + } + } + js_free_rt(rt, rt->class_array); + +#ifdef ENABLE_DUMPS // JS_DUMP_ATOM_LEAKS + /* only the atoms defined in JS_InitAtoms() should be left */ + if (check_dump_flag(rt, JS_DUMP_ATOM_LEAKS)) { + bool header_done = false; + + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p) /* && p->str*/) { + if (i >= JS_ATOM_END || p->header.ref_count != 1) { + if (!header_done) { + header_done = true; + if (rt->rt_info) { + printf("%s:1: atom leakage:", rt->rt_info); + } else { + printf("Atom leaks:\n" + " %6s %6s %s\n", + "ID", "REFCNT", "NAME"); + } + } + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u %6u ", i, p->header.ref_count); + } + switch (p->atom_type) { + case JS_ATOM_TYPE_STRING: + JS_DumpString(rt, p); + break; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + printf("Symbol.for("); + JS_DumpString(rt, p); + printf(")"); + break; + case JS_ATOM_TYPE_SYMBOL: + if (p->hash == JS_ATOM_HASH_SYMBOL) { + printf("Symbol("); + JS_DumpString(rt, p); + printf(")"); + } else { + printf("Private("); + JS_DumpString(rt, p); + printf(")"); + } + break; + } + if (rt->rt_info) { + printf(":%u", p->header.ref_count); + } else { + printf("\n"); + } + } + } + } + if (rt->rt_info && header_done) + printf("\n"); + } +#endif + + /* free the atoms */ + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + } + } + js_free_rt(rt, rt->atom_array); + js_free_rt(rt, rt->atom_hash); + js_free_rt(rt, rt->shape_hash); +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + if (check_dump_flag(rt, JS_DUMP_LEAKS) && !list_empty(&rt->string_list)) { + if (rt->rt_info) { + printf("%s:1: string leakage:", rt->rt_info); + } else { + printf("String leaks:\n" + " %6s %s\n", + "REFCNT", "VALUE"); + } + list_for_each_safe(el, el1, &rt->string_list) { + JSString *str = list_entry(el, JSString, link); + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u ", str->header.ref_count); + } + JS_DumpString(rt, str); + if (rt->rt_info) { + printf(":%u", str->header.ref_count); + } else { + printf("\n"); + } + list_del(&str->link); + js_free_rt(rt, str); + } + if (rt->rt_info) + printf("\n"); + } +#endif + + while (rt->finalizers) { + JSRuntimeFinalizerState *fs = rt->finalizers; + rt->finalizers = fs->next; + fs->finalizer(rt, fs->arg); + js_free_rt(rt, fs); + } + +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + if (check_dump_flag(rt, JS_DUMP_LEAKS)) { + JSMallocState *s = &rt->malloc_state; + if (s->malloc_count > 1) { + if (rt->rt_info) + printf("%s:1: ", rt->rt_info); + printf("Memory leak: %zd bytes lost in %zd block%s\n", + s->malloc_size - sizeof(JSRuntime), + s->malloc_count - 1, &"s"[s->malloc_count == 2]); + } + } +#endif + + { + JSMallocState *ms = &rt->malloc_state; + rt->mf.js_free(ms->opaque, rt); + } +} + +JSContext *JS_NewContextRaw(JSRuntime *rt) +{ + JSContext *ctx; + int i; + + ctx = js_mallocz_rt(rt, sizeof(JSContext)); + if (!ctx) + return NULL; + ctx->header.ref_count = 1; + add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); + + ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * + rt->class_count); + if (!ctx->class_proto) { + js_free_rt(rt, ctx); + return NULL; + } + ctx->rt = rt; + list_add_tail(&ctx->link, &rt->context_list); + for(i = 0; i < rt->class_count; i++) + ctx->class_proto[i] = JS_NULL; + ctx->array_ctor = JS_NULL; + ctx->iterator_ctor = JS_NULL; + ctx->iterator_ctor_getset = JS_NULL; + ctx->regexp_ctor = JS_NULL; + ctx->promise_ctor = JS_NULL; + ctx->error_ctor = JS_NULL; + ctx->error_back_trace = JS_UNDEFINED; + ctx->error_prepare_stack = JS_UNDEFINED; + ctx->error_stack_trace_limit = js_int32(10); + init_list_head(&ctx->loaded_modules); + + JS_AddIntrinsicBasicObjects(ctx); + return ctx; +} + +JSContext *JS_NewContext(JSRuntime *rt) +{ + JSContext *ctx; + + ctx = JS_NewContextRaw(rt); + if (!ctx) + return NULL; + + JS_AddIntrinsicBaseObjects(ctx); + JS_AddIntrinsicDate(ctx); + JS_AddIntrinsicEval(ctx); + JS_AddIntrinsicRegExp(ctx); + JS_AddIntrinsicJSON(ctx); + JS_AddIntrinsicProxy(ctx); + JS_AddIntrinsicMapSet(ctx); + JS_AddIntrinsicTypedArrays(ctx); + JS_AddIntrinsicPromise(ctx); + JS_AddIntrinsicBigInt(ctx); + JS_AddIntrinsicWeakRef(ctx); + JS_AddIntrinsicDOMException(ctx); + + JS_AddPerformance(ctx); + + return ctx; +} + +void *JS_GetContextOpaque(JSContext *ctx) +{ + return ctx->user_opaque; +} + +void JS_SetContextOpaque(JSContext *ctx, void *opaque) +{ + ctx->user_opaque = opaque; +} + +/* set the new value and free the old value after (freeing the value + can reallocate the object data) */ +static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val) +{ + JSValue old_val; + old_val = *pval; + *pval = new_val; + JS_FreeValue(ctx, old_val); +} + +void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj) +{ + assert(class_id < ctx->rt->class_count); + set_value(ctx, &ctx->class_proto[class_id], obj); +} + +JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id) +{ + assert(class_id < ctx->rt->class_count); + return js_dup(ctx->class_proto[class_id]); +} + +JSValue JS_GetFunctionProto(JSContext *ctx) +{ + return js_dup(ctx->function_proto); +} + +typedef enum JSFreeModuleEnum { + JS_FREE_MODULE_ALL, + JS_FREE_MODULE_NOT_RESOLVED, +} JSFreeModuleEnum; + +/* XXX: would be more efficient with separate module lists */ +static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) +{ + struct list_head *el, *el1; + list_for_each_safe(el, el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + if (flag == JS_FREE_MODULE_ALL || + (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) { + js_free_module_def(ctx, m); + } + } +} + +JSContext *JS_DupContext(JSContext *ctx) +{ + ctx->header.ref_count++; + return ctx; +} + +/* used by the GC */ +static void JS_MarkContext(JSRuntime *rt, JSContext *ctx, + JS_MarkFunc *mark_func) +{ + int i; + struct list_head *el; + + /* modules are not seen by the GC, so we directly mark the objects + referenced by each module */ + list_for_each(el, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + js_mark_module_def(rt, m, mark_func); + } + + JS_MarkValue(rt, ctx->global_obj, mark_func); + JS_MarkValue(rt, ctx->global_var_obj, mark_func); + + JS_MarkValue(rt, ctx->throw_type_error, mark_func); + JS_MarkValue(rt, ctx->eval_obj, mark_func); + + JS_MarkValue(rt, ctx->array_proto_values, mark_func); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_MarkValue(rt, ctx->native_error_proto[i], mark_func); + } + JS_MarkValue(rt, ctx->error_ctor, mark_func); + JS_MarkValue(rt, ctx->error_back_trace, mark_func); + JS_MarkValue(rt, ctx->error_prepare_stack, mark_func); + JS_MarkValue(rt, ctx->error_stack_trace_limit, mark_func); + for(i = 0; i < rt->class_count; i++) { + JS_MarkValue(rt, ctx->class_proto[i], mark_func); + } + JS_MarkValue(rt, ctx->iterator_ctor, mark_func); + JS_MarkValue(rt, ctx->iterator_ctor_getset, mark_func); + JS_MarkValue(rt, ctx->async_iterator_proto, mark_func); + JS_MarkValue(rt, ctx->promise_ctor, mark_func); + JS_MarkValue(rt, ctx->array_ctor, mark_func); + JS_MarkValue(rt, ctx->regexp_ctor, mark_func); + JS_MarkValue(rt, ctx->function_ctor, mark_func); + JS_MarkValue(rt, ctx->function_proto, mark_func); + + if (ctx->array_shape) + mark_func(rt, &ctx->array_shape->header); +} + +void JS_FreeContext(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + int i; + + if (--ctx->header.ref_count > 0) + return; + assert(ctx->header.ref_count == 0); + +#ifdef ENABLE_DUMPS // JS_DUMP_ATOMS + if (check_dump_flag(rt, JS_DUMP_ATOMS)) + JS_DumpAtoms(ctx->rt); +#endif +#ifdef ENABLE_DUMPS // JS_DUMP_SHAPES + if (check_dump_flag(rt, JS_DUMP_SHAPES)) + JS_DumpShapes(ctx->rt); +#endif +#ifdef ENABLE_DUMPS // JS_DUMP_OBJECTS + if (check_dump_flag(rt, JS_DUMP_OBJECTS)) { + struct list_head *el; + JSGCObjectHeader *p; + printf("JSObjects: {\n"); + JS_DumpObjectHeader(ctx->rt); + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + JS_DumpGCObject(rt, p); + } + printf("}\n"); + } +#endif +#ifdef ENABLE_DUMPS // JS_DUMP_MEM + if (check_dump_flag(rt, JS_DUMP_MEM)) { + JSMemoryUsage stats; + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(stdout, &stats, rt); + } +#endif + + js_free_modules(ctx, JS_FREE_MODULE_ALL); + + JS_FreeValue(ctx, ctx->global_obj); + JS_FreeValue(ctx, ctx->global_var_obj); + + JS_FreeValue(ctx, ctx->throw_type_error); + JS_FreeValue(ctx, ctx->eval_obj); + + JS_FreeValue(ctx, ctx->array_proto_values); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_FreeValue(ctx, ctx->native_error_proto[i]); + } + JS_FreeValue(ctx, ctx->error_ctor); + JS_FreeValue(ctx, ctx->error_back_trace); + JS_FreeValue(ctx, ctx->error_prepare_stack); + JS_FreeValue(ctx, ctx->error_stack_trace_limit); + for(i = 0; i < rt->class_count; i++) { + JS_FreeValue(ctx, ctx->class_proto[i]); + } + js_free_rt(rt, ctx->class_proto); + JS_FreeValue(ctx, ctx->iterator_ctor); + JS_FreeValue(ctx, ctx->iterator_ctor_getset); + JS_FreeValue(ctx, ctx->async_iterator_proto); + JS_FreeValue(ctx, ctx->promise_ctor); + JS_FreeValue(ctx, ctx->array_ctor); + JS_FreeValue(ctx, ctx->regexp_ctor); + JS_FreeValue(ctx, ctx->function_ctor); + JS_FreeValue(ctx, ctx->function_proto); + + js_free_shape_null(ctx->rt, ctx->array_shape); + + list_del(&ctx->link); + remove_gc_object(&ctx->header); + js_free_rt(ctx->rt, ctx); +} + +JSRuntime *JS_GetRuntime(JSContext *ctx) +{ + return ctx->rt; +} + +static void update_stack_limit(JSRuntime *rt) +{ +#if defined(__wasi__) + rt->stack_limit = 0; /* no limit */ +#else + if (rt->stack_size == 0) { + rt->stack_limit = 0; /* no limit */ + } else { + rt->stack_limit = rt->stack_top - rt->stack_size; + } +#endif +} + +void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size) +{ + rt->stack_size = stack_size; + update_stack_limit(rt); +} + +void JS_UpdateStackTop(JSRuntime *rt) +{ + rt->stack_top = js_get_stack_pointer(); + update_stack_limit(rt); +} + +static inline bool is_strict_mode(JSContext *ctx) +{ + JSStackFrame *sf = ctx->rt->current_stack_frame; + return sf && sf->is_strict_mode; +} + +/* JSAtom support */ + +#define JS_ATOM_TAG_INT (1U << 31) +#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) +#define JS_ATOM_MAX ((1U << 30) - 1) + +/* return the max count from the hash size */ +#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) + +static inline bool __JS_AtomIsConst(JSAtom v) +{ + return (int32_t)v < JS_ATOM_END; +} + +static inline bool __JS_AtomIsTaggedInt(JSAtom v) +{ + return (v & JS_ATOM_TAG_INT) != 0; +} + +static inline JSAtom __JS_AtomFromUInt32(uint32_t v) +{ + return v | JS_ATOM_TAG_INT; +} + +static inline uint32_t __JS_AtomToUInt32(JSAtom atom) +{ + return atom & ~JS_ATOM_TAG_INT; +} + +static inline int is_num(int c) +{ + return c >= '0' && c <= '9'; +} + +/* return true if the string is a number n with 0 <= n <= 2^32-1 */ +static inline bool is_num_string(uint32_t *pval, JSString *p) +{ + uint32_t n; + uint64_t n64; + int c, i, len; + + len = p->len; + if (len == 0 || len > 10) + return false; + c = string_get(p, 0); + if (is_num(c)) { + if (c == '0') { + if (len != 1) + return false; + n = 0; + } else { + n = c - '0'; + for(i = 1; i < len; i++) { + c = string_get(p, i); + if (!is_num(c)) + return false; + n64 = (uint64_t)n * 10 + (c - '0'); + if ((n64 >> 32) != 0) + return false; + n = n64; + } + } + *pval = n; + return true; + } else { + return false; + } +} + +/* XXX: could use faster version ? */ +static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h; +} + +static inline uint32_t hash_string16(const uint16_t *str, + size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h; +} + +static uint32_t hash_string(JSString *str, uint32_t h) +{ + if (str->is_wide_char) + h = hash_string16(str16(str), str->len, h); + else + h = hash_string8(str8(str), str->len, h); + return h; +} + +static __maybe_unused void JS_DumpString(JSRuntime *rt, JSString *p) +{ + int i, c, sep; + + if (p == NULL) { + printf(""); + return; + } + if (p->header.ref_count != 1) + printf("%d", p->header.ref_count); + if (p->is_wide_char) + putchar('L'); + sep = '\"'; + putchar(sep); + for(i = 0; i < p->len; i++) { + c = string_get(p, i); + if (c == sep || c == '\\') { + putchar('\\'); + putchar(c); + } else if (c >= ' ' && c <= 126) { + putchar(c); + } else if (c == '\n') { + putchar('\\'); + putchar('n'); + } else { + printf("\\u%04x", c); + } + } + putchar(sep); +} + +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) +{ + JSAtomStruct *p; + int h, i; + /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */ + printf("JSAtom count=%d size=%d hash_size=%d:\n", + rt->atom_count, rt->atom_size, rt->atom_hash_size); + printf("JSAtom hash table: {\n"); + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + if (h) { + printf(" %d:", i); + while (h) { + p = rt->atom_array[h]; + printf(" "); + JS_DumpString(rt, p); + h = p->hash_next; + } + printf("\n"); + } + } + printf("}\n"); + printf("JSAtom table: {\n"); + for(i = 0; i < rt->atom_size; i++) { + p = rt->atom_array[i]; + if (!atom_is_free(p)) { + printf(" %d: { %d %08x ", i, p->atom_type, p->hash); + if (!(p->len == 0 && p->is_wide_char != 0)) + JS_DumpString(rt, p); + printf(" %d }\n", p->hash_next); + } + } + printf("}\n"); +} + +static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size) +{ + JSAtomStruct *p; + uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash; + + assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */ + new_hash_mask = new_hash_size - 1; + new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size); + if (!new_hash) + return -1; + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + while (h != 0) { + p = rt->atom_array[h]; + hash_next1 = p->hash_next; + /* add in new hash table */ + j = p->hash & new_hash_mask; + p->hash_next = new_hash[j]; + new_hash[j] = h; + h = hash_next1; + } + } + js_free_rt(rt, rt->atom_hash); + rt->atom_hash = new_hash; + rt->atom_hash_size = new_hash_size; + rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size); + // JS_DumpAtoms(rt); + return 0; +} + +static int JS_InitAtoms(JSRuntime *rt) +{ + int i, len, atom_type; + const char *p; + + rt->atom_hash_size = 0; + rt->atom_hash = NULL; + rt->atom_count = 0; + rt->atom_size = 0; + rt->atom_free_index = 0; + if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */ + return -1; + + p = js_atom_init; + for(i = 1; i < JS_ATOM_END; i++) { + if (i == JS_ATOM_Private_brand) + atom_type = JS_ATOM_TYPE_PRIVATE; + else if (i >= JS_ATOM_Symbol_toPrimitive) + atom_type = JS_ATOM_TYPE_SYMBOL; + else + atom_type = JS_ATOM_TYPE_STRING; + len = strlen(p); + if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL) + return -1; + p = p + len + 1; + } + return 0; +} + +static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) +{ + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + p = rt->atom_array[v]; + p->header.ref_count++; + } + return v; +} + +JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + rt = ctx->rt; + p = rt->atom_array[v]; + p->header.ref_count++; + } + return v; +} + +static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return JS_ATOM_KIND_STRING; + p = rt->atom_array[v]; + switch(p->atom_type) { + case JS_ATOM_TYPE_STRING: + return JS_ATOM_KIND_STRING; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + return JS_ATOM_KIND_SYMBOL; + case JS_ATOM_TYPE_SYMBOL: + switch(p->hash) { + case JS_ATOM_HASH_SYMBOL: + return JS_ATOM_KIND_SYMBOL; + case JS_ATOM_HASH_PRIVATE: + return JS_ATOM_KIND_PRIVATE; + default: + abort(); + } + default: + abort(); + } + return (JSAtomKindEnum){-1}; // pacify compiler +} + +static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p) +{ + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p1; + + i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)]; + p1 = rt->atom_array[i]; + while (p1 != p) { + assert(i != 0); + i = p1->hash_next; + p1 = rt->atom_array[i]; + } + } + return i; +} + +/* string case (internal). Return JS_ATOM_NULL if error. 'str' is + freed. */ +static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + int len; + + if (atom_type < JS_ATOM_TYPE_SYMBOL) { + /* str is not NULL */ + if (str->atom_type == atom_type) { + /* str is the atom, return its index */ + i = js_get_atom_index(rt, str); + /* reduce string refcount and increase atom's unless constant */ + if (__JS_AtomIsConst(i)) + str->header.ref_count--; + return i; + } + /* try and locate an already registered atom */ + len = str->len; + h = hash_string(str, atom_type); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == atom_type && + p->len == len && + js_string_memcmp(p, str, len) == 0) { + if (!__JS_AtomIsConst(i)) + p->header.ref_count++; + goto done; + } + i = p->hash_next; + } + } else { + h1 = 0; /* avoid warning */ + if (atom_type == JS_ATOM_TYPE_SYMBOL) { + h = JS_ATOM_HASH_SYMBOL; + } else { + h = JS_ATOM_HASH_PRIVATE; + atom_type = JS_ATOM_TYPE_SYMBOL; + } + } + + if (rt->atom_free_index == 0) { + /* allow new atom entries */ + uint32_t new_size, start; + JSAtomStruct **new_array; + + /* alloc new with size progression 3/2: + 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 + preallocating space for predefined atoms (at least 195). + */ + new_size = max_int(211, rt->atom_size * 3 / 2); + if (new_size > JS_ATOM_MAX) + goto fail; + /* XXX: should use realloc2 to use slack space */ + new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size); + if (!new_array) + goto fail; + /* Note: the atom 0 is not used */ + start = rt->atom_size; + if (start == 0) { + /* JS_ATOM_NULL entry */ + p = js_mallocz_rt(rt, sizeof(JSAtomStruct)); + if (!p) { + js_free_rt(rt, new_array); + goto fail; + } + p->header.ref_count = 1; /* not refcounted */ + p->atom_type = JS_ATOM_TYPE_SYMBOL; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + new_array[0] = p; + rt->atom_count++; + start = 1; + } + rt->atom_size = new_size; + rt->atom_array = new_array; + rt->atom_free_index = start; + for(i = start; i < new_size; i++) { + uint32_t next; + if (i == (new_size - 1)) + next = 0; + else + next = i + 1; + rt->atom_array[i] = atom_set_free(next); + } + } + + if (str) { + if (str->atom_type == 0) { + p = str; + p->atom_type = atom_type; + } else { + p = js_malloc_rt(rt, sizeof(JSString) + + (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + if (unlikely(!p)) + goto fail; + p->header.ref_count = 1; + p->is_wide_char = str->is_wide_char; + p->len = str->len; + p->kind = JS_STRING_KIND_NORMAL; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + memcpy(str8(p), str8(str), (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + js_free_string(rt, str); + } + } else { + p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ + if (!p) + return JS_ATOM_NULL; + p->header.ref_count = 1; + p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ + p->len = 0; + p->kind = JS_STRING_KIND_NORMAL; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + } + + /* use an already free entry */ + i = rt->atom_free_index; + rt->atom_free_index = atom_get_free(rt->atom_array[i]); + rt->atom_array[i] = p; + + p->hash = h; + p->hash_next = i; /* atom_index */ + p->atom_type = atom_type; + p->first_weak_ref = NULL; + + rt->atom_count++; + + if (atom_type != JS_ATOM_TYPE_SYMBOL) { + p->hash_next = rt->atom_hash[h1]; + rt->atom_hash[h1] = i; + if (unlikely(rt->atom_count >= rt->atom_count_resize)) + JS_ResizeAtomHash(rt, rt->atom_hash_size * 2); + } + + // JS_DumpAtoms(rt); + return i; + + fail: + i = JS_ATOM_NULL; + done: + if (str) + js_free_string(rt, str); + return i; +} + +// XXX: `str` must be pure ASCII. No UTF-8 encoded strings +// XXX: `str` must not be the string representation of a small integer +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type) +{ + JSString *p; + p = js_alloc_string_rt(rt, len, 0); + if (!p) + return JS_ATOM_NULL; + memcpy(str8(p), str, len); + str8(p)[len] = '\0'; + return __JS_NewAtom(rt, p, atom_type); +} + +// XXX: `str` must be raw 8-bit contents. No UTF-8 encoded strings +static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, + int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + + h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == JS_ATOM_TYPE_STRING && + p->len == len && + p->is_wide_char == 0 && + memcmp(str8(p), str, len) == 0) { + if (!__JS_AtomIsConst(i)) + p->header.ref_count++; + return i; + } + i = p->hash_next; + } + return JS_ATOM_NULL; +} + +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) +{ + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p0, *p1; + uint32_t h0; + + h0 = p->hash & (rt->atom_hash_size - 1); + i = rt->atom_hash[h0]; + p1 = rt->atom_array[i]; + if (p1 == p) { + rt->atom_hash[h0] = p1->hash_next; + } else { + for(;;) { + assert(i != 0); + p0 = p1; + i = p1->hash_next; + p1 = rt->atom_array[i]; + if (p1 == p) { + p0->hash_next = p1->hash_next; + break; + } + } + } + } + /* insert in free atom list */ + rt->atom_array[i] = atom_set_free(rt->atom_free_index); + rt->atom_free_index = i; + if (unlikely(p->first_weak_ref)) { + reset_weak_ref(rt, &p->first_weak_ref); + } + /* free the string structure */ +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + rt->atom_count--; + assert(rt->atom_count >= 0); +} + +static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) +{ + JSAtomStruct *p; + + p = rt->atom_array[i]; + if (--p->header.ref_count > 0) + return; + JS_FreeAtomStruct(rt, p); +} + +/* Warning: 'p' is freed */ +static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p) +{ + JSRuntime *rt = ctx->rt; + uint32_t n; + if (is_num_string(&n, p)) { + if (n <= JS_ATOM_MAX_INT) { + js_free_string(rt, p); + return __JS_AtomFromUInt32(n); + } + } + /* XXX: should generate an exception */ + return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING); +} + +/* `str` may be pure ASCII or UTF-8 encoded */ +JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) +{ + JSValue val; + + if (len == 0 || !is_digit(*str)) { + // TODO(chqrlie): this does not work if `str` has UTF-8 encoded contents + // bug example: `({ "\u00c3\u00a9": 1 }).\u00e9` evaluates to `1`. + JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING); + if (atom) + return atom; + } + val = JS_NewStringLen(ctx, str, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val)); +} + +/* `str` may be pure ASCII or UTF-8 encoded */ +JSAtom JS_NewAtom(JSContext *ctx, const char *str) +{ + return JS_NewAtomLen(ctx, str, strlen(str)); +} + +JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n) +{ + if (n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32(n); + } else { + char buf[16]; + size_t len = u32toa(buf, n); + JSValue val = js_new_string8_len(ctx, buf, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n) +{ + if ((uint64_t)n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32((uint32_t)n); + } else { + char buf[24]; + size_t len = i64toa(buf, n); + JSValue val = js_new_string8_len(ctx, buf, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +/* 'p' is freed */ +static JSValue JS_NewSymbolInternal(JSContext *ctx, JSString *p, int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSAtom atom; + atom = __JS_NewAtom(rt, p, atom_type); + if (atom == JS_ATOM_NULL) + return JS_ThrowOutOfMemory(ctx); + return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]); +} + +/* descr must be a non-numeric string atom */ +static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr, + int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSString *p; + + assert(!__JS_AtomIsTaggedInt(descr)); + assert(descr < rt->atom_size); + p = rt->atom_array[descr]; + js_dup(JS_MKPTR(JS_TAG_STRING, p)); + return JS_NewSymbolInternal(ctx, p, atom_type); +} + +/* `description` may be pure ASCII or UTF-8 encoded */ +JSValue JS_NewSymbol(JSContext *ctx, const char *description, bool is_global) +{ + JSAtom atom = JS_NewAtom(ctx, description); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + return JS_NewSymbolFromAtom(ctx, atom, is_global ? JS_ATOM_TYPE_GLOBAL_SYMBOL : JS_ATOM_TYPE_SYMBOL); +} + +#define ATOM_GET_STR_BUF_SIZE 64 + +static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, + JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom)); + } else if (atom == JS_ATOM_NULL) { + snprintf(buf, buf_size, ""); + } else if (atom >= rt->atom_size) { + assert(atom < rt->atom_size); + snprintf(buf, buf_size, "", atom); + } else { + JSAtomStruct *p = rt->atom_array[atom]; + *buf = '\0'; + if (atom_is_free(p)) { + snprintf(buf, buf_size, "", atom); + } else if (p != NULL) { + JSString *str = p; + if (str->is_wide_char) { + /* encode surrogates correctly */ + utf8_encode_buf16(buf, buf_size, str16(str), str->len); + } else { + utf8_encode_buf8(buf, buf_size, str8(str), str->len); + } + } + } + return buf; +} + +static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom) +{ + return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom); +} + +static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, bool force_string) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + + if (__JS_AtomIsTaggedInt(atom)) { + size_t len = u32toa(buf, __JS_AtomToUInt32(atom)); + return js_new_string8_len(ctx, buf, len); + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING) { + goto ret_string; + } else if (force_string) { + if (p->len == 0 && p->is_wide_char != 0) { + /* no description string */ + p = rt->atom_array[JS_ATOM_empty_string]; + } + ret_string: + return js_dup(JS_MKPTR(JS_TAG_STRING, p)); + } else { + return js_dup(JS_MKPTR(JS_TAG_SYMBOL, p)); + } + } +} + +JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, false); +} + +JSValue JS_AtomToString(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, true); +} + +/* return true if the atom is an array index (i.e. 0 <= index <= + 2^32-2 and return its value */ +static bool JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + *pval = __JS_AtomToUInt32(atom); + return true; + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + uint32_t val; + + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING && + is_num_string(&val, p) && val != -1) { + *pval = val; + return true; + } else { + *pval = 0; + return false; + } + } +} + +/* This test must be fast if atom is not a numeric index (e.g. a + method name). Return JS_UNDEFINED if not a numeric + index. JS_EXCEPTION can also be returned. */ +static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) +{ + JSRuntime *rt = ctx->rt; + JSAtomStruct *p1; + JSString *p; + int c, len, ret; + JSValue num, str; + + if (__JS_AtomIsTaggedInt(atom)) + return js_int32(__JS_AtomToUInt32(atom)); + assert(atom < rt->atom_size); + p1 = rt->atom_array[atom]; + if (p1->atom_type != JS_ATOM_TYPE_STRING) + return JS_UNDEFINED; + p = p1; + len = p->len; + if (p->is_wide_char) { + const uint16_t *r = str16(p), *r_end = str16(p) + len; + if (r >= r_end) + return JS_UNDEFINED; + c = *r; + if (c == '-') { + if (r >= r_end) + return JS_UNDEFINED; + r++; + c = *r; + /* -0 case is specific */ + if (c == '0' && len == 2) + goto minus_zero; + } + /* XXX: should test NaN, but the tests do not check it */ + if (!is_num(c)) { + /* XXX: String should be normalized, therefore 8-bit only */ + const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' }; + if (!(c =='I' && (r_end - r) == 8 && + !memcmp(r + 1, nfinity16, sizeof(nfinity16)))) + return JS_UNDEFINED; + } + } else { + const uint8_t *r = str8(p), *r_end = str8(p) + len; + if (r >= r_end) + return JS_UNDEFINED; + c = *r; + if (c == '-') { + if (r >= r_end) + return JS_UNDEFINED; + r++; + c = *r; + /* -0 case is specific */ + if (c == '0' && len == 2) { + minus_zero: + return js_float64(-0.0); + } + } + if (!is_num(c)) { + if (!(c =='I' && (r_end - r) == 8 && + !memcmp(r + 1, "nfinity", 7))) + return JS_UNDEFINED; + } + } + /* this is ECMA CanonicalNumericIndexString primitive */ + num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p)); + if (JS_IsException(num)) + return num; + str = JS_ToString(ctx, num); + if (JS_IsException(str)) { + JS_FreeValue(ctx, num); + return str; + } + ret = js_string_eq(p, JS_VALUE_GET_STRING(str)); + JS_FreeValue(ctx, str); + if (ret) { + return num; + } else { + JS_FreeValue(ctx, num); + return JS_UNDEFINED; + } +} + +/* return -1 if exception or true/false */ +static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom) +{ + JSValue num; + num = JS_AtomIsNumericIndex1(ctx, atom); + if (likely(JS_IsUndefined(num))) + return false; + if (JS_IsException(num)) + return -1; + JS_FreeValue(ctx, num); + return true; +} + +void JS_FreeAtom(JSContext *ctx, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(ctx->rt, v); +} + +void JS_FreeAtomRT(JSRuntime *rt, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(rt, v); +} + +/* return true if 'v' is a symbol with a string description */ +static bool JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return false; + p = rt->atom_array[v]; + return (((p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash == JS_ATOM_HASH_SYMBOL) || + p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && + !(p->len == 0 && p->is_wide_char != 0)); +} + +static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + const char *p; + int i; + + /* XXX: should handle embedded null characters */ + /* XXX: should move encoding code to JS_AtomGetStr */ + p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); + for (i = 0; p[i]; i++) { + int c = (unsigned char)p[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) + break; + } + if (i > 0 && p[i] == '\0') { + printf("%s", p); + } else { + putchar('"'); + printf("%.*s", i, p); + for (; p[i]; i++) { + int c = (unsigned char)p[i]; + if (c == '\"' || c == '\\') { + putchar('\\'); + putchar(c); + } else if (c >= ' ' && c <= 126) { + putchar(c); + } else if (c == '\n') { + putchar('\\'); + putchar('n'); + } else { + printf("\\u%04x", c); + } + } + putchar('\"'); + } +} + +/* free with JS_FreeCString() */ +const char *JS_AtomToCStringLen(JSContext *ctx, size_t *plen, JSAtom atom) +{ + JSValue str; + const char *cstr; + + str = JS_AtomToString(ctx, atom); + if (JS_IsException(str)) { + if (plen) + *plen = 0; + return NULL; + } + cstr = JS_ToCStringLen(ctx, plen, str); + JS_FreeValue(ctx, str); + return cstr; +} + +#ifndef QJS_DISABLE_PARSER + +/* return a string atom containing name concatenated with str1 */ +/* `str1` may be pure ASCII or UTF-8 encoded */ +// TODO(chqrlie): use string concatenation instead of UTF-8 conversion +static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1) +{ + JSValue str; + JSAtom atom; + const char *cstr; + char *cstr2; + size_t len, len1; + + str = JS_AtomToString(ctx, name); + if (JS_IsException(str)) + return JS_ATOM_NULL; + cstr = JS_ToCStringLen(ctx, &len, str); + if (!cstr) + goto fail; + len1 = strlen(str1); + cstr2 = js_malloc(ctx, len + len1 + 1); + if (!cstr2) + goto fail; + memcpy(cstr2, cstr, len); + memcpy(cstr2 + len, str1, len1); + cstr2[len + len1] = '\0'; + atom = JS_NewAtomLen(ctx, cstr2, len + len1); + js_free(ctx, cstr2); + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return atom; + fail: + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return JS_ATOM_NULL; +} + +static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) +{ + char buf[16]; + size_t len; + + len = u32toa(buf, n); + buf[len] = '\0'; + return js_atom_concat_str(ctx, name, buf); +} + +#endif // QJS_DISABLE_PARSER + +static inline bool JS_IsEmptyString(JSValueConst v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0; +} + +/* JSClass support */ + +/* a new class ID is allocated if *pclass_id == 0, otherwise *pclass_id is left unchanged */ +JSClassID JS_NewClassID(JSRuntime *rt, JSClassID *pclass_id) +{ + JSClassID class_id = *pclass_id; + if (class_id == 0) { + class_id = rt->js_class_id_alloc++; + *pclass_id = class_id; + } + return class_id; +} + +JSClassID JS_GetClassID(JSValueConst v) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) + return JS_INVALID_CLASS_ID; + p = JS_VALUE_GET_OBJ(v); + return p->class_id; +} + +bool JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) +{ + return (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0); +} + +JSAtom JS_GetClassName(JSRuntime *rt, JSClassID class_id) +{ + if (JS_IsRegisteredClass(rt, class_id)) { + return JS_DupAtomRT(rt, rt->class_array[class_id].class_id); + } else { + return JS_ATOM_NULL; + } +} + +/* create a new object internal class. Return -1 if error, 0 if + OK. The finalizer can be NULL if none is needed. */ +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name) +{ + int new_size, i; + JSClass *cl, *new_class_array; + struct list_head *el; + + if (class_id >= (1 << 16)) + return -1; + if (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0) + return -1; + + if (class_id >= rt->class_count) { + new_size = max_int(JS_CLASS_INIT_COUNT, + max_int(class_id + 1, rt->class_count * 3 / 2)); + + /* reallocate the context class prototype array, if any */ + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSValue *new_tab; + new_tab = js_realloc_rt(rt, ctx->class_proto, + sizeof(ctx->class_proto[0]) * new_size); + if (!new_tab) + return -1; + for(i = rt->class_count; i < new_size; i++) + new_tab[i] = JS_NULL; + ctx->class_proto = new_tab; + } + /* reallocate the class array */ + new_class_array = js_realloc_rt(rt, rt->class_array, + sizeof(JSClass) * new_size); + if (!new_class_array) + return -1; + memset(new_class_array + rt->class_count, 0, + (new_size - rt->class_count) * sizeof(JSClass)); + rt->class_array = new_class_array; + rt->class_count = new_size; + } + cl = &rt->class_array[class_id]; + cl->class_id = class_id; + cl->class_name = JS_DupAtomRT(rt, name); + cl->finalizer = class_def->finalizer; + cl->gc_mark = class_def->gc_mark; + cl->call = class_def->call; + cl->exotic = class_def->exotic; + return 0; +} + +int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) +{ + int ret, len; + JSAtom name; + + // XXX: class_def->class_name must be raw 8-bit contents. No UTF-8 encoded strings + len = strlen(class_def->class_name); + name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) { + name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) + return -1; + } + ret = JS_NewClass1(rt, class_id, class_def, name); + JS_FreeAtomRT(rt, name); + return ret; +} + +static inline JSValue js_empty_string(JSRuntime *rt) +{ + JSAtomStruct *p = rt->atom_array[JS_ATOM_empty_string]; + return js_dup(JS_MKPTR(JS_TAG_STRING, p)); +} + +// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed +// XXX: no special case for len == 0 +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len) +{ + JSString *str; + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + memcpy(str8(str), buf, len); + str8(str)[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); +} + +// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed +// XXX: no special case for the empty string +static inline JSValue js_new_string8(JSContext *ctx, const char *str) +{ + return js_new_string8_len(ctx, str, strlen(str)); +} + +static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len) +{ + JSString *str; + str = js_alloc_string(ctx, len, 1); + if (!str) + return JS_EXCEPTION; + memcpy(str16(str), buf, len * 2); + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue js_new_string_char(JSContext *ctx, uint16_t c) +{ + if (c < 0x100) { + char ch8 = c; + return js_new_string8_len(ctx, &ch8, 1); + } else { + uint16_t ch16 = c; + return js_new_string16_len(ctx, &ch16, 1); + } +} + +static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) +{ + JSStringSlice *slice; + JSString *q; + int len; + + len = end - start; + if (start == 0 && end == p->len) { + return js_dup(JS_MKPTR(JS_TAG_STRING, p)); + } + if (len <= 0) { + return js_empty_string(ctx->rt); + } + if (len > (JS_STRING_SLICE_LEN_MAX >> p->is_wide_char)) { + if (p->kind == JS_STRING_KIND_SLICE) { + slice = (void *)&p[1]; + p = slice->parent; + start += slice->start >> p->is_wide_char; // bytes -> chars + } + // allocate as 16 bit wide string to avoid wastage; + // js_alloc_string allocates 1 byte extra for 8 bit strings; + q = js_alloc_string(ctx, sizeof(*slice)/2, /*is_wide_char*/true); + if (!q) + return JS_EXCEPTION; + q->is_wide_char = p->is_wide_char; + q->kind = JS_STRING_KIND_SLICE; + q->len = len; + slice = (void *)&q[1]; + slice->parent = p; + slice->start = start << p->is_wide_char; // chars -> bytes + p->header.ref_count++; + return JS_MKPTR(JS_TAG_STRING, q); + } + if (p->is_wide_char) { + JSString *str; + int i; + uint16_t c = 0; + for (i = start; i < end; i++) { + c |= str16(p)[i]; + } + if (c > 0xFF) + return js_new_string16_len(ctx, str16(p) + start, len); + + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + for (i = 0; i < len; i++) { + str8(str)[i] = str16(p)[start + i]; + } + str8(str)[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); + } else { + return js_new_string8_len(ctx, (const char *)(str8(p) + start), len); + } +} + +typedef struct StringBuffer { + JSContext *ctx; + JSString *str; + int len; + int size; + int is_wide_char; + int error_status; +} StringBuffer; + +/* It is valid to call string_buffer_end() and all string_buffer functions even + if string_buffer_init() or another string_buffer function returns an error. + If the error_status is set, string_buffer_end() returns JS_EXCEPTION. + */ +static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size, + int is_wide) +{ + s->ctx = ctx; + s->size = size; + s->len = 0; + s->is_wide_char = is_wide; + s->error_status = 0; + s->str = js_alloc_string(ctx, size, is_wide); + if (unlikely(!s->str)) { + s->size = 0; + return s->error_status = -1; + } +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + /* the StringBuffer may reallocate the JSString, only link it at the end */ + list_del(&s->str->link); +#endif + return 0; +} + +static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size) +{ + return string_buffer_init2(ctx, s, size, 0); +} + +static void string_buffer_free(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; +} + +static int string_buffer_set_error(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; + s->size = 0; + s->len = 0; + return s->error_status = -1; +} + +static no_inline int string_buffer_widen(StringBuffer *s, int size) +{ + JSString *str; + size_t slack; + int i; + + if (s->error_status) + return -1; + + str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack); + if (!str) + return string_buffer_set_error(s); + size += slack >> 1; + for(i = s->len; i-- > 0;) { + str16(str)[i] = str8(str)[i]; + } + s->is_wide_char = 1; + s->size = size; + s->str = str; + return 0; +} + +static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c) +{ + JSString *new_str; + int new_size; + size_t new_size_bytes, slack; + + if (s->error_status) + return -1; + + if (new_len > JS_STRING_LEN_MAX) { + JS_ThrowRangeError(s->ctx, "invalid string length"); + return string_buffer_set_error(s); + } + new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX); + if (!s->is_wide_char && c >= 0x100) { + return string_buffer_widen(s, new_size); + } + new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char; + new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack); + if (!new_str) + return string_buffer_set_error(s); + new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX); + s->size = new_size; + s->str = new_str; + return 0; +} + +static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + str16(s->str)[s->len++] = c; + } else if (c < 0x100) { + str8(s->str)[s->len++] = c; + } else { + if (string_buffer_widen(s, s->size)) + return -1; + str16(s->str)[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xff */ +static int string_buffer_putc8(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + str16(s->str)[s->len++] = c; + } else { + str8(s->str)[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xffff */ +static int string_buffer_putc16(StringBuffer *s, uint32_t c) +{ + if (likely(s->len < s->size)) { + if (s->is_wide_char) { + str16(s->str)[s->len++] = c; + return 0; + } else if (c < 0x100) { + str8(s->str)[s->len++] = c; + return 0; + } + } + return string_buffer_putc_slow(s, c); +} + +/* 0 <= c <= 0x10ffff */ +static int string_buffer_putc(StringBuffer *s, uint32_t c) +{ + if (unlikely(c >= 0x10000)) { + /* surrogate pair */ + if (string_buffer_putc16(s, get_hi_surrogate(c))) + return -1; + c = get_lo_surrogate(c); + } + return string_buffer_putc16(s, c); +} + +static int string_getc(JSString *p, int *pidx) +{ + int idx, c, c1; + idx = *pidx; + if (p->is_wide_char) { + c = str16(p)[idx++]; + if (is_hi_surrogate(c) && idx < p->len) { + c1 = str16(p)[idx]; + if (is_lo_surrogate(c1)) { + c = from_surrogate(c, c1); + idx++; + } + } + } else { + c = str8(p)[idx++]; + } + *pidx = idx; + return c; +} + +static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len) +{ + int i; + + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, 0)) + return -1; + } + if (s->is_wide_char) { + for (i = 0; i < len; i++) { + str16(s->str)[s->len + i] = p[i]; + } + s->len += len; + } else { + memcpy(&str8(s->str)[s->len], p, len); + s->len += len; + } + return 0; +} + +static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len) +{ + int c = 0, i; + + for (i = 0; i < len; i++) { + c |= p[i]; + } + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, c)) + return -1; + } else if (!s->is_wide_char && c >= 0x100) { + if (string_buffer_widen(s, s->size)) + return -1; + } + if (s->is_wide_char) { + memcpy(&str16(s->str)[s->len], p, len << 1); + s->len += len; + } else { + for (i = 0; i < len; i++) { + str8(s->str)[s->len + i] = p[i]; + } + s->len += len; + } + return 0; +} + +/* appending an ASCII string */ +static int string_buffer_puts8(StringBuffer *s, const char *str) +{ + return string_buffer_write8(s, (const uint8_t *)str, strlen(str)); +} + +static int string_buffer_concat(StringBuffer *s, JSString *p, + uint32_t from, uint32_t to) +{ + if (to <= from) + return 0; + if (p->is_wide_char) + return string_buffer_write16(s, str16(p) + from, to - from); + else + return string_buffer_write8(s, str8(p) + from, to - from); +} + +static int string_buffer_concat_value(StringBuffer *s, JSValueConst v) +{ + JSString *p; + JSValue v1; + int res; + + if (s->error_status) { + /* prevent exception overload */ + return -1; + } + if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { + v1 = JS_ToString(s->ctx, v); + if (JS_IsException(v1)) + return string_buffer_set_error(s); + p = JS_VALUE_GET_STRING(v1); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v1); + return res; + } + p = JS_VALUE_GET_STRING(v); + return string_buffer_concat(s, p, 0, p->len); +} + +static int string_buffer_concat_value_free(StringBuffer *s, JSValue v) +{ + JSString *p; + int res; + + if (s->error_status) { + /* prevent exception overload */ + JS_FreeValue(s->ctx, v); + return -1; + } + if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { + v = JS_ToStringFree(s->ctx, v); + if (JS_IsException(v)) + return string_buffer_set_error(s); + } + p = JS_VALUE_GET_STRING(v); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v); + return res; +} + +static int string_buffer_fill(StringBuffer *s, int c, int count) +{ + /* XXX: optimize */ + if (s->len + count > s->size) { + if (string_buffer_realloc(s, s->len + count, c)) + return -1; + } + while (count-- > 0) { + if (string_buffer_putc16(s, c)) + return -1; + } + return 0; +} + +static JSValue string_buffer_end(StringBuffer *s) +{ + JSString *str; + str = s->str; + if (s->error_status) + return JS_EXCEPTION; + if (s->len == 0) { + js_free(s->ctx, str); + s->str = NULL; + return js_empty_string(s->ctx->rt); + } + if (s->len < s->size) { + /* smaller size so js_realloc should not fail, but OK if it does */ + /* XXX: should add some slack to avoid unnecessary calls */ + /* XXX: might need to use malloc+free to ensure smaller size */ + str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) + + (s->len << s->is_wide_char) + 1 - s->is_wide_char); + if (str == NULL) + str = s->str; + s->str = str; + } + if (!s->is_wide_char) + str8(str)[s->len] = 0; +#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS + list_add_tail(&str->link, &s->ctx->rt->string_list); +#endif + str->is_wide_char = s->is_wide_char; + str->len = s->len; + s->str = NULL; + return JS_MKPTR(JS_TAG_STRING, str); +} + +/* create a string from a UTF-8 buffer */ +JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len) +{ + JSString *str; + size_t len; + int kind; + + if (buf_len <= 0) { + return js_empty_string(ctx->rt); + } + /* Compute string kind and length: 7-bit, 8-bit, 16-bit, 16-bit UTF-16 */ + kind = utf8_scan(buf, buf_len, &len); + if (len > JS_STRING_LEN_MAX) + return JS_ThrowRangeError(ctx, "invalid string length"); + + switch (kind) { + case UTF8_PLAIN_ASCII: + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + memcpy(str8(str), buf, len); + str8(str)[len] = '\0'; + break; + case UTF8_NON_ASCII: + /* buf contains non-ASCII code-points, but limited to 8-bit values */ + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + utf8_decode_buf8(str8(str), len + 1, buf, buf_len); + break; + default: + // This causes a potential problem in JS_ThrowError if message is invalid + //if (kind & UTF8_HAS_ERRORS) + // return JS_ThrowRangeError(ctx, "invalid UTF-8 sequence"); + str = js_alloc_string(ctx, len, 1); + if (!str) + return JS_EXCEPTION; + utf8_decode_buf16(str16(str), len, buf, buf_len); + break; + } + return JS_MKPTR(JS_TAG_STRING, str); +} + +JSValue JS_NewTwoByteString(JSContext *ctx, const uint16_t *buf, size_t len) +{ + JSString *str; + + if (!len) + return js_empty_string(ctx->rt); + str = js_alloc_string(ctx, len, 1); + if (!str) + return JS_EXCEPTION; + memcpy(str16(str), buf, len * sizeof(*buf)); + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue JS_ConcatString3(JSContext *ctx, const char *str1, + JSValue str2, const char *str3) +{ + StringBuffer b_s, *b = &b_s; + int len1, len3; + JSString *p; + + if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) { + str2 = JS_ToStringFree(ctx, str2); + if (JS_IsException(str2)) + goto fail; + } + p = JS_VALUE_GET_STRING(str2); + len1 = strlen(str1); + len3 = strlen(str3); + + if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char)) + goto fail; + + string_buffer_write8(b, (const uint8_t *)str1, len1); + string_buffer_concat(b, p, 0, p->len); + string_buffer_write8(b, (const uint8_t *)str3, len3); + + JS_FreeValue(ctx, str2); + return string_buffer_end(b); + + fail: + JS_FreeValue(ctx, str2); + return JS_EXCEPTION; +} + +/* `str` may be pure ASCII or UTF-8 encoded */ +JSValue JS_NewAtomString(JSContext *ctx, const char *str) +{ + JSAtom atom = JS_NewAtom(ctx, str); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + JSValue val = JS_AtomToString(ctx, atom); + JS_FreeAtom(ctx, atom); + return val; +} + +/* return (NULL, 0) if exception. */ +/* return pointer into a JSString with a live ref_count */ +/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */ +const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, + bool cesu8) +{ + JSValue val; + JSString *str, *str_new; + int pos, len, c, c1; + JSObject *p; + uint8_t *q; + + if (JS_VALUE_GET_TAG(val1) == JS_TAG_STRING) { + val = js_dup(val1); + goto go; + } + + val = JS_ToString(ctx, val1); + if (!JS_IsException(val)) + goto go; + + // Stringification can fail when there is an exception pending, + // e.g. a stack overflow InternalError. Special-case exception + // objects to make debugging easier, look up the .message property + // and stringify that. + if (JS_VALUE_GET_TAG(val1) != JS_TAG_OBJECT) + goto fail; + + p = JS_VALUE_GET_OBJ(val1); + if (p->class_id != JS_CLASS_ERROR) + goto fail; + + val = JS_GetProperty(ctx, val1, JS_ATOM_message); + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) { + JS_FreeValue(ctx, val); + goto fail; + } + +go: + str = JS_VALUE_GET_STRING(val); + len = str->len; + if (!str->is_wide_char) { + const uint8_t *src = str8(str); + int count; + + /* count the number of non-ASCII characters */ + /* Scanning the whole string is required for ASCII strings, + and computing the number of non-ASCII bytes is less expensive + than testing each byte, hence this method is faster for ASCII + strings, which is the most common case. + */ + count = 0; + for (pos = 0; pos < len; pos++) { + count += src[pos] >> 7; + } + if (count == 0 && str->kind == JS_STRING_KIND_NORMAL) { + if (plen) + *plen = len; + return (const char *)src; + } + str_new = js_alloc_string(ctx, len + count, 0); + if (!str_new) + goto fail; + q = str8(str_new); + for (pos = 0; pos < len; pos++) { + c = src[pos]; + if (c < 0x80) { + *q++ = c; + } else { + *q++ = (c >> 6) | 0xc0; + *q++ = (c & 0x3f) | 0x80; + } + } + } else { + const uint16_t *src = str16(str); + /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may + produce 4 bytes but use 2 code points. + */ + str_new = js_alloc_string(ctx, len * 3, 0); + if (!str_new) + goto fail; + q = str8(str_new); + pos = 0; + while (pos < len) { + c = src[pos++]; + if (c < 0x80) { + *q++ = c; + } else { + if (is_hi_surrogate(c)) { + if (pos < len && !cesu8) { + c1 = src[pos]; + if (is_lo_surrogate(c1)) { + pos++; + c = from_surrogate(c, c1); + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } + q += utf8_encode(q, c); + } + } + } + + *q = '\0'; + str_new->len = q - str8(str_new); + JS_FreeValue(ctx, val); + if (plen) + *plen = str_new->len; + return (const char *)str8(str_new); + fail: + if (plen) + *plen = 0; + return NULL; +} + +void JS_FreeCString(JSContext *ctx, const char *ptr) +{ + if (!ptr) + return; + /* purposely removing constness */ + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, (JSString *)ptr - 1)); +} + +static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int js_string_memcmp(JSString *p1, JSString *p2, int len) +{ + int res; + + if (likely(!p1->is_wide_char)) { + if (likely(!p2->is_wide_char)) + res = memcmp(str8(p1), str8(p2), len); + else + res = -memcmp16_8(str16(p2), str8(p1), len); + } else { + if (!p2->is_wide_char) + res = memcmp16_8(str16(p1), str8(p2), len); + else + res = memcmp16(str16(p1), str16(p2), len); + } + return res; +} + +static bool js_string_eq(JSString *p1, JSString *p2) { + if (p1->len != p2->len) + return false; + return js_string_memcmp(p1, p2, p1->len) == 0; +} + +/* return < 0, 0 or > 0 */ +static int js_string_compare(JSString *p1, JSString *p2) +{ + int res, len; + len = min_int(p1->len, p2->len); + res = js_string_memcmp(p1, p2, len); + if (res == 0) + res = compare_u32(p1->len, p2->len); + return res; +} + +static void copy_str16(uint16_t *dst, JSString *p, int offset, int len) +{ + if (p->is_wide_char) { + memcpy(dst, str16(p) + offset, len * 2); + } else { + const uint8_t *src1 = str8(p) + offset; + int i; + + for(i = 0; i < len; i++) + dst[i] = src1[i]; + } +} + +static JSValue JS_ConcatString1(JSContext *ctx, JSString *p1, JSString *p2) +{ + JSString *p; + uint32_t len; + int is_wide_char; + + len = p1->len + p2->len; + if (len > JS_STRING_LEN_MAX) + return JS_ThrowRangeError(ctx, "invalid string length"); + is_wide_char = p1->is_wide_char | p2->is_wide_char; + p = js_alloc_string(ctx, len, is_wide_char); + if (!p) + return JS_EXCEPTION; + if (!is_wide_char) { + memcpy(str8(p), str8(p1), p1->len); + memcpy(str8(p) + p1->len, str8(p2), p2->len); + str8(p)[len] = '\0'; + } else { + copy_str16(str16(p), p1, 0, p1->len); + copy_str16(str16(p) + p1->len, p2, 0, p2->len); + } + return JS_MKPTR(JS_TAG_STRING, p); +} + +/* op1 and op2 are converted to strings. For convience, op1 or op2 = + JS_EXCEPTION are accepted and return JS_EXCEPTION. */ +static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) +{ + JSValue ret; + JSString *p1, *p2; + + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING)) { + op1 = JS_ToStringFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; + } + } + if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING)) { + op2 = JS_ToStringFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + return JS_EXCEPTION; + } + } + p1 = JS_VALUE_GET_STRING(op1); + p2 = JS_VALUE_GET_STRING(op2); + + /* XXX: could also check if p1 is empty */ + if (p2->len == 0) { + goto ret_op1; + } + if (p1->header.ref_count == 1 && p1->is_wide_char == p2->is_wide_char + && js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) { + /* Concatenate in place in available space at the end of p1 */ + if (p1->is_wide_char) { + memcpy(str16(p1) + p1->len, str16(p2), p2->len << 1); + p1->len += p2->len; + } else { + memcpy(str8(p1) + p1->len, str8(p2), p2->len); + p1->len += p2->len; + str8(p1)[p1->len] = '\0'; + } + ret_op1: + JS_FreeValue(ctx, op2); + return op1; + } + ret = JS_ConcatString1(ctx, p1, p2); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return ret; +} + +/* Shape support */ + +static inline size_t get_shape_size(size_t hash_size, size_t prop_size) +{ + return hash_size * sizeof(uint32_t) + sizeof(JSShape) + + prop_size * sizeof(JSShapeProperty); +} + +static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) +{ + return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); +} + +static inline uint32_t *prop_hash_end(JSShape *sh) +{ + return (uint32_t *)sh; +} + +static inline void *get_alloc_from_shape(JSShape *sh) +{ + return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); +} + +static inline JSShapeProperty *get_shape_prop(JSShape *sh) +{ + return sh->prop; +} + +static int init_shape_hash(JSRuntime *rt) +{ + rt->shape_hash_bits = 6; /* 64 shapes */ + rt->shape_hash_size = 1 << rt->shape_hash_bits; + rt->shape_hash_count = 0; + rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + rt->shape_hash_size); + if (!rt->shape_hash) + return -1; + return 0; +} + +/* same magic hash multiplier as the Linux kernel */ +static uint32_t shape_hash(uint32_t h, uint32_t val) +{ + return (h + val) * 0x9e370001; +} + +/* truncate the shape hash to 'hash_bits' bits */ +static uint32_t get_shape_hash(uint32_t h, int hash_bits) +{ + return h >> (32 - hash_bits); +} + +static uint32_t shape_initial_hash(JSObject *proto) +{ + uint32_t h; + h = shape_hash(1, (uintptr_t)proto); + if (sizeof(proto) > 4) + h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32); + return h; +} + +static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits) +{ + int new_shape_hash_size, i; + uint32_t h; + JSShape **new_shape_hash, *sh, *sh_next; + + new_shape_hash_size = 1 << new_shape_hash_bits; + new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + new_shape_hash_size); + if (!new_shape_hash) + return -1; + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) { + sh_next = sh->shape_hash_next; + h = get_shape_hash(sh->hash, new_shape_hash_bits); + sh->shape_hash_next = new_shape_hash[h]; + new_shape_hash[h] = sh; + } + } + js_free_rt(rt, rt->shape_hash); + rt->shape_hash_bits = new_shape_hash_bits; + rt->shape_hash_size = new_shape_hash_size; + rt->shape_hash = new_shape_hash; + return 0; +} + +static void js_shape_hash_link(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + sh->shape_hash_next = rt->shape_hash[h]; + rt->shape_hash[h] = sh; + rt->shape_hash_count++; +} + +static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + JSShape **psh; + + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + psh = &rt->shape_hash[h]; + while (*psh != sh) + psh = &(*psh)->shape_hash_next; + *psh = sh->shape_hash_next; + rt->shape_hash_count--; +} + +/* create a new empty shape with prototype 'proto' */ +static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, + int hash_size, int prop_size) +{ + JSRuntime *rt = ctx->rt; + void *sh_alloc; + JSShape *sh; + + /* resize the shape hash table if necessary */ + if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { + resize_shape_hash(rt, rt->shape_hash_bits + 1); + } + + sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); + if (!sh_alloc) + return NULL; + sh = get_shape_from_alloc(sh_alloc, hash_size); + sh->header.ref_count = 1; + add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + if (proto) + js_dup(JS_MKPTR(JS_TAG_OBJECT, proto)); + sh->proto = proto; + memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * + hash_size); + sh->prop_hash_mask = hash_size - 1; + sh->prop_size = prop_size; + sh->prop_count = 0; + sh->deleted_prop_count = 0; + + /* insert in the hash table */ + sh->hash = shape_initial_hash(proto); + sh->is_hashed = true; + sh->has_small_array_index = false; + js_shape_hash_link(ctx->rt, sh); + return sh; +} + +static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) +{ + return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE, + JS_PROP_INITIAL_SIZE); +} + +/* The shape is cloned. The new shape is not inserted in the shape + hash table */ +static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) +{ + JSShape *sh; + void *sh_alloc, *sh_alloc1; + size_t size; + JSShapeProperty *pr; + uint32_t i, hash_size; + + hash_size = sh1->prop_hash_mask + 1; + size = get_shape_size(hash_size, sh1->prop_size); + sh_alloc = js_malloc(ctx, size); + if (!sh_alloc) + return NULL; + sh_alloc1 = get_alloc_from_shape(sh1); + memcpy(sh_alloc, sh_alloc1, size); + sh = get_shape_from_alloc(sh_alloc, hash_size); + sh->header.ref_count = 1; + add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + sh->is_hashed = false; + if (sh->proto) { + js_dup(JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { + JS_DupAtom(ctx, pr->atom); + } + return sh; +} + +static JSShape *js_dup_shape(JSShape *sh) +{ + sh->header.ref_count++; + return sh; +} + +static void js_free_shape0(JSRuntime *rt, JSShape *sh) +{ + uint32_t i; + JSShapeProperty *pr; + + assert(sh->header.ref_count == 0); + if (sh->is_hashed) + js_shape_hash_unlink(rt, sh); + if (sh->proto != NULL) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JS_FreeAtomRT(rt, pr->atom); + pr++; + } + remove_gc_object(&sh->header); + js_free_rt(rt, get_alloc_from_shape(sh)); +} + +static void js_free_shape(JSRuntime *rt, JSShape *sh) +{ + if (unlikely(--sh->header.ref_count <= 0)) { + js_free_shape0(rt, sh); + } +} + +static void js_free_shape_null(JSRuntime *rt, JSShape *sh) +{ + if (sh) + js_free_shape(rt, sh); +} + +/* make space to hold at least 'count' properties */ +static no_inline int resize_properties(JSContext *ctx, JSShape **psh, + JSObject *p, uint32_t count) +{ + JSShape *sh; + uint32_t new_size, new_hash_size, new_hash_mask, i; + JSShapeProperty *pr; + void *sh_alloc; + intptr_t h; + + sh = *psh; + new_size = max_int(count, sh->prop_size * 3 / 2); + /* Reallocate prop array first to avoid crash or size inconsistency + in case of memory allocation failure */ + if (p) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (unlikely(!new_prop)) + return -1; + p->prop = new_prop; + } + new_hash_size = sh->prop_hash_mask + 1; + while (new_hash_size < new_size) + new_hash_size = 2 * new_hash_size; + if (new_hash_size != (sh->prop_hash_mask + 1)) { + JSShape *old_sh; + /* resize the hash table and the properties */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + /* copy all the fields and the properties */ + memcpy(sh, old_sh, + sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + new_hash_mask = new_hash_size - 1; + sh->prop_hash_mask = new_hash_mask; + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { + if (pr->atom != JS_ATOM_NULL) { + h = ((uintptr_t)pr->atom & new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = i + 1; + } + } + js_free(ctx, get_alloc_from_shape(old_sh)); + } else { + /* only resize the properties */ + list_del(&sh->header.link); + sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh), + get_shape_size(new_hash_size, new_size)); + if (unlikely(!sh_alloc)) { + /* insert again in the GC list */ + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + return -1; + } + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + } + *psh = sh; + sh->prop_size = new_size; + return 0; +} + +/* remove the deleted properties. */ +static int compact_properties(JSContext *ctx, JSObject *p) +{ + JSShape *sh, *old_sh; + void *sh_alloc; + intptr_t h; + uint32_t new_hash_size, i, j, new_hash_mask, new_size; + JSShapeProperty *old_pr, *pr; + JSProperty *prop, *new_prop; + + sh = p->shape; + assert(!sh->is_hashed); + + new_size = max_int(JS_PROP_INITIAL_SIZE, + sh->prop_count - sh->deleted_prop_count); + assert(new_size <= sh->prop_size); + + new_hash_size = sh->prop_hash_mask + 1; + while ((new_hash_size / 2) >= new_size) + new_hash_size = new_hash_size / 2; + new_hash_mask = new_hash_size - 1; + + /* resize the hash table and the properties */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + memcpy(sh, old_sh, sizeof(JSShape)); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + + j = 0; + old_pr = old_sh->prop; + pr = sh->prop; + prop = p->prop; + for(i = 0; i < sh->prop_count; i++) { + if (old_pr->atom != JS_ATOM_NULL) { + pr->atom = old_pr->atom; + pr->flags = old_pr->flags; + h = ((uintptr_t)old_pr->atom & new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = j + 1; + prop[j] = prop[i]; + j++; + pr++; + } + old_pr++; + } + assert(j == (sh->prop_count - sh->deleted_prop_count)); + sh->prop_hash_mask = new_hash_mask; + sh->prop_size = new_size; + sh->deleted_prop_count = 0; + sh->prop_count = j; + + p->shape = sh; + js_free(ctx, get_alloc_from_shape(old_sh)); + + /* reduce the size of the object properties */ + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (new_prop) + p->prop = new_prop; + return 0; +} + +static int add_shape_property(JSContext *ctx, JSShape **psh, + JSObject *p, JSAtom atom, int prop_flags) +{ + JSRuntime *rt = ctx->rt; + JSShape *sh = *psh; + JSShapeProperty *pr, *prop; + uint32_t hash_mask, new_shape_hash = 0; + intptr_t h; + + /* update the shape hash */ + if (sh->is_hashed) { + js_shape_hash_unlink(rt, sh); + new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags); + } + + if (unlikely(sh->prop_count >= sh->prop_size)) { + if (resize_properties(ctx, psh, p, sh->prop_count + 1)) { + /* in case of error, reinsert in the hash table. + sh is still valid if resize_properties() failed */ + if (sh->is_hashed) + js_shape_hash_link(rt, sh); + return -1; + } + sh = *psh; + } + if (sh->is_hashed) { + sh->hash = new_shape_hash; + js_shape_hash_link(rt, sh); + } + /* Initialize the new shape property. + The object property at p->prop[sh->prop_count] is uninitialized */ + prop = get_shape_prop(sh); + pr = &prop[sh->prop_count++]; + pr->atom = JS_DupAtom(ctx, atom); + pr->flags = prop_flags; + sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); + /* add in hash table */ + hash_mask = sh->prop_hash_mask; + h = atom & hash_mask; + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = sh->prop_count; + return 0; +} + +/* find a hashed empty shape matching the prototype. Return NULL if + not found */ +static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto) +{ + JSShape *sh1; + uint32_t h, h1; + + h = shape_initial_hash(proto); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + if (sh1->hash == h && + sh1->proto == proto && + sh1->prop_count == 0) { + return sh1; + } + } + return NULL; +} + +/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if + not found */ +static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, + JSAtom atom, int prop_flags) +{ + JSShape *sh1; + uint32_t h, h1, i, n; + + h = sh->hash; + h = shape_hash(h, atom); + h = shape_hash(h, prop_flags); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + /* we test the hash first so that the rest is done only if the + shapes really match */ + if (sh1->hash == h && + sh1->proto == sh->proto && + sh1->prop_count == ((n = sh->prop_count) + 1)) { + for(i = 0; i < n; i++) { + if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) || + unlikely(sh1->prop[i].flags != sh->prop[i].flags)) + goto next; + } + if (unlikely(sh1->prop[n].atom != atom) || + unlikely(sh1->prop[n].flags != prop_flags)) + goto next; + return sh1; + } + next: ; + } + return NULL; +} + +static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) +{ + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + int j; + + /* XXX: should output readable class prototype */ + printf("%5d %3d%c %14p %5d %5d", i, + sh->header.ref_count, " *"[sh->is_hashed], + (void *)sh->proto, sh->prop_size, sh->prop_count); + for(j = 0; j < sh->prop_count; j++) { + printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), + sh->prop[j].atom)); + } + printf("\n"); +} + +static __maybe_unused void JS_DumpShapes(JSRuntime *rt) +{ + int i; + JSShape *sh; + struct list_head *el; + JSObject *p; + JSGCObjectHeader *gp; + + printf("JSShapes: {\n"); + printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS"); + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + JS_DumpShape(rt, i, sh); + assert(sh->is_hashed); + } + } + /* dump non-hashed shapes */ + list_for_each(el, &rt->gc_obj_list) { + gp = list_entry(el, JSGCObjectHeader, link); + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + if (!p->shape->is_hashed) { + JS_DumpShape(rt, -1, p->shape); + } + } + } + printf("}\n"); +} + +static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id) +{ + JSObject *p; + + js_trigger_gc(ctx->rt, sizeof(JSObject)); + p = js_malloc(ctx, sizeof(JSObject)); + if (unlikely(!p)) + goto fail; + p->class_id = class_id; + p->extensible = true; + p->free_mark = 0; + p->is_exotic = 0; + p->fast_array = 0; + p->is_constructor = 0; + p->is_uncatchable_error = 0; + p->tmp_mark = 0; + p->is_HTMLDDA = 0; + p->first_weak_ref = NULL; + p->u.opaque = NULL; + p->shape = sh; + p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); + if (unlikely(!p->prop)) { + js_free(ctx, p); + fail: + js_free_shape(ctx->rt, sh); + return JS_EXCEPTION; + } + + switch(class_id) { + case JS_CLASS_OBJECT: + break; + case JS_CLASS_ARRAY: + { + JSProperty *pr; + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.values = NULL; + p->u.array.count = 0; + p->u.array.u1.size = 0; + /* the length property is always the first one */ + if (likely(sh == ctx->array_shape)) { + pr = &p->prop[0]; + } else { + /* only used for the first array */ + /* cannot fail */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_LENGTH); + } + pr->u.value = js_int32(0); + } + break; + case JS_CLASS_C_FUNCTION: + p->prop[0].u.value = JS_UNDEFINED; + break; + case JS_CLASS_ARGUMENTS: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT16_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_DATAVIEW: + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: + p->u.object_data = JS_UNDEFINED; + goto set_exotic; + case JS_CLASS_REGEXP: + p->u.regexp.pattern = NULL; + p->u.regexp.bytecode = NULL; + goto set_exotic; + default: + set_exotic: + if (ctx->rt->class_array[class_id].exotic) { + p->is_exotic = 1; + } + break; + } + p->header.ref_count = 1; + add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); + return JS_MKPTR(JS_TAG_OBJECT, p); +} + +static JSObject *get_proto_obj(JSValueConst proto_val) +{ + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) + return NULL; + else + return JS_VALUE_GET_OBJ(proto_val); +} + +/* WARNING: proto must be an object or JS_NULL */ +JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val, + JSClassID class_id) +{ + JSShape *sh; + JSObject *proto; + + proto = get_proto_obj(proto_val); + sh = find_hashed_shape_proto(ctx->rt, proto); + if (likely(sh)) { + sh = js_dup_shape(sh); + } else { + sh = js_new_shape(ctx, proto); + if (!sh) + return JS_EXCEPTION; + } + return JS_NewObjectFromShape(ctx, sh, class_id); +} + +static int JS_SetObjectData(JSContext *ctx, JSValueConst obj, JSValue val) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + switch(p->class_id) { + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: + JS_FreeValue(ctx, p->u.object_data); + p->u.object_data = val; + return 0; + } + } + JS_FreeValue(ctx, val); + if (!JS_IsException(obj)) + JS_ThrowTypeError(ctx, "invalid object type"); + return -1; +} + +JSValue JS_NewObjectClass(JSContext *ctx, JSClassID class_id) +{ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id); +} + +JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto) +{ + return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT); +} + +JSValue JS_NewObjectFrom(JSContext *ctx, int count, const JSAtom *props, + const JSValue *values) +{ + JSShapeProperty *pr; + uint32_t *hash; + JSRuntime *rt; + JSObject *p; + JSShape *sh; + JSValue obj; + JSAtom atom; + intptr_t h; + int i; + + rt = ctx->rt; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + if (count > 0) { + p = JS_VALUE_GET_OBJ(obj); + sh = p->shape; + assert(sh->is_hashed); + assert(sh->header.ref_count == 1); + js_shape_hash_unlink(rt, sh); + if (resize_properties(ctx, &sh, p, count)) { + js_shape_hash_link(rt, sh); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + p->shape = sh; + for (i = 0; i < count; i++) { + atom = props[i]; + pr = &sh->prop[i]; + sh->hash = shape_hash(shape_hash(sh->hash, atom), JS_PROP_C_W_E); + sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); + h = atom & sh->prop_hash_mask; + hash = &prop_hash_end(sh)[-h - 1]; + pr->hash_next = *hash; + *hash = i + 1; + pr->atom = JS_DupAtom(ctx, atom); + pr->flags = JS_PROP_C_W_E; + p->prop[i].u.value = values[i]; + } + js_shape_hash_link(rt, sh); + sh->prop_count = count; + } + return obj; +} + +JSValue JS_NewObjectFromStr(JSContext *ctx, int count, const char **props, + const JSValue *values) +{ + JSAtom atoms_s[16], *atoms = atoms_s; + JSValue ret; + int i; + + i = 0; + ret = JS_EXCEPTION; + if (count < 1) + goto out; + if (count > (int)countof(atoms_s)) { + atoms = js_malloc(ctx, count * sizeof(*atoms)); + if (!atoms) + return JS_EXCEPTION; + } + for (i = 0; i < count; i++) { + atoms[i] = JS_NewAtom(ctx, props[i]); + if (atoms[i] == JS_ATOM_NULL) + goto out; + } + ret = JS_NewObjectFrom(ctx, count, atoms, values); +out: + while (i-- > 0) + JS_FreeAtom(ctx, atoms[i]); + if (atoms != atoms_s) + js_free(ctx, atoms); + return ret; +} + +JSValue JS_NewArray(JSContext *ctx) +{ + return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape), + JS_CLASS_ARRAY); +} + +// note: takes ownership of |values|, unlike js_create_array +JSValue JS_NewArrayFrom(JSContext *ctx, int count, const JSValue *values) +{ + JSObject *p; + JSValue obj; + int i; + + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + goto exception; + if (count > 0) { + p = JS_VALUE_GET_OBJ(obj); + if (expand_fast_array(ctx, p, count)) { + JS_FreeValue(ctx, obj); + goto exception; + } + p->u.array.count = count; + p->prop[0].u.value = js_int32(count); + memcpy(p->u.array.u.values, values, count * sizeof(*values)); + } + return obj; +exception: + for (i = 0; i < count; i++) + JS_FreeValue(ctx, values[i]); + return JS_EXCEPTION; +} + +JSValue JS_NewObject(JSContext *ctx) +{ + /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); +} + +static void js_function_set_properties(JSContext *ctx, JSValue func_obj, + JSAtom name, int len) +{ + /* ES6 feature non compatible with ES5.1: length is configurable */ + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, js_int32(len), + JS_PROP_CONFIGURABLE); + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, + JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE); +} + +static bool js_class_has_bytecode(JSClassID class_id) +{ + return (class_id == JS_CLASS_BYTECODE_FUNCTION || + class_id == JS_CLASS_GENERATOR_FUNCTION || + class_id == JS_CLASS_ASYNC_FUNCTION || + class_id == JS_CLASS_ASYNC_GENERATOR_FUNCTION); +} + +/* return NULL without exception if not a function or no bytecode */ +static JSFunctionBytecode *JS_GetFunctionBytecode(JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(val); + if (!js_class_has_bytecode(p->class_id)) + return NULL; + return p->u.func.function_bytecode; +} + +static void js_method_set_home_object(JSContext *ctx, JSValue func_obj, + JSValue home_obj) +{ + JSObject *p, *p1; + JSFunctionBytecode *b; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(func_obj); + if (!js_class_has_bytecode(p->class_id)) + return; + b = p->u.func.function_bytecode; + if (b->need_home_object) { + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT) + p1 = JS_VALUE_GET_OBJ(js_dup(home_obj)); + else + p1 = NULL; + p->u.func.home_object = p1; + } +} + +static JSValue js_get_function_name(JSContext *ctx, JSAtom name) +{ + JSValue name_str; + + name_str = JS_AtomToString(ctx, name); + if (JS_AtomSymbolHasDescription(ctx, name)) { + name_str = JS_ConcatString3(ctx, "[", name_str, "]"); + } + return name_str; +} + +/* Modify the name of a method according to the atom and + 'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and + JS_PROP_HAS_SET. Also set the home object of the method. + Return < 0 if exception. */ +static int js_method_set_properties(JSContext *ctx, JSValue func_obj, + JSAtom name, int flags, JSValue home_obj) +{ + JSValue name_str; + + name_str = js_get_function_name(ctx, name); + if (flags & JS_PROP_HAS_GET) { + name_str = JS_ConcatString3(ctx, "get ", name_str, ""); + } else if (flags & JS_PROP_HAS_SET) { + name_str = JS_ConcatString3(ctx, "set ", name_str, ""); + } + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str, + JS_PROP_CONFIGURABLE) < 0) + return -1; + js_method_set_home_object(ctx, func_obj, home_obj); + return 0; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +/* `name` may be NULL, pure ASCII or UTF-8 encoded */ +JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic, + JSValueConst proto_val) +{ + JSValue func_obj; + JSObject *p; + JSAtom name_atom; + + func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); + if (JS_IsException(func_obj)) + return func_obj; + p = JS_VALUE_GET_OBJ(func_obj); + p->u.cfunc.realm = JS_DupContext(ctx); + p->u.cfunc.c_function.generic = func; + p->u.cfunc.length = length; + p->u.cfunc.cproto = cproto; + p->u.cfunc.magic = magic; + p->is_constructor = (cproto == JS_CFUNC_constructor || + cproto == JS_CFUNC_constructor_magic || + cproto == JS_CFUNC_constructor_or_func || + cproto == JS_CFUNC_constructor_or_func_magic); + name_atom = JS_ATOM_empty_string; + if (name && *name) { + name_atom = JS_NewAtom(ctx, name); + if (name_atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + } + js_function_set_properties(ctx, func_obj, name_atom, length); + JS_FreeAtom(ctx, name_atom); + return func_obj; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic) +{ + return JS_NewCFunction3(ctx, func, name, length, cproto, magic, + ctx->function_proto); +} + +typedef struct JSCFunctionDataRecord { + JSCFunctionData *func; + uint8_t length; + uint8_t data_len; + uint16_t magic; + JSValue data[]; +} JSCFunctionDataRecord; + +static void js_c_function_data_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_FreeValueRT(rt, s->data[i]); + } + js_free_rt(rt, s); + } +} + +static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_MarkValue(rt, s->data[i], mark_func); + } + } +} + +static JSValue js_call_c_function_data(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame sf_s, *sf = &sf_s, *prev_sf; + JSCFunctionDataRecord *s; + JSValueConst *arg_buf; + JSValue ret; + size_t stack_size; + int arg_count; + int i; + + s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA); + if (!s) + return JS_EXCEPTION; // can't really happen + arg_buf = argv; + arg_count = s->length; + if (unlikely(argc < arg_count)) { + stack_size = arg_count * sizeof(arg_buf[0]); + if (js_check_stack_overflow(rt, stack_size)) + return JS_ThrowStackOverflow(ctx); + arg_buf = alloca(stack_size); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + } + prev_sf = rt->current_stack_frame; + sf->prev_frame = prev_sf; + rt->current_stack_frame = sf; + // TODO(bnoordhuis) switch realms like js_call_c_function does + sf->is_strict_mode = false; + sf->cur_func = unsafe_unconst(func_obj); + sf->arg_count = argc; + ret = s->func(ctx, this_val, argc, arg_buf, s->magic, vc(s->data)); + rt->current_stack_frame = sf->prev_frame; + return ret; +} + +JSValue JS_NewCFunctionData2(JSContext *ctx, JSCFunctionData *func, + const char *name, + int length, int magic, int data_len, + JSValueConst *data) +{ + JSCFunctionDataRecord *s; + JSAtom name_atom; + JSValue func_obj; + int i; + + func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_C_FUNCTION_DATA); + if (JS_IsException(func_obj)) + return func_obj; + s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue)); + if (!s) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + s->func = func; + s->length = length; + s->data_len = data_len; + s->magic = magic; + for(i = 0; i < data_len; i++) + s->data[i] = js_dup(data[i]); + JS_SetOpaqueInternal(func_obj, s); + name_atom = JS_ATOM_empty_string; + if (name && *name) { + name_atom = JS_NewAtom(ctx, name); + if (name_atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + } + js_function_set_properties(ctx, func_obj, name_atom, length); + JS_FreeAtom(ctx, name_atom); + return func_obj; +} + +JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, + int length, int magic, int data_len, + JSValueConst *data) +{ + return JS_NewCFunctionData2(ctx, func, NULL, length, magic, data_len, data); +} + +static JSContext *js_autoinit_get_realm(JSProperty *pr) +{ + return (JSContext *)(pr->u.init.realm_and_id & ~3); +} + +static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr) +{ + return pr->u.init.realm_and_id & 3; +} + +static void js_autoinit_free(JSRuntime *rt, JSProperty *pr) +{ + JS_FreeContext(js_autoinit_get_realm(pr)); +} + +static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr, + JS_MarkFunc *mark_func) +{ + mark_func(rt, &js_autoinit_get_realm(pr)->header); +} + +static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags) +{ + if (unlikely(prop_flags & JS_PROP_TMASK)) { + if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + free_var_ref(rt, pr->u.var_ref); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_free(rt, pr); + } + } else { + JS_FreeValueRT(rt, pr->u.value); + } +} + +static force_inline JSShapeProperty *find_own_property1(JSObject *p, + JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = (uintptr_t)atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + return pr; + } + h = pr->hash_next; + } + return NULL; +} + +static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, + JSObject *p, + JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = (uintptr_t)atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + *ppr = &p->prop[h - 1]; + /* the compiler should be able to assume that pr != NULL here */ + return pr; + } + h = pr->hash_next; + } + *ppr = NULL; + return NULL; +} + +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) +{ + if (var_ref) { + assert(var_ref->header.ref_count > 0); + if (--var_ref->header.ref_count == 0) { + if (var_ref->is_detached) { + JS_FreeValueRT(rt, var_ref->value); + remove_gc_object(&var_ref->header); + } else { + list_del(&var_ref->header.link); /* still on the stack */ + } + js_free_rt(rt, var_ref); + } + } +} + +static void js_array_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + int i; + + for(i = 0; i < p->u.array.count; i++) { + JS_FreeValueRT(rt, p->u.array.u.values[i]); + } + js_free_rt(rt, p->u.array.u.values); +} + +static void js_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + int i; + + for(i = 0; i < p->u.array.count; i++) { + JS_MarkValue(rt, p->u.array.u.values[i], mark_func); + } +} + +static void js_object_data_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_FreeValueRT(rt, p->u.object_data); + p->u.object_data = JS_UNDEFINED; +} + +static void js_object_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_MarkValue(rt, p->u.object_data, mark_func); +} + +static void js_c_function_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + JS_FreeContext(p->u.cfunc.realm); +} + +static void js_c_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + mark_func(rt, &p->u.cfunc.realm->header); +} + +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p1, *p = JS_VALUE_GET_OBJ(val); + JSFunctionBytecode *b; + JSVarRef **var_refs; + int i; + + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + b = p->u.func.function_bytecode; + if (b) { + var_refs = p->u.func.var_refs; + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) + free_var_ref(rt, var_refs[i]); + js_free_rt(rt, var_refs); + } + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b)); + } +} + +static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSVarRef **var_refs = p->u.func.var_refs; + JSFunctionBytecode *b = p->u.func.function_bytecode; + int i; + + if (p->u.func.home_object) { + JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object), + mark_func); + } + if (b) { + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) { + JSVarRef *var_ref = var_refs[i]; + if (var_ref && var_ref->is_detached) { + mark_func(rt, &var_ref->header); + } + } + } + /* must mark the function bytecode because template objects may be + part of a cycle */ + JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func); + } +} + +static void js_bound_function_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_FreeValueRT(rt, bf->func_obj); + JS_FreeValueRT(rt, bf->this_val); + for(i = 0; i < bf->argc; i++) { + JS_FreeValueRT(rt, bf->argv[i]); + } + js_free_rt(rt, bf); +} + +static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_MarkValue(rt, bf->func_obj, mark_func); + JS_MarkValue(rt, bf->this_val, mark_func); + for(i = 0; i < bf->argc; i++) + JS_MarkValue(rt, bf->argv[i], mark_func); +} + +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + JS_FreeValueRT(rt, it->obj); + js_free_rt(rt, it); +} + +static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + JS_MarkValue(rt, it->obj, mark_func); +} + +static void free_object(JSRuntime *rt, JSObject *p) +{ + int i; + JSClassFinalizer *finalizer; + JSShape *sh; + JSShapeProperty *pr; + + p->free_mark = 1; /* used to tell the object is invalid when + freeing cycles */ + /* free all the fields */ + sh = p->shape; + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + free_property(rt, &p->prop[i], pr->flags); + pr++; + } + js_free_rt(rt, p->prop); + /* as an optimization we destroy the shape immediately without + putting it in gc_zero_ref_count_list */ + js_free_shape(rt, sh); + + /* fail safe */ + p->shape = NULL; + p->prop = NULL; + + if (unlikely(p->first_weak_ref)) { + reset_weak_ref(rt, &p->first_weak_ref); + } + + finalizer = rt->class_array[p->class_id].finalizer; + if (finalizer) + (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); + + /* fail safe */ + p->class_id = 0; + p->u.opaque = NULL; + p->u.func.var_refs = NULL; + p->u.func.home_object = NULL; + + remove_gc_object(&p->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) { + list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); + } else { + js_free_rt(rt, p); + } +} + +static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) +{ + switch(gp->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + free_object(rt, (JSObject *)gp); + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + free_function_bytecode(rt, (JSFunctionBytecode *)gp); + break; + default: + abort(); + } +} + +static void free_zero_refcount(JSRuntime *rt) +{ + struct list_head *el; + JSGCObjectHeader *p; + + rt->gc_phase = JS_GC_PHASE_DECREF; + for(;;) { + el = rt->gc_zero_ref_count_list.next; + if (el == &rt->gc_zero_ref_count_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + assert(p->ref_count == 0); + free_gc_object(rt, p); + } + rt->gc_phase = JS_GC_PHASE_NONE; +} + +/* called with the ref_count of 'v' reaches zero. */ +static void js_free_value_rt(JSRuntime *rt, JSValue v) +{ + uint32_t tag = JS_VALUE_GET_TAG(v); + +#ifdef ENABLE_DUMPS // JS_DUMP_FREE + if (check_dump_flag(rt, JS_DUMP_FREE)) { + /* Prevent invalid object access during GC */ + if ((rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) + || (tag != JS_TAG_OBJECT && tag != JS_TAG_FUNCTION_BYTECODE)) { + printf("Freeing "); + if (tag == JS_TAG_OBJECT) { + JS_DumpObject(rt, JS_VALUE_GET_OBJ(v)); + } else { + JS_DumpValue(rt, v); + printf("\n"); + } + } + } +#endif + + switch(tag) { + case JS_TAG_STRING: + js_free_string0(rt, JS_VALUE_GET_STRING(v)); + break; + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + { + JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); + if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { + list_del(&p->link); + list_add(&p->link, &rt->gc_zero_ref_count_list); + if (rt->gc_phase == JS_GC_PHASE_NONE) { + free_zero_refcount(rt); + } + } + } + break; + case JS_TAG_MODULE: + abort(); /* never freed here */ + break; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(v); + js_free_rt(rt, p); + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(v); + JS_FreeAtomStruct(rt, p); + } + break; + default: + printf("js_free_value_rt: unknown tag=%d\n", tag); + abort(); + } +} + +void JS_FreeValueRT(JSRuntime *rt, JSValue v) +{ + if (JS_VALUE_HAS_REF_COUNT(v)) { + JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + if (--p->ref_count <= 0) { + js_free_value_rt(rt, v); + } + } +} + +void JS_FreeValue(JSContext *ctx, JSValue v) +{ + JS_FreeValueRT(ctx->rt, v); +} + +/* garbage collection */ + +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type) +{ + h->mark = 0; + h->gc_obj_type = type; + list_add_tail(&h->link, &rt->gc_obj_list); +} + +static void remove_gc_object(JSGCObjectHeader *h) +{ + list_del(&h->link); +} + +void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + if (JS_VALUE_HAS_REF_COUNT(val)) { + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + mark_func(rt, JS_VALUE_GET_PTR(val)); + break; + default: + break; + } + } +} + +static void mark_weak_map_value(JSRuntime *rt, JSWeakRefRecord *first_weak_ref, JS_MarkFunc *mark_func) { + JSWeakRefRecord *wr; + JSMapRecord *mr; + JSMapState *s; + + for (wr = first_weak_ref; wr != NULL; wr = wr->next_weak_ref) { + if (wr->kind == JS_WEAK_REF_KIND_MAP) { + mr = wr->u.map_record; + s = mr->map; + assert(s->is_weak); + assert(!mr->empty); /* no iterator on WeakMap/WeakSet */ + JS_MarkValue(rt, mr->value, mark_func); + } + } +} + +static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, + JS_MarkFunc *mark_func) +{ + switch(gp->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + { + JSObject *p = (JSObject *)gp; + JSShapeProperty *prs; + JSShape *sh; + int i; + sh = p->shape; + mark_func(rt, &sh->header); + /* mark all the fields */ + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL) { + if (prs->flags & JS_PROP_TMASK) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + mark_func(rt, &pr->u.getset.getter->header); + if (pr->u.getset.setter) + mark_func(rt, &pr->u.getset.setter->header); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (pr->u.var_ref->is_detached) { + /* Note: the tag does not matter + provided it is a GC object */ + mark_func(rt, &pr->u.var_ref->header); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_mark(rt, pr, mark_func); + } + } else { + JS_MarkValue(rt, pr->u.value, mark_func); + } + } + prs++; + } + + if (unlikely(p->first_weak_ref)) { + mark_weak_map_value(rt, p->first_weak_ref, mark_func); + } + + if (p->class_id != JS_CLASS_OBJECT) { + JSClassGCMark *gc_mark; + gc_mark = rt->class_array[p->class_id].gc_mark; + if (gc_mark) + gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func); + } + } + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + /* the template objects can be part of a cycle */ + { + JSFunctionBytecode *b = (JSFunctionBytecode *)gp; + int i; + for(i = 0; i < b->cpool_count; i++) { + JS_MarkValue(rt, b->cpool[i], mark_func); + } + if (b->realm) + mark_func(rt, &b->realm->header); + } + break; + case JS_GC_OBJ_TYPE_VAR_REF: + { + JSVarRef *var_ref = (JSVarRef *)gp; + /* only detached variable referenced are taken into account */ + assert(var_ref->is_detached); + JS_MarkValue(rt, *var_ref->pvalue, mark_func); + } + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + { + JSAsyncFunctionData *s = (JSAsyncFunctionData *)gp; + if (s->is_active) + async_func_mark(rt, &s->func_state, mark_func); + JS_MarkValue(rt, s->resolving_funcs[0], mark_func); + JS_MarkValue(rt, s->resolving_funcs[1], mark_func); + } + break; + case JS_GC_OBJ_TYPE_SHAPE: + { + JSShape *sh = (JSShape *)gp; + if (sh->proto != NULL) { + mark_func(rt, &sh->proto->header); + } + } + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + { + JSContext *ctx = (JSContext *)gp; + JS_MarkContext(rt, ctx, mark_func); + } + break; + default: + abort(); + } +} + +static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + assert(p->ref_count > 0); + p->ref_count--; + if (p->ref_count == 0 && p->mark == 1) { + list_del(&p->link); + list_add_tail(&p->link, &rt->tmp_obj_list); + } +} + +static void gc_decref(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; + + init_list_head(&rt->tmp_obj_list); + + /* decrement the refcount of all the children of all the GC + objects and move the GC objects with zero refcount to + tmp_obj_list */ + list_for_each_safe(el, el1, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->mark == 0); + mark_children(rt, p, gc_decref_child); + p->mark = 1; + if (p->ref_count == 0) { + list_del(&p->link); + list_add_tail(&p->link, &rt->tmp_obj_list); + } + } +} + +static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + p->ref_count++; + if (p->ref_count == 1) { + /* ref_count was 0: remove from tmp_obj_list and add at the + end of gc_obj_list */ + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_obj_list); + p->mark = 0; /* reset the mark for the next GC call */ + } +} + +static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) +{ + p->ref_count++; +} + +static void gc_scan(JSRuntime *rt) +{ + struct list_head *el; + JSGCObjectHeader *p; + + /* keep the objects with a refcount > 0 and their children. */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->ref_count > 0); + p->mark = 0; /* reset the mark for the next GC call */ + mark_children(rt, p, gc_scan_incref_child); + } + + /* restore the refcount of the objects to be deleted. */ + list_for_each(el, &rt->tmp_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + mark_children(rt, p, gc_scan_incref_child2); + } +} + +static void gc_free_cycles(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; +#ifdef ENABLE_DUMPS // JS_DUMP_GC_FREE + bool header_done = false; +#endif + + rt->gc_phase = JS_GC_PHASE_REMOVE_CYCLES; + + for(;;) { + el = rt->tmp_obj_list.next; + if (el == &rt->tmp_obj_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + /* Only need to free the GC object associated with JS + values. The rest will be automatically removed because they + must be referenced by them. */ + switch(p->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: +#ifdef ENABLE_DUMPS // JS_DUMP_GC_FREE + if (check_dump_flag(rt, JS_DUMP_GC_FREE)) { + if (!header_done) { + printf("Freeing cycles:\n"); + JS_DumpObjectHeader(rt); + header_done = true; + } + JS_DumpGCObject(rt, p); + } +#endif + free_gc_object(rt, p); + break; + default: + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_zero_ref_count_list); + break; + } + } + rt->gc_phase = JS_GC_PHASE_NONE; + + list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || + p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); + js_free_rt(rt, p); + } + + init_list_head(&rt->gc_zero_ref_count_list); +} + +void JS_RunGC(JSRuntime *rt) +{ + /* decrement the reference of the children of each object. mark = + 1 after this pass. */ + gc_decref(rt); + + /* keep the GC objects with a non zero refcount and their childs */ + gc_scan(rt); + + /* free the GC objects in a cycle */ + gc_free_cycles(rt); +} + +/* Return false if not an object or if the object has already been + freed (zombie objects are visible in finalizers when freeing + cycles). */ +bool JS_IsLiveObject(JSRuntime *rt, JSValueConst obj) +{ + JSObject *p; + if (!JS_IsObject(obj)) + return false; + p = JS_VALUE_GET_OBJ(obj); + return !p->free_mark; +} + +/* Compute memory used by various object types */ +/* XXX: poor man's approach to handling multiply referenced objects */ +typedef struct JSMemoryUsage_helper { + double memory_used_count; + double str_count; + double str_size; + int64_t js_func_count; + double js_func_size; + int64_t js_func_code_size; + int64_t js_func_pc2line_count; + int64_t js_func_pc2line_size; +} JSMemoryUsage_helper; + +static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp); + +static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) +{ + if (!str->atom_type) { /* atoms are handled separately */ + double s_ref_count = str->header.ref_count; + hp->str_count += 1 / s_ref_count; + hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + + 1 - str->is_wide_char) / s_ref_count); + } +} + +static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp) +{ + int memory_used_count, js_func_size, i; + + memory_used_count = 0; + js_func_size = sizeof(*b); + if (b->vardefs) { + js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs); + } + if (b->cpool) { + js_func_size += b->cpool_count * sizeof(*b->cpool); + for (i = 0; i < b->cpool_count; i++) { + JSValue val = b->cpool[i]; + compute_value_size(val, hp); + } + } + if (b->closure_var) { + js_func_size += b->closure_var_count * sizeof(*b->closure_var); + } + if (b->byte_code_buf) { + hp->js_func_code_size += b->byte_code_len; + } + memory_used_count++; + js_func_size += b->source_len + 1; + if (b->pc2line_len) { + memory_used_count++; + hp->js_func_pc2line_count += 1; + hp->js_func_pc2line_size += b->pc2line_len; + } + hp->js_func_size += js_func_size; + hp->js_func_count += 1; + hp->memory_used_count += memory_used_count; +} + +static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp) +{ + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_STRING: + compute_jsstring_size(JS_VALUE_GET_STRING(val), hp); + break; + case JS_TAG_BIG_INT: + /* should track JSBigInt usage */ + break; + } +} + +void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) +{ + struct list_head *el, *el1; + int i; + JSMemoryUsage_helper mem = { 0 }, *hp = &mem; + + memset(s, 0, sizeof(*s)); + s->malloc_count = rt->malloc_state.malloc_count; + s->malloc_size = rt->malloc_state.malloc_size; + s->malloc_limit = rt->malloc_state.malloc_limit; + + s->memory_used_count = 2; /* rt + rt->class_array */ + s->memory_used_size = sizeof(JSRuntime) + sizeof(JSClass) * rt->class_count; + + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSShape *sh = ctx->array_shape; + s->memory_used_count += 2; /* ctx + ctx->class_proto */ + s->memory_used_size += sizeof(JSContext) + + sizeof(JSValue) * rt->class_count; + s->binary_object_count += ctx->binary_object_count; + s->binary_object_size += ctx->binary_object_size; + + /* the hashed shapes are counted separately */ + if (sh && !sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + list_for_each(el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el1, JSModuleDef, link); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*m); + if (m->req_module_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries); + } + if (m->export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries); + for (i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) { + /* potential multiple count */ + s->memory_used_count += 1; + compute_value_size(me->u.local.var_ref->value, hp); + } + } + } + if (m->star_export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries); + } + if (m->import_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries); + } + compute_value_size(m->module_ns, hp); + compute_value_size(m->func_obj, hp); + } + } + + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + JSShape *sh; + JSShapeProperty *prs; + + /* XXX: could count the other GC object types too */ + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { + compute_bytecode_size((JSFunctionBytecode *)gp, hp); + continue; + } else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { + continue; + } + p = (JSObject *)gp; + sh = p->shape; + s->obj_count++; + if (p->prop) { + s->memory_used_count++; + s->prop_size += sh->prop_size * sizeof(*p->prop); + s->prop_count += sh->prop_count; + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) { + compute_value_size(pr->u.value, hp); + } + prs++; + } + } + /* the hashed shapes are counted separately */ + if (!sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + + switch(p->class_id) { + case JS_CLASS_ARRAY: /* u.array | length */ + case JS_CLASS_ARGUMENTS: /* u.array | length */ + s->array_count++; + if (p->fast_array) { + s->fast_array_count++; + if (p->u.array.u.values) { + s->memory_used_count++; + s->memory_used_size += p->u.array.count * + sizeof(*p->u.array.u.values); + s->fast_array_elements += p->u.array.count; + for (i = 0; i < p->u.array.count; i++) { + compute_value_size(p->u.array.u.values[i], hp); + } + } + } + break; + case JS_CLASS_NUMBER: /* u.object_data */ + case JS_CLASS_STRING: /* u.object_data */ + case JS_CLASS_BOOLEAN: /* u.object_data */ + case JS_CLASS_SYMBOL: /* u.object_data */ + case JS_CLASS_DATE: /* u.object_data */ + case JS_CLASS_BIG_INT: /* u.object_data */ + compute_value_size(p->u.object_data, hp); + break; + case JS_CLASS_C_FUNCTION: /* u.cfunc */ + s->c_func_count++; + break; + case JS_CLASS_BYTECODE_FUNCTION: /* u.func */ + { + JSFunctionBytecode *b = p->u.func.function_bytecode; + JSVarRef **var_refs = p->u.func.var_refs; + /* home_object: object will be accounted for in list scan */ + if (var_refs) { + s->memory_used_count++; + s->js_func_size += b->closure_var_count * sizeof(*var_refs); + for (i = 0; i < b->closure_var_count; i++) { + if (var_refs[i]) { + double ref_count = var_refs[i]->header.ref_count; + s->memory_used_count += 1 / ref_count; + s->js_func_size += sizeof(*var_refs[i]) / ref_count; + /* handle non object closed values */ + if (var_refs[i]->pvalue == &var_refs[i]->value) { + /* potential multiple count */ + compute_value_size(var_refs[i]->value, hp); + } + } + } + } + } + break; + case JS_CLASS_BOUND_FUNCTION: /* u.bound_function */ + { + JSBoundFunction *bf = p->u.bound_function; + /* func_obj and this_val are objects */ + for (i = 0; i < bf->argc; i++) { + compute_value_size(bf->argv[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv); + } + break; + case JS_CLASS_C_FUNCTION_DATA: /* u.c_function_data_record */ + { + JSCFunctionDataRecord *fd = p->u.c_function_data_record; + if (fd) { + for (i = 0; i < fd->data_len; i++) { + compute_value_size(fd->data[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data); + } + } + break; + case JS_CLASS_REGEXP: /* u.regexp */ + compute_jsstring_size(p->u.regexp.pattern, hp); + compute_jsstring_size(p->u.regexp.bytecode, hp); + break; + + case JS_CLASS_FOR_IN_ITERATOR: /* u.for_in_iterator */ + { + JSForInIterator *it = p->u.for_in_iterator; + if (it) { + compute_value_size(it->obj, hp); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*it); + } + } + break; + case JS_CLASS_ARRAY_BUFFER: /* u.array_buffer */ + case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */ + { + JSArrayBuffer *abuf = p->u.array_buffer; + if (abuf) { + s->memory_used_count += 1; + s->memory_used_size += sizeof(*abuf); + if (abuf->data) { + s->memory_used_count += 1; + s->memory_used_size += abuf->byte_length; + } + } + } + break; + case JS_CLASS_GENERATOR: /* u.generator_data */ + case JS_CLASS_UINT8C_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_DATAVIEW: /* u.typed_array */ + case JS_CLASS_MAP: /* u.map_state */ + case JS_CLASS_SET: /* u.map_state */ + case JS_CLASS_WEAKMAP: /* u.map_state */ + case JS_CLASS_WEAKSET: /* u.map_state */ + case JS_CLASS_MAP_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_SET_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_ARRAY_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_STRING_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_PROXY: /* u.proxy_data */ + case JS_CLASS_PROMISE: /* u.promise_data */ + case JS_CLASS_PROMISE_RESOLVE_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_PROMISE_REJECT_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_ASYNC_FUNCTION_RESOLVE: /* u.async_function_data */ + case JS_CLASS_ASYNC_FUNCTION_REJECT: /* u.async_function_data */ + case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR: /* u.async_from_sync_iterator_data */ + case JS_CLASS_ASYNC_GENERATOR: /* u.async_generator_data */ + /* TODO */ + default: + /* XXX: class definition should have an opaque block size */ + if (p->u.opaque) { + s->memory_used_count += 1; + } + break; + } + } + s->obj_size += s->obj_count * sizeof(JSObject); + + /* hashed shapes */ + s->memory_used_count++; /* rt->shape_hash */ + s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size; + for(i = 0; i < rt->shape_hash_size; i++) { + JSShape *sh; + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + } + + /* atoms */ + s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */ + s->atom_count = rt->atom_count; + s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size + + sizeof(rt->atom_hash[0]) * rt->atom_hash_size; + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { + s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) + + 1 - p->is_wide_char); + } + } + s->str_count = round(mem.str_count); + s->str_size = round(mem.str_size); + s->js_func_count = mem.js_func_count; + s->js_func_size = round(mem.js_func_size); + s->js_func_code_size = mem.js_func_code_size; + s->js_func_pc2line_count = mem.js_func_pc2line_count; + s->js_func_pc2line_size = mem.js_func_pc2line_size; + s->memory_used_count += round(mem.memory_used_count) + + s->atom_count + s->str_count + + s->obj_count + s->shape_count + + s->js_func_count + s->js_func_pc2line_count; + s->memory_used_size += s->atom_size + s->str_size + + s->obj_size + s->prop_size + s->shape_size + + s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size; +} + +void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) +{ + fprintf(fp, "QuickJS-ng memory usage -- %s version, %d-bit, %s Endian, malloc limit: %"PRId64"\n\n", + JS_GetVersion(), (int)sizeof(void *) * 8, is_be() ? "Big" : "Little", s->malloc_limit); + if (rt) { + static const struct { + const char *name; + size_t size; + } object_types[] = { + { "JSRuntime", sizeof(JSRuntime) }, + { "JSContext", sizeof(JSContext) }, + { "JSObject", sizeof(JSObject) }, + { "JSString", sizeof(JSString) }, + { "JSFunctionBytecode", sizeof(JSFunctionBytecode) }, + }; + int i, usage_size_ok = 0; + for(i = 0; i < countof(object_types); i++) { + unsigned int size = object_types[i].size; + void *p = js_malloc_rt(rt, size); + if (p) { + unsigned int size1 = js_malloc_usable_size_rt(rt, p); + if (size1 >= size) { + usage_size_ok = 1; + fprintf(fp, " %3u + %-2u %s\n", + size, size1 - size, object_types[i].name); + } + js_free_rt(rt, p); + } + } + if (!usage_size_ok) { + fprintf(fp, " malloc_usable_size unavailable\n"); + } + { + int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 }; + int class_id; + struct list_head *el; + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; + } + } + fprintf(fp, "\n" "JSObject classes\n"); + if (obj_classes[0]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none"); + for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { + if (obj_classes[class_id] && class_id < rt->class_count) { + char buf[ATOM_GET_STR_BUF_SIZE]; + fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id, + JS_AtomGetStrRT(rt, buf, sizeof(buf), rt->class_array[class_id].class_name)); + } + } + if (obj_classes[JS_CLASS_INIT_COUNT]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other"); + } + fprintf(fp, "\n"); + } + fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE"); + + if (s->malloc_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per block)\n", + "memory allocated", s->malloc_count, s->malloc_size, + (double)s->malloc_size / s->malloc_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%d overhead, %0.1f average slack)\n", + "memory used", s->memory_used_count, s->memory_used_size, + MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) / + s->memory_used_count)); + } + if (s->atom_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per atom)\n", + "atoms", s->atom_count, s->atom_size, + (double)s->atom_size / s->atom_count); + } + if (s->str_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per string)\n", + "strings", s->str_count, s->str_size, + (double)s->str_size / s->str_count); + } + if (s->obj_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + "objects", s->obj_count, s->obj_size, + (double)s->obj_size / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + " properties", s->prop_count, s->prop_size, + (double)s->prop_count / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per shape)\n", + " shapes", s->shape_count, s->shape_size, + (double)s->shape_size / s->shape_count); + } + if (s->js_func_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "bytecode functions", s->js_func_count, s->js_func_size); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " bytecode", s->js_func_count, s->js_func_code_size, + (double)s->js_func_code_size / s->js_func_count); + if (s->js_func_pc2line_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " pc2line", s->js_func_pc2line_count, + s->js_func_pc2line_size, + (double)s->js_func_pc2line_size / s->js_func_pc2line_count); + } + } + if (s->c_func_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count); + } + if (s->array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count); + if (s->fast_array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", " fast arrays", s->fast_array_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per fast array)\n", + " elements", s->fast_array_elements, + s->fast_array_elements * (int)sizeof(JSValue), + (double)s->fast_array_elements / s->fast_array_count); + } + } + if (s->binary_object_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "binary objects", s->binary_object_count, s->binary_object_size); + } +} + +JSValue JS_GetGlobalObject(JSContext *ctx) +{ + return js_dup(ctx->global_obj); +} + +/* WARNING: obj is freed */ +JSValue JS_Throw(JSContext *ctx, JSValue obj) +{ + JSRuntime *rt = ctx->rt; + JS_FreeValue(ctx, rt->current_exception); + rt->current_exception = obj; + return JS_EXCEPTION; +} + +/* return the pending exception (cannot be called twice). */ +JSValue JS_GetException(JSContext *ctx) +{ + JSValue val; + JSRuntime *rt = ctx->rt; + val = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + return val; +} + +bool JS_HasException(JSContext *ctx) +{ + return !JS_IsUninitialized(ctx->rt->current_exception); +} + +static void dbuf_put_leb128(DynBuf *s, uint32_t v) +{ + uint32_t a; + for(;;) { + a = v & 0x7f; + v >>= 7; + if (v != 0) { + dbuf_putc(s, a | 0x80); + } else { + dbuf_putc(s, a); + break; + } + } +} + +static void dbuf_put_sleb128(DynBuf *s, int32_t v1) +{ + uint32_t v = v1; + dbuf_put_leb128(s, (2 * v) ^ -(v >> 31)); +} + +static int get_leb128(uint32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + const uint8_t *ptr = buf; + uint32_t v, a, i; + v = 0; + for(i = 0; i < 5; i++) { + if (unlikely(ptr >= buf_end)) + break; + a = *ptr++; + v |= (a & 0x7f) << (i * 7); + if (!(a & 0x80)) { + *pval = v; + return ptr - buf; + } + } + *pval = 0; + return -1; +} + +static int get_sleb128(int32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + int ret; + uint32_t val; + ret = get_leb128(&val, buf, buf_end); + if (ret < 0) { + *pval = 0; + return -1; + } + *pval = (val >> 1) ^ -(val & 1); + return ret; +} + +static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, + uint32_t pc_value, int *col) +{ + const uint8_t *p_end, *p; + int new_line_num, new_col_num, line_num, col_num, pc, v, ret; + unsigned int op; + + *col = 1; + p = b->pc2line_buf; + if (!p) + goto fail; + p_end = p + b->pc2line_len; + pc = 0; + line_num = b->line_num; + col_num = b->col_num; + while (p < p_end) { + op = *p++; + if (op == 0) { + uint32_t val; + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + pc += val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + new_line_num = line_num + v; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + new_col_num = col_num + v; + if (pc_value < pc) + break; + line_num = new_line_num; + col_num = new_col_num; + } + *col = col_num; + return line_num; +fail: + /* should never happen */ + return b->line_num; +} + +/* in order to avoid executing arbitrary code during the stack trace + generation, we only look at simple 'name' properties containing a + string. */ +static const char *get_func_name(JSContext *ctx, JSValueConst func) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValue val; + + if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT) + return NULL; + prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name); + if (!prs) + return NULL; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return NULL; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return NULL; + return JS_ToCString(ctx, val); +} + +/* Note: it is important that no exception is returned by this function */ +static bool can_add_backtrace(JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != JS_CLASS_ERROR && p->class_id != JS_CLASS_DOM_EXCEPTION) + return false; + if (find_own_property1(p, JS_ATOM_stack)) + return false; + return true; +} + +#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) +/* only taken into account if filename is provided */ +#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1) +#define JS_BACKTRACE_FLAG_FILTER_FUNC (1 << 2) + +/* if filename != NULL, an additional level is added with the filename + and line number information (used for parse error). */ +static void build_backtrace(JSContext *ctx, JSValueConst error_val, + JSValueConst filter_func, const char *filename, + int line_num, int col_num, int backtrace_flags) +{ + JSStackFrame *sf, *sf_start; + JSValue stack, prepare, saved_exception; + DynBuf dbuf; + const char *func_name_str; + const char *str1; + JSObject *p; + JSFunctionBytecode *b; + bool backtrace_barrier, has_prepare, has_filter_func; + JSRuntime *rt; + JSCallSiteData csd[64]; + uint32_t i; + double d; + int stack_trace_limit; + + rt = ctx->rt; + if (rt->in_build_stack_trace) + return; + rt->in_build_stack_trace = true; + + // Save exception because conversion to double may fail. + saved_exception = JS_GetException(ctx); + + // Extract stack trace limit. + // Ignore error since it sets d to NAN anyway. + // coverity[check_return] + JS_ToFloat64(ctx, &d, ctx->error_stack_trace_limit); + if (isnan(d) || d < 0.0) + stack_trace_limit = 0; + else if (d > INT32_MAX) + stack_trace_limit = INT32_MAX; + else + stack_trace_limit = fabs(d); + + // Restore current exception. + JS_Throw(ctx, saved_exception); + saved_exception = JS_UNINITIALIZED; + + stack_trace_limit = min_int(stack_trace_limit, countof(csd)); + stack_trace_limit = max_int(stack_trace_limit, 0); + has_prepare = false; + has_filter_func = backtrace_flags & JS_BACKTRACE_FLAG_FILTER_FUNC; + i = 0; + + if (!JS_IsNull(ctx->error_ctor)) { + prepare = js_dup(ctx->error_prepare_stack); + has_prepare = JS_IsFunction(ctx, prepare); + } + + if (has_prepare) { + saved_exception = JS_GetException(ctx); + if (stack_trace_limit == 0) + goto done; + if (filename) + js_new_callsite_data2(ctx, &csd[i++], filename, line_num, col_num); + } else { + js_dbuf_init(ctx, &dbuf); + if (stack_trace_limit == 0) + goto done; + if (filename) { + i++; + dbuf_printf(&dbuf, " at %s", filename); + if (line_num != -1) + dbuf_printf(&dbuf, ":%d:%d", line_num, col_num); + dbuf_putc(&dbuf, '\n'); + } + } + + if (filename && (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL)) + goto done; + + sf_start = rt->current_stack_frame; + + /* Find the frame we want to start from. Note that when a filter is used the filter + function will be the first, but we also specify we want to skip the first one. */ + if (has_filter_func) { + for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { + if (js_same_value(ctx, sf->cur_func, filter_func)) { + sf_start = sf; + break; + } + } + } + + for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { + if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { + backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; + continue; + } + + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = NULL; + backtrace_barrier = false; + + if (js_class_has_bytecode(p->class_id)) { + b = p->u.func.function_bytecode; + backtrace_barrier = b->backtrace_barrier; + } + + if (has_prepare) { + js_new_callsite_data(ctx, &csd[i], sf); + } else { + /* func_name_str is UTF-8 encoded if needed */ + func_name_str = get_func_name(ctx, sf->cur_func); + if (!func_name_str || func_name_str[0] == '\0') + str1 = ""; + else + str1 = func_name_str; + dbuf_printf(&dbuf, " at %s", str1); + JS_FreeCString(ctx, func_name_str); + + if (b && sf->cur_pc) { + const char *atom_str; + int line_num1, col_num1; + uint32_t pc; + + pc = sf->cur_pc - b->byte_code_buf - 1; + line_num1 = find_line_num(ctx, b, pc, &col_num1); + atom_str = b->filename ? JS_AtomToCString(ctx, b->filename) : NULL; + dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : ""); + JS_FreeCString(ctx, atom_str); + if (line_num1 != -1) + dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1); + dbuf_putc(&dbuf, ')'); + } else if (b) { + // FIXME(bnoordhuis) Missing `sf->cur_pc = pc` in bytecode + // handler in JS_CallInternal. Almost never user observable + // except with intercepting JS proxies that throw exceptions. + dbuf_printf(&dbuf, " (missing)"); + } else { + dbuf_printf(&dbuf, " (native)"); + } + dbuf_putc(&dbuf, '\n'); + } + i++; + + /* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */ + if (backtrace_barrier) + break; + } + done: + if (has_prepare) { + int j = 0, k; + stack = JS_NewArray(ctx); + if (JS_IsException(stack)) { + stack = JS_NULL; + } else { + for (; j < i; j++) { + JSValue v = js_new_callsite(ctx, &csd[j]); + if (JS_IsException(v)) + break; + if (JS_DefinePropertyValueUint32(ctx, stack, j, v, JS_PROP_C_W_E) < 0) { + JS_FreeValue(ctx, v); + break; + } + } + } + // Clear the csd's we didn't use in case of error. + for (k = j; k < i; k++) { + JS_FreeValue(ctx, csd[k].filename); + JS_FreeValue(ctx, csd[k].func); + JS_FreeValue(ctx, csd[k].func_name); + } + JSValueConst args[] = { + error_val, + stack, + }; + JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args); + JS_FreeValue(ctx, stack); + if (JS_IsException(stack2)) + stack = JS_NULL; + else + stack = stack2; + JS_FreeValue(ctx, prepare); + JS_Throw(ctx, saved_exception); + } else { + if (dbuf_error(&dbuf)) + stack = JS_NULL; + else + stack = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); + dbuf_free(&dbuf); + } + + if (JS_IsUndefined(ctx->error_back_trace)) + ctx->error_back_trace = js_dup(stack); + if (has_filter_func || can_add_backtrace(error_val)) { + JS_DefinePropertyValue(ctx, error_val, JS_ATOM_stack, stack, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } else { + JS_FreeValue(ctx, stack); + } + + rt->in_build_stack_trace = false; +} + +JSValue JS_NewError(JSContext *ctx) +{ + JSValue obj = JS_NewObjectClass(ctx, JS_CLASS_ERROR); + if (JS_IsException(obj)) + return JS_EXCEPTION; + build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0); + return obj; +} + +static JSValue JS_MakeError2(JSContext *ctx, JSErrorEnum error_num, + bool add_backtrace, const char *message) +{ + JSValue obj, msg; + + if (error_num == JS_PLAIN_ERROR) { + obj = JS_NewObjectClass(ctx, JS_CLASS_ERROR); + } else { + obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num], + JS_CLASS_ERROR); + } + if (JS_IsException(obj)) + return JS_EXCEPTION; + msg = JS_NewString(ctx, message); + if (JS_IsException(msg)) + msg = JS_NewString(ctx, "Invalid error message"); + if (!JS_IsException(msg)) { + JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + if (add_backtrace) + build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0); + return obj; +} + +static JSValue JS_PRINTF_FORMAT_ATTR(4, 0) +JS_MakeError(JSContext *ctx, JSErrorEnum error_num, bool add_backtrace, + JS_PRINTF_FORMAT const char *fmt, va_list ap) +{ + char buf[256]; + + vsnprintf(buf, sizeof(buf), fmt, ap); + return JS_MakeError2(ctx, error_num, add_backtrace, buf); +} + +/* fmt and arguments may be pure ASCII or UTF-8 encoded contents */ +static JSValue JS_PRINTF_FORMAT_ATTR(4, 0) +JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, bool add_backtrace, + JS_PRINTF_FORMAT const char *fmt, va_list ap) +{ + JSValue obj; + + obj = JS_MakeError(ctx, error_num, add_backtrace, fmt, ap); + if (unlikely(JS_IsException(obj))) { + /* out of memory: throw JS_NULL to avoid recursing */ + obj = JS_NULL; + } + return JS_Throw(ctx, obj); +} + +static JSValue JS_PRINTF_FORMAT_ATTR(3, 0) +JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, + JS_PRINTF_FORMAT const char *fmt, va_list ap) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame *sf; + bool add_backtrace; + + /* the backtrace is added later if called from a bytecode function */ + sf = rt->current_stack_frame; + add_backtrace = !rt->in_out_of_memory && + (!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL)); + return JS_ThrowError2(ctx, error_num, add_backtrace, fmt, ap); +} + +#define JS_ERROR_MAP(X) \ + X(Internal, INTERNAL) \ + X(Plain, PLAIN) \ + X(Range, RANGE) \ + X(Reference, REFERENCE) \ + X(Syntax, SYNTAX) \ + X(Type, TYPE) \ + +#define X(lc, uc) \ + JSValue JS_PRINTF_FORMAT_ATTR(2, 3) \ + JS_New##lc##Error(JSContext *ctx, \ + JS_PRINTF_FORMAT const char *fmt, ...) \ + { \ + JSValue val; \ + va_list ap; \ + \ + va_start(ap, fmt); \ + val = JS_MakeError(ctx, JS_##uc##_ERROR, \ + /*add_backtrace*/true, fmt, ap); \ + va_end(ap); \ + return val; \ + } \ + JSValue JS_PRINTF_FORMAT_ATTR(2, 3) \ + JS_Throw##lc##Error(JSContext *ctx, \ + JS_PRINTF_FORMAT const char *fmt, ...) \ + { \ + JSValue val; \ + va_list ap; \ + \ + va_start(ap, fmt); \ + val = JS_ThrowError(ctx, JS_##uc##_ERROR, fmt, ap); \ + va_end(ap); \ + return val; \ + } \ + +JS_ERROR_MAP(X) + +#undef X +#undef JS_ERROR_MAP + +static int JS_PRINTF_FORMAT_ATTR(3, 4) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, JS_PRINTF_FORMAT const char *fmt, ...) +{ + va_list ap; + + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + va_start(ap, fmt); + JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); + va_end(ap); + return -1; + } else { + return false; + } +} + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif // __GNUC__ +static JSValue JS_ThrowTypeErrorAtom(JSContext *ctx, const char *fmt, JSAtom atom) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + JS_AtomGetStr(ctx, buf, sizeof(buf), atom); + return JS_ThrowTypeError(ctx, fmt, buf); +} + +static JSValue JS_ThrowSyntaxErrorAtom(JSContext *ctx, const char *fmt, JSAtom atom) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + JS_AtomGetStr(ctx, buf, sizeof(buf), atom); + return JS_ThrowSyntaxError(ctx, fmt, buf); +} +#ifdef __GNUC__ +#pragma GCC diagnostic pop // ignored "-Wformat-nonliteral" +#endif // __GNUC__ + +static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) +{ + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); + return -1; + } else { + return false; + } +} + +JSValue JS_ThrowOutOfMemory(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + if (!rt->in_out_of_memory) { + rt->in_out_of_memory = true; + JS_ThrowInternalError(ctx, "out of memory"); + rt->in_out_of_memory = false; + } + return JS_EXCEPTION; +} + +static JSValue JS_ThrowStackOverflow(JSContext *ctx) +{ + return JS_ThrowRangeError(ctx, "Maximum call stack size exceeded"); +} + +static JSValue JS_ThrowTypeErrorNotAConstructor(JSContext *ctx, + JSValueConst func_obj) +{ + JSObject *p; + JSAtom name; + + if (JS_TAG_OBJECT != JS_VALUE_GET_TAG(func_obj)) + goto fini; + p = JS_VALUE_GET_OBJ(func_obj); + if (!js_class_has_bytecode(p->class_id)) + goto fini; + name = p->u.func.function_bytecode->func_name; + if (name == JS_ATOM_NULL) + goto fini; + return JS_ThrowTypeErrorAtom(ctx, "%s is not a constructor", name); +fini: + return JS_ThrowTypeError(ctx, "not a constructor"); +} + +static JSValue JS_ThrowTypeErrorNotAFunction(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not a function"); +} + +static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not an object"); +} + +static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not a symbol"); +} + +static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "%s is not defined", + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "%s is not initialized", + name == JS_ATOM_NULL ? "lexical variable" : + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx, + JSFunctionBytecode *b, + int idx, bool is_ref) +{ + JSAtom atom = JS_ATOM_NULL; + if (is_ref) { + atom = b->closure_var[idx].var_name; + } else { + /* not present if the function is stripped and contains no eval() */ + if (b->vardefs) + atom = b->vardefs[b->arg_count + idx].var_name; + } + return JS_ThrowReferenceErrorUninitialized(ctx, atom); +} + +static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) +{ + JSRuntime *rt = ctx->rt; + JSAtom name; + name = rt->class_array[class_id].class_name; + return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); +} + +static void JS_ThrowInterrupted(JSContext *ctx) +{ + JS_ThrowInternalError(ctx, "interrupted"); + JS_SetUncatchableError(ctx, ctx->rt->current_exception); +} + +static no_inline __exception int __js_poll_interrupts(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (rt->interrupt_handler) { + if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { + JS_ThrowInterrupted(ctx); + return -1; + } + } + return 0; +} + +static inline __exception int js_poll_interrupts(JSContext *ctx) +{ + if (unlikely(--ctx->interrupt_counter <= 0)) { + return __js_poll_interrupts(ctx); + } else { + return 0; + } +} + +/* return -1 (exception) or true/false */ +static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, + JSValueConst proto_val, bool throw_flag) +{ + JSObject *proto, *p, *p1; + JSShape *sh; + + if (throw_flag) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL || + JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED) + goto not_obj; + } else { + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + goto not_obj; + } + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) { + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + proto = NULL; + } else { + proto = JS_VALUE_GET_OBJ(proto_val); + } + + if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return true; + + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag); + sh = p->shape; + if (sh->proto == proto) + return true; + if (p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "'Immutable prototype object \'Object.prototype\' cannot have their prototype set'"); + return -1; + } + return false; + } + if (!p->extensible) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "object is not extensible"); + return -1; + } else { + return false; + } + } + if (proto) { + /* check if there is a cycle */ + p1 = proto; + do { + if (p1 == p) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "circular prototype chain"); + return -1; + } else { + return false; + } + } + /* Note: for Proxy objects, proto is NULL */ + p1 = p1->shape->proto; + } while (p1 != NULL); + js_dup(proto_val); + } + + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + sh = p->shape; + if (sh->proto) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + sh->proto = proto; + return true; +} + +/* return -1 (exception) or true/false */ +int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val) +{ + return JS_SetPrototypeInternal(ctx, obj, proto_val, true); +} + +/* Only works for primitive types, otherwise return JS_NULL. */ +static JSValueConst JS_GetPrototypePrimitive(JSContext *ctx, JSValueConst val) +{ + JSValue ret; + switch(JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + ret = ctx->class_proto[JS_CLASS_BIG_INT]; + break; + case JS_TAG_INT: + case JS_TAG_FLOAT64: + ret = ctx->class_proto[JS_CLASS_NUMBER]; + break; + case JS_TAG_BOOL: + ret = ctx->class_proto[JS_CLASS_BOOLEAN]; + break; + case JS_TAG_STRING: + ret = ctx->class_proto[JS_CLASS_STRING]; + break; + case JS_TAG_SYMBOL: + ret = ctx->class_proto[JS_CLASS_SYMBOL]; + break; + case JS_TAG_OBJECT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + default: + ret = JS_NULL; + break; + } + return ret; +} + +/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ +JSValue JS_GetPrototype(JSContext *ctx, JSValueConst obj) +{ + JSValue val; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) { + val = js_proxy_getPrototypeOf(ctx, obj); + } else { + p = p->shape->proto; + if (!p) + val = JS_NULL; + else + val = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + } + } else { + val = js_dup(JS_GetPrototypePrimitive(ctx, obj)); + } + return val; +} + +static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj) +{ + JSValue obj1; + obj1 = JS_GetPrototype(ctx, obj); + JS_FreeValue(ctx, obj); + return obj1; +} + +int JS_GetLength(JSContext *ctx, JSValueConst obj, int64_t *pres) { + return js_get_length64(ctx, pres, obj); +} + +int JS_SetLength(JSContext *ctx, JSValueConst obj, int64_t len) { + return js_set_length64(ctx, obj, len); +} + +/* return true, false or (-1) in case of exception */ +static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val, + JSValueConst obj) +{ + JSValue obj_proto; + JSObject *proto; + const JSObject *p, *proto1; + int ret; + + if (!JS_IsFunction(ctx, obj)) + return false; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_BOUND_FUNCTION) { + JSBoundFunction *s = p->u.bound_function; + return JS_IsInstanceOf(ctx, val, s->func_obj); + } + + /* Only explicitly boxed values are instances of constructors */ + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype); + if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) { + if (!JS_IsException(obj_proto)) + JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object"); + ret = -1; + goto done; + } + proto = JS_VALUE_GET_OBJ(obj_proto); + p = JS_VALUE_GET_OBJ(val); + for(;;) { + proto1 = p->shape->proto; + if (!proto1) { + /* slow case if proxy in the prototype chain */ + if (unlikely(p->class_id == JS_CLASS_PROXY)) { + JSValue obj1; + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsException(obj1)) { + ret = -1; + break; + } + if (JS_IsNull(obj1)) { + ret = false; + break; + } + if (proto == JS_VALUE_GET_OBJ(obj1)) { + JS_FreeValue(ctx, obj1); + ret = true; + break; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + ret = -1; + break; + } + } + } else { + ret = false; + } + break; + } + p = proto1; + if (proto == p) { + ret = true; + break; + } + } +done: + JS_FreeValue(ctx, obj_proto); + return ret; +} + +/* return true, false or (-1) in case of exception */ +int JS_IsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj) +{ + JSValue method; + + if (!JS_IsObject(obj)) + goto fail; + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance); + if (JS_IsException(method)) + return -1; + if (!JS_IsNull(method) && !JS_IsUndefined(method)) { + JSValue ret; + ret = JS_CallFree(ctx, method, obj, 1, &val); + return JS_ToBoolFree(ctx, ret); + } + + /* legacy case */ + if (!JS_IsFunction(ctx, obj)) { + fail: + JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); + return -1; + } + return JS_OrdinaryIsInstanceOf(ctx, val, obj); +} + +#include "builtin-array-fromasync.h" + +static JSValue js_bytecode_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, + void *opaque) +{ + switch ((uintptr_t)opaque) { + default: + abort(); + case JS_BUILTIN_ARRAY_FROMASYNC: + { + JSValue obj = JS_ReadObject(ctx, qjsc_builtin_array_fromasync, + sizeof(qjsc_builtin_array_fromasync), + JS_READ_OBJ_BYTECODE); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JSValue fun = JS_EvalFunction(ctx, obj); + if (JS_IsException(fun)) + return JS_EXCEPTION; + assert(JS_IsFunction(ctx, fun)); + JSValue args[] = { + JS_NewCFunction(ctx, js_array_constructor, "Array", 0), + JS_NewCFunctionMagic(ctx, js_error_constructor, "TypeError", 1, + JS_CFUNC_constructor_or_func_magic, + JS_TYPE_ERROR), + JS_AtomToValue(ctx, JS_ATOM_Symbol_asyncIterator), + JS_NewCFunctionMagic(ctx, js_object_defineProperty, + "Object.defineProperty", 3, + JS_CFUNC_generic_magic, 0), + JS_AtomToValue(ctx, JS_ATOM_Symbol_iterator), + }; + JSValue result = JS_Call(ctx, fun, JS_UNDEFINED, + countof(args), vc(args)); + for (size_t i = 0; i < countof(args); i++) + JS_FreeValue(ctx, args[i]); + JS_FreeValue(ctx, fun); + if (JS_SetPrototypeInternal(ctx, result, ctx->function_proto, + /*throw_flag*/true) < 0) { + JS_FreeValue(ctx, result); + return JS_EXCEPTION; + } + return result; + } + } + return JS_UNDEFINED; +} + +/* return the value associated to the autoinit property or an exception */ +typedef JSValue JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); + +static JSAutoInitFunc *const js_autoinit_func_table[] = { + js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */ + js_module_ns_autoinit, /* JS_AUTOINIT_ID_MODULE_NS */ + JS_InstantiateFunctionListItem2, /* JS_AUTOINIT_ID_PROP */ + js_bytecode_autoinit, /* JS_AUTOINIT_ID_BYTECODE */ +}; + +/* warning: 'prs' is reallocated after it */ +static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, + JSProperty *pr, JSShapeProperty *prs) +{ + JSValue val; + JSContext *realm; + JSAutoInitFunc *func; + + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + + realm = js_autoinit_get_realm(pr); + func = js_autoinit_func_table[js_autoinit_get_id(pr)]; + /* 'func' shall not modify the object properties 'pr' */ + val = func(realm, p, prop, pr->u.init.opaque); + js_autoinit_free(ctx->rt, pr); + prs->flags &= ~JS_PROP_TMASK; + pr->u.value = JS_UNDEFINED; + if (JS_IsException(val)) + return -1; + pr->u.value = val; + return 0; +} + +static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, + JSAtom prop, JSValueConst this_obj, + bool throw_ref_error) +{ + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + uint32_t tag; + + tag = JS_VALUE_GET_TAG(obj); + if (unlikely(tag != JS_TAG_OBJECT)) { + switch(tag) { + case JS_TAG_NULL: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", prop); + case JS_TAG_UNDEFINED: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", prop); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_STRING: + { + JSString *p1 = JS_VALUE_GET_STRING(obj); + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx, ch; + idx = __JS_AtomToUInt32(prop); + if (idx < p1->len) { + ch = string_get(p1, idx); + return js_new_string_char(ctx, ch); + } + } else if (prop == JS_ATOM_length) { + return js_int32(p1->len); + } + } + break; + default: + break; + } + /* cannot raise an exception */ + p = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); + if (!p) + return JS_UNDEFINED; + } else { + p = JS_VALUE_GET_OBJ(obj); + } + + for(;;) { + prs = find_own_property(&pr, p, prop); + if (prs) { + /* found */ + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (unlikely(!pr->u.getset.getter)) { + return JS_UNDEFINED; + } else { + JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter); + /* Note: the field could be removed in the getter */ + func = js_dup(func); + return JS_CallFree(ctx, func, this_obj, 0, NULL); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) + return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return js_dup(val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return JS_EXCEPTION; + continue; + } + } else { + return js_dup(pr->u.value); + } + } + if (unlikely(p->is_exotic)) { + /* exotic behaviors */ + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + /* we avoid duplicating the code */ + return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } else if (is_typed_array(p->class_id)) { + return JS_UNDEFINED; + } + } else if (is_typed_array(p->class_id)) { + int ret; + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return JS_EXCEPTION; + return JS_UNDEFINED; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->get_property) { + JSValue obj1, retval; + /* XXX: should pass throw_ref_error */ + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + retval = em->get_property(ctx, obj1, prop, this_obj); + JS_FreeValue(ctx, obj1); + return retval; + } + if (em->get_own_property) { + JSPropertyDescriptor desc; + int ret; + JSValue obj1; + + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->get_own_property(ctx, &desc, obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) + return JS_EXCEPTION; + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.setter); + return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL); + } else { + return desc.value; + } + } + } + } + } + } + p = p->shape->proto; + if (!p) + break; + } + if (unlikely(throw_ref_error)) { + return JS_ThrowReferenceErrorNotDefined(ctx, prop); + } else { + return JS_UNDEFINED; + } +} + +JSValue JS_GetProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop) +{ + return JS_GetPropertyInternal(ctx, this_obj, prop, this_obj, false); +} + +static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) +{ + return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist", + atom); +} + +/* Private fields can be added even on non extensible objects or + Proxies */ +static int JS_DefinePrivateField(JSContext *ctx, JSValueConst obj, + JSValue name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", + prop); + goto fail; + } + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (unlikely(!pr)) { + fail: + JS_FreeValue(ctx, val); + return -1; + } + pr->u.value = val; + return 0; +} + +static JSValue JS_GetPrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return JS_ThrowTypeErrorNotAnObject(ctx); + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) + return JS_ThrowTypeErrorNotASymbol(ctx); + prop = js_symbol_to_atom(ctx, name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + return JS_EXCEPTION; + } + return js_dup(pr->u.value); +} + +static int JS_SetPrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + fail: + JS_FreeValue(ctx, val); + return -1; + } + set_value(ctx, &pr->u.value, val); + return 0; +} + +/* add a private brand field to 'home_obj' if not already present and + if obj is != null add a private brand to it */ +static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + JSValue brand; + JSAtom brand_atom; + + if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(home_obj); + prs = find_own_property(&pr, p, JS_ATOM_Private_brand); + if (!prs) { + /* if the brand is not present, add it */ + brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(brand)) + return -1; + pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); + if (!pr) { + JS_FreeValue(ctx, brand); + return -1; + } + pr->u.value = js_dup(brand); + } else { + brand = js_dup(pr->u.value); + } + brand_atom = js_symbol_to_atom(ctx, brand); + + if (JS_IsObject(obj)) { + p1 = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p1, brand_atom); + if (unlikely(prs)) { + JS_FreeAtom(ctx, brand_atom); + JS_ThrowTypeError(ctx, "private method is already present"); + return -1; + } + pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); + JS_FreeAtom(ctx, brand_atom); + if (!pr) + return -1; + pr->u.value = JS_UNDEFINED; + } else { + JS_FreeAtom(ctx, brand_atom); + } + + return 0; +} + +/* return a boolean telling if the brand of the home object of 'func' + is present on 'obj' or -1 in case of exception */ +static int JS_CheckBrand(JSContext *ctx, JSValue obj, JSValue func) +{ + JSObject *p, *p1, *home_obj; + JSShapeProperty *prs; + JSProperty *pr; + JSValue brand; + + /* get the home object of 'func' */ + if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) + goto not_obj; + p1 = JS_VALUE_GET_OBJ(func); + if (!js_class_has_bytecode(p1->class_id)) + goto not_obj; + home_obj = p1->u.func.home_object; + if (!home_obj) + goto not_obj; + prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand); + if (!prs) { + JS_ThrowTypeError(ctx, "expecting private field"); + return -1; + } + brand = pr->u.value; + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL)) + goto not_obj; + + /* get the brand array of 'obj' */ + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, brand)); + return (prs != NULL); +} + +static uint32_t js_string_obj_get_length(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + JSString *p1; + uint32_t len = 0; + + /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { + p1 = JS_VALUE_GET_STRING(p->u.object_data); + len = p1->len; + } + return len; +} + +static int num_keys_cmp(const void *p1, const void *p2, void *opaque) +{ + JSContext *ctx = opaque; + JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom; + JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom; + uint32_t v1, v2; + bool atom1_is_integer, atom2_is_integer; + + atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1); + atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2); + assert(atom1_is_integer && atom2_is_integer); + if (v1 < v2) + return -1; + else if (v1 == v2) + return 0; + else + return 1; +} + +static void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) +{ + uint32_t i; + if (tab) { + for(i = 0; i < len; i++) + JS_FreeAtom(ctx, tab[i].atom); + js_free(ctx, tab); + } +} + +/* return < 0 in case if exception, 0 if OK. ptab and its atoms must + be freed by the user. */ +static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, + JSPropertyEnum **ptab, + uint32_t *plen, + JSObject *p, int flags) +{ + int i, j; + JSShape *sh; + JSShapeProperty *prs; + JSPropertyEnum *tab_atom, *tab_exotic; + JSAtom atom; + uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count; + uint32_t num_index, str_index, sym_index, exotic_count, exotic_keys_count; + bool is_enumerable, num_sorted; + uint32_t num_key; + JSAtomKindEnum kind; + + /* clear pointer for consistency in case of failure */ + *ptab = NULL; + *plen = 0; + + /* compute the number of returned properties */ + num_keys_count = 0; + str_keys_count = 0; + sym_keys_count = 0; + exotic_keys_count = 0; + exotic_count = 0; + tab_exotic = NULL; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + /* need to raise an exception in case of the module + name space (implicit GetOwnProperty) */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) && + (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) { + JSVarRef *var_ref = p->prop[i].u.var_ref; + if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + num_keys_count++; + } else if (kind == JS_ATOM_KIND_STRING) { + str_keys_count++; + } else { + sym_keys_count++; + } + } + } + } + + if (p->is_exotic) { + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += p->u.array.count; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property_names) { + if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count, + JS_MKPTR(JS_TAG_OBJECT, p))) + return -1; + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + kind = JS_AtomGetKind(ctx, atom); + if (((flags >> kind) & 1) != 0) { + is_enumerable = false; + if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) { + JSPropertyDescriptor desc; + int res; + /* set the "is_enumerable" field if necessary */ + res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); + if (res < 0) { + js_free_prop_enum(ctx, tab_exotic, exotic_count); + return -1; + } + if (res) { + is_enumerable = + ((desc.flags & JS_PROP_ENUMERABLE) != 0); + js_free_desc(ctx, &desc); + } + tab_exotic[i].is_enumerable = is_enumerable; + } + if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) { + exotic_keys_count++; + } + } + } + } + } + } + + /* fill them */ + + atom_count = num_keys_count + str_keys_count + sym_keys_count + exotic_keys_count; + /* avoid allocating 0 bytes */ + tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); + if (!tab_atom) { + js_free_prop_enum(ctx, tab_exotic, exotic_count); + return -1; + } + + num_index = 0; + str_index = num_keys_count; + sym_index = str_index + str_keys_count; + + num_sorted = true; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + j = num_index++; + num_sorted = false; + } else if (kind == JS_ATOM_KIND_STRING) { + j = str_index++; + } else { + j = sym_index++; + } + tab_atom[j].atom = JS_DupAtom(ctx, atom); + tab_atom[j].is_enumerable = is_enumerable; + } + } + } + + if (p->is_exotic) { + int len; + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + len = p->u.array.count; + goto add_array_keys; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + len = js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + add_array_keys: + for(i = 0; i < len; i++) { + tab_atom[num_index].atom = __JS_AtomFromUInt32(i); + if (tab_atom[num_index].atom == JS_ATOM_NULL) { + js_free_prop_enum(ctx, tab_atom, num_index); + return -1; + } + tab_atom[num_index].is_enumerable = true; + num_index++; + } + } + } else { + /* Note: exotic keys are not reordered and comes after the object own properties. */ + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + is_enumerable = tab_exotic[i].is_enumerable; + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + tab_atom[sym_index].atom = atom; + tab_atom[sym_index].is_enumerable = is_enumerable; + sym_index++; + } else { + JS_FreeAtom(ctx, atom); + } + } + js_free(ctx, tab_exotic); + } + } + + assert(num_index == num_keys_count); + assert(str_index == num_keys_count + str_keys_count); + assert(sym_index == atom_count); + + if (num_keys_count != 0 && !num_sorted) { + rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp, + ctx); + } + *ptab = tab_atom; + *plen = atom_count; + return 0; +} + +int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, JSValueConst obj, int flags) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, + JS_VALUE_GET_OBJ(obj), flags); +} + +/* Return -1 if exception, + false if the property does not exist, true if it exists. If true is + returned, the property descriptor 'desc' is filled present. */ +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop) +{ + JSShapeProperty *prs; + JSProperty *pr; + +retry: + prs = find_own_property(&pr, p, prop); + if (prs) { + if (desc) { + desc->flags = prs->flags & JS_PROP_C_W_E; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_UNDEFINED; + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + desc->flags |= JS_PROP_GETSET; + if (pr->u.getset.getter) + desc->getter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + desc->setter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + desc->value = js_dup(val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto retry; + } + } else { + desc->value = js_dup(pr->u.value); + } + } else { + /* for consistency, send the exception even if desc is NULL */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) { + if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* nothing to do: delay instantiation until actual value and/or attributes are read */ + } + } + return true; + } + if (p->is_exotic) { + if (p->fast_array) { + /* specific case for fast arrays */ + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx; + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + if (desc) { + desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | + JS_PROP_CONFIGURABLE; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } + return true; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property) { + return em->get_own_property(ctx, desc, + JS_MKPTR(JS_TAG_OBJECT, p), prop); + } + } + } + return false; +} + +int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop); +} + +void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, + uint32_t len) +{ + js_free_prop_enum(ctx, tab, len); +} + +/* return -1 if exception (Proxy object only) or true/false */ +int JS_IsExtensible(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return false; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_isExtensible(ctx, obj); + else + return p->extensible; +} + +/* return -1 if exception (Proxy object only) or true/false */ +int JS_PreventExtensions(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return false; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_preventExtensions(ctx, obj); + p->extensible = false; + return true; +} + +/* return -1 if exception otherwise true or false */ +int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop) +{ + JSObject *p; + int ret; + JSValue obj1; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return false; + p = JS_VALUE_GET_OBJ(obj); + for(;;) { + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->has_property) { + /* has_property can free the prototype */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->has_property(ctx, obj1, prop); + JS_FreeValue(ctx, obj1); + return ret; + } + } + /* JS_GetOwnPropertyInternal can free the prototype */ + js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret != 0) + return ret; + if (is_typed_array(p->class_id)) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return false; + } + } + p = p->shape->proto; + if (!p) + break; + } + return false; +} + +/* val must be a symbol */ +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValueConst val) +{ + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + return js_get_atom_index(ctx->rt, p); +} + +/* return JS_ATOM_NULL in case of exception */ +static JSAtom JS_ValueToAtomInternal(JSContext *ctx, JSValueConst val, + int flags) +{ + JSAtom atom; + uint32_t tag; + tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_INT && + (uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) { + /* fast path for integer values */ + atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val)); + } else if (tag == JS_TAG_SYMBOL) { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p)); + } else { + JSValue str; + str = JS_ToPropertyKeyInternal(ctx, val, flags); + if (JS_IsException(str)) + return JS_ATOM_NULL; + if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) { + atom = js_symbol_to_atom(ctx, str); + } else { + atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str)); + } + } + return atom; +} + +JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val) +{ + return JS_ValueToAtomInternal(ctx, val, /*flags*/0); +} + +static bool js_get_fast_array_element(JSContext *ctx, JSObject *p, + uint32_t idx, JSValue *pval) +{ + switch(p->class_id) { + case JS_CLASS_ARRAY: + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_dup(p->u.array.u.values[idx]); + return true; + case JS_CLASS_INT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.int8_ptr[idx]); + return true; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.uint8_ptr[idx]); + return true; + case JS_CLASS_INT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.int16_ptr[idx]); + return true; + case JS_CLASS_UINT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.uint16_ptr[idx]); + return true; + case JS_CLASS_INT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_int32(p->u.array.u.int32_ptr[idx]); + return true; + case JS_CLASS_UINT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_uint32(p->u.array.u.uint32_ptr[idx]); + return true; + case JS_CLASS_BIG_INT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); + return true; + case JS_CLASS_BIG_UINT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); + return true; + case JS_CLASS_FLOAT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_float64(fromfp16(p->u.array.u.fp16_ptr[idx])); + return true; + case JS_CLASS_FLOAT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_float64(p->u.array.u.float_ptr[idx]); + return true; + case JS_CLASS_FLOAT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) return false; + *pval = js_float64(p->u.array.u.double_ptr[idx]); + return true; + default: + return false; + } +} + +static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop) +{ + JSAtom atom; + JSValue ret; + uint32_t tag; + + tag = JS_VALUE_GET_TAG(this_obj); + if (likely(tag == JS_TAG_OBJECT)) { + if (JS_VALUE_GET_TAG(prop) == JS_TAG_INT) { + JSObject *p = JS_VALUE_GET_OBJ(this_obj); + uint32_t idx = JS_VALUE_GET_INT(prop); + JSValue val; + /* fast path for array and typed array access */ + if (js_get_fast_array_element(ctx, p, idx, &val)) + return val; + } + } else if (unlikely(tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED)) { + // per spec: not allowed to call ToPropertyKey before ToObject + // so we must ensure to not invoke JS anything that's observable + // from JS code + atom = JS_ValueToAtomInternal(ctx, prop, JS_TO_STRING_NO_SIDE_EFFECTS); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + if (tag == JS_TAG_NULL) { + JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", atom); + } else { + JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", atom); + } + JS_FreeAtom(ctx, atom); + return JS_EXCEPTION; + } + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + +JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx) +{ + return JS_GetPropertyInt64(ctx, this_obj, idx); +} + +/* Check if an object has a generalized numeric property. Return value: + -1 for exception, *pval set to JS_EXCEPTION + true if property exists, stored into *pval, + false if property does not exist. *pval set to JS_UNDEFINED. + */ +static int JS_TryGetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, JSValue *pval) +{ + JSValue val; + JSAtom prop; + int present; + + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && + (uint64_t)idx <= INT32_MAX)) { + /* fast path for array and typed array access */ + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (js_get_fast_array_element(ctx, p, idx, pval)) + return true; + } + val = JS_EXCEPTION; + present = -1; + prop = JS_NewAtomInt64(ctx, idx); + if (likely(prop != JS_ATOM_NULL)) { + present = JS_HasProperty(ctx, obj, prop); + if (present > 0) { + val = JS_GetProperty(ctx, obj, prop); + if (unlikely(JS_IsException(val))) + present = -1; + } else if (present == false) { + val = JS_UNDEFINED; + } + JS_FreeAtom(ctx, prop); + } + *pval = val; + return present; +} + +JSValue JS_GetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx) +{ + JSAtom prop; + JSValue val; + + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && + (uint64_t)idx <= INT32_MAX)) { + /* fast path for array and typed array access */ + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (js_get_fast_array_element(ctx, p, idx, &val)) + return val; + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return JS_EXCEPTION; + + val = JS_GetProperty(ctx, obj, prop); + JS_FreeAtom(ctx, prop); + return val; +} + +/* `prop` may be pure ASCII or UTF-8 encoded */ +JSValue JS_GetPropertyStr(JSContext *ctx, JSValueConst this_obj, + const char *prop) +{ + JSAtom atom; + JSValue ret; + atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* Note: the property value is not initialized. Return NULL if memory + error. */ +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags) +{ + JSShape *sh, *new_sh; + + sh = p->shape; + if (sh->is_hashed) { + /* try to find an existing shape */ + new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags); + if (new_sh) { + /* matching shape found: use it */ + /* the property array may need to be resized */ + if (new_sh->prop_size != sh->prop_size) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) * + new_sh->prop_size); + if (!new_prop) + return NULL; + p->prop = new_prop; + } + p->shape = js_dup_shape(new_sh); + js_free_shape(ctx->rt, sh); + return &p->prop[new_sh->prop_count - 1]; + } else if (sh->header.ref_count != 1) { + /* if the shape is shared, clone it */ + new_sh = js_clone_shape(ctx, sh); + if (!new_sh) + return NULL; + /* hash the cloned shape */ + new_sh->is_hashed = true; + js_shape_hash_link(ctx->rt, new_sh); + js_free_shape(ctx->rt, p->shape); + p->shape = new_sh; + } + } + assert(p->shape->header.ref_count == 1); + if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) + return NULL; + return &p->prop[p->shape->prop_count - 1]; +} + +/* can be called on Array or Arguments objects. return < 0 if + memory alloc error. */ +static no_inline __exception int convert_fast_array_to_array(JSContext *ctx, + JSObject *p) +{ + JSProperty *pr; + JSShape *sh; + JSValue *tab; + uint32_t i, len, new_count; + + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + len = p->u.array.count; + /* resize the properties once to simplify the error handling */ + sh = p->shape; + new_count = sh->prop_count + len; + if (new_count > sh->prop_size) { + if (resize_properties(ctx, &p->shape, p, new_count)) + return -1; + } + + tab = p->u.array.u.values; + for(i = 0; i < len; i++) { + /* add_property cannot fail here but + __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E); + pr->u.value = *tab++; + } + js_free(ctx, p->u.array.u.values); + p->u.array.count = 0; + p->u.array.u.values = NULL; /* fail safe */ + p->u.array.u1.size = 0; + p->fast_array = 0; + return 0; +} + +static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *lpr, *prop; + JSProperty *pr1; + uint32_t lpr_idx; + intptr_t h, h1; + + redo: + sh = p->shape; + h1 = atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h1 - 1]; + prop = get_shape_prop(sh); + lpr = NULL; + lpr_idx = 0; /* prevent warning */ + while (h != 0) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + /* found ! */ + if (!(pr->flags & JS_PROP_CONFIGURABLE)) + return false; + /* realloc the shape if needed */ + if (lpr) + lpr_idx = lpr - get_shape_prop(sh); + if (js_shape_prepare_update(ctx, p, &pr)) + return -1; + sh = p->shape; + /* remove property */ + if (lpr) { + lpr = get_shape_prop(sh) + lpr_idx; + lpr->hash_next = pr->hash_next; + } else { + prop_hash_end(sh)[-h1 - 1] = pr->hash_next; + } + sh->deleted_prop_count++; + /* free the entry */ + pr1 = &p->prop[h - 1]; + free_property(ctx->rt, pr1, pr->flags); + JS_FreeAtom(ctx, pr->atom); + /* put default values */ + pr->flags = 0; + pr->atom = JS_ATOM_NULL; + pr1->u.value = JS_UNDEFINED; + + /* compact the properties if too many deleted properties */ + if (sh->deleted_prop_count >= 8 && + sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { + compact_properties(ctx, p); + } + return true; + } + lpr = pr; + h = pr->hash_next; + } + + if (p->is_exotic) { + if (p->fast_array) { + uint32_t idx; + if (JS_AtomIsArrayIndex(ctx, &idx, atom) && + idx < p->u.array.count) { + if (p->class_id == JS_CLASS_ARRAY || + p->class_id == JS_CLASS_ARGUMENTS) { + /* Special case deleting the last element of a fast Array */ + if (idx == p->u.array.count - 1) { + JS_FreeValue(ctx, p->u.array.u.values[idx]); + p->u.array.count = idx; + return true; + } + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto redo; + } else { + return false; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->delete_property) { + return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom); + } + } + } + /* not found */ + return true; +} + +static int call_setter(JSContext *ctx, JSObject *setter, + JSValueConst this_obj, JSValue val, int flags) +{ + JSValue ret, func; + if (likely(setter)) { + func = JS_MKPTR(JS_TAG_OBJECT, setter); + /* Note: the field could be removed in the setter */ + func = js_dup(func); + ret = JS_CallFree(ctx, func, this_obj, 1, vc(&val)); + JS_FreeValue(ctx, val); + if (JS_IsException(ret)) + return -1; + JS_FreeValue(ctx, ret); + return true; + } else { + JS_FreeValue(ctx, val); + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "no setter for property"); + return -1; + } + return false; + } +} + +/* set the array length and remove the array elements if necessary. */ +static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, + int flags) +{ + uint32_t len, idx, cur_len; + int i, ret; + + /* Note: this call can reallocate the properties of 'p' */ + ret = JS_ToArrayLengthFree(ctx, &len, val, false); + if (ret) + return -1; + /* JS_ToArrayLengthFree() must be done before the read-only test */ + if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + + if (likely(p->fast_array)) { + uint32_t old_len = p->u.array.count; + if (len < old_len) { + for(i = len; i < old_len; i++) { + JS_FreeValue(ctx, p->u.array.u.values[i]); + } + p->u.array.count = len; + } + p->prop[0].u.value = js_uint32(len); + } else { + /* Note: length is always a uint32 because the object is an + array */ + JS_ToUint32(ctx, &cur_len, p->prop[0].u.value); + if (len < cur_len) { + uint32_t d; + JSShape *sh; + JSShapeProperty *pr; + + d = cur_len - len; + sh = p->shape; + if (d <= sh->prop_count) { + JSAtom atom; + + /* faster to iterate */ + while (cur_len > len) { + atom = JS_NewAtomUInt32(ctx, cur_len - 1); + ret = delete_property(ctx, p, atom); + JS_FreeAtom(ctx, atom); + if (unlikely(!ret)) { + /* unlikely case: property is not + configurable */ + break; + } + cur_len--; + } + } else { + /* faster to iterate thru all the properties. Need two + passes in case one of the property is not + configurable */ + cur_len = len; + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len && + !(pr->flags & JS_PROP_CONFIGURABLE)) { + cur_len = idx + 1; + } + } + } + + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len) { + /* remove the property */ + delete_property(ctx, p, pr->atom); + /* WARNING: the shape may have been modified */ + sh = p->shape; + pr = get_shape_prop(sh) + i; + } + } + } + } + } else { + cur_len = len; + } + set_value(ctx, &p->prop[0].u.value, js_uint32(cur_len)); + if (unlikely(cur_len > len)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable"); + } + } + return true; +} + +/* return -1 if exception */ +static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) +{ + uint32_t new_size; + size_t slack; + JSValue *new_array_prop; + /* XXX: potential arithmetic overflow */ + new_size = max_int(new_len, p->u.array.u1.size * 3 / 2); + new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack); + if (!new_array_prop) + return -1; + new_size += slack / sizeof(*new_array_prop); + p->u.array.u.values = new_array_prop; + p->u.array.u1.size = new_size; + return 0; +} + +/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = + true and p->extensible = true */ +static int add_fast_array_element(JSContext *ctx, JSObject *p, + JSValue val, int flags) +{ + uint32_t new_len, array_len; + /* extend the array by one */ + /* XXX: convert to slow array if new_len > 2^31-1 elements */ + new_len = p->u.array.count + 1; + /* update the length if necessary. We assume that if the length is + not an integer, then if it >= 2^31. */ + if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) { + array_len = JS_VALUE_GET_INT(p->prop[0].u.value); + if (new_len > array_len) { + if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + } + p->prop[0].u.value = js_int32(new_len); + } + } + if (unlikely(new_len > p->u.array.u1.size)) { + if (expand_fast_array(ctx, p, new_len)) { + JS_FreeValue(ctx, val); + return -1; + } + } + p->u.array.u.values[new_len - 1] = val; + p->u.array.count = new_len; + return true; +} + +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) +{ + JS_FreeValue(ctx, desc->getter); + JS_FreeValue(ctx, desc->setter); + JS_FreeValue(ctx, desc->value); +} + +/* return -1 in case of exception or true or false. Warning: 'val' is + freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD, + JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set, + the new property is not added and an error is raised. + 'obj' must be an object when obj != this_obj. + */ +static int JS_SetPropertyInternal2(JSContext *ctx, JSValueConst obj, JSAtom prop, + JSValue val, JSValueConst this_obj, int flags) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + JSPropertyDescriptor desc; + int ret; + + switch(JS_VALUE_GET_TAG(this_obj)) { + case JS_TAG_NULL: + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); + goto fail; + case JS_TAG_UNDEFINED: + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); + goto fail; + case JS_TAG_OBJECT: + p = JS_VALUE_GET_OBJ(this_obj); + p1 = JS_VALUE_GET_OBJ(obj); + if (p == p1) + break; + goto retry2; + default: + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + obj = JS_GetPrototypePrimitive(ctx, obj); + p = NULL; + p1 = JS_VALUE_GET_OBJ(obj); + goto prototype_lookup; + } + +retry: + prs = find_own_property(&pr, p1, prop); + if (prs) { + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast case */ + set_value(ctx, &pr->u.value, val); + return true; + } else if (prs->flags & JS_PROP_LENGTH) { + assert(p->class_id == JS_CLASS_ARRAY); + assert(prop == JS_ATOM_length); + return set_array_length(ctx, p, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* JS_PROP_WRITABLE is always true for variable + references, but they are write protected in module name + spaces. */ + if (p->class_id == JS_CLASS_MODULE_NS) + goto read_only_prop; + set_value(ctx, pr->u.var_ref->pvalue, val); + return true; + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + goto fail; + goto retry; + } else { + goto read_only_prop; + } + } + + for(;;) { + if (p1->is_exotic) { + if (p1->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p1->u.array.count) { + if (unlikely(p == p1)) + return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, flags); + else + break; + } else if (is_typed_array(p1->class_id)) { + goto typed_array_oob; + } + } else if (is_typed_array(p1->class_id)) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + goto fail; + typed_array_oob: + // per spec: evaluate value for side effects + if (p1->class_id == JS_CLASS_BIG_INT64_ARRAY || + p1->class_id == JS_CLASS_BIG_UINT64_ARRAY) { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + return -1; + } else { + val = JS_ToNumberFree(ctx, val); + JS_FreeValue(ctx, val); + if (JS_IsException(val)) + return -1; + } + return true; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; + if (em) { + JSValue obj1; + if (em->set_property) { + /* set_property can free the prototype */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->set_property(ctx, obj1, prop, + val, this_obj, flags); + JS_FreeValue(ctx, obj1); + JS_FreeValue(ctx, val); + return ret; + } + if (em->get_own_property) { + /* get_own_property can free the prototype */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->get_own_property(ctx, &desc, + obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) + goto fail; + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JSObject *setter; + if (JS_IsUndefined(desc.setter)) + setter = NULL; + else + setter = JS_VALUE_GET_OBJ(desc.setter); + ret = call_setter(ctx, setter, this_obj, val, flags); + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + return ret; + } else { + JS_FreeValue(ctx, desc.value); + if (!(desc.flags & JS_PROP_WRITABLE)) + goto read_only_prop; + if (likely(p == p1)) { + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + JS_FreeValue(ctx, val); + return ret; + } else { + break; + } + } + } + } + } + } + } + p1 = p1->shape->proto; + prototype_lookup: + if (!p1) + break; + + retry2: + prs = find_own_property(&pr, p1, prop); + if (prs) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p1, prop, pr, prs)) + return -1; + goto retry2; + } else if (!(prs->flags & JS_PROP_WRITABLE)) { + goto read_only_prop; + } else { + break; + } + } + } + + if (unlikely(flags & JS_PROP_NO_ADD)) { + JS_ThrowReferenceErrorNotDefined(ctx, prop); + goto fail; + } + + if (unlikely(!p)) { + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object"); + goto done; + } + + if (unlikely(!p->extensible)) { + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + goto done; + } + + if (p == JS_VALUE_GET_OBJ(obj)) { + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY && p->fast_array && + __JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + /* fast case */ + return add_fast_array_element(ctx, p, val, flags); + } + } + goto generic_create_prop; + } else { + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (!pr) + goto fail; + pr->u.value = val; + return true; + } + } + + // TODO(bnoordhuis) return JSProperty slot and update in place + // when plain property (not is_exotic/setter/etc.) to avoid + // calling find_own_property() thrice? + ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); + if (ret < 0) + goto fail; + + if (ret) { + JS_FreeValue(ctx, desc.value); + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); + goto done; + } else if (!(desc.flags & JS_PROP_WRITABLE) || + p->class_id == JS_CLASS_MODULE_NS) { + read_only_prop: + ret = JS_ThrowTypeErrorReadOnly(ctx, flags, prop); + goto done; + } + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + } else { + generic_create_prop: + ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | + JS_PROP_HAS_VALUE | + JS_PROP_HAS_ENUMERABLE | + JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_CONFIGURABLE | + JS_PROP_C_W_E); + } + +done: + JS_FreeValue(ctx, val); + return ret; +fail: + JS_FreeValue(ctx, val); + return -1; +} + +static int JS_SetPropertyInternal(JSContext *ctx, JSValueConst obj, JSAtom prop, + JSValue val, int flags) +{ + return JS_SetPropertyInternal2(ctx, obj, prop, val, obj, flags); +} + +int JS_SetProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop, JSValue val) +{ + return JS_SetPropertyInternal(ctx, this_obj, prop, val, JS_PROP_THROW); +} + +/* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */ +static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags) +{ + if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { + JSObject *p; + uint32_t idx; + double d; + int32_t v; + + /* fast path for array access */ + p = JS_VALUE_GET_OBJ(this_obj); + idx = JS_VALUE_GET_INT(prop); + switch(p->class_id) { + case JS_CLASS_ARRAY: + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + JSObject *p1; + JSShape *sh1; + + /* fast path to add an element to the array */ + if (idx != (uint32_t)p->u.array.count || + !p->fast_array || !p->extensible) + goto slow_path; + /* check if prototype chain has a numeric property */ + p1 = p->shape->proto; + while (p1 != NULL) { + sh1 = p1->shape; + if (p1->class_id == JS_CLASS_ARRAY) { + if (unlikely(!p1->fast_array)) + goto slow_path; + } else if (p1->class_id == JS_CLASS_OBJECT) { + if (unlikely(sh1->has_small_array_index)) + goto slow_path; + } else { + goto slow_path; + } + p1 = sh1->proto; + } + /* add element */ + return add_fast_array_element(ctx, p, val, flags); + } + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto slow_path; + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_UINT8C_ARRAY: + if (JS_ToUint8ClampFree(ctx, &v, val)) + goto ta_cvt_fail; + /* Note: the conversion can detach the typed array, so the + array bound check must be done after */ + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint16_ptr[idx] = v; + break; + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint32_ptr[idx] = v; + break; + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + /* XXX: need specific conversion function */ + { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint64_ptr[idx] = v; + } + break; + case JS_CLASS_FLOAT16_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.fp16_ptr[idx] = tofp16(d); + break; + case JS_CLASS_FLOAT32_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.float_ptr[idx] = d; + break; + case JS_CLASS_FLOAT64_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) { + ta_cvt_fail: + if (flags & JS_PROP_REFLECT_DEFINE_PROPERTY) { + JS_FreeValue(ctx, JS_GetException(ctx)); + return false; + } + return -1; + } + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + ta_out_of_bound: + if (typed_array_is_oob(p)) + if (flags & JS_PROP_DEFINE_PROPERTY) + return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index"); + return true; // per spec: no OOB exception + } + p->u.array.u.double_ptr[idx] = d; + break; + default: + goto slow_path; + } + return true; + } else { + JSAtom atom; + int ret; + slow_path: + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; + } +} + +int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx, JSValue val) +{ + return JS_SetPropertyValue(ctx, this_obj, js_uint32(idx), val, + JS_PROP_THROW); +} + +int JS_SetPropertyInt64(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= INT32_MAX) { + /* fast path for fast arrays */ + return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, + JS_PROP_THROW); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + res = JS_SetProperty(ctx, this_obj, prop, val); + JS_FreeAtom(ctx, prop); + return res; +} + +/* `prop` may be pure ASCII or UTF-8 encoded */ +int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj, + const char *prop, JSValue val) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, JS_PROP_THROW); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* compute the property flags. For each flag: (JS_PROP_HAS_x forces + it, otherwise def_flags is used) + Note: makes assumption about the bit pattern of the flags +*/ +static int get_prop_flags(int flags, int def_flags) +{ + int mask; + mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E; + return (flags & mask) | (def_flags & ~mask); +} + +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, + int flags) +{ + JSProperty *pr; + int ret, prop_flags; + + /* add a new property or modify an existing exotic one */ + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY) { + uint32_t idx, len; + + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + if (!p->extensible) + goto not_extensible; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) + goto convert_to_array; + prop_flags = get_prop_flags(flags, 0); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_array; + return add_fast_array_element(ctx, p, + js_dup(val), flags); + } else { + goto convert_to_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + /* convert the fast array to normal array */ + convert_to_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto generic_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + JSProperty *plen; + JSShapeProperty *pslen; + generic_array: + /* update the length field */ + plen = &p->prop[0]; + JS_ToUint32(ctx, &len, plen->u.value); + if ((idx + 1) > len) { + pslen = get_shape_prop(p->shape); + if (unlikely(!(pslen->flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + /* XXX: should update the length after defining + the property */ + len = idx + 1; + set_value(ctx, &plen->u.value, js_uint32(len)); + } + } + } else if (is_typed_array(p->class_id)) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array"); + } + } else if (!(flags & JS_PROP_NO_EXOTIC)) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->define_own_property) { + return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), + prop, val, getter, setter, flags); + } + ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret < 0) + return -1; + if (!ret) + goto not_extensible; + } + } + } + + if (!p->extensible) { + not_extensible: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + } + + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + } else { + prop_flags = flags & JS_PROP_C_W_E; + } + pr = add_property(ctx, p, prop, prop_flags); + if (unlikely(!pr)) + return -1; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + pr->u.getset.getter = NULL; + if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) { + pr->u.getset.getter = + JS_VALUE_GET_OBJ(js_dup(getter)); + } + pr->u.getset.setter = NULL; + if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) { + pr->u.getset.setter = + JS_VALUE_GET_OBJ(js_dup(setter)); + } + } else { + if (flags & JS_PROP_HAS_VALUE) { + pr->u.value = js_dup(val); + } else { + pr->u.value = JS_UNDEFINED; + } + } + return true; +} + +/* return false if not OK */ +static bool check_define_prop_flags(int prop_flags, int flags) +{ + bool has_accessor, is_getset; + + if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == + (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) { + return false; + } + if ((flags & JS_PROP_HAS_ENUMERABLE) && + (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE)) + return false; + } + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0); + is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET); + if (has_accessor != is_getset) + return false; + if (!has_accessor && !is_getset && !(prop_flags & JS_PROP_WRITABLE)) { + /* not writable: cannot set the writable bit */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) + return false; + } + } + } + return true; +} + +/* ensure that the shape can be safely modified */ +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs) +{ + JSShape *sh; + uint32_t idx = 0; /* prevent warning */ + + sh = p->shape; + if (sh->is_hashed) { + if (sh->header.ref_count != 1) { + if (pprs) + idx = *pprs - get_shape_prop(sh); + /* clone the shape (the resulting one is no longer hashed) */ + sh = js_clone_shape(ctx, sh); + if (!sh) + return -1; + js_free_shape(ctx->rt, p->shape); + p->shape = sh; + if (pprs) + *pprs = get_shape_prop(sh) + idx; + } else { + js_shape_hash_unlink(ctx->rt, sh); + sh->is_hashed = false; + } + } + return 0; +} + +static int js_update_property_flags(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs, int flags) +{ + if (flags != (*pprs)->flags) { + if (js_shape_prepare_update(ctx, p, pprs)) + return -1; + (*pprs)->flags = flags; + } + return 0; +} + +/* allowed flags: + JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE + JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE, + JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE, + JS_PROP_THROW, JS_PROP_NO_EXOTIC. + If JS_PROP_THROW is set, return an exception instead of false. + if JS_PROP_NO_EXOTIC is set, do not call the exotic + define_own_property callback. + return -1 (exception), false or true. +*/ +int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int mask, res; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(this_obj); + + redo_prop_update: + prs = find_own_property(&pr, p, prop); + if (prs) { + /* the range of the Array length property is always tested before */ + if ((prs->flags & JS_PROP_LENGTH) && (flags & JS_PROP_HAS_VALUE)) { + uint32_t array_length; + if (JS_ToArrayLengthFree(ctx, &array_length, + js_dup(val), false)) { + return -1; + } + /* this code relies on the fact that Uint32 are never allocated */ + val = js_uint32(array_length); + /* prs may have been modified */ + prs = find_own_property(&pr, p, prop); + assert(prs != NULL); + } + /* property already exists */ + if (!check_define_prop_flags(prs->flags, flags)) { + not_configurable: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); + } + + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto redo_prop_update; + } + + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + JSObject *new_getter, *new_setter; + + if (JS_IsFunction(ctx, getter)) { + new_getter = JS_VALUE_GET_OBJ(getter); + } else { + new_getter = NULL; + } + if (JS_IsFunction(ctx, setter)) { + new_setter = JS_VALUE_GET_OBJ(setter); + } else { + new_setter = NULL; + } + + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) { + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + /* convert to getset */ + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + free_var_ref(ctx->rt, pr->u.var_ref); + } else { + JS_FreeValue(ctx, pr->u.value); + } + prs->flags = (prs->flags & + (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + pr->u.getset.getter = NULL; + pr->u.getset.setter = NULL; + } else { + if (!(prs->flags & JS_PROP_CONFIGURABLE)) { + if ((flags & JS_PROP_HAS_GET) && + new_getter != pr->u.getset.getter) { + goto not_configurable; + } + if ((flags & JS_PROP_HAS_SET) && + new_setter != pr->u.getset.setter) { + goto not_configurable; + } + } + } + if (flags & JS_PROP_HAS_GET) { + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (new_getter) + js_dup(getter); + pr->u.getset.getter = new_getter; + } + if (flags & JS_PROP_HAS_SET) { + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + if (new_setter) + js_dup(setter); + pr->u.getset.setter = new_setter; + } + } else { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + /* convert to data descriptor */ + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + pr->u.value = JS_UNDEFINED; + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* Note: JS_PROP_VARREF is always writable */ + } else { + if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && + (flags & JS_PROP_HAS_VALUE)) { + if (!js_same_value(ctx, val, pr->u.value)) { + goto not_configurable; + } else { + return true; + } + } + } + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (flags & JS_PROP_HAS_VALUE) { + if (p->class_id == JS_CLASS_MODULE_NS) { + /* JS_PROP_WRITABLE is always true for variable + references, but they are write protected in module name + spaces. */ + if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue)) + goto not_configurable; + } + /* update the reference */ + set_value(ctx, pr->u.var_ref->pvalue, js_dup(val)); + } + /* if writable is set to false, no longer a + reference (for mapped arguments) */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { + JSValue val1; + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + val1 = js_dup(*pr->u.var_ref->pvalue); + free_var_ref(ctx->rt, pr->u.var_ref); + pr->u.value = val1; + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + } + } else if (prs->flags & JS_PROP_LENGTH) { + if (flags & JS_PROP_HAS_VALUE) { + /* Note: no JS code is executable because + 'val' is guaranted to be a Uint32 */ + res = set_array_length(ctx, p, js_dup(val), flags); + } else { + res = true; + } + /* still need to reset the writable flag if + needed. The JS_PROP_LENGTH is kept because the + Uint32 test is still done if the length + property is read-only. */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + JS_PROP_HAS_WRITABLE) { + prs = get_shape_prop(p->shape); + if (js_update_property_flags(ctx, p, &prs, + prs->flags & ~JS_PROP_WRITABLE)) + return -1; + } + return res; + } else { + if (flags & JS_PROP_HAS_VALUE) { + JS_FreeValue(ctx, pr->u.value); + pr->u.value = js_dup(val); + } + if (flags & JS_PROP_HAS_WRITABLE) { + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~JS_PROP_WRITABLE) | + (flags & JS_PROP_WRITABLE))) + return -1; + } + } + } + } + mask = 0; + if (flags & JS_PROP_HAS_CONFIGURABLE) + mask |= JS_PROP_CONFIGURABLE; + if (flags & JS_PROP_HAS_ENUMERABLE) + mask |= JS_PROP_ENUMERABLE; + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~mask) | (flags & mask))) + return -1; + return true; + } + + /* handle modification of fast array elements */ + if (p->fast_array) { + uint32_t idx; + uint32_t prop_flags; + if (p->class_id == JS_CLASS_ARRAY) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + prop_flags = get_prop_flags(flags, JS_PROP_C_W_E); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_slow_array; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + convert_to_slow_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + else + goto redo_prop_update; + } + if (flags & JS_PROP_HAS_VALUE) { + set_value(ctx, &p->u.array.u.values[idx], js_dup(val)); + } + return true; + } + } + } else if (is_typed_array(p->class_id)) { + JSValue num; + int ret; + + if (!__JS_AtomIsTaggedInt(prop)) { + /* slow path with to handle all numeric indexes */ + num = JS_AtomIsNumericIndex1(ctx, prop); + if (JS_IsUndefined(num)) + goto typed_array_done; + if (JS_IsException(num)) + return -1; + ret = JS_NumberIsInteger(ctx, num); + if (ret < 0) { + JS_FreeValue(ctx, num); + return -1; + } + if (!ret) { + JS_FreeValue(ctx, num); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array"); + } + ret = JS_NumberIsNegativeOrMinusZero(ctx, num); + JS_FreeValue(ctx, num); + if (ret) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array"); + } + if (!__JS_AtomIsTaggedInt(prop)) + goto typed_array_oob; + } + idx = __JS_AtomToUInt32(prop); + /* if the typed array is detached, p->u.array.count = 0 */ + if (idx >= p->u.array.count) { + typed_array_oob: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array"); + } + prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) || + prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags"); + } + if (flags & JS_PROP_HAS_VALUE) { + return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), js_dup(val), flags); + } + return true; + typed_array_done: ; + } + } + + return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags); +} + +static int JS_DefineAutoInitProperty(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSAutoInitIDEnum id, + void *opaque, int flags) +{ + JSObject *p; + JSProperty *pr; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) + return false; + + p = JS_VALUE_GET_OBJ(this_obj); + + if (find_own_property(&pr, p, prop)) { + /* property already exists */ + abort(); + return false; + } + + /* Specialized CreateProperty */ + pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT); + if (unlikely(!pr)) + return -1; + pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx); + assert((pr->u.init.realm_and_id & 3) == 0); + assert(id <= 3); + pr->u.init.realm_and_id |= id; + pr->u.init.opaque = opaque; + return true; +} + +/* shortcut to add or redefine a new property value */ +int JS_DefinePropertyValue(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValue val, int flags) +{ + int ret; + ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE); + JS_FreeValue(ctx, val); + return ret; +} + +int JS_DefinePropertyValueValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +int JS_DefinePropertyValueUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, js_uint32(idx), + val, flags); +} + +int JS_DefinePropertyValueInt64(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), + val, flags); +} + +/* `prop` may be pure ASCII or UTF-8 encoded */ +int JS_DefinePropertyValueStr(JSContext *ctx, JSValueConst this_obj, + const char *prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* shortcut to add getter & setter */ +int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValue getter, JSValue setter, + int flags) +{ + int ret; + ret = JS_DefineProperty(ctx, this_obj, prop, JS_UNDEFINED, getter, setter, + flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE); + JS_FreeValue(ctx, getter); + JS_FreeValue(ctx, setter); + return ret; +} + +static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), + val, flags | JS_PROP_CONFIGURABLE | + JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); +} + + +/* return true if 'obj' has a non empty 'name' string */ +static bool js_object_has_name(JSContext *ctx, JSValue obj) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValue val; + JSString *p; + + prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name); + if (!prs) + return false; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return true; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return true; + p = JS_VALUE_GET_STRING(val); + return (p->len != 0); +} + +static int JS_DefineObjectName(JSContext *ctx, JSValue obj, + JSAtom name, int flags) +{ + if (name != JS_ATOM_NULL + && JS_IsObject(obj) + && !js_object_has_name(ctx, obj) + && JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) { + return -1; + } + return 0; +} + +static int JS_DefineObjectNameComputed(JSContext *ctx, JSValue obj, + JSValue str, int flags) +{ + if (JS_IsObject(obj) && + !js_object_has_name(ctx, obj)) { + JSAtom prop; + JSValue name_str; + prop = JS_ValueToAtom(ctx, str); + if (prop == JS_ATOM_NULL) + return -1; + name_str = js_get_function_name(ctx, prop); + JS_FreeAtom(ctx, prop); + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0) + return -1; + } + return 0; +} + +#define DEFINE_GLOBAL_LEX_VAR (1 << 7) +#define DEFINE_GLOBAL_FUNC_VAR (1 << 6) + +static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) +{ + return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); +} + +/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ +/* XXX: could support exotic global object. */ +static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property1(p, prop); + /* XXX: should handle JS_PROP_AUTOINIT */ + if (flags & DEFINE_GLOBAL_LEX_VAR) { + if (prs && !(prs->flags & JS_PROP_CONFIGURABLE)) + goto fail_redeclaration; + } else { + if (!prs && !p->extensible) + goto define_error; + if (flags & DEFINE_GLOBAL_FUNC_VAR) { + if (prs) { + if (!(prs->flags & JS_PROP_CONFIGURABLE) && + ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET || + ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != + (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { + define_error: + JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", + prop); + return -1; + } + } + } + } + /* check if there already is a lexical declaration */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property1(p, prop); + if (prs) { + fail_redeclaration: + JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop); + return -1; + } + return 0; +} + +/* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) | + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE */ +/* XXX: could support exotic global object. */ +static int JS_DefineGlobalVar(JSContext *ctx, JSAtom prop, int def_flags) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSValue val; + int flags; + + if (def_flags & DEFINE_GLOBAL_LEX_VAR) { + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + flags = JS_PROP_ENUMERABLE | (def_flags & JS_PROP_WRITABLE) | + JS_PROP_CONFIGURABLE; + val = JS_UNINITIALIZED; + } else { + p = JS_VALUE_GET_OBJ(ctx->global_obj); + flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | + (def_flags & JS_PROP_CONFIGURABLE); + val = JS_UNDEFINED; + } + prs = find_own_property1(p, prop); + if (prs) + return 0; + if (!p->extensible) + return 0; + pr = add_property(ctx, p, prop, flags); + if (unlikely(!pr)) + return -1; + pr->u.value = val; + return 0; +} + +/* 'def_flags' is 0 or JS_PROP_CONFIGURABLE. */ +/* XXX: could support exotic global object. */ +static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop, + JSValue func, int def_flags) +{ + + JSObject *p; + JSShapeProperty *prs; + int flags; + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property1(p, prop); + flags = JS_PROP_HAS_VALUE | JS_PROP_THROW; + if (!prs || (prs->flags & JS_PROP_CONFIGURABLE)) { + flags |= JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | def_flags | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE; + } + if (JS_DefineProperty(ctx, ctx->global_obj, prop, func, + JS_UNDEFINED, JS_UNDEFINED, flags) < 0) + return -1; + return 0; +} + +static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop, + bool throw_ref_error) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_TMASK properties */ + if (unlikely(JS_IsUninitialized(pr->u.value))) + return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return js_dup(pr->u.value); + } + return JS_GetPropertyInternal(ctx, ctx->global_obj, prop, + ctx->global_obj, throw_ref_error); +} + +/* construct a reference to a global variable */ +static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_AUTOINIT properties? */ + /* XXX: conformance: do these tests in + OP_put_var_ref/OP_get_var_ref ? */ + if (unlikely(JS_IsUninitialized(pr->u.value))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { + return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); + } + sp[0] = js_dup(ctx->global_var_obj); + } else { + int ret; + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + if (ret) { + sp[0] = js_dup(ctx->global_obj); + } else { + sp[0] = JS_UNDEFINED; + } + } + sp[1] = JS_AtomToValue(ctx, prop); + return 0; +} + +/* use for strict variable access: test if the variable exists */ +static int JS_CheckGlobalVar(JSContext *ctx, JSAtom prop) +{ + JSObject *p; + JSShapeProperty *prs; + int ret; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property1(p, prop); + if (prs) { + ret = true; + } else { + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + } + return ret; +} + +/* flag = 0: normal variable write + flag = 1: initialize lexical variable + flag = 2: normal variable write, strict check was done before +*/ +static int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val, + int flag) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int flags; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_AUTOINIT properties? */ + if (flag != 1) { + if (unlikely(JS_IsUninitialized(pr->u.value))) { + JS_FreeValue(ctx, val); + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); + } + } + set_value(ctx, &pr->u.value, val); + return 0; + } + flags = JS_PROP_THROW_STRICT; + if (is_strict_mode(ctx)) + flags |= JS_PROP_NO_ADD; + return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, flags); +} + +/* return -1, false or true */ +static int JS_DeleteGlobalVar(JSContext *ctx, JSAtom prop) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int ret; + + /* 9.1.1.4.7 DeleteBinding ( N ) */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) + return false; /* lexical variables cannot be deleted */ + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + if (ret) { + return JS_DeleteProperty(ctx, ctx->global_obj, prop, 0); + } else { + return true; + } +} + +/* return -1, false or true. return false if not configurable or + invalid object. return -1 in case of exception. + flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */ +int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags) +{ + JSValue obj1; + JSObject *p; + int res; + + obj1 = JS_ToObject(ctx, obj); + if (JS_IsException(obj1)) + return -1; + p = JS_VALUE_GET_OBJ(obj1); + res = delete_property(ctx, p, prop); + JS_FreeValue(ctx, obj1); + if (res != false) + return res; + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "could not delete property"); + return -1; + } + return false; +} + +int JS_DeletePropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, int flags) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= JS_ATOM_MAX_INT) { + /* fast path for fast arrays */ + return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return -1; + res = JS_DeleteProperty(ctx, obj, prop, flags); + JS_FreeAtom(ctx, prop); + return res; +} + +bool JS_IsFunction(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(val); + switch(p->class_id) { + case JS_CLASS_BYTECODE_FUNCTION: + return true; + case JS_CLASS_PROXY: + return p->u.proxy_data->is_func; + default: + return (ctx->rt->class_array[p->class_id].call != NULL); + } +} + +static bool JS_IsCFunction(JSContext *ctx, JSValueConst val, JSCFunction *func, + int magic) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(val); + if (p->class_id == JS_CLASS_C_FUNCTION) + return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic); + else + return false; +} + +bool JS_IsConstructor(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(val); + return p->is_constructor; +} + +bool JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, bool val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(func_obj); + p->is_constructor = val; + return true; +} + +bool JS_IsRegExp(JSValueConst val) +{ + return JS_CLASS_REGEXP == JS_GetClassID(val); +} + +bool JS_IsMap(JSValueConst val) +{ + return JS_CLASS_MAP == JS_GetClassID(val); +} + +bool JS_IsSet(JSValueConst val) +{ + return JS_CLASS_SET == JS_GetClassID(val); +} + +bool JS_IsWeakRef(JSValueConst val) +{ + return JS_CLASS_WEAK_REF == JS_GetClassID(val); +} + +bool JS_IsWeakSet(JSValueConst val) +{ + return JS_CLASS_WEAKSET == JS_GetClassID(val); +} + +bool JS_IsWeakMap(JSValueConst val) +{ + return JS_CLASS_WEAKMAP == JS_GetClassID(val); +} + +bool JS_IsDataView(JSValueConst val) +{ + return JS_CLASS_DATAVIEW == JS_GetClassID(val); +} + +bool JS_IsError(JSValueConst val) +{ + return JS_CLASS_ERROR == JS_GetClassID(val); +} + +/* used to avoid catching interrupt exceptions */ +bool JS_IsUncatchableError(JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(val); + return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error; +} + +static void js_set_uncatchable_error(JSContext *ctx, JSValueConst val, bool flag) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(val); + if (p->class_id == JS_CLASS_ERROR) + p->is_uncatchable_error = flag; +} + +void JS_SetUncatchableError(JSContext *ctx, JSValueConst val) +{ + js_set_uncatchable_error(ctx, val, true); +} + +void JS_ClearUncatchableError(JSContext *ctx, JSValueConst val) +{ + js_set_uncatchable_error(ctx, val, false); +} + +void JS_ResetUncatchableError(JSContext *ctx) +{ + js_set_uncatchable_error(ctx, ctx->rt->current_exception, false); +} + +int JS_SetOpaque(JSValueConst obj, void *opaque) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + // User code can't set the opaque of internal objects. + if (p->class_id >= JS_CLASS_INIT_COUNT) { + p->u.opaque = opaque; + return 0; + } + } + + return -1; +} + +/* |obj| must be a JSObject of an internal class. */ +static void JS_SetOpaqueInternal(JSValueConst obj, void *opaque) +{ + JSObject *p; + assert(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT); + p = JS_VALUE_GET_OBJ(obj); + assert(p->class_id < JS_CLASS_INIT_COUNT); + p->u.opaque = opaque; +} + +/* return NULL if not an object of class class_id */ +void *JS_GetOpaque(JSValueConst obj, JSClassID class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != class_id) + return NULL; + return p->u.opaque; +} + +void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id) +{ + void *p = JS_GetOpaque(obj, class_id); + if (unlikely(!p)) { + JS_ThrowTypeErrorInvalidClass(ctx, class_id); + } + return p; +} + +void *JS_GetAnyOpaque(JSValueConst obj, JSClassID *class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + *class_id = 0; + return NULL; + } + p = JS_VALUE_GET_OBJ(obj); + *class_id = p->class_id; + return p->u.opaque; +} + +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) +{ + int i; + bool force_ordinary; + + JSAtom method_name; + JSValue method, ret; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return val; + force_ordinary = hint & HINT_FORCE_ORDINARY; + hint &= ~HINT_FORCE_ORDINARY; + if (!force_ordinary) { + method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive); + if (JS_IsException(method)) + goto exception; + /* ECMA says *If exoticToPrim is not undefined* but tests in + test262 use null as a non callable converter */ + if (!JS_IsUndefined(method) && !JS_IsNull(method)) { + JSAtom atom; + JSValue arg; + switch(hint) { + case HINT_STRING: + atom = JS_ATOM_string; + break; + case HINT_NUMBER: + atom = JS_ATOM_number; + break; + default: + case HINT_NONE: + atom = JS_ATOM_default; + break; + } + arg = JS_AtomToString(ctx, atom); + ret = JS_CallFree(ctx, method, val, 1, vc(&arg)); + JS_FreeValue(ctx, arg); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) + return ret; + JS_FreeValue(ctx, ret); + return JS_ThrowTypeError(ctx, "toPrimitive"); + } + } + if (hint != HINT_STRING) + hint = HINT_NUMBER; + for(i = 0; i < 2; i++) { + if ((i ^ hint) == 0) { + method_name = JS_ATOM_toString; + } else { + method_name = JS_ATOM_valueOf; + } + method = JS_GetProperty(ctx, val, method_name); + if (JS_IsException(method)) + goto exception; + if (JS_IsFunction(ctx, method)) { + ret = JS_CallFree(ctx, method, val, 0, NULL); + if (JS_IsException(ret)) + goto exception; + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { + JS_FreeValue(ctx, val); + return ret; + } + JS_FreeValue(ctx, ret); + } else { + JS_FreeValue(ctx, method); + } + } + JS_ThrowTypeError(ctx, "toPrimitive"); +exception: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue JS_ToPrimitive(JSContext *ctx, JSValueConst val, int hint) +{ + return JS_ToPrimitiveFree(ctx, js_dup(val), hint); +} + +void JS_SetIsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(obj); + p->is_HTMLDDA = true; +} + +static inline bool JS_IsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(obj); + return p->is_HTMLDDA; +} + +static int JS_ToBoolFree(JSContext *ctx, JSValue val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + return JS_VALUE_GET_INT(val) != 0; + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + return JS_VALUE_GET_INT(val); + case JS_TAG_EXCEPTION: + return -1; + case JS_TAG_STRING: + { + bool ret = JS_VALUE_GET_STRING(val)->len != 0; + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_SHORT_BIG_INT: + return JS_VALUE_GET_SHORT_BIG_INT(val) != 0; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + bool ret; + int i; + + /* fail safe: we assume it is not necessarily + normalized. Beginning from the MSB ensures that the + test is fast. */ + ret = false; + for(i = p->len - 1; i >= 0; i--) { + if (p->tab[i] != 0) { + ret = true; + break; + } + } + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + bool ret = !p->is_HTMLDDA; + JS_FreeValue(ctx, val); + return ret; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d = JS_VALUE_GET_FLOAT64(val); + return !isnan(d) && d != 0; + } else { + JS_FreeValue(ctx, val); + return true; + } + } +} + +int JS_ToBool(JSContext *ctx, JSValueConst val) +{ + return JS_ToBoolFree(ctx, js_dup(val)); +} + +/* pc points to pure ASCII or UTF-8, null terminated contents */ +static int skip_spaces(const char *pc) +{ + const uint8_t *p, *p_next, *p_start; + uint32_t c; + + p = p_start = (const uint8_t *)pc; + for (;;) { + c = *p++; + if (c < 0x80) { + if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) + break; + } else { + c = utf8_decode(p - 1, &p_next); + /* no need to test for invalid UTF-8, 0xFFFD is not a space */ + if (!lre_is_space(c)) + break; + p = p_next; + } + } + return p - 1 - p_start; +} + +static inline int js_to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* bigint support */ + +#define ADDC(res, carry_out, op1, op2, carry_in) \ +do { \ + js_limb_t __v, __a, __k, __k1; \ + __v = (op1); \ + __a = __v + (op2); \ + __k1 = __a < __v; \ + __k = (carry_in); \ + __a = __a + __k; \ + carry_out = (__a < __k) | __k1; \ + res = __a; \ +} while (0) + +/* a != 0 */ +static inline js_limb_t js_limb_clz(js_limb_t a) +{ + if (!a) + return JS_LIMB_BITS; + return clz32(a); +} + +static js_limb_t js_mp_add(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, + js_limb_t n, js_limb_t carry) +{ + int i; + for(i = 0;i < n; i++) { + ADDC(res[i], carry, op1[i], op2[i], carry); + } + return carry; +} + +static js_limb_t js_mp_sub(js_limb_t *res, const js_limb_t *op1, const js_limb_t *op2, + int n, js_limb_t carry) +{ + int i; + js_limb_t k, a, v, k1; + + k = carry; + for(i=0;i v; + v = a - k; + k = (v > a) | k1; + res[i] = v; + } + return k; +} + +/* compute 0 - op2. carry = 0 or 1. */ +static js_limb_t js_mp_neg(js_limb_t *res, const js_limb_t *op2, int n) +{ + int i; + js_limb_t v, carry; + + carry = 1; + for(i=0;i> JS_LIMB_BITS; + } + return l; +} + +static js_limb_t js_mp_div1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b, js_limb_t r) +{ + js_slimb_t i; + js_dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + return r; +} + +/* tabr[] += taba[] * b, return the high word. */ +static js_limb_t js_mp_add_mul1(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b) +{ + js_limb_t i, l; + js_dlimb_t t; + + l = 0; + for(i = 0; i < n; i++) { + t = (js_dlimb_t)taba[i] * (js_dlimb_t)b + l + tabr[i]; + tabr[i] = t; + l = t >> JS_LIMB_BITS; + } + return l; +} + +/* size of the result : op1_size + op2_size. */ +static void js_mp_mul_basecase(js_limb_t *result, + const js_limb_t *op1, js_limb_t op1_size, + const js_limb_t *op2, js_limb_t op2_size) +{ + int i; + js_limb_t r; + + result[op1_size] = js_mp_mul1(result, op1, op1_size, op2[0], 0); + for(i=1;i> JS_LIMB_BITS); + } + return l; +} + +/* WARNING: d must be >= 2^(JS_LIMB_BITS-1) */ +static inline js_limb_t js_udiv1norm_init(js_limb_t d) +{ + js_limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((js_dlimb_t)a1 << JS_LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^JS_LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline js_limb_t js_udiv1norm(js_limb_t *pr, js_limb_t a1, js_limb_t a0, + js_limb_t d, js_limb_t d_inv) +{ + js_limb_t n1m, n_adj, q, r, ah; + js_dlimb_t a; + n1m = ((js_slimb_t)a0 >> (JS_LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (js_dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> JS_LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is\ + between 0 and d - 1 */ + a = ((js_dlimb_t)a1 << JS_LIMB_BITS) | a0; + a = a - (js_dlimb_t)q * d - d; + ah = a >> JS_LIMB_BITS; + q += 1 + ah; + r = (js_limb_t)a + (ah & d); + *pr = r; + return q; +} + +#define UDIV1NORM_THRESHOLD 3 + +/* b must be >= 1 << (JS_LIMB_BITS - 1) */ +static js_limb_t js_mp_div1norm(js_limb_t *tabr, const js_limb_t *taba, js_limb_t n, + js_limb_t b, js_limb_t r) +{ + js_slimb_t i; + + if (n >= UDIV1NORM_THRESHOLD) { + js_limb_t b_inv; + b_inv = js_udiv1norm_init(b); + for(i = n - 1; i >= 0; i--) { + tabr[i] = js_udiv1norm(&r, r, taba[i], b, b_inv); + } + } else { + js_dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((js_dlimb_t)r << JS_LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + } + return r; +} + +/* base case division: divides taba[0..na-1] by tabb[0..nb-1]. tabb[nb + - 1] must be >= 1 << (JS_LIMB_BITS - 1). na - nb must be >= 0. 'taba' + is modified and contains the remainder (nb limbs). tabq[0..na-nb] + contains the quotient with tabq[na - nb] <= 1. */ +static void js_mp_divnorm(js_limb_t *tabq, js_limb_t *taba, js_limb_t na, + const js_limb_t *tabb, js_limb_t nb) +{ + js_limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r; + int i, j; + + b1 = tabb[nb - 1]; + if (nb == 1) { + taba[0] = js_mp_div1norm(tabq, taba, na, b1, 0); + return; + } + n = na - nb; + + if (n >= UDIV1NORM_THRESHOLD) + b1_inv = js_udiv1norm_init(b1); + else + b1_inv = 0; + + /* first iteration: the quotient is only 0 or 1 */ + q = 1; + for(j = nb - 1; j >= 0; j--) { + if (taba[n + j] != tabb[j]) { + if (taba[n + j] < tabb[j]) + q = 0; + break; + } + } + tabq[n] = q; + if (q) { + js_mp_sub(taba + n, taba + n, tabb, nb, 0); + } + + for(i = n - 1; i >= 0; i--) { + if (unlikely(taba[i + nb] >= b1)) { + q = -1; + } else if (b1_inv) { + q = js_udiv1norm(&dummy_r, taba[i + nb], taba[i + nb - 1], b1, b1_inv); + } else { + js_dlimb_t al; + al = ((js_dlimb_t)taba[i + nb] << JS_LIMB_BITS) | taba[i + nb - 1]; + q = al / b1; + r = al % b1; + } + r = js_mp_sub_mul1(taba + i, tabb, nb, q); + + v = taba[i + nb]; + a = v - r; + c = (a > v); + taba[i + nb] = a; + + if (c != 0) { + /* negative result */ + for(;;) { + q--; + c = js_mp_add(taba + i, taba + i, tabb, nb, 0); + /* propagate carry and test if positive result */ + if (c != 0) { + if (++taba[i + nb] == 0) { + break; + } + } + } + } + tabq[i] = q; + } +} + +/* 1 <= shift <= JS_LIMB_BITS - 1 */ +static js_limb_t js_mp_shl(js_limb_t *tabr, const js_limb_t *taba, int n, + int shift) +{ + int i; + js_limb_t l, v; + l = 0; + for(i = 0; i < n; i++) { + v = taba[i]; + tabr[i] = (v << shift) | l; + l = v >> (JS_LIMB_BITS - shift); + } + return l; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static js_limb_t js_mp_shr(js_limb_t *tab_r, const js_limb_t *tab, int n, + int shift, js_limb_t high) +{ + int i; + js_limb_t l, a; + + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + tab_r[i] = (a >> shift) | (l << (JS_LIMB_BITS - shift)); + l = a; + } + return l & (((js_limb_t)1 << shift) - 1); +} + +static JSBigInt *js_bigint_new(JSContext *ctx, int len) +{ + JSBigInt *r; + if (len > JS_BIGINT_MAX_SIZE) { + JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); + return NULL; + } + r = js_malloc(ctx, sizeof(JSBigInt) + len * sizeof(js_limb_t)); + if (!r) + return NULL; + r->header.ref_count = 1; + r->len = len; + return r; +} + +static JSBigInt *js_bigint_set_si(JSBigIntBuf *buf, js_slimb_t a) +{ + JSBigInt *r = (JSBigInt *)buf->big_int_buf; + r->header.ref_count = 0; /* fail safe */ + r->len = 1; + r->tab[0] = a; + return r; +} + +static JSBigInt *js_bigint_set_si64(JSBigIntBuf *buf, int64_t a) +{ + JSBigInt *r = (JSBigInt *)buf->big_int_buf; + r->header.ref_count = 0; /* fail safe */ + if (a >= INT32_MIN && a <= INT32_MAX) { + r->len = 1; + r->tab[0] = a; + } else { + r->len = 2; + r->tab[0] = a; + r->tab[1] = a >> JS_LIMB_BITS; + } + return r; +} + +/* val must be a short big int */ +static JSBigInt *js_bigint_set_short(JSBigIntBuf *buf, JSValueConst val) +{ + return js_bigint_set_si(buf, JS_VALUE_GET_SHORT_BIG_INT(val)); +} + +static __maybe_unused void js_bigint_dump1(JSContext *ctx, const char *str, + const js_limb_t *tab, int len) +{ + int i; + printf("%s: ", str); + for(i = len - 1; i >= 0; i--) { + printf(" %08x", tab[i]); + } + printf("\n"); +} + +static __maybe_unused void js_bigint_dump(JSContext *ctx, const char *str, + const JSBigInt *p) +{ + js_bigint_dump1(ctx, str, p->tab, p->len); +} + +static JSBigInt *js_bigint_new_si(JSContext *ctx, js_slimb_t a) +{ + JSBigInt *r; + r = js_bigint_new(ctx, 1); + if (!r) + return NULL; + r->tab[0] = a; + return r; +} + +static JSBigInt *js_bigint_new_si64(JSContext *ctx, int64_t a) +{ + if (a >= INT32_MIN && a <= INT32_MAX) { + return js_bigint_new_si(ctx, a); + } else { + JSBigInt *r; + r = js_bigint_new(ctx, 2); + if (!r) + return NULL; + r->tab[0] = a; + r->tab[1] = a >> 32; + return r; + } +} + +static JSBigInt *js_bigint_new_ui64(JSContext *ctx, uint64_t a) +{ + if (a <= INT64_MAX) { + return js_bigint_new_si64(ctx, a); + } else { + JSBigInt *r; + r = js_bigint_new(ctx, (65 + JS_LIMB_BITS - 1) / JS_LIMB_BITS); + if (!r) + return NULL; + r->tab[0] = a; + r->tab[1] = a >> 32; + r->tab[2] = 0; + return r; + } +} + +static JSBigInt *js_bigint_new_di(JSContext *ctx, js_sdlimb_t a) +{ + JSBigInt *r; + if (a == (js_slimb_t)a) { + r = js_bigint_new(ctx, 1); + if (!r) + return NULL; + r->tab[0] = a; + } else { + r = js_bigint_new(ctx, 2); + if (!r) + return NULL; + r->tab[0] = a; + r->tab[1] = a >> JS_LIMB_BITS; + } + return r; +} + +/* Remove redundant high order limbs. Warning: 'a' may be + reallocated. Can never fail. +*/ +static JSBigInt *js_bigint_normalize1(JSContext *ctx, JSBigInt *a, int l) +{ + js_limb_t v; + + assert(a->header.ref_count == 1); + while (l > 1) { + v = a->tab[l - 1]; + if ((v != 0 && v != -1) || + (v & 1) != (a->tab[l - 2] >> (JS_LIMB_BITS - 1))) { + break; + } + l--; + } + if (l != a->len) { + JSBigInt *a1; + /* realloc to reduce the size */ + a->len = l; + a1 = js_realloc(ctx, a, sizeof(JSBigInt) + l * sizeof(js_limb_t)); + if (a1) + a = a1; + } + return a; +} + +static JSBigInt *js_bigint_normalize(JSContext *ctx, JSBigInt *a) +{ + return js_bigint_normalize1(ctx, a, a->len); +} + +/* return 0 or 1 depending on the sign */ +static inline int js_bigint_sign(const JSBigInt *a) +{ + return a->tab[a->len - 1] >> (JS_LIMB_BITS - 1); +} + +static js_slimb_t js_bigint_get_si_sat(const JSBigInt *a) +{ + if (a->len == 1) { + return a->tab[0]; + } else { + if (js_bigint_sign(a)) + return INT32_MIN; + else + return INT32_MAX; + } +} + +/* add the op1 limb */ +static JSBigInt *js_bigint_extend(JSContext *ctx, JSBigInt *r, + js_limb_t op1) +{ + int n2 = r->len; + if ((op1 != 0 && op1 != -1) || + (op1 & 1) != r->tab[n2 - 1] >> (JS_LIMB_BITS - 1)) { + JSBigInt *r1; + r1 = js_realloc(ctx, r, + sizeof(JSBigInt) + (n2 + 1) * sizeof(js_limb_t)); + if (!r1) { + js_free(ctx, r); + return NULL; + } + r = r1; + r->len = n2 + 1; + r->tab[n2] = op1; + } else { + /* otherwise still need to normalize the result */ + r = js_bigint_normalize(ctx, r); + } + return r; +} + +/* return NULL in case of error. Compute a + b (b_neg = 0) or a - b + (b_neg = 1) */ +/* XXX: optimize */ +static JSBigInt *js_bigint_add(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, int b_neg) +{ + JSBigInt *r; + int n1, n2, i; + js_limb_t carry, op1, op2, a_sign, b_sign; + + n2 = max_int(a->len, b->len); + n1 = min_int(a->len, b->len); + r = js_bigint_new(ctx, n2); + if (!r) + return NULL; + /* XXX: optimize */ + /* common part */ + carry = b_neg; + for(i = 0; i < n1; i++) { + op1 = a->tab[i]; + op2 = b->tab[i] ^ (-b_neg); + ADDC(r->tab[i], carry, op1, op2, carry); + } + a_sign = -js_bigint_sign(a); + b_sign = (-js_bigint_sign(b)) ^ (-b_neg); + /* part with sign extension of one operand */ + if (a->len > b->len) { + for(i = n1; i < n2; i++) { + op1 = a->tab[i]; + ADDC(r->tab[i], carry, op1, b_sign, carry); + } + } else if (a->len < b->len) { + for(i = n1; i < n2; i++) { + op2 = b->tab[i] ^ (-b_neg); + ADDC(r->tab[i], carry, a_sign, op2, carry); + } + } + + /* part with sign extension for both operands. Extend the result + if necessary */ + return js_bigint_extend(ctx, r, a_sign + b_sign + carry); +} + +/* XXX: optimize */ +static JSBigInt *js_bigint_neg(JSContext *ctx, const JSBigInt *a) +{ + JSBigIntBuf buf; + JSBigInt *b; + b = js_bigint_set_si(&buf, 0); + return js_bigint_add(ctx, b, a, 1); +} + +static JSBigInt *js_bigint_mul(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b) +{ + JSBigInt *r; + + r = js_bigint_new(ctx, a->len + b->len); + if (!r) + return NULL; + js_mp_mul_basecase(r->tab, a->tab, a->len, b->tab, b->len); + /* correct the result if negative operands (no overflow is + possible) */ + if (js_bigint_sign(a)) + js_mp_sub(r->tab + a->len, r->tab + a->len, b->tab, b->len, 0); + if (js_bigint_sign(b)) + js_mp_sub(r->tab + b->len, r->tab + b->len, a->tab, a->len, 0); + return js_bigint_normalize(ctx, r); +} + +/* return the division or the remainder. 'b' must be != 0. return NULL + in case of exception (division by zero or memory error) */ +static JSBigInt *js_bigint_divrem(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, bool is_rem) +{ + JSBigInt *r, *q; + js_limb_t *tabb, h; + int na, nb, a_sign, b_sign, shift; + + if (b->len == 1 && b->tab[0] == 0) { + JS_ThrowRangeError(ctx, "BigInt division by zero"); + return NULL; + } + + a_sign = js_bigint_sign(a); + b_sign = js_bigint_sign(b); + na = a->len; + nb = b->len; + + r = js_bigint_new(ctx, na + 2); + if (!r) + return NULL; + if (a_sign) { + js_mp_neg(r->tab, a->tab, na); + } else { + memcpy(r->tab, a->tab, na * sizeof(a->tab[0])); + } + /* normalize */ + while (na > 1 && r->tab[na - 1] == 0) + na--; + + tabb = js_malloc(ctx, nb * sizeof(tabb[0])); + if (!tabb) { + js_free(ctx, r); + return NULL; + } + if (b_sign) { + js_mp_neg(tabb, b->tab, nb); + } else { + memcpy(tabb, b->tab, nb * sizeof(tabb[0])); + } + /* normalize */ + while (nb > 1 && tabb[nb - 1] == 0) + nb--; + + /* trivial case if 'a' is small */ + if (na < nb) { + js_free(ctx, r); + js_free(ctx, tabb); + if (is_rem) { + /* r = a */ + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + memcpy(r->tab, a->tab, a->len * sizeof(a->tab[0])); + return r; + } else { + /* q = 0 */ + return js_bigint_new_si(ctx, 0); + } + } + + /* normalize 'b' */ + shift = js_limb_clz(tabb[nb - 1]); + if (shift != 0) { + js_mp_shl(tabb, tabb, nb, shift); + h = js_mp_shl(r->tab, r->tab, na, shift); + if (h != 0) + r->tab[na++] = h; + } + + q = js_bigint_new(ctx, na - nb + 2); /* one more limb for the sign */ + if (!q) { + js_free(ctx, r); + js_free(ctx, tabb); + return NULL; + } + + // js_bigint_dump1(ctx, "a", r->tab, na); + // js_bigint_dump1(ctx, "b", tabb, nb); + js_mp_divnorm(q->tab, r->tab, na, tabb, nb); + js_free(ctx, tabb); + + if (is_rem) { + js_free(ctx, q); + if (shift != 0) + js_mp_shr(r->tab, r->tab, nb, shift, 0); + r->tab[nb++] = 0; + if (a_sign) + js_mp_neg(r->tab, r->tab, nb); + r = js_bigint_normalize1(ctx, r, nb); + return r; + } else { + js_free(ctx, r); + q->tab[na - nb + 1] = 0; + if (a_sign ^ b_sign) { + js_mp_neg(q->tab, q->tab, q->len); + } + q = js_bigint_normalize(ctx, q); + return q; + } +} + +/* and, or, xor */ +static JSBigInt *js_bigint_logic(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b, OPCodeEnum op) +{ + JSBigInt *r; + js_limb_t b_sign; + int a_len, b_len, i; + + if (a->len < b->len) { + const JSBigInt *tmp; + tmp = a; + a = b; + b = tmp; + } + /* a_len >= b_len */ + a_len = a->len; + b_len = b->len; + b_sign = -js_bigint_sign(b); + + r = js_bigint_new(ctx, a_len); + if (!r) + return NULL; + switch(op) { + case OP_or: + for(i = 0; i < b_len; i++) { + r->tab[i] = a->tab[i] | b->tab[i]; + } + for(i = b_len; i < a_len; i++) { + r->tab[i] = a->tab[i] | b_sign; + } + break; + case OP_and: + for(i = 0; i < b_len; i++) { + r->tab[i] = a->tab[i] & b->tab[i]; + } + for(i = b_len; i < a_len; i++) { + r->tab[i] = a->tab[i] & b_sign; + } + break; + case OP_xor: + for(i = 0; i < b_len; i++) { + r->tab[i] = a->tab[i] ^ b->tab[i]; + } + for(i = b_len; i < a_len; i++) { + r->tab[i] = a->tab[i] ^ b_sign; + } + break; + default: + abort(); + } + return js_bigint_normalize(ctx, r); +} + +static JSBigInt *js_bigint_not(JSContext *ctx, const JSBigInt *a) +{ + JSBigInt *r; + int i; + + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + for(i = 0; i < a->len; i++) { + r->tab[i] = ~a->tab[i]; + } + /* no normalization is needed */ + return r; +} + +static JSBigInt *js_bigint_shl(JSContext *ctx, const JSBigInt *a, + unsigned int shift1) +{ + int d, i, shift; + JSBigInt *r; + js_limb_t l; + + if (a->len == 1 && a->tab[0] == 0) + return js_bigint_new_si(ctx, 0); /* zero case */ + d = shift1 / JS_LIMB_BITS; + shift = shift1 % JS_LIMB_BITS; + r = js_bigint_new(ctx, a->len + d); + if (!r) + return NULL; + for(i = 0; i < d; i++) + r->tab[i] = 0; + if (shift == 0) { + for(i = 0; i < a->len; i++) { + r->tab[i + d] = a->tab[i]; + } + } else { + l = js_mp_shl(r->tab + d, a->tab, a->len, shift); + if (js_bigint_sign(a)) + l |= (js_limb_t)(-1) << shift; + r = js_bigint_extend(ctx, r, l); + } + return r; +} + +static JSBigInt *js_bigint_shr(JSContext *ctx, const JSBigInt *a, + unsigned int shift1) +{ + int d, i, shift, a_sign, n1; + JSBigInt *r; + + d = shift1 / JS_LIMB_BITS; + shift = shift1 % JS_LIMB_BITS; + a_sign = js_bigint_sign(a); + if (d >= a->len) + return js_bigint_new_si(ctx, -a_sign); + n1 = a->len - d; + r = js_bigint_new(ctx, n1); + if (!r) + return NULL; + if (shift == 0) { + for(i = 0; i < n1; i++) { + r->tab[i] = a->tab[i + d]; + } + /* no normalization is needed */ + } else { + js_mp_shr(r->tab, a->tab + d, n1, shift, -a_sign); + r = js_bigint_normalize(ctx, r); + } + return r; +} + +static JSBigInt *js_bigint_pow(JSContext *ctx, const JSBigInt *a, JSBigInt *b) +{ + uint32_t e; + int n_bits, i; + JSBigInt *r, *r1; + + /* b must be >= 0 */ + if (js_bigint_sign(b)) { + JS_ThrowRangeError(ctx, "BigInt negative exponent"); + return NULL; + } + if (b->len == 1 && b->tab[0] == 0) { + /* a^0 = 1 */ + return js_bigint_new_si(ctx, 1); + } else if (a->len == 1) { + js_limb_t v; + bool is_neg; + + v = a->tab[0]; + if (v <= 1) + return js_bigint_new_si(ctx, v); + else if (v == -1) + return js_bigint_new_si(ctx, 1 - 2 * (b->tab[0] & 1)); + is_neg = (js_slimb_t)v < 0; + if (is_neg) + v = -v; + if ((v & (v - 1)) == 0) { + uint64_t e1; + int n; + /* v = 2^n */ + n = JS_LIMB_BITS - 1 - js_limb_clz(v); + if (b->len > 1) + goto overflow; + if (b->tab[0] > INT32_MAX) + goto overflow; + e = b->tab[0]; + e1 = (uint64_t)e * n; + if (e1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) + goto overflow; + e = e1; + if (is_neg) + is_neg = b->tab[0] & 1; + r = js_bigint_new(ctx, + (e + JS_LIMB_BITS + 1 - is_neg) / JS_LIMB_BITS); + if (!r) + return NULL; + memset(r->tab, 0, sizeof(r->tab[0]) * r->len); + r->tab[e / JS_LIMB_BITS] = + (js_limb_t)(1 - 2 * is_neg) << (e % JS_LIMB_BITS); + return r; + } + } + if (b->len > 1) + goto overflow; + if (b->tab[0] > INT32_MAX) + goto overflow; + e = b->tab[0]; + n_bits = 32 - clz32(e); + + r = js_bigint_new(ctx, a->len); + if (!r) + return NULL; + memcpy(r->tab, a->tab, a->len * sizeof(a->tab[0])); + for(i = n_bits - 2; i >= 0; i--) { + r1 = js_bigint_mul(ctx, r, r); + if (!r1) + return NULL; + js_free(ctx, r); + r = r1; + if ((e >> i) & 1) { + r1 = js_bigint_mul(ctx, r, a); + if (!r1) + return NULL; + js_free(ctx, r); + r = r1; + } + } + return r; + overflow: + JS_ThrowRangeError(ctx, "BigInt is too large"); + return NULL; +} + +/* return (mant, exp) so that abs(a) ~ mant*2^(exp - (limb_bits - + 1). a must be != 0. */ +static uint64_t js_bigint_get_mant_exp(JSContext *ctx, + int *pexp, const JSBigInt *a) +{ + js_limb_t t[4 - JS_LIMB_BITS / 32], carry, v, low_bits; + int n1, n2, sgn, shift, i, j, e; + uint64_t a1, a0; + + n2 = 4 - JS_LIMB_BITS / 32; + n1 = a->len - n2; + sgn = js_bigint_sign(a); + + /* low_bits != 0 if there are a non zero low bit in abs(a) */ + low_bits = 0; + carry = sgn; + for(i = 0; i < n1; i++) { + v = (a->tab[i] ^ (-sgn)) + carry; + carry = v < carry; + low_bits |= v; + } + /* get the n2 high limbs of abs(a) */ + for(j = 0; j < n2; j++) { + i = j + n1; + if (i < 0) { + v = 0; + } else { + v = (a->tab[i] ^ (-sgn)) + carry; + carry = v < carry; + } + t[j] = v; + } + + a1 = ((uint64_t)t[2] << 32) | t[1]; + a0 = (uint64_t)t[0] << 32; + a0 |= (low_bits != 0); + /* normalize */ + { + shift = clz64(a1); + if (shift != 0) { + a1 = (a1 << shift) | (a0 >> (64 - shift)); + a0 <<= shift; + } + } + a1 |= (a0 != 0); /* keep the bits for the final rounding */ + /* compute the exponent */ + e = a->len * JS_LIMB_BITS - shift - 1; + *pexp = e; + return a1; +} + +/* shift left with round to nearest, ties to even. n >= 1 */ +static uint64_t shr_rndn(uint64_t a, int n) +{ + uint64_t addend = ((a >> n) & 1) + ((1 << (n - 1)) - 1); + return (a + addend) >> n; +} + +/* convert to float64 with round to nearest, ties to even. Return + +/-infinity if too large. */ +static double js_bigint_to_float64(JSContext *ctx, const JSBigInt *a) +{ + int sgn, e; + uint64_t mant; + + if (a->len == 1) { + /* fast case, including zero */ + return (double)(js_slimb_t)a->tab[0]; + } + + sgn = js_bigint_sign(a); + mant = js_bigint_get_mant_exp(ctx, &e, a); + if (e > 1023) { + /* overflow: return infinity */ + mant = 0; + e = 1024; + } else { + mant = (mant >> 1) | (mant & 1); /* avoid overflow in rounding */ + mant = shr_rndn(mant, 10); + /* rounding can cause an overflow */ + if (mant >= ((uint64_t)1 << 53)) { + mant >>= 1; + e++; + } + mant &= (((uint64_t)1 << 52) - 1); + } + return uint64_as_float64(((uint64_t)sgn << 63) | + ((uint64_t)(e + 1023) << 52) | + mant); +} + +/* return (1, NULL) if not an integer, (2, NULL) if NaN or Infinity, + (0, n) if an integer, (0, NULL) in case of memory error */ +static JSBigInt *js_bigint_from_float64(JSContext *ctx, int *pres, double a1) +{ + uint64_t a = float64_as_uint64(a1); + int sgn, e, shift; + uint64_t mant; + JSBigIntBuf buf; + JSBigInt *r; + + sgn = a >> 63; + e = (a >> 52) & ((1 << 11) - 1); + mant = a & (((uint64_t)1 << 52) - 1); + if (e == 2047) { + /* NaN, Infinity */ + *pres = 2; + return NULL; + } + if (e == 0 && mant == 0) { + /* zero */ + *pres = 0; + return js_bigint_new_si(ctx, 0); + } + e -= 1023; + /* 0 < a < 1 : not an integer */ + if (e < 0) + goto not_an_integer; + mant |= (uint64_t)1 << 52; + if (e < 52) { + shift = 52 - e; + /* check that there is no fractional part */ + if (mant & (((uint64_t)1 << shift) - 1)) { + not_an_integer: + *pres = 1; + return NULL; + } + mant >>= shift; + e = 0; + } else { + e -= 52; + } + if (sgn) + mant = -mant; + /* the integer is mant*2^e */ + r = js_bigint_set_si64(&buf, (int64_t)mant); + *pres = 0; + return js_bigint_shl(ctx, r, e); +} + +/* return -1, 0, 1 or (2) (unordered) */ +static int js_bigint_float64_cmp(JSContext *ctx, const JSBigInt *a, + double b) +{ + int b_sign, a_sign, e, f; + uint64_t mant, b1, a_mant; + + b1 = float64_as_uint64(b); + b_sign = b1 >> 63; + e = (b1 >> 52) & ((1 << 11) - 1); + mant = b1 & (((uint64_t)1 << 52) - 1); + a_sign = js_bigint_sign(a); + if (e == 2047) { + if (mant != 0) { + /* NaN */ + return 2; + } else { + /* +/- infinity */ + return 2 * b_sign - 1; + } + } else if (e == 0 && mant == 0) { + /* b = +/-0 */ + if (a->len == 1 && a->tab[0] == 0) + return 0; + else + return 1 - 2 * a_sign; + } else if (a->len == 1 && a->tab[0] == 0) { + /* a = 0, b != 0 */ + return 2 * b_sign - 1; + } else if (a_sign != b_sign) { + return 1 - 2 * a_sign; + } else { + e -= 1023; + /* Note: handling denormals is not necessary because we + compare to integers hence f >= 0 */ + /* compute f so that 2^f <= abs(a) < 2^(f+1) */ + a_mant = js_bigint_get_mant_exp(ctx, &f, a); + if (f != e) { + if (f < e) + return -1; + else + return 1; + } else { + mant = (mant | ((uint64_t)1 << 52)) << 11; /* align to a_mant */ + if (a_mant < mant) + return 2 * a_sign - 1; + else if (a_mant > mant) + return 1 - 2 * a_sign; + else + return 0; + } + } +} + +/* return -1, 0 or 1 */ +static int js_bigint_cmp(JSContext *ctx, const JSBigInt *a, + const JSBigInt *b) +{ + int a_sign, b_sign, res, i; + a_sign = js_bigint_sign(a); + b_sign = js_bigint_sign(b); + if (a_sign != b_sign) { + res = 1 - 2 * a_sign; + } else { + /* we assume the numbers are normalized */ + if (a->len != b->len) { + if (a->len < b->len) + res = 2 * a_sign - 1; + else + res = 1 - 2 * a_sign; + } else { + res = 0; + for(i = a->len -1; i >= 0; i--) { + if (a->tab[i] != b->tab[i]) { + if (a->tab[i] < b->tab[i]) + res = -1; + else + res = 1; + break; + } + } + } + } + return res; +} + +/* contains 10^i */ +static const js_limb_t js_pow_dec[JS_LIMB_DIGITS + 1] = { + 1U, + 10U, + 100U, + 1000U, + 10000U, + 100000U, + 1000000U, + 10000000U, + 100000000U, + 1000000000U, +}; + +/* syntax: [-]digits in base radix. Return NULL if memory error. radix + = 10, 2, 8 or 16. */ +static JSBigInt *js_bigint_from_string(JSContext *ctx, + const char *str, int radix) +{ + const char *p = str; + size_t n_digits1; + int is_neg, n_digits, n_limbs, len, log2_radix, n_bits, i; + JSBigInt *r; + js_limb_t v, c, h; + + is_neg = 0; + if (*p == '-') { + is_neg = 1; + p++; + } + while (*p == '0') + p++; + n_digits1 = strlen(p); + /* the real check for overflox is done js_bigint_new(). Here + we just avoid integer overflow */ + if (n_digits1 > JS_BIGINT_MAX_SIZE * JS_LIMB_BITS) { + JS_ThrowRangeError(ctx, "BigInt is too large to allocate"); + return NULL; + } + n_digits = n_digits1; + log2_radix = 32 - clz32(radix - 1); /* ceil(log2(radix)) */ + /* compute the maximum number of limbs */ + if (radix == 10) { + n_bits = (n_digits * 27 + 7) / 8; /* >= ceil(n_digits * log2(10)) */ + } else { + n_bits = n_digits * log2_radix; + } + /* we add one extra bit for the sign */ + n_limbs = max_int(1, n_bits / JS_LIMB_BITS + 1); + r = js_bigint_new(ctx, n_limbs); + if (!r) + return NULL; + if (radix == 10) { + int digits_per_limb = JS_LIMB_DIGITS; + len = 1; + r->tab[0] = 0; + for(;;) { + /* XXX: slow */ + v = 0; + for(i = 0; i < digits_per_limb; i++) { + c = js_to_digit(*p); + if (c >= radix) + break; + p++; + v = v * 10 + c; + } + if (i == 0) + break; + if (len == 1 && r->tab[0] == 0) { + r->tab[0] = v; + } else { + h = js_mp_mul1(r->tab, r->tab, len, js_pow_dec[i], v); + if (h != 0) { + r->tab[len++] = h; + } + } + } + /* add one extra limb to have the correct sign*/ + if ((r->tab[len - 1] >> (JS_LIMB_BITS - 1)) != 0) + r->tab[len++] = 0; + r->len = len; + } else { + unsigned int bit_pos, shift, pos; + + /* power of two base: no multiplication is needed */ + r->len = n_limbs; + memset(r->tab, 0, sizeof(r->tab[0]) * n_limbs); + for(i = 0; i < n_digits; i++) { + c = js_to_digit(p[n_digits - 1 - i]); + assert(c < radix); + bit_pos = i * log2_radix; + shift = bit_pos & (JS_LIMB_BITS - 1); + pos = bit_pos / JS_LIMB_BITS; + r->tab[pos] |= c << shift; + /* if log2_radix does not divide JS_LIMB_BITS, needed an + additional op */ + if (shift + log2_radix > JS_LIMB_BITS) { + r->tab[pos + 1] |= c >> (JS_LIMB_BITS - shift); + } + } + } + r = js_bigint_normalize(ctx, r); + /* XXX: could do it in place */ + if (is_neg) { + JSBigInt *r1; + r1 = js_bigint_neg(ctx, r); + js_free(ctx, r); + r = r1; + } + return r; +} + +/* 2 <= base <= 36 */ +static char const digits[36] = { + '0','1','2','3','4','5','6','7','8','9', + 'a','b','c','d','e','f','g','h','i','j', + 'k','l','m','n','o','p','q','r','s','t', + 'u','v','w','x','y','z' +}; + +/* special version going backwards */ +/* XXX: use dtoa.c */ +static char *js_u64toa(char *q, int64_t n, unsigned int base) +{ + int digit; + if (base == 10) { + /* division by known base uses multiplication */ + do { + digit = (uint64_t)n % 10; + n = (uint64_t)n / 10; + *--q = '0' + digit; + } while (n != 0); + } else { + do { + digit = (uint64_t)n % base; + n = (uint64_t)n / base; + *--q = digits[digit]; + } while (n != 0); + } + return q; +} + +/* len >= 1. 2 <= radix <= 36 */ +static char *js_limb_to_a(char *q, js_limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ + /* XXX: optimize */ + for(i = 0; i < len; i++) { + digit = (js_limb_t)n % 10; + n = (js_limb_t)n / 10; + *--q = digit + '0'; + } + } else { + for(i = 0; i < len; i++) { + digit = (js_limb_t)n % radix; + n = (js_limb_t)n / radix; + *--q = digits[digit]; + } + } + return q; +} + +#define JS_RADIX_MAX 36 + +static const uint8_t js_digits_per_limb_table[JS_RADIX_MAX - 1] = { +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +}; + +static const js_limb_t js_radix_base_table[JS_RADIX_MAX - 1] = { + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +}; + +static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { + char buf[66]; + int len; + len = i64toa_radix(buf, JS_VALUE_GET_SHORT_BIG_INT(val), radix); + return js_new_string8_len(ctx, buf, len); + } else { + JSBigInt *r, *tmp = NULL; + char *buf, *q, *buf_end; + int is_neg, n_bits, log2_radix, n_digits; + bool is_binary_radix; + JSValue res; + + assert(JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT); + r = JS_VALUE_GET_PTR(val); + if (r->len == 1 && r->tab[0] == 0) { + /* '0' case */ + return js_new_string8_len(ctx, "0", 1); + } + is_binary_radix = ((radix & (radix - 1)) == 0); + is_neg = js_bigint_sign(r); + if (is_neg) { + tmp = js_bigint_neg(ctx, r); + if (!tmp) + return JS_EXCEPTION; + r = tmp; + } else if (!is_binary_radix) { + /* need to modify 'r' */ + tmp = js_bigint_new(ctx, r->len); + if (!tmp) + return JS_EXCEPTION; + memcpy(tmp->tab, r->tab, r->len * sizeof(r->tab[0])); + r = tmp; + } + log2_radix = 31 - clz32(radix); /* floor(log2(radix)) */ + n_bits = r->len * JS_LIMB_BITS - js_limb_clz(r->tab[r->len - 1]); + /* n_digits is exact only if radix is a power of + two. Otherwise it is >= the exact number of digits */ + n_digits = (n_bits + log2_radix - 1) / log2_radix; + /* XXX: could directly build the JSString */ + buf = js_malloc(ctx, n_digits + is_neg + 1); + if (!buf) { + js_free(ctx, tmp); + return JS_EXCEPTION; + } + q = buf + n_digits + is_neg + 1; + *--q = '\0'; + buf_end = q; + if (!is_binary_radix) { + int len; + js_limb_t radix_base, v; + radix_base = js_radix_base_table[radix - 2]; + len = r->len; + for(;;) { + /* remove leading zero limbs */ + while (len > 1 && r->tab[len - 1] == 0) + len--; + if (len == 1 && r->tab[0] < radix_base) { + v = r->tab[0]; + if (v != 0) { + q = js_u64toa(q, v, radix); + } + break; + } else { + v = js_mp_div1(r->tab, r->tab, len, radix_base, 0); + q = js_limb_to_a(q, v, radix, js_digits_per_limb_table[radix - 2]); + } + } + } else { + int i, shift; + unsigned int bit_pos, pos, c; + + /* radix is a power of two */ + for(i = 0; i < n_digits; i++) { + bit_pos = i * log2_radix; + pos = bit_pos / JS_LIMB_BITS; + shift = bit_pos % JS_LIMB_BITS; + c = r->tab[pos] >> shift; + if ((shift + log2_radix) > JS_LIMB_BITS && + (pos + 1) < r->len) { + c |= r->tab[pos + 1] << (JS_LIMB_BITS - shift); + } + c &= (radix - 1); + *--q = digits[c]; + } + } + if (is_neg) + *--q = '-'; + js_free(ctx, tmp); + res = js_new_string8_len(ctx, q, buf_end - q); + js_free(ctx, buf); + return res; + } +} + +/* if possible transform a BigInt to short big and free it, otherwise + return a normal bigint */ +static JSValue JS_CompactBigInt(JSContext *ctx, JSBigInt *p) +{ + JSValue res; + if (p->len == 1) { + res = __JS_NewShortBigInt(ctx, (js_slimb_t)p->tab[0]); + js_free(ctx, p); + return res; + } else { + return JS_MKPTR(JS_TAG_BIG_INT, p); + } +} + +#define ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define ATOD_ACCEPT_BIN_OCT (1 << 2) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4) +/* accept _ between digits as a digit separator */ +#define ATOD_ACCEPT_UNDERSCORES (1 << 5) +/* allow a suffix to override the type */ +#define ATOD_ACCEPT_SUFFIX (1 << 6) +/* default type */ +#define ATOD_TYPE_MASK (3 << 7) +#define ATOD_TYPE_FLOAT64 (0 << 7) +#define ATOD_TYPE_BIG_INT (1 << 7) +/* accept -0x1 */ +#define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) + +/* return an exception in case of memory error. Return JS_NAN if + invalid syntax */ +/* XXX: directly use js_atod() */ +static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, + int radix, int flags) +{ + const char *p, *p_start; + int sep, is_neg; + bool is_float, has_legacy_octal; + int atod_type = flags & ATOD_TYPE_MASK; + char buf1[64], *buf; + int i, j, len; + bool buf_allocated = false; + JSValue val; + JSATODTempMem atod_mem; + + /* optional separator between digits */ + sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + has_legacy_octal = false; + + p = str; + p_start = p; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start++; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; + } else if (p[0] == '-') { + p++; + p_start++; + is_neg = 1; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; + } + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + has_legacy_octal = true; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (js_to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + no_radix_prefix: + if (!(flags & ATOD_INT_ONLY) && + (atod_type == ATOD_TYPE_FLOAT64) && + js__strstart(p, "Infinity", &p)) { + double d = INF; + if (is_neg) + d = -d; + val = js_float64(d); + goto done; + } + } + if (radix == 0) + radix = 10; + is_float = false; + p_start = p; + while (js_to_digit((uint8_t)*p) < radix + || (*p == sep && (radix != 10 || + p != p_start + 1 || p[-1] != '0') && + js_to_digit((uint8_t)p[1]) < radix)) { + p++; + } + if (!(flags & ATOD_INT_ONLY) && radix == 10) { + if (*p == '.' && (p > p_start || js_to_digit((uint8_t)p[1]) < radix)) { + is_float = true; + p++; + if (*p == sep) + goto fail; + while (js_to_digit((uint8_t)*p) < radix || + (*p == sep && js_to_digit((uint8_t)p[1]) < radix)) + p++; + } + if (p > p_start && (*p == 'e' || *p == 'E')) { + const char *p1 = p + 1; + is_float = true; + if (*p1 == '+') { + p1++; + } else if (*p1 == '-') { + p1++; + } + if (is_digit((uint8_t)*p1)) { + p = p1 + 1; + while (is_digit((uint8_t)*p) || (*p == sep && is_digit((uint8_t)p[1]))) + p++; + } + } + } + if (p == p_start) + goto fail; + + buf = buf1; + buf_allocated = false; + len = p - p_start; + if (unlikely((len + 2) > sizeof(buf1))) { + buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */ + if (!buf) + goto mem_error; + buf_allocated = true; + } + /* remove the separators and the radix prefixes */ + j = 0; + if (is_neg) + buf[j++] = '-'; + for (i = 0; i < len; i++) { + if (p_start[i] != '_') + buf[j++] = p_start[i]; + } + buf[j] = '\0'; + + if (flags & ATOD_ACCEPT_SUFFIX) { + if (*p == 'n') { + p++; + atod_type = ATOD_TYPE_BIG_INT; + } + } + + switch(atod_type) { + case ATOD_TYPE_FLOAT64: + { + double d; + d = js_atod(buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, + &atod_mem); + /* return int or float64 */ + val = js_number(d); + } + break; + case ATOD_TYPE_BIG_INT: + { + JSBigInt *r; + if (has_legacy_octal || is_float) + goto fail; + r = js_bigint_from_string(ctx, buf, radix); + if (!r) { + val = JS_EXCEPTION; + goto done; + } + val = JS_CompactBigInt(ctx, r); + } + break; + default: + abort(); + } + +done: + if (buf_allocated) + js_free_rt(ctx->rt, buf); + if (pp) + *pp = p; + return val; + fail: + val = JS_NAN; + goto done; + mem_error: + val = JS_ThrowOutOfMemory(ctx); + goto done; +} + +typedef enum JSToNumberHintEnum { + TON_FLAG_NUMBER, + TON_FLAG_NUMERIC, +} JSToNumberHintEnum; + +static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val, + JSToNumberHintEnum flag) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_BIG_INT: + case JS_TAG_SHORT_BIG_INT: + if (flag != TON_FLAG_NUMERIC) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert BigInt to number"); + } + ret = val; + break; + case JS_TAG_FLOAT64: + case JS_TAG_INT: + case JS_TAG_EXCEPTION: + ret = val; + break; + case JS_TAG_BOOL: + case JS_TAG_NULL: + ret = js_int32(JS_VALUE_GET_INT(val)); + break; + case JS_TAG_UNDEFINED: + ret = JS_NAN; + break; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return JS_EXCEPTION; + goto redo; + case JS_TAG_STRING: + { + const char *str; + const char *p; + size_t len; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + ret = JS_NewInt32(ctx, 0); + } else { + int flags = ATOD_ACCEPT_BIN_OCT; + ret = js_atof(ctx, p, &p, 0, flags); + if (!JS_IsException(ret)) { + p += skip_spaces(p); + if ((p - str) != len) { + JS_FreeValue(ctx, ret); + ret = JS_NAN; + } + } + } + JS_FreeCString(ctx, str); + } + break; + case JS_TAG_SYMBOL: + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert symbol to number"); + default: + JS_FreeValue(ctx, val); + ret = JS_NAN; + break; + } + return ret; +} + +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER); +} + +static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC); +} + +static JSValue JS_ToNumeric(JSContext *ctx, JSValueConst val) +{ + return JS_ToNumericFree(ctx, js_dup(val)); +} + +static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres, + JSValue val) +{ + double d; + uint32_t tag; + + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + goto fail; + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + d = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + d = JS_VALUE_GET_FLOAT64(val); + break; + default: + abort(); + } + *pres = d; + return 0; +fail: + *pres = NAN; + return -1; +} + +static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_TAG(val); + if (tag <= JS_TAG_NULL) { + *pres = JS_VALUE_GET_INT(val); + return 0; + } else if (JS_TAG_IS_FLOAT64(tag)) { + *pres = JS_VALUE_GET_FLOAT64(val); + return 0; + } else { + return __JS_ToFloat64Free(ctx, pres, val); + } +} + +int JS_ToFloat64(JSContext *ctx, double *pres, JSValueConst val) +{ + return JS_ToFloat64Free(ctx, pres, js_dup(val)); +} + +JSValue JS_ToNumber(JSContext *ctx, JSValueConst val) +{ + return JS_ToNumberFree(ctx, js_dup(val)); +} + +/* same as JS_ToNumber() but return 0 in case of NaN/Undefined */ +static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = js_int32(JS_VALUE_GET_INT(val)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = js_int32(0); + } else { + /* convert -0 to +0 */ + d = trunc(d) + 0.0; + ret = js_number(d); + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return val; + goto redo; + } + return ret; +} + +/* Note: the integer value is satured to 32 bits */ +static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val) +{ + uint32_t tag; + int ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = 0; + } else { + if (d < INT32_MIN) + ret = INT32_MIN; + else if (d > INT32_MAX) + ret = INT32_MAX; + else + ret = (int)d; + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +static int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValueConst val) +{ + return JS_ToInt32SatFree(ctx, pres, js_dup(val)); +} + +static int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValueConst val, + int min, int max, int min_offset) +{ + int res = JS_ToInt32SatFree(ctx, pres, js_dup(val)); + if (res == 0) { + if (*pres < min) { + *pres += min_offset; + if (*pres < min) + *pres = min; + } else { + if (*pres > max) + *pres = max; + } + } + return res; +} + +static int JS_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + *pres = JS_VALUE_GET_INT(val); + return 0; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + *pres = 0; + } else { + if (d < INT64_MIN) + *pres = INT64_MIN; + else if (d >= 0x1p63) + *pres = INT64_MAX; + else + *pres = (int64_t)d; + } + } + return 0; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } +} + +int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToInt64SatFree(ctx, pres, js_dup(val)); +} + +int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValueConst val, + int64_t min, int64_t max, int64_t neg_offset) +{ + int res = JS_ToInt64SatFree(ctx, pres, js_dup(val)); + if (res == 0) { + if (*pres < 0) + *pres += neg_offset; + if (*pres < min) + *pres = min; + else if (*pres > max) + *pres = max; + } + return res; +} + +/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0) + in case of exception */ +static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + int64_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^64) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 62))) { + /* fast case */ + ret = (int64_t)d; + } else if (e <= (1023 + 62 + 53)) { + uint64_t v; + /* remainder modulo 2^64 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + ret = v << ((e - 1023) - 52); + /* take the sign into account */ + if (u.u64 >> 63) + if (ret != INT64_MIN) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToInt64Free(ctx, pres, js_dup(val)); +} + +int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + if (JS_IsBigInt(val)) + return JS_ToBigInt64(ctx, pres, val); + else + return JS_ToInt64(ctx, pres, val); +} + +/* return (<0, 0) in case of exception */ +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int32_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^32) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 30))) { + /* fast case */ + ret = (int32_t)d; + } else if (e <= (1023 + 30 + 53)) { + uint64_t v; + /* remainder modulo 2^32 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + v = v << ((e - 1023) - 52 + 32); + ret = v >> 32; + /* take the sign into account */ + if (u.u64 >> 63) + if (ret != INT32_MIN) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val) +{ + return JS_ToInt32Free(ctx, pres, js_dup(val)); +} + +static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val) +{ + return JS_ToInt32Free(ctx, (int32_t *)pres, val); +} + +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int res; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = JS_VALUE_GET_INT(val); + res = max_int(0, min_int(255, res)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + res = 0; + } else { + if (d < 0) + res = 0; + else if (d > 255) + res = 255; + else + res = lrint(d); + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = res; + return 0; +} + +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, bool is_array_ctor) +{ + uint32_t tag, len; + + tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + { + int v; + v = JS_VALUE_GET_INT(val); + if (v < 0) + goto fail; + len = v; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d; + d = JS_VALUE_GET_FLOAT64(val); + if (!(d >= 0 && d <= UINT32_MAX)) + goto fail; + len = (uint32_t)d; + if (len != d) + goto fail; + } else { + uint32_t len1; + + if (is_array_ctor) { + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len, val, true)) + return -1; + } else { + /* legacy behavior: must do the conversion twice and compare */ + if (JS_ToUint32(ctx, &len, val)) { + JS_FreeValue(ctx, val); + return -1; + } + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len1, val, false)) + return -1; + if (len1 != len) { + fail: + JS_ThrowRangeError(ctx, "invalid array length"); + return -1; + } + } + } + break; + } + *plen = len; + return 0; +} + +#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) + +static bool is_safe_integer(double d) +{ + return isfinite(d) && floor(d) == d && + fabs(d) <= (double)MAX_SAFE_INTEGER; +} + +int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValueConst val) +{ + int64_t v; + if (JS_ToInt64Sat(ctx, &v, val)) + return -1; + if (v < 0 || v > MAX_SAFE_INTEGER) { + JS_ThrowRangeError(ctx, "invalid array index"); + *plen = 0; + return -1; + } + *plen = v; + return 0; +} + +/* convert a value to a length between 0 and MAX_SAFE_INTEGER. + return -1 for exception */ +static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen, + JSValue val) +{ + int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); + JS_FreeValue(ctx, val); + return res; +} + +/* Note: can return an exception */ +static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val) +{ + double d; + if (!JS_IsNumber(val)) + return false; + if (unlikely(JS_ToFloat64(ctx, &d, val))) + return -1; + return isfinite(d) && floor(d) == d; +} + +static bool JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + { + int v; + v = JS_VALUE_GET_INT(val); + return (v < 0); + } + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + u.d = JS_VALUE_GET_FLOAT64(val); + return (u.u64 >> 63); + } + case JS_TAG_SHORT_BIG_INT: + return (JS_VALUE_GET_SHORT_BIG_INT(val) < 0); + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + return js_bigint_sign(p); + } + default: + return false; + } +} + +static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val) +{ + return js_bigint_to_string1(ctx, val, 10); +} + +/*---- floating point number to string conversions ----*/ + +static JSValue js_dtoa2(JSContext *ctx, + double d, int radix, int n_digits, int flags) +{ + char static_buf[128], *buf, *tmp_buf; + int len, len_max; + JSValue res; + JSDTOATempMem dtoa_mem; + len_max = js_dtoa_max_len(d, radix, n_digits, flags); + + /* longer buffer may be used if radix != 10 */ + if (len_max > sizeof(static_buf) - 1) { + tmp_buf = js_malloc(ctx, len_max + 1); + if (!tmp_buf) + return JS_EXCEPTION; + buf = tmp_buf; + } else { + tmp_buf = NULL; + buf = static_buf; + } + len = js_dtoa(buf, d, radix, n_digits, flags, &dtoa_mem); + res = js_new_string8_len(ctx, buf, len); + js_free(ctx, tmp_buf); + return res; +} + +static JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, + int flags) +{ + uint32_t tag; + char buf[32]; + size_t len; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_STRING: + return js_dup(val); + case JS_TAG_INT: + len = i32toa(buf, JS_VALUE_GET_INT(val)); + return js_new_string8_len(ctx, buf, len); + case JS_TAG_BOOL: + return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? + JS_ATOM_true : JS_ATOM_false); + case JS_TAG_NULL: + return JS_AtomToString(ctx, JS_ATOM_null); + case JS_TAG_UNDEFINED: + return JS_AtomToString(ctx, JS_ATOM_undefined); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_OBJECT: + if (flags & JS_TO_STRING_NO_SIDE_EFFECTS) { + return js_new_string8(ctx, "{}"); + } else { + JSValue val1, ret; + val1 = JS_ToPrimitive(ctx, val, HINT_STRING); + if (JS_IsException(val1)) + return val1; + ret = JS_ToStringInternal(ctx, val1, flags); + JS_FreeValue(ctx, val1); + return ret; + } + break; + case JS_TAG_FUNCTION_BYTECODE: + return js_new_string8(ctx, "[function bytecode]"); + case JS_TAG_SYMBOL: + if (flags & JS_TO_STRING_IS_PROPERTY_KEY) { + return js_dup(val); + } else { + return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); + } + case JS_TAG_FLOAT64: + return js_dtoa2(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0, + JS_DTOA_FORMAT_FREE); + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + return js_bigint_to_string(ctx, val); + case JS_TAG_UNINITIALIZED: + return js_new_string8(ctx, "[uninitialized]"); + default: + return js_new_string8(ctx, "[unsupported type]"); + } +} + +JSValue JS_ToString(JSContext *ctx, JSValueConst val) +{ + return JS_ToStringInternal(ctx, val, /*flags*/0); +} + +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val) +{ + JSValue ret; + ret = JS_ToString(ctx, val); + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val) +{ + if (JS_IsUndefined(val) || JS_IsNull(val)) + return JS_ToStringFree(ctx, val); + return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL); +} + +static JSValue JS_ToPropertyKeyInternal(JSContext *ctx, JSValueConst val, + int flags) +{ + return JS_ToStringInternal(ctx, val, flags | JS_TO_STRING_IS_PROPERTY_KEY); +} + +JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val) +{ + return JS_ToPropertyKeyInternal(ctx, val, /*flags*/0); +} + +static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValueConst val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return JS_ThrowTypeError(ctx, "null or undefined are forbidden"); + return JS_ToString(ctx, val); +} + +static JSValue JS_ToQuotedString(JSContext *ctx, JSValueConst val1) +{ + JSValue val; + JSString *p; + int i; + uint32_t c; + StringBuffer b_s, *b = &b_s; + char buf[16]; + + val = JS_ToStringCheckObject(ctx, val1); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_STRING(val); + + if (string_buffer_init(ctx, b, p->len + 2)) + goto fail; + + if (string_buffer_putc8(b, '\"')) + goto fail; + for(i = 0; i < p->len; ) { + c = string_getc(p, &i); + switch(c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\"': + case '\\': + quote: + if (string_buffer_putc8(b, '\\')) + goto fail; + if (string_buffer_putc8(b, c)) + goto fail; + break; + default: + if (c < 32 || is_surrogate(c)) { + snprintf(buf, sizeof(buf), "\\u%04x", c); + if (string_buffer_write8(b, (uint8_t*)buf, 6)) + goto fail; + } else { + if (string_buffer_putc(b, c)) + goto fail; + } + break; + } + } + if (string_buffer_putc8(b, '\"')) + goto fail; + JS_FreeValue(ctx, val); + return string_buffer_end(b); + fail: + JS_FreeValue(ctx, val); + string_buffer_free(b); + return JS_EXCEPTION; +} + +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) +{ + printf("%14s %4s %4s %14s %10s %s\n", + "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); +} + +/* for debug only: dump an object without side effect */ +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) +{ + uint32_t i; + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + JSShape *sh; + JSShapeProperty *prs; + JSProperty *pr; + bool is_first = true; + + /* XXX: should encode atoms with special characters */ + sh = p->shape; /* the shape can be NULL while freeing an object */ + printf("%14p %4d ", + (void *)p, + p->header.ref_count); + if (sh) { + printf("%3d%c %14p ", + sh->header.ref_count, + " *"[sh->is_hashed], + (void *)sh->proto); + } else { + printf("%3s %14s ", "-", "-"); + } + printf("%10s ", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name)); + if (p->is_exotic && p->fast_array) { + printf("[ "); + for(i = 0; i < p->u.array.count; i++) { + if (i != 0) + printf(", "); + switch (p->class_id) { + case JS_CLASS_ARRAY: + case JS_CLASS_ARGUMENTS: + JS_DumpValue(rt, p->u.array.u.values[i]); + break; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT16_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + { + int size = 1 << typed_array_size_log2(p->class_id); + const uint8_t *b = p->u.array.u.uint8_ptr + i * size; + while (size-- > 0) + printf("%02X", *b++); + } + break; + } + } + printf(" ] "); + } + + if (sh) { + printf("{ "); + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->atom != JS_ATOM_NULL) { + pr = &p->prop[i]; + if (!is_first) + printf(", "); + printf("%s: ", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + printf("[getset %p %p]", (void *)pr->u.getset.getter, + (void *)pr->u.getset.setter); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + printf("[varref %p]", (void *)pr->u.var_ref); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + printf("[autoinit %p %d %p]", + (void *)js_autoinit_get_realm(pr), + js_autoinit_get_id(pr), + (void *)pr->u.init.opaque); + } else { + JS_DumpValue(rt, pr->u.value); + } + is_first = false; + } + } + printf(" }"); + } + + if (js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b = p->u.func.function_bytecode; + JSVarRef **var_refs; + if (b->closure_var_count) { + var_refs = p->u.func.var_refs; + printf(" Closure:"); + for(i = 0; i < b->closure_var_count; i++) { + printf(" "); + JS_DumpValue(rt, var_refs[i]->value); + } + if (p->u.func.home_object) { + printf(" HomeObject: "); + JS_DumpValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); + } + } + } + printf("\n"); +} + +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) +{ + if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + JS_DumpObject(rt, (JSObject *)p); + } else { + printf("%14p %4d ", + (void *)p, + p->ref_count); + switch(p->gc_obj_type) { + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + printf("[function bytecode]"); + break; + case JS_GC_OBJ_TYPE_SHAPE: + printf("[shape]"); + break; + case JS_GC_OBJ_TYPE_VAR_REF: + printf("[var_ref]"); + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + printf("[async_function]"); + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + printf("[js_context]"); + break; + default: + printf("[unknown %d]", p->gc_obj_type); + break; + } + printf("\n"); + } +} + +static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValueConst val) +{ + uint32_t tag = JS_VALUE_GET_NORM_TAG(val); + const char *str; + + switch(tag) { + case JS_TAG_INT: + printf("%d", JS_VALUE_GET_INT(val)); + break; + case JS_TAG_BOOL: + if (JS_VALUE_GET_BOOL(val)) + str = "true"; + else + str = "false"; + goto print_str; + case JS_TAG_NULL: + str = "null"; + goto print_str; + case JS_TAG_EXCEPTION: + str = "exception"; + goto print_str; + case JS_TAG_UNINITIALIZED: + str = "uninitialized"; + goto print_str; + case JS_TAG_UNDEFINED: + str = "undefined"; + print_str: + printf("%s", str); + break; + case JS_TAG_FLOAT64: + printf("%.14g", JS_VALUE_GET_FLOAT64(val)); + break; + case JS_TAG_SHORT_BIG_INT: + printf("%" PRId64 "n", (int64_t)JS_VALUE_GET_SHORT_BIG_INT(val)); + break; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + int sgn, i; + /* In order to avoid allocations we just dump the limbs */ + sgn = js_bigint_sign(p); + if (sgn) + printf("BigInt.asIntN(%d,", p->len * JS_LIMB_BITS); + printf("0x"); + for(i = p->len - 1; i >= 0; i--) { + if (i != p->len - 1) + printf("_"); + printf("%08x", p->tab[i]); + } + printf("n"); + if (sgn) + printf(")"); + } + break; + case JS_TAG_STRING: + { + JSString *p; + p = JS_VALUE_GET_STRING(val); + JS_DumpString(rt, p); + } + break; + case JS_TAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); + char buf[ATOM_GET_STR_BUF_SIZE]; + if (b->func_name) { + printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); + } else { + printf("[bytecode (anonymous)]"); + } + } + break; + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAtom atom = rt->class_array[p->class_id].class_name; + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + printf("[%s %p]", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + printf("Symbol(%s)", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); + } + break; + case JS_TAG_MODULE: + printf("[module]"); + break; + default: + printf("[unknown tag %d]", tag); + break; + } +} + +bool JS_IsArray(JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(val); + return p->class_id == JS_CLASS_ARRAY; + } + return false; +} + +/* return -1 if exception (proxy case) or true/false */ +static int js_is_array(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(val); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_isArray(ctx, val); + else + return p->class_id == JS_CLASS_ARRAY; + } else { + return false; + } +} + +static double js_math_pow(double a, double b) +{ + double d; + + if (unlikely(!isfinite(b)) && fabs(a) == 1) { + /* not compatible with IEEE 754 */ + d = NAN; + } else { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + d = pow(a, b); + JS_X87_FPCW_RESTORE(fpcw); + } + return d; +} + +JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) +{ + if (v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX) { + return __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *p; + p = js_bigint_new_si64(ctx, v); + if (!p) + return JS_EXCEPTION; + return JS_MKPTR(JS_TAG_BIG_INT, p); + } +} + +JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) +{ + if (v <= JS_SHORT_BIG_INT_MAX) { + return __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *p; + p = js_bigint_new_ui64(ctx, v); + if (!p) + return JS_EXCEPTION; + return JS_MKPTR(JS_TAG_BIG_INT, p); + } +} + +/* return NaN if bad bigint literal */ +static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) +{ + const char *str, *p; + size_t len; + int flags; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + val = JS_NewBigInt64(ctx, 0); + } else { + flags = ATOD_INT_ONLY | ATOD_ACCEPT_BIN_OCT | ATOD_TYPE_BIG_INT; + val = js_atof(ctx, p, &p, 0, flags); + p += skip_spaces(p); + if (!JS_IsException(val)) { + if ((p - str) != len) { + JS_FreeValue(ctx, val); + val = JS_NAN; + } + } + } + JS_FreeCString(ctx, str); + return val; +} + +static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val) +{ + val = JS_StringToBigInt(ctx, val); + if (JS_VALUE_IS_NAN(val)) + return JS_ThrowSyntaxError(ctx, "invalid BigInt literal"); + return val; +} + +/* JS Numbers are not allowed */ +static JSValue JS_ToBigIntFree(JSContext *ctx, JSValue val) +{ + uint32_t tag; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + break; + case JS_TAG_INT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + case JS_TAG_FLOAT64: + goto fail; + case JS_TAG_BOOL: + val = __JS_NewShortBigInt(ctx, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_STRING: + val = JS_StringToBigIntErr(ctx, val); + if (JS_IsException(val)) + return val; + goto redo; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return val; + goto redo; + default: + fail: + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert to bigint"); + } + return val; +} + +static JSValue JS_ToBigInt(JSContext *ctx, JSValueConst val) +{ + return JS_ToBigIntFree(ctx, js_dup(val)); +} + +/* XXX: merge with JS_ToInt64Free with a specific flag */ +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint64_t res; + + val = JS_ToBigIntFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + if (JS_VALUE_GET_TAG(val) == JS_TAG_SHORT_BIG_INT) { + res = JS_VALUE_GET_SHORT_BIG_INT(val); + } else { + JSBigInt *p = JS_VALUE_GET_PTR(val); + /* return the value mod 2^64 */ + res = p->tab[0]; + if (p->len >= 2) + res |= (uint64_t)p->tab[1] << 32; + JS_FreeValue(ctx, val); + } + *pres = res; + return 0; +} + +int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToBigInt64Free(ctx, pres, js_dup(val)); +} + +int JS_ToBigUint64(JSContext *ctx, uint64_t *pres, JSValueConst val) +{ + return JS_ToBigInt64Free(ctx, (int64_t *)pres, js_dup(val)); +} + +static no_inline __exception int js_unary_arith_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1; + int v; + uint32_t tag; + JSBigIntBuf buf1; + JSBigInt *p1; + + op1 = sp[-1]; + /* fast path for float64 */ + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) + goto handle_float64; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) + goto exception; + tag = JS_VALUE_GET_TAG(op1); + switch(tag) { + case JS_TAG_INT: + { + int64_t v64; + v64 = JS_VALUE_GET_INT(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + v64 += v; + break; + case OP_plus: + break; + case OP_neg: + if (v64 == 0) { + sp[-1] = js_float64(-0.0); + return 0; + } else { + v64 = -v64; + } + break; + default: + abort(); + } + sp[-1] = js_int64(v64); + } + break; + case JS_TAG_SHORT_BIG_INT: + { + int64_t v; + v = JS_VALUE_GET_SHORT_BIG_INT(op1); + switch(op) { + case OP_plus: + JS_ThrowTypeError(ctx, "bigint argument with unary +"); + goto exception; + case OP_inc: + if (v == JS_SHORT_BIG_INT_MAX) + goto bigint_slow_case; + sp[-1] = __JS_NewShortBigInt(ctx, v + 1); + break; + case OP_dec: + if (v == JS_SHORT_BIG_INT_MIN) + goto bigint_slow_case; + sp[-1] = __JS_NewShortBigInt(ctx, v - 1); + break; + case OP_neg: + v = JS_VALUE_GET_SHORT_BIG_INT(op1); + if (v == JS_SHORT_BIG_INT_MIN) { + bigint_slow_case: + p1 = js_bigint_set_short(&buf1, op1); + goto bigint_slow_case1; + } + sp[-1] = __JS_NewShortBigInt(ctx, -v); + break; + default: + abort(); + } + } + break; + case JS_TAG_BIG_INT: + { + JSBigInt *r; + p1 = JS_VALUE_GET_PTR(op1); + bigint_slow_case1: + switch(op) { + case OP_plus: + JS_ThrowTypeError(ctx, "bigint argument with unary +"); + JS_FreeValue(ctx, op1); + goto exception; + case OP_inc: + case OP_dec: + { + JSBigIntBuf buf2; + JSBigInt *p2; + p2 = js_bigint_set_si(&buf2, 2 * (op - OP_dec) - 1); + r = js_bigint_add(ctx, p1, p2, 0); + } + break; + case OP_neg: + r = js_bigint_neg(ctx, p1); + break; + case OP_not: + r = js_bigint_not(ctx, p1); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + if (!r) + goto exception; + sp[-1] = JS_CompactBigInt(ctx, r); + } + break; + default: + handle_float64: + { + double d; + d = JS_VALUE_GET_FLOAT64(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + d += v; + break; + case OP_plus: + break; + case OP_neg: + d = -d; + break; + default: + abort(); + } + sp[-1] = js_float64(d); + } + break; + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static __exception int js_post_inc_slow(JSContext *ctx, + JSValue *sp, OPCodeEnum op) +{ + JSValue op1; + + /* XXX: allow custom operators */ + op1 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + sp[-1] = JS_UNDEFINED; + return -1; + } + sp[-1] = op1; + sp[0] = js_dup(op1); + return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec); +} + +static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1; + + op1 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) + goto exception; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) { + sp[-1] = __JS_NewShortBigInt(ctx, ~JS_VALUE_GET_SHORT_BIG_INT(op1)); + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { + JSBigInt *r; + r = js_bigint_not(ctx, JS_VALUE_GET_PTR(op1)); + JS_FreeValue(ctx, op1); + if (!r) + goto exception; + sp[-1] = JS_CompactBigInt(ctx, r); + } else { + int32_t v1; + if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) + goto exception; + sp[-1] = js_int32(~v1); + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + double d1, d2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float operations */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + goto handle_float64; + } + /* fast path for short big int operations */ + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2; + js_sdlimb_t v; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + switch(op) { + case OP_sub: + v = (js_sdlimb_t)v1 - (js_sdlimb_t)v2; + break; + case OP_mul: + v = (js_sdlimb_t)v1 * (js_sdlimb_t)v2; + break; + case OP_div: + if (v2 == 0 || + ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && + v2 == -1)) { + goto slow_big_int; + } + sp[-2] = __JS_NewShortBigInt(ctx, v1 / v2); + return 0; + case OP_mod: + if (v2 == 0 || + ((js_limb_t)v1 == (js_limb_t)1 << (JS_LIMB_BITS - 1) && + v2 == -1)) { + goto slow_big_int; + } + sp[-2] = __JS_NewShortBigInt(ctx, v1 % v2); + return 0; + case OP_pow: + goto slow_big_int; + default: + abort(); + } + if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { + sp[-2] = __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *r = js_bigint_new_di(ctx, v); + if (!r) + goto exception; + sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); + } + return 0; + } + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + switch(op) { + case OP_sub: + v = (int64_t)v1 - (int64_t)v2; + break; + case OP_mul: + v = (int64_t)v1 * (int64_t)v2; + if (v == 0 && (v1 | v2) < 0) { + sp[-2] = js_float64(-0.0); + return 0; + } + break; + case OP_div: + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_number((double)v1 / (double)v2); + JS_X87_FPCW_RESTORE(fpcw); + return 0; + case OP_mod: + if (v1 < 0 || v2 <= 0) { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_number(fmod(v1, v2)); + JS_X87_FPCW_RESTORE(fpcw); + return 0; + } else { + v = (int64_t)v1 % (int64_t)v2; + } + break; + case OP_pow: + sp[-2] = js_number(js_math_pow(v1, v2)); + return 0; + default: + abort(); + } + sp[-2] = js_int64(v); + } else if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_BIG_INT) && + (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + slow_big_int: + /* bigint result */ + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + switch(op) { + case OP_add: + r = js_bigint_add(ctx, p1, p2, 0); + break; + case OP_sub: + r = js_bigint_add(ctx, p1, p2, 1); + break; + case OP_mul: + r = js_bigint_mul(ctx, p1, p2); + break; + case OP_div: + r = js_bigint_divrem(ctx, p1, p2, false); + break; + case OP_mod: + r = js_bigint_divrem(ctx, p1, p2, true); + break; + case OP_pow: + r = js_bigint_pow(ctx, p1, p2); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp[-2] = JS_CompactBigInt(ctx, r); + } else { + double dr; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + handle_float64: + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + switch(op) { + case OP_sub: + dr = d1 - d2; + break; + case OP_mul: + dr = d1 * d2; + break; + case OP_div: + dr = d1 / d2; + break; + case OP_mod: + dr = fmod(d1, d2); + break; + case OP_pow: + dr = js_math_pow(d1, d2); + break; + default: + abort(); + } + JS_X87_FPCW_RESTORE(fpcw); + sp[-2] = js_float64(dr); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float64 */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + double d1, d2; + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + sp[-2] = js_float64(d1 + d2); + return 0; + } + /* fast path for short bigint */ + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2; + js_sdlimb_t v; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + v = (js_sdlimb_t)v1 + (js_sdlimb_t)v2; + if (likely(v >= JS_SHORT_BIG_INT_MIN && v <= JS_SHORT_BIG_INT_MAX)) { + sp[-2] = __JS_NewShortBigInt(ctx, v); + } else { + JSBigInt *r = js_bigint_new_di(ctx, v); + if (!r) + goto exception; + sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); + } + return 0; + } + + if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + } + + if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { + sp[-2] = JS_ConcatString(ctx, op1, op2); + if (JS_IsException(sp[-2])) + goto exception; + return 0; + } + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + v = (int64_t)v1 + (int64_t)v2; + sp[-2] = js_int64(v); + } else if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + /* bigint result */ + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + r = js_bigint_add(ctx, p1, p2, 0); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp[-2] = JS_CompactBigInt(ctx, r); + } else { + double d1, d2; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_float64(d1 + d2); + JS_X87_FPCW_RESTORE(fpcw); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_binary_logic_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_SHORT_BIG_INT && tag2 == JS_TAG_SHORT_BIG_INT) { + js_slimb_t v1, v2, v; + js_sdlimb_t vd; + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + /* bigint fast path */ + switch(op) { + case OP_and: + v = v1 & v2; + break; + case OP_or: + v = v1 | v2; + break; + case OP_xor: + v = v1 ^ v2; + break; + case OP_sar: + if (v2 > (JS_LIMB_BITS - 1)) { + goto slow_big_int; + } else if (v2 < 0) { + if (v2 < -(JS_LIMB_BITS - 1)) + goto slow_big_int; + v2 = -v2; + goto bigint_shl; + } + bigint_sar: + v = v1 >> v2; + break; + case OP_shl: + if (v2 > (JS_LIMB_BITS - 1)) { + goto slow_big_int; + } else if (v2 < 0) { + if (v2 < -(JS_LIMB_BITS - 1)) + goto slow_big_int; + v2 = -v2; + goto bigint_sar; + } + bigint_shl: + vd = (js_dlimb_t)v1 << v2; + if (likely(vd >= JS_SHORT_BIG_INT_MIN && + vd <= JS_SHORT_BIG_INT_MAX)) { + v = vd; + } else { + JSBigInt *r = js_bigint_new_di(ctx, vd); + if (!r) + goto exception; + sp[-2] = JS_MKPTR(JS_TAG_BIG_INT, r); + return 0; + } + break; + default: + abort(); + } + sp[-2] = __JS_NewShortBigInt(ctx, v); + return 0; + } + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + + tag1 = JS_VALUE_GET_TAG(op1); + tag2 = JS_VALUE_GET_TAG(op2); + if ((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + (tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT)) { + JSBigInt *p1, *p2, *r; + JSBigIntBuf buf1, buf2; + slow_big_int: + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + switch(op) { + case OP_and: + case OP_or: + case OP_xor: + r = js_bigint_logic(ctx, p1, p2, op); + break; + case OP_shl: + case OP_sar: + { + js_slimb_t shift; + shift = js_bigint_get_si_sat(p2); + if (shift > INT32_MAX) + shift = INT32_MAX; + else if (shift < -INT32_MAX) + shift = -INT32_MAX; + if (op == OP_sar) + shift = -shift; + if (shift >= 0) + r = js_bigint_shl(ctx, p1, shift); + else + r = js_bigint_shr(ctx, p1, -shift); + } + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (!r) + goto exception; + sp[-2] = JS_CompactBigInt(ctx, r); + } else { + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) + goto exception; + switch(op) { + case OP_shl: + r = v1 << (v2 & 0x1f); + break; + case OP_sar: + r = (int)v1 >> (v2 & 0x1f); + break; + case OP_and: + r = v1 & v2; + break; + case OP_or: + r = v1 | v2; + break; + case OP_xor: + r = v1 ^ v2; + break; + default: + abort(); + } + sp[-2] = js_int32(r); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +/* op1 must be a bigint or int. */ +static JSBigInt *JS_ToBigIntBuf(JSContext *ctx, JSBigIntBuf *buf1, + JSValue op1) +{ + JSBigInt *p1; + + switch(JS_VALUE_GET_TAG(op1)) { + case JS_TAG_INT: + p1 = js_bigint_set_si(buf1, JS_VALUE_GET_INT(op1)); + break; + case JS_TAG_SHORT_BIG_INT: + p1 = js_bigint_set_short(buf1, op1); + break; + case JS_TAG_BIG_INT: + p1 = JS_VALUE_GET_PTR(op1); + break; + default: + abort(); + } + return p1; +} + +/* op1 and op2 must be numeric types and at least one must be a + bigint. No exception is generated. */ +static int js_compare_bigint(JSContext *ctx, OPCodeEnum op, + JSValue op1, JSValue op2) +{ + int res, val, tag1, tag2; + JSBigIntBuf buf1, buf2; + JSBigInt *p1, *p2; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + if ((tag1 == JS_TAG_SHORT_BIG_INT || tag1 == JS_TAG_INT) && + (tag2 == JS_TAG_SHORT_BIG_INT || tag2 == JS_TAG_INT)) { + /* fast path */ + js_slimb_t v1, v2; + if (tag1 == JS_TAG_INT) + v1 = JS_VALUE_GET_INT(op1); + else + v1 = JS_VALUE_GET_SHORT_BIG_INT(op1); + if (tag2 == JS_TAG_INT) + v2 = JS_VALUE_GET_INT(op2); + else + v2 = JS_VALUE_GET_SHORT_BIG_INT(op2); + val = (v1 > v2) - (v1 < v2); + } else { + if (tag1 == JS_TAG_FLOAT64) { + p2 = JS_ToBigIntBuf(ctx, &buf2, op2); + val = js_bigint_float64_cmp(ctx, p2, JS_VALUE_GET_FLOAT64(op1)); + if (val == 2) + goto unordered; + val = -val; + } else if (tag2 == JS_TAG_FLOAT64) { + p1 = JS_ToBigIntBuf(ctx, &buf1, op1); + val = js_bigint_float64_cmp(ctx, p1, JS_VALUE_GET_FLOAT64(op2)); + if (val == 2) { + unordered: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return false; + } + } else { + p1 = JS_ToBigIntBuf(ctx, &buf1, op1); + p2 = JS_ToBigIntBuf(ctx, &buf2, op2); + val = js_bigint_cmp(ctx, p1, p2); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } + + switch(op) { + case OP_lt: + res = val < 0; + break; + case OP_lte: + res = val <= 0; + break; + case OP_gt: + res = val > 0; + break; + case OP_gte: + res = val >= 0; + break; + case OP_eq: + res = val == 0; + break; + default: + abort(); + } + return res; +} + +static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + JSString *p1, *p2; + p1 = JS_VALUE_GET_STRING(op1); + p2 = JS_VALUE_GET_STRING(op2); + res = js_string_compare(p1, p2); + switch(op) { + case OP_lt: + res = (res < 0); + break; + case OP_lte: + res = (res <= 0); + break; + case OP_gt: + res = (res > 0); + break; + default: + case OP_gte: + res = (res >= 0); + break; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) && + (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) { + /* fast path for float64/int */ + goto float64_compare; + } else { + if ((((tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT) && + tag2 == JS_TAG_STRING) || + ((tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) && + tag1 == JS_TAG_STRING))) { + if (tag1 == JS_TAG_STRING) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) + goto invalid_bigint_string; + } + if (tag2 == JS_TAG_STRING) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = false; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || + tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { + res = js_compare_bigint(ctx, op, op1, op2); + } else { + double d1, d2; + + float64_compare: + /* can use floating point comparison */ + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + switch(op) { + case OP_lt: + res = (d1 < d2); /* if NaN return false */ + break; + case OP_lte: + res = (d1 <= d2); /* if NaN return false */ + break; + case OP_gt: + res = (d1 > d2); /* if NaN return false */ + break; + default: + case OP_gte: + res = (d1 >= d2); /* if NaN return false */ + break; + } + } + } + done: + sp[-2] = js_bool(res); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static bool tag_is_number(uint32_t tag) +{ + return (tag == JS_TAG_INT || + tag == JS_TAG_FLOAT64 || + tag == JS_TAG_BIG_INT || tag == JS_TAG_SHORT_BIG_INT); +} + +static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, + bool is_neq) +{ + JSValue op1, op2; + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + redo: + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + if (tag_is_number(tag1) && tag_is_number(tag2)) { + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + } else if ((tag1 == JS_TAG_FLOAT64 && + (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) || + (tag2 == JS_TAG_FLOAT64 && + (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) { + double d1, d2; + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + res = (d1 == d2); + } else { + res = js_compare_bigint(ctx, OP_eq, op1, op2); + if (res < 0) + goto exception; + } + } else if (tag1 == tag2) { + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || + (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { + res = true; + } else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) || + (tag2 == JS_TAG_STRING && tag_is_number(tag1))) { + + if (tag1 == JS_TAG_BIG_INT || tag1 == JS_TAG_SHORT_BIG_INT || + tag2 == JS_TAG_BIG_INT || tag2 == JS_TAG_SHORT_BIG_INT) { + if (tag1 == JS_TAG_STRING) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op1) != JS_TAG_SHORT_BIG_INT) + goto invalid_bigint_string; + } + if (tag2 == JS_TAG_STRING) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT && + JS_VALUE_GET_TAG(op2) != JS_TAG_SHORT_BIG_INT ) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = false; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + res = js_strict_eq(ctx, op1, op2); + } else if (tag1 == JS_TAG_BOOL) { + op1 = js_int32(JS_VALUE_GET_INT(op1)); + goto redo; + } else if (tag2 == JS_TAG_BOOL) { + op2 = js_int32(JS_VALUE_GET_INT(op2)); + goto redo; + } else if ((tag1 == JS_TAG_OBJECT && + (tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) || + (tag2 == JS_TAG_OBJECT && + (tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) { + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + goto redo; + } else { + /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ + if ((JS_IsHTMLDDA(ctx, op1) && + (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || + (JS_IsHTMLDDA(ctx, op2) && + (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { + res = true; + } else { + res = false; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } + done: + sp[-2] = js_bool(res ^ is_neq); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + + if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) { + JS_ThrowTypeError(ctx, "BigInt operands are forbidden for >>>"); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + goto exception; + } + /* cannot give an exception */ + JS_ToUint32Free(ctx, &v1, op1); + JS_ToUint32Free(ctx, &v2, op2); + r = v1 >> (v2 & 0x1f); + sp[-2] = js_uint32(r); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static bool js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, + JSStrictEqModeEnum eq_mode) +{ + bool res; + int tag1, tag2; + double d1, d2; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + switch(tag1) { + case JS_TAG_BOOL: + if (tag1 != tag2) { + res = false; + } else { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + goto done_no_free; + } + break; + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = (tag1 == tag2); + break; + case JS_TAG_STRING: + { + JSString *p1, *p2; + if (tag1 != tag2) { + res = false; + } else { + p1 = JS_VALUE_GET_STRING(op1); + p2 = JS_VALUE_GET_STRING(op2); + res = js_string_eq(p1, p2); + } + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p1, *p2; + if (tag1 != tag2) { + res = false; + } else { + p1 = JS_VALUE_GET_PTR(op1); + p2 = JS_VALUE_GET_PTR(op2); + res = (p1 == p2); + } + } + break; + case JS_TAG_OBJECT: + if (tag1 != tag2) + res = false; + else + res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); + break; + case JS_TAG_INT: + d1 = JS_VALUE_GET_INT(op1); + if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + goto number_test; + } else if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + goto number_test; + } else { + res = false; + } + break; + case JS_TAG_FLOAT64: + d1 = JS_VALUE_GET_FLOAT64(op1); + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + } else { + res = false; + break; + } + number_test: + if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { + JSFloat64Union u1, u2; + /* NaN is not always normalized, so this test is necessary */ + if (isnan(d1) || isnan(d2)) { + res = isnan(d1) == isnan(d2); + } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) { + res = (d1 == d2); /* +0 == -0 */ + } else { + u1.d = d1; + u2.d = d2; + res = (u1.u64 == u2.u64); /* +0 != -0 */ + } + } else { + res = (d1 == d2); /* if NaN return false and +0 == -0 */ + } + goto done_no_free; + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + { + JSBigIntBuf buf1, buf2; + JSBigInt *p1, *p2; + + if (tag2 != JS_TAG_SHORT_BIG_INT && + tag2 != JS_TAG_BIG_INT) { + res = false; + break; + } + + if (JS_VALUE_GET_TAG(op1) == JS_TAG_SHORT_BIG_INT) + p1 = js_bigint_set_short(&buf1, op1); + else + p1 = JS_VALUE_GET_PTR(op1); + if (JS_VALUE_GET_TAG(op2) == JS_TAG_SHORT_BIG_INT) + p2 = js_bigint_set_short(&buf2, op2); + else + p2 = JS_VALUE_GET_PTR(op2); + res = (js_bigint_cmp(ctx, p1, p2) == 0); + } + break; + default: + res = false; + break; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + done_no_free: + return res; +} + +static bool js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2) +{ + return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); +} + +static bool js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE); +} + +static bool js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE_ZERO); +} + +static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, + bool is_neq) +{ + bool res; + res = js_strict_eq(ctx, sp[-2], sp[-1]); + sp[-2] = js_bool(res ^ is_neq); + return 0; +} + +static __exception int js_operator_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + + if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + atom = JS_ValueToAtom(ctx, op1); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_HasProperty(ctx, op2, atom); + JS_FreeAtom(ctx, atom); + if (ret < 0) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static __exception int js_operator_private_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + int ret; + op1 = sp[-2]; /* object */ + op2 = sp[-1]; /* field name or method function */ + if (JS_VALUE_GET_TAG(op1) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + if (JS_IsObject(op2)) { + /* method: use the brand */ + ret = JS_CheckBrand(ctx, op1, op2); + if (ret < 0) + return -1; + } else { + JSAtom atom; + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + /* field */ + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + p = JS_VALUE_GET_OBJ(op1); + prs = find_own_property(&pr, p, atom); + JS_FreeAtom(ctx, atom); + ret = (prs != NULL); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static __exception int js_has_unscopable(JSContext *ctx, JSValue obj, + JSAtom atom) +{ + JSValue arr, val; + int ret; + + arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables); + if (JS_IsException(arr)) + return -1; + ret = 0; + if (JS_IsObject(arr)) { + val = JS_GetProperty(ctx, arr, atom); + ret = JS_ToBoolFree(ctx, val); + } + JS_FreeValue(ctx, arr); + return ret; +} + +static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + ret = JS_IsInstanceOf(ctx, op1, op2); + if (ret < 0) + return ret; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static __exception int js_operator_typeof(JSContext *ctx, JSValue op1) +{ + JSAtom atom; + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(op1); + switch(tag) { + case JS_TAG_SHORT_BIG_INT: + case JS_TAG_BIG_INT: + atom = JS_ATOM_bigint; + break; + case JS_TAG_INT: + case JS_TAG_FLOAT64: + atom = JS_ATOM_number; + break; + case JS_TAG_UNDEFINED: + atom = JS_ATOM_undefined; + break; + case JS_TAG_BOOL: + atom = JS_ATOM_boolean; + break; + case JS_TAG_STRING: + atom = JS_ATOM_string; + break; + case JS_TAG_OBJECT: + { + JSObject *p; + p = JS_VALUE_GET_OBJ(op1); + if (unlikely(p->is_HTMLDDA)) + atom = JS_ATOM_undefined; + else if (JS_IsFunction(ctx, op1)) + atom = JS_ATOM_function; + else + goto obj_type; + } + break; + case JS_TAG_NULL: + obj_type: + atom = JS_ATOM_object; + break; + case JS_TAG_SYMBOL: + atom = JS_ATOM_symbol; + break; + default: + atom = JS_ATOM_unknown; + break; + } + return atom; +} + +static __exception int js_operator_delete(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + if (unlikely(ret < 0)) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (!b || b->is_strict_mode || !b->has_prototype) { + return JS_ThrowTypeError(ctx, "invalid property access"); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_fileName(JSContext *ctx, + JSValueConst this_val) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b) { + return JS_AtomToString(ctx, b->filename); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_int32(JSContext *ctx, + JSValueConst this_val, + int magic) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b) { + int *field = (int *) ((char *)b + magic); + return js_int32(*field); + } + return JS_UNDEFINED; +} + +static int js_arguments_define_own_property(JSContext *ctx, + JSValueConst this_obj, + JSAtom prop, JSValueConst val, + JSValueConst getter, + JSValueConst setter, int flags) +{ + JSObject *p; + uint32_t idx; + p = JS_VALUE_GET_OBJ(this_obj); + /* convert to normal array when redefining an existing numeric field */ + if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) && + idx < p->u.array.count) { + if (convert_fast_array_to_array(ctx, p)) + return -1; + } + /* run the default define own property */ + return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, + flags | JS_PROP_NO_EXOTIC); +} + +static const JSClassExoticMethods js_arguments_exotic_methods = { + .define_own_property = js_arguments_define_own_property, +}; + +static JSValue js_build_arguments(JSContext *ctx, int argc, JSValueConst *argv) +{ + JSValue val, *tab; + JSProperty *pr; + JSObject *p; + int i; + + val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_ARGUMENTS); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* add the length field (cannot fail) */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (!pr) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + pr->u.value = js_int32(argc); + + /* initialize the fast array part */ + tab = NULL; + if (argc > 0) { + tab = js_malloc(ctx, sizeof(tab[0]) * argc); + if (!tab) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + for(i = 0; i < argc; i++) { + tab[i] = js_dup(argv[i]); + } + } + p->u.array.u.values = tab; + p->u.array.count = argc; + + JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, + js_dup(ctx->array_proto_values), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + /* add callee property to throw a TypeError in strict mode */ + JS_DefineProperty(ctx, val, JS_ATOM_callee, JS_UNDEFINED, + ctx->throw_type_error, ctx->throw_type_error, + JS_PROP_HAS_GET | JS_PROP_HAS_SET); + return val; +} + +#define GLOBAL_VAR_OFFSET 0x40000000 +#define ARGUMENT_VAR_OFFSET 0x20000000 + +/* legacy arguments object: add references to the function arguments */ +static JSValue js_build_mapped_arguments(JSContext *ctx, int argc, + JSValueConst *argv, + JSStackFrame *sf, int arg_count) +{ + JSValue val; + JSProperty *pr; + JSObject *p; + int i; + + val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_MAPPED_ARGUMENTS); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* add the length field (cannot fail) */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (!pr) + goto fail; + pr->u.value = js_int32(argc); + + for(i = 0; i < arg_count; i++) { + JSVarRef *var_ref; + var_ref = get_var_ref(ctx, sf, i, true); + if (!var_ref) + goto fail; + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF); + if (!pr) { + free_var_ref(ctx->rt, var_ref); + goto fail; + } + pr->u.var_ref = var_ref; + } + + /* the arguments not mapped to the arguments of the function can + be normal properties */ + for(i = arg_count; i < argc; i++) { + if (JS_DefinePropertyValueUint32(ctx, val, i, + js_dup(argv[i]), + JS_PROP_C_W_E) < 0) + goto fail; + } + + JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, + js_dup(ctx->array_proto_values), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + /* callee returns this function in non strict mode */ + JS_DefinePropertyValue(ctx, val, JS_ATOM_callee, + js_dup(ctx->rt->current_stack_frame->cur_func), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + return val; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) +{ + JSObject *p; + JSPropertyEnum *tab_atom; + int i; + JSValue enum_obj, obj1; + JSForInIterator *it; + uint32_t tag, tab_atom_count; + + tag = JS_VALUE_GET_TAG(obj); + if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) { + obj = JS_ToObjectFree(ctx, obj); + } + + it = js_malloc(ctx, sizeof(*it)); + if (!it) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR); + if (JS_IsException(enum_obj)) { + js_free(ctx, it); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + it->is_array = false; + it->obj = obj; + it->idx = 0; + p = JS_VALUE_GET_OBJ(enum_obj); + p->u.for_in_iterator = it; + + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return enum_obj; + + /* fast path: assume no enumerable properties in the prototype chain */ + obj1 = js_dup(obj); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsNull(obj1)) + break; + if (JS_IsException(obj1)) + goto fail; + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(obj1), + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + if (tab_atom_count != 0) { + JS_FreeValue(ctx, obj1); + goto slow_path; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + } + + p = JS_VALUE_GET_OBJ(obj); + + if (p->fast_array) { + JSShape *sh; + JSShapeProperty *prs; + /* check that there are no enumerable normal fields */ + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->flags & JS_PROP_ENUMERABLE) + goto normal_case; + } + /* for fast arrays, we only store the number of elements */ + it->is_array = true; + it->array_length = p->u.array.count; + } else { + normal_case: + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) + goto fail; + for(i = 0; i < tab_atom_count; i++) { + JS_SetPropertyInternal(ctx, enum_obj, tab_atom[i].atom, JS_NULL, 0); + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + } + return enum_obj; + + slow_path: + /* non enumerable properties hide the enumerables ones in the + prototype chain */ + obj1 = js_dup(obj); + for(;;) { + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(obj1), + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + for(i = 0; i < tab_atom_count; i++) { + JS_DefinePropertyValue(ctx, enum_obj, tab_atom[i].atom, JS_NULL, + (tab_atom[i].is_enumerable ? + JS_PROP_ENUMERABLE : 0)); + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsNull(obj1)) + break; + if (JS_IsException(obj1)) + goto fail; + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + } + return enum_obj; + + fail: + JS_FreeValue(ctx, enum_obj); + return JS_EXCEPTION; +} + +/* obj -> enum_obj */ +static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) +{ + sp[-1] = build_for_in_iterator(ctx, sp[-1]); + if (JS_IsException(sp[-1])) + return -1; + return 0; +} + +/* enum_obj -> enum_obj value done */ +static __exception int js_for_in_next(JSContext *ctx, JSValue *sp) +{ + JSValue enum_obj; + JSObject *p; + JSAtom prop; + JSForInIterator *it; + int ret; + + enum_obj = sp[-1]; + /* fail safe */ + if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT) + goto done; + p = JS_VALUE_GET_OBJ(enum_obj); + if (p->class_id != JS_CLASS_FOR_IN_ITERATOR) + goto done; + it = p->u.for_in_iterator; + + for(;;) { + if (it->is_array) { + if (it->idx >= it->array_length) + goto done; + prop = __JS_AtomFromUInt32(it->idx); + it->idx++; + } else { + JSShape *sh = p->shape; + JSShapeProperty *prs; + if (it->idx >= sh->prop_count) + goto done; + prs = get_shape_prop(sh) + it->idx; + prop = prs->atom; + it->idx++; + if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE)) + continue; + } + // check if the property was deleted unless we're dealing with a proxy + JSValue obj = it->obj; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_PROXY) + break; + } + ret = JS_HasProperty(ctx, obj, prop); + if (ret < 0) + return ret; + if (ret) + break; + } + /* return the property */ + sp[0] = JS_AtomToValue(ctx, prop); + sp[1] = JS_FALSE; + return 0; + done: + /* return the end */ + sp[0] = JS_UNDEFINED; + sp[1] = JS_TRUE; + return 0; +} + +static JSValue JS_GetIterator2(JSContext *ctx, JSValueConst obj, + JSValueConst method) +{ + JSValue enum_obj; + + enum_obj = JS_Call(ctx, method, obj, 0, NULL); + if (JS_IsException(enum_obj)) + return enum_obj; + if (!JS_IsObject(enum_obj)) { + JS_FreeValue(ctx, enum_obj); + return JS_ThrowTypeErrorNotAnObject(ctx); + } + return enum_obj; +} + +static JSValue JS_GetIterator(JSContext *ctx, JSValueConst obj, bool is_async) +{ + JSValue method, ret, sync_iter; + + if (is_async) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator); + if (JS_IsException(method)) + return method; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + sync_iter = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + if (JS_IsException(sync_iter)) + return sync_iter; + ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter); + JS_FreeValue(ctx, sync_iter); + return ret; + } + } else { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + } + if (!JS_IsFunction(ctx, method)) { + JS_FreeValue(ctx, method); + return JS_ThrowTypeError(ctx, "value is not iterable"); + } + ret = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + return ret; +} + +/* return *pdone = 2 if the iterator object is not parsed */ +static JSValue JS_IteratorNext2(JSContext *ctx, JSValueConst enum_obj, + JSValueConst method, + int argc, JSValueConst *argv, int *pdone) +{ + JSValue obj; + + /* fast path for the built-in iterators (avoid creating the + intermediate result object) */ + if (JS_IsObject(method)) { + JSObject *p = JS_VALUE_GET_OBJ(method); + if (p->class_id == JS_CLASS_C_FUNCTION && + p->u.cfunc.cproto == JS_CFUNC_iterator_next) { + JSCFunctionType func; + JSValueConst args[1]; + + /* in case the function expects one argument */ + if (argc == 0) { + args[0] = JS_UNDEFINED; + argv = args; + } + func = p->u.cfunc.c_function; + return func.iterator_next(ctx, enum_obj, argc, argv, + pdone, p->u.cfunc.magic); + } + } + obj = JS_Call(ctx, method, enum_obj, argc, argv); + if (JS_IsException(obj)) + goto fail; + if (!JS_IsObject(obj)) { + JS_FreeValue(ctx, obj); + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto fail; + } + *pdone = 2; + return obj; + fail: + *pdone = false; + return JS_EXCEPTION; +} + +static JSValue JS_IteratorNext(JSContext *ctx, JSValueConst enum_obj, + JSValueConst method, + int argc, JSValueConst *argv, int *pdone) +{ + JSValue obj, value, done_val; + int done; + + obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done); + if (JS_IsException(obj)) + goto fail; + if (likely(done == 0)) { + *pdone = false; + return obj; + } else if (done != 2) { + JS_FreeValue(ctx, obj); + *pdone = true; + return JS_UNDEFINED; + } else { + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + *pdone = JS_ToBoolFree(ctx, done_val); + value = JS_UNDEFINED; + if (!*pdone) { + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + } + JS_FreeValue(ctx, obj); + return value; + } + fail: + JS_FreeValue(ctx, obj); + *pdone = false; + return JS_EXCEPTION; +} + +/* return < 0 in case of exception */ +static int JS_IteratorClose(JSContext *ctx, JSValueConst enum_obj, + bool is_exception_pending) +{ + JSValue method, ret, ex_obj; + int res; + + if (is_exception_pending) { + ex_obj = ctx->rt->current_exception; + ctx->rt->current_exception = JS_UNINITIALIZED; + res = -1; + } else { + ex_obj = JS_UNDEFINED; + res = 0; + } + method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return); + if (JS_IsException(method)) { + res = -1; + goto done; + } + if (JS_IsUndefined(method) || JS_IsNull(method)) { + goto done; + } + ret = JS_CallFree(ctx, method, enum_obj, 0, NULL); + if (!is_exception_pending) { + if (JS_IsException(ret)) { + res = -1; + } else if (!JS_IsObject(ret)) { + JS_ThrowTypeErrorNotAnObject(ctx); + res = -1; + } + } + JS_FreeValue(ctx, ret); + done: + if (is_exception_pending) { + JS_Throw(ctx, ex_obj); + } + return res; +} + +/* obj -> enum_rec (3 slots) */ +static __exception int js_for_of_start(JSContext *ctx, JSValue *sp, + bool is_async) +{ + JSValue op1, obj, method; + op1 = sp[-1]; + obj = JS_GetIterator(ctx, op1, is_async); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, op1); + sp[-1] = obj; + method = JS_GetProperty(ctx, obj, JS_ATOM_next); + if (JS_IsException(method)) + return -1; + sp[0] = method; + return 0; +} + +/* enum_rec [objs] -> enum_rec [objs] value done. There are 'offset' + objs. If 'done' is true or in case of exception, 'enum_rec' is set + to undefined. If 'done' is true, 'value' is always set to + undefined. */ +static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) +{ + JSValue value = JS_UNDEFINED; + int done = 1; + + if (likely(!JS_IsUndefined(sp[offset]))) { + value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done); + if (JS_IsException(value)) + done = -1; + if (done) { + /* value is JS_UNDEFINED or JS_EXCEPTION */ + /* replace the iteration object with undefined */ + JS_FreeValue(ctx, sp[offset]); + sp[offset] = JS_UNDEFINED; + if (done < 0) { + return -1; + } else { + JS_FreeValue(ctx, value); + value = JS_UNDEFINED; + } + } + } + sp[0] = value; + sp[1] = js_bool(done); + return 0; +} + +static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValue obj, + int *pdone) +{ + JSValue done_val, value; + int done; + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + done = JS_ToBoolFree(ctx, done_val); + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + if (JS_IsException(value)) + goto fail; + *pdone = done; + return value; + fail: + *pdone = false; + return JS_EXCEPTION; +} + +static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) +{ + JSValue obj, value; + int done; + obj = sp[-1]; + if (!JS_IsObject(obj)) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + return -1; + } + value = JS_IteratorGetCompleteValue(ctx, obj, &done); + if (JS_IsException(value)) + return -1; + JS_FreeValue(ctx, obj); + sp[-1] = value; + sp[0] = js_bool(done); + return 0; +} + +static JSValue js_create_iterator_result(JSContext *ctx, + JSValue val, + bool done) +{ + JSValue obj; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, val); + return obj; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value, + val, JS_PROP_C_W_E) < 0) { + goto fail; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done, + js_bool(done), JS_PROP_C_W_E) < 0) { + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int *pdone, int magic); + +static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); + +static bool js_is_fast_array(JSContext *ctx, JSValue obj) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + return true; + } + } + return false; +} + +/* Access an Array's internal JSValue array if available */ +static bool js_get_fast_array(JSContext *ctx, JSValue obj, + JSValue **arrpp, uint32_t *countp) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + *countp = p->u.array.count; + *arrpp = p->u.array.u.values; + return true; + } + } + return false; +} + +static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) +{ + JSValue iterator, enumobj, method, value; + int is_array_iterator; + JSValue *arrp; + uint32_t i, count32, pos; + + if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) { + JS_ThrowInternalError(ctx, "invalid index for append"); + return -1; + } + + pos = JS_VALUE_GET_INT(sp[-2]); + + /* XXX: further optimisations: + - use ctx->array_proto_values? + - check if array_iterator_prototype next method is built-in and + avoid constructing actual iterator object? + - build this into js_for_of_start and use in all `for (x of o)` loops + */ + iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator); + if (JS_IsException(iterator)) + return -1; + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft = { .generic_magic = js_create_array_iterator }; + is_array_iterator = JS_IsCFunction(ctx, iterator, + ft.generic, + JS_ITERATOR_KIND_VALUE); + JS_FreeValue(ctx, iterator); + + enumobj = JS_GetIterator(ctx, sp[-1], false); + if (JS_IsException(enumobj)) + return -1; + method = JS_GetProperty(ctx, enumobj, JS_ATOM_next); + if (JS_IsException(method)) { + JS_FreeValue(ctx, enumobj); + return -1; + } + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft2 = { .iterator_next = js_array_iterator_next }; + if (is_array_iterator + && JS_IsCFunction(ctx, method, ft2.generic, 0) + && js_get_fast_array(ctx, sp[-1], &arrp, &count32)) { + uint32_t len; + if (js_get_length32(ctx, &len, sp[-1])) + goto exception; + /* if len > count32, the elements >= count32 might be read in + the prototypes and might have side effects */ + if (len != count32) + goto general_case; + /* Handle fast arrays explicitly */ + for (i = 0; i < count32; i++) { + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, + js_dup(arrp[i]), JS_PROP_C_W_E) < 0) + goto exception; + } + } else { + general_case: + for (;;) { + int done; + value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done); + if (JS_IsException(value)) + goto exception; + if (done) { + /* value is JS_UNDEFINED */ + break; + } + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0) + goto exception; + } + } + /* Note: could raise an error if too many elements */ + sp[-2] = js_int32(pos); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return 0; + +exception: + JS_IteratorClose(ctx, enumobj, true); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return -1; +} + +static __exception int JS_CopyDataProperties(JSContext *ctx, + JSValue target, + JSValue source, + JSValue excluded, + bool setprop) +{ + JSPropertyEnum *tab_atom; + JSValue val; + uint32_t i, tab_atom_count; + JSObject *p; + JSObject *pexcl = NULL; + int ret, gpn_flags; + JSPropertyDescriptor desc; + bool is_enumerable; + + if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT) + return 0; + + if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT) + pexcl = JS_VALUE_GET_OBJ(excluded); + + p = JS_VALUE_GET_OBJ(source); + + gpn_flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY; + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + /* cannot use JS_GPN_ENUM_ONLY with e.g. proxies because it + introduces a visible change */ + if (em && em->get_own_property_names) { + gpn_flags &= ~JS_GPN_ENUM_ONLY; + } + } + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + gpn_flags)) + return -1; + + for (i = 0; i < tab_atom_count; i++) { + if (pexcl) { + ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom); + if (ret) { + if (ret < 0) + goto exception; + continue; + } + } + if (!(gpn_flags & JS_GPN_ENUM_ONLY)) { + /* test if the property is enumerable */ + ret = JS_GetOwnPropertyInternal(ctx, &desc, p, tab_atom[i].atom); + if (ret < 0) + goto exception; + if (!ret) + continue; + is_enumerable = (desc.flags & JS_PROP_ENUMERABLE) != 0; + js_free_desc(ctx, &desc); + if (!is_enumerable) + continue; + } + val = JS_GetProperty(ctx, source, tab_atom[i].atom); + if (JS_IsException(val)) + goto exception; + if (setprop) + ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val); + else + ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, + JS_PROP_C_W_E); + if (ret < 0) + goto exception; + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + return 0; + exception: + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + return -1; +} + +/* only valid inside C functions */ +static JSValueConst JS_GetActiveFunction(JSContext *ctx) +{ + return ctx->rt->current_stack_frame->cur_func; +} + +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, + int var_idx, bool is_arg) +{ + JSVarRef *var_ref; + struct list_head *el; + JSValue *pvalue; + + if (is_arg) + pvalue = &sf->arg_buf[var_idx]; + else + pvalue = &sf->var_buf[var_idx]; + + list_for_each(el, &sf->var_ref_list) { + var_ref = list_entry(el, JSVarRef, header.link); + if (var_ref->pvalue == pvalue) { + var_ref->header.ref_count++; + return var_ref; + } + } + /* create a new one */ + var_ref = js_malloc(ctx, sizeof(JSVarRef)); + if (!var_ref) + return NULL; + var_ref->header.ref_count = 1; + var_ref->is_detached = false; + list_add_tail(&var_ref->header.link, &sf->var_ref_list); + var_ref->pvalue = pvalue; + var_ref->value = JS_UNDEFINED; + return var_ref; +} + +static JSValue js_closure2(JSContext *ctx, JSValue func_obj, + JSFunctionBytecode *b, + JSVarRef **cur_var_refs, + JSStackFrame *sf) +{ + JSObject *p; + JSVarRef **var_refs; + int i; + + p = JS_VALUE_GET_OBJ(func_obj); + p->u.func.function_bytecode = b; + p->u.func.home_object = NULL; + p->u.func.var_refs = NULL; + if (b->closure_var_count) { + var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count); + if (!var_refs) + goto fail; + p->u.func.var_refs = var_refs; + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + JSVarRef *var_ref; + if (cv->is_local) { + /* reuse the existing variable reference if it already exists */ + var_ref = get_var_ref(ctx, sf, cv->var_idx, cv->is_arg); + if (!var_ref) + goto fail; + } else { + var_ref = cur_var_refs[cv->var_idx]; + var_ref->header.ref_count++; + } + var_refs[i] = var_ref; + } + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) +{ + JSValue obj, this_val; + int ret; + + this_val = JS_MKPTR(JS_TAG_OBJECT, p); + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor, + js_dup(this_val), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (ret < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static const uint16_t func_kind_to_class_id[] = { + [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION, + [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION, + [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION, + [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION, +}; + +static JSValue js_closure(JSContext *ctx, JSValue bfunc, + JSVarRef **cur_var_refs, + JSStackFrame *sf) +{ + JSFunctionBytecode *b; + JSValue func_obj; + JSAtom name_atom; + + b = JS_VALUE_GET_PTR(bfunc); + func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]); + if (JS_IsException(func_obj)) { + JS_FreeValue(ctx, bfunc); + return JS_EXCEPTION; + } + func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf); + if (JS_IsException(func_obj)) { + /* bfunc has been freed */ + goto fail; + } + name_atom = b->func_name; + if (name_atom == JS_ATOM_NULL) + name_atom = JS_ATOM_empty_string; + js_function_set_properties(ctx, func_obj, name_atom, + b->defined_arg_count); + + if (b->func_kind & JS_FUNC_GENERATOR) { + JSValue proto; + int proto_class_id; + /* generators have a prototype field which is used as + prototype for the generator object */ + if (b->func_kind == JS_FUNC_ASYNC_GENERATOR) + proto_class_id = JS_CLASS_ASYNC_GENERATOR; + else + proto_class_id = JS_CLASS_GENERATOR; + proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]); + if (JS_IsException(proto)) + goto fail; + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto, + JS_PROP_WRITABLE); + } else if (b->has_prototype) { + /* add the 'prototype' property: delay instantiation to avoid + creating cycles for every javascript function. The prototype + object is created on the fly when first accessed */ + JS_SetConstructorBit(ctx, func_obj, true); + JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype, + JS_AUTOINIT_ID_PROTOTYPE, NULL, + JS_PROP_WRITABLE); + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +#define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0) + +static int js_op_define_class(JSContext *ctx, JSValue *sp, + JSAtom class_name, int class_flags, + JSVarRef **cur_var_refs, + JSStackFrame *sf, bool is_computed_name) +{ + JSValue bfunc, parent_class, proto = JS_UNDEFINED; + JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED; + JSFunctionBytecode *b; + + parent_class = sp[-2]; + bfunc = sp[-1]; + + if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) { + if (JS_IsNull(parent_class)) { + parent_proto = JS_NULL; + parent_class = js_dup(ctx->function_proto); + } else { + if (!JS_IsConstructor(ctx, parent_class)) { + JS_ThrowTypeError(ctx, "parent class must be constructor"); + goto fail; + } + parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype); + if (JS_IsException(parent_proto)) + goto fail; + if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) { + JS_ThrowTypeError(ctx, "parent prototype must be an object or null"); + goto fail; + } + } + } else { + /* parent_class is JS_UNDEFINED in this case */ + parent_proto = js_dup(ctx->class_proto[JS_CLASS_OBJECT]); + parent_class = js_dup(ctx->function_proto); + } + proto = JS_NewObjectProto(ctx, parent_proto); + if (JS_IsException(proto)) + goto fail; + + b = JS_VALUE_GET_PTR(bfunc); + assert(b->func_kind == JS_FUNC_NORMAL); + ctor = JS_NewObjectProtoClass(ctx, parent_class, + JS_CLASS_BYTECODE_FUNCTION); + if (JS_IsException(ctor)) + goto fail; + ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf); + bfunc = JS_UNDEFINED; + if (JS_IsException(ctor)) + goto fail; + js_method_set_home_object(ctx, ctor, proto); + JS_SetConstructorBit(ctx, ctor, true); + + JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length, + js_int32(b->defined_arg_count), + JS_PROP_CONFIGURABLE); + + if (is_computed_name) { + if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3], + JS_PROP_CONFIGURABLE) < 0) + goto fail; + } else { + if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0) + goto fail; + } + + /* the constructor property must be first. It can be overriden by + computed property names */ + if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, + js_dup(ctor), + JS_PROP_CONFIGURABLE | + JS_PROP_WRITABLE | JS_PROP_THROW) < 0) + goto fail; + /* set the prototype property */ + if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype, + js_dup(proto), JS_PROP_THROW) < 0) + goto fail; + + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, parent_class); + + sp[-2] = ctor; + sp[-1] = proto; + return 0; + fail: + JS_FreeValue(ctx, parent_class); + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, bfunc); + JS_FreeValue(ctx, proto); + JS_FreeValue(ctx, ctor); + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) +{ + struct list_head *el, *el1; + JSVarRef *var_ref; + + list_for_each_safe(el, el1, &sf->var_ref_list) { + var_ref = list_entry(el, JSVarRef, header.link); + var_ref->value = js_dup(*var_ref->pvalue); + var_ref->pvalue = &var_ref->value; + /* the reference is no longer to a local variable */ + var_ref->is_detached = true; + add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + } +} + +static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int var_idx) +{ + JSValue *pvalue; + struct list_head *el, *el1; + JSVarRef *var_ref; + + pvalue = &sf->var_buf[var_idx]; + list_for_each_safe(el, el1, &sf->var_ref_list) { + var_ref = list_entry(el, JSVarRef, header.link); + if (var_ref->pvalue == pvalue) { + var_ref->value = js_dup(*var_ref->pvalue); + var_ref->pvalue = &var_ref->value; + list_del(&var_ref->header.link); + /* the reference is no longer to a local variable */ + var_ref->is_detached = true; + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + } + } +} + +#define JS_CALL_FLAG_COPY_ARGV (1 << 1) +#define JS_CALL_FLAG_GENERATOR (1 << 2) + +static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSRuntime *rt = ctx->rt; + JSCFunctionType func; + JSObject *p; + JSStackFrame sf_s, *sf = &sf_s, *prev_sf; + JSValue ret_val; + JSValueConst *arg_buf; + int arg_count, i; + JSCFunctionEnum cproto; + + p = JS_VALUE_GET_OBJ(func_obj); + cproto = p->u.cfunc.cproto; + arg_count = p->u.cfunc.length; + + /* better to always check stack overflow */ + if (js_check_stack_overflow(rt, sizeof(arg_buf[0]) * arg_count)) + return JS_ThrowStackOverflow(ctx); + + prev_sf = rt->current_stack_frame; + sf->prev_frame = prev_sf; + rt->current_stack_frame = sf; + ctx = p->u.cfunc.realm; /* change the current realm */ + + sf->is_strict_mode = false; + sf->cur_func = unsafe_unconst(func_obj); + sf->arg_count = argc; + arg_buf = argv; + + if (unlikely(argc < arg_count)) { + /* ensure that at least argc_count arguments are readable */ + arg_buf = alloca(sizeof(arg_buf[0]) * arg_count); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = arg_count; + } + sf->arg_buf = (JSValue *)arg_buf; + + func = p->u.cfunc.c_function; + switch(cproto) { + case JS_CFUNC_constructor: + case JS_CFUNC_constructor_or_func: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor) { + not_a_constructor: + ret_val = JS_ThrowTypeError(ctx, "must be called with new"); + break; + } else { + this_obj = JS_UNDEFINED; + } + } + /* here this_obj is new_target */ + /* fall thru */ + case JS_CFUNC_generic: + ret_val = func.generic(ctx, this_obj, argc, arg_buf); + break; + case JS_CFUNC_constructor_magic: + case JS_CFUNC_constructor_or_func_magic: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor_magic) { + goto not_a_constructor; + } else { + this_obj = JS_UNDEFINED; + } + } + /* fall thru */ + case JS_CFUNC_generic_magic: + ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf, + p->u.cfunc.magic); + break; + case JS_CFUNC_getter: + ret_val = func.getter(ctx, this_obj); + break; + case JS_CFUNC_setter: + ret_val = func.setter(ctx, this_obj, arg_buf[0]); + break; + case JS_CFUNC_getter_magic: + ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic); + break; + case JS_CFUNC_setter_magic: + ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic); + break; + case JS_CFUNC_f_f: + { + double d1; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = js_number(func.f_f(d1)); + } + break; + case JS_CFUNC_f_f_f: + { + double d1, d2; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = js_number(func.f_f_f(d1, d2)); + } + break; + case JS_CFUNC_iterator_next: + { + int done; + ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf, + &done, p->u.cfunc.magic); + if (!JS_IsException(ret_val) && done != 2) { + ret_val = js_create_iterator_result(ctx, ret_val, done); + } + } + break; + default: + abort(); + } + + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSObject *p; + JSBoundFunction *bf; + JSValueConst *arg_buf, new_target; + int arg_count, i; + + p = JS_VALUE_GET_OBJ(func_obj); + bf = p->u.bound_function; + arg_count = bf->argc + argc; + if (js_check_stack_overflow(ctx->rt, sizeof(JSValue) * arg_count)) + return JS_ThrowStackOverflow(ctx); + arg_buf = alloca(sizeof(JSValue) * arg_count); + for(i = 0; i < bf->argc; i++) { + arg_buf[i] = bf->argv[i]; + } + for(i = 0; i < argc; i++) { + arg_buf[bf->argc + i] = argv[i]; + } + if (flags & JS_CALL_FLAG_CONSTRUCTOR) { + new_target = this_obj; + if (js_same_value(ctx, func_obj, new_target)) + new_target = bf->func_obj; + return JS_CallConstructor2(ctx, bf->func_obj, new_target, + arg_count, arg_buf); + } else { + return JS_Call(ctx, bf->func_obj, bf->this_val, + arg_count, arg_buf); + } +} + +/* argument of OP_special_object */ +typedef enum { + OP_SPECIAL_OBJECT_ARGUMENTS, + OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS, + OP_SPECIAL_OBJECT_THIS_FUNC, + OP_SPECIAL_OBJECT_NEW_TARGET, + OP_SPECIAL_OBJECT_HOME_OBJECT, + OP_SPECIAL_OBJECT_VAR_OBJECT, + OP_SPECIAL_OBJECT_IMPORT_META, + OP_SPECIAL_OBJECT_NULL_PROTO, +} OPSpecialObjectEnum; + +#define FUNC_RET_AWAIT 0 +#define FUNC_RET_YIELD 1 +#define FUNC_RET_YIELD_STAR 2 + +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* +static void dump_single_byte_code(JSContext *ctx, const uint8_t *pc, + JSFunctionBytecode *b, int start_pos); +static void print_func_name(JSFunctionBytecode *b); +#endif + +static bool needs_backtrace(JSValue exc) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(exc) != JS_TAG_OBJECT) + return false; + p = JS_VALUE_GET_OBJ(exc); + if (p->class_id != JS_CLASS_ERROR) + return false; + return !find_own_property1(p, JS_ATOM_stack); +} + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValueConst *argv, int flags) +{ + JSRuntime *rt = caller_ctx->rt; + JSContext *ctx; + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame sf_s, *sf = &sf_s; + uint8_t *pc; + int opcode, arg_allocated_size, i; + JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; + JSVarRef **var_refs; + size_t alloca_size; + +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_STEP +#define DUMP_BYTECODE_OR_DONT(pc) \ + if (check_dump_flag(ctx->rt, JS_DUMP_BYTECODE_STEP)) dump_single_byte_code(ctx, pc, b, 0); +#else +#define DUMP_BYTECODE_OR_DONT(pc) +#endif + +#if !DIRECT_DISPATCH +#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) switch (opcode = *pc++) +#define CASE(op) case op +#define DEFAULT default +#define BREAK break +#else + __extension__ static const void * const dispatch_table[256] = { +#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" + [ OP_COUNT ... 255 ] = &&case_default + }; +#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) __extension__ ({ goto *dispatch_table[opcode = *pc++]; }); +#define CASE(op) case_ ## op +#define DEFAULT case_default +#define BREAK SWITCH(pc) +#endif + + if (js_poll_interrupts(caller_ctx)) + return JS_EXCEPTION; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { + if (flags & JS_CALL_FLAG_GENERATOR) { + JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); + /* func_obj get contains a pointer to JSFuncAsyncState */ + /* the stack frame is already allocated */ + sf = &s->frame; + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = p->u.func.function_bytecode; + ctx = b->realm; + var_refs = p->u.func.var_refs; + local_buf = arg_buf = sf->arg_buf; + var_buf = sf->var_buf; + stack_buf = sf->var_buf + b->var_count; + sp = sf->cur_sp; + sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */ + pc = sf->cur_pc; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + if (s->throw_flag) + goto exception; + else + goto restart; + } else { + goto not_a_function; + } + } + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeErrorNotAFunction(caller_ctx); + } + return call_func(caller_ctx, func_obj, this_obj, argc, + argv, flags); + } + b = p->u.func.function_bytecode; + + if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { + arg_allocated_size = b->arg_count; + } else { + arg_allocated_size = 0; + } + + alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count + + b->stack_size); + if (js_check_stack_overflow(rt, alloca_size)) + return JS_ThrowStackOverflow(caller_ctx); + + sf->is_strict_mode = b->is_strict_mode; + arg_buf = (JSValue *)argv; + sf->arg_count = argc; + sf->cur_func = unsafe_unconst(func_obj); + init_list_head(&sf->var_ref_list); + var_refs = p->u.func.var_refs; + + local_buf = alloca(alloca_size); + if (unlikely(arg_allocated_size)) { + int n = min_int(argc, b->arg_count); + arg_buf = local_buf; + for(i = 0; i < n; i++) + arg_buf[i] = js_dup(argv[i]); + for(; i < b->arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = b->arg_count; + } + var_buf = local_buf + arg_allocated_size; + sf->var_buf = var_buf; + sf->arg_buf = arg_buf; + + for(i = 0; i < b->var_count; i++) + var_buf[i] = JS_UNDEFINED; + + stack_buf = var_buf + b->var_count; + sp = stack_buf; + pc = b->byte_code_buf; + /* sf->cur_pc must we set to pc before any recursive calls to JS_CallInternal. */ + sf->cur_pc = NULL; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + ctx = b->realm; /* set the current realm */ + +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_STEP + if (check_dump_flag(ctx->rt, JS_DUMP_BYTECODE_STEP)) + print_func_name(b); +#endif + + restart: + for(;;) { + int call_argc; + JSValue *call_argv; + + SWITCH(pc) { + CASE(OP_push_i32): + *sp++ = js_int32(get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_push_bigint_i32): + *sp++ = __JS_NewShortBigInt(ctx, (int)get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_push_const): + *sp++ = js_dup(b->cpool[get_u32(pc)]); + pc += 4; + BREAK; + CASE(OP_push_minus1): + CASE(OP_push_0): + CASE(OP_push_1): + CASE(OP_push_2): + CASE(OP_push_3): + CASE(OP_push_4): + CASE(OP_push_5): + CASE(OP_push_6): + CASE(OP_push_7): + *sp++ = js_int32(opcode - OP_push_0); + BREAK; + CASE(OP_push_i8): + *sp++ = js_int32(get_i8(pc)); + pc += 1; + BREAK; + CASE(OP_push_i16): + *sp++ = js_int32(get_i16(pc)); + pc += 2; + BREAK; + CASE(OP_push_const8): + *sp++ = js_dup(b->cpool[*pc++]); + BREAK; + CASE(OP_fclosure8): + *sp++ = js_closure(ctx, js_dup(b->cpool[*pc++]), var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_push_empty_string): + *sp++ = js_empty_string(rt); + BREAK; + CASE(OP_get_length): + { + JSValue val; + + sf->cur_pc = pc; + val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + CASE(OP_push_atom_value): + *sp++ = JS_AtomToValue(ctx, get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_undefined): + *sp++ = JS_UNDEFINED; + BREAK; + CASE(OP_null): + *sp++ = JS_NULL; + BREAK; + CASE(OP_push_this): + /* OP_push_this is only called at the start of a function */ + { + JSValue val; + if (!b->is_strict_mode) { + uint32_t tag = JS_VALUE_GET_TAG(this_obj); + if (likely(tag == JS_TAG_OBJECT)) + goto normal_this; + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) { + val = js_dup(ctx->global_obj); + } else { + val = JS_ToObject(ctx, this_obj); + if (JS_IsException(val)) + goto exception; + } + } else { + normal_this: + val = js_dup(this_obj); + } + *sp++ = val; + } + BREAK; + CASE(OP_push_false): + *sp++ = JS_FALSE; + BREAK; + CASE(OP_push_true): + *sp++ = JS_TRUE; + BREAK; + CASE(OP_object): + *sp++ = JS_NewObject(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_special_object): + { + int arg = *pc++; + switch(arg) { + case OP_SPECIAL_OBJECT_ARGUMENTS: + *sp++ = js_build_arguments(ctx, argc, argv); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS: + *sp++ = js_build_mapped_arguments(ctx, argc, argv, + sf, min_int(argc, b->arg_count)); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_THIS_FUNC: + *sp++ = js_dup(sf->cur_func); + break; + case OP_SPECIAL_OBJECT_NEW_TARGET: + *sp++ = js_dup(new_target); + break; + case OP_SPECIAL_OBJECT_HOME_OBJECT: + { + JSObject *p1; + p1 = p->u.func.home_object; + if (unlikely(!p1)) + *sp++ = JS_UNDEFINED; + else + *sp++ = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + } + break; + case OP_SPECIAL_OBJECT_VAR_OBJECT: + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_IMPORT_META: + *sp++ = js_import_meta(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_NULL_PROTO: + *sp++ = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_OBJECT); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + default: + abort(); + } + } + BREAK; + CASE(OP_rest): + { + int i, n, first = get_u16(pc); + pc += 2; + i = min_int(first, argc); + n = argc - i; + *sp++ = js_create_array(ctx, n, &argv[i]); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; + + CASE(OP_drop): + JS_FreeValue(ctx, sp[-1]); + sp--; + BREAK; + CASE(OP_nip): + JS_FreeValue(ctx, sp[-2]); + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_nip1): /* a b c -> b c */ + JS_FreeValue(ctx, sp[-3]); + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_dup): + sp[0] = js_dup(sp[-1]); + sp++; + BREAK; + CASE(OP_dup2): /* a b -> a b a b */ + sp[0] = js_dup(sp[-2]); + sp[1] = js_dup(sp[-1]); + sp += 2; + BREAK; + CASE(OP_dup3): /* a b c -> a b c a b c */ + sp[0] = js_dup(sp[-3]); + sp[1] = js_dup(sp[-2]); + sp[2] = js_dup(sp[-1]); + sp += 3; + BREAK; + CASE(OP_dup1): /* a b -> a a b */ + sp[0] = sp[-1]; + sp[-1] = js_dup(sp[-2]); + sp++; + BREAK; + CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = js_dup(sp[0]); + sp++; + BREAK; + CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = js_dup(sp[0]); + sp++; + BREAK; + CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = js_dup(sp[0]); + sp++; + BREAK; + CASE(OP_perm3): /* obj a b -> a obj b (213) */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_rot3l): /* x a b -> a b x (231) */ + { + JSValue tmp; + tmp = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot4l): /* x a b c -> a b c x */ + { + JSValue tmp; + tmp = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot5l): /* x a b c d -> a b c d x */ + { + JSValue tmp; + tmp = sp[-5]; + sp[-5] = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot3r): /* a b x -> x a b (312) */ + { + JSValue tmp; + tmp = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_perm4): /* obj prop a b -> a obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = tmp; + } + BREAK; + CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = sp[-5]; + sp[-5] = tmp; + } + BREAK; + CASE(OP_swap): /* a b -> b a */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_swap2): /* a b c d -> c d a b */ + { + JSValue tmp1, tmp2; + tmp1 = sp[-4]; + tmp2 = sp[-3]; + sp[-4] = sp[-2]; + sp[-3] = sp[-1]; + sp[-2] = tmp1; + sp[-1] = tmp2; + } + BREAK; + + CASE(OP_fclosure): + { + JSValue bfunc = js_dup(b->cpool[get_u32(pc)]); + pc += 4; + *sp++ = js_closure(ctx, bfunc, var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; + CASE(OP_call0): + CASE(OP_call1): + CASE(OP_call2): + CASE(OP_call3): + call_argc = opcode - OP_call0; + goto has_call_argc; + CASE(OP_call): + CASE(OP_tail_call): + { + call_argc = get_u16(pc); + pc += 2; + goto has_call_argc; + has_call_argc: + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, + vc(call_argv), 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call) + goto done; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_constructor): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallConstructorInternal(ctx, call_argv[-2], + call_argv[-1], call_argc, + vc(call_argv), 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_method): + CASE(OP_tail_call_method): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2], + JS_UNDEFINED, call_argc, + vc(call_argv), 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call_method) + goto done; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_array_from): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + ret_val = JS_NewArrayFrom(ctx, call_argc, call_argv); + sp -= call_argc; + if (unlikely(JS_IsException(ret_val))) + goto exception; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_apply): + { + int magic; + magic = get_u16(pc); + pc += 2; + sf->cur_pc = pc; + + ret_val = js_function_apply(ctx, sp[-3], 2, vc(&sp[-2]), magic); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + *sp++ = ret_val; + } + BREAK; + CASE(OP_return): + ret_val = *--sp; + goto done; + CASE(OP_return_undef): + ret_val = JS_UNDEFINED; + goto done; + + CASE(OP_check_ctor_return): + /* return true if 'this' should be returned */ + if (!JS_IsObject(sp[-1])) { + if (!JS_IsUndefined(sp[-1])) { + JS_ThrowTypeError(caller_ctx, "derived class constructor must return an object or undefined"); + goto exception; + } + sp[0] = JS_TRUE; + } else { + sp[0] = JS_FALSE; + } + sp++; + BREAK; + CASE(OP_check_ctor): + if (JS_IsUndefined(new_target)) { + non_ctor_call: + JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'"); + goto exception; + } + BREAK; + CASE(OP_init_ctor): + { + JSValue super, ret; + sf->cur_pc = pc; + if (JS_IsUndefined(new_target)) + goto non_ctor_call; + super = JS_GetPrototype(ctx, func_obj); + if (JS_IsException(super)) + goto exception; + ret = JS_CallConstructor2(ctx, super, new_target, argc, argv); + JS_FreeValue(ctx, super); + if (JS_IsException(ret)) + goto exception; + *sp++ = ret; + } + BREAK; + CASE(OP_check_brand): + { + int ret = JS_CheckBrand(ctx, sp[-2], sp[-1]); + if (ret < 0) + goto exception; + if (!ret) { + JS_ThrowTypeError(ctx, "invalid brand on object"); + goto exception; + } + } + BREAK; + CASE(OP_add_brand): + if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + BREAK; + + CASE(OP_throw): + JS_Throw(ctx, *--sp); + goto exception; + + CASE(OP_throw_error): +#define JS_THROW_VAR_RO 0 +#define JS_THROW_VAR_REDECL 1 +#define JS_THROW_VAR_UNINITIALIZED 2 +#define JS_THROW_ERROR_DELETE_SUPER 3 +#define JS_THROW_ERROR_ITERATOR_THROW 4 + { + JSAtom atom; + int type; + atom = get_u32(pc); + type = pc[4]; + pc += 5; + if (type == JS_THROW_VAR_RO) + JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom); + else + if (type == JS_THROW_VAR_REDECL) + JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom); + else + if (type == JS_THROW_VAR_UNINITIALIZED) + JS_ThrowReferenceErrorUninitialized(ctx, atom); + else + if (type == JS_THROW_ERROR_DELETE_SUPER) + JS_ThrowReferenceError(ctx, "unsupported reference to 'super'"); + else + if (type == JS_THROW_ERROR_ITERATOR_THROW) + JS_ThrowTypeError(ctx, "iterator does not have a throw method"); + else + JS_ThrowInternalError(ctx, "invalid throw var type %d", type); + } + goto exception; + + CASE(OP_eval): + { + JSValue obj; + int scope_idx; + call_argc = get_u16(pc); + scope_idx = get_u16(pc + 2) - 1; + pc += 4; + call_argv = sp - call_argc; + sf->cur_pc = pc; + if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) { + if (call_argc >= 1) + obj = call_argv[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, + vc(call_argv), 0); + } + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + /* could merge with OP_apply */ + CASE(OP_apply_eval): + { + int scope_idx; + uint32_t len; + JSValue *tab; + JSValue obj; + + scope_idx = get_u16(pc) - 1; + pc += 2; + sf->cur_pc = pc; + tab = build_arg_list(ctx, &len, sp[-1]); + if (!tab) + goto exception; + if (js_same_value(ctx, sp[-2], ctx->eval_obj)) { + if (len >= 1) + obj = tab[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, len, vc(tab)); + } + free_arg_list(ctx, tab, len); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_regexp): + { + sp[-2] = js_regexp_constructor_internal(ctx, JS_UNDEFINED, + sp[-2], sp[-1]); + sp--; + } + BREAK; + + CASE(OP_get_super): + { + JSValue proto; + proto = JS_GetPrototype(ctx, sp[-1]); + if (JS_IsException(proto)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = proto; + } + BREAK; + + CASE(OP_import): + { + JSValue val; + sf->cur_pc = pc; + val = js_dynamic_import(ctx, sp[-1]); + if (JS_IsException(val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + + CASE(OP_check_var): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_CheckGlobalVar(ctx, atom); + if (ret < 0) + goto exception; + *sp++ = js_bool(ret); + } + BREAK; + + CASE(OP_get_var_undef): + CASE(OP_get_var): + { + JSValue val; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_put_var): + CASE(OP_put_var_init): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_var_strict): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + /* sp[-2] is JS_TRUE or JS_FALSE */ + if (unlikely(!JS_VALUE_GET_INT(sp[-2]))) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + ret = JS_SetGlobalVar(ctx, atom, sp[-1], 2); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_check_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_CheckDefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_DefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_func): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp--; + } + BREAK; + + CASE(OP_get_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = js_dup(var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], js_dup(sp[-1])); + } + BREAK; + CASE(OP_get_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = js_dup(arg_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], js_dup(sp[-1])); + } + BREAK; + + CASE(OP_get_loc8): *sp++ = js_dup(var_buf[*pc++]); BREAK; + CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; + CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], js_dup(sp[-1])); BREAK; + + // Observation: get_loc0 and get_loc1 are individually very + // frequent opcodes _and_ they are very often paired together, + // making them ideal candidates for opcode fusion. + CASE(OP_get_loc0_loc1): + *sp++ = js_dup(var_buf[0]); + *sp++ = js_dup(var_buf[1]); + BREAK; + + CASE(OP_get_loc0): *sp++ = js_dup(var_buf[0]); BREAK; + CASE(OP_get_loc1): *sp++ = js_dup(var_buf[1]); BREAK; + CASE(OP_get_loc2): *sp++ = js_dup(var_buf[2]); BREAK; + CASE(OP_get_loc3): *sp++ = js_dup(var_buf[3]); BREAK; + CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; + CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; + CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; + CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK; + CASE(OP_set_loc0): set_value(ctx, &var_buf[0], js_dup(sp[-1])); BREAK; + CASE(OP_set_loc1): set_value(ctx, &var_buf[1], js_dup(sp[-1])); BREAK; + CASE(OP_set_loc2): set_value(ctx, &var_buf[2], js_dup(sp[-1])); BREAK; + CASE(OP_set_loc3): set_value(ctx, &var_buf[3], js_dup(sp[-1])); BREAK; + CASE(OP_get_arg0): *sp++ = js_dup(arg_buf[0]); BREAK; + CASE(OP_get_arg1): *sp++ = js_dup(arg_buf[1]); BREAK; + CASE(OP_get_arg2): *sp++ = js_dup(arg_buf[2]); BREAK; + CASE(OP_get_arg3): *sp++ = js_dup(arg_buf[3]); BREAK; + CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK; + CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK; + CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK; + CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK; + CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], js_dup(sp[-1])); BREAK; + CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], js_dup(sp[-1])); BREAK; + CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], js_dup(sp[-1])); BREAK; + CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], js_dup(sp[-1])); BREAK; + CASE(OP_get_var_ref0): *sp++ = js_dup(*var_refs[0]->pvalue); BREAK; + CASE(OP_get_var_ref1): *sp++ = js_dup(*var_refs[1]->pvalue); BREAK; + CASE(OP_get_var_ref2): *sp++ = js_dup(*var_refs[2]->pvalue); BREAK; + CASE(OP_get_var_ref3): *sp++ = js_dup(*var_refs[3]->pvalue); BREAK; + CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK; + CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, js_dup(sp[-1])); BREAK; + CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, js_dup(sp[-1])); BREAK; + CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, js_dup(sp[-1])); BREAK; + CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, js_dup(sp[-1])); BREAK; + + CASE(OP_get_var_ref): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + sp[0] = js_dup(val); + sp++; + } + BREAK; + CASE(OP_put_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, js_dup(sp[-1])); + } + BREAK; + CASE(OP_get_var_ref_check): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); + goto exception; + } + sp[0] = js_dup(val); + sp++; + } + BREAK; + CASE(OP_put_var_ref_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_var_ref_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc_uninitialized): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], JS_UNINITIALIZED); + } + BREAK; + CASE(OP_get_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, + false); + goto exception; + } + sp[0] = js_dup(var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, + false); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_loc_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceError(caller_ctx, + "'this' can be initialized only once"); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_close_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + close_lexical_var(ctx, sf, idx); + } + BREAK; + + CASE(OP_make_loc_ref): + CASE(OP_make_arg_ref): + CASE(OP_make_var_ref_ref): + { + JSVarRef *var_ref; + JSProperty *pr; + JSAtom atom; + int idx; + atom = get_u32(pc); + idx = get_u16(pc + 4); + pc += 6; + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + if (opcode == OP_make_var_ref_ref) { + var_ref = var_refs[idx]; + var_ref->header.ref_count++; + } else { + var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); + if (!var_ref) + goto exception; + } + pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom, + JS_PROP_WRITABLE | JS_PROP_VARREF); + if (!pr) { + free_var_ref(rt, var_ref); + goto exception; + } + pr->u.var_ref = var_ref; + *sp++ = JS_AtomToValue(ctx, atom); + } + BREAK; + CASE(OP_make_var_ref): + { + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + if (JS_GetGlobalVarRef(ctx, atom, sp)) + goto exception; + sp += 2; + } + BREAK; + + CASE(OP_goto): + pc += (int32_t)get_u32(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_goto16): + pc += (int16_t)get_u16(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_goto8): + pc += (int8_t)pc[0]; + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_if_true): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_true8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_catch): + { + int32_t diff; + diff = get_u32(pc); + sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf); + sp++; + pc += 4; + } + BREAK; + CASE(OP_gosub): + { + int32_t diff; + diff = get_u32(pc); + /* XXX: should have a different tag to avoid security flaw */ + sp[0] = js_int32(pc + 4 - b->byte_code_buf); + sp++; + pc += diff; + } + BREAK; + CASE(OP_ret): + { + JSValue op1; + uint32_t pos; + op1 = sp[-1]; + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) + goto ret_fail; + pos = JS_VALUE_GET_INT(op1); + if (unlikely(pos >= b->byte_code_len)) { + ret_fail: + JS_ThrowInternalError(ctx, "invalid ret value"); + goto exception; + } + sp--; + pc = b->byte_code_buf + pos; + } + BREAK; + + CASE(OP_for_in_start): + sf->cur_pc = pc; + if (js_for_in_start(ctx, sp)) + goto exception; + BREAK; + CASE(OP_for_in_next): + sf->cur_pc = pc; + if (js_for_in_next(ctx, sp)) + goto exception; + sp += 2; + BREAK; + CASE(OP_for_of_start): + sf->cur_pc = pc; + if (js_for_of_start(ctx, sp, false)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_for_of_next): + { + int offset = -3 - pc[0]; + pc += 1; + sf->cur_pc = pc; + if (js_for_of_next(ctx, sp, offset)) + goto exception; + sp += 2; + } + BREAK; + CASE(OP_for_await_of_start): + sf->cur_pc = pc; + if (js_for_of_start(ctx, sp, true)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_iterator_get_value_done): + sf->cur_pc = pc; + if (js_iterator_get_value_done(ctx, sp)) + goto exception; + sp += 1; + BREAK; + CASE(OP_iterator_check_object): + if (unlikely(!JS_IsObject(sp[-1]))) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto exception; + } + BREAK; + + CASE(OP_iterator_close): + /* iter_obj next catch_offset -> */ + sp--; /* drop the catch offset to avoid getting caught by exception */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + if (!JS_IsUndefined(sp[-1])) { + sf->cur_pc = pc; + if (JS_IteratorClose(ctx, sp[-1], false)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + } + sp--; + BREAK; + CASE(OP_nip_catch): + { + JSValue ret_val; + /* catch_offset ... ret_val -> ret_eval */ + ret_val = *--sp; + while (sp > stack_buf && + JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { + JS_FreeValue(ctx, *--sp); + } + if (unlikely(sp == stack_buf)) { + JS_ThrowInternalError(ctx, "nip_catch"); + JS_FreeValue(ctx, ret_val); + goto exception; + } + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_iterator_next): + /* stack: iter_obj next catch_offset val */ + { + JSValue ret; + sf->cur_pc = pc; + ret = JS_Call(ctx, sp[-3], sp[-4], 1, vc(sp - 1)); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + } + BREAK; + + CASE(OP_iterator_call): + /* stack: iter_obj next catch_offset val */ + { + JSValue method, ret; + bool ret_flag; + int flags; + flags = *pc++; + sf->cur_pc = pc; + method = JS_GetProperty(ctx, sp[-4], (flags & 1) ? + JS_ATOM_throw : JS_ATOM_return); + if (JS_IsException(method)) + goto exception; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + ret_flag = true; + } else { + if (flags & 2) { + /* no argument */ + ret = JS_CallFree(ctx, method, sp[-4], + 0, NULL); + } else { + ret = JS_CallFree(ctx, method, sp[-4], + 1, vc(sp - 1)); + } + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + ret_flag = false; + } + sp[0] = js_bool(ret_flag); + sp += 1; + } + BREAK; + + CASE(OP_lnot): + { + int res; + JSValue op1; + + op1 = sp[-1]; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1) != 0; + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp[-1] = js_bool(!res); + } + BREAK; + + CASE(OP_get_field): + { + JSValue val; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + val = JS_GetPropertyInternal(ctx, sp[-1], atom, sp[-1], false); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + + CASE(OP_get_field2): + { + JSValue val; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + val = JS_GetPropertyInternal(ctx, sp[-1], atom, sp[-1], false); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_put_field): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + ret = JS_SetPropertyInternal2(ctx, + sp[-2], atom, + sp[-1], sp[-2], + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_private_symbol): + { + JSAtom atom; + JSValue val; + + atom = get_u32(pc); + pc += 4; + val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(val)) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_get_private_field): + { + JSValue val; + sf->cur_pc = pc; + val = JS_GetPrivateField(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_put_private_field): + { + int ret; + sf->cur_pc = pc; + ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_private_field): + { + int ret; + ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_field): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_set_name): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_name_computed): + { + int ret; + ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_proto): + { + JSValue proto; + proto = sp[-1]; + if (JS_IsObject(proto) || JS_IsNull(proto)) { + if (JS_SetPrototypeInternal(ctx, sp[-2], proto, true) < 0) + goto exception; + } + JS_FreeValue(ctx, proto); + sp--; + } + BREAK; + CASE(OP_set_home_object): + js_method_set_home_object(ctx, sp[-1], sp[-2]); + BREAK; + CASE(OP_define_method): + CASE(OP_define_method_computed): + { + JSValue getter, setter, value; + JSValue obj; + JSAtom atom; + int flags, ret, op_flags; + bool is_computed; +#define OP_DEFINE_METHOD_METHOD 0 +#define OP_DEFINE_METHOD_GETTER 1 +#define OP_DEFINE_METHOD_SETTER 2 +#define OP_DEFINE_METHOD_ENUMERABLE 4 + + is_computed = (opcode == OP_define_method_computed); + if (is_computed) { + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + opcode += OP_define_method - OP_define_method_computed; + } else { + atom = get_u32(pc); + pc += 4; + } + op_flags = *pc++; + + obj = sp[-2 - is_computed]; + flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | + JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; + if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) + flags |= JS_PROP_ENUMERABLE; + op_flags &= 3; + value = JS_UNDEFINED; + getter = JS_UNDEFINED; + setter = JS_UNDEFINED; + if (op_flags == OP_DEFINE_METHOD_METHOD) { + value = sp[-1]; + flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; + } else if (op_flags == OP_DEFINE_METHOD_GETTER) { + getter = sp[-1]; + flags |= JS_PROP_HAS_GET; + } else { + setter = sp[-1]; + flags |= JS_PROP_HAS_SET; + } + ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj); + if (ret >= 0) { + ret = JS_DefineProperty(ctx, obj, atom, value, + getter, setter, flags); + } + JS_FreeValue(ctx, sp[-1]); + if (is_computed) { + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-2]); + } + sp -= 1 + is_computed; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_class): + CASE(OP_define_class_computed): + { + int class_flags; + JSAtom atom; + + atom = get_u32(pc); + class_flags = pc[4]; + pc += 5; + if (js_op_define_class(ctx, sp, atom, class_flags, + var_refs, sf, + (opcode == OP_define_class_computed)) < 0) + goto exception; + } + BREAK; + + CASE(OP_get_array_el): + { + JSValue val; + + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_array_el2): + { + JSValue val; + + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + sp[-1] = val; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_ref_value): + { + JSValue val; + sf->cur_pc = pc; + if (unlikely(JS_IsUndefined(sp[-2]))) { + JSAtom atom = JS_ValueToAtom(ctx, sp[-1]); + if (atom != JS_ATOM_NULL) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + } + goto exception; + } + val = JS_GetPropertyValue(ctx, sp[-2], + js_dup(sp[-1])); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + sp++; + } + BREAK; + + CASE(OP_get_super_value): + { + JSValue val; + JSAtom atom; + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp[-1]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + val = JS_GetPropertyInternal(ctx, sp[-2], atom, sp[-3], false); + JS_FreeAtom(ctx, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-3]); + sp[-3] = val; + sp -= 2; + } + BREAK; + + CASE(OP_put_array_el): + { + int ret; + sf->cur_pc = pc; + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_ref_value): + { + int ret, flags; + sf->cur_pc = pc; + flags = JS_PROP_THROW_STRICT; + if (unlikely(JS_IsUndefined(sp[-3]))) { + if (is_strict_mode(ctx)) { + JSAtom atom = JS_ValueToAtom(ctx, sp[-2]); + if (atom != JS_ATOM_NULL) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + } + goto exception; + } else { + sp[-3] = js_dup(ctx->global_obj); + } + } else { + if (is_strict_mode(ctx)) + flags |= JS_PROP_NO_ADD; + } + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], flags); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_super_value): + { + int ret; + JSAtom atom; + sf->cur_pc = pc; + if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto exception; + } + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + ret = JS_SetPropertyInternal2(ctx, + sp[-3], atom, + sp[-1], sp[-4], + JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-4]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + sp -= 4; + if (ret < 0) + goto exception; + } + BREAK; + + CASE(OP_define_array_el): + { + int ret; + ret = JS_DefinePropertyValueValue(ctx, sp[-3], js_dup(sp[-2]), sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp -= 1; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_append): /* array pos enumobj -- array pos */ + { + sf->cur_pc = pc; + if (js_append_enumerate(ctx, sp)) + goto exception; + JS_FreeValue(ctx, *--sp); + } + BREAK; + + CASE(OP_copy_data_properties): /* target source excludeList */ + { + /* stack offsets (-1 based): + 2 bits for target, + 3 bits for source, + 2 bits for exclusionList */ + int mask; + + mask = *pc++; + sf->cur_pc = pc; + if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)], + sp[-1 - ((mask >> 2) & 7)], + sp[-1 - ((mask >> 5) & 7)], 0)) + goto exception; + } + BREAK; + + CASE(OP_add): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) + goto add_slow; + sp[-2] = js_int32(r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) + + JS_VALUE_GET_FLOAT64(op2)); + JS_X87_FPCW_RESTORE(fpcw); + sp--; + } else { + add_slow: + sf->cur_pc = pc; + if (js_add_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_add_loc): + { + JSValue *pv; + int idx; + idx = *pc; + pc += 1; + + pv = &var_buf[idx]; + if (likely(JS_VALUE_IS_BOTH_INT(*pv, sp[-1]))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(*pv) + + JS_VALUE_GET_INT(sp[-1]); + if (unlikely((int)r != r)) + goto add_loc_slow; + *pv = js_int32(r); + sp--; + } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) { + JSValue op1; + op1 = sp[-1]; + sp--; + sf->cur_pc = pc; + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) + goto exception; + op1 = JS_ConcatString(ctx, js_dup(*pv), op1); + if (JS_IsException(op1)) + goto exception; + set_value(ctx, pv, op1); + } else { + JSValue ops[2]; + add_loc_slow: + /* In case of exception, js_add_slow frees ops[0] + and ops[1], so we must duplicate *pv */ + sf->cur_pc = pc; + ops[0] = js_dup(*pv); + ops[1] = sp[-1]; + sp--; + if (js_add_slow(ctx, ops + 2)) + goto exception; + set_value(ctx, pv, ops[0]); + } + } + BREAK; + CASE(OP_sub): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) + goto binary_arith_slow; + sp[-2] = js_int32(r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) - + JS_VALUE_GET_FLOAT64(op2)); + JS_X87_FPCW_RESTORE(fpcw); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mul): + { + JSValue op1, op2; + double d; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int32_t v1, v2; + int64_t r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + r = (int64_t)v1 * v2; + if (unlikely((int)r != r)) { + d = (double)r; + goto mul_fp_res; + } + /* need to test zero case for -0 result */ + if (unlikely(r == 0 && (v1 | v2) < 0)) { + d = -0.0; + goto mul_fp_res; + } + sp[-2] = js_int32(r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + JS_X87_FPCW_SAVE_AND_ADJUST(fpcw); + d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); + JS_X87_FPCW_RESTORE(fpcw); + mul_fp_res: + sp[-2] = js_float64(d); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_div): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + sp[-2] = js_number((double)v1 / (double)v2); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mod): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2, r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = + -1 and the cases where the result is -0. */ + if (unlikely(v1 < 0 || v2 <= 0)) + goto binary_arith_slow; + r = v1 % v2; + sp[-2] = js_int32(r); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_pow): + binary_arith_slow: + sf->cur_pc = pc; + if (js_binary_arith_slow(ctx, sp, opcode)) + goto exception; + sp--; + BREAK; + + CASE(OP_plus): + { + JSValue op1; + uint32_t tag; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_neg): + { + JSValue op1; + uint32_t tag; + int val; + double d; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + /* Note: -0 cannot be expressed as integer */ + if (unlikely(val == 0)) { + d = -0.0; + goto neg_fp_res; + } + if (unlikely(val == INT32_MIN)) { + d = -(double)val; + goto neg_fp_res; + } + sp[-1] = js_int32(-val); + } else if (JS_TAG_IS_FLOAT64(tag)) { + d = -JS_VALUE_GET_FLOAT64(op1); + neg_fp_res: + sp[-1] = js_float64(d); + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_inc): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_slow; + sp[-1] = js_int32(val + 1); + } else { + inc_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_dec): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_slow; + sp[-1] = js_int32(val - 1); + } else { + dec_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_post_inc): + CASE(OP_post_dec): + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + sp++; + BREAK; + CASE(OP_inc_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_loc_slow; + var_buf[idx] = js_int32(val + 1); + } else { + inc_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = js_dup(op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_dec_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_loc_slow; + var_buf[idx] = js_int32(val - 1); + } else { + dec_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = js_dup(op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_not): + { + JSValue op1; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + sp[-1] = js_int32(~JS_VALUE_GET_INT(op1)); + } else { + sf->cur_pc = pc; + if (js_not_slow(ctx, sp)) + goto exception; + } + } + BREAK; + + CASE(OP_shl): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2) & 0x1f; + sp[-2] = js_int32(v1 << v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_shr): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + v2 &= 0x1f; + sp[-2] = js_uint32((uint32_t)JS_VALUE_GET_INT(op1) >> v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_shr_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_sar): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + if (unlikely(v2 > 0x1f)) { + v2 &= 0x1f; + } + sp[-2] = js_int32((int)JS_VALUE_GET_INT(op1) >> v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_and): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = js_int32(JS_VALUE_GET_INT(op1) & JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_or): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = js_int32(JS_VALUE_GET_INT(op1) | JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_xor): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = js_int32(JS_VALUE_GET_INT(op1) ^ JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + + +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ + sp[-2] = js_bool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ + sp--; \ + } else { \ + sf->cur_pc = pc; \ + if (slow_call) \ + goto exception; \ + sp--; \ + } \ + } \ + BREAK + + OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); + OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); + OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); + OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); + + CASE(OP_in): + sf->cur_pc = pc; + if (js_operator_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_private_in): + if (js_operator_private_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_instanceof): + sf->cur_pc = pc; + if (js_operator_instanceof(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_typeof): + { + JSValue op1; + JSAtom atom; + + op1 = sp[-1]; + atom = js_operator_typeof(ctx, op1); + JS_FreeValue(ctx, op1); + sp[-1] = JS_AtomToString(ctx, atom); + } + BREAK; + CASE(OP_delete): + sf->cur_pc = pc; + if (js_operator_delete(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_delete_var): + { + JSAtom atom; + int ret; + + atom = get_u32(pc); + pc += 4; + + sf->cur_pc = pc; + ret = JS_DeleteGlobalVar(ctx, atom); + if (unlikely(ret < 0)) + goto exception; + *sp++ = js_bool(ret); + } + BREAK; + + CASE(OP_to_object): + if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) { + sf->cur_pc = pc; + ret_val = JS_ToObject(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_to_propkey): + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; + + CASE(OP_to_propkey2): + /* must be tested first */ + if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) { + JS_ThrowTypeError(ctx, "value has no property"); + goto exception; + } + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; + CASE(OP_with_get_var): + CASE(OP_with_put_var): + CASE(OP_with_delete_var): + CASE(OP_with_make_ref): + CASE(OP_with_get_ref): + CASE(OP_with_get_ref_undef): + { + JSAtom atom; + int32_t diff; + JSValue obj, val; + int ret, is_with; + atom = get_u32(pc); + diff = get_u32(pc + 4); + is_with = pc[8]; + pc += 9; + sf->cur_pc = pc; + + obj = sp[-1]; + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) { + if (is_with) { + ret = js_has_unscopable(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) + goto no_with; + } + switch (opcode) { + case OP_with_get_var: + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + set_value(ctx, &sp[-1], val); + break; + case OP_with_put_var: + /* XXX: check if strict mode */ + ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2], + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + break; + case OP_with_delete_var: + ret = JS_DeleteProperty(ctx, obj, atom, 0); + if (unlikely(ret < 0)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = js_bool(ret); + break; + case OP_with_make_ref: + /* produce a pair object/propname on the stack */ + *sp++ = JS_AtomToValue(ctx, atom); + break; + case OP_with_get_ref: + /* produce a pair object/method on the stack */ + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + break; + case OP_with_get_ref_undef: + /* produce a pair undefined/function on the stack */ + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_UNDEFINED; + *sp++ = val; + break; + } + pc += diff - 5; + } else { + no_with: + /* if not jumping, drop the object argument */ + JS_FreeValue(ctx, sp[-1]); + sp--; + } + } + BREAK; + + CASE(OP_await): + ret_val = js_int32(FUNC_RET_AWAIT); + goto done_generator; + CASE(OP_yield): + ret_val = js_int32(FUNC_RET_YIELD); + goto done_generator; + CASE(OP_yield_star): + CASE(OP_async_yield_star): + ret_val = js_int32(FUNC_RET_YIELD_STAR); + goto done_generator; + CASE(OP_return_async): + CASE(OP_initial_yield): + ret_val = JS_UNDEFINED; + goto done_generator; + + CASE(OP_nop): + BREAK; + CASE(OP_is_undefined_or_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED || + JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } + CASE(OP_is_undefined): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) { + goto set_true; + } else { + goto free_and_set_false; + } + CASE(OP_is_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } + /* XXX: could merge to a single opcode */ + CASE(OP_typeof_is_undefined): + /* different from OP_is_undefined because of isHTMLDDA */ + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_undefined) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + CASE(OP_typeof_is_function): + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + free_and_set_true: + JS_FreeValue(ctx, sp[-1]); + set_true: + sp[-1] = JS_TRUE; + BREAK; + free_and_set_false: + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_FALSE; + BREAK; + CASE(OP_invalid): + DEFAULT: + JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", + (int)(pc - b->byte_code_buf - 1), opcode); + goto exception; + } + } + exception: + if (needs_backtrace(rt->current_exception) + || JS_IsUndefined(ctx->error_back_trace)) { + sf->cur_pc = pc; + build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, + NULL, 0, 0, 0); + } + if (!JS_IsUncatchableError(rt->current_exception)) { + while (sp > stack_buf) { + JSValue val = *--sp; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { + int pos = JS_VALUE_GET_INT(val); + if (pos == 0) { + /* enumerator: close it with a throw */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + JS_IteratorClose(ctx, sp[-1], true); + } else { + *sp++ = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + JS_FreeValueRT(rt, ctx->error_back_trace); + ctx->error_back_trace = JS_UNDEFINED; + pc = b->byte_code_buf + pos; + goto restart; + } + } + } + } + ret_val = JS_EXCEPTION; + /* the local variables are freed by the caller in the generator + case. Hence the label 'done' should never be reached in a + generator function. */ + if (b->func_kind != JS_FUNC_NORMAL) { + done_generator: + sf->cur_pc = pc; + sf->cur_sp = sp; + } else { + done: + if (unlikely(!list_empty(&sf->var_ref_list))) { + /* variable references reference the stack: must close them */ + close_var_refs(rt, sf); + } + /* free the local variables and stack */ + for(pval = local_buf; pval < sp; pval++) { + JS_FreeValue(ctx, *pval); + } + } + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + return JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, argv, JS_CALL_FLAG_COPY_ARGV); +} + +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, argv, JS_CALL_FLAG_COPY_ARGV); + JS_FreeValue(ctx, func_obj); + return res; +} + +/* warning: the refcount of the context is not incremented. Return + NULL in case of exception (case of revoked proxy only) */ +static JSContext *JS_GetFunctionRealm(JSContext *ctx, JSValueConst func_obj) +{ + JSObject *p; + JSContext *realm; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return ctx; + p = JS_VALUE_GET_OBJ(func_obj); + switch(p->class_id) { + case JS_CLASS_C_FUNCTION: + realm = p->u.cfunc.realm; + break; + case JS_CLASS_BYTECODE_FUNCTION: + case JS_CLASS_GENERATOR_FUNCTION: + case JS_CLASS_ASYNC_FUNCTION: + case JS_CLASS_ASYNC_GENERATOR_FUNCTION: + { + JSFunctionBytecode *b; + b = p->u.func.function_bytecode; + realm = b->realm; + } + break; + case JS_CLASS_PROXY: + { + JSProxyData *s = p->u.opaque; + if (!s) + return ctx; + if (s->is_revoked) { + JS_ThrowTypeErrorRevokedProxy(ctx); + return NULL; + } else { + realm = JS_GetFunctionRealm(ctx, s->target); + } + } + break; + case JS_CLASS_BOUND_FUNCTION: + { + JSBoundFunction *bf = p->u.bound_function; + realm = JS_GetFunctionRealm(ctx, bf->func_obj); + } + break; + default: + realm = ctx; + break; + } + return realm; +} + +static JSValue js_create_from_ctor(JSContext *ctx, JSValueConst ctor, + int class_id) +{ + JSValue proto, obj; + JSContext *realm; + + if (JS_IsUndefined(ctor)) { + proto = js_dup(ctx->class_proto[class_id]); + } else { + proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(proto)) { + JS_FreeValue(ctx, proto); + realm = JS_GetFunctionRealm(ctx, ctor); + if (!realm) + return JS_EXCEPTION; + proto = js_dup(realm->class_proto[class_id]); + } + } + obj = JS_NewObjectProtoClass(ctx, proto, class_id); + JS_FreeValue(ctx, proto); + return obj; +} + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValueConst *argv, + int flags) +{ + JSObject *p; + JSFunctionBytecode *b; + + if (js_poll_interrupts(ctx)) + return JS_EXCEPTION; + flags |= JS_CALL_FLAG_CONSTRUCTOR; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) + goto not_a_function; + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(!p->is_constructor)) + return JS_ThrowTypeErrorNotAConstructor(ctx, func_obj); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = ctx->rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeErrorNotAFunction(ctx); + } + return call_func(ctx, func_obj, new_target, argc, + argv, flags); + } + + b = p->u.func.function_bytecode; + if (b->is_derived_class_constructor) { + return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags); + } else { + JSValue obj, ret; + /* legacy constructor behavior */ + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); + if (JS_IsException(obj)) + return JS_EXCEPTION; + ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags); + if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT || + JS_IsException(ret)) { + JS_FreeValue(ctx, obj); + return ret; + } else { + JS_FreeValue(ctx, ret); + return obj; + } + } +} + +JSValue JS_CallConstructor2(JSContext *ctx, JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValueConst *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, new_target, + argc, argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_CallConstructor(JSContext *ctx, JSValueConst func_obj, + int argc, JSValueConst *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, func_obj, + argc, argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_Invoke(JSContext *ctx, JSValueConst this_val, JSAtom atom, + int argc, JSValueConst *argv) +{ + JSValue func_obj; + func_obj = JS_GetProperty(ctx, this_val, atom); + if (JS_IsException(func_obj)) + return func_obj; + return JS_CallFree(ctx, func_obj, this_val, argc, argv); +} + +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValueConst *argv) +{ + JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv); + JS_FreeValue(ctx, this_val); + return res; +} + +/* JSAsyncFunctionState (used by generator and async functions) */ +static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s, + JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame *sf; + int local_count, i, arg_buf_len, n; + + sf = &s->frame; + init_list_head(&sf->var_ref_list); + p = JS_VALUE_GET_OBJ(func_obj); + b = p->u.func.function_bytecode; + sf->is_strict_mode = b->is_strict_mode; + sf->cur_pc = b->byte_code_buf; + arg_buf_len = max_int(b->arg_count, argc); + local_count = arg_buf_len + b->var_count + b->stack_size; + sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1)); + if (!sf->arg_buf) + return -1; + sf->cur_func = js_dup(func_obj); + s->this_val = js_dup(this_obj); + s->argc = argc; + sf->arg_count = arg_buf_len; + sf->var_buf = sf->arg_buf + arg_buf_len; + sf->cur_sp = sf->var_buf + b->var_count; + for(i = 0; i < argc; i++) + sf->arg_buf[i] = js_dup(argv[i]); + n = arg_buf_len + b->var_count; + for(i = argc; i < n; i++) + sf->arg_buf[i] = JS_UNDEFINED; + return 0; +} + +static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, + JS_MarkFunc *mark_func) +{ + JSStackFrame *sf; + JSValue *sp; + + sf = &s->frame; + JS_MarkValue(rt, sf->cur_func, mark_func); + JS_MarkValue(rt, s->this_val, mark_func); + if (sf->cur_sp) { + /* if the function is running, cur_sp is not known so we + cannot mark the stack. Marking the variables is not needed + because a running function cannot be part of a removable + cycle */ + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) + JS_MarkValue(rt, *sp, mark_func); + } +} + +static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +{ + JSStackFrame *sf; + JSValue *sp; + + sf = &s->frame; + + /* close the closure variables. */ + close_var_refs(rt, sf); + + if (sf->arg_buf) { + /* cannot free the function if it is running */ + assert(sf->cur_sp != NULL); + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) { + JS_FreeValueRT(rt, *sp); + } + js_free_rt(rt, sf->arg_buf); + } + JS_FreeValueRT(rt, sf->cur_func); + JS_FreeValueRT(rt, s->this_val); +} + +static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) +{ + JSValue func_obj; + + if (js_check_stack_overflow(ctx->rt, 0)) + return JS_ThrowStackOverflow(ctx); + + /* the tag does not matter provided it is not an object */ + func_obj = JS_MKPTR(JS_TAG_INT, s); + return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, + s->argc, vc(s->frame.arg_buf), + JS_CALL_FLAG_GENERATOR); +} + + +/* Generators */ + +typedef enum JSGeneratorStateEnum { + JS_GENERATOR_STATE_SUSPENDED_START, + JS_GENERATOR_STATE_SUSPENDED_YIELD, + JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_GENERATOR_STATE_EXECUTING, + JS_GENERATOR_STATE_COMPLETED, +} JSGeneratorStateEnum; + +typedef struct JSGeneratorData { + JSGeneratorStateEnum state; + JSAsyncFunctionState func_state; +} JSGeneratorData; + +static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s) +{ + if (s->state == JS_GENERATOR_STATE_COMPLETED) + return; + async_func_free(rt, &s->func_state); + s->state = JS_GENERATOR_STATE_COMPLETED; +} + +static void js_generator_finalizer(JSRuntime *rt, JSValueConst obj) +{ + JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR); + + if (s) { + free_generator_stack_rt(rt, s); + js_free_rt(rt, s); + } +} + +static void free_generator_stack(JSContext *ctx, JSGeneratorData *s) +{ + free_generator_stack_rt(ctx->rt, s); +} + +static void js_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSGeneratorData *s = p->u.generator_data; + + if (!s || s->state == JS_GENERATOR_STATE_COMPLETED) + return; + async_func_mark(rt, &s->func_state, mark_func); +} + +/* XXX: use enum */ +#define GEN_MAGIC_NEXT 0 +#define GEN_MAGIC_RETURN 1 +#define GEN_MAGIC_THROW 2 + +static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int *pdone, int magic) +{ + JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR); + JSStackFrame *sf; + JSValue ret, func_ret; + + *pdone = true; + if (!s) + return JS_ThrowTypeError(ctx, "not a generator"); + sf = &s->func_state.frame; + switch(s->state) { + default: + case JS_GENERATOR_STATE_SUSPENDED_START: + if (magic == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + free_generator_stack(ctx, s); + goto done; + } + break; + case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + case JS_GENERATOR_STATE_SUSPENDED_YIELD: + /* cur_sp[-1] was set to JS_UNDEFINED in the previous call */ + ret = js_dup(argv[0]); + if (magic == GEN_MAGIC_THROW && + s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, ret); + s->func_state.throw_flag = true; + } else { + sf->cur_sp[-1] = ret; + sf->cur_sp[0] = js_int32(magic); + sf->cur_sp++; + exec_no_arg: + s->func_state.throw_flag = false; + } + s->state = JS_GENERATOR_STATE_EXECUTING; + func_ret = async_func_resume(ctx, &s->func_state); + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD; + if (JS_IsException(func_ret)) { + /* finalize the execution in case of exception */ + free_generator_stack(ctx, s); + return func_ret; + } + if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { + /* get the returned yield value at the top of the stack */ + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) { + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + /* return (value, done) object */ + *pdone = 2; + } else { + *pdone = false; + } + } else { + /* end of iterator */ + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + JS_FreeValue(ctx, func_ret); + free_generator_stack(ctx, s); + } + break; + case JS_GENERATOR_STATE_COMPLETED: + done: + /* execution is finished */ + switch(magic) { + default: + case GEN_MAGIC_NEXT: + ret = JS_UNDEFINED; + break; + case GEN_MAGIC_RETURN: + ret = js_dup(argv[0]); + break; + case GEN_MAGIC_THROW: + ret = JS_Throw(ctx, js_dup(argv[0])); + break; + } + break; + case JS_GENERATOR_STATE_EXECUTING: + ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator"); + break; + } + return ret; +} + +static JSValue js_call_generator_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSValue obj, func_ret; + JSGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_GENERATOR_STATE_SUSPENDED_START; + if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + s->state = JS_GENERATOR_STATE_COMPLETED; + goto fail; + } + + /* execute the function up to 'OP_initial_yield' */ + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR); + if (JS_IsException(obj)) + goto fail; + JS_SetOpaqueInternal(obj, s); + return obj; + fail: + free_generator_stack_rt(ctx->rt, s); + js_free(ctx, s); + return JS_EXCEPTION; +} + +/* AsyncFunction */ + +static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s) +{ + if (s->is_active) { + async_func_free(rt, &s->func_state); + s->is_active = false; + } +} + +static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s) +{ + js_async_function_terminate(rt, s); + JS_FreeValueRT(rt, s->resolving_funcs[0]); + JS_FreeValueRT(rt, s->resolving_funcs[1]); + remove_gc_object(&s->header); + js_free_rt(rt, s); +} + +static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s) +{ + if (--s->header.ref_count == 0) { + js_async_function_free0(rt, s); + } +} + +static void js_async_function_resolve_finalizer(JSRuntime *rt, + JSValueConst val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionData *s = p->u.async_function_data; + if (s) { + js_async_function_free(rt, s); + } +} + +static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionData *s = p->u.async_function_data; + if (s) { + mark_func(rt, &s->header); + } +} + +static int js_async_function_resolve_create(JSContext *ctx, + JSAsyncFunctionData *s, + JSValue *resolving_funcs) +{ + int i; + JSObject *p; + + for(i = 0; i < 2; i++) { + resolving_funcs[i] = + JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_ASYNC_FUNCTION_RESOLVE + i); + if (JS_IsException(resolving_funcs[i])) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + return -1; + } + p = JS_VALUE_GET_OBJ(resolving_funcs[i]); + s->header.ref_count++; + p->u.async_function_data = s; + } + return 0; +} + +static bool js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s) +{ + bool is_success = true; + JSValue func_ret, ret2; + + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) { + fail: + if (unlikely(JS_IsUncatchableError(ctx->rt->current_exception))) { + is_success = false; + } else { + JSValue error = JS_GetException(ctx); + ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED, + 1, vc(&error)); + JS_FreeValue(ctx, error); + resolved: + if (unlikely(JS_IsException(ret2))) { + if (JS_IsUncatchableError(ctx->rt->current_exception)) { + is_success = false; + } else { + abort(); /* BUG */ + } + } + JS_FreeValue(ctx, ret2); + } + js_async_function_terminate(ctx->rt, s); + } else { + JSValue value; + value = s->func_state.frame.cur_sp[-1]; + s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; + if (JS_IsUndefined(func_ret)) { + /* function returned */ + ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED, + 1, vc(&value)); + JS_FreeValue(ctx, value); + goto resolved; + } else { + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + /* await */ + JS_FreeValue(ctx, func_ret); /* not used */ + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, vc(&value), 0); + JS_FreeValue(ctx, value); + if (JS_IsException(promise)) + goto fail; + if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + vc(resolving_funcs), + vc(resolving_funcs1)); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + } + } + return is_success; +} + +static JSValue js_async_function_resolve_call(JSContext *ctx, + JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSObject *p = JS_VALUE_GET_OBJ(func_obj); + JSAsyncFunctionData *s = p->u.async_function_data; + bool is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE; + JSValueConst arg; + + if (argc > 0) + arg = argv[0]; + else + arg = JS_UNDEFINED; + s->func_state.throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, js_dup(arg)); + } else { + /* return value of await */ + s->func_state.frame.cur_sp[-1] = js_dup(arg); + } + if (!js_async_function_resume(ctx, s)) + return JS_EXCEPTION; + return JS_UNDEFINED; +} + +static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSValue promise; + JSAsyncFunctionData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->header.ref_count = 1; + add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); + s->is_active = false; + s->resolving_funcs[0] = JS_UNDEFINED; + s->resolving_funcs[1] = JS_UNDEFINED; + + promise = JS_NewPromiseCapability(ctx, s->resolving_funcs); + if (JS_IsException(promise)) + goto fail; + + if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + fail: + JS_FreeValue(ctx, promise); + js_async_function_free(ctx->rt, s); + return JS_EXCEPTION; + } + s->is_active = true; + + if (!js_async_function_resume(ctx, s)) + goto fail; + + js_async_function_free(ctx->rt, s); + + return promise; +} + +/* AsyncGenerator */ + +typedef enum JSAsyncGeneratorStateEnum { + JS_ASYNC_GENERATOR_STATE_SUSPENDED_START, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_ASYNC_GENERATOR_STATE_EXECUTING, + JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN, + JS_ASYNC_GENERATOR_STATE_COMPLETED, +} JSAsyncGeneratorStateEnum; + +typedef struct JSAsyncGeneratorRequest { + struct list_head link; + /* completion */ + int completion_type; /* GEN_MAGIC_x */ + JSValue result; + /* promise capability */ + JSValue promise; + JSValue resolving_funcs[2]; +} JSAsyncGeneratorRequest; + +typedef struct JSAsyncGeneratorData { + JSObject *generator; /* back pointer to the object (const) */ + JSAsyncGeneratorStateEnum state; + JSAsyncFunctionState func_state; + struct list_head queue; /* list of JSAsyncGeneratorRequest.link */ +} JSAsyncGeneratorData; + +static void js_async_generator_free(JSRuntime *rt, + JSAsyncGeneratorData *s) +{ + struct list_head *el, *el1; + JSAsyncGeneratorRequest *req; + + list_for_each_safe(el, el1, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_FreeValueRT(rt, req->result); + JS_FreeValueRT(rt, req->promise); + JS_FreeValueRT(rt, req->resolving_funcs[0]); + JS_FreeValueRT(rt, req->resolving_funcs[1]); + js_free_rt(rt, req); + } + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && + s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { + async_func_free(rt, &s->func_state); + } + js_free_rt(rt, s); +} + +static void js_async_generator_finalizer(JSRuntime *rt, JSValueConst obj) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR); + + if (s) { + js_async_generator_free(rt, s); + } +} + +static void js_async_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR); + struct list_head *el; + JSAsyncGeneratorRequest *req; + if (s) { + list_for_each(el, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_MarkValue(rt, req->result, mark_func); + JS_MarkValue(rt, req->promise, mark_func); + JS_MarkValue(rt, req->resolving_funcs[0], mark_func); + JS_MarkValue(rt, req->resolving_funcs[1], mark_func); + } + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && + s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { + async_func_mark(rt, &s->func_state, mark_func); + } + } +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int magic, JSValueConst *func_data); + +static int js_async_generator_resolve_function_create(JSContext *ctx, + JSValue generator, + JSValue *resolving_funcs, + bool is_resume_next) +{ + int i; + JSValue func; + + for(i = 0; i < 2; i++) { + func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1, + i + is_resume_next * 2, 1, vc(&generator)); + if (JS_IsException(func)) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + return -1; + } + resolving_funcs[i] = func; + } + return 0; +} + +static int js_async_generator_await(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValue value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, vc(&value), 0); + if (JS_IsException(promise)) + goto fail; + + if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs, false)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + vc(resolving_funcs), + vc(resolving_funcs1)); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + return 0; + fail: + return -1; +} + +static void js_async_generator_resolve_or_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst result, + int is_reject) +{ + JSAsyncGeneratorRequest *next; + JSValue ret; + + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + list_del(&next->link); + ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1, + &result); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, next->result); + JS_FreeValue(ctx, next->promise); + JS_FreeValue(ctx, next->resolving_funcs[0]); + JS_FreeValue(ctx, next->resolving_funcs[1]); + js_free(ctx, next); +} + +static void js_async_generator_resolve(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst value, + bool done) +{ + JSValue result; + result = js_create_iterator_result(ctx, js_dup(value), done); + /* XXX: better exception handling ? */ + js_async_generator_resolve_or_reject(ctx, s, result, 0); + JS_FreeValue(ctx, result); + } + +static void js_async_generator_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst exception) +{ + js_async_generator_resolve_or_reject(ctx, s, exception, 1); +} + +static void js_async_generator_complete(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) { + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + async_func_free(ctx->rt, &s->func_state); + } +} + +static int js_async_generator_completed_return(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValue value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int res; + + // Can fail looking up JS_ATOM_constructor when is_reject==0. + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, vc(&value), + /*is_reject*/0); + // A poisoned .constructor property is observable and the resulting + // exception should be delivered to the catch handler. + if (JS_IsException(promise)) { + JSValue err = JS_GetException(ctx); + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, vc(&err), + /*is_reject*/1); + JS_FreeValue(ctx, err); + if (JS_IsException(promise)) + return -1; + } + if (js_async_generator_resolve_function_create(ctx, + JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs1, + true)) { + JS_FreeValue(ctx, promise); + return -1; + } + resolving_funcs[0] = JS_UNDEFINED; + resolving_funcs[1] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + vc(resolving_funcs1), + vc(resolving_funcs)); + JS_FreeValue(ctx, resolving_funcs1[0]); + JS_FreeValue(ctx, resolving_funcs1[1]); + JS_FreeValue(ctx, promise); + return res; +} + +static void js_async_generator_resume_next(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + JSAsyncGeneratorRequest *next; + JSValue func_ret, value; + + for(;;) { + if (list_empty(&s->queue)) + break; + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + switch(s->state) { + case JS_ASYNC_GENERATOR_STATE_EXECUTING: + /* only happens when restarting execution after await() */ + goto resume_exec; + case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN: + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START: + if (next->completion_type == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + js_async_generator_complete(ctx, s); + } + break; + case JS_ASYNC_GENERATOR_STATE_COMPLETED: + if (next->completion_type == GEN_MAGIC_NEXT) { + js_async_generator_resolve(ctx, s, JS_UNDEFINED, true); + } else if (next->completion_type == GEN_MAGIC_RETURN) { + s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN; + js_async_generator_completed_return(ctx, s, next->result); + } else { + js_async_generator_reject(ctx, s, next->result); + } + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD: + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + value = js_dup(next->result); + if (next->completion_type == GEN_MAGIC_THROW && + s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, value); + s->func_state.throw_flag = true; + } else { + /* 'yield' returns a value. 'yield *' also returns a value + in case the 'throw' method is called */ + s->func_state.frame.cur_sp[-1] = value; + s->func_state.frame.cur_sp[0] = + js_int32(next->completion_type); + s->func_state.frame.cur_sp++; + exec_no_arg: + s->func_state.throw_flag = false; + } + s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING; + resume_exec: + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) { + value = JS_GetException(ctx); + js_async_generator_complete(ctx, s); + js_async_generator_reject(ctx, s, value); + JS_FreeValue(ctx, value); + } else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { + int func_ret_code, ret; + value = s->func_state.frame.cur_sp[-1]; + s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; + func_ret_code = JS_VALUE_GET_INT(func_ret); + switch(func_ret_code) { + case FUNC_RET_YIELD: + case FUNC_RET_YIELD_STAR: + if (func_ret_code == FUNC_RET_YIELD_STAR) + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + else + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD; + js_async_generator_resolve(ctx, s, value, false); + JS_FreeValue(ctx, value); + break; + case FUNC_RET_AWAIT: + ret = js_async_generator_await(ctx, s, value); + JS_FreeValue(ctx, value); + if (ret < 0) { + /* exception: throw it */ + s->func_state.throw_flag = true; + goto resume_exec; + } + goto done; + default: + abort(); + } + } else { + assert(JS_IsUndefined(func_ret)); + /* end of function */ + value = s->func_state.frame.cur_sp[-1]; + s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; + js_async_generator_complete(ctx, s); + js_async_generator_resolve(ctx, s, value, true); + JS_FreeValue(ctx, value); + } + break; + default: + abort(); + } + } + done: ; +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int magic, JSValueConst *func_data) +{ + bool is_reject = magic & 1; + JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR); + JSValueConst arg = argv[0]; + + /* XXX: what if s == NULL */ + + if (magic >= 2) { + /* resume next case in AWAITING_RETURN state */ + assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN || + s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED); + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + if (is_reject) { + js_async_generator_reject(ctx, s, arg); + } else { + js_async_generator_resolve(ctx, s, arg, true); + } + } else { + /* restart function execution after await() */ + assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); + s->func_state.throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, js_dup(arg)); + } else { + /* return value of await */ + s->func_state.frame.cur_sp[-1] = js_dup(arg); + } + js_async_generator_resume_next(ctx, s); + } + return JS_UNDEFINED; +} + +/* magic = GEN_MAGIC_x */ +static JSValue js_async_generator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int magic) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR); + JSValue promise, resolving_funcs[2]; + JSAsyncGeneratorRequest *req; + + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + if (!s) { + JSValue err, res2; + JS_ThrowTypeError(ctx, "not an AsyncGenerator object"); + err = JS_GetException(ctx); + res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, + 1, vc(&err)); + JS_FreeValue(ctx, err); + JS_FreeValue(ctx, res2); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; + } + req = js_mallocz(ctx, sizeof(*req)); + if (!req) + goto fail; + req->completion_type = magic; + req->result = js_dup(argv[0]); + req->promise = js_dup(promise); + req->resolving_funcs[0] = resolving_funcs[0]; + req->resolving_funcs[1] = resolving_funcs[1]; + list_add_tail(&req->link, &s->queue); + if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) { + js_async_generator_resume_next(ctx, s); + } + return promise; + fail: + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + JS_FreeValue(ctx, promise); + return JS_EXCEPTION; +} + +static JSValue js_async_generator_function_call(JSContext *ctx, + JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSValue obj, func_ret; + JSAsyncGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START; + init_list_head(&s->queue); + if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + goto fail; + } + + /* execute the function up to 'OP_initial_yield' (no yield nor + await are possible) */ + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR); + if (JS_IsException(obj)) + goto fail; + s->generator = JS_VALUE_GET_OBJ(obj); + JS_SetOpaqueInternal(obj, s); + return obj; + fail: + js_async_generator_free(ctx->rt, s); + return JS_EXCEPTION; +} + +/* JS parser */ + +enum { + TOK_NUMBER = -128, + TOK_STRING, + TOK_TEMPLATE, + TOK_IDENT, + TOK_REGEXP, + /* warning: order matters (see js_parse_assign_expr) */ + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_MOD_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_SHL_ASSIGN, + TOK_SAR_ASSIGN, + TOK_SHR_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, + TOK_POW_ASSIGN, + TOK_LAND_ASSIGN, + TOK_LOR_ASSIGN, + TOK_DOUBLE_QUESTION_MARK_ASSIGN, + TOK_DEC, + TOK_INC, + TOK_SHL, + TOK_SAR, + TOK_SHR, + TOK_LT, + TOK_LTE, + TOK_GT, + TOK_GTE, + TOK_EQ, + TOK_STRICT_EQ, + TOK_NEQ, + TOK_STRICT_NEQ, + TOK_LAND, + TOK_LOR, + TOK_POW, + TOK_ARROW, + TOK_ELLIPSIS, + TOK_DOUBLE_QUESTION_MARK, + TOK_QUESTION_MARK_DOT, + TOK_ERROR, + TOK_PRIVATE_NAME, + TOK_EOF, + /* keywords: WARNING: same order as atoms */ + TOK_NULL, /* must be first */ + TOK_FALSE, + TOK_TRUE, + TOK_IF, + TOK_ELSE, + TOK_RETURN, + TOK_VAR, + TOK_THIS, + TOK_DELETE, + TOK_VOID, + TOK_TYPEOF, + TOK_NEW, + TOK_IN, + TOK_INSTANCEOF, + TOK_DO, + TOK_WHILE, + TOK_FOR, + TOK_BREAK, + TOK_CONTINUE, + TOK_SWITCH, + TOK_CASE, + TOK_DEFAULT, + TOK_THROW, + TOK_TRY, + TOK_CATCH, + TOK_FINALLY, + TOK_FUNCTION, + TOK_DEBUGGER, + TOK_WITH, + /* FutureReservedWord */ + TOK_CLASS, + TOK_CONST, + TOK_ENUM, + TOK_EXPORT, + TOK_EXTENDS, + TOK_IMPORT, + TOK_SUPER, + /* FutureReservedWords when parsing strict mode code */ + TOK_IMPLEMENTS, + TOK_INTERFACE, + TOK_LET, + TOK_PACKAGE, + TOK_PRIVATE, + TOK_PROTECTED, + TOK_PUBLIC, + TOK_STATIC, + TOK_YIELD, + TOK_AWAIT, /* must be last */ + TOK_OF, /* only used for js_parse_skip_parens_token() */ +}; + +#define TOK_FIRST_KEYWORD TOK_NULL +#define TOK_LAST_KEYWORD TOK_AWAIT + +/* unicode code points */ +#define CP_NBSP 0x00a0 +#define CP_BOM 0xfeff + +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +typedef struct BlockEnv { + struct BlockEnv *prev; + JSAtom label_name; /* JS_ATOM_NULL if none */ + int label_break; /* -1 if none */ + int label_cont; /* -1 if none */ + int drop_count; /* number of stack elements to drop */ + int label_finally; /* -1 if none */ + int scope_level; + uint8_t has_iterator : 1; + uint8_t is_regular_stmt : 1; // i.e. not a loop statement +} BlockEnv; + +typedef struct JSGlobalVar { + int cpool_idx; /* if >= 0, index in the constant pool for hoisted + function defintion*/ + uint8_t force_init : 1; /* force initialization to undefined */ + uint8_t is_lexical : 1; /* global let/const definition */ + uint8_t is_const : 1; /* const definition */ + int scope_level; /* scope of definition */ + JSAtom var_name; /* variable name */ +} JSGlobalVar; + +typedef struct RelocEntry { + struct RelocEntry *next; + uint32_t addr; /* address to patch */ + int size; /* address size: 1, 2 or 4 bytes */ +} RelocEntry; + +typedef struct JumpSlot { + int op; + int size; + int pos; + int label; +} JumpSlot; + +typedef struct LabelSlot { + int ref_count; + int pos; /* phase 1 address, -1 means not resolved yet */ + int pos2; /* phase 2 address, -1 means not resolved yet */ + int addr; /* phase 3 address, -1 means not resolved yet */ + RelocEntry *first_reloc; +} LabelSlot; + +typedef struct SourceLocSlot { + uint32_t pc; + int line_num; + int col_num; +} SourceLocSlot; + +typedef enum JSParseFunctionEnum { + JS_PARSE_FUNC_STATEMENT, + JS_PARSE_FUNC_VAR, + JS_PARSE_FUNC_EXPR, + JS_PARSE_FUNC_ARROW, + JS_PARSE_FUNC_GETTER, + JS_PARSE_FUNC_SETTER, + JS_PARSE_FUNC_METHOD, + JS_PARSE_FUNC_CLASS_STATIC_INIT, + JS_PARSE_FUNC_CLASS_CONSTRUCTOR, + JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, +} JSParseFunctionEnum; + +typedef enum JSParseExportEnum { + JS_PARSE_EXPORT_NONE, + JS_PARSE_EXPORT_NAMED, + JS_PARSE_EXPORT_DEFAULT, +} JSParseExportEnum; + +typedef struct JSFunctionDef { + JSContext *ctx; + struct JSFunctionDef *parent; + int parent_cpool_idx; /* index in the constant pool of the parent + or -1 if none */ + int parent_scope_level; /* scope level in parent at point of definition */ + struct list_head child_list; /* list of JSFunctionDef.link */ + struct list_head link; + + int eval_type; /* only valid if is_eval = true */ + + /* Pack all boolean flags together as 1-bit fields to reduce struct size + while avoiding padding and compiler deoptimization. */ + bool is_eval : 1; /* true if eval code */ + bool is_global_var : 1; /* true if variables are not defined locally: + eval global, eval module or non strict eval */ + bool is_func_expr : 1; /* true if function expression */ + bool has_home_object : 1; /* true if the home object is available */ + bool has_prototype : 1; /* true if a prototype field is necessary */ + bool has_simple_parameter_list : 1; + bool has_parameter_expressions : 1; /* if true, an argument scope is created */ + bool has_use_strict : 1; /* to reject directive in special cases */ + bool has_eval_call : 1; /* true if the function contains a call to eval() */ + bool has_arguments_binding : 1; /* true if the 'arguments' binding is + available in the function */ + bool has_this_binding : 1; /* true if the 'this' and new.target binding are + available in the function */ + bool new_target_allowed : 1; /* true if the 'new.target' does not + throw a syntax error */ + bool super_call_allowed : 1; /* true if super() is allowed */ + bool super_allowed : 1; /* true if super. or super[] is allowed */ + bool arguments_allowed : 1; /* true if the 'arguments' identifier is allowed */ + bool is_derived_class_constructor : 1; + bool in_function_body : 1; + bool backtrace_barrier : 1; + bool need_home_object : 1; + bool use_short_opcodes : 1; /* true if short opcodes are used in byte_code */ + bool has_await : 1; /* true if await is used (used in module eval) */ + + JSFunctionKindEnum func_kind : 8; + JSParseFunctionEnum func_type : 7; + uint8_t is_strict_mode : 1; + JSAtom func_name; /* JS_ATOM_NULL if no name */ + + JSVarDef *vars; + uint32_t *vars_htab; // indexes into vars[] + int var_size; /* allocated size for vars[] */ + int var_count; + JSVarDef *args; + int arg_size; /* allocated size for args[] */ + int arg_count; /* number of arguments */ + int defined_arg_count; + int var_object_idx; /* -1 if none */ + int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ + int arguments_var_idx; /* -1 if none */ + int arguments_arg_idx; /* argument variable definition in argument scope, + -1 if none */ + int func_var_idx; /* variable containing the current function (-1 + if none, only used if is_func_expr is true) */ + int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */ + int this_var_idx; /* variable containg the 'this' value, -1 if none */ + int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */ + int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */ + int home_object_var_idx; + + int scope_level; /* index into fd->scopes if the current lexical scope */ + int scope_first; /* index into vd->vars of first lexically scoped variable */ + int scope_size; /* allocated size of fd->scopes array */ + int scope_count; /* number of entries used in the fd->scopes array */ + JSVarScope *scopes; + JSVarScope def_scope_array[4]; + int body_scope; /* scope of the body of the function or eval */ + + int global_var_count; + int global_var_size; + JSGlobalVar *global_vars; + + DynBuf byte_code; + int last_opcode_pos; /* -1 if no last opcode */ + + LabelSlot *label_slots; + int label_size; /* allocated size for label_slots[] */ + int label_count; + BlockEnv *top_break; /* break/continue label stack */ + + /* constant pool (strings, functions, numbers) */ + JSValue *cpool; + int cpool_count; + int cpool_size; + + /* list of variables in the closure */ + int closure_var_count; + int closure_var_size; + JSClosureVar *closure_var; + + JumpSlot *jump_slots; + int jump_size; + int jump_count; + + SourceLocSlot *source_loc_slots; + int source_loc_size; + int source_loc_count; + int line_number_last; + int line_number_last_pc; + int col_number_last; + + /* pc2line table */ + JSAtom filename; + int line_num; + int col_num; + DynBuf pc2line; + + char *source; /* raw source, utf-8 encoded */ + int source_len; + + JSModuleDef *module; /* != NULL when parsing a module */ +} JSFunctionDef; + +typedef struct JSToken { + int val; + int line_num; /* line number of token start */ + int col_num; /* column number of token start */ + const uint8_t *ptr; + union { + struct { + JSValue str; + int sep; + } str; + struct { + JSValue val; + } num; + struct { + JSAtom atom; + bool has_escape; + bool is_reserved; + } ident; + struct { + JSValue body; + JSValue flags; + } regexp; + } u; +} JSToken; + +typedef struct JSParseState { + JSContext *ctx; + int last_line_num; /* line number of last token */ + int last_col_num; /* column number of last token */ + int line_num; /* line number of current offset */ + int col_num; /* column number of current offset */ + const char *filename; + JSToken token; + bool got_lf; /* true if got line feed before the current token */ + const uint8_t *last_ptr; + const uint8_t *buf_start; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + const uint8_t *eol; // most recently seen end-of-line character + const uint8_t *mark; // first token character, invariant: eol < mark + + /* current function code */ + JSFunctionDef *cur_func; + bool is_module; /* parsing a module */ + bool allow_html_comments; +} JSParseState; + +typedef struct JSOpCode { +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* + const char *name; +#endif + uint8_t size; /* in bytes */ + /* the opcodes remove n_pop items from the top of the stack, then + pushes n_push items */ + uint8_t n_pop; + uint8_t n_push; + uint8_t fmt; +} JSOpCode; + +static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { +#define FMT(f) +#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* +#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f }, +#else +#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f }, +#endif +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +}; + +/* After the final compilation pass, short opcodes are used. Their + opcodes overlap with the temporary opcodes which cannot appear in + the final bytecode. Their description is after the temporary + opcodes in opcode_info[]. */ +#define short_opcode_info(op) \ + opcode_info[(op) >= OP_TEMP_START ? \ + (op) + (OP_TEMP_END - OP_TEMP_START) : (op)] + +static void free_token(JSParseState *s, JSToken *token) +{ + switch(token->val) { + case TOK_NUMBER: + JS_FreeValue(s->ctx, token->u.num.val); + break; + case TOK_STRING: + case TOK_TEMPLATE: + JS_FreeValue(s->ctx, token->u.str.str); + break; + case TOK_REGEXP: + JS_FreeValue(s->ctx, token->u.regexp.body); + JS_FreeValue(s->ctx, token->u.regexp.flags); + break; + case TOK_IDENT: + case TOK_PRIVATE_NAME: + JS_FreeAtom(s->ctx, token->u.ident.atom); + break; + default: + if (token->val >= TOK_FIRST_KEYWORD && + token->val <= TOK_LAST_KEYWORD) { + JS_FreeAtom(s->ctx, token->u.ident.atom); + } + break; + } +} + +static void __attribute((unused)) dump_token(JSParseState *s, + const JSToken *token) +{ + printf("%d:%d ", token->line_num, token->col_num); + switch(token->val) { + case TOK_NUMBER: + { + double d; + JS_ToFloat64(s->ctx, &d, token->u.num.val); /* no exception possible */ + printf("number: %.14g\n", d); + } + break; + case TOK_IDENT: + dump_atom: + { + char buf[ATOM_GET_STR_BUF_SIZE]; + printf("ident: '%s'\n", + JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom)); + } + break; + case TOK_STRING: + { + const char *str; + /* XXX: quote the string */ + str = JS_ToCString(s->ctx, token->u.str.str); + printf("string: '%s'\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_TEMPLATE: + { + const char *str; + str = JS_ToCString(s->ctx, token->u.str.str); + printf("template: `%s`\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_REGEXP: + { + const char *str, *str2; + str = JS_ToCString(s->ctx, token->u.regexp.body); + str2 = JS_ToCString(s->ctx, token->u.regexp.flags); + printf("regexp: '%s' '%s'\n", str, str2); + JS_FreeCString(s->ctx, str); + JS_FreeCString(s->ctx, str2); + } + break; + case TOK_EOF: + printf("eof\n"); + break; + default: + if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { + goto dump_atom; + } else if (s->token.val >= 256) { + printf("token: %d\n", token->val); + } else { + printf("token: '%c'\n", token->val); + } + break; + } +} + +int JS_PRINTF_FORMAT_ATTR(2, 3) js_parse_error(JSParseState *s, JS_PRINTF_FORMAT const char *fmt, ...) +{ + JSContext *ctx = s->ctx; + va_list ap; + int backtrace_flags; + + va_start(ap, fmt); + JS_ThrowError2(ctx, JS_SYNTAX_ERROR, false, fmt, ap); + va_end(ap); + backtrace_flags = 0; + if (s->cur_func && s->cur_func->backtrace_barrier) + backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; + build_backtrace(ctx, ctx->rt->current_exception, JS_UNDEFINED, s->filename, + s->line_num, s->col_num, backtrace_flags); + return -1; +} + +#ifndef QJS_DISABLE_PARSER + +static __exception int next_token(JSParseState *s); + +static int js_parse_expect(JSParseState *s, int tok) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + + if (s->token.val == tok) + return next_token(s); + + switch(s->token.val) { + case TOK_EOF: + return js_parse_error(s, "Unexpected end of input"); + case TOK_NUMBER: + return js_parse_error(s, "Unexpected number"); + case TOK_STRING: + return js_parse_error(s, "Unexpected string"); + case TOK_TEMPLATE: + return js_parse_error(s, "Unexpected string template"); + case TOK_REGEXP: + return js_parse_error(s, "Unexpected regexp"); + case TOK_IDENT: + return js_parse_error(s, "Unexpected identifier '%s'", + JS_AtomGetStr(s->ctx, buf, sizeof(buf), + s->token.u.ident.atom)); + case TOK_ERROR: + return js_parse_error(s, "Invalid or unexpected token"); + default: + return js_parse_error(s, "Unexpected token '%.*s'", + (int)(s->buf_ptr - s->token.ptr), + (const char *)s->token.ptr); + } +} + +static int js_parse_expect_semi(JSParseState *s) +{ + if (s->token.val != ';') { + /* automatic insertion of ';' */ + if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { + return 0; + } + return js_parse_error(s, "expecting '%c'", ';'); + } + return next_token(s); +} + +static int js_parse_error_reserved_identifier(JSParseState *s) +{ + char buf1[ATOM_GET_STR_BUF_SIZE]; + return js_parse_error(s, "'%s' is a reserved identifier", + JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), + s->token.u.ident.atom)); +} + +static __exception int js_parse_template_part(JSParseState *s, + const uint8_t *p) +{ + const uint8_t *p_next; + uint32_t c; + StringBuffer b_s, *b = &b_s; + JSValue str; + + /* p points to the first byte of the template part */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + if (c == '`') { + /* template end part */ + break; + } + if (c == '$' && *p == '{') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + } + /* newline sequences are normalized as single '\n' bytes */ + if (c == '\r') { + if (*p == '\n') + p++; + c = '\n'; + } + if (c == '\n') { + s->line_num++; + s->eol = &p[-1]; + s->mark = p; + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + str = string_buffer_end(b); + if (JS_IsException(str)) + return -1; + s->token.val = TOK_TEMPLATE; + s->token.u.str.sep = c; + s->token.u.str.str = str; + s->buf_ptr = p; + return 0; + + unexpected_eof: + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static __exception int js_parse_string(JSParseState *s, int sep, + bool do_throw, const uint8_t *p, + JSToken *token, const uint8_t **pp) +{ + const uint8_t *p_next; + int ret; + uint32_t c; + StringBuffer b_s, *b = &b_s; + JSValue str; + + /* string */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto invalid_char; + c = *p; + if (c < 0x20) { + if (sep == '`') { + if (c == '\r') { + if (p[1] == '\n') + p++; + c = '\n'; + } + /* do not update s->line_num */ + } else if (c == '\n' || c == '\r') + goto invalid_char; + } + p++; + if (c == sep) + break; + if (c == '$' && *p == '{' && sep == '`') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + c = *p; + switch(c) { + case '\0': + if (p >= s->buf_end) { + if (sep != '`') + goto invalid_char; + if (do_throw) + js_parse_error(s, "Unexpected end of input"); + goto fail; + } + p++; + break; + case '\'': + case '\"': + case '\\': + p++; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + /* ignore escaped newline sequence */ + p++; + if (sep != '`') { + s->line_num++; + s->eol = &p[-1]; + s->mark = p; + } + continue; + default: + if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { + /* accept isolated \0 */ + p++; + c = '\0'; + } else + if ((c >= '0' && c <= '9') + && (s->cur_func->is_strict_mode || sep == '`')) { + if (do_throw) { + js_parse_error(s, "%s are not allowed in %s", + (c >= '8') ? "\\8 and \\9" : "Octal escape sequences", + (sep == '`') ? "template strings" : "strict mode"); + } + goto fail; + } else if (c >= 0x80) { + c = utf8_decode(p, &p_next); + if (p_next == p + 1) { + goto invalid_utf8; + } + p = p_next; + /* LS or PS are skipped */ + if (c == CP_LS || c == CP_PS) + continue; + } else { + ret = lre_parse_escape(&p, true); + if (ret == -1) { + if (do_throw) { + js_parse_error(s, "Invalid %s escape sequence", + c == 'u' ? "Unicode" : "hexadecimal"); + } + goto fail; + } else if (ret < 0) { + /* ignore the '\' (could output a warning) */ + p++; + } else { + c = ret; + } + } + break; + } + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) + goto invalid_utf8; + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + str = string_buffer_end(b); + if (JS_IsException(str)) + return -1; + token->val = TOK_STRING; + token->u.str.sep = c; + token->u.str.str = str; + *pp = p; + return 0; + + invalid_utf8: + if (do_throw) + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + invalid_char: + if (do_throw) + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static inline bool token_is_pseudo_keyword(JSParseState *s, JSAtom atom) { + return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom && + !s->token.u.ident.has_escape; +} + +static __exception int js_parse_regexp(JSParseState *s) +{ + const uint8_t *p, *p_next; + bool in_class; + StringBuffer b_s, *b = &b_s; + StringBuffer b2_s, *b2 = &b2_s; + uint32_t c; + JSValue body_str, flags_str; + + p = s->buf_ptr; + p++; + in_class = false; + if (string_buffer_init(s->ctx, b, 32)) + return -1; + if (string_buffer_init(s->ctx, b2, 1)) + goto fail; + for(;;) { + if (p >= s->buf_end) { + eof_error: + js_parse_error(s, "unexpected end of regexp"); + goto fail; + } + c = *p++; + if (c == '\n' || c == '\r') { + goto eol_error; + } else if (c == '/') { + if (!in_class) + break; + } else if (c == '[') { + in_class = true; + } else if (c == ']') { + /* XXX: incorrect as the first character in a class */ + in_class = false; + } else if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + c = *p++; + if (c == '\n' || c == '\r') + goto eol_error; + else if (c == '\0' && p >= s->buf_end) + goto eof_error; + else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + goto invalid_utf8; + } + p = p_next; + if (c == CP_LS || c == CP_PS) + goto eol_error; + } + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + invalid_utf8: + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + eol_error: + js_parse_error(s, "unexpected line terminator in regexp"); + goto fail; + } + } + if (string_buffer_putc(b, c)) + goto fail; + } + + /* flags */ + for(;;) { + c = utf8_decode(p, &p_next); + /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ + if (!lre_js_is_ident_next(c)) + break; + if (string_buffer_putc(b2, c)) + goto fail; + p = p_next; + } + + body_str = string_buffer_end(b); + flags_str = string_buffer_end(b2); + if (JS_IsException(body_str) || + JS_IsException(flags_str)) { + JS_FreeValue(s->ctx, body_str); + JS_FreeValue(s->ctx, flags_str); + return -1; + } + s->token.val = TOK_REGEXP; + s->token.u.regexp.body = body_str; + s->token.u.regexp.flags = flags_str; + s->buf_ptr = p; + return 0; + fail: + string_buffer_free(b); + string_buffer_free(b2); + return -1; +} + +#endif // QJS_DISABLE_PARSER + +static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize, + char *static_buf) +{ + char *buf, *new_buf; + size_t size, new_size; + + buf = *pbuf; + size = *psize; + if (size >= (SIZE_MAX / 3) * 2) + new_size = SIZE_MAX; + else + new_size = size + (size >> 1); + if (buf == static_buf) { + new_buf = js_malloc(ctx, new_size); + if (!new_buf) + return -1; + memcpy(new_buf, buf, size); + } else { + new_buf = js_realloc(ctx, buf, new_size); + if (!new_buf) + return -1; + } + *pbuf = new_buf; + *psize = new_size; + return 0; +} + +#ifndef QJS_DISABLE_PARSER + +/* convert a TOK_IDENT to a keyword when needed */ +static void update_token_ident(JSParseState *s) +{ + if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || + (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && + s->cur_func->is_strict_mode) || + (s->token.u.ident.atom == JS_ATOM_yield && + ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || + (s->token.u.ident.atom == JS_ATOM_await && + (s->is_module || + (s->cur_func->func_kind & JS_FUNC_ASYNC) || + s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + ((s->cur_func->parent->func_kind & JS_FUNC_ASYNC) || + s->cur_func->parent->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))))) { + if (s->token.u.ident.has_escape) { + s->token.u.ident.is_reserved = true; + s->token.val = TOK_IDENT; + } else { + /* The keywords atoms are pre allocated */ + s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; + } + } +} + +/* if the current token is an identifier or keyword, reparse it + according to the current function type */ +static void reparse_ident_token(JSParseState *s) +{ + if (s->token.val == TOK_IDENT || + (s->token.val >= TOK_FIRST_KEYWORD && + s->token.val <= TOK_LAST_KEYWORD)) { + s->token.val = TOK_IDENT; + s->token.u.ident.is_reserved = false; + update_token_ident(s); + } +} + +/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ +static JSAtom parse_ident(JSParseState *s, const uint8_t **pp, + bool *pident_has_escape, int c, bool is_private) +{ + const uint8_t *p, *p_next; + char ident_buf[128], *buf; + size_t ident_size, ident_pos; + JSAtom atom = JS_ATOM_NULL; + + p = *pp; + buf = ident_buf; + ident_size = sizeof(ident_buf); + ident_pos = 0; + if (is_private) + buf[ident_pos++] = '#'; + for(;;) { + if (c < 0x80) { + buf[ident_pos++] = c; + } else { + ident_pos += utf8_encode((uint8_t*)buf + ident_pos, c); + } + c = *p; + p_next = p + 1; + if (c == '\\' && *p_next == 'u') { + c = lre_parse_escape(&p_next, true); + *pident_has_escape = true; + } else if (c >= 0x80) { + c = utf8_decode(p, &p_next); + /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ + } + if (!lre_js_is_ident_next(c)) + break; + p = p_next; + if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { + if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) + goto done; + } + } + /* buf is pure ASCII or UTF-8 encoded */ + atom = JS_NewAtomLen(s->ctx, buf, ident_pos); + done: + if (unlikely(buf != ident_buf)) + js_free(s->ctx, buf); + *pp = p; + return atom; +} + + +static __exception int next_token(JSParseState *s) +{ + const uint8_t *p, *p_next; + int c; + bool ident_has_escape; + JSAtom atom; + + if (js_check_stack_overflow(s->ctx->rt, 1000)) { + JS_ThrowStackOverflow(s->ctx); + return -1; + } + + free_token(s, &s->token); + + p = s->last_ptr = s->buf_ptr; + s->got_lf = false; + s->last_line_num = s->token.line_num; + s->last_col_num = s->token.col_num; + redo: + s->token.line_num = s->line_num; + s->token.col_num = s->col_num; + s->token.ptr = p; + c = *p; + switch(c) { + case 0: + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { + goto def_token; + } + break; + case '`': + if (js_parse_template_part(s, p + 1)) + goto fail; + p = s->buf_ptr; + break; + case '\'': + case '\"': + if (js_parse_string(s, c, true, p + 1, &s->token, &p)) + goto fail; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + p++; + line_terminator: + s->eol = &p[-1]; + s->mark = p; + s->got_lf = true; + s->line_num++; + goto redo; + case '\f': + case '\v': + case ' ': + case '\t': + s->mark = ++p; + goto redo; + case '/': + if (p[1] == '*') { + /* comment */ + p += 2; + for(;;) { + if (*p == '\0' && p >= s->buf_end) { + js_parse_error(s, "unexpected end of comment"); + goto fail; + } + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + if (*p == '\n') { + s->line_num++; + s->got_lf = true; /* considered as LF for ASI */ + s->eol = p++; + s->mark = p; + } else if (*p == '\r') { + s->got_lf = true; /* considered as LF for ASI */ + p++; + } else if (*p >= 0x80) { + c = utf8_decode(p, &p); + /* ignore invalid UTF-8 in comments */ + if (c == CP_LS || c == CP_PS) { + s->got_lf = true; /* considered as LF for ASI */ + } + } else { + p++; + } + } + s->mark = p; + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + skip_line_comment: + for(;;) { + if (*p == '\0' && p >= s->buf_end) + break; + if (*p == '\r' || *p == '\n') + break; + if (*p >= 0x80) { + c = utf8_decode(p, &p); + /* ignore invalid UTF-8 in comments */ + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + break; + } + } else { + p++; + } + } + s->mark = p; + goto redo; + } else if (p[1] == '=') { + p += 2; + s->token.val = TOK_DIV_ASSIGN; + } else { + p++; + s->token.val = c; + } + break; + case '\\': + if (p[1] == 'u') { + const uint8_t *p1 = p + 1; + int c1 = lre_parse_escape(&p1, true); + if (c1 >= 0 && lre_js_is_ident_first(c1)) { + c = c1; + p = p1; + ident_has_escape = true; + goto has_ident; + } else { + /* XXX: syntax error? */ + } + } + goto def_token; + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case '$': + /* identifier */ + s->mark = p; + p++; + ident_has_escape = false; + has_ident: + atom = parse_ident(s, &p, &ident_has_escape, c, false); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.u.ident.has_escape = ident_has_escape; + s->token.u.ident.is_reserved = false; + s->token.val = TOK_IDENT; + update_token_ident(s); + break; + case '#': + /* private name */ + { + p++; + c = *p; + p_next = p + 1; + if (c == '\\' && *p_next == 'u') { + c = lre_parse_escape(&p_next, true); + } else if (c >= 0x80) { + c = utf8_decode(p, &p_next); + if (p_next == p + 1) + goto invalid_utf8; + } + if (!lre_js_is_ident_first(c)) { + js_parse_error(s, "invalid first character of private name"); + goto fail; + } + p = p_next; + ident_has_escape = false; /* not used */ + atom = parse_ident(s, &p, &ident_has_escape, c, true); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.val = TOK_PRIVATE_NAME; + } + break; + case '.': + if (p[1] == '.' && p[2] == '.') { + p += 3; + s->token.val = TOK_ELLIPSIS; + break; + } + if (p[1] >= '0' && p[1] <= '9') { + goto parse_number; + } else { + goto def_token; + } + break; + case '0': + /* in strict mode, octal literals are not accepted */ + if (is_digit(p[1]) && (s->cur_func->is_strict_mode)) { + js_parse_error(s, "Octal literals are not allowed in strict mode"); + goto fail; + } + goto parse_number; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + /* number */ + parse_number: + { + JSValue ret; + const uint8_t *p1; + int flags; + flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | + ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; + ret = js_atof(s->ctx, (const char *)p, (const char **)&p, 0, + flags); + if (JS_IsException(ret)) + goto fail; + /* reject `10instanceof Number` */ + if (JS_VALUE_IS_NAN(ret) || + lre_js_is_ident_next(utf8_decode(p, &p1))) { + JS_FreeValue(s->ctx, ret); + js_parse_error(s, "invalid number literal"); + goto fail; + } + s->token.val = TOK_NUMBER; + s->token.u.num.val = ret; + } + break; + case '*': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MUL_ASSIGN; + } else if (p[1] == '*') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_POW_ASSIGN; + } else { + p += 2; + s->token.val = TOK_POW; + } + } else { + goto def_token; + } + break; + case '%': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MOD_ASSIGN; + } else { + goto def_token; + } + break; + case '+': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_PLUS_ASSIGN; + } else if (p[1] == '+') { + p += 2; + s->token.val = TOK_INC; + } else { + goto def_token; + } + break; + case '-': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MINUS_ASSIGN; + } else if (p[1] == '-') { + if (s->allow_html_comments && p[2] == '>' && + (s->got_lf || s->last_ptr == s->buf_start)) { + /* Annex B: `-->` at beginning of line is an html comment end. + It extends to the end of the line. + */ + goto skip_line_comment; + } + p += 2; + s->token.val = TOK_DEC; + } else { + goto def_token; + } + break; + case '<': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_LTE; + } else if (p[1] == '<') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_SHL_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SHL; + } + } else if (s->allow_html_comments && + p[1] == '!' && p[2] == '-' && p[3] == '-') { + /* Annex B: handle ` <----- 32 bit -----> +class wasm_addr_t { + public: + wasm_addr_t(uint32_t module_id, uint32_t offset) + : module_id_(module_id), offset_(offset) {} + explicit wasm_addr_t(uint64_t address) + : module_id_(address >> 32), offset_(address & 0xffffffff) {} + + inline uint32_t ModuleId() const { return module_id_; } + inline uint32_t Offset() const { return offset_; } + + inline operator uint64_t() const { + return static_cast(module_id_) << 32 | offset_; + } + + private: + uint32_t module_id_; + uint32_t offset_; +}; + +} // namespace gdb_server +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/gdb-server-thread.h b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/gdb-server-thread.h new file mode 100644 index 000000000..3d9a01a3a --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/gdb-server-thread.h @@ -0,0 +1,63 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_ +#define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_ + +#include "src/base/platform/platform.h" +#include "src/base/platform/semaphore.h" +#include "src/debug/wasm/gdb-server/target.h" +#include "src/debug/wasm/gdb-server/transport.h" + +namespace v8 { +namespace internal { +namespace wasm { +namespace gdb_server { + +class GdbServer; + +// class GdbServerThread spawns a thread where all communication with a debugger +// happens. +class GdbServerThread : public v8::base::Thread { + public: + explicit GdbServerThread(GdbServer* gdb_server); + GdbServerThread(const GdbServerThread&) = delete; + GdbServerThread& operator=(const GdbServerThread&) = delete; + + // base::Thread + void Run() override; + + // Starts the GDB-server thread and waits Run() method is called on the new + // thread and the initialization completes. + bool StartAndInitialize(); + + // Stops the GDB-server thread when the V8 process shuts down; gracefully + // closes any active debugging session. + void Stop(); + + Target& GetTarget() { return *target_; } + + private: + void CleanupThread(); + + GdbServer* gdb_server_; + + // Used to block the caller on StartAndInitialize() waiting for the new thread + // to have completed its initialization. + // (Note that Thread::StartSynchronously() wouldn't work in this case because + // it returns as soon as the new thread starts, but before Run() is called). + base::Semaphore start_semaphore_; + + base::Mutex mutex_; + // Protected by {mutex_}: + std::unique_ptr transport_; + std::unique_ptr target_; +}; + +} // namespace gdb_server +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/gdb-server.h b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/gdb-server.h new file mode 100644 index 000000000..146877f73 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/gdb-server.h @@ -0,0 +1,216 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_ +#define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_ + +#include +#include +#include "src/debug/wasm/gdb-server/gdb-server-thread.h" +#include "src/debug/wasm/gdb-server/wasm-module-debug.h" + +namespace v8 { +namespace internal { +namespace wasm { +namespace gdb_server { + +class TaskRunner; + +// class GdbServer acts as a manager for the GDB-remote stub. It is instantiated +// as soon as the first Wasm module is loaded in the Wasm engine and spawns a +// separate thread to accept connections and exchange messages with a debugger. +// It will contain the logic to serve debugger queries and access the state of +// the Wasm engine. +class GdbServer { + public: + GdbServer(const GdbServer&) = delete; + GdbServer& operator=(const GdbServer&) = delete; + + // Factory method: creates and returns a GdbServer. Spawns a "GDB-remote" + // thread that will be used to communicate with the debugger. + // May return null on failure. + // This should be called once, the first time a Wasm module is loaded in the + // Wasm engine. + static std::unique_ptr Create(); + + // Stops the "GDB-remote" thread and waits for it to complete. This should be + // called once, when the Wasm engine shuts down. + ~GdbServer(); + + // Queries the set of the Wasm modules currently loaded. Each module is + // identified by a unique integer module id. + struct WasmModuleInfo { + uint32_t module_id; + std::string module_name; + }; + std::vector GetLoadedModules( + bool clear_module_list_changed_flag = false); + + bool HasModuleListChanged() const { return has_module_list_changed_; } + + // Queries the value of the {index} global value in the Wasm module identified + // by {frame_index}. + // + bool GetWasmGlobal(uint32_t frame_index, uint32_t index, uint8_t* buffer, + uint32_t buffer_size, uint32_t* size); + + // Queries the value of the {index} local value in the {frame_index}th stack + // frame in the Wasm module identified by {frame_index}. + // + bool GetWasmLocal(uint32_t frame_index, uint32_t index, uint8_t* buffer, + uint32_t buffer_size, uint32_t* size); + + // Queries the value of the {index} value in the operand stack. + // + bool GetWasmStackValue(uint32_t frame_index, uint32_t index, uint8_t* buffer, + uint32_t buffer_size, uint32_t* size); + + // Reads {size} bytes, starting from {offset}, from the Memory instance + // associated to the Wasm module identified by {module_id}. + // Returns the number of bytes copied to {buffer}, or 0 is case of error. + // Note: only one Memory for Module is currently supported. + // + uint32_t GetWasmMemory(uint32_t module_id, uint32_t offset, uint8_t* buffer, + uint32_t size); + + // Reads {size} bytes, starting from {offset}, from the first Data segment + // in the Wasm module identified by {module_id}. + // Returns the number of bytes copied to {buffer}, or 0 is case of error. + // Note: only one Memory for Module is currently supported. + // + uint32_t GetWasmData(uint32_t module_id, uint32_t offset, uint8_t* buffer, + uint32_t size); + + // Reads {size} bytes, starting from the low dword of {address}, from the Code + // space of th Wasm module identified by high dword of {address}. + // Returns the number of bytes copied to {buffer}, or 0 is case of error. + uint32_t GetWasmModuleBytes(wasm_addr_t address, uint8_t* buffer, + uint32_t size); + + // Inserts a breakpoint at the offset {offset} of the Wasm module identified + // by {wasm_module_id}. + // Returns true if the breakpoint was successfully added. + bool AddBreakpoint(uint32_t wasm_module_id, uint32_t offset); + + // Removes a breakpoint at the offset {offset} of the Wasm module identified + // by {wasm_module_id}. + // Returns true if the breakpoint was successfully removed. + bool RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset); + + // Returns the current call stack as a vector of program counters. + std::vector GetWasmCallStack() const; + + // Manage the set of Isolates for this GdbServer. + void AddIsolate(Isolate* isolate); + void RemoveIsolate(Isolate* isolate); + + // Requests that the thread suspend execution at the next Wasm instruction. + void Suspend(); + + // Handle stepping in wasm functions via the wasm interpreter. + void PrepareStep(); + + // Called when the target debuggee can resume execution (for example after + // having been suspended on a breakpoint). Terminates the task runner leaving + // all pending tasks in the queue. + void QuitMessageLoopOnPause(); + + private: + GdbServer(); + + // When the target debuggee is suspended for a breakpoint or exception, blocks + // the main (isolate) thread and enters in a message loop. Here it waits on a + // queue of Task objects that are posted by the GDB-stub thread and that + // represent queries received from the debugger via the GDB-remote protocol. + void RunMessageLoopOnPause(); + + // Post a task to run a callback in the isolate thread. + template + auto RunSyncTask(Callback&& callback) const; + + void AddWasmModule(uint32_t module_id, Local wasm_script); + + // Given a Wasm module id, retrieves the corresponding debugging WasmScript + // object. + bool GetModuleDebugHandler(uint32_t module_id, + WasmModuleDebug** wasm_module_debug); + + // Returns the debugging target. + Target& GetTarget() const; + + // Class DebugDelegate implements the debug::DebugDelegate interface to + // receive notifications when debug events happen in a given isolate, like a + // script being loaded, a breakpoint being hit, an exception being thrown. + class DebugDelegate : public debug::DebugDelegate { + public: + DebugDelegate(Isolate* isolate, GdbServer* gdb_server); + ~DebugDelegate(); + + // debug::DebugDelegate + void ScriptCompiled(Local script, bool is_live_edited, + bool has_compile_error) override; + void BreakProgramRequested( + Local paused_context, + const std::vector& inspector_break_points_hit, + v8::debug::BreakReasons break_reasons) override; + void ExceptionThrown(Local paused_context, + Local exception, Local promise, + bool is_uncaught, + debug::ExceptionType exception_type) override; + bool IsFunctionBlackboxed(Local script, + const debug::Location& start, + const debug::Location& end) override; + + private: + // Calculates module_id as: + // +--------------------+------------------- + + // | DebugDelegate::id_ | Script::Id() | + // +--------------------+------------------- + + // <----- 16 bit -----> <----- 16 bit -----> + uint32_t GetModuleId(uint32_t script_id) const { + DCHECK_LT(script_id, 0x10000); + DCHECK_LT(id_, 0x10000); + return id_ << 16 | script_id; + } + + Isolate* isolate_; + uint32_t id_; + GdbServer* gdb_server_; + + static std::atomic id_s; + }; + + // The GDB-stub thread where all the communication with the debugger happens. + std::unique_ptr thread_; + + // Used to transform the queries that arrive in the GDB-stub thread into + // tasks executed in the main (isolate) thread. + std::unique_ptr task_runner_; + + std::atomic has_module_list_changed_; + + ////////////////////////////////////////////////////////////////////////////// + // Always accessed in the isolate thread. + + // Set of breakpoints currently defines in Wasm code. + typedef std::map BreakpointsMap; + BreakpointsMap breakpoints_; + + typedef std::map ScriptsMap; + ScriptsMap scripts_; + + typedef std::map> + IsolateDebugDelegateMap; + IsolateDebugDelegateMap isolate_delegates_; + + // End of fields always accessed in the isolate thread. + ////////////////////////////////////////////////////////////////////////////// +}; + +} // namespace gdb_server +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/packet.h b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/packet.h new file mode 100644 index 000000000..87b5b6b91 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/packet.h @@ -0,0 +1,105 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_DEBUG_WASM_GDB_SERVER_PACKET_H_ +#define V8_DEBUG_WASM_GDB_SERVER_PACKET_H_ + +#include + +#include "src/base/macros.h" + +namespace v8 { +namespace internal { +namespace wasm { +namespace gdb_server { + +class V8_EXPORT_PRIVATE Packet { + public: + Packet(); + + // Empty the vector and reset the read/write pointers. + void Clear(); + + // Reset the read pointer, allowing the packet to be re-read. + void Rewind(); + + // Return true of the read pointer has reached the write pointer. + bool EndOfPacket() const; + + // Store a single raw 8 bit value + void AddRawChar(char ch); + + // Store a block of data as hex pairs per byte + void AddBlock(const void* ptr, uint32_t len); + + // Store a byte as a 2 chars block. + void AddWord8(uint8_t val); + + // Store a number up to 64 bits, formatted as a big-endian hex string with + // preceeding zeros removed. Since zeros can be removed, the width of this + // number is unknown, and the number is always followed by a NULL or a + // separator (non hex digit). + void AddNumberSep(uint64_t val, char sep); + + // Add a raw string. + void AddString(const char* str); + + // Add a string stored as a stream of ASCII hex digit pairs. It is safe + // to use any non-null character in this stream. If this does not terminate + // the packet, there should be a separator (non hex digit) immediately + // following. + void AddHexString(const char* str); + + // Retrieve a single character if available + bool GetRawChar(char* ch); + + // Retrieve "len" ASCII character pairs. + bool GetBlock(void* ptr, uint32_t len); + + // Retrieve a 8, 16, 32, or 64 bit word as pairs of hex digits. These + // functions will always consume bits/4 characters from the stream. + bool GetWord8(uint8_t* val); + + // Retrieve a number (formatted as a big-endian hex string) and a separator. + // If 'sep' is null, the separator is consumed but thrown away. + bool GetNumberSep(uint64_t* val, char* sep); + + // Get a string from the stream + bool GetString(std::string* str); + bool GetHexString(std::string* str); + + // Return a pointer to the entire packet payload + const char* GetPayload() const; + size_t GetPayloadSize() const; + + // Returns true and the sequence number, or false if it is unset. + bool GetSequence(int32_t* seq) const; + + // Parses sequence number in package data and moves read pointer past it. + void ParseSequence(); + + // Set the sequence number. + void SetSequence(int32_t seq); + + enum class ErrDef { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 }; + void SetError(ErrDef); + + // Returns the full content of a GDB-remote packet, in the format: + // $payload#checksum + // where the two-digit checksum is computed as the modulo 256 sum of all + // characters between the leading ‘$’ and the trailing ‘#’. + std::string GetPacketData() const; + + private: + int32_t seq_; + std::string data_; + size_t read_index_; +}; + +} // namespace gdb_server +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_DEBUG_WASM_GDB_SERVER_PACKET_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/session.h b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/session.h new file mode 100644 index 000000000..ba052cab5 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/session.h @@ -0,0 +1,73 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_DEBUG_WASM_GDB_SERVER_SESSION_H_ +#define V8_DEBUG_WASM_GDB_SERVER_SESSION_H_ + +#include "src/base/macros.h" + +namespace v8 { +namespace internal { +namespace wasm { +namespace gdb_server { + +class Packet; +class TransportBase; + +// Represents a gdb-remote debugging session. +class V8_EXPORT_PRIVATE Session { + public: + explicit Session(TransportBase* transport); + Session(const Session&) = delete; + Session& operator=(const Session&) = delete; + + // Attempt to send a packet and optionally wait for an ACK from the receiver. + bool SendPacket(Packet* packet, bool expect_ack = true); + + // Attempt to receive a packet. + bool GetPacket(Packet* packet); + + // Return true if there is data to read. + bool IsDataAvailable() const; + + // Return true if the connection is still valid. + bool IsConnected() const; + + // Shutdown the connection. + void Disconnect(); + + // When a debugging session is active, the GDB-remote thread can block waiting + // for events and it will resume execution when one of these two events arise: + // - A network event (a new packet arrives, or the connection is dropped) + // - A thread event (the execution stopped because of a trap or breakpoint). + void WaitForDebugStubEvent(); + + // Signal that the debuggee execution stopped because of a trap or breakpoint. + bool SignalThreadEvent(); + + // By default, when either the debugger or the GDB-stub sends a packet, + // the first response expected is an acknowledgment: either '+' (to indicate + // the packet was received correctly) or '-' (to request retransmission). + // When a transport is reliable, the debugger may request that acknowledgement + // be disabled by means of the 'QStartNoAckMode' packet. + void EnableAck(bool ack_enabled) { ack_enabled_ = ack_enabled; } + + private: + // Read a single character from the transport. + bool GetChar(char* ch); + + // Read the content of a packet, from a leading '$' to a trailing '#'. + bool GetPayload(Packet* pkt, uint8_t* checksum); + + TransportBase* io_; // Transport object not owned by the Session. + bool connected_; // Is the connection still valid. + bool ack_enabled_; // If true, emit or wait for '+' from RSP stream. +}; + +} // namespace gdb_server +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_DEBUG_WASM_GDB_SERVER_SESSION_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/target.h b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/target.h new file mode 100644 index 000000000..bba854931 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/target.h @@ -0,0 +1,142 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_DEBUG_WASM_GDB_SERVER_TARGET_H_ +#define V8_DEBUG_WASM_GDB_SERVER_TARGET_H_ + +#include +#include + +#include "src/base/platform/mutex.h" +#include "src/base/platform/semaphore.h" +#include "src/debug/wasm/gdb-server/gdb-remote-util.h" + +namespace v8 { +namespace internal { +namespace wasm { +namespace gdb_server { + +class GdbServer; +class Packet; +class Session; + +// Class Target represents a debugging target. It contains the logic to decode +// incoming GDB-remote packets, execute them forwarding the debugger commands +// and queries to the Wasm engine, and send back GDB-remote packets. +class Target { + public: + // Contruct a Target object. + explicit Target(GdbServer* gdb_server); + Target(const Target&) = delete; + Target& operator=(const Target&) = delete; + + // This function spin on a debugging session, until it closes. + void Run(Session* ses); + + void Terminate(); + bool IsTerminated() const { return status_ == Status::Terminated; } + + // Notifies that the debuggee thread suspended at a breakpoint. + void OnProgramBreak(Isolate* isolate, + const std::vector& call_frames); + // Notifies that the debuggee thread suspended because of an unhandled + // exception. + void OnException(Isolate* isolate, + const std::vector& call_frames); + + // Returns the state at the moment of the thread suspension. + const std::vector GetCallStack() const; + wasm_addr_t GetCurrentPc() const; + Isolate* GetCurrentIsolate() const { return current_isolate_; } + + private: + void OnSuspended(Isolate* isolate, int signal, + const std::vector& call_frames); + + // Initializes a map used to make fast lookups when handling query packets + // that have a constant response. + void InitQueryPropertyMap(); + + // Blocks waiting for one of these two events to occur: + // - A network packet arrives from the debugger, or the debugger connection is + // closed; + // - The debuggee suspends execution because of a trap or breakpoint. + void WaitForDebugEvent(); + void ProcessDebugEvent(); + + // Processes GDB-remote packets that arrive from the debugger. + // This method should be called when the debuggee has suspended its execution. + void ProcessCommands(); + + // Requests that the thread suspends execution at the next Wasm instruction. + void Suspend(); + + enum class ErrorCode { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 }; + + enum class ProcessPacketResult { + Paused, // The command was processed, debuggee still paused. + Continue, // The debuggee should resume execution. + Detach, // Request to detach from the debugger. + Kill // Request to terminate the debuggee process. + }; + // This function always succeedes, since all errors are reported as an error + // string "Exx" where xx is a two digit number. + // The return value indicates if the target can resume execution or it is + // still paused. + ProcessPacketResult ProcessPacket(Packet* pkt_in, Packet* pkt_out); + + // Processes a general query packet + ErrorCode ProcessQueryPacket(const Packet* pkt_in, Packet* pkt_out); + + // Formats a 'Stop-reply' packet, which is sent in response of a 'c' + // (continue), 's' (step) and '?' (query halt reason) commands. + void SetStopReply(Packet* pkt_out) const; + + enum class Status { Running, WaitingForSuspension, Suspended, Terminated }; + + void SetStatus(Status status, int8_t signal = 0, + std::vector call_frames_ = {}, + Isolate* isolate = nullptr); + + GdbServer* gdb_server_; + + std::atomic status_; + + // Signal being processed. + std::atomic cur_signal_; + + // Session object not owned by the Target. + Session* session_; + + // Map used to make fast lookups when handling query packets. + typedef std::map QueryPropertyMap; + QueryPropertyMap query_properties_; + + bool debugger_initial_suspension_; + + // Used to block waiting for suspension + v8::base::Semaphore semaphore_; + + mutable v8::base::Mutex mutex_; + ////////////////////////////////////////////////////////////////////////////// + // Protected by {mutex_}: + + // Current isolate. This is not null only when the target is in a Suspended + // state and it is the isolate associated to the current call stack and used + // for all debugging activities. + Isolate* current_isolate_; + + // Call stack when the execution is suspended. + std::vector call_frames_; + + // End of fields protected by {mutex_}. + ////////////////////////////////////////////////////////////////////////////// +}; + +} // namespace gdb_server +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_DEBUG_WASM_GDB_SERVER_TARGET_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/transport.h b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/transport.h new file mode 100644 index 000000000..279c8d3d8 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/transport.h @@ -0,0 +1,192 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_ +#define V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_ + +#include + +#include "src/base/macros.h" +#include "src/debug/wasm/gdb-server/gdb-remote-util.h" + +#if _WIN32 +#include +#include + +typedef SOCKET SocketHandle; + +#define CloseSocket closesocket +#define InvalidSocket INVALID_SOCKET +#define SocketGetLastError() WSAGetLastError() +static const int kErrInterrupt = WSAEINTR; +typedef int ssize_t; +typedef int socklen_t; + +#else // _WIN32 + +#include +#include +#include +#include +#include +#include + +typedef int SocketHandle; + +#define CloseSocket close +#define InvalidSocket (-1) +#define SocketGetLastError() errno +static const int kErrInterrupt = EINTR; + +#endif // _WIN32 + +namespace v8 { +namespace internal { +namespace wasm { +namespace gdb_server { + +class SocketTransport; + +// Acts as a factory for Transport objects bound to a specified TCP port. +class SocketBinding { + public: + // Wrap existing socket handle. + explicit SocketBinding(SocketHandle socket_handle); + + // Bind to the specified TCP port. + static SocketBinding Bind(uint16_t tcp_port); + + bool IsValid() const { return socket_handle_ != InvalidSocket; } + + // Create a transport object from this socket binding + std::unique_ptr CreateTransport(); + + // Get port the socket is bound to. + uint16_t GetBoundPort(); + + private: + SocketHandle socket_handle_; +}; + +class V8_EXPORT_PRIVATE TransportBase { + public: + virtual ~TransportBase() {} + + // Waits for an incoming connection on the bound port. + virtual bool AcceptConnection() = 0; + + // Read {len} bytes from this transport, possibly blocking until enough data + // is available. + // {dst} must point to a buffer large enough to contain {len} bytes. + // Returns true on success. + // Returns false if the connection is closed; in that case the {dst} may have + // been partially overwritten. + virtual bool Read(char* dst, int32_t len) = 0; + + // Write {len} bytes to this transport. + // Return true on success, false if the connection is closed. + virtual bool Write(const char* src, int32_t len) = 0; + + // Return true if there is data to read. + virtual bool IsDataAvailable() const = 0; + + // If we are connected to a debugger, gracefully closes the connection. + // This should be called when a debugging session gets closed. + virtual void Disconnect() = 0; + + // Shuts down this transport, gracefully closing the existing connection and + // also closing the listening socket. This should be called when the GDB stub + // shuts down, when the program terminates. + virtual void Close() = 0; + + // Blocks waiting for one of these two events to occur: + // - A network event (a new packet arrives, or the connection is dropped), + // - A thread event is signaled (the execution stopped because of a trap or + // breakpoint). + virtual void WaitForDebugStubEvent() = 0; + + // Signal that this transport should leave an alertable wait state because + // the execution of the debuggee was stopped because of a trap or breakpoint. + virtual bool SignalThreadEvent() = 0; +}; + +class Transport : public TransportBase { + public: + explicit Transport(SocketHandle s); + ~Transport() override; + + // TransportBase + bool Read(char* dst, int32_t len) override; + bool Write(const char* src, int32_t len) override; + bool IsDataAvailable() const override; + void Disconnect() override; + void Close() override; + + static const int kBufSize = 4096; + + protected: + // Copy buffered data to *dst up to len bytes and update dst and len. + void CopyFromBuffer(char** dst, int32_t* len); + + // Read available data from the socket. Return false on EOF or error. + virtual bool ReadSomeData() = 0; + + std::unique_ptr buf_; + int32_t pos_; + int32_t size_; + SocketHandle handle_bind_; + SocketHandle handle_accept_; +}; + +#if _WIN32 + +class SocketTransport : public Transport { + public: + explicit SocketTransport(SocketHandle s); + ~SocketTransport() override; + SocketTransport(const SocketTransport&) = delete; + SocketTransport& operator=(const SocketTransport&) = delete; + + // TransportBase + bool AcceptConnection() override; + void Disconnect() override; + void WaitForDebugStubEvent() override; + bool SignalThreadEvent() override; + + private: + bool ReadSomeData() override; + + HANDLE socket_event_; + HANDLE faulted_thread_event_; +}; + +#else // _WIN32 + +class SocketTransport : public Transport { + public: + explicit SocketTransport(SocketHandle s); + ~SocketTransport() override; + SocketTransport(const SocketTransport&) = delete; + SocketTransport& operator=(const SocketTransport&) = delete; + + // TransportBase + bool AcceptConnection() override; + void WaitForDebugStubEvent() override; + bool SignalThreadEvent() override; + + private: + bool ReadSomeData() override; + + int faulted_thread_fd_read_; + int faulted_thread_fd_write_; +}; + +#endif // _WIN32 + +} // namespace gdb_server +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/wasm-module-debug.h b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/wasm-module-debug.h new file mode 100644 index 000000000..726e512a1 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/debug/wasm/gdb-server/wasm-module-debug.h @@ -0,0 +1,111 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_ +#define V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_ + +#include "src/debug/debug-interface.h" +#include "src/debug/wasm/gdb-server/gdb-remote-util.h" +#include "src/execution/frames.h" + +namespace v8 { +namespace internal { +namespace wasm { + +class WasmValue; + +namespace gdb_server { + +// Represents the interface to access the Wasm engine state for a given module. +// For the moment it only works with interpreted functions, in the future it +// could be extended to also support Liftoff. +class WasmModuleDebug { + public: + WasmModuleDebug(v8::Isolate* isolate, Local script); + + std::string GetModuleName() const; + i::Isolate* GetIsolate() const { + return reinterpret_cast(isolate_); + } + + // Gets the value of the {index}th global value. + static bool GetWasmGlobal(Isolate* isolate, uint32_t frame_index, + uint32_t index, uint8_t* buffer, + uint32_t buffer_size, uint32_t* size); + + // Gets the value of the {index}th local value in the {frame_index}th stack + // frame. + static bool GetWasmLocal(Isolate* isolate, uint32_t frame_index, + uint32_t index, uint8_t* buffer, + uint32_t buffer_size, uint32_t* size); + + // Gets the value of the {index}th value in the operand stack. + static bool GetWasmStackValue(Isolate* isolate, uint32_t frame_index, + uint32_t index, uint8_t* buffer, + uint32_t buffer_size, uint32_t* size); + + // Reads {size} bytes, starting from {offset}, from the Memory instance + // associated to this module. + // Returns the number of byte copied to {buffer}, or 0 is case of error. + // Note: only one Memory for Module is currently supported. + uint32_t GetWasmMemory(Isolate* isolate, uint32_t offset, uint8_t* buffer, + uint32_t size); + + // Reads {size} bytes, starting from {offset}, from the first segment + // associated to this module. + // Returns the number of byte copied to {buffer}, or 0 is case of error. + // Note: only one Memory for Module is currently supported. + uint32_t GetWasmData(Isolate* isolate, uint32_t offset, uint8_t* buffer, + uint32_t size); + + // Gets {size} bytes, starting from {offset}, from the Code space of this + // module. + // Returns the number of byte copied to {buffer}, or 0 is case of error. + uint32_t GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer, + uint32_t size); + + // Inserts a breakpoint at the offset {offset} of this module. + // Returns {true} if the breakpoint was successfully added. + bool AddBreakpoint(uint32_t offset, int* breakpoint_id); + + // Removes a breakpoint at the offset {offset} of the this module. + void RemoveBreakpoint(uint32_t offset, int breakpoint_id); + + // Handle stepping in wasm functions via the wasm interpreter. + void PrepareStep(); + + // Returns the current stack trace as a vector of instruction pointers. + static std::vector GetCallStack(uint32_t debug_context_id, + Isolate* isolate); + + private: + // Returns the module WasmInstance associated to the {frame_index}th frame + // in the call stack. + static Handle GetWasmInstance(Isolate* isolate, + uint32_t frame_index); + + // Returns its first WasmInstance for this Wasm module. + Handle GetFirstWasmInstance(); + + // Iterates on current stack frames and return frame information for the + // {frame_index} specified. + // Returns an empty array if the frame specified does not correspond to a Wasm + // stack frame. + static std::vector FindWasmFrame( + StackTraceFrameIterator* frame_it, uint32_t* frame_index); + + // Converts a WasmValue into an array of bytes. + static bool GetWasmValue(const wasm::WasmValue& wasm_value, uint8_t* buffer, + uint32_t buffer_size, uint32_t* size); + + v8::Isolate* isolate_; + Global wasm_script_; +}; + +} // namespace gdb_server +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/BUILD.gn b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/BUILD.gn new file mode 100644 index 000000000..87ae628aa --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/BUILD.gn @@ -0,0 +1,168 @@ +# Copyright 2016 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../gni/v8.gni") + +_inspector_protocol = v8_path_prefix + "/third_party/inspector_protocol" +import("$_inspector_protocol/inspector_protocol.gni") + +_protocol_generated = [ + "protocol/Forward.h", + "protocol/Protocol.cpp", + "protocol/Protocol.h", + "protocol/Console.cpp", + "protocol/Console.h", + "protocol/Debugger.cpp", + "protocol/Debugger.h", + "protocol/HeapProfiler.cpp", + "protocol/HeapProfiler.h", + "protocol/Profiler.cpp", + "protocol/Profiler.h", + "protocol/Runtime.cpp", + "protocol/Runtime.h", + "protocol/Schema.cpp", + "protocol/Schema.h", + "../../include/inspector/Debugger.h", + "../../include/inspector/Runtime.h", + "../../include/inspector/Schema.h", +] + +action("protocol_compatibility") { + visibility = [ ":*" ] # Only targets in this file can depend on this. + script = "$_inspector_protocol/check_protocol_compatibility.py" + inputs = [ v8_inspector_js_protocol ] + _stamp = "$target_gen_dir/js_protocol.stamp" + outputs = [ _stamp ] + args = [ + "--stamp", + rebase_path(_stamp, root_build_dir), + rebase_path(v8_inspector_js_protocol, root_build_dir), + ] +} + +inspector_protocol_generate("protocol_generated_sources") { + deps = [ ":protocol_compatibility" ] + + inspector_protocol_dir = _inspector_protocol + out_dir = target_gen_dir + _protocol_path = rebase_path(v8_inspector_js_protocol, root_build_dir) + config_values = [ "protocol.path=$_protocol_path" ] + config_file = v8_path_prefix + "/src/inspector/inspector_protocol_config.json" + inputs = [ + v8_inspector_js_protocol, + config_file, + ] + outputs = _protocol_generated +} + +config("inspector_config") { + visibility = [ ":*" ] # Only targets in this file can depend on this. + + configs = [ "../../:internal_config" ] + include_dirs = [ "../../include" ] +} + +v8_header_set("inspector_test_headers") { + configs = [ ":inspector_config" ] + + public_deps = [ "../..:v8_headers" ] + + sources = [ "test-interface.h" ] +} + +v8_source_set("inspector_string_conversions") { + sources = [ + "v8-string-conversions.cc", + "v8-string-conversions.h", + ] + configs = [ "../..:internal_config_base" ] + deps = [ "../..:v8_libbase" ] +} + +v8_source_set("inspector") { + deps = [ + ":inspector_string_conversions", + "../..:v8_tracing", + "../..:v8_version", + "../../third_party/inspector_protocol:crdtp", + ] + + public_deps = [ + ":inspector_test_headers", + ":protocol_generated_sources", + "../../:v8_libbase", + ] + + configs = [ ":inspector_config" ] + + sources = rebase_path(_protocol_generated, ".", target_gen_dir) + sources += [ + "../../include/v8-inspector-protocol.h", + "../../include/v8-inspector.h", + ] + sources += [ + "crc32.cc", + "crc32.h", + "custom-preview.cc", + "custom-preview.h", + "injected-script.cc", + "injected-script.h", + "inspected-context.cc", + "inspected-context.h", + "remote-object-id.cc", + "remote-object-id.h", + "search-util.cc", + "search-util.h", + "string-16.cc", + "string-16.h", + "string-util.cc", + "string-util.h", + "test-interface.cc", + "v8-console-agent-impl.cc", + "v8-console-agent-impl.h", + "v8-console-message.cc", + "v8-console-message.h", + "v8-console.cc", + "v8-console.h", + "v8-debugger-agent-impl.cc", + "v8-debugger-agent-impl.h", + "v8-debugger-barrier.cc", + "v8-debugger-barrier.h", + "v8-debugger-id.cc", + "v8-debugger-id.h", + "v8-debugger-script.cc", + "v8-debugger-script.h", + "v8-debugger.cc", + "v8-debugger.h", + "v8-heap-profiler-agent-impl.cc", + "v8-heap-profiler-agent-impl.h", + "v8-inspector-impl.cc", + "v8-inspector-impl.h", + "v8-inspector-session-impl.cc", + "v8-inspector-session-impl.h", + "v8-profiler-agent-impl.cc", + "v8-profiler-agent-impl.h", + "v8-regex.cc", + "v8-regex.h", + "v8-runtime-agent-impl.cc", + "v8-runtime-agent-impl.h", + "v8-schema-agent-impl.cc", + "v8-schema-agent-impl.h", + "v8-stack-trace-impl.cc", + "v8-stack-trace-impl.h", + "v8-value-utils.cc", + "v8-value-utils.h", + "v8-webdriver-serializer.cc", + "v8-webdriver-serializer.h", + "value-mirror.cc", + "value-mirror.h", + ] +} + +#Target to generate all .cc files. +group("v8_generated_cc_files") { + testonly = true + + deps = [ ":protocol_generated_sources" ] +} diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/DEPS b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/DEPS new file mode 100644 index 000000000..0fc0768b6 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/DEPS @@ -0,0 +1,26 @@ +include_rules = [ + "-src", + "-include/v8-debug.h", + "+src/base/atomicops.h", + "+src/base/compiler-specific.h", + "+src/base/logging.h", + "+src/base/macros.h", + "+src/base/memory.h", + "+src/base/optional.h", + "+src/base/platform/mutex.h", + "+src/base/platform/platform.h", + "+src/base/platform/time.h", + "+src/base/safe_conversions.h", + "+src/base/template-utils.h", + "+src/base/v8-fallthrough.h", + "+src/numbers/conversions.h", + "+src/inspector", + "+src/tracing", + "+src/debug/debug-interface.h", + "+src/debug/interface-types.h", + "+src/base/vector.h", + "+src/base/enum-set.h", + "+src/utils/sha-256.h", + "+third_party/inspector_protocol/crdtp", + "+../../third_party/inspector_protocol/crdtp", +] diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/DIR_METADATA b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/DIR_METADATA new file mode 100644 index 000000000..3ba1106a5 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/DIR_METADATA @@ -0,0 +1,11 @@ +# Metadata information for this directory. +# +# For more information on DIR_METADATA files, see: +# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md +# +# For the schema of this file, see Metadata message: +# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto + +monorail { + component: "Platform>DevTools>JavaScript" +} \ No newline at end of file diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/OWNERS b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/OWNERS new file mode 100644 index 000000000..d04f45a46 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/OWNERS @@ -0,0 +1,10 @@ +bmeurer@chromium.org +caseq@chromium.org +jarin@chromium.org +kimanh@chromium.org +leese@chromium.org +pfaffe@chromium.org +szuend@chromium.org +yangguo@chromium.org + +per-file PRESUBMIT.py=file:../../INFRA_OWNERS diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/crc32.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/crc32.h new file mode 100644 index 000000000..c20b56a66 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/crc32.h @@ -0,0 +1,16 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_CRC32_H_ +#define V8_INSPECTOR_CRC32_H_ + +#include "src/inspector/string-16.h" + +namespace v8_inspector { + +int32_t computeCrc32(const String16&); + +} + +#endif // V8_INSPECTOR_CRC32_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/custom-preview.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/custom-preview.h new file mode 100644 index 000000000..d7b24adcc --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/custom-preview.h @@ -0,0 +1,24 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_CUSTOM_PREVIEW_H_ +#define V8_INSPECTOR_CUSTOM_PREVIEW_H_ + +#include + +#include "src/inspector/protocol/Protocol.h" +#include "src/inspector/protocol/Runtime.h" + +namespace v8_inspector { + +const int kMaxCustomPreviewDepth = 20; + +void generateCustomPreview( + int sessionId, const String16& groupName, v8::Local object, + v8::MaybeLocal config, int maxDepth, + std::unique_ptr* preview); + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_CUSTOM_PREVIEW_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/injected-script.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/injected-script.h new file mode 100644 index 000000000..4f5bbf282 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/injected-script.h @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef V8_INSPECTOR_INJECTED_SCRIPT_H_ +#define V8_INSPECTOR_INJECTED_SCRIPT_H_ + +#include +#include +#include + +#include "include/v8-exception.h" +#include "include/v8-local-handle.h" +#include "include/v8-persistent-handle.h" +#include "src/base/macros.h" +#include "src/inspector/inspected-context.h" +#include "src/inspector/protocol/Forward.h" +#include "src/inspector/protocol/Runtime.h" +#include "src/inspector/v8-console.h" +#include "src/inspector/v8-debugger.h" + +namespace v8_inspector { + +class RemoteObjectId; +class V8InspectorImpl; +class V8InspectorSessionImpl; +class ValueMirror; +enum class WrapMode; + +using protocol::Maybe; +using protocol::Response; + +class EvaluateCallback { + public: + static void sendSuccess( + std::weak_ptr callback, InjectedScript* injectedScript, + std::unique_ptr result, + protocol::Maybe exceptionDetails); + static void sendFailure(std::weak_ptr callback, + InjectedScript* injectedScript, + const protocol::DispatchResponse& response); + + virtual ~EvaluateCallback() = default; + + private: + virtual void sendSuccess( + std::unique_ptr result, + protocol::Maybe + exceptionDetails) = 0; + virtual void sendFailure(const protocol::DispatchResponse& response) = 0; +}; + +class InjectedScript final { + public: + InjectedScript(InspectedContext*, int sessionId); + ~InjectedScript(); + InjectedScript(const InjectedScript&) = delete; + InjectedScript& operator=(const InjectedScript&) = delete; + + InspectedContext* context() const { return m_context; } + + Response getProperties( + v8::Local, const String16& groupName, bool ownProperties, + bool accessorPropertiesOnly, bool nonIndexedPropertiesOnly, + WrapMode wrapMode, + std::unique_ptr>* + result, + Maybe*); + + Response getInternalAndPrivateProperties( + v8::Local, const String16& groupName, + bool accessorPropertiesOnly, + std::unique_ptr< + protocol::Array>* + internalProperties, + std::unique_ptr< + protocol::Array>* + privateProperties); + + void releaseObject(const String16& objectId); + + Response wrapObject(v8::Local, const String16& groupName, + WrapMode wrapMode, + std::unique_ptr* result); + Response wrapObject(v8::Local, const String16& groupName, + WrapMode wrapMode, + v8::MaybeLocal customPreviewConfig, + int maxCustomPreviewDepth, + std::unique_ptr* result); + Response wrapObjectMirror( + const ValueMirror& mirror, const String16& groupName, WrapMode wrapMode, + v8::MaybeLocal customPreviewConfig, int maxCustomPreviewDepth, + std::unique_ptr* result); + std::unique_ptr wrapTable( + v8::Local table, v8::MaybeLocal columns); + + void addPromiseCallback(V8InspectorSessionImpl* session, + v8::MaybeLocal value, + const String16& objectGroup, WrapMode wrapMode, + bool replMode, bool throwOnSideEffect, + std::shared_ptr callback); + + Response findObject(const RemoteObjectId&, v8::Local*) const; + String16 objectGroupName(const RemoteObjectId&) const; + void releaseObjectGroup(const String16&); + void setCustomObjectFormatterEnabled(bool); + Response resolveCallArgument(protocol::Runtime::CallArgument*, + v8::Local* result); + + Response createExceptionDetails( + const v8::TryCatch&, const String16& groupName, + Maybe* result); + Response createExceptionDetails( + v8::Local message, v8::Local exception, + const String16& groupName, + Maybe* result); + + Response wrapEvaluateResult( + v8::MaybeLocal maybeResultValue, const v8::TryCatch&, + const String16& objectGroup, WrapMode wrapMode, bool throwOnSideEffect, + std::unique_ptr* result, + Maybe*); + v8::Local lastEvaluationResult() const; + void setLastEvaluationResult(v8::Local result); + + class Scope { + public: + Response initialize(); + void installCommandLineAPI(); + void ignoreExceptionsAndMuteConsole(); + void pretendUserGesture(); + void allowCodeGenerationFromStrings(); + v8::Local context() const { return m_context; } + InjectedScript* injectedScript() const { return m_injectedScript; } + const v8::TryCatch& tryCatch() const { return m_tryCatch; } + V8InspectorImpl* inspector() const { return m_inspector; } + + protected: + explicit Scope(V8InspectorSessionImpl*); + virtual ~Scope(); + virtual Response findInjectedScript(V8InspectorSessionImpl*) = 0; + + V8InspectorImpl* m_inspector; + InjectedScript* m_injectedScript; + + private: + void cleanup(); + v8::debug::ExceptionBreakState setPauseOnExceptionsState( + v8::debug::ExceptionBreakState); + + v8::HandleScope m_handleScope; + v8::TryCatch m_tryCatch; + v8::Local m_context; + std::unique_ptr m_commandLineAPIScope; + bool m_ignoreExceptionsAndMuteConsole; + v8::debug::ExceptionBreakState m_previousPauseOnExceptionsState; + bool m_userGesture; + bool m_allowEval; + int m_contextGroupId; + int m_sessionId; + }; + + class ContextScope : public Scope, + public V8InspectorSession::CommandLineAPIScope { + public: + ContextScope(V8InspectorSessionImpl*, int executionContextId); + ~ContextScope() override; + ContextScope(const ContextScope&) = delete; + ContextScope& operator=(const ContextScope&) = delete; + + private: + Response findInjectedScript(V8InspectorSessionImpl*) override; + int m_executionContextId; + }; + + class ObjectScope : public Scope { + public: + ObjectScope(V8InspectorSessionImpl*, const String16& remoteObjectId); + ~ObjectScope() override; + ObjectScope(const ObjectScope&) = delete; + ObjectScope& operator=(const ObjectScope&) = delete; + const String16& objectGroupName() const { return m_objectGroupName; } + v8::Local object() const { return m_object; } + + private: + Response findInjectedScript(V8InspectorSessionImpl*) override; + String16 m_remoteObjectId; + String16 m_objectGroupName; + v8::Local m_object; + }; + + class CallFrameScope : public Scope { + public: + CallFrameScope(V8InspectorSessionImpl*, const String16& remoteCallFrameId); + ~CallFrameScope() override; + CallFrameScope(const CallFrameScope&) = delete; + CallFrameScope& operator=(const CallFrameScope&) = delete; + size_t frameOrdinal() const { return m_frameOrdinal; } + + private: + Response findInjectedScript(V8InspectorSessionImpl*) override; + String16 m_remoteCallFrameId; + size_t m_frameOrdinal; + }; + String16 bindObject(v8::Local, const String16& groupName); + + private: + friend class EvaluateCallback; + + v8::Local commandLineAPI(); + void unbindObject(int id); + + static Response bindRemoteObjectIfNeeded( + int sessionId, v8::Local context, v8::Local, + const String16& groupName, protocol::Runtime::RemoteObject* remoteObject); + + class ProtocolPromiseHandler; + void discardEvaluateCallbacks(); + void deleteEvaluateCallback(std::shared_ptr callback); + Response addExceptionToDetails( + v8::Local exception, + protocol::Runtime::ExceptionDetails* exceptionDetails, + const String16& objectGroup); + + InspectedContext* m_context; + int m_sessionId; + v8::Global m_lastEvaluationResult; + v8::Global m_commandLineAPI; + int m_lastBoundObjectId = 1; + std::unordered_map> m_idToWrappedObject; + std::unordered_map m_idToObjectGroupName; + std::unordered_map> m_nameToObjectGroup; + std::unordered_set> m_evaluateCallbacks; + bool m_customPreviewEnabled = false; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_INJECTED_SCRIPT_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/inspected-context.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/inspected-context.h new file mode 100644 index 000000000..987d3a764 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/inspected-context.h @@ -0,0 +1,85 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_INSPECTED_CONTEXT_H_ +#define V8_INSPECTOR_INSPECTED_CONTEXT_H_ + +#include +#include +#include + +#include "include/v8-local-handle.h" +#include "include/v8-persistent-handle.h" +#include "src/base/macros.h" +#include "src/debug/debug-interface.h" +#include "src/inspector/string-16.h" +#include "src/inspector/v8-debugger-id.h" + +namespace v8 { +class Context; +class Object; +} // namespace v8 + +namespace v8_inspector { + +class InjectedScript; +class InjectedScriptHost; +class V8ContextInfo; +class V8InspectorImpl; + +enum class V8InternalValueType { kNone, kEntry, kScope, kScopeList }; + +class InspectedContext { + public: + ~InspectedContext(); + InspectedContext(const InspectedContext&) = delete; + InspectedContext& operator=(const InspectedContext&) = delete; + + static int contextId(v8::Local); + + v8::Local context() const; + int contextId() const { return m_contextId; } + int contextGroupId() const { return m_contextGroupId; } + String16 origin() const { return m_origin; } + String16 humanReadableName() const { return m_humanReadableName; } + internal::V8DebuggerId uniqueId() const { return m_uniqueId; } + String16 auxData() const { return m_auxData; } + + bool isReported(int sessionId) const; + void setReported(int sessionId, bool reported); + + v8::Isolate* isolate() const; + V8InspectorImpl* inspector() const { return m_inspector; } + + InjectedScript* getInjectedScript(int sessionId); + InjectedScript* createInjectedScript(int sessionId); + void discardInjectedScript(int sessionId); + + bool addInternalObject(v8::Local object, + V8InternalValueType type); + V8InternalValueType getInternalType(v8::Local object); + + private: + friend class V8InspectorImpl; + InspectedContext(V8InspectorImpl*, const V8ContextInfo&, int contextId); + + class WeakCallbackData; + + V8InspectorImpl* m_inspector; + v8::Global m_context; + int m_contextId; + int m_contextGroupId; + const String16 m_origin; + const String16 m_humanReadableName; + const String16 m_auxData; + const internal::V8DebuggerId m_uniqueId; + std::unordered_set m_reportedSessionIds; + std::unordered_map> m_injectedScripts; + WeakCallbackData* m_weakCallbackData; + v8::Global m_internalObjects; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_INSPECTED_CONTEXT_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/inspector_protocol_config.json b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/inspector_protocol_config.json new file mode 100644 index 000000000..ba96b3457 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/inspector_protocol_config.json @@ -0,0 +1,52 @@ +{ + "protocol": { + "package": "src/inspector/protocol", + "output": "protocol", + "namespace": ["v8_inspector", "protocol"], + "options": [ + { + "domain": "Schema", + "exported": ["Domain"] + }, + { + "domain": "Runtime", + "async": ["evaluate", "awaitPromise", "callFunctionOn", "runScript", "terminateExecution"], + "exported": ["StackTrace", "StackTraceId", "RemoteObject", "ExecutionContextId"] + }, + { + "domain": "Debugger", + "exported": ["SearchMatch", "paused.reason"] + }, + { + "domain": "Console" + }, + { + "domain": "Profiler" + }, + { + "domain": "HeapProfiler", + "async": ["collectGarbage"] + } + ] + }, + + "exported": { + "package": "include/inspector", + "output": "../../include/inspector", + "string_header": "v8-inspector.h", + "string_in": "StringView", + "string_out": "std::unique_ptr", + "to_string_out": "StringBufferFrom(std::move(%s))", + "export_macro": "V8_EXPORT" + }, + + "lib": { + "package": "src/inspector/protocol", + "output": "protocol", + "string_header": "src/inspector/string-util.h" + }, + + "crdtp": { + "namespace": "v8_crdtp" + } +} diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Console.cpp b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Console.cpp new file mode 100644 index 000000000..1246101a6 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Console.cpp @@ -0,0 +1,228 @@ +// This file is generated by TypeBuilder_cpp.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/inspector/protocol/Console.h" + +#include "src/inspector/protocol/Protocol.h" + +#include "third_party/inspector_protocol/crdtp/cbor.h" +#include "third_party/inspector_protocol/crdtp/find_by_first.h" +#include "third_party/inspector_protocol/crdtp/span.h" + +namespace v8_inspector { +namespace protocol { +namespace Console { + +using v8_crdtp::DeserializerState; +using v8_crdtp::ProtocolTypeTraits; + +// ------------- Enum values from types. + +const char Metainfo::domainName[] = "Console"; +const char Metainfo::commandPrefix[] = "Console."; +const char Metainfo::version[] = "1.3"; + + +const char* ConsoleMessage::SourceEnum::Xml = "xml"; +const char* ConsoleMessage::SourceEnum::Javascript = "javascript"; +const char* ConsoleMessage::SourceEnum::Network = "network"; +const char* ConsoleMessage::SourceEnum::ConsoleApi = "console-api"; +const char* ConsoleMessage::SourceEnum::Storage = "storage"; +const char* ConsoleMessage::SourceEnum::Appcache = "appcache"; +const char* ConsoleMessage::SourceEnum::Rendering = "rendering"; +const char* ConsoleMessage::SourceEnum::Security = "security"; +const char* ConsoleMessage::SourceEnum::Other = "other"; +const char* ConsoleMessage::SourceEnum::Deprecation = "deprecation"; +const char* ConsoleMessage::SourceEnum::Worker = "worker"; + +const char* ConsoleMessage::LevelEnum::Log = "log"; +const char* ConsoleMessage::LevelEnum::Warning = "warning"; +const char* ConsoleMessage::LevelEnum::Error = "error"; +const char* ConsoleMessage::LevelEnum::Debug = "debug"; +const char* ConsoleMessage::LevelEnum::Info = "info"; +V8_CRDTP_BEGIN_DESERIALIZER(ConsoleMessage) + V8_CRDTP_DESERIALIZE_FIELD_OPT("column", m_column), + V8_CRDTP_DESERIALIZE_FIELD("level", m_level), + V8_CRDTP_DESERIALIZE_FIELD_OPT("line", m_line), + V8_CRDTP_DESERIALIZE_FIELD("source", m_source), + V8_CRDTP_DESERIALIZE_FIELD("text", m_text), + V8_CRDTP_DESERIALIZE_FIELD_OPT("url", m_url), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(ConsoleMessage) + V8_CRDTP_SERIALIZE_FIELD("source", m_source); + V8_CRDTP_SERIALIZE_FIELD("level", m_level); + V8_CRDTP_SERIALIZE_FIELD("text", m_text); + V8_CRDTP_SERIALIZE_FIELD("url", m_url); + V8_CRDTP_SERIALIZE_FIELD("line", m_line); + V8_CRDTP_SERIALIZE_FIELD("column", m_column); +V8_CRDTP_END_SERIALIZER(); + + +// ------------- Enum values from params. + + +// ------------- Frontend notifications. + +void Frontend::messageAdded(std::unique_ptr message) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("message"), message); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Console.messageAdded", serializer.Finish())); +} + +void Frontend::flush() +{ + frontend_channel_->FlushProtocolNotifications(); +} + +void Frontend::sendRawNotification(std::unique_ptr notification) +{ + frontend_channel_->SendProtocolNotification(std::move(notification)); +} + +// --------------------- Dispatcher. + +class DomainDispatcherImpl : public protocol::DomainDispatcher { +public: + DomainDispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) + : DomainDispatcher(frontendChannel) + , m_backend(backend) {} + ~DomainDispatcherImpl() override { } + + using CallHandler = void (DomainDispatcherImpl::*)(const v8_crdtp::Dispatchable& dispatchable); + + std::function Dispatch(v8_crdtp::span command_name) override; + + void clearMessages(const v8_crdtp::Dispatchable& dispatchable); + void disable(const v8_crdtp::Dispatchable& dispatchable); + void enable(const v8_crdtp::Dispatchable& dispatchable); + protected: + Backend* m_backend; +}; + +namespace { +// This helper method with a static map of command methods (instance methods +// of DomainDispatcherImpl declared just above) by their name is used immediately below, +// in the DomainDispatcherImpl::Dispatch method. +DomainDispatcherImpl::CallHandler CommandByName(v8_crdtp::span command_name) { + static auto* commands = [](){ + auto* commands = new std::vector, + DomainDispatcherImpl::CallHandler>>{ + { + v8_crdtp::SpanFrom("clearMessages"), + &DomainDispatcherImpl::clearMessages + }, + { + v8_crdtp::SpanFrom("disable"), + &DomainDispatcherImpl::disable + }, + { + v8_crdtp::SpanFrom("enable"), + &DomainDispatcherImpl::enable + }, + }; + return commands; + }(); + return v8_crdtp::FindByFirst(*commands, command_name, nullptr); +} +} // namespace + +std::function DomainDispatcherImpl::Dispatch(v8_crdtp::span command_name) { + CallHandler handler = CommandByName(command_name); + if (!handler) return nullptr; + + return [this, handler](const v8_crdtp::Dispatchable& dispatchable) { + (this->*handler)(dispatchable); + }; +} + + +namespace { + + +} // namespace + +void DomainDispatcherImpl::clearMessages(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->clearMessages(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Console.clearMessages"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::disable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Console.disable"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::enable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->enable(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Console.enable"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { +// This helper method (with a static map of redirects) is used from Dispatcher::wire +// immediately below. +const std::vector, v8_crdtp::span>>& SortedRedirects() { + static auto* redirects = [](){ + auto* redirects = new std::vector, v8_crdtp::span>>{ + }; + return redirects; + }(); + return *redirects; +} +} // namespace + +// static +void Dispatcher::wire(UberDispatcher* uber, Backend* backend) +{ + auto dispatcher = std::make_unique(uber->channel(), backend); + uber->WireBackend(v8_crdtp::SpanFrom("Console"), SortedRedirects(), std::move(dispatcher)); +} + +} // Console +} // namespace v8_inspector +} // namespace protocol diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Console.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Console.h new file mode 100644 index 000000000..4a4b7f4e2 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Console.h @@ -0,0 +1,211 @@ +// This file is generated by TypeBuilder_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Console_h +#define v8_inspector_protocol_Console_h + +#include "src/inspector/protocol/Protocol.h" +// For each imported domain we generate a ValueConversions struct instead of a full domain definition +// and include Domain::API version from there. +#include "src/inspector/protocol/Runtime.h" + +namespace v8_inspector { +namespace protocol { +namespace Console { +class ConsoleMessage; + +// ------------- Forward and enum declarations. + +// ------------- Type and builder declarations. + +class ConsoleMessage : public ::v8_crdtp::ProtocolObject { +public: + ~ConsoleMessage() override { } + + struct SourceEnum { + static const char* Xml; + static const char* Javascript; + static const char* Network; + static const char* ConsoleApi; + static const char* Storage; + static const char* Appcache; + static const char* Rendering; + static const char* Security; + static const char* Other; + static const char* Deprecation; + static const char* Worker; + }; // SourceEnum + + String getSource() { return m_source; } + void setSource(const String& value) { m_source = value; } + + struct LevelEnum { + static const char* Log; + static const char* Warning; + static const char* Error; + static const char* Debug; + static const char* Info; + }; // LevelEnum + + String getLevel() { return m_level; } + void setLevel(const String& value) { m_level = value; } + + String getText() { return m_text; } + void setText(const String& value) { m_text = value; } + + bool hasUrl() { return m_url.isJust(); } + String getUrl(const String& defaultValue) { return m_url.isJust() ? m_url.fromJust() : defaultValue; } + void setUrl(const String& value) { m_url = value; } + + bool hasLine() { return m_line.isJust(); } + int getLine(int defaultValue) { return m_line.isJust() ? m_line.fromJust() : defaultValue; } + void setLine(int value) { m_line = value; } + + bool hasColumn() { return m_column.isJust(); } + int getColumn(int defaultValue) { return m_column.isJust() ? m_column.fromJust() : defaultValue; } + void setColumn(int value) { m_column = value; } + + template + class ConsoleMessageBuilder { + public: + enum { + NoFieldsSet = 0, + SourceSet = 1 << 1, + LevelSet = 1 << 2, + TextSet = 1 << 3, + AllFieldsSet = (SourceSet | LevelSet | TextSet | 0)}; + + + ConsoleMessageBuilder& setSource(const String& value) + { + static_assert(!(STATE & SourceSet), "property source should not be set yet"); + m_result->setSource(value); + return castState(); + } + + ConsoleMessageBuilder& setLevel(const String& value) + { + static_assert(!(STATE & LevelSet), "property level should not be set yet"); + m_result->setLevel(value); + return castState(); + } + + ConsoleMessageBuilder& setText(const String& value) + { + static_assert(!(STATE & TextSet), "property text should not be set yet"); + m_result->setText(value); + return castState(); + } + + ConsoleMessageBuilder& setUrl(const String& value) + { + m_result->setUrl(value); + return *this; + } + + ConsoleMessageBuilder& setLine(int value) + { + m_result->setLine(value); + return *this; + } + + ConsoleMessageBuilder& setColumn(int value) + { + m_result->setColumn(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ConsoleMessage; + ConsoleMessageBuilder() : m_result(new ConsoleMessage()) { } + + template ConsoleMessageBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static ConsoleMessageBuilder<0> create() + { + return ConsoleMessageBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + ConsoleMessage() + { + } + + String m_source; + String m_level; + String m_text; + Maybe m_url; + Maybe m_line; + Maybe m_column; +}; + + +// ------------- Backend interface. + +class Backend { +public: + virtual ~Backend() { } + + virtual DispatchResponse clearMessages() = 0; + virtual DispatchResponse disable() = 0; + virtual DispatchResponse enable() = 0; + +}; + +// ------------- Frontend interface. + +class Frontend { +public: + explicit Frontend(FrontendChannel* frontend_channel) : frontend_channel_(frontend_channel) {} + void messageAdded(std::unique_ptr message); + + void flush(); + void sendRawNotification(std::unique_ptr); + private: + FrontendChannel* frontend_channel_; +}; + +// ------------- Dispatcher. + +class Dispatcher { +public: + static void wire(UberDispatcher*, Backend*); + +private: + Dispatcher() { } +}; + +// ------------- Metainfo. + +class Metainfo { +public: + using BackendClass = Backend; + using FrontendClass = Frontend; + using DispatcherClass = Dispatcher; + static const char domainName[]; + static const char commandPrefix[]; + static const char version[]; +}; + +} // namespace Console +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Console_h) diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Debugger.cpp b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Debugger.cpp new file mode 100644 index 000000000..642d65a30 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Debugger.cpp @@ -0,0 +1,1884 @@ +// This file is generated by TypeBuilder_cpp.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/inspector/protocol/Debugger.h" + +#include "src/inspector/protocol/Protocol.h" + +#include "third_party/inspector_protocol/crdtp/cbor.h" +#include "third_party/inspector_protocol/crdtp/find_by_first.h" +#include "third_party/inspector_protocol/crdtp/span.h" + +namespace v8_inspector { +namespace protocol { +namespace Debugger { + +using v8_crdtp::DeserializerState; +using v8_crdtp::ProtocolTypeTraits; + +// ------------- Enum values from types. + +const char Metainfo::domainName[] = "Debugger"; +const char Metainfo::commandPrefix[] = "Debugger."; +const char Metainfo::version[] = "1.3"; + + + +V8_CRDTP_BEGIN_DESERIALIZER(Location) + V8_CRDTP_DESERIALIZE_FIELD_OPT("columnNumber", m_columnNumber), + V8_CRDTP_DESERIALIZE_FIELD("lineNumber", m_lineNumber), + V8_CRDTP_DESERIALIZE_FIELD("scriptId", m_scriptId), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(Location) + V8_CRDTP_SERIALIZE_FIELD("scriptId", m_scriptId); + V8_CRDTP_SERIALIZE_FIELD("lineNumber", m_lineNumber); + V8_CRDTP_SERIALIZE_FIELD("columnNumber", m_columnNumber); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(ScriptPosition) + V8_CRDTP_DESERIALIZE_FIELD("columnNumber", m_columnNumber), + V8_CRDTP_DESERIALIZE_FIELD("lineNumber", m_lineNumber), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(ScriptPosition) + V8_CRDTP_SERIALIZE_FIELD("lineNumber", m_lineNumber); + V8_CRDTP_SERIALIZE_FIELD("columnNumber", m_columnNumber); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(LocationRange) + V8_CRDTP_DESERIALIZE_FIELD("end", m_end), + V8_CRDTP_DESERIALIZE_FIELD("scriptId", m_scriptId), + V8_CRDTP_DESERIALIZE_FIELD("start", m_start), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(LocationRange) + V8_CRDTP_SERIALIZE_FIELD("scriptId", m_scriptId); + V8_CRDTP_SERIALIZE_FIELD("start", m_start); + V8_CRDTP_SERIALIZE_FIELD("end", m_end); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(CallFrame) + V8_CRDTP_DESERIALIZE_FIELD("callFrameId", m_callFrameId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("canBeRestarted", m_canBeRestarted), + V8_CRDTP_DESERIALIZE_FIELD_OPT("functionLocation", m_functionLocation), + V8_CRDTP_DESERIALIZE_FIELD("functionName", m_functionName), + V8_CRDTP_DESERIALIZE_FIELD("location", m_location), + V8_CRDTP_DESERIALIZE_FIELD_OPT("returnValue", m_returnValue), + V8_CRDTP_DESERIALIZE_FIELD("scopeChain", m_scopeChain), + V8_CRDTP_DESERIALIZE_FIELD("this", m_this), + V8_CRDTP_DESERIALIZE_FIELD("url", m_url), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(CallFrame) + V8_CRDTP_SERIALIZE_FIELD("callFrameId", m_callFrameId); + V8_CRDTP_SERIALIZE_FIELD("functionName", m_functionName); + V8_CRDTP_SERIALIZE_FIELD("functionLocation", m_functionLocation); + V8_CRDTP_SERIALIZE_FIELD("location", m_location); + V8_CRDTP_SERIALIZE_FIELD("url", m_url); + V8_CRDTP_SERIALIZE_FIELD("scopeChain", m_scopeChain); + V8_CRDTP_SERIALIZE_FIELD("this", m_this); + V8_CRDTP_SERIALIZE_FIELD("returnValue", m_returnValue); + V8_CRDTP_SERIALIZE_FIELD("canBeRestarted", m_canBeRestarted); +V8_CRDTP_END_SERIALIZER(); + + + +const char* Scope::TypeEnum::Global = "global"; +const char* Scope::TypeEnum::Local = "local"; +const char* Scope::TypeEnum::With = "with"; +const char* Scope::TypeEnum::Closure = "closure"; +const char* Scope::TypeEnum::Catch = "catch"; +const char* Scope::TypeEnum::Block = "block"; +const char* Scope::TypeEnum::Script = "script"; +const char* Scope::TypeEnum::Eval = "eval"; +const char* Scope::TypeEnum::Module = "module"; +const char* Scope::TypeEnum::WasmExpressionStack = "wasm-expression-stack"; +V8_CRDTP_BEGIN_DESERIALIZER(Scope) + V8_CRDTP_DESERIALIZE_FIELD_OPT("endLocation", m_endLocation), + V8_CRDTP_DESERIALIZE_FIELD_OPT("name", m_name), + V8_CRDTP_DESERIALIZE_FIELD("object", m_object), + V8_CRDTP_DESERIALIZE_FIELD_OPT("startLocation", m_startLocation), + V8_CRDTP_DESERIALIZE_FIELD("type", m_type), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(Scope) + V8_CRDTP_SERIALIZE_FIELD("type", m_type); + V8_CRDTP_SERIALIZE_FIELD("object", m_object); + V8_CRDTP_SERIALIZE_FIELD("name", m_name); + V8_CRDTP_SERIALIZE_FIELD("startLocation", m_startLocation); + V8_CRDTP_SERIALIZE_FIELD("endLocation", m_endLocation); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(SearchMatch) + V8_CRDTP_DESERIALIZE_FIELD("lineContent", m_lineContent), + V8_CRDTP_DESERIALIZE_FIELD("lineNumber", m_lineNumber), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(SearchMatch) + V8_CRDTP_SERIALIZE_FIELD("lineNumber", m_lineNumber); + V8_CRDTP_SERIALIZE_FIELD("lineContent", m_lineContent); +V8_CRDTP_END_SERIALIZER(); + +// static +std::unique_ptr API::SearchMatch::fromBinary(const uint8_t* data, size_t length) +{ + return protocol::Debugger::SearchMatch::FromBinary(data, length); +} + + +const char* BreakLocation::TypeEnum::DebuggerStatement = "debuggerStatement"; +const char* BreakLocation::TypeEnum::Call = "call"; +const char* BreakLocation::TypeEnum::Return = "return"; +V8_CRDTP_BEGIN_DESERIALIZER(BreakLocation) + V8_CRDTP_DESERIALIZE_FIELD_OPT("columnNumber", m_columnNumber), + V8_CRDTP_DESERIALIZE_FIELD("lineNumber", m_lineNumber), + V8_CRDTP_DESERIALIZE_FIELD("scriptId", m_scriptId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("type", m_type), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(BreakLocation) + V8_CRDTP_SERIALIZE_FIELD("scriptId", m_scriptId); + V8_CRDTP_SERIALIZE_FIELD("lineNumber", m_lineNumber); + V8_CRDTP_SERIALIZE_FIELD("columnNumber", m_columnNumber); + V8_CRDTP_SERIALIZE_FIELD("type", m_type); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(WasmDisassemblyChunk) + V8_CRDTP_DESERIALIZE_FIELD("bytecodeOffsets", m_bytecodeOffsets), + V8_CRDTP_DESERIALIZE_FIELD("lines", m_lines), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(WasmDisassemblyChunk) + V8_CRDTP_SERIALIZE_FIELD("lines", m_lines); + V8_CRDTP_SERIALIZE_FIELD("bytecodeOffsets", m_bytecodeOffsets); +V8_CRDTP_END_SERIALIZER(); + + +namespace ScriptLanguageEnum { +const char JavaScript[] = "JavaScript"; +const char WebAssembly[] = "WebAssembly"; +} // namespace ScriptLanguageEnum + + + +const char* DebugSymbols::TypeEnum::None = "None"; +const char* DebugSymbols::TypeEnum::SourceMap = "SourceMap"; +const char* DebugSymbols::TypeEnum::EmbeddedDWARF = "EmbeddedDWARF"; +const char* DebugSymbols::TypeEnum::ExternalDWARF = "ExternalDWARF"; +V8_CRDTP_BEGIN_DESERIALIZER(DebugSymbols) + V8_CRDTP_DESERIALIZE_FIELD_OPT("externalURL", m_externalURL), + V8_CRDTP_DESERIALIZE_FIELD("type", m_type), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(DebugSymbols) + V8_CRDTP_SERIALIZE_FIELD("type", m_type); + V8_CRDTP_SERIALIZE_FIELD("externalURL", m_externalURL); +V8_CRDTP_END_SERIALIZER(); + + +// ------------- Enum values from params. + + +namespace ContinueToLocation { +namespace TargetCallFramesEnum { +const char* Any = "any"; +const char* Current = "current"; +} // namespace TargetCallFramesEnum +} // namespace ContinueToLocation + +namespace RestartFrame { +namespace ModeEnum { +const char* StepInto = "StepInto"; +} // namespace ModeEnum +} // namespace RestartFrame + +namespace SetInstrumentationBreakpoint { +namespace InstrumentationEnum { +const char* BeforeScriptExecution = "beforeScriptExecution"; +const char* BeforeScriptWithSourceMapExecution = "beforeScriptWithSourceMapExecution"; +} // namespace InstrumentationEnum +} // namespace SetInstrumentationBreakpoint + +namespace SetPauseOnExceptions { +namespace StateEnum { +const char* None = "none"; +const char* Caught = "caught"; +const char* Uncaught = "uncaught"; +const char* All = "all"; +} // namespace StateEnum +} // namespace SetPauseOnExceptions + +namespace SetScriptSource { +namespace StatusEnum { +const char* Ok = "Ok"; +const char* CompileError = "CompileError"; +const char* BlockedByActiveGenerator = "BlockedByActiveGenerator"; +const char* BlockedByActiveFunction = "BlockedByActiveFunction"; +} // namespace StatusEnum +} // namespace SetScriptSource + +namespace Paused { +namespace ReasonEnum { +const char* Ambiguous = "ambiguous"; +const char* Assert = "assert"; +const char* CSPViolation = "CSPViolation"; +const char* DebugCommand = "debugCommand"; +const char* DOM = "DOM"; +const char* EventListener = "EventListener"; +const char* Exception = "exception"; +const char* Instrumentation = "instrumentation"; +const char* OOM = "OOM"; +const char* Other = "other"; +const char* PromiseRejection = "promiseRejection"; +const char* XHR = "XHR"; +} // namespace ReasonEnum +} // namespace Paused + +namespace API { +namespace Paused { +namespace ReasonEnum { +const char* Ambiguous = "ambiguous"; +const char* Assert = "assert"; +const char* CSPViolation = "CSPViolation"; +const char* DebugCommand = "debugCommand"; +const char* DOM = "DOM"; +const char* EventListener = "EventListener"; +const char* Exception = "exception"; +const char* Instrumentation = "instrumentation"; +const char* OOM = "OOM"; +const char* Other = "other"; +const char* PromiseRejection = "promiseRejection"; +const char* XHR = "XHR"; +} // namespace ReasonEnum +} // namespace Paused +} // namespace API + +// ------------- Frontend notifications. + +void Frontend::breakpointResolved(const String& breakpointId, std::unique_ptr location) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("breakpointId"), breakpointId); + serializer.AddField(v8_crdtp::MakeSpan("location"), location); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Debugger.breakpointResolved", serializer.Finish())); +} + +void Frontend::paused(std::unique_ptr> callFrames, const String& reason, Maybe data, Maybe> hitBreakpoints, Maybe asyncStackTrace, Maybe asyncStackTraceId, Maybe asyncCallStackTraceId) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("callFrames"), callFrames); + serializer.AddField(v8_crdtp::MakeSpan("reason"), reason); + serializer.AddField(v8_crdtp::MakeSpan("data"), data); + serializer.AddField(v8_crdtp::MakeSpan("hitBreakpoints"), hitBreakpoints); + serializer.AddField(v8_crdtp::MakeSpan("asyncStackTrace"), asyncStackTrace); + serializer.AddField(v8_crdtp::MakeSpan("asyncStackTraceId"), asyncStackTraceId); + serializer.AddField(v8_crdtp::MakeSpan("asyncCallStackTraceId"), asyncCallStackTraceId); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Debugger.paused", serializer.Finish())); +} + +void Frontend::resumed() +{ + if (!frontend_channel_) + return; + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Debugger.resumed")); +} + +void Frontend::scriptFailedToParse(const String& scriptId, const String& url, int startLine, int startColumn, int endLine, int endColumn, int executionContextId, const String& hash, Maybe executionContextAuxData, Maybe sourceMapURL, Maybe hasSourceURL, Maybe isModule, Maybe length, Maybe stackTrace, Maybe codeOffset, Maybe scriptLanguage, Maybe embedderName) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("scriptId"), scriptId); + serializer.AddField(v8_crdtp::MakeSpan("url"), url); + serializer.AddField(v8_crdtp::MakeSpan("startLine"), startLine); + serializer.AddField(v8_crdtp::MakeSpan("startColumn"), startColumn); + serializer.AddField(v8_crdtp::MakeSpan("endLine"), endLine); + serializer.AddField(v8_crdtp::MakeSpan("endColumn"), endColumn); + serializer.AddField(v8_crdtp::MakeSpan("executionContextId"), executionContextId); + serializer.AddField(v8_crdtp::MakeSpan("hash"), hash); + serializer.AddField(v8_crdtp::MakeSpan("executionContextAuxData"), executionContextAuxData); + serializer.AddField(v8_crdtp::MakeSpan("sourceMapURL"), sourceMapURL); + serializer.AddField(v8_crdtp::MakeSpan("hasSourceURL"), hasSourceURL); + serializer.AddField(v8_crdtp::MakeSpan("isModule"), isModule); + serializer.AddField(v8_crdtp::MakeSpan("length"), length); + serializer.AddField(v8_crdtp::MakeSpan("stackTrace"), stackTrace); + serializer.AddField(v8_crdtp::MakeSpan("codeOffset"), codeOffset); + serializer.AddField(v8_crdtp::MakeSpan("scriptLanguage"), scriptLanguage); + serializer.AddField(v8_crdtp::MakeSpan("embedderName"), embedderName); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Debugger.scriptFailedToParse", serializer.Finish())); +} + +void Frontend::scriptParsed(const String& scriptId, const String& url, int startLine, int startColumn, int endLine, int endColumn, int executionContextId, const String& hash, Maybe executionContextAuxData, Maybe isLiveEdit, Maybe sourceMapURL, Maybe hasSourceURL, Maybe isModule, Maybe length, Maybe stackTrace, Maybe codeOffset, Maybe scriptLanguage, Maybe debugSymbols, Maybe embedderName) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("scriptId"), scriptId); + serializer.AddField(v8_crdtp::MakeSpan("url"), url); + serializer.AddField(v8_crdtp::MakeSpan("startLine"), startLine); + serializer.AddField(v8_crdtp::MakeSpan("startColumn"), startColumn); + serializer.AddField(v8_crdtp::MakeSpan("endLine"), endLine); + serializer.AddField(v8_crdtp::MakeSpan("endColumn"), endColumn); + serializer.AddField(v8_crdtp::MakeSpan("executionContextId"), executionContextId); + serializer.AddField(v8_crdtp::MakeSpan("hash"), hash); + serializer.AddField(v8_crdtp::MakeSpan("executionContextAuxData"), executionContextAuxData); + serializer.AddField(v8_crdtp::MakeSpan("isLiveEdit"), isLiveEdit); + serializer.AddField(v8_crdtp::MakeSpan("sourceMapURL"), sourceMapURL); + serializer.AddField(v8_crdtp::MakeSpan("hasSourceURL"), hasSourceURL); + serializer.AddField(v8_crdtp::MakeSpan("isModule"), isModule); + serializer.AddField(v8_crdtp::MakeSpan("length"), length); + serializer.AddField(v8_crdtp::MakeSpan("stackTrace"), stackTrace); + serializer.AddField(v8_crdtp::MakeSpan("codeOffset"), codeOffset); + serializer.AddField(v8_crdtp::MakeSpan("scriptLanguage"), scriptLanguage); + serializer.AddField(v8_crdtp::MakeSpan("debugSymbols"), debugSymbols); + serializer.AddField(v8_crdtp::MakeSpan("embedderName"), embedderName); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Debugger.scriptParsed", serializer.Finish())); +} + +void Frontend::flush() +{ + frontend_channel_->FlushProtocolNotifications(); +} + +void Frontend::sendRawNotification(std::unique_ptr notification) +{ + frontend_channel_->SendProtocolNotification(std::move(notification)); +} + +// --------------------- Dispatcher. + +class DomainDispatcherImpl : public protocol::DomainDispatcher { +public: + DomainDispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) + : DomainDispatcher(frontendChannel) + , m_backend(backend) {} + ~DomainDispatcherImpl() override { } + + using CallHandler = void (DomainDispatcherImpl::*)(const v8_crdtp::Dispatchable& dispatchable); + + std::function Dispatch(v8_crdtp::span command_name) override; + + void continueToLocation(const v8_crdtp::Dispatchable& dispatchable); + void disable(const v8_crdtp::Dispatchable& dispatchable); + void enable(const v8_crdtp::Dispatchable& dispatchable); + void evaluateOnCallFrame(const v8_crdtp::Dispatchable& dispatchable); + void getPossibleBreakpoints(const v8_crdtp::Dispatchable& dispatchable); + void getScriptSource(const v8_crdtp::Dispatchable& dispatchable); + void disassembleWasmModule(const v8_crdtp::Dispatchable& dispatchable); + void nextWasmDisassemblyChunk(const v8_crdtp::Dispatchable& dispatchable); + void getWasmBytecode(const v8_crdtp::Dispatchable& dispatchable); + void getStackTrace(const v8_crdtp::Dispatchable& dispatchable); + void pause(const v8_crdtp::Dispatchable& dispatchable); + void pauseOnAsyncCall(const v8_crdtp::Dispatchable& dispatchable); + void removeBreakpoint(const v8_crdtp::Dispatchable& dispatchable); + void restartFrame(const v8_crdtp::Dispatchable& dispatchable); + void resume(const v8_crdtp::Dispatchable& dispatchable); + void searchInContent(const v8_crdtp::Dispatchable& dispatchable); + void setAsyncCallStackDepth(const v8_crdtp::Dispatchable& dispatchable); + void setBlackboxPatterns(const v8_crdtp::Dispatchable& dispatchable); + void setBlackboxedRanges(const v8_crdtp::Dispatchable& dispatchable); + void setBreakpoint(const v8_crdtp::Dispatchable& dispatchable); + void setInstrumentationBreakpoint(const v8_crdtp::Dispatchable& dispatchable); + void setBreakpointByUrl(const v8_crdtp::Dispatchable& dispatchable); + void setBreakpointOnFunctionCall(const v8_crdtp::Dispatchable& dispatchable); + void setBreakpointsActive(const v8_crdtp::Dispatchable& dispatchable); + void setPauseOnExceptions(const v8_crdtp::Dispatchable& dispatchable); + void setReturnValue(const v8_crdtp::Dispatchable& dispatchable); + void setScriptSource(const v8_crdtp::Dispatchable& dispatchable); + void setSkipAllPauses(const v8_crdtp::Dispatchable& dispatchable); + void setVariableValue(const v8_crdtp::Dispatchable& dispatchable); + void stepInto(const v8_crdtp::Dispatchable& dispatchable); + void stepOut(const v8_crdtp::Dispatchable& dispatchable); + void stepOver(const v8_crdtp::Dispatchable& dispatchable); + protected: + Backend* m_backend; +}; + +namespace { +// This helper method with a static map of command methods (instance methods +// of DomainDispatcherImpl declared just above) by their name is used immediately below, +// in the DomainDispatcherImpl::Dispatch method. +DomainDispatcherImpl::CallHandler CommandByName(v8_crdtp::span command_name) { + static auto* commands = [](){ + auto* commands = new std::vector, + DomainDispatcherImpl::CallHandler>>{ + { + v8_crdtp::SpanFrom("continueToLocation"), + &DomainDispatcherImpl::continueToLocation + }, + { + v8_crdtp::SpanFrom("disable"), + &DomainDispatcherImpl::disable + }, + { + v8_crdtp::SpanFrom("disassembleWasmModule"), + &DomainDispatcherImpl::disassembleWasmModule + }, + { + v8_crdtp::SpanFrom("enable"), + &DomainDispatcherImpl::enable + }, + { + v8_crdtp::SpanFrom("evaluateOnCallFrame"), + &DomainDispatcherImpl::evaluateOnCallFrame + }, + { + v8_crdtp::SpanFrom("getPossibleBreakpoints"), + &DomainDispatcherImpl::getPossibleBreakpoints + }, + { + v8_crdtp::SpanFrom("getScriptSource"), + &DomainDispatcherImpl::getScriptSource + }, + { + v8_crdtp::SpanFrom("getStackTrace"), + &DomainDispatcherImpl::getStackTrace + }, + { + v8_crdtp::SpanFrom("getWasmBytecode"), + &DomainDispatcherImpl::getWasmBytecode + }, + { + v8_crdtp::SpanFrom("nextWasmDisassemblyChunk"), + &DomainDispatcherImpl::nextWasmDisassemblyChunk + }, + { + v8_crdtp::SpanFrom("pause"), + &DomainDispatcherImpl::pause + }, + { + v8_crdtp::SpanFrom("pauseOnAsyncCall"), + &DomainDispatcherImpl::pauseOnAsyncCall + }, + { + v8_crdtp::SpanFrom("removeBreakpoint"), + &DomainDispatcherImpl::removeBreakpoint + }, + { + v8_crdtp::SpanFrom("restartFrame"), + &DomainDispatcherImpl::restartFrame + }, + { + v8_crdtp::SpanFrom("resume"), + &DomainDispatcherImpl::resume + }, + { + v8_crdtp::SpanFrom("searchInContent"), + &DomainDispatcherImpl::searchInContent + }, + { + v8_crdtp::SpanFrom("setAsyncCallStackDepth"), + &DomainDispatcherImpl::setAsyncCallStackDepth + }, + { + v8_crdtp::SpanFrom("setBlackboxPatterns"), + &DomainDispatcherImpl::setBlackboxPatterns + }, + { + v8_crdtp::SpanFrom("setBlackboxedRanges"), + &DomainDispatcherImpl::setBlackboxedRanges + }, + { + v8_crdtp::SpanFrom("setBreakpoint"), + &DomainDispatcherImpl::setBreakpoint + }, + { + v8_crdtp::SpanFrom("setBreakpointByUrl"), + &DomainDispatcherImpl::setBreakpointByUrl + }, + { + v8_crdtp::SpanFrom("setBreakpointOnFunctionCall"), + &DomainDispatcherImpl::setBreakpointOnFunctionCall + }, + { + v8_crdtp::SpanFrom("setBreakpointsActive"), + &DomainDispatcherImpl::setBreakpointsActive + }, + { + v8_crdtp::SpanFrom("setInstrumentationBreakpoint"), + &DomainDispatcherImpl::setInstrumentationBreakpoint + }, + { + v8_crdtp::SpanFrom("setPauseOnExceptions"), + &DomainDispatcherImpl::setPauseOnExceptions + }, + { + v8_crdtp::SpanFrom("setReturnValue"), + &DomainDispatcherImpl::setReturnValue + }, + { + v8_crdtp::SpanFrom("setScriptSource"), + &DomainDispatcherImpl::setScriptSource + }, + { + v8_crdtp::SpanFrom("setSkipAllPauses"), + &DomainDispatcherImpl::setSkipAllPauses + }, + { + v8_crdtp::SpanFrom("setVariableValue"), + &DomainDispatcherImpl::setVariableValue + }, + { + v8_crdtp::SpanFrom("stepInto"), + &DomainDispatcherImpl::stepInto + }, + { + v8_crdtp::SpanFrom("stepOut"), + &DomainDispatcherImpl::stepOut + }, + { + v8_crdtp::SpanFrom("stepOver"), + &DomainDispatcherImpl::stepOver + }, + }; + return commands; + }(); + return v8_crdtp::FindByFirst(*commands, command_name, nullptr); +} +} // namespace + +std::function DomainDispatcherImpl::Dispatch(v8_crdtp::span command_name) { + CallHandler handler = CommandByName(command_name); + if (!handler) return nullptr; + + return [this, handler](const v8_crdtp::Dispatchable& dispatchable) { + (this->*handler)(dispatchable); + }; +} + + +namespace { + +struct continueToLocationParams : public v8_crdtp::DeserializableProtocolObject { + std::unique_ptr location; + Maybe targetCallFrames; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(continueToLocationParams) + V8_CRDTP_DESERIALIZE_FIELD("location", location), + V8_CRDTP_DESERIALIZE_FIELD_OPT("targetCallFrames", targetCallFrames), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::continueToLocation(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + continueToLocationParams params; + if (!continueToLocationParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->continueToLocation(std::move(params.location), std::move(params.targetCallFrames)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.continueToLocation"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::disable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.disable"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct enableParams : public v8_crdtp::DeserializableProtocolObject { + Maybe maxScriptsCacheSize; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(enableParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("maxScriptsCacheSize", maxScriptsCacheSize), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::enable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + enableParams params; + if (!enableParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + String out_debuggerId; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->enable(std::move(params.maxScriptsCacheSize), &out_debuggerId); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.enable"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("debuggerId"), out_debuggerId); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct evaluateOnCallFrameParams : public v8_crdtp::DeserializableProtocolObject { + String callFrameId; + String expression; + Maybe objectGroup; + Maybe includeCommandLineAPI; + Maybe silent; + Maybe returnByValue; + Maybe generatePreview; + Maybe throwOnSideEffect; + Maybe timeout; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(evaluateOnCallFrameParams) + V8_CRDTP_DESERIALIZE_FIELD("callFrameId", callFrameId), + V8_CRDTP_DESERIALIZE_FIELD("expression", expression), + V8_CRDTP_DESERIALIZE_FIELD_OPT("generatePreview", generatePreview), + V8_CRDTP_DESERIALIZE_FIELD_OPT("includeCommandLineAPI", includeCommandLineAPI), + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectGroup", objectGroup), + V8_CRDTP_DESERIALIZE_FIELD_OPT("returnByValue", returnByValue), + V8_CRDTP_DESERIALIZE_FIELD_OPT("silent", silent), + V8_CRDTP_DESERIALIZE_FIELD_OPT("throwOnSideEffect", throwOnSideEffect), + V8_CRDTP_DESERIALIZE_FIELD_OPT("timeout", timeout), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::evaluateOnCallFrame(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + evaluateOnCallFrameParams params; + if (!evaluateOnCallFrameParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr out_result; + Maybe out_exceptionDetails; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->evaluateOnCallFrame(params.callFrameId, params.expression, std::move(params.objectGroup), std::move(params.includeCommandLineAPI), std::move(params.silent), std::move(params.returnByValue), std::move(params.generatePreview), std::move(params.throwOnSideEffect), std::move(params.timeout), &out_result, &out_exceptionDetails); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.evaluateOnCallFrame"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), out_result); + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), out_exceptionDetails); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct getPossibleBreakpointsParams : public v8_crdtp::DeserializableProtocolObject { + std::unique_ptr start; + Maybe end; + Maybe restrictToFunction; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(getPossibleBreakpointsParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("end", end), + V8_CRDTP_DESERIALIZE_FIELD_OPT("restrictToFunction", restrictToFunction), + V8_CRDTP_DESERIALIZE_FIELD("start", start), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::getPossibleBreakpoints(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + getPossibleBreakpointsParams params; + if (!getPossibleBreakpointsParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr> out_locations; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getPossibleBreakpoints(std::move(params.start), std::move(params.end), std::move(params.restrictToFunction), &out_locations); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.getPossibleBreakpoints"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("locations"), out_locations); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct getScriptSourceParams : public v8_crdtp::DeserializableProtocolObject { + String scriptId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(getScriptSourceParams) + V8_CRDTP_DESERIALIZE_FIELD("scriptId", scriptId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::getScriptSource(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + getScriptSourceParams params; + if (!getScriptSourceParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + String out_scriptSource; + Maybe out_bytecode; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getScriptSource(params.scriptId, &out_scriptSource, &out_bytecode); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.getScriptSource"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("scriptSource"), out_scriptSource); + serializer.AddField(v8_crdtp::MakeSpan("bytecode"), out_bytecode); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct disassembleWasmModuleParams : public v8_crdtp::DeserializableProtocolObject { + String scriptId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(disassembleWasmModuleParams) + V8_CRDTP_DESERIALIZE_FIELD("scriptId", scriptId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::disassembleWasmModule(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + disassembleWasmModuleParams params; + if (!disassembleWasmModuleParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + Maybe out_streamId; + int out_totalNumberOfLines; + std::unique_ptr> out_functionBodyOffsets; + std::unique_ptr out_chunk; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disassembleWasmModule(params.scriptId, &out_streamId, &out_totalNumberOfLines, &out_functionBodyOffsets, &out_chunk); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.disassembleWasmModule"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("streamId"), out_streamId); + serializer.AddField(v8_crdtp::MakeSpan("totalNumberOfLines"), out_totalNumberOfLines); + serializer.AddField(v8_crdtp::MakeSpan("functionBodyOffsets"), out_functionBodyOffsets); + serializer.AddField(v8_crdtp::MakeSpan("chunk"), out_chunk); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct nextWasmDisassemblyChunkParams : public v8_crdtp::DeserializableProtocolObject { + String streamId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(nextWasmDisassemblyChunkParams) + V8_CRDTP_DESERIALIZE_FIELD("streamId", streamId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::nextWasmDisassemblyChunk(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + nextWasmDisassemblyChunkParams params; + if (!nextWasmDisassemblyChunkParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr out_chunk; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->nextWasmDisassemblyChunk(params.streamId, &out_chunk); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.nextWasmDisassemblyChunk"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("chunk"), out_chunk); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct getWasmBytecodeParams : public v8_crdtp::DeserializableProtocolObject { + String scriptId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(getWasmBytecodeParams) + V8_CRDTP_DESERIALIZE_FIELD("scriptId", scriptId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::getWasmBytecode(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + getWasmBytecodeParams params; + if (!getWasmBytecodeParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + Binary out_bytecode; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getWasmBytecode(params.scriptId, &out_bytecode); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.getWasmBytecode"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("bytecode"), out_bytecode); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct getStackTraceParams : public v8_crdtp::DeserializableProtocolObject { + std::unique_ptr stackTraceId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(getStackTraceParams) + V8_CRDTP_DESERIALIZE_FIELD("stackTraceId", stackTraceId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::getStackTrace(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + getStackTraceParams params; + if (!getStackTraceParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr out_stackTrace; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getStackTrace(std::move(params.stackTraceId), &out_stackTrace); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.getStackTrace"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("stackTrace"), out_stackTrace); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::pause(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->pause(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.pause"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct pauseOnAsyncCallParams : public v8_crdtp::DeserializableProtocolObject { + std::unique_ptr parentStackTraceId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(pauseOnAsyncCallParams) + V8_CRDTP_DESERIALIZE_FIELD("parentStackTraceId", parentStackTraceId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::pauseOnAsyncCall(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + pauseOnAsyncCallParams params; + if (!pauseOnAsyncCallParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->pauseOnAsyncCall(std::move(params.parentStackTraceId)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.pauseOnAsyncCall"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct removeBreakpointParams : public v8_crdtp::DeserializableProtocolObject { + String breakpointId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(removeBreakpointParams) + V8_CRDTP_DESERIALIZE_FIELD("breakpointId", breakpointId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::removeBreakpoint(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + removeBreakpointParams params; + if (!removeBreakpointParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->removeBreakpoint(params.breakpointId); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.removeBreakpoint"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct restartFrameParams : public v8_crdtp::DeserializableProtocolObject { + String callFrameId; + Maybe mode; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(restartFrameParams) + V8_CRDTP_DESERIALIZE_FIELD("callFrameId", callFrameId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("mode", mode), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::restartFrame(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + restartFrameParams params; + if (!restartFrameParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr> out_callFrames; + Maybe out_asyncStackTrace; + Maybe out_asyncStackTraceId; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->restartFrame(params.callFrameId, std::move(params.mode), &out_callFrames, &out_asyncStackTrace, &out_asyncStackTraceId); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.restartFrame"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("callFrames"), out_callFrames); + serializer.AddField(v8_crdtp::MakeSpan("asyncStackTrace"), out_asyncStackTrace); + serializer.AddField(v8_crdtp::MakeSpan("asyncStackTraceId"), out_asyncStackTraceId); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct resumeParams : public v8_crdtp::DeserializableProtocolObject { + Maybe terminateOnResume; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(resumeParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("terminateOnResume", terminateOnResume), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::resume(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + resumeParams params; + if (!resumeParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->resume(std::move(params.terminateOnResume)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.resume"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct searchInContentParams : public v8_crdtp::DeserializableProtocolObject { + String scriptId; + String query; + Maybe caseSensitive; + Maybe isRegex; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(searchInContentParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("caseSensitive", caseSensitive), + V8_CRDTP_DESERIALIZE_FIELD_OPT("isRegex", isRegex), + V8_CRDTP_DESERIALIZE_FIELD("query", query), + V8_CRDTP_DESERIALIZE_FIELD("scriptId", scriptId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::searchInContent(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + searchInContentParams params; + if (!searchInContentParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr> out_result; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->searchInContent(params.scriptId, params.query, std::move(params.caseSensitive), std::move(params.isRegex), &out_result); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.searchInContent"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), out_result); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct setAsyncCallStackDepthParams : public v8_crdtp::DeserializableProtocolObject { + int maxDepth; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setAsyncCallStackDepthParams) + V8_CRDTP_DESERIALIZE_FIELD("maxDepth", maxDepth), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setAsyncCallStackDepth(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setAsyncCallStackDepthParams params; + if (!setAsyncCallStackDepthParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setAsyncCallStackDepth(params.maxDepth); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setAsyncCallStackDepth"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct setBlackboxPatternsParams : public v8_crdtp::DeserializableProtocolObject { + std::unique_ptr> patterns; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setBlackboxPatternsParams) + V8_CRDTP_DESERIALIZE_FIELD("patterns", patterns), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setBlackboxPatterns(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setBlackboxPatternsParams params; + if (!setBlackboxPatternsParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBlackboxPatterns(std::move(params.patterns)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setBlackboxPatterns"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct setBlackboxedRangesParams : public v8_crdtp::DeserializableProtocolObject { + String scriptId; + std::unique_ptr> positions; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setBlackboxedRangesParams) + V8_CRDTP_DESERIALIZE_FIELD("positions", positions), + V8_CRDTP_DESERIALIZE_FIELD("scriptId", scriptId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setBlackboxedRanges(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setBlackboxedRangesParams params; + if (!setBlackboxedRangesParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBlackboxedRanges(params.scriptId, std::move(params.positions)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setBlackboxedRanges"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct setBreakpointParams : public v8_crdtp::DeserializableProtocolObject { + std::unique_ptr location; + Maybe condition; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setBreakpointParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("condition", condition), + V8_CRDTP_DESERIALIZE_FIELD("location", location), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setBreakpoint(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setBreakpointParams params; + if (!setBreakpointParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + String out_breakpointId; + std::unique_ptr out_actualLocation; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBreakpoint(std::move(params.location), std::move(params.condition), &out_breakpointId, &out_actualLocation); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setBreakpoint"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("breakpointId"), out_breakpointId); + serializer.AddField(v8_crdtp::MakeSpan("actualLocation"), out_actualLocation); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct setInstrumentationBreakpointParams : public v8_crdtp::DeserializableProtocolObject { + String instrumentation; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setInstrumentationBreakpointParams) + V8_CRDTP_DESERIALIZE_FIELD("instrumentation", instrumentation), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setInstrumentationBreakpoint(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setInstrumentationBreakpointParams params; + if (!setInstrumentationBreakpointParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + String out_breakpointId; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setInstrumentationBreakpoint(params.instrumentation, &out_breakpointId); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setInstrumentationBreakpoint"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("breakpointId"), out_breakpointId); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct setBreakpointByUrlParams : public v8_crdtp::DeserializableProtocolObject { + int lineNumber; + Maybe url; + Maybe urlRegex; + Maybe scriptHash; + Maybe columnNumber; + Maybe condition; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setBreakpointByUrlParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("columnNumber", columnNumber), + V8_CRDTP_DESERIALIZE_FIELD_OPT("condition", condition), + V8_CRDTP_DESERIALIZE_FIELD("lineNumber", lineNumber), + V8_CRDTP_DESERIALIZE_FIELD_OPT("scriptHash", scriptHash), + V8_CRDTP_DESERIALIZE_FIELD_OPT("url", url), + V8_CRDTP_DESERIALIZE_FIELD_OPT("urlRegex", urlRegex), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setBreakpointByUrl(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setBreakpointByUrlParams params; + if (!setBreakpointByUrlParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + String out_breakpointId; + std::unique_ptr> out_locations; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBreakpointByUrl(params.lineNumber, std::move(params.url), std::move(params.urlRegex), std::move(params.scriptHash), std::move(params.columnNumber), std::move(params.condition), &out_breakpointId, &out_locations); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setBreakpointByUrl"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("breakpointId"), out_breakpointId); + serializer.AddField(v8_crdtp::MakeSpan("locations"), out_locations); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct setBreakpointOnFunctionCallParams : public v8_crdtp::DeserializableProtocolObject { + String objectId; + Maybe condition; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setBreakpointOnFunctionCallParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("condition", condition), + V8_CRDTP_DESERIALIZE_FIELD("objectId", objectId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setBreakpointOnFunctionCall(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setBreakpointOnFunctionCallParams params; + if (!setBreakpointOnFunctionCallParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + String out_breakpointId; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBreakpointOnFunctionCall(params.objectId, std::move(params.condition), &out_breakpointId); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setBreakpointOnFunctionCall"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("breakpointId"), out_breakpointId); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct setBreakpointsActiveParams : public v8_crdtp::DeserializableProtocolObject { + bool active; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setBreakpointsActiveParams) + V8_CRDTP_DESERIALIZE_FIELD("active", active), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setBreakpointsActive(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setBreakpointsActiveParams params; + if (!setBreakpointsActiveParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setBreakpointsActive(params.active); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setBreakpointsActive"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct setPauseOnExceptionsParams : public v8_crdtp::DeserializableProtocolObject { + String state; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setPauseOnExceptionsParams) + V8_CRDTP_DESERIALIZE_FIELD("state", state), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setPauseOnExceptions(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setPauseOnExceptionsParams params; + if (!setPauseOnExceptionsParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setPauseOnExceptions(params.state); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setPauseOnExceptions"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct setReturnValueParams : public v8_crdtp::DeserializableProtocolObject { + std::unique_ptr newValue; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setReturnValueParams) + V8_CRDTP_DESERIALIZE_FIELD("newValue", newValue), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setReturnValue(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setReturnValueParams params; + if (!setReturnValueParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setReturnValue(std::move(params.newValue)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setReturnValue"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct setScriptSourceParams : public v8_crdtp::DeserializableProtocolObject { + String scriptId; + String scriptSource; + Maybe dryRun; + Maybe allowTopFrameEditing; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setScriptSourceParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("allowTopFrameEditing", allowTopFrameEditing), + V8_CRDTP_DESERIALIZE_FIELD_OPT("dryRun", dryRun), + V8_CRDTP_DESERIALIZE_FIELD("scriptId", scriptId), + V8_CRDTP_DESERIALIZE_FIELD("scriptSource", scriptSource), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setScriptSource(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setScriptSourceParams params; + if (!setScriptSourceParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + Maybe> out_callFrames; + Maybe out_stackChanged; + Maybe out_asyncStackTrace; + Maybe out_asyncStackTraceId; + String out_status; + Maybe out_exceptionDetails; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setScriptSource(params.scriptId, params.scriptSource, std::move(params.dryRun), std::move(params.allowTopFrameEditing), &out_callFrames, &out_stackChanged, &out_asyncStackTrace, &out_asyncStackTraceId, &out_status, &out_exceptionDetails); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setScriptSource"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("callFrames"), out_callFrames); + serializer.AddField(v8_crdtp::MakeSpan("stackChanged"), out_stackChanged); + serializer.AddField(v8_crdtp::MakeSpan("asyncStackTrace"), out_asyncStackTrace); + serializer.AddField(v8_crdtp::MakeSpan("asyncStackTraceId"), out_asyncStackTraceId); + serializer.AddField(v8_crdtp::MakeSpan("status"), out_status); + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), out_exceptionDetails); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct setSkipAllPausesParams : public v8_crdtp::DeserializableProtocolObject { + bool skip; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setSkipAllPausesParams) + V8_CRDTP_DESERIALIZE_FIELD("skip", skip), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setSkipAllPauses(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setSkipAllPausesParams params; + if (!setSkipAllPausesParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setSkipAllPauses(params.skip); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setSkipAllPauses"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct setVariableValueParams : public v8_crdtp::DeserializableProtocolObject { + int scopeNumber; + String variableName; + std::unique_ptr newValue; + String callFrameId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setVariableValueParams) + V8_CRDTP_DESERIALIZE_FIELD("callFrameId", callFrameId), + V8_CRDTP_DESERIALIZE_FIELD("newValue", newValue), + V8_CRDTP_DESERIALIZE_FIELD("scopeNumber", scopeNumber), + V8_CRDTP_DESERIALIZE_FIELD("variableName", variableName), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setVariableValue(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setVariableValueParams params; + if (!setVariableValueParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setVariableValue(params.scopeNumber, params.variableName, std::move(params.newValue), params.callFrameId); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.setVariableValue"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct stepIntoParams : public v8_crdtp::DeserializableProtocolObject { + Maybe breakOnAsyncCall; + Maybe> skipList; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(stepIntoParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("breakOnAsyncCall", breakOnAsyncCall), + V8_CRDTP_DESERIALIZE_FIELD_OPT("skipList", skipList), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::stepInto(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + stepIntoParams params; + if (!stepIntoParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stepInto(std::move(params.breakOnAsyncCall), std::move(params.skipList)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.stepInto"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::stepOut(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stepOut(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.stepOut"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct stepOverParams : public v8_crdtp::DeserializableProtocolObject { + Maybe> skipList; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(stepOverParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("skipList", skipList), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::stepOver(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + stepOverParams params; + if (!stepOverParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stepOver(std::move(params.skipList)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Debugger.stepOver"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { +// This helper method (with a static map of redirects) is used from Dispatcher::wire +// immediately below. +const std::vector, v8_crdtp::span>>& SortedRedirects() { + static auto* redirects = [](){ + auto* redirects = new std::vector, v8_crdtp::span>>{ + }; + return redirects; + }(); + return *redirects; +} +} // namespace + +// static +void Dispatcher::wire(UberDispatcher* uber, Backend* backend) +{ + auto dispatcher = std::make_unique(uber->channel(), backend); + uber->WireBackend(v8_crdtp::SpanFrom("Debugger"), SortedRedirects(), std::move(dispatcher)); +} + +} // Debugger +} // namespace v8_inspector +} // namespace protocol diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Debugger.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Debugger.h new file mode 100644 index 000000000..e0079dcd9 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Debugger.h @@ -0,0 +1,991 @@ +// This file is generated by TypeBuilder_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Debugger_h +#define v8_inspector_protocol_Debugger_h + +#include "src/inspector/protocol/Protocol.h" +// For each imported domain we generate a ValueConversions struct instead of a full domain definition +// and include Domain::API version from there. +#include "src/inspector/protocol/Runtime.h" +#include "include/inspector/Debugger.h" + +namespace v8_inspector { +namespace protocol { +namespace Debugger { +using BreakpointId = String; +using CallFrameId = String; +class Location; +class ScriptPosition; +class LocationRange; +class CallFrame; +class Scope; +class SearchMatch; +class BreakLocation; +class WasmDisassemblyChunk; +using ScriptLanguage = String; +class DebugSymbols; + +// ------------- Forward and enum declarations. + +namespace ScriptLanguageEnum { + extern const char JavaScript[]; + extern const char WebAssembly[]; +} // namespace ScriptLanguageEnum + +namespace ContinueToLocation { +namespace TargetCallFramesEnum { + extern const char* Any; + extern const char* Current; +} // TargetCallFramesEnum +} // ContinueToLocation + +namespace RestartFrame { +namespace ModeEnum { + extern const char* StepInto; +} // ModeEnum +} // RestartFrame + +namespace SetInstrumentationBreakpoint { +namespace InstrumentationEnum { + extern const char* BeforeScriptExecution; + extern const char* BeforeScriptWithSourceMapExecution; +} // InstrumentationEnum +} // SetInstrumentationBreakpoint + +namespace SetPauseOnExceptions { +namespace StateEnum { + extern const char* None; + extern const char* Caught; + extern const char* Uncaught; + extern const char* All; +} // StateEnum +} // SetPauseOnExceptions + +namespace SetScriptSource { +namespace StatusEnum { + extern const char* Ok; + extern const char* CompileError; + extern const char* BlockedByActiveGenerator; + extern const char* BlockedByActiveFunction; +} // StatusEnum +} // SetScriptSource + +namespace Paused { +namespace ReasonEnum { + extern const char* Ambiguous; + extern const char* Assert; + extern const char* CSPViolation; + extern const char* DebugCommand; + extern const char* DOM; + extern const char* EventListener; + extern const char* Exception; + extern const char* Instrumentation; + extern const char* OOM; + extern const char* Other; + extern const char* PromiseRejection; + extern const char* XHR; +} // ReasonEnum +} // Paused + +// ------------- Type and builder declarations. + +class Location : public ::v8_crdtp::ProtocolObject { +public: + ~Location() override { } + + String getScriptId() { return m_scriptId; } + void setScriptId(const String& value) { m_scriptId = value; } + + int getLineNumber() { return m_lineNumber; } + void setLineNumber(int value) { m_lineNumber = value; } + + bool hasColumnNumber() { return m_columnNumber.isJust(); } + int getColumnNumber(int defaultValue) { return m_columnNumber.isJust() ? m_columnNumber.fromJust() : defaultValue; } + void setColumnNumber(int value) { m_columnNumber = value; } + + template + class LocationBuilder { + public: + enum { + NoFieldsSet = 0, + ScriptIdSet = 1 << 1, + LineNumberSet = 1 << 2, + AllFieldsSet = (ScriptIdSet | LineNumberSet | 0)}; + + + LocationBuilder& setScriptId(const String& value) + { + static_assert(!(STATE & ScriptIdSet), "property scriptId should not be set yet"); + m_result->setScriptId(value); + return castState(); + } + + LocationBuilder& setLineNumber(int value) + { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + LocationBuilder& setColumnNumber(int value) + { + m_result->setColumnNumber(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class Location; + LocationBuilder() : m_result(new Location()) { } + + template LocationBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static LocationBuilder<0> create() + { + return LocationBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + Location() + { + m_lineNumber = 0; + } + + String m_scriptId; + int m_lineNumber; + Maybe m_columnNumber; +}; + + +class ScriptPosition : public ::v8_crdtp::ProtocolObject { +public: + ~ScriptPosition() override { } + + int getLineNumber() { return m_lineNumber; } + void setLineNumber(int value) { m_lineNumber = value; } + + int getColumnNumber() { return m_columnNumber; } + void setColumnNumber(int value) { m_columnNumber = value; } + + template + class ScriptPositionBuilder { + public: + enum { + NoFieldsSet = 0, + LineNumberSet = 1 << 1, + ColumnNumberSet = 1 << 2, + AllFieldsSet = (LineNumberSet | ColumnNumberSet | 0)}; + + + ScriptPositionBuilder& setLineNumber(int value) + { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + ScriptPositionBuilder& setColumnNumber(int value) + { + static_assert(!(STATE & ColumnNumberSet), "property columnNumber should not be set yet"); + m_result->setColumnNumber(value); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ScriptPosition; + ScriptPositionBuilder() : m_result(new ScriptPosition()) { } + + template ScriptPositionBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static ScriptPositionBuilder<0> create() + { + return ScriptPositionBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + ScriptPosition() + { + m_lineNumber = 0; + m_columnNumber = 0; + } + + int m_lineNumber; + int m_columnNumber; +}; + + +class LocationRange : public ::v8_crdtp::ProtocolObject { +public: + ~LocationRange() override { } + + String getScriptId() { return m_scriptId; } + void setScriptId(const String& value) { m_scriptId = value; } + + protocol::Debugger::ScriptPosition* getStart() { return m_start.get(); } + void setStart(std::unique_ptr value) { m_start = std::move(value); } + + protocol::Debugger::ScriptPosition* getEnd() { return m_end.get(); } + void setEnd(std::unique_ptr value) { m_end = std::move(value); } + + template + class LocationRangeBuilder { + public: + enum { + NoFieldsSet = 0, + ScriptIdSet = 1 << 1, + StartSet = 1 << 2, + EndSet = 1 << 3, + AllFieldsSet = (ScriptIdSet | StartSet | EndSet | 0)}; + + + LocationRangeBuilder& setScriptId(const String& value) + { + static_assert(!(STATE & ScriptIdSet), "property scriptId should not be set yet"); + m_result->setScriptId(value); + return castState(); + } + + LocationRangeBuilder& setStart(std::unique_ptr value) + { + static_assert(!(STATE & StartSet), "property start should not be set yet"); + m_result->setStart(std::move(value)); + return castState(); + } + + LocationRangeBuilder& setEnd(std::unique_ptr value) + { + static_assert(!(STATE & EndSet), "property end should not be set yet"); + m_result->setEnd(std::move(value)); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class LocationRange; + LocationRangeBuilder() : m_result(new LocationRange()) { } + + template LocationRangeBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static LocationRangeBuilder<0> create() + { + return LocationRangeBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + LocationRange() + { + } + + String m_scriptId; + std::unique_ptr m_start; + std::unique_ptr m_end; +}; + + +class CallFrame : public ::v8_crdtp::ProtocolObject { +public: + ~CallFrame() override { } + + String getCallFrameId() { return m_callFrameId; } + void setCallFrameId(const String& value) { m_callFrameId = value; } + + String getFunctionName() { return m_functionName; } + void setFunctionName(const String& value) { m_functionName = value; } + + bool hasFunctionLocation() { return m_functionLocation.isJust(); } + protocol::Debugger::Location* getFunctionLocation(protocol::Debugger::Location* defaultValue) { return m_functionLocation.isJust() ? m_functionLocation.fromJust() : defaultValue; } + void setFunctionLocation(std::unique_ptr value) { m_functionLocation = std::move(value); } + + protocol::Debugger::Location* getLocation() { return m_location.get(); } + void setLocation(std::unique_ptr value) { m_location = std::move(value); } + + String getUrl() { return m_url; } + void setUrl(const String& value) { m_url = value; } + + protocol::Array* getScopeChain() { return m_scopeChain.get(); } + void setScopeChain(std::unique_ptr> value) { m_scopeChain = std::move(value); } + + protocol::Runtime::RemoteObject* getThis() { return m_this.get(); } + void setThis(std::unique_ptr value) { m_this = std::move(value); } + + bool hasReturnValue() { return m_returnValue.isJust(); } + protocol::Runtime::RemoteObject* getReturnValue(protocol::Runtime::RemoteObject* defaultValue) { return m_returnValue.isJust() ? m_returnValue.fromJust() : defaultValue; } + void setReturnValue(std::unique_ptr value) { m_returnValue = std::move(value); } + + bool hasCanBeRestarted() { return m_canBeRestarted.isJust(); } + bool getCanBeRestarted(bool defaultValue) { return m_canBeRestarted.isJust() ? m_canBeRestarted.fromJust() : defaultValue; } + void setCanBeRestarted(bool value) { m_canBeRestarted = value; } + + template + class CallFrameBuilder { + public: + enum { + NoFieldsSet = 0, + CallFrameIdSet = 1 << 1, + FunctionNameSet = 1 << 2, + LocationSet = 1 << 3, + UrlSet = 1 << 4, + ScopeChainSet = 1 << 5, + ThisSet = 1 << 6, + AllFieldsSet = (CallFrameIdSet | FunctionNameSet | LocationSet | UrlSet | ScopeChainSet | ThisSet | 0)}; + + + CallFrameBuilder& setCallFrameId(const String& value) + { + static_assert(!(STATE & CallFrameIdSet), "property callFrameId should not be set yet"); + m_result->setCallFrameId(value); + return castState(); + } + + CallFrameBuilder& setFunctionName(const String& value) + { + static_assert(!(STATE & FunctionNameSet), "property functionName should not be set yet"); + m_result->setFunctionName(value); + return castState(); + } + + CallFrameBuilder& setFunctionLocation(std::unique_ptr value) + { + m_result->setFunctionLocation(std::move(value)); + return *this; + } + + CallFrameBuilder& setLocation(std::unique_ptr value) + { + static_assert(!(STATE & LocationSet), "property location should not be set yet"); + m_result->setLocation(std::move(value)); + return castState(); + } + + CallFrameBuilder& setUrl(const String& value) + { + static_assert(!(STATE & UrlSet), "property url should not be set yet"); + m_result->setUrl(value); + return castState(); + } + + CallFrameBuilder& setScopeChain(std::unique_ptr> value) + { + static_assert(!(STATE & ScopeChainSet), "property scopeChain should not be set yet"); + m_result->setScopeChain(std::move(value)); + return castState(); + } + + CallFrameBuilder& setThis(std::unique_ptr value) + { + static_assert(!(STATE & ThisSet), "property this should not be set yet"); + m_result->setThis(std::move(value)); + return castState(); + } + + CallFrameBuilder& setReturnValue(std::unique_ptr value) + { + m_result->setReturnValue(std::move(value)); + return *this; + } + + CallFrameBuilder& setCanBeRestarted(bool value) + { + m_result->setCanBeRestarted(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class CallFrame; + CallFrameBuilder() : m_result(new CallFrame()) { } + + template CallFrameBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static CallFrameBuilder<0> create() + { + return CallFrameBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + CallFrame() + { + } + + String m_callFrameId; + String m_functionName; + Maybe m_functionLocation; + std::unique_ptr m_location; + String m_url; + std::unique_ptr> m_scopeChain; + std::unique_ptr m_this; + Maybe m_returnValue; + Maybe m_canBeRestarted; +}; + + +class Scope : public ::v8_crdtp::ProtocolObject { +public: + ~Scope() override { } + + struct TypeEnum { + static const char* Global; + static const char* Local; + static const char* With; + static const char* Closure; + static const char* Catch; + static const char* Block; + static const char* Script; + static const char* Eval; + static const char* Module; + static const char* WasmExpressionStack; + }; // TypeEnum + + String getType() { return m_type; } + void setType(const String& value) { m_type = value; } + + protocol::Runtime::RemoteObject* getObject() { return m_object.get(); } + void setObject(std::unique_ptr value) { m_object = std::move(value); } + + bool hasName() { return m_name.isJust(); } + String getName(const String& defaultValue) { return m_name.isJust() ? m_name.fromJust() : defaultValue; } + void setName(const String& value) { m_name = value; } + + bool hasStartLocation() { return m_startLocation.isJust(); } + protocol::Debugger::Location* getStartLocation(protocol::Debugger::Location* defaultValue) { return m_startLocation.isJust() ? m_startLocation.fromJust() : defaultValue; } + void setStartLocation(std::unique_ptr value) { m_startLocation = std::move(value); } + + bool hasEndLocation() { return m_endLocation.isJust(); } + protocol::Debugger::Location* getEndLocation(protocol::Debugger::Location* defaultValue) { return m_endLocation.isJust() ? m_endLocation.fromJust() : defaultValue; } + void setEndLocation(std::unique_ptr value) { m_endLocation = std::move(value); } + + template + class ScopeBuilder { + public: + enum { + NoFieldsSet = 0, + TypeSet = 1 << 1, + ObjectSet = 1 << 2, + AllFieldsSet = (TypeSet | ObjectSet | 0)}; + + + ScopeBuilder& setType(const String& value) + { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + ScopeBuilder& setObject(std::unique_ptr value) + { + static_assert(!(STATE & ObjectSet), "property object should not be set yet"); + m_result->setObject(std::move(value)); + return castState(); + } + + ScopeBuilder& setName(const String& value) + { + m_result->setName(value); + return *this; + } + + ScopeBuilder& setStartLocation(std::unique_ptr value) + { + m_result->setStartLocation(std::move(value)); + return *this; + } + + ScopeBuilder& setEndLocation(std::unique_ptr value) + { + m_result->setEndLocation(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class Scope; + ScopeBuilder() : m_result(new Scope()) { } + + template ScopeBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static ScopeBuilder<0> create() + { + return ScopeBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + Scope() + { + } + + String m_type; + std::unique_ptr m_object; + Maybe m_name; + Maybe m_startLocation; + Maybe m_endLocation; +}; + + +class SearchMatch : public ::v8_crdtp::ProtocolObject, + public API::SearchMatch { +public: + ~SearchMatch() override { } + + double getLineNumber() { return m_lineNumber; } + void setLineNumber(double value) { m_lineNumber = value; } + + String getLineContent() { return m_lineContent; } + void setLineContent(const String& value) { m_lineContent = value; } + + template + class SearchMatchBuilder { + public: + enum { + NoFieldsSet = 0, + LineNumberSet = 1 << 1, + LineContentSet = 1 << 2, + AllFieldsSet = (LineNumberSet | LineContentSet | 0)}; + + + SearchMatchBuilder& setLineNumber(double value) + { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + SearchMatchBuilder& setLineContent(const String& value) + { + static_assert(!(STATE & LineContentSet), "property lineContent should not be set yet"); + m_result->setLineContent(value); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class SearchMatch; + SearchMatchBuilder() : m_result(new SearchMatch()) { } + + template SearchMatchBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static SearchMatchBuilder<0> create() + { + return SearchMatchBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + SearchMatch() + { + m_lineNumber = 0; + } + + double m_lineNumber; + String m_lineContent; +}; + + +class BreakLocation : public ::v8_crdtp::ProtocolObject { +public: + ~BreakLocation() override { } + + String getScriptId() { return m_scriptId; } + void setScriptId(const String& value) { m_scriptId = value; } + + int getLineNumber() { return m_lineNumber; } + void setLineNumber(int value) { m_lineNumber = value; } + + bool hasColumnNumber() { return m_columnNumber.isJust(); } + int getColumnNumber(int defaultValue) { return m_columnNumber.isJust() ? m_columnNumber.fromJust() : defaultValue; } + void setColumnNumber(int value) { m_columnNumber = value; } + + struct TypeEnum { + static const char* DebuggerStatement; + static const char* Call; + static const char* Return; + }; // TypeEnum + + bool hasType() { return m_type.isJust(); } + String getType(const String& defaultValue) { return m_type.isJust() ? m_type.fromJust() : defaultValue; } + void setType(const String& value) { m_type = value; } + + template + class BreakLocationBuilder { + public: + enum { + NoFieldsSet = 0, + ScriptIdSet = 1 << 1, + LineNumberSet = 1 << 2, + AllFieldsSet = (ScriptIdSet | LineNumberSet | 0)}; + + + BreakLocationBuilder& setScriptId(const String& value) + { + static_assert(!(STATE & ScriptIdSet), "property scriptId should not be set yet"); + m_result->setScriptId(value); + return castState(); + } + + BreakLocationBuilder& setLineNumber(int value) + { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + BreakLocationBuilder& setColumnNumber(int value) + { + m_result->setColumnNumber(value); + return *this; + } + + BreakLocationBuilder& setType(const String& value) + { + m_result->setType(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class BreakLocation; + BreakLocationBuilder() : m_result(new BreakLocation()) { } + + template BreakLocationBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static BreakLocationBuilder<0> create() + { + return BreakLocationBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + BreakLocation() + { + m_lineNumber = 0; + } + + String m_scriptId; + int m_lineNumber; + Maybe m_columnNumber; + Maybe m_type; +}; + + +class WasmDisassemblyChunk : public ::v8_crdtp::ProtocolObject { +public: + ~WasmDisassemblyChunk() override { } + + protocol::Array* getLines() { return m_lines.get(); } + void setLines(std::unique_ptr> value) { m_lines = std::move(value); } + + protocol::Array* getBytecodeOffsets() { return m_bytecodeOffsets.get(); } + void setBytecodeOffsets(std::unique_ptr> value) { m_bytecodeOffsets = std::move(value); } + + template + class WasmDisassemblyChunkBuilder { + public: + enum { + NoFieldsSet = 0, + LinesSet = 1 << 1, + BytecodeOffsetsSet = 1 << 2, + AllFieldsSet = (LinesSet | BytecodeOffsetsSet | 0)}; + + + WasmDisassemblyChunkBuilder& setLines(std::unique_ptr> value) + { + static_assert(!(STATE & LinesSet), "property lines should not be set yet"); + m_result->setLines(std::move(value)); + return castState(); + } + + WasmDisassemblyChunkBuilder& setBytecodeOffsets(std::unique_ptr> value) + { + static_assert(!(STATE & BytecodeOffsetsSet), "property bytecodeOffsets should not be set yet"); + m_result->setBytecodeOffsets(std::move(value)); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class WasmDisassemblyChunk; + WasmDisassemblyChunkBuilder() : m_result(new WasmDisassemblyChunk()) { } + + template WasmDisassemblyChunkBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static WasmDisassemblyChunkBuilder<0> create() + { + return WasmDisassemblyChunkBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + WasmDisassemblyChunk() + { + } + + std::unique_ptr> m_lines; + std::unique_ptr> m_bytecodeOffsets; +}; + + +class DebugSymbols : public ::v8_crdtp::ProtocolObject { +public: + ~DebugSymbols() override { } + + struct TypeEnum { + static const char* None; + static const char* SourceMap; + static const char* EmbeddedDWARF; + static const char* ExternalDWARF; + }; // TypeEnum + + String getType() { return m_type; } + void setType(const String& value) { m_type = value; } + + bool hasExternalURL() { return m_externalURL.isJust(); } + String getExternalURL(const String& defaultValue) { return m_externalURL.isJust() ? m_externalURL.fromJust() : defaultValue; } + void setExternalURL(const String& value) { m_externalURL = value; } + + template + class DebugSymbolsBuilder { + public: + enum { + NoFieldsSet = 0, + TypeSet = 1 << 1, + AllFieldsSet = (TypeSet | 0)}; + + + DebugSymbolsBuilder& setType(const String& value) + { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + DebugSymbolsBuilder& setExternalURL(const String& value) + { + m_result->setExternalURL(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class DebugSymbols; + DebugSymbolsBuilder() : m_result(new DebugSymbols()) { } + + template DebugSymbolsBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static DebugSymbolsBuilder<0> create() + { + return DebugSymbolsBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + DebugSymbols() + { + } + + String m_type; + Maybe m_externalURL; +}; + + +// ------------- Backend interface. + +class Backend { +public: + virtual ~Backend() { } + + virtual DispatchResponse continueToLocation(std::unique_ptr in_location, Maybe in_targetCallFrames) = 0; + virtual DispatchResponse disable() = 0; + virtual DispatchResponse enable(Maybe in_maxScriptsCacheSize, String* out_debuggerId) = 0; + virtual DispatchResponse evaluateOnCallFrame(const String& in_callFrameId, const String& in_expression, Maybe in_objectGroup, Maybe in_includeCommandLineAPI, Maybe in_silent, Maybe in_returnByValue, Maybe in_generatePreview, Maybe in_throwOnSideEffect, Maybe in_timeout, std::unique_ptr* out_result, Maybe* out_exceptionDetails) = 0; + virtual DispatchResponse getPossibleBreakpoints(std::unique_ptr in_start, Maybe in_end, Maybe in_restrictToFunction, std::unique_ptr>* out_locations) = 0; + virtual DispatchResponse getScriptSource(const String& in_scriptId, String* out_scriptSource, Maybe* out_bytecode) = 0; + virtual DispatchResponse disassembleWasmModule(const String& in_scriptId, Maybe* out_streamId, int* out_totalNumberOfLines, std::unique_ptr>* out_functionBodyOffsets, std::unique_ptr* out_chunk) = 0; + virtual DispatchResponse nextWasmDisassemblyChunk(const String& in_streamId, std::unique_ptr* out_chunk) = 0; + virtual DispatchResponse getWasmBytecode(const String& in_scriptId, Binary* out_bytecode) = 0; + virtual DispatchResponse getStackTrace(std::unique_ptr in_stackTraceId, std::unique_ptr* out_stackTrace) = 0; + virtual DispatchResponse pause() = 0; + virtual DispatchResponse pauseOnAsyncCall(std::unique_ptr in_parentStackTraceId) = 0; + virtual DispatchResponse removeBreakpoint(const String& in_breakpointId) = 0; + virtual DispatchResponse restartFrame(const String& in_callFrameId, Maybe in_mode, std::unique_ptr>* out_callFrames, Maybe* out_asyncStackTrace, Maybe* out_asyncStackTraceId) = 0; + virtual DispatchResponse resume(Maybe in_terminateOnResume) = 0; + virtual DispatchResponse searchInContent(const String& in_scriptId, const String& in_query, Maybe in_caseSensitive, Maybe in_isRegex, std::unique_ptr>* out_result) = 0; + virtual DispatchResponse setAsyncCallStackDepth(int in_maxDepth) = 0; + virtual DispatchResponse setBlackboxPatterns(std::unique_ptr> in_patterns) = 0; + virtual DispatchResponse setBlackboxedRanges(const String& in_scriptId, std::unique_ptr> in_positions) = 0; + virtual DispatchResponse setBreakpoint(std::unique_ptr in_location, Maybe in_condition, String* out_breakpointId, std::unique_ptr* out_actualLocation) = 0; + virtual DispatchResponse setInstrumentationBreakpoint(const String& in_instrumentation, String* out_breakpointId) = 0; + virtual DispatchResponse setBreakpointByUrl(int in_lineNumber, Maybe in_url, Maybe in_urlRegex, Maybe in_scriptHash, Maybe in_columnNumber, Maybe in_condition, String* out_breakpointId, std::unique_ptr>* out_locations) = 0; + virtual DispatchResponse setBreakpointOnFunctionCall(const String& in_objectId, Maybe in_condition, String* out_breakpointId) = 0; + virtual DispatchResponse setBreakpointsActive(bool in_active) = 0; + virtual DispatchResponse setPauseOnExceptions(const String& in_state) = 0; + virtual DispatchResponse setReturnValue(std::unique_ptr in_newValue) = 0; + virtual DispatchResponse setScriptSource(const String& in_scriptId, const String& in_scriptSource, Maybe in_dryRun, Maybe in_allowTopFrameEditing, Maybe>* out_callFrames, Maybe* out_stackChanged, Maybe* out_asyncStackTrace, Maybe* out_asyncStackTraceId, String* out_status, Maybe* out_exceptionDetails) = 0; + virtual DispatchResponse setSkipAllPauses(bool in_skip) = 0; + virtual DispatchResponse setVariableValue(int in_scopeNumber, const String& in_variableName, std::unique_ptr in_newValue, const String& in_callFrameId) = 0; + virtual DispatchResponse stepInto(Maybe in_breakOnAsyncCall, Maybe> in_skipList) = 0; + virtual DispatchResponse stepOut() = 0; + virtual DispatchResponse stepOver(Maybe> in_skipList) = 0; + +}; + +// ------------- Frontend interface. + +class Frontend { +public: + explicit Frontend(FrontendChannel* frontend_channel) : frontend_channel_(frontend_channel) {} + void breakpointResolved(const String& breakpointId, std::unique_ptr location); + void paused(std::unique_ptr> callFrames, const String& reason, Maybe data = Maybe(), Maybe> hitBreakpoints = Maybe>(), Maybe asyncStackTrace = Maybe(), Maybe asyncStackTraceId = Maybe(), Maybe asyncCallStackTraceId = Maybe()); + void resumed(); + void scriptFailedToParse(const String& scriptId, const String& url, int startLine, int startColumn, int endLine, int endColumn, int executionContextId, const String& hash, Maybe executionContextAuxData = Maybe(), Maybe sourceMapURL = Maybe(), Maybe hasSourceURL = Maybe(), Maybe isModule = Maybe(), Maybe length = Maybe(), Maybe stackTrace = Maybe(), Maybe codeOffset = Maybe(), Maybe scriptLanguage = Maybe(), Maybe embedderName = Maybe()); + void scriptParsed(const String& scriptId, const String& url, int startLine, int startColumn, int endLine, int endColumn, int executionContextId, const String& hash, Maybe executionContextAuxData = Maybe(), Maybe isLiveEdit = Maybe(), Maybe sourceMapURL = Maybe(), Maybe hasSourceURL = Maybe(), Maybe isModule = Maybe(), Maybe length = Maybe(), Maybe stackTrace = Maybe(), Maybe codeOffset = Maybe(), Maybe scriptLanguage = Maybe(), Maybe debugSymbols = Maybe(), Maybe embedderName = Maybe()); + + void flush(); + void sendRawNotification(std::unique_ptr); + private: + FrontendChannel* frontend_channel_; +}; + +// ------------- Dispatcher. + +class Dispatcher { +public: + static void wire(UberDispatcher*, Backend*); + +private: + Dispatcher() { } +}; + +// ------------- Metainfo. + +class Metainfo { +public: + using BackendClass = Backend; + using FrontendClass = Frontend; + using DispatcherClass = Dispatcher; + static const char domainName[]; + static const char commandPrefix[]; + static const char version[]; +}; + +} // namespace Debugger +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Debugger_h) diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Forward.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Forward.h new file mode 100644 index 000000000..f9e5b6985 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Forward.h @@ -0,0 +1,74 @@ +// This file is generated by Forward_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Forward_h +#define v8_inspector_protocol_Forward_h + + +#include +#include + +#include "third_party/inspector_protocol/crdtp/error_support.h" +#include "third_party/inspector_protocol/crdtp/dispatch.h" +#include "third_party/inspector_protocol/crdtp/frontend_channel.h" +#include "third_party/inspector_protocol/crdtp/protocol_core.h" + +#include "src/inspector/string-util.h" + +namespace v8_inspector { +namespace protocol { + +using DispatchResponse = v8_crdtp::DispatchResponse; +using ErrorSupport = v8_crdtp::ErrorSupport; +using Serializable = v8_crdtp::Serializable; +using FrontendChannel = v8_crdtp::FrontendChannel; +using DomainDispatcher = v8_crdtp::DomainDispatcher; +using UberDispatcher = v8_crdtp::UberDispatcher; +using Response = DispatchResponse; + +class DictionaryValue; +class FundamentalValue; +class ListValue; +class Object; +class SerializedValue; +class StringValue; +class Value; + +using v8_crdtp::detail::PtrMaybe; +using v8_crdtp::detail::ValueMaybe; + +template +using Maybe = v8_crdtp::Maybe; + +namespace detail { + +template +struct ArrayTypedef { typedef std::vector> type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +template <> +struct ArrayTypedef { typedef std::vector type; }; + +} // namespace detail + +template +using Array = typename detail::ArrayTypedef::type; + +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Forward_h) diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/HeapProfiler.cpp b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/HeapProfiler.cpp new file mode 100644 index 000000000..c20c069c7 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/HeapProfiler.cpp @@ -0,0 +1,669 @@ +// This file is generated by TypeBuilder_cpp.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/inspector/protocol/HeapProfiler.h" + +#include "src/inspector/protocol/Protocol.h" + +#include "third_party/inspector_protocol/crdtp/cbor.h" +#include "third_party/inspector_protocol/crdtp/find_by_first.h" +#include "third_party/inspector_protocol/crdtp/span.h" + +namespace v8_inspector { +namespace protocol { +namespace HeapProfiler { + +using v8_crdtp::DeserializerState; +using v8_crdtp::ProtocolTypeTraits; + +// ------------- Enum values from types. + +const char Metainfo::domainName[] = "HeapProfiler"; +const char Metainfo::commandPrefix[] = "HeapProfiler."; +const char Metainfo::version[] = "1.3"; + + +V8_CRDTP_BEGIN_DESERIALIZER(SamplingHeapProfileNode) + V8_CRDTP_DESERIALIZE_FIELD("callFrame", m_callFrame), + V8_CRDTP_DESERIALIZE_FIELD("children", m_children), + V8_CRDTP_DESERIALIZE_FIELD("id", m_id), + V8_CRDTP_DESERIALIZE_FIELD("selfSize", m_selfSize), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(SamplingHeapProfileNode) + V8_CRDTP_SERIALIZE_FIELD("callFrame", m_callFrame); + V8_CRDTP_SERIALIZE_FIELD("selfSize", m_selfSize); + V8_CRDTP_SERIALIZE_FIELD("id", m_id); + V8_CRDTP_SERIALIZE_FIELD("children", m_children); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(SamplingHeapProfileSample) + V8_CRDTP_DESERIALIZE_FIELD("nodeId", m_nodeId), + V8_CRDTP_DESERIALIZE_FIELD("ordinal", m_ordinal), + V8_CRDTP_DESERIALIZE_FIELD("size", m_size), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(SamplingHeapProfileSample) + V8_CRDTP_SERIALIZE_FIELD("size", m_size); + V8_CRDTP_SERIALIZE_FIELD("nodeId", m_nodeId); + V8_CRDTP_SERIALIZE_FIELD("ordinal", m_ordinal); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(SamplingHeapProfile) + V8_CRDTP_DESERIALIZE_FIELD("head", m_head), + V8_CRDTP_DESERIALIZE_FIELD("samples", m_samples), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(SamplingHeapProfile) + V8_CRDTP_SERIALIZE_FIELD("head", m_head); + V8_CRDTP_SERIALIZE_FIELD("samples", m_samples); +V8_CRDTP_END_SERIALIZER(); + + +// ------------- Enum values from params. + + +// ------------- Frontend notifications. + +void Frontend::addHeapSnapshotChunk(const String& chunk) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("chunk"), chunk); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("HeapProfiler.addHeapSnapshotChunk", serializer.Finish())); +} + +void Frontend::heapStatsUpdate(std::unique_ptr> statsUpdate) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("statsUpdate"), statsUpdate); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("HeapProfiler.heapStatsUpdate", serializer.Finish())); +} + +void Frontend::lastSeenObjectId(int lastSeenObjectId, double timestamp) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("lastSeenObjectId"), lastSeenObjectId); + serializer.AddField(v8_crdtp::MakeSpan("timestamp"), timestamp); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("HeapProfiler.lastSeenObjectId", serializer.Finish())); +} + +void Frontend::reportHeapSnapshotProgress(int done, int total, Maybe finished) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("done"), done); + serializer.AddField(v8_crdtp::MakeSpan("total"), total); + serializer.AddField(v8_crdtp::MakeSpan("finished"), finished); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("HeapProfiler.reportHeapSnapshotProgress", serializer.Finish())); +} + +void Frontend::resetProfiles() +{ + if (!frontend_channel_) + return; + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("HeapProfiler.resetProfiles")); +} + +void Frontend::flush() +{ + frontend_channel_->FlushProtocolNotifications(); +} + +void Frontend::sendRawNotification(std::unique_ptr notification) +{ + frontend_channel_->SendProtocolNotification(std::move(notification)); +} + +// --------------------- Dispatcher. + +class DomainDispatcherImpl : public protocol::DomainDispatcher { +public: + DomainDispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) + : DomainDispatcher(frontendChannel) + , m_backend(backend) {} + ~DomainDispatcherImpl() override { } + + using CallHandler = void (DomainDispatcherImpl::*)(const v8_crdtp::Dispatchable& dispatchable); + + std::function Dispatch(v8_crdtp::span command_name) override; + + void addInspectedHeapObject(const v8_crdtp::Dispatchable& dispatchable); + void collectGarbage(const v8_crdtp::Dispatchable& dispatchable); + void disable(const v8_crdtp::Dispatchable& dispatchable); + void enable(const v8_crdtp::Dispatchable& dispatchable); + void getHeapObjectId(const v8_crdtp::Dispatchable& dispatchable); + void getObjectByHeapObjectId(const v8_crdtp::Dispatchable& dispatchable); + void getSamplingProfile(const v8_crdtp::Dispatchable& dispatchable); + void startSampling(const v8_crdtp::Dispatchable& dispatchable); + void startTrackingHeapObjects(const v8_crdtp::Dispatchable& dispatchable); + void stopSampling(const v8_crdtp::Dispatchable& dispatchable); + void stopTrackingHeapObjects(const v8_crdtp::Dispatchable& dispatchable); + void takeHeapSnapshot(const v8_crdtp::Dispatchable& dispatchable); + protected: + Backend* m_backend; +}; + +namespace { +// This helper method with a static map of command methods (instance methods +// of DomainDispatcherImpl declared just above) by their name is used immediately below, +// in the DomainDispatcherImpl::Dispatch method. +DomainDispatcherImpl::CallHandler CommandByName(v8_crdtp::span command_name) { + static auto* commands = [](){ + auto* commands = new std::vector, + DomainDispatcherImpl::CallHandler>>{ + { + v8_crdtp::SpanFrom("addInspectedHeapObject"), + &DomainDispatcherImpl::addInspectedHeapObject + }, + { + v8_crdtp::SpanFrom("collectGarbage"), + &DomainDispatcherImpl::collectGarbage + }, + { + v8_crdtp::SpanFrom("disable"), + &DomainDispatcherImpl::disable + }, + { + v8_crdtp::SpanFrom("enable"), + &DomainDispatcherImpl::enable + }, + { + v8_crdtp::SpanFrom("getHeapObjectId"), + &DomainDispatcherImpl::getHeapObjectId + }, + { + v8_crdtp::SpanFrom("getObjectByHeapObjectId"), + &DomainDispatcherImpl::getObjectByHeapObjectId + }, + { + v8_crdtp::SpanFrom("getSamplingProfile"), + &DomainDispatcherImpl::getSamplingProfile + }, + { + v8_crdtp::SpanFrom("startSampling"), + &DomainDispatcherImpl::startSampling + }, + { + v8_crdtp::SpanFrom("startTrackingHeapObjects"), + &DomainDispatcherImpl::startTrackingHeapObjects + }, + { + v8_crdtp::SpanFrom("stopSampling"), + &DomainDispatcherImpl::stopSampling + }, + { + v8_crdtp::SpanFrom("stopTrackingHeapObjects"), + &DomainDispatcherImpl::stopTrackingHeapObjects + }, + { + v8_crdtp::SpanFrom("takeHeapSnapshot"), + &DomainDispatcherImpl::takeHeapSnapshot + }, + }; + return commands; + }(); + return v8_crdtp::FindByFirst(*commands, command_name, nullptr); +} +} // namespace + +std::function DomainDispatcherImpl::Dispatch(v8_crdtp::span command_name) { + CallHandler handler = CommandByName(command_name); + if (!handler) return nullptr; + + return [this, handler](const v8_crdtp::Dispatchable& dispatchable) { + (this->*handler)(dispatchable); + }; +} + + +namespace { + +struct addInspectedHeapObjectParams : public v8_crdtp::DeserializableProtocolObject { + String heapObjectId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(addInspectedHeapObjectParams) + V8_CRDTP_DESERIALIZE_FIELD("heapObjectId", heapObjectId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::addInspectedHeapObject(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + addInspectedHeapObjectParams params; + if (!addInspectedHeapObjectParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->addInspectedHeapObject(params.heapObjectId); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.addInspectedHeapObject"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +class CollectGarbageCallbackImpl : public Backend::CollectGarbageCallback, public DomainDispatcher::Callback { +public: + CollectGarbageCallbackImpl(std::unique_ptr backendImpl, int callId, v8_crdtp::span message) + : DomainDispatcher::Callback(std::move(backendImpl), callId, +v8_crdtp::SpanFrom("HeapProfiler.collectGarbage"), message) { } + + void sendSuccess() override + { + v8_crdtp::ObjectSerializer serializer; + sendIfActive(serializer.Finish(), DispatchResponse::Success()); + } + + void fallThrough() override + { + fallThroughIfActive(); + } + + void sendFailure(const DispatchResponse& response) override + { + DCHECK(response.IsError()); + sendIfActive(nullptr, response); + } +}; + +namespace { + + +} // namespace + +void DomainDispatcherImpl::collectGarbage(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + m_backend->collectGarbage(std::make_unique(weakPtr(), dispatchable.CallId(), dispatchable.Serialized())); +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::disable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.disable"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::enable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->enable(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.enable"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct getHeapObjectIdParams : public v8_crdtp::DeserializableProtocolObject { + String objectId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(getHeapObjectIdParams) + V8_CRDTP_DESERIALIZE_FIELD("objectId", objectId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::getHeapObjectId(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + getHeapObjectIdParams params; + if (!getHeapObjectIdParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + String out_heapSnapshotObjectId; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getHeapObjectId(params.objectId, &out_heapSnapshotObjectId); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.getHeapObjectId"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("heapSnapshotObjectId"), out_heapSnapshotObjectId); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct getObjectByHeapObjectIdParams : public v8_crdtp::DeserializableProtocolObject { + String objectId; + Maybe objectGroup; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(getObjectByHeapObjectIdParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectGroup", objectGroup), + V8_CRDTP_DESERIALIZE_FIELD("objectId", objectId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::getObjectByHeapObjectId(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + getObjectByHeapObjectIdParams params; + if (!getObjectByHeapObjectIdParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr out_result; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getObjectByHeapObjectId(params.objectId, std::move(params.objectGroup), &out_result); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.getObjectByHeapObjectId"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), out_result); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::getSamplingProfile(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + // Declare output parameters. + std::unique_ptr out_profile; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getSamplingProfile(&out_profile); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.getSamplingProfile"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("profile"), out_profile); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct startSamplingParams : public v8_crdtp::DeserializableProtocolObject { + Maybe samplingInterval; + Maybe includeObjectsCollectedByMajorGC; + Maybe includeObjectsCollectedByMinorGC; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(startSamplingParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("includeObjectsCollectedByMajorGC", includeObjectsCollectedByMajorGC), + V8_CRDTP_DESERIALIZE_FIELD_OPT("includeObjectsCollectedByMinorGC", includeObjectsCollectedByMinorGC), + V8_CRDTP_DESERIALIZE_FIELD_OPT("samplingInterval", samplingInterval), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::startSampling(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + startSamplingParams params; + if (!startSamplingParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->startSampling(std::move(params.samplingInterval), std::move(params.includeObjectsCollectedByMajorGC), std::move(params.includeObjectsCollectedByMinorGC)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.startSampling"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct startTrackingHeapObjectsParams : public v8_crdtp::DeserializableProtocolObject { + Maybe trackAllocations; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(startTrackingHeapObjectsParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("trackAllocations", trackAllocations), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::startTrackingHeapObjects(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + startTrackingHeapObjectsParams params; + if (!startTrackingHeapObjectsParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->startTrackingHeapObjects(std::move(params.trackAllocations)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.startTrackingHeapObjects"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::stopSampling(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + // Declare output parameters. + std::unique_ptr out_profile; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stopSampling(&out_profile); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.stopSampling"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("profile"), out_profile); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct stopTrackingHeapObjectsParams : public v8_crdtp::DeserializableProtocolObject { + Maybe reportProgress; + Maybe treatGlobalObjectsAsRoots; + Maybe captureNumericValue; + Maybe exposeInternals; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(stopTrackingHeapObjectsParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("captureNumericValue", captureNumericValue), + V8_CRDTP_DESERIALIZE_FIELD_OPT("exposeInternals", exposeInternals), + V8_CRDTP_DESERIALIZE_FIELD_OPT("reportProgress", reportProgress), + V8_CRDTP_DESERIALIZE_FIELD_OPT("treatGlobalObjectsAsRoots", treatGlobalObjectsAsRoots), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::stopTrackingHeapObjects(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + stopTrackingHeapObjectsParams params; + if (!stopTrackingHeapObjectsParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stopTrackingHeapObjects(std::move(params.reportProgress), std::move(params.treatGlobalObjectsAsRoots), std::move(params.captureNumericValue), std::move(params.exposeInternals)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.stopTrackingHeapObjects"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct takeHeapSnapshotParams : public v8_crdtp::DeserializableProtocolObject { + Maybe reportProgress; + Maybe treatGlobalObjectsAsRoots; + Maybe captureNumericValue; + Maybe exposeInternals; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(takeHeapSnapshotParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("captureNumericValue", captureNumericValue), + V8_CRDTP_DESERIALIZE_FIELD_OPT("exposeInternals", exposeInternals), + V8_CRDTP_DESERIALIZE_FIELD_OPT("reportProgress", reportProgress), + V8_CRDTP_DESERIALIZE_FIELD_OPT("treatGlobalObjectsAsRoots", treatGlobalObjectsAsRoots), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::takeHeapSnapshot(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + takeHeapSnapshotParams params; + if (!takeHeapSnapshotParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->takeHeapSnapshot(std::move(params.reportProgress), std::move(params.treatGlobalObjectsAsRoots), std::move(params.captureNumericValue), std::move(params.exposeInternals)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("HeapProfiler.takeHeapSnapshot"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { +// This helper method (with a static map of redirects) is used from Dispatcher::wire +// immediately below. +const std::vector, v8_crdtp::span>>& SortedRedirects() { + static auto* redirects = [](){ + auto* redirects = new std::vector, v8_crdtp::span>>{ + }; + return redirects; + }(); + return *redirects; +} +} // namespace + +// static +void Dispatcher::wire(UberDispatcher* uber, Backend* backend) +{ + auto dispatcher = std::make_unique(uber->channel(), backend); + uber->WireBackend(v8_crdtp::SpanFrom("HeapProfiler"), SortedRedirects(), std::move(dispatcher)); +} + +} // HeapProfiler +} // namespace v8_inspector +} // namespace protocol diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/HeapProfiler.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/HeapProfiler.h new file mode 100644 index 000000000..9b0030c8e --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/HeapProfiler.h @@ -0,0 +1,346 @@ +// This file is generated by TypeBuilder_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_HeapProfiler_h +#define v8_inspector_protocol_HeapProfiler_h + +#include "src/inspector/protocol/Protocol.h" +// For each imported domain we generate a ValueConversions struct instead of a full domain definition +// and include Domain::API version from there. +#include "src/inspector/protocol/Runtime.h" + +namespace v8_inspector { +namespace protocol { +namespace HeapProfiler { +using HeapSnapshotObjectId = String; +class SamplingHeapProfileNode; +class SamplingHeapProfileSample; +class SamplingHeapProfile; + +// ------------- Forward and enum declarations. + +// ------------- Type and builder declarations. + +class SamplingHeapProfileNode : public ::v8_crdtp::ProtocolObject { +public: + ~SamplingHeapProfileNode() override { } + + protocol::Runtime::CallFrame* getCallFrame() { return m_callFrame.get(); } + void setCallFrame(std::unique_ptr value) { m_callFrame = std::move(value); } + + double getSelfSize() { return m_selfSize; } + void setSelfSize(double value) { m_selfSize = value; } + + int getId() { return m_id; } + void setId(int value) { m_id = value; } + + protocol::Array* getChildren() { return m_children.get(); } + void setChildren(std::unique_ptr> value) { m_children = std::move(value); } + + template + class SamplingHeapProfileNodeBuilder { + public: + enum { + NoFieldsSet = 0, + CallFrameSet = 1 << 1, + SelfSizeSet = 1 << 2, + IdSet = 1 << 3, + ChildrenSet = 1 << 4, + AllFieldsSet = (CallFrameSet | SelfSizeSet | IdSet | ChildrenSet | 0)}; + + + SamplingHeapProfileNodeBuilder& setCallFrame(std::unique_ptr value) + { + static_assert(!(STATE & CallFrameSet), "property callFrame should not be set yet"); + m_result->setCallFrame(std::move(value)); + return castState(); + } + + SamplingHeapProfileNodeBuilder& setSelfSize(double value) + { + static_assert(!(STATE & SelfSizeSet), "property selfSize should not be set yet"); + m_result->setSelfSize(value); + return castState(); + } + + SamplingHeapProfileNodeBuilder& setId(int value) + { + static_assert(!(STATE & IdSet), "property id should not be set yet"); + m_result->setId(value); + return castState(); + } + + SamplingHeapProfileNodeBuilder& setChildren(std::unique_ptr> value) + { + static_assert(!(STATE & ChildrenSet), "property children should not be set yet"); + m_result->setChildren(std::move(value)); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class SamplingHeapProfileNode; + SamplingHeapProfileNodeBuilder() : m_result(new SamplingHeapProfileNode()) { } + + template SamplingHeapProfileNodeBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static SamplingHeapProfileNodeBuilder<0> create() + { + return SamplingHeapProfileNodeBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + SamplingHeapProfileNode() + { + m_selfSize = 0; + m_id = 0; + } + + std::unique_ptr m_callFrame; + double m_selfSize; + int m_id; + std::unique_ptr> m_children; +}; + + +class SamplingHeapProfileSample : public ::v8_crdtp::ProtocolObject { +public: + ~SamplingHeapProfileSample() override { } + + double getSize() { return m_size; } + void setSize(double value) { m_size = value; } + + int getNodeId() { return m_nodeId; } + void setNodeId(int value) { m_nodeId = value; } + + double getOrdinal() { return m_ordinal; } + void setOrdinal(double value) { m_ordinal = value; } + + template + class SamplingHeapProfileSampleBuilder { + public: + enum { + NoFieldsSet = 0, + SizeSet = 1 << 1, + NodeIdSet = 1 << 2, + OrdinalSet = 1 << 3, + AllFieldsSet = (SizeSet | NodeIdSet | OrdinalSet | 0)}; + + + SamplingHeapProfileSampleBuilder& setSize(double value) + { + static_assert(!(STATE & SizeSet), "property size should not be set yet"); + m_result->setSize(value); + return castState(); + } + + SamplingHeapProfileSampleBuilder& setNodeId(int value) + { + static_assert(!(STATE & NodeIdSet), "property nodeId should not be set yet"); + m_result->setNodeId(value); + return castState(); + } + + SamplingHeapProfileSampleBuilder& setOrdinal(double value) + { + static_assert(!(STATE & OrdinalSet), "property ordinal should not be set yet"); + m_result->setOrdinal(value); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class SamplingHeapProfileSample; + SamplingHeapProfileSampleBuilder() : m_result(new SamplingHeapProfileSample()) { } + + template SamplingHeapProfileSampleBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static SamplingHeapProfileSampleBuilder<0> create() + { + return SamplingHeapProfileSampleBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + SamplingHeapProfileSample() + { + m_size = 0; + m_nodeId = 0; + m_ordinal = 0; + } + + double m_size; + int m_nodeId; + double m_ordinal; +}; + + +class SamplingHeapProfile : public ::v8_crdtp::ProtocolObject { +public: + ~SamplingHeapProfile() override { } + + protocol::HeapProfiler::SamplingHeapProfileNode* getHead() { return m_head.get(); } + void setHead(std::unique_ptr value) { m_head = std::move(value); } + + protocol::Array* getSamples() { return m_samples.get(); } + void setSamples(std::unique_ptr> value) { m_samples = std::move(value); } + + template + class SamplingHeapProfileBuilder { + public: + enum { + NoFieldsSet = 0, + HeadSet = 1 << 1, + SamplesSet = 1 << 2, + AllFieldsSet = (HeadSet | SamplesSet | 0)}; + + + SamplingHeapProfileBuilder& setHead(std::unique_ptr value) + { + static_assert(!(STATE & HeadSet), "property head should not be set yet"); + m_result->setHead(std::move(value)); + return castState(); + } + + SamplingHeapProfileBuilder& setSamples(std::unique_ptr> value) + { + static_assert(!(STATE & SamplesSet), "property samples should not be set yet"); + m_result->setSamples(std::move(value)); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class SamplingHeapProfile; + SamplingHeapProfileBuilder() : m_result(new SamplingHeapProfile()) { } + + template SamplingHeapProfileBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static SamplingHeapProfileBuilder<0> create() + { + return SamplingHeapProfileBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + SamplingHeapProfile() + { + } + + std::unique_ptr m_head; + std::unique_ptr> m_samples; +}; + + +// ------------- Backend interface. + +class Backend { +public: + virtual ~Backend() { } + + virtual DispatchResponse addInspectedHeapObject(const String& in_heapObjectId) = 0; + class CollectGarbageCallback { + public: + virtual void sendSuccess() = 0; + virtual void sendFailure(const DispatchResponse&) = 0; + virtual void fallThrough() = 0; + virtual ~CollectGarbageCallback() { } + }; + virtual void collectGarbage(std::unique_ptr callback) = 0; + virtual DispatchResponse disable() = 0; + virtual DispatchResponse enable() = 0; + virtual DispatchResponse getHeapObjectId(const String& in_objectId, String* out_heapSnapshotObjectId) = 0; + virtual DispatchResponse getObjectByHeapObjectId(const String& in_objectId, Maybe in_objectGroup, std::unique_ptr* out_result) = 0; + virtual DispatchResponse getSamplingProfile(std::unique_ptr* out_profile) = 0; + virtual DispatchResponse startSampling(Maybe in_samplingInterval, Maybe in_includeObjectsCollectedByMajorGC, Maybe in_includeObjectsCollectedByMinorGC) = 0; + virtual DispatchResponse startTrackingHeapObjects(Maybe in_trackAllocations) = 0; + virtual DispatchResponse stopSampling(std::unique_ptr* out_profile) = 0; + virtual DispatchResponse stopTrackingHeapObjects(Maybe in_reportProgress, Maybe in_treatGlobalObjectsAsRoots, Maybe in_captureNumericValue, Maybe in_exposeInternals) = 0; + virtual DispatchResponse takeHeapSnapshot(Maybe in_reportProgress, Maybe in_treatGlobalObjectsAsRoots, Maybe in_captureNumericValue, Maybe in_exposeInternals) = 0; + +}; + +// ------------- Frontend interface. + +class Frontend { +public: + explicit Frontend(FrontendChannel* frontend_channel) : frontend_channel_(frontend_channel) {} + void addHeapSnapshotChunk(const String& chunk); + void heapStatsUpdate(std::unique_ptr> statsUpdate); + void lastSeenObjectId(int lastSeenObjectId, double timestamp); + void reportHeapSnapshotProgress(int done, int total, Maybe finished = Maybe()); + void resetProfiles(); + + void flush(); + void sendRawNotification(std::unique_ptr); + private: + FrontendChannel* frontend_channel_; +}; + +// ------------- Dispatcher. + +class Dispatcher { +public: + static void wire(UberDispatcher*, Backend*); + +private: + Dispatcher() { } +}; + +// ------------- Metainfo. + +class Metainfo { +public: + using BackendClass = Backend; + using FrontendClass = Frontend; + using DispatcherClass = Dispatcher; + static const char domainName[]; + static const char commandPrefix[]; + static const char version[]; +}; + +} // namespace HeapProfiler +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_HeapProfiler_h) diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Profiler.cpp b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Profiler.cpp new file mode 100644 index 000000000..61c59cdd9 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Profiler.cpp @@ -0,0 +1,530 @@ +// This file is generated by TypeBuilder_cpp.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/inspector/protocol/Profiler.h" + +#include "src/inspector/protocol/Protocol.h" + +#include "third_party/inspector_protocol/crdtp/cbor.h" +#include "third_party/inspector_protocol/crdtp/find_by_first.h" +#include "third_party/inspector_protocol/crdtp/span.h" + +namespace v8_inspector { +namespace protocol { +namespace Profiler { + +using v8_crdtp::DeserializerState; +using v8_crdtp::ProtocolTypeTraits; + +// ------------- Enum values from types. + +const char Metainfo::domainName[] = "Profiler"; +const char Metainfo::commandPrefix[] = "Profiler."; +const char Metainfo::version[] = "1.3"; + +V8_CRDTP_BEGIN_DESERIALIZER(ProfileNode) + V8_CRDTP_DESERIALIZE_FIELD("callFrame", m_callFrame), + V8_CRDTP_DESERIALIZE_FIELD_OPT("children", m_children), + V8_CRDTP_DESERIALIZE_FIELD_OPT("deoptReason", m_deoptReason), + V8_CRDTP_DESERIALIZE_FIELD_OPT("hitCount", m_hitCount), + V8_CRDTP_DESERIALIZE_FIELD("id", m_id), + V8_CRDTP_DESERIALIZE_FIELD_OPT("positionTicks", m_positionTicks), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(ProfileNode) + V8_CRDTP_SERIALIZE_FIELD("id", m_id); + V8_CRDTP_SERIALIZE_FIELD("callFrame", m_callFrame); + V8_CRDTP_SERIALIZE_FIELD("hitCount", m_hitCount); + V8_CRDTP_SERIALIZE_FIELD("children", m_children); + V8_CRDTP_SERIALIZE_FIELD("deoptReason", m_deoptReason); + V8_CRDTP_SERIALIZE_FIELD("positionTicks", m_positionTicks); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(Profile) + V8_CRDTP_DESERIALIZE_FIELD("endTime", m_endTime), + V8_CRDTP_DESERIALIZE_FIELD("nodes", m_nodes), + V8_CRDTP_DESERIALIZE_FIELD_OPT("samples", m_samples), + V8_CRDTP_DESERIALIZE_FIELD("startTime", m_startTime), + V8_CRDTP_DESERIALIZE_FIELD_OPT("timeDeltas", m_timeDeltas), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(Profile) + V8_CRDTP_SERIALIZE_FIELD("nodes", m_nodes); + V8_CRDTP_SERIALIZE_FIELD("startTime", m_startTime); + V8_CRDTP_SERIALIZE_FIELD("endTime", m_endTime); + V8_CRDTP_SERIALIZE_FIELD("samples", m_samples); + V8_CRDTP_SERIALIZE_FIELD("timeDeltas", m_timeDeltas); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(PositionTickInfo) + V8_CRDTP_DESERIALIZE_FIELD("line", m_line), + V8_CRDTP_DESERIALIZE_FIELD("ticks", m_ticks), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(PositionTickInfo) + V8_CRDTP_SERIALIZE_FIELD("line", m_line); + V8_CRDTP_SERIALIZE_FIELD("ticks", m_ticks); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(CoverageRange) + V8_CRDTP_DESERIALIZE_FIELD("count", m_count), + V8_CRDTP_DESERIALIZE_FIELD("endOffset", m_endOffset), + V8_CRDTP_DESERIALIZE_FIELD("startOffset", m_startOffset), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(CoverageRange) + V8_CRDTP_SERIALIZE_FIELD("startOffset", m_startOffset); + V8_CRDTP_SERIALIZE_FIELD("endOffset", m_endOffset); + V8_CRDTP_SERIALIZE_FIELD("count", m_count); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(FunctionCoverage) + V8_CRDTP_DESERIALIZE_FIELD("functionName", m_functionName), + V8_CRDTP_DESERIALIZE_FIELD("isBlockCoverage", m_isBlockCoverage), + V8_CRDTP_DESERIALIZE_FIELD("ranges", m_ranges), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(FunctionCoverage) + V8_CRDTP_SERIALIZE_FIELD("functionName", m_functionName); + V8_CRDTP_SERIALIZE_FIELD("ranges", m_ranges); + V8_CRDTP_SERIALIZE_FIELD("isBlockCoverage", m_isBlockCoverage); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(ScriptCoverage) + V8_CRDTP_DESERIALIZE_FIELD("functions", m_functions), + V8_CRDTP_DESERIALIZE_FIELD("scriptId", m_scriptId), + V8_CRDTP_DESERIALIZE_FIELD("url", m_url), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(ScriptCoverage) + V8_CRDTP_SERIALIZE_FIELD("scriptId", m_scriptId); + V8_CRDTP_SERIALIZE_FIELD("url", m_url); + V8_CRDTP_SERIALIZE_FIELD("functions", m_functions); +V8_CRDTP_END_SERIALIZER(); + + +// ------------- Enum values from params. + + +// ------------- Frontend notifications. + +void Frontend::consoleProfileFinished(const String& id, std::unique_ptr location, std::unique_ptr profile, Maybe title) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("id"), id); + serializer.AddField(v8_crdtp::MakeSpan("location"), location); + serializer.AddField(v8_crdtp::MakeSpan("profile"), profile); + serializer.AddField(v8_crdtp::MakeSpan("title"), title); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Profiler.consoleProfileFinished", serializer.Finish())); +} + +void Frontend::consoleProfileStarted(const String& id, std::unique_ptr location, Maybe title) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("id"), id); + serializer.AddField(v8_crdtp::MakeSpan("location"), location); + serializer.AddField(v8_crdtp::MakeSpan("title"), title); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Profiler.consoleProfileStarted", serializer.Finish())); +} + +void Frontend::preciseCoverageDeltaUpdate(double timestamp, const String& occasion, std::unique_ptr> result) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("timestamp"), timestamp); + serializer.AddField(v8_crdtp::MakeSpan("occasion"), occasion); + serializer.AddField(v8_crdtp::MakeSpan("result"), result); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Profiler.preciseCoverageDeltaUpdate", serializer.Finish())); +} + +void Frontend::flush() +{ + frontend_channel_->FlushProtocolNotifications(); +} + +void Frontend::sendRawNotification(std::unique_ptr notification) +{ + frontend_channel_->SendProtocolNotification(std::move(notification)); +} + +// --------------------- Dispatcher. + +class DomainDispatcherImpl : public protocol::DomainDispatcher { +public: + DomainDispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) + : DomainDispatcher(frontendChannel) + , m_backend(backend) {} + ~DomainDispatcherImpl() override { } + + using CallHandler = void (DomainDispatcherImpl::*)(const v8_crdtp::Dispatchable& dispatchable); + + std::function Dispatch(v8_crdtp::span command_name) override; + + void disable(const v8_crdtp::Dispatchable& dispatchable); + void enable(const v8_crdtp::Dispatchable& dispatchable); + void getBestEffortCoverage(const v8_crdtp::Dispatchable& dispatchable); + void setSamplingInterval(const v8_crdtp::Dispatchable& dispatchable); + void start(const v8_crdtp::Dispatchable& dispatchable); + void startPreciseCoverage(const v8_crdtp::Dispatchable& dispatchable); + void stop(const v8_crdtp::Dispatchable& dispatchable); + void stopPreciseCoverage(const v8_crdtp::Dispatchable& dispatchable); + void takePreciseCoverage(const v8_crdtp::Dispatchable& dispatchable); + protected: + Backend* m_backend; +}; + +namespace { +// This helper method with a static map of command methods (instance methods +// of DomainDispatcherImpl declared just above) by their name is used immediately below, +// in the DomainDispatcherImpl::Dispatch method. +DomainDispatcherImpl::CallHandler CommandByName(v8_crdtp::span command_name) { + static auto* commands = [](){ + auto* commands = new std::vector, + DomainDispatcherImpl::CallHandler>>{ + { + v8_crdtp::SpanFrom("disable"), + &DomainDispatcherImpl::disable + }, + { + v8_crdtp::SpanFrom("enable"), + &DomainDispatcherImpl::enable + }, + { + v8_crdtp::SpanFrom("getBestEffortCoverage"), + &DomainDispatcherImpl::getBestEffortCoverage + }, + { + v8_crdtp::SpanFrom("setSamplingInterval"), + &DomainDispatcherImpl::setSamplingInterval + }, + { + v8_crdtp::SpanFrom("start"), + &DomainDispatcherImpl::start + }, + { + v8_crdtp::SpanFrom("startPreciseCoverage"), + &DomainDispatcherImpl::startPreciseCoverage + }, + { + v8_crdtp::SpanFrom("stop"), + &DomainDispatcherImpl::stop + }, + { + v8_crdtp::SpanFrom("stopPreciseCoverage"), + &DomainDispatcherImpl::stopPreciseCoverage + }, + { + v8_crdtp::SpanFrom("takePreciseCoverage"), + &DomainDispatcherImpl::takePreciseCoverage + }, + }; + return commands; + }(); + return v8_crdtp::FindByFirst(*commands, command_name, nullptr); +} +} // namespace + +std::function DomainDispatcherImpl::Dispatch(v8_crdtp::span command_name) { + CallHandler handler = CommandByName(command_name); + if (!handler) return nullptr; + + return [this, handler](const v8_crdtp::Dispatchable& dispatchable) { + (this->*handler)(dispatchable); + }; +} + + +namespace { + + +} // namespace + +void DomainDispatcherImpl::disable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Profiler.disable"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::enable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->enable(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Profiler.enable"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::getBestEffortCoverage(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + // Declare output parameters. + std::unique_ptr> out_result; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getBestEffortCoverage(&out_result); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Profiler.getBestEffortCoverage"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), out_result); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct setSamplingIntervalParams : public v8_crdtp::DeserializableProtocolObject { + int interval; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setSamplingIntervalParams) + V8_CRDTP_DESERIALIZE_FIELD("interval", interval), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setSamplingInterval(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setSamplingIntervalParams params; + if (!setSamplingIntervalParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setSamplingInterval(params.interval); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Profiler.setSamplingInterval"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::start(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->start(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Profiler.start"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct startPreciseCoverageParams : public v8_crdtp::DeserializableProtocolObject { + Maybe callCount; + Maybe detailed; + Maybe allowTriggeredUpdates; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(startPreciseCoverageParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("allowTriggeredUpdates", allowTriggeredUpdates), + V8_CRDTP_DESERIALIZE_FIELD_OPT("callCount", callCount), + V8_CRDTP_DESERIALIZE_FIELD_OPT("detailed", detailed), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::startPreciseCoverage(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + startPreciseCoverageParams params; + if (!startPreciseCoverageParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + double out_timestamp; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->startPreciseCoverage(std::move(params.callCount), std::move(params.detailed), std::move(params.allowTriggeredUpdates), &out_timestamp); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Profiler.startPreciseCoverage"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("timestamp"), out_timestamp); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::stop(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + // Declare output parameters. + std::unique_ptr out_profile; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stop(&out_profile); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Profiler.stop"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("profile"), out_profile); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::stopPreciseCoverage(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->stopPreciseCoverage(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Profiler.stopPreciseCoverage"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::takePreciseCoverage(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + // Declare output parameters. + std::unique_ptr> out_result; + double out_timestamp; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->takePreciseCoverage(&out_result, &out_timestamp); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Profiler.takePreciseCoverage"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), out_result); + serializer.AddField(v8_crdtp::MakeSpan("timestamp"), out_timestamp); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { +// This helper method (with a static map of redirects) is used from Dispatcher::wire +// immediately below. +const std::vector, v8_crdtp::span>>& SortedRedirects() { + static auto* redirects = [](){ + auto* redirects = new std::vector, v8_crdtp::span>>{ + }; + return redirects; + }(); + return *redirects; +} +} // namespace + +// static +void Dispatcher::wire(UberDispatcher* uber, Backend* backend) +{ + auto dispatcher = std::make_unique(uber->channel(), backend); + uber->WireBackend(v8_crdtp::SpanFrom("Profiler"), SortedRedirects(), std::move(dispatcher)); +} + +} // Profiler +} // namespace v8_inspector +} // namespace protocol diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Profiler.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Profiler.h new file mode 100644 index 000000000..a1eef3025 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Profiler.h @@ -0,0 +1,626 @@ +// This file is generated by TypeBuilder_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Profiler_h +#define v8_inspector_protocol_Profiler_h + +#include "src/inspector/protocol/Protocol.h" +// For each imported domain we generate a ValueConversions struct instead of a full domain definition +// and include Domain::API version from there. +#include "src/inspector/protocol/Runtime.h" +#include "src/inspector/protocol/Debugger.h" + +namespace v8_inspector { +namespace protocol { +namespace Profiler { +class ProfileNode; +class Profile; +class PositionTickInfo; +class CoverageRange; +class FunctionCoverage; +class ScriptCoverage; + +// ------------- Forward and enum declarations. + +// ------------- Type and builder declarations. + +class ProfileNode : public ::v8_crdtp::ProtocolObject { +public: + ~ProfileNode() override { } + + int getId() { return m_id; } + void setId(int value) { m_id = value; } + + protocol::Runtime::CallFrame* getCallFrame() { return m_callFrame.get(); } + void setCallFrame(std::unique_ptr value) { m_callFrame = std::move(value); } + + bool hasHitCount() { return m_hitCount.isJust(); } + int getHitCount(int defaultValue) { return m_hitCount.isJust() ? m_hitCount.fromJust() : defaultValue; } + void setHitCount(int value) { m_hitCount = value; } + + bool hasChildren() { return m_children.isJust(); } + protocol::Array* getChildren(protocol::Array* defaultValue) { return m_children.isJust() ? m_children.fromJust() : defaultValue; } + void setChildren(std::unique_ptr> value) { m_children = std::move(value); } + + bool hasDeoptReason() { return m_deoptReason.isJust(); } + String getDeoptReason(const String& defaultValue) { return m_deoptReason.isJust() ? m_deoptReason.fromJust() : defaultValue; } + void setDeoptReason(const String& value) { m_deoptReason = value; } + + bool hasPositionTicks() { return m_positionTicks.isJust(); } + protocol::Array* getPositionTicks(protocol::Array* defaultValue) { return m_positionTicks.isJust() ? m_positionTicks.fromJust() : defaultValue; } + void setPositionTicks(std::unique_ptr> value) { m_positionTicks = std::move(value); } + + template + class ProfileNodeBuilder { + public: + enum { + NoFieldsSet = 0, + IdSet = 1 << 1, + CallFrameSet = 1 << 2, + AllFieldsSet = (IdSet | CallFrameSet | 0)}; + + + ProfileNodeBuilder& setId(int value) + { + static_assert(!(STATE & IdSet), "property id should not be set yet"); + m_result->setId(value); + return castState(); + } + + ProfileNodeBuilder& setCallFrame(std::unique_ptr value) + { + static_assert(!(STATE & CallFrameSet), "property callFrame should not be set yet"); + m_result->setCallFrame(std::move(value)); + return castState(); + } + + ProfileNodeBuilder& setHitCount(int value) + { + m_result->setHitCount(value); + return *this; + } + + ProfileNodeBuilder& setChildren(std::unique_ptr> value) + { + m_result->setChildren(std::move(value)); + return *this; + } + + ProfileNodeBuilder& setDeoptReason(const String& value) + { + m_result->setDeoptReason(value); + return *this; + } + + ProfileNodeBuilder& setPositionTicks(std::unique_ptr> value) + { + m_result->setPositionTicks(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ProfileNode; + ProfileNodeBuilder() : m_result(new ProfileNode()) { } + + template ProfileNodeBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static ProfileNodeBuilder<0> create() + { + return ProfileNodeBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + ProfileNode() + { + m_id = 0; + } + + int m_id; + std::unique_ptr m_callFrame; + Maybe m_hitCount; + Maybe> m_children; + Maybe m_deoptReason; + Maybe> m_positionTicks; +}; + + +class Profile : public ::v8_crdtp::ProtocolObject { +public: + ~Profile() override { } + + protocol::Array* getNodes() { return m_nodes.get(); } + void setNodes(std::unique_ptr> value) { m_nodes = std::move(value); } + + double getStartTime() { return m_startTime; } + void setStartTime(double value) { m_startTime = value; } + + double getEndTime() { return m_endTime; } + void setEndTime(double value) { m_endTime = value; } + + bool hasSamples() { return m_samples.isJust(); } + protocol::Array* getSamples(protocol::Array* defaultValue) { return m_samples.isJust() ? m_samples.fromJust() : defaultValue; } + void setSamples(std::unique_ptr> value) { m_samples = std::move(value); } + + bool hasTimeDeltas() { return m_timeDeltas.isJust(); } + protocol::Array* getTimeDeltas(protocol::Array* defaultValue) { return m_timeDeltas.isJust() ? m_timeDeltas.fromJust() : defaultValue; } + void setTimeDeltas(std::unique_ptr> value) { m_timeDeltas = std::move(value); } + + template + class ProfileBuilder { + public: + enum { + NoFieldsSet = 0, + NodesSet = 1 << 1, + StartTimeSet = 1 << 2, + EndTimeSet = 1 << 3, + AllFieldsSet = (NodesSet | StartTimeSet | EndTimeSet | 0)}; + + + ProfileBuilder& setNodes(std::unique_ptr> value) + { + static_assert(!(STATE & NodesSet), "property nodes should not be set yet"); + m_result->setNodes(std::move(value)); + return castState(); + } + + ProfileBuilder& setStartTime(double value) + { + static_assert(!(STATE & StartTimeSet), "property startTime should not be set yet"); + m_result->setStartTime(value); + return castState(); + } + + ProfileBuilder& setEndTime(double value) + { + static_assert(!(STATE & EndTimeSet), "property endTime should not be set yet"); + m_result->setEndTime(value); + return castState(); + } + + ProfileBuilder& setSamples(std::unique_ptr> value) + { + m_result->setSamples(std::move(value)); + return *this; + } + + ProfileBuilder& setTimeDeltas(std::unique_ptr> value) + { + m_result->setTimeDeltas(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class Profile; + ProfileBuilder() : m_result(new Profile()) { } + + template ProfileBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static ProfileBuilder<0> create() + { + return ProfileBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + Profile() + { + m_startTime = 0; + m_endTime = 0; + } + + std::unique_ptr> m_nodes; + double m_startTime; + double m_endTime; + Maybe> m_samples; + Maybe> m_timeDeltas; +}; + + +class PositionTickInfo : public ::v8_crdtp::ProtocolObject { +public: + ~PositionTickInfo() override { } + + int getLine() { return m_line; } + void setLine(int value) { m_line = value; } + + int getTicks() { return m_ticks; } + void setTicks(int value) { m_ticks = value; } + + template + class PositionTickInfoBuilder { + public: + enum { + NoFieldsSet = 0, + LineSet = 1 << 1, + TicksSet = 1 << 2, + AllFieldsSet = (LineSet | TicksSet | 0)}; + + + PositionTickInfoBuilder& setLine(int value) + { + static_assert(!(STATE & LineSet), "property line should not be set yet"); + m_result->setLine(value); + return castState(); + } + + PositionTickInfoBuilder& setTicks(int value) + { + static_assert(!(STATE & TicksSet), "property ticks should not be set yet"); + m_result->setTicks(value); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class PositionTickInfo; + PositionTickInfoBuilder() : m_result(new PositionTickInfo()) { } + + template PositionTickInfoBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static PositionTickInfoBuilder<0> create() + { + return PositionTickInfoBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + PositionTickInfo() + { + m_line = 0; + m_ticks = 0; + } + + int m_line; + int m_ticks; +}; + + +class CoverageRange : public ::v8_crdtp::ProtocolObject { +public: + ~CoverageRange() override { } + + int getStartOffset() { return m_startOffset; } + void setStartOffset(int value) { m_startOffset = value; } + + int getEndOffset() { return m_endOffset; } + void setEndOffset(int value) { m_endOffset = value; } + + int getCount() { return m_count; } + void setCount(int value) { m_count = value; } + + template + class CoverageRangeBuilder { + public: + enum { + NoFieldsSet = 0, + StartOffsetSet = 1 << 1, + EndOffsetSet = 1 << 2, + CountSet = 1 << 3, + AllFieldsSet = (StartOffsetSet | EndOffsetSet | CountSet | 0)}; + + + CoverageRangeBuilder& setStartOffset(int value) + { + static_assert(!(STATE & StartOffsetSet), "property startOffset should not be set yet"); + m_result->setStartOffset(value); + return castState(); + } + + CoverageRangeBuilder& setEndOffset(int value) + { + static_assert(!(STATE & EndOffsetSet), "property endOffset should not be set yet"); + m_result->setEndOffset(value); + return castState(); + } + + CoverageRangeBuilder& setCount(int value) + { + static_assert(!(STATE & CountSet), "property count should not be set yet"); + m_result->setCount(value); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class CoverageRange; + CoverageRangeBuilder() : m_result(new CoverageRange()) { } + + template CoverageRangeBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static CoverageRangeBuilder<0> create() + { + return CoverageRangeBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + CoverageRange() + { + m_startOffset = 0; + m_endOffset = 0; + m_count = 0; + } + + int m_startOffset; + int m_endOffset; + int m_count; +}; + + +class FunctionCoverage : public ::v8_crdtp::ProtocolObject { +public: + ~FunctionCoverage() override { } + + String getFunctionName() { return m_functionName; } + void setFunctionName(const String& value) { m_functionName = value; } + + protocol::Array* getRanges() { return m_ranges.get(); } + void setRanges(std::unique_ptr> value) { m_ranges = std::move(value); } + + bool getIsBlockCoverage() { return m_isBlockCoverage; } + void setIsBlockCoverage(bool value) { m_isBlockCoverage = value; } + + template + class FunctionCoverageBuilder { + public: + enum { + NoFieldsSet = 0, + FunctionNameSet = 1 << 1, + RangesSet = 1 << 2, + IsBlockCoverageSet = 1 << 3, + AllFieldsSet = (FunctionNameSet | RangesSet | IsBlockCoverageSet | 0)}; + + + FunctionCoverageBuilder& setFunctionName(const String& value) + { + static_assert(!(STATE & FunctionNameSet), "property functionName should not be set yet"); + m_result->setFunctionName(value); + return castState(); + } + + FunctionCoverageBuilder& setRanges(std::unique_ptr> value) + { + static_assert(!(STATE & RangesSet), "property ranges should not be set yet"); + m_result->setRanges(std::move(value)); + return castState(); + } + + FunctionCoverageBuilder& setIsBlockCoverage(bool value) + { + static_assert(!(STATE & IsBlockCoverageSet), "property isBlockCoverage should not be set yet"); + m_result->setIsBlockCoverage(value); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class FunctionCoverage; + FunctionCoverageBuilder() : m_result(new FunctionCoverage()) { } + + template FunctionCoverageBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static FunctionCoverageBuilder<0> create() + { + return FunctionCoverageBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + FunctionCoverage() + { + m_isBlockCoverage = false; + } + + String m_functionName; + std::unique_ptr> m_ranges; + bool m_isBlockCoverage; +}; + + +class ScriptCoverage : public ::v8_crdtp::ProtocolObject { +public: + ~ScriptCoverage() override { } + + String getScriptId() { return m_scriptId; } + void setScriptId(const String& value) { m_scriptId = value; } + + String getUrl() { return m_url; } + void setUrl(const String& value) { m_url = value; } + + protocol::Array* getFunctions() { return m_functions.get(); } + void setFunctions(std::unique_ptr> value) { m_functions = std::move(value); } + + template + class ScriptCoverageBuilder { + public: + enum { + NoFieldsSet = 0, + ScriptIdSet = 1 << 1, + UrlSet = 1 << 2, + FunctionsSet = 1 << 3, + AllFieldsSet = (ScriptIdSet | UrlSet | FunctionsSet | 0)}; + + + ScriptCoverageBuilder& setScriptId(const String& value) + { + static_assert(!(STATE & ScriptIdSet), "property scriptId should not be set yet"); + m_result->setScriptId(value); + return castState(); + } + + ScriptCoverageBuilder& setUrl(const String& value) + { + static_assert(!(STATE & UrlSet), "property url should not be set yet"); + m_result->setUrl(value); + return castState(); + } + + ScriptCoverageBuilder& setFunctions(std::unique_ptr> value) + { + static_assert(!(STATE & FunctionsSet), "property functions should not be set yet"); + m_result->setFunctions(std::move(value)); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ScriptCoverage; + ScriptCoverageBuilder() : m_result(new ScriptCoverage()) { } + + template ScriptCoverageBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static ScriptCoverageBuilder<0> create() + { + return ScriptCoverageBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + ScriptCoverage() + { + } + + String m_scriptId; + String m_url; + std::unique_ptr> m_functions; +}; + + +// ------------- Backend interface. + +class Backend { +public: + virtual ~Backend() { } + + virtual DispatchResponse disable() = 0; + virtual DispatchResponse enable() = 0; + virtual DispatchResponse getBestEffortCoverage(std::unique_ptr>* out_result) = 0; + virtual DispatchResponse setSamplingInterval(int in_interval) = 0; + virtual DispatchResponse start() = 0; + virtual DispatchResponse startPreciseCoverage(Maybe in_callCount, Maybe in_detailed, Maybe in_allowTriggeredUpdates, double* out_timestamp) = 0; + virtual DispatchResponse stop(std::unique_ptr* out_profile) = 0; + virtual DispatchResponse stopPreciseCoverage() = 0; + virtual DispatchResponse takePreciseCoverage(std::unique_ptr>* out_result, double* out_timestamp) = 0; + +}; + +// ------------- Frontend interface. + +class Frontend { +public: + explicit Frontend(FrontendChannel* frontend_channel) : frontend_channel_(frontend_channel) {} + void consoleProfileFinished(const String& id, std::unique_ptr location, std::unique_ptr profile, Maybe title = Maybe()); + void consoleProfileStarted(const String& id, std::unique_ptr location, Maybe title = Maybe()); + void preciseCoverageDeltaUpdate(double timestamp, const String& occasion, std::unique_ptr> result); + + void flush(); + void sendRawNotification(std::unique_ptr); + private: + FrontendChannel* frontend_channel_; +}; + +// ------------- Dispatcher. + +class Dispatcher { +public: + static void wire(UberDispatcher*, Backend*); + +private: + Dispatcher() { } +}; + +// ------------- Metainfo. + +class Metainfo { +public: + using BackendClass = Backend; + using FrontendClass = Frontend; + using DispatcherClass = Dispatcher; + static const char domainName[]; + static const char commandPrefix[]; + static const char version[]; +}; + +} // namespace Profiler +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Profiler_h) diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Protocol.cpp b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Protocol.cpp new file mode 100644 index 000000000..04960ab56 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Protocol.cpp @@ -0,0 +1,726 @@ +// This file is generated by Protocol_cpp.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/inspector/protocol/Protocol.h" + +#include +#include +#include +#include + + +// This file is generated by Values_cpp.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//#include "Values.h" + +#include "third_party/inspector_protocol/crdtp/cbor.h" + +namespace v8_inspector { +namespace protocol { + +namespace { +using v8_crdtp::Status; +using v8_crdtp::ParserHandler; +using v8_crdtp::span; +namespace cbor { +using v8_crdtp::cbor::ParseCBOR; +using v8_crdtp::cbor::EncodeBinary; +using v8_crdtp::cbor::EncodeDouble; +using v8_crdtp::cbor::EncodeFalse; +using v8_crdtp::cbor::EncodeFromLatin1; +using v8_crdtp::cbor::EncodeFromUTF16; +using v8_crdtp::cbor::EncodeIndefiniteLengthArrayStart; +using v8_crdtp::cbor::EncodeIndefiniteLengthMapStart; +using v8_crdtp::cbor::EncodeInt32; +using v8_crdtp::cbor::EncodeNull; +using v8_crdtp::cbor::EncodeStop; +using v8_crdtp::cbor::EncodeString8; +using v8_crdtp::cbor::EncodeTrue; +using v8_crdtp::cbor::EnvelopeEncoder; +} // namespace cbor + +// Uses the parsing events received from driver of |ParserHandler| +// (e.g. cbor::ParseCBOR) into a protocol::Value instance. +class ValueParserHandler : public ParserHandler { + public: + // Provides the parsed protocol::Value. + std::unique_ptr ReleaseRoot() { return std::move(root_); } + + // The first parsing error encountered; |status().ok()| is the default. + Status status() const { return status_; } + + private: + // + // Implementation of ParserHandler. + // + void HandleMapBegin() override { + if (!status_.ok()) return; + std::unique_ptr dict = DictionaryValue::create(); + DictionaryValue* dict_ptr = dict.get(); + AddValueToParent(std::move(dict)); + stack_.emplace_back(dict_ptr); + } + + void HandleMapEnd() override { + if (!status_.ok()) return; + DCHECK(!stack_.empty()); + DCHECK(stack_.back().is_dict); + stack_.pop_back(); + } + + void HandleArrayBegin() override { + if (!status_.ok()) return; + std::unique_ptr list = ListValue::create(); + ListValue* list_ptr = list.get(); + AddValueToParent(std::move(list)); + stack_.emplace_back(list_ptr); + } + + void HandleArrayEnd() override { + if (!status_.ok()) return; + DCHECK(!stack_.empty()); + DCHECK(!stack_.back().is_dict); + stack_.pop_back(); + } + + void HandleString8(span chars) override { + AddStringToParent(StringUtil::fromUTF8(chars.data(), chars.size())); + } + + void HandleString16(span chars) override { + AddStringToParent( + StringUtil::fromUTF16LE(chars.data(), chars.size())); + } + + void HandleBinary(span bytes) override { + AddValueToParent( + BinaryValue::create(Binary::fromSpan(bytes.data(), bytes.size()))); + } + + void HandleDouble(double value) override { + AddValueToParent(FundamentalValue::create(value)); + } + + void HandleInt32(int32_t value) override { + AddValueToParent(FundamentalValue::create(value)); + } + + void HandleBool(bool value) override { + AddValueToParent(FundamentalValue::create(value)); + } + + void HandleNull() override { + AddValueToParent(Value::null()); + } + + void HandleError(Status error) override { + status_ = error; + } + + // + // Adding strings and values to the parent value. + // Strings are handled separately because they can be keys for + // dictionary values. + // + void AddStringToParent(String str) { + if (!status_.ok()) return; + if (!root_) { + DCHECK(!key_is_pending_); + root_ = StringValue::create(str); + } else if (stack_.back().is_dict) { + // If we already have a pending key, then this is the value of the + // key/value pair. Otherwise, it's the new pending key. + if (key_is_pending_) { + stack_.back().dict->setString(pending_key_, str); + key_is_pending_ = false; + } else { + pending_key_ = std::move(str); + key_is_pending_ = true; + } + } else { // Top of the stack is a list. + DCHECK(!key_is_pending_); + stack_.back().list->pushValue(StringValue::create(str)); + } + } + + void AddValueToParent(std::unique_ptr value) { + if (!status_.ok()) return; + if (!root_) { + DCHECK(!key_is_pending_); + root_ = std::move(value); + } else if (stack_.back().is_dict) { + DCHECK(key_is_pending_); + stack_.back().dict->setValue(pending_key_, std::move(value)); + key_is_pending_ = false; + } else { // Top of the stack is a list. + DCHECK(!key_is_pending_); + stack_.back().list->pushValue(std::move(value)); + } + } + + // |status_.ok()| is the default; if we receive an error event + // we keep the first one and stop modifying any other state. + Status status_; + + // The root of the parsed protocol::Value tree. + std::unique_ptr root_; + + // If root_ is a list or a dictionary, this stack keeps track of + // the container we're currently parsing as well as its ancestors. + struct ContainerState { + ContainerState(DictionaryValue* dict) : is_dict(true), dict(dict) {} + ContainerState(ListValue* list) : is_dict(false), list(list) {} + + bool is_dict; + union { + DictionaryValue* dict; + ListValue* list; + }; + }; + std::vector stack_; + + // For maps, keys and values are alternating events, so we keep the + // key around and process it when the value arrives. + bool key_is_pending_ = false; + String pending_key_; +}; +} // anonymous namespace + +// static +std::unique_ptr Value::parseBinary(const uint8_t* data, size_t size) { + ValueParserHandler handler; + cbor::ParseCBOR(span(data, size), &handler); + // TODO(johannes): We have decent error info in handler.status(); provide + // a richer interface that makes this available to client code. + if (handler.status().ok()) + return handler.ReleaseRoot(); + return nullptr; +} + +bool Value::asBoolean(bool*) const +{ + return false; +} + +bool Value::asDouble(double*) const +{ + return false; +} + +bool Value::asInteger(int*) const +{ + return false; +} + +bool Value::asString(String*) const +{ + return false; +} + +bool Value::asBinary(Binary*) const +{ + return false; +} + +void Value::AppendSerialized(std::vector* bytes) const { + DCHECK(m_type == TypeNull); + bytes->push_back(cbor::EncodeNull()); +} + +std::unique_ptr Value::clone() const +{ + return Value::null(); +} + +bool FundamentalValue::asBoolean(bool* output) const +{ + if (type() != TypeBoolean) + return false; + *output = m_boolValue; + return true; +} + +bool FundamentalValue::asDouble(double* output) const +{ + if (type() == TypeDouble) { + *output = m_doubleValue; + return true; + } + if (type() == TypeInteger) { + *output = m_integerValue; + return true; + } + return false; +} + +bool FundamentalValue::asInteger(int* output) const +{ + if (type() != TypeInteger) + return false; + *output = m_integerValue; + return true; +} + +void FundamentalValue::AppendSerialized(std::vector* bytes) const { + switch (type()) { + case TypeDouble: + cbor::EncodeDouble(m_doubleValue, bytes); + return; + case TypeInteger: + cbor::EncodeInt32(m_integerValue, bytes); + return; + case TypeBoolean: + bytes->push_back(m_boolValue ? cbor::EncodeTrue() : cbor::EncodeFalse()); + return; + default: + DCHECK(false); + } +} + +std::unique_ptr FundamentalValue::clone() const +{ + switch (type()) { + case TypeDouble: return FundamentalValue::create(m_doubleValue); + case TypeInteger: return FundamentalValue::create(m_integerValue); + case TypeBoolean: return FundamentalValue::create(m_boolValue); + default: + DCHECK(false); + } + return nullptr; +} + +bool StringValue::asString(String* output) const +{ + *output = m_stringValue; + return true; +} + +namespace { +// This routine distinguishes between the current encoding for a given +// string |s|, and calls encoding routines that will +// - Ensure that all ASCII strings end up being encoded as UTF8 in +// the wire format - e.g., EncodeFromUTF16 will detect ASCII and +// do the (trivial) transcode to STRING8 on the wire, but if it's +// not ASCII it'll do STRING16. +// - Select a format that's cheap to convert to. E.g., we don't +// have LATIN1 on the wire, so we call EncodeFromLatin1 which +// transcodes to UTF8 if needed. +void EncodeString(const String& s, std::vector* out) { + if (StringUtil::CharacterCount(s) == 0) { + cbor::EncodeString8(span(nullptr, 0), out); // Empty string. + } else if (StringUtil::CharactersLatin1(s)) { + cbor::EncodeFromLatin1(span(StringUtil::CharactersLatin1(s), + StringUtil::CharacterCount(s)), + out); + } else if (StringUtil::CharactersUTF16(s)) { + cbor::EncodeFromUTF16(span(StringUtil::CharactersUTF16(s), + StringUtil::CharacterCount(s)), + out); + } else if (StringUtil::CharactersUTF8(s)) { + cbor::EncodeString8(span(StringUtil::CharactersUTF8(s), + StringUtil::CharacterCount(s)), + out); + } +} +} // namespace +void StringValue::AppendSerialized(std::vector* bytes) const { + EncodeString(m_stringValue, bytes); +} + +std::unique_ptr StringValue::clone() const +{ + return StringValue::create(m_stringValue); +} + +bool BinaryValue::asBinary(Binary* output) const +{ + *output = m_binaryValue; + return true; +} + +void BinaryValue::AppendSerialized(std::vector* bytes) const { + cbor::EncodeBinary(span(m_binaryValue.data(), + m_binaryValue.size()), bytes); +} + +std::unique_ptr BinaryValue::clone() const +{ + return BinaryValue::create(m_binaryValue); +} + + +DictionaryValue::~DictionaryValue() +{ +} + +void DictionaryValue::setBoolean(const String& name, bool value) +{ + setValue(name, FundamentalValue::create(value)); +} + +void DictionaryValue::setInteger(const String& name, int value) +{ + setValue(name, FundamentalValue::create(value)); +} + +void DictionaryValue::setDouble(const String& name, double value) +{ + setValue(name, FundamentalValue::create(value)); +} + +void DictionaryValue::setString(const String& name, const String& value) +{ + setValue(name, StringValue::create(value)); +} + +void DictionaryValue::setValue(const String& name, std::unique_ptr value) +{ + set(name, value); +} + +void DictionaryValue::setObject(const String& name, std::unique_ptr value) +{ + set(name, value); +} + +void DictionaryValue::setArray(const String& name, std::unique_ptr value) +{ + set(name, value); +} + +bool DictionaryValue::getBoolean(const String& name, bool* output) const +{ + protocol::Value* value = get(name); + if (!value) + return false; + return value->asBoolean(output); +} + +bool DictionaryValue::getInteger(const String& name, int* output) const +{ + Value* value = get(name); + if (!value) + return false; + return value->asInteger(output); +} + +bool DictionaryValue::getDouble(const String& name, double* output) const +{ + Value* value = get(name); + if (!value) + return false; + return value->asDouble(output); +} + +bool DictionaryValue::getString(const String& name, String* output) const +{ + protocol::Value* value = get(name); + if (!value) + return false; + return value->asString(output); +} + +DictionaryValue* DictionaryValue::getObject(const String& name) const +{ + return DictionaryValue::cast(get(name)); +} + +protocol::ListValue* DictionaryValue::getArray(const String& name) const +{ + return ListValue::cast(get(name)); +} + +protocol::Value* DictionaryValue::get(const String& name) const +{ + Dictionary::const_iterator it = m_data.find(name); + if (it == m_data.end()) + return nullptr; + return it->second.get(); +} + +DictionaryValue::Entry DictionaryValue::at(size_t index) const +{ + const String key = m_order[index]; + return std::make_pair(key, m_data.find(key)->second.get()); +} + +bool DictionaryValue::booleanProperty(const String& name, bool defaultValue) const +{ + bool result = defaultValue; + getBoolean(name, &result); + return result; +} + +int DictionaryValue::integerProperty(const String& name, int defaultValue) const +{ + int result = defaultValue; + getInteger(name, &result); + return result; +} + +double DictionaryValue::doubleProperty(const String& name, double defaultValue) const +{ + double result = defaultValue; + getDouble(name, &result); + return result; +} + +void DictionaryValue::remove(const String& name) +{ + m_data.erase(name); + m_order.erase(std::remove(m_order.begin(), m_order.end(), name), m_order.end()); +} + +void DictionaryValue::AppendSerialized(std::vector* bytes) const { + cbor::EnvelopeEncoder encoder; + encoder.EncodeStart(bytes); + bytes->push_back(cbor::EncodeIndefiniteLengthMapStart()); + for (size_t i = 0; i < m_order.size(); ++i) { + const String& key = m_order[i]; + Dictionary::const_iterator value = m_data.find(key); + DCHECK(value != m_data.cend() && value->second); + EncodeString(key, bytes); + value->second->AppendSerialized(bytes); + } + bytes->push_back(cbor::EncodeStop()); + encoder.EncodeStop(bytes); +} + +std::unique_ptr DictionaryValue::clone() const +{ + std::unique_ptr result = DictionaryValue::create(); + for (size_t i = 0; i < m_order.size(); ++i) { + String key = m_order[i]; + Dictionary::const_iterator value = m_data.find(key); + DCHECK(value != m_data.cend() && value->second); + result->setValue(key, value->second->clone()); + } + return result; +} + +DictionaryValue::DictionaryValue() + : Value(TypeObject) +{ +} + +ListValue::~ListValue() +{ +} + +void ListValue::AppendSerialized(std::vector* bytes) const { + cbor::EnvelopeEncoder encoder; + encoder.EncodeStart(bytes); + bytes->push_back(cbor::EncodeIndefiniteLengthArrayStart()); + for (size_t i = 0; i < m_data.size(); ++i) { + m_data[i]->AppendSerialized(bytes); + } + bytes->push_back(cbor::EncodeStop()); + encoder.EncodeStop(bytes); +} + +std::unique_ptr ListValue::clone() const +{ + std::unique_ptr result = ListValue::create(); + for (const std::unique_ptr& value : m_data) + result->pushValue(value->clone()); + return result; +} + +ListValue::ListValue() + : Value(TypeArray) +{ +} + +void ListValue::pushValue(std::unique_ptr value) +{ + DCHECK(value); + m_data.push_back(std::move(value)); +} + +protocol::Value* ListValue::at(size_t index) +{ + DCHECK_LT(index, m_data.size()); + return m_data[index].get(); +} + +} // namespace v8_inspector +} // namespace protocol + + +// This file is generated by Object_cpp.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//#include "Object.h" + +namespace v8_inspector { +namespace protocol { + +std::unique_ptr Object::fromValue(protocol::Value* value, ErrorSupport* errors) +{ + protocol::DictionaryValue* dictionary = DictionaryValue::cast(value); + if (!dictionary) { + errors->AddError("object expected"); + return nullptr; + } + dictionary = static_cast(dictionary->clone().release()); + return std::unique_ptr(new Object(std::unique_ptr(dictionary))); +} + +// Implements Serializable. +void Object::AppendSerialized(std::vector* out) const { + m_object->AppendSerialized(out); +} + +std::unique_ptr Object::toValue() const +{ + return DictionaryValue::cast(m_object->clone()); +} + +std::unique_ptr Object::clone() const +{ + return std::unique_ptr(new Object(DictionaryValue::cast(m_object->clone()))); +} + +Object::Object(std::unique_ptr object) : m_object(std::move(object)) { } + +Object::~Object() { } + +} // namespace v8_inspector +} // namespace protocol + + +// This file is generated by ValueConversions_cpp.template. + +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/inspector/protocol/Protocol.h" + +#include +#include +#include + +//#include "ValueConversions.h" +//#include "Values.h" + +namespace v8_inspector { +namespace protocol { + +} // namespace +} // namespace + + +namespace v8_crdtp { + +namespace { + +using v8_inspector::protocol::Binary; +using v8_inspector::protocol::Object; +using v8_inspector::protocol::Value; +using v8_inspector::protocol::String; +using v8_inspector::protocol::DictionaryValue; +using v8_inspector::protocol::FundamentalValue; +using v8_inspector::protocol::StringValue; +using v8_inspector::protocol::StringUtil; +//using v8_inspector::protocol::EncodeString; + +std::unique_ptr ReadValue(DeserializerState* state) { + cbor::CBORTokenizer* tokenizer = state->tokenizer(); + switch (tokenizer->TokenTag()) { + case cbor::CBORTokenTag::TRUE_VALUE: + return FundamentalValue::create(true); + case cbor::CBORTokenTag::FALSE_VALUE: + return FundamentalValue::create(false); + case cbor::CBORTokenTag::NULL_VALUE: + return Value::null(); + case cbor::CBORTokenTag::INT32: + return FundamentalValue::create(tokenizer->GetInt32()); + case cbor::CBORTokenTag::DOUBLE: + return FundamentalValue::create(tokenizer->GetDouble()); + case cbor::CBORTokenTag::STRING8: { + const auto str = tokenizer->GetString8(); + return StringValue::create(StringUtil::fromUTF8(str.data(), str.size())); + } + case cbor::CBORTokenTag::STRING16: { + const auto str = tokenizer->GetString16WireRep(); + return StringValue::create(StringUtil::fromUTF16LE(reinterpret_cast(str.data()), str.size() / 2)); + } + case cbor::CBORTokenTag::ENVELOPE: { + const auto env = tokenizer->GetEnvelope(); + return Value::parseBinary(env.data(), env.size()); + } + // Intentionally not supported. + case cbor::CBORTokenTag::BINARY: + // Should not be encountered outside of envelope. + case cbor::CBORTokenTag::MAP_START: + case cbor::CBORTokenTag::ARRAY_START: + default: + state->RegisterError(Error::CBOR_UNSUPPORTED_VALUE); + return nullptr; + } +} + +} // namespace + +// static +bool ProtocolTypeTraits>::Deserialize( + DeserializerState* state, std::unique_ptr* value) { + auto result = ReadValue(state); + if (!result) + return false; + *value = std::move(result); + return true; +} + +// static +void ProtocolTypeTraits>::Serialize( + const std::unique_ptr& value, std::vector* bytes) { + value->AppendSerialized(bytes); +} + +// static +bool ProtocolTypeTraits>::Deserialize( + DeserializerState* state, std::unique_ptr* value) { + std::unique_ptr res; + if (!ProtocolTypeTraits>::Deserialize(state, &res)) + return false; + if (res->type() != Value::TypeObject) { + state->RegisterError(Error::BINDINGS_DICTIONARY_VALUE_EXPECTED); + return false; + } + *value = DictionaryValue::cast(std::move(res)); + return true; +} + +// static +void ProtocolTypeTraits>::Serialize( + const std::unique_ptr& value, std::vector* bytes) { + value->AppendSerialized(bytes); +} + +// static +bool ProtocolTypeTraits>::Deserialize(DeserializerState* state, std::unique_ptr* value) { + auto res = DictionaryValue::create(); + if (ProtocolTypeTraits>::Deserialize(state, &res)) { + *value = std::make_unique(std::move(res)); + return true; + } + return false; +} + +void ProtocolTypeTraits>::Serialize(const std::unique_ptr& value, std::vector* bytes) { + value->AppendSerialized(bytes); +} + +} // namespace v8_crdtp diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Protocol.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Protocol.h new file mode 100644 index 000000000..e4e3784ae --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Protocol.h @@ -0,0 +1,626 @@ +// This file is generated by Values_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Values_h +#define v8_inspector_protocol_Values_h + +//#include "Allocator.h" +//#include "Forward.h" + +#include +#include +#include +#include + +#include "src/inspector/protocol/Forward.h" + +namespace v8_inspector { +namespace protocol { + +class ListValue; +class DictionaryValue; +class Value; + +#define PROTOCOL_DISALLOW_COPY(ClassName) \ + private: \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(const ClassName&) = delete + +class Value : public Serializable { + PROTOCOL_DISALLOW_COPY(Value); +public: + virtual ~Value() override { } + + static std::unique_ptr null() + { + return std::unique_ptr(new Value()); + } + + static std::unique_ptr parseBinary(const uint8_t* data, size_t size); + + enum ValueType { + TypeNull = 0, + TypeBoolean, + TypeInteger, + TypeDouble, + TypeString, + TypeBinary, + TypeObject, + TypeArray, + TypeImported + }; + + ValueType type() const { return m_type; } + + bool isNull() const { return m_type == TypeNull; } + + virtual bool asBoolean(bool* output) const; + virtual bool asDouble(double* output) const; + virtual bool asInteger(int* output) const; + virtual bool asString(String* output) const; + virtual bool asBinary(Binary* output) const; + + virtual void AppendSerialized(std::vector* bytes) const override; + virtual std::unique_ptr clone() const; + +protected: + Value() : m_type(TypeNull) { } + explicit Value(ValueType type) : m_type(type) { } + +private: + friend class DictionaryValue; + friend class ListValue; + + ValueType m_type; +}; + +class FundamentalValue : public Value { +public: + static std::unique_ptr create(bool value) + { + return std::unique_ptr(new FundamentalValue(value)); + } + + static std::unique_ptr create(int value) + { + return std::unique_ptr(new FundamentalValue(value)); + } + + static std::unique_ptr create(double value) + { + return std::unique_ptr(new FundamentalValue(value)); + } + + bool asBoolean(bool* output) const override; + bool asDouble(double* output) const override; + bool asInteger(int* output) const override; + void AppendSerialized(std::vector* bytes) const override; + std::unique_ptr clone() const override; + +private: + explicit FundamentalValue(bool value) : Value(TypeBoolean), m_boolValue(value) { } + explicit FundamentalValue(int value) : Value(TypeInteger), m_integerValue(value) { } + explicit FundamentalValue(double value) : Value(TypeDouble), m_doubleValue(value) { } + + union { + bool m_boolValue; + double m_doubleValue; + int m_integerValue; + }; +}; + +class StringValue : public Value { +public: + static std::unique_ptr create(const String& value) + { + return std::unique_ptr(new StringValue(value)); + } + + static std::unique_ptr create(const char* value) + { + return std::unique_ptr(new StringValue(value)); + } + + bool asString(String* output) const override; + void AppendSerialized(std::vector* bytes) const override; + std::unique_ptr clone() const override; + +private: + explicit StringValue(const String& value) : Value(TypeString), m_stringValue(value) { } + explicit StringValue(const char* value) : Value(TypeString), m_stringValue(value) { } + + String m_stringValue; +}; + +class BinaryValue : public Value { +public: + static std::unique_ptr create(const Binary& value) + { + return std::unique_ptr(new BinaryValue(value)); + } + + bool asBinary(Binary* output) const override; + void AppendSerialized(std::vector* bytes) const override; + std::unique_ptr clone() const override; + +private: + explicit BinaryValue(const Binary& value) : Value(TypeBinary), m_binaryValue(value) { } + + Binary m_binaryValue; +}; + +class DictionaryValue : public Value { +public: + using Entry = std::pair; + static std::unique_ptr create() + { + return std::unique_ptr(new DictionaryValue()); + } + + static DictionaryValue* cast(Value* value) + { + if (!value || value->type() != TypeObject) + return nullptr; + return static_cast(value); + } + + static std::unique_ptr cast(std::unique_ptr value) + { + DictionaryValue* dictionaryValue = cast(value.get()); + if (dictionaryValue) value.release(); + return std::unique_ptr(dictionaryValue); + } + + void AppendSerialized(std::vector* bytes) const override; + std::unique_ptr clone() const override; + + size_t size() const { return m_data.size(); } + + void setBoolean(const String& name, bool); + void setInteger(const String& name, int); + void setDouble(const String& name, double); + void setString(const String& name, const String&); + void setValue(const String& name, std::unique_ptr); + void setObject(const String& name, std::unique_ptr); + void setArray(const String& name, std::unique_ptr); + + bool getBoolean(const String& name, bool* output) const; + bool getInteger(const String& name, int* output) const; + bool getDouble(const String& name, double* output) const; + bool getString(const String& name, String* output) const; + + DictionaryValue* getObject(const String& name) const; + ListValue* getArray(const String& name) const; + Value* get(const String& name) const; + Entry at(size_t index) const; + + bool booleanProperty(const String& name, bool defaultValue) const; + int integerProperty(const String& name, int defaultValue) const; + double doubleProperty(const String& name, double defaultValue) const; + void remove(const String& name); + + ~DictionaryValue() override; + +private: + DictionaryValue(); + template + void set(const String& key, std::unique_ptr& value) + { + DCHECK(value); + bool isNew = m_data.find(key) == m_data.end(); + m_data[key] = std::move(value); + if (isNew) + m_order.push_back(key); + } + + using Dictionary = std::unordered_map>; + Dictionary m_data; + std::vector m_order; +}; + +class ListValue : public Value { +public: + static std::unique_ptr create() + { + return std::unique_ptr(new ListValue()); + } + + static ListValue* cast(Value* value) + { + if (!value || value->type() != TypeArray) + return nullptr; + return static_cast(value); + } + + static std::unique_ptr cast(std::unique_ptr value) + { + ListValue* listValue = cast(value.get()); + if (listValue) value.release(); + return std::unique_ptr(listValue); + } + + ~ListValue() override; + + void AppendSerialized(std::vector* bytes) const override; + std::unique_ptr clone() const override; + + void pushValue(std::unique_ptr); + + Value* at(size_t index); + size_t size() const { return m_data.size(); } + void reserve(size_t capacity) { m_data.reserve(capacity); } + +private: + ListValue(); + std::vector> m_data; +}; + +} // namespace v8_inspector +} // namespace protocol + +#endif // v8_inspector_protocol_Values_h + + +// This file is generated by Object_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Object_h +#define v8_inspector_protocol_Object_h + +//#include "ErrorSupport.h" +//#include "Forward.h" +//#include "Values.h" + +#include "third_party/inspector_protocol/crdtp/serializable.h" + +namespace v8_inspector { +namespace protocol { + +class Object : public v8_crdtp::Serializable { +public: + static std::unique_ptr fromValue(protocol::Value*, ErrorSupport*); + explicit Object(std::unique_ptr); + ~Object(); + + // Implements Serializable. + void AppendSerialized(std::vector* out) const override; + + std::unique_ptr toValue() const; + std::unique_ptr clone() const; + +private: + Object() = default; + friend struct v8_crdtp::ProtocolTypeTraits, void>; + + std::unique_ptr m_object; +}; + +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Object_h) + + +// This file is generated by ValueConversions_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_ValueConversions_h +#define v8_inspector_protocol_ValueConversions_h + +//#include "ErrorSupport.h" +//#include "Forward.h" +//#include "Values.h" + +namespace v8_inspector { +namespace protocol { + +template +struct ValueConversions { + static std::unique_ptr fromValue(protocol::Value* value, ErrorSupport* errors) + { + return T::fromValue(value, errors); + } + + static std::unique_ptr toValue(T* value) + { + return value->toValue(); + } + + static std::unique_ptr toValue(const std::unique_ptr& value) + { + return value->toValue(); + } +}; + +template<> +struct ValueConversions { + static bool fromValue(protocol::Value* value, ErrorSupport* errors) + { + bool result = false; + bool success = value ? value->asBoolean(&result) : false; + if (!success) + errors->AddError("boolean value expected"); + return result; + } + + static std::unique_ptr toValue(bool value) + { + return FundamentalValue::create(value); + } +}; + +template<> +struct ValueConversions { + static int fromValue(protocol::Value* value, ErrorSupport* errors) + { + int result = 0; + bool success = value ? value->asInteger(&result) : false; + if (!success) + errors->AddError("integer value expected"); + return result; + } + + static std::unique_ptr toValue(int value) + { + return FundamentalValue::create(value); + } +}; + +template<> +struct ValueConversions { + static double fromValue(protocol::Value* value, ErrorSupport* errors) + { + double result = 0; + bool success = value ? value->asDouble(&result) : false; + if (!success) + errors->AddError("double value expected"); + return result; + } + + static std::unique_ptr toValue(double value) + { + return FundamentalValue::create(value); + } +}; + +template<> +struct ValueConversions { + static String fromValue(protocol::Value* value, ErrorSupport* errors) + { + String result; + bool success = value ? value->asString(&result) : false; + if (!success) + errors->AddError("string value expected"); + return result; + } + + static std::unique_ptr toValue(const String& value) + { + return StringValue::create(value); + } +}; + +template<> +struct ValueConversions { + static Binary fromValue(protocol::Value* value, ErrorSupport* errors) + { + if (!value || + (value->type() != Value::TypeBinary && value->type() != Value::TypeString)) { + errors->AddError("Either string base64 or binary value expected"); + return Binary(); + } + Binary binary; + if (value->asBinary(&binary)) + return binary; + String result; + value->asString(&result); + bool success; + Binary out = Binary::fromBase64(result, &success); + if (!success) + errors->AddError("base64 decoding error"); + return out; + } + + static std::unique_ptr toValue(const Binary& value) + { + return BinaryValue::create(value); + } +}; + +template +struct ValueConversions>> { + static std::unique_ptr>> fromValue(protocol::Value* value, ErrorSupport* errors) { + protocol::ListValue* array = ListValue::cast(value); + if (!array) { + errors->AddError("array expected"); + return nullptr; + } + errors->Push(); + std::unique_ptr>> result( + new std::vector>()); + result->reserve(array->size()); + for (size_t i = 0; i < array->size(); ++i) { + errors->SetIndex(i); + auto item = ValueConversions::fromValue(array->at(i), errors); + result->emplace_back(std::move(item)); + } + errors->Pop(); + if (!errors->Errors().empty()) + return nullptr; + return result; + } + + static std::unique_ptr toValue(std::vector>* v) + { + std::unique_ptr result = ListValue::create(); + result->reserve(v->size()); + for (auto& item : *v) + result->pushValue(ValueConversions::toValue(item.get())); + return result; + } + +}; + +template +struct ValueConversions> { + static std::unique_ptr> fromValue(protocol::Value* value, ErrorSupport* errors) { + protocol::ListValue* array = ListValue::cast(value); + if (!array) { + errors->AddError("array expected"); + return nullptr; + } + errors->Push(); + std::unique_ptr> result(new std::vector()); + result->reserve(array->size()); + for (size_t i = 0; i < array->size(); ++i) { + errors->SetIndex(i); + auto item = ValueConversions::fromValue(array->at(i), errors); + result->emplace_back(std::move(item)); + } + errors->Pop(); + if (!errors->Errors().empty()) + return nullptr; + return result; + } + + static std::unique_ptr toValue(std::vector* v) + { + std::unique_ptr result = ListValue::create(); + result->reserve(v->size()); + for (auto& item : *v) + result->pushValue(ValueConversions::toValue(item)); + return result; + } +}; + +template<> +struct ValueConversions { + static std::unique_ptr fromValue(protocol::Value* value, ErrorSupport* errors) + { + bool success = !!value; + if (!success) { + errors->AddError("value expected"); + return nullptr; + } + return value->clone(); + } + + static std::unique_ptr toValue(Value* value) + { + return value->clone(); + } + + static std::unique_ptr toValue(const std::unique_ptr& value) + { + return value->clone(); + } +}; + +template<> +struct ValueConversions { + static std::unique_ptr fromValue(protocol::Value* value, ErrorSupport* errors) + { + bool success = value && value->type() == protocol::Value::TypeObject; + if (!success) + errors->AddError("object expected"); + return DictionaryValue::cast(value->clone()); + } + + static std::unique_ptr toValue(DictionaryValue* value) + { + return value->clone(); + } + + static std::unique_ptr toValue(const std::unique_ptr& value) + { + return value->clone(); + } +}; + +template<> +struct ValueConversions { + static std::unique_ptr fromValue(protocol::Value* value, ErrorSupport* errors) + { + bool success = value && value->type() == protocol::Value::TypeArray; + if (!success) + errors->AddError("list expected"); + return ListValue::cast(value->clone()); + } + + static std::unique_ptr toValue(ListValue* value) + { + return value->clone(); + } + + static std::unique_ptr toValue(const std::unique_ptr& value) + { + return value->clone(); + } +}; + +template struct ValueTypeConverter { + static std::unique_ptr FromValue(const protocol::Value& value) { + std::vector bytes; + value.AppendSerialized(&bytes); + return T::FromBinary(bytes.data(), bytes.size()); + } + + static std::unique_ptr ToValue(const T& obj) { + std::vector bytes; + obj.AppendSerialized(&bytes); + auto result = Value::parseBinary(bytes.data(), bytes.size()); + return DictionaryValue::cast(std::move(result)); + } +}; + +} // namespace v8_inspector +} // namespace protocol + +namespace v8_crdtp { + +template +struct ProtocolTypeTraits::value>::type> { + static void Serialize(const v8_inspector::protocol::Value& value, std::vector* bytes) { + value.AppendSerialized(bytes); + } +}; + +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::unique_ptr* value); + static void Serialize(const std::unique_ptr& value, std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::unique_ptr* value); + static void Serialize(const std::unique_ptr& value, std::vector* bytes); +}; + +// TODO(caseq): get rid of it, it's just a DictionaryValue really. +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::unique_ptr* value); + static void Serialize(const std::unique_ptr& value, std::vector* bytes); +}; + +template<> +struct ProtocolTypeTraits { + static void Serialize(const v8_inspector::protocol::Object& value, std::vector* bytes) { + value.AppendSerialized(bytes); + } +}; + +} // namespace v8_crdtp + +#endif // !defined(v8_inspector_protocol_ValueConversions_h) diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Runtime.cpp b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Runtime.cpp new file mode 100644 index 000000000..811cfaecd --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Runtime.cpp @@ -0,0 +1,1625 @@ +// This file is generated by TypeBuilder_cpp.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/inspector/protocol/Runtime.h" + +#include "src/inspector/protocol/Protocol.h" + +#include "third_party/inspector_protocol/crdtp/cbor.h" +#include "third_party/inspector_protocol/crdtp/find_by_first.h" +#include "third_party/inspector_protocol/crdtp/span.h" + +namespace v8_inspector { +namespace protocol { +namespace Runtime { + +using v8_crdtp::DeserializerState; +using v8_crdtp::ProtocolTypeTraits; + +// ------------- Enum values from types. + +const char Metainfo::domainName[] = "Runtime"; +const char Metainfo::commandPrefix[] = "Runtime."; +const char Metainfo::version[] = "1.3"; + + + +const char* WebDriverValue::TypeEnum::Undefined = "undefined"; +const char* WebDriverValue::TypeEnum::Null = "null"; +const char* WebDriverValue::TypeEnum::String = "string"; +const char* WebDriverValue::TypeEnum::Number = "number"; +const char* WebDriverValue::TypeEnum::Boolean = "boolean"; +const char* WebDriverValue::TypeEnum::Bigint = "bigint"; +const char* WebDriverValue::TypeEnum::Regexp = "regexp"; +const char* WebDriverValue::TypeEnum::Date = "date"; +const char* WebDriverValue::TypeEnum::Symbol = "symbol"; +const char* WebDriverValue::TypeEnum::Array = "array"; +const char* WebDriverValue::TypeEnum::Object = "object"; +const char* WebDriverValue::TypeEnum::Function = "function"; +const char* WebDriverValue::TypeEnum::Map = "map"; +const char* WebDriverValue::TypeEnum::Set = "set"; +const char* WebDriverValue::TypeEnum::Weakmap = "weakmap"; +const char* WebDriverValue::TypeEnum::Weakset = "weakset"; +const char* WebDriverValue::TypeEnum::Error = "error"; +const char* WebDriverValue::TypeEnum::Proxy = "proxy"; +const char* WebDriverValue::TypeEnum::Promise = "promise"; +const char* WebDriverValue::TypeEnum::Typedarray = "typedarray"; +const char* WebDriverValue::TypeEnum::Arraybuffer = "arraybuffer"; +const char* WebDriverValue::TypeEnum::Node = "node"; +const char* WebDriverValue::TypeEnum::Window = "window"; +V8_CRDTP_BEGIN_DESERIALIZER(WebDriverValue) + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectId", m_objectId), + V8_CRDTP_DESERIALIZE_FIELD("type", m_type), + V8_CRDTP_DESERIALIZE_FIELD_OPT("value", m_value), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(WebDriverValue) + V8_CRDTP_SERIALIZE_FIELD("type", m_type); + V8_CRDTP_SERIALIZE_FIELD("value", m_value); + V8_CRDTP_SERIALIZE_FIELD("objectId", m_objectId); +V8_CRDTP_END_SERIALIZER(); + + + + + +const char* RemoteObject::TypeEnum::Object = "object"; +const char* RemoteObject::TypeEnum::Function = "function"; +const char* RemoteObject::TypeEnum::Undefined = "undefined"; +const char* RemoteObject::TypeEnum::String = "string"; +const char* RemoteObject::TypeEnum::Number = "number"; +const char* RemoteObject::TypeEnum::Boolean = "boolean"; +const char* RemoteObject::TypeEnum::Symbol = "symbol"; +const char* RemoteObject::TypeEnum::Bigint = "bigint"; + +const char* RemoteObject::SubtypeEnum::Array = "array"; +const char* RemoteObject::SubtypeEnum::Null = "null"; +const char* RemoteObject::SubtypeEnum::Node = "node"; +const char* RemoteObject::SubtypeEnum::Regexp = "regexp"; +const char* RemoteObject::SubtypeEnum::Date = "date"; +const char* RemoteObject::SubtypeEnum::Map = "map"; +const char* RemoteObject::SubtypeEnum::Set = "set"; +const char* RemoteObject::SubtypeEnum::Weakmap = "weakmap"; +const char* RemoteObject::SubtypeEnum::Weakset = "weakset"; +const char* RemoteObject::SubtypeEnum::Iterator = "iterator"; +const char* RemoteObject::SubtypeEnum::Generator = "generator"; +const char* RemoteObject::SubtypeEnum::Error = "error"; +const char* RemoteObject::SubtypeEnum::Proxy = "proxy"; +const char* RemoteObject::SubtypeEnum::Promise = "promise"; +const char* RemoteObject::SubtypeEnum::Typedarray = "typedarray"; +const char* RemoteObject::SubtypeEnum::Arraybuffer = "arraybuffer"; +const char* RemoteObject::SubtypeEnum::Dataview = "dataview"; +const char* RemoteObject::SubtypeEnum::Webassemblymemory = "webassemblymemory"; +const char* RemoteObject::SubtypeEnum::Wasmvalue = "wasmvalue"; +V8_CRDTP_BEGIN_DESERIALIZER(RemoteObject) + V8_CRDTP_DESERIALIZE_FIELD_OPT("className", m_className), + V8_CRDTP_DESERIALIZE_FIELD_OPT("customPreview", m_customPreview), + V8_CRDTP_DESERIALIZE_FIELD_OPT("description", m_description), + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectId", m_objectId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("preview", m_preview), + V8_CRDTP_DESERIALIZE_FIELD_OPT("subtype", m_subtype), + V8_CRDTP_DESERIALIZE_FIELD("type", m_type), + V8_CRDTP_DESERIALIZE_FIELD_OPT("unserializableValue", m_unserializableValue), + V8_CRDTP_DESERIALIZE_FIELD_OPT("value", m_value), + V8_CRDTP_DESERIALIZE_FIELD_OPT("webDriverValue", m_webDriverValue), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(RemoteObject) + V8_CRDTP_SERIALIZE_FIELD("type", m_type); + V8_CRDTP_SERIALIZE_FIELD("subtype", m_subtype); + V8_CRDTP_SERIALIZE_FIELD("className", m_className); + V8_CRDTP_SERIALIZE_FIELD("value", m_value); + V8_CRDTP_SERIALIZE_FIELD("unserializableValue", m_unserializableValue); + V8_CRDTP_SERIALIZE_FIELD("description", m_description); + V8_CRDTP_SERIALIZE_FIELD("webDriverValue", m_webDriverValue); + V8_CRDTP_SERIALIZE_FIELD("objectId", m_objectId); + V8_CRDTP_SERIALIZE_FIELD("preview", m_preview); + V8_CRDTP_SERIALIZE_FIELD("customPreview", m_customPreview); +V8_CRDTP_END_SERIALIZER(); + +// static +std::unique_ptr API::RemoteObject::fromBinary(const uint8_t* data, size_t length) +{ + return protocol::Runtime::RemoteObject::FromBinary(data, length); +} + +V8_CRDTP_BEGIN_DESERIALIZER(CustomPreview) + V8_CRDTP_DESERIALIZE_FIELD_OPT("bodyGetterId", m_bodyGetterId), + V8_CRDTP_DESERIALIZE_FIELD("header", m_header), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(CustomPreview) + V8_CRDTP_SERIALIZE_FIELD("header", m_header); + V8_CRDTP_SERIALIZE_FIELD("bodyGetterId", m_bodyGetterId); +V8_CRDTP_END_SERIALIZER(); + + + +const char* ObjectPreview::TypeEnum::Object = "object"; +const char* ObjectPreview::TypeEnum::Function = "function"; +const char* ObjectPreview::TypeEnum::Undefined = "undefined"; +const char* ObjectPreview::TypeEnum::String = "string"; +const char* ObjectPreview::TypeEnum::Number = "number"; +const char* ObjectPreview::TypeEnum::Boolean = "boolean"; +const char* ObjectPreview::TypeEnum::Symbol = "symbol"; +const char* ObjectPreview::TypeEnum::Bigint = "bigint"; + +const char* ObjectPreview::SubtypeEnum::Array = "array"; +const char* ObjectPreview::SubtypeEnum::Null = "null"; +const char* ObjectPreview::SubtypeEnum::Node = "node"; +const char* ObjectPreview::SubtypeEnum::Regexp = "regexp"; +const char* ObjectPreview::SubtypeEnum::Date = "date"; +const char* ObjectPreview::SubtypeEnum::Map = "map"; +const char* ObjectPreview::SubtypeEnum::Set = "set"; +const char* ObjectPreview::SubtypeEnum::Weakmap = "weakmap"; +const char* ObjectPreview::SubtypeEnum::Weakset = "weakset"; +const char* ObjectPreview::SubtypeEnum::Iterator = "iterator"; +const char* ObjectPreview::SubtypeEnum::Generator = "generator"; +const char* ObjectPreview::SubtypeEnum::Error = "error"; +const char* ObjectPreview::SubtypeEnum::Proxy = "proxy"; +const char* ObjectPreview::SubtypeEnum::Promise = "promise"; +const char* ObjectPreview::SubtypeEnum::Typedarray = "typedarray"; +const char* ObjectPreview::SubtypeEnum::Arraybuffer = "arraybuffer"; +const char* ObjectPreview::SubtypeEnum::Dataview = "dataview"; +const char* ObjectPreview::SubtypeEnum::Webassemblymemory = "webassemblymemory"; +const char* ObjectPreview::SubtypeEnum::Wasmvalue = "wasmvalue"; +V8_CRDTP_BEGIN_DESERIALIZER(ObjectPreview) + V8_CRDTP_DESERIALIZE_FIELD_OPT("description", m_description), + V8_CRDTP_DESERIALIZE_FIELD_OPT("entries", m_entries), + V8_CRDTP_DESERIALIZE_FIELD("overflow", m_overflow), + V8_CRDTP_DESERIALIZE_FIELD("properties", m_properties), + V8_CRDTP_DESERIALIZE_FIELD_OPT("subtype", m_subtype), + V8_CRDTP_DESERIALIZE_FIELD("type", m_type), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(ObjectPreview) + V8_CRDTP_SERIALIZE_FIELD("type", m_type); + V8_CRDTP_SERIALIZE_FIELD("subtype", m_subtype); + V8_CRDTP_SERIALIZE_FIELD("description", m_description); + V8_CRDTP_SERIALIZE_FIELD("overflow", m_overflow); + V8_CRDTP_SERIALIZE_FIELD("properties", m_properties); + V8_CRDTP_SERIALIZE_FIELD("entries", m_entries); +V8_CRDTP_END_SERIALIZER(); + + + +const char* PropertyPreview::TypeEnum::Object = "object"; +const char* PropertyPreview::TypeEnum::Function = "function"; +const char* PropertyPreview::TypeEnum::Undefined = "undefined"; +const char* PropertyPreview::TypeEnum::String = "string"; +const char* PropertyPreview::TypeEnum::Number = "number"; +const char* PropertyPreview::TypeEnum::Boolean = "boolean"; +const char* PropertyPreview::TypeEnum::Symbol = "symbol"; +const char* PropertyPreview::TypeEnum::Accessor = "accessor"; +const char* PropertyPreview::TypeEnum::Bigint = "bigint"; + +const char* PropertyPreview::SubtypeEnum::Array = "array"; +const char* PropertyPreview::SubtypeEnum::Null = "null"; +const char* PropertyPreview::SubtypeEnum::Node = "node"; +const char* PropertyPreview::SubtypeEnum::Regexp = "regexp"; +const char* PropertyPreview::SubtypeEnum::Date = "date"; +const char* PropertyPreview::SubtypeEnum::Map = "map"; +const char* PropertyPreview::SubtypeEnum::Set = "set"; +const char* PropertyPreview::SubtypeEnum::Weakmap = "weakmap"; +const char* PropertyPreview::SubtypeEnum::Weakset = "weakset"; +const char* PropertyPreview::SubtypeEnum::Iterator = "iterator"; +const char* PropertyPreview::SubtypeEnum::Generator = "generator"; +const char* PropertyPreview::SubtypeEnum::Error = "error"; +const char* PropertyPreview::SubtypeEnum::Proxy = "proxy"; +const char* PropertyPreview::SubtypeEnum::Promise = "promise"; +const char* PropertyPreview::SubtypeEnum::Typedarray = "typedarray"; +const char* PropertyPreview::SubtypeEnum::Arraybuffer = "arraybuffer"; +const char* PropertyPreview::SubtypeEnum::Dataview = "dataview"; +const char* PropertyPreview::SubtypeEnum::Webassemblymemory = "webassemblymemory"; +const char* PropertyPreview::SubtypeEnum::Wasmvalue = "wasmvalue"; +V8_CRDTP_BEGIN_DESERIALIZER(PropertyPreview) + V8_CRDTP_DESERIALIZE_FIELD("name", m_name), + V8_CRDTP_DESERIALIZE_FIELD_OPT("subtype", m_subtype), + V8_CRDTP_DESERIALIZE_FIELD("type", m_type), + V8_CRDTP_DESERIALIZE_FIELD_OPT("value", m_value), + V8_CRDTP_DESERIALIZE_FIELD_OPT("valuePreview", m_valuePreview), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(PropertyPreview) + V8_CRDTP_SERIALIZE_FIELD("name", m_name); + V8_CRDTP_SERIALIZE_FIELD("type", m_type); + V8_CRDTP_SERIALIZE_FIELD("value", m_value); + V8_CRDTP_SERIALIZE_FIELD("valuePreview", m_valuePreview); + V8_CRDTP_SERIALIZE_FIELD("subtype", m_subtype); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(EntryPreview) + V8_CRDTP_DESERIALIZE_FIELD_OPT("key", m_key), + V8_CRDTP_DESERIALIZE_FIELD("value", m_value), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(EntryPreview) + V8_CRDTP_SERIALIZE_FIELD("key", m_key); + V8_CRDTP_SERIALIZE_FIELD("value", m_value); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(PropertyDescriptor) + V8_CRDTP_DESERIALIZE_FIELD("configurable", m_configurable), + V8_CRDTP_DESERIALIZE_FIELD("enumerable", m_enumerable), + V8_CRDTP_DESERIALIZE_FIELD_OPT("get", m_get), + V8_CRDTP_DESERIALIZE_FIELD_OPT("isOwn", m_isOwn), + V8_CRDTP_DESERIALIZE_FIELD("name", m_name), + V8_CRDTP_DESERIALIZE_FIELD_OPT("set", m_set), + V8_CRDTP_DESERIALIZE_FIELD_OPT("symbol", m_symbol), + V8_CRDTP_DESERIALIZE_FIELD_OPT("value", m_value), + V8_CRDTP_DESERIALIZE_FIELD_OPT("wasThrown", m_wasThrown), + V8_CRDTP_DESERIALIZE_FIELD_OPT("writable", m_writable), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(PropertyDescriptor) + V8_CRDTP_SERIALIZE_FIELD("name", m_name); + V8_CRDTP_SERIALIZE_FIELD("value", m_value); + V8_CRDTP_SERIALIZE_FIELD("writable", m_writable); + V8_CRDTP_SERIALIZE_FIELD("get", m_get); + V8_CRDTP_SERIALIZE_FIELD("set", m_set); + V8_CRDTP_SERIALIZE_FIELD("configurable", m_configurable); + V8_CRDTP_SERIALIZE_FIELD("enumerable", m_enumerable); + V8_CRDTP_SERIALIZE_FIELD("wasThrown", m_wasThrown); + V8_CRDTP_SERIALIZE_FIELD("isOwn", m_isOwn); + V8_CRDTP_SERIALIZE_FIELD("symbol", m_symbol); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(InternalPropertyDescriptor) + V8_CRDTP_DESERIALIZE_FIELD("name", m_name), + V8_CRDTP_DESERIALIZE_FIELD_OPT("value", m_value), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(InternalPropertyDescriptor) + V8_CRDTP_SERIALIZE_FIELD("name", m_name); + V8_CRDTP_SERIALIZE_FIELD("value", m_value); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(PrivatePropertyDescriptor) + V8_CRDTP_DESERIALIZE_FIELD_OPT("get", m_get), + V8_CRDTP_DESERIALIZE_FIELD("name", m_name), + V8_CRDTP_DESERIALIZE_FIELD_OPT("set", m_set), + V8_CRDTP_DESERIALIZE_FIELD_OPT("value", m_value), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(PrivatePropertyDescriptor) + V8_CRDTP_SERIALIZE_FIELD("name", m_name); + V8_CRDTP_SERIALIZE_FIELD("value", m_value); + V8_CRDTP_SERIALIZE_FIELD("get", m_get); + V8_CRDTP_SERIALIZE_FIELD("set", m_set); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(CallArgument) + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectId", m_objectId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("unserializableValue", m_unserializableValue), + V8_CRDTP_DESERIALIZE_FIELD_OPT("value", m_value), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(CallArgument) + V8_CRDTP_SERIALIZE_FIELD("value", m_value); + V8_CRDTP_SERIALIZE_FIELD("unserializableValue", m_unserializableValue); + V8_CRDTP_SERIALIZE_FIELD("objectId", m_objectId); +V8_CRDTP_END_SERIALIZER(); + + + +V8_CRDTP_BEGIN_DESERIALIZER(ExecutionContextDescription) + V8_CRDTP_DESERIALIZE_FIELD_OPT("auxData", m_auxData), + V8_CRDTP_DESERIALIZE_FIELD("id", m_id), + V8_CRDTP_DESERIALIZE_FIELD("name", m_name), + V8_CRDTP_DESERIALIZE_FIELD("origin", m_origin), + V8_CRDTP_DESERIALIZE_FIELD("uniqueId", m_uniqueId), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(ExecutionContextDescription) + V8_CRDTP_SERIALIZE_FIELD("id", m_id); + V8_CRDTP_SERIALIZE_FIELD("origin", m_origin); + V8_CRDTP_SERIALIZE_FIELD("name", m_name); + V8_CRDTP_SERIALIZE_FIELD("uniqueId", m_uniqueId); + V8_CRDTP_SERIALIZE_FIELD("auxData", m_auxData); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(ExceptionDetails) + V8_CRDTP_DESERIALIZE_FIELD("columnNumber", m_columnNumber), + V8_CRDTP_DESERIALIZE_FIELD_OPT("exception", m_exception), + V8_CRDTP_DESERIALIZE_FIELD("exceptionId", m_exceptionId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("exceptionMetaData", m_exceptionMetaData), + V8_CRDTP_DESERIALIZE_FIELD_OPT("executionContextId", m_executionContextId), + V8_CRDTP_DESERIALIZE_FIELD("lineNumber", m_lineNumber), + V8_CRDTP_DESERIALIZE_FIELD_OPT("scriptId", m_scriptId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("stackTrace", m_stackTrace), + V8_CRDTP_DESERIALIZE_FIELD("text", m_text), + V8_CRDTP_DESERIALIZE_FIELD_OPT("url", m_url), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(ExceptionDetails) + V8_CRDTP_SERIALIZE_FIELD("exceptionId", m_exceptionId); + V8_CRDTP_SERIALIZE_FIELD("text", m_text); + V8_CRDTP_SERIALIZE_FIELD("lineNumber", m_lineNumber); + V8_CRDTP_SERIALIZE_FIELD("columnNumber", m_columnNumber); + V8_CRDTP_SERIALIZE_FIELD("scriptId", m_scriptId); + V8_CRDTP_SERIALIZE_FIELD("url", m_url); + V8_CRDTP_SERIALIZE_FIELD("stackTrace", m_stackTrace); + V8_CRDTP_SERIALIZE_FIELD("exception", m_exception); + V8_CRDTP_SERIALIZE_FIELD("executionContextId", m_executionContextId); + V8_CRDTP_SERIALIZE_FIELD("exceptionMetaData", m_exceptionMetaData); +V8_CRDTP_END_SERIALIZER(); + + + + +V8_CRDTP_BEGIN_DESERIALIZER(CallFrame) + V8_CRDTP_DESERIALIZE_FIELD("columnNumber", m_columnNumber), + V8_CRDTP_DESERIALIZE_FIELD("functionName", m_functionName), + V8_CRDTP_DESERIALIZE_FIELD("lineNumber", m_lineNumber), + V8_CRDTP_DESERIALIZE_FIELD("scriptId", m_scriptId), + V8_CRDTP_DESERIALIZE_FIELD("url", m_url), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(CallFrame) + V8_CRDTP_SERIALIZE_FIELD("functionName", m_functionName); + V8_CRDTP_SERIALIZE_FIELD("scriptId", m_scriptId); + V8_CRDTP_SERIALIZE_FIELD("url", m_url); + V8_CRDTP_SERIALIZE_FIELD("lineNumber", m_lineNumber); + V8_CRDTP_SERIALIZE_FIELD("columnNumber", m_columnNumber); +V8_CRDTP_END_SERIALIZER(); + + +V8_CRDTP_BEGIN_DESERIALIZER(StackTrace) + V8_CRDTP_DESERIALIZE_FIELD("callFrames", m_callFrames), + V8_CRDTP_DESERIALIZE_FIELD_OPT("description", m_description), + V8_CRDTP_DESERIALIZE_FIELD_OPT("parent", m_parent), + V8_CRDTP_DESERIALIZE_FIELD_OPT("parentId", m_parentId), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(StackTrace) + V8_CRDTP_SERIALIZE_FIELD("description", m_description); + V8_CRDTP_SERIALIZE_FIELD("callFrames", m_callFrames); + V8_CRDTP_SERIALIZE_FIELD("parent", m_parent); + V8_CRDTP_SERIALIZE_FIELD("parentId", m_parentId); +V8_CRDTP_END_SERIALIZER(); + +// static +std::unique_ptr API::StackTrace::fromBinary(const uint8_t* data, size_t length) +{ + return protocol::Runtime::StackTrace::FromBinary(data, length); +} + + +V8_CRDTP_BEGIN_DESERIALIZER(StackTraceId) + V8_CRDTP_DESERIALIZE_FIELD_OPT("debuggerId", m_debuggerId), + V8_CRDTP_DESERIALIZE_FIELD("id", m_id), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(StackTraceId) + V8_CRDTP_SERIALIZE_FIELD("id", m_id); + V8_CRDTP_SERIALIZE_FIELD("debuggerId", m_debuggerId); +V8_CRDTP_END_SERIALIZER(); + +// static +std::unique_ptr API::StackTraceId::fromBinary(const uint8_t* data, size_t length) +{ + return protocol::Runtime::StackTraceId::FromBinary(data, length); +} + +// ------------- Enum values from params. + + +namespace ConsoleAPICalled { +namespace TypeEnum { +const char* Log = "log"; +const char* Debug = "debug"; +const char* Info = "info"; +const char* Error = "error"; +const char* Warning = "warning"; +const char* Dir = "dir"; +const char* Dirxml = "dirxml"; +const char* Table = "table"; +const char* Trace = "trace"; +const char* Clear = "clear"; +const char* StartGroup = "startGroup"; +const char* StartGroupCollapsed = "startGroupCollapsed"; +const char* EndGroup = "endGroup"; +const char* Assert = "assert"; +const char* Profile = "profile"; +const char* ProfileEnd = "profileEnd"; +const char* Count = "count"; +const char* TimeEnd = "timeEnd"; +} // namespace TypeEnum +} // namespace ConsoleAPICalled + +// ------------- Frontend notifications. + +void Frontend::bindingCalled(const String& name, const String& payload, int executionContextId) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("name"), name); + serializer.AddField(v8_crdtp::MakeSpan("payload"), payload); + serializer.AddField(v8_crdtp::MakeSpan("executionContextId"), executionContextId); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Runtime.bindingCalled", serializer.Finish())); +} + +void Frontend::consoleAPICalled(const String& type, std::unique_ptr> args, int executionContextId, double timestamp, Maybe stackTrace, Maybe context) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("type"), type); + serializer.AddField(v8_crdtp::MakeSpan("args"), args); + serializer.AddField(v8_crdtp::MakeSpan("executionContextId"), executionContextId); + serializer.AddField(v8_crdtp::MakeSpan("timestamp"), timestamp); + serializer.AddField(v8_crdtp::MakeSpan("stackTrace"), stackTrace); + serializer.AddField(v8_crdtp::MakeSpan("context"), context); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Runtime.consoleAPICalled", serializer.Finish())); +} + +void Frontend::exceptionRevoked(const String& reason, int exceptionId) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("reason"), reason); + serializer.AddField(v8_crdtp::MakeSpan("exceptionId"), exceptionId); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Runtime.exceptionRevoked", serializer.Finish())); +} + +void Frontend::exceptionThrown(double timestamp, std::unique_ptr exceptionDetails) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("timestamp"), timestamp); + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), exceptionDetails); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Runtime.exceptionThrown", serializer.Finish())); +} + +void Frontend::executionContextCreated(std::unique_ptr context) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("context"), context); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Runtime.executionContextCreated", serializer.Finish())); +} + +void Frontend::executionContextDestroyed(int executionContextId, const String& executionContextUniqueId) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("executionContextId"), executionContextId); + serializer.AddField(v8_crdtp::MakeSpan("executionContextUniqueId"), executionContextUniqueId); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Runtime.executionContextDestroyed", serializer.Finish())); +} + +void Frontend::executionContextsCleared() +{ + if (!frontend_channel_) + return; + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Runtime.executionContextsCleared")); +} + +void Frontend::inspectRequested(std::unique_ptr object, std::unique_ptr hints, Maybe executionContextId) +{ + if (!frontend_channel_) + return; + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("object"), object); + serializer.AddField(v8_crdtp::MakeSpan("hints"), hints); + serializer.AddField(v8_crdtp::MakeSpan("executionContextId"), executionContextId); + frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("Runtime.inspectRequested", serializer.Finish())); +} + +void Frontend::flush() +{ + frontend_channel_->FlushProtocolNotifications(); +} + +void Frontend::sendRawNotification(std::unique_ptr notification) +{ + frontend_channel_->SendProtocolNotification(std::move(notification)); +} + +// --------------------- Dispatcher. + +class DomainDispatcherImpl : public protocol::DomainDispatcher { +public: + DomainDispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) + : DomainDispatcher(frontendChannel) + , m_backend(backend) {} + ~DomainDispatcherImpl() override { } + + using CallHandler = void (DomainDispatcherImpl::*)(const v8_crdtp::Dispatchable& dispatchable); + + std::function Dispatch(v8_crdtp::span command_name) override; + + void awaitPromise(const v8_crdtp::Dispatchable& dispatchable); + void callFunctionOn(const v8_crdtp::Dispatchable& dispatchable); + void compileScript(const v8_crdtp::Dispatchable& dispatchable); + void disable(const v8_crdtp::Dispatchable& dispatchable); + void discardConsoleEntries(const v8_crdtp::Dispatchable& dispatchable); + void enable(const v8_crdtp::Dispatchable& dispatchable); + void evaluate(const v8_crdtp::Dispatchable& dispatchable); + void getIsolateId(const v8_crdtp::Dispatchable& dispatchable); + void getHeapUsage(const v8_crdtp::Dispatchable& dispatchable); + void getProperties(const v8_crdtp::Dispatchable& dispatchable); + void globalLexicalScopeNames(const v8_crdtp::Dispatchable& dispatchable); + void queryObjects(const v8_crdtp::Dispatchable& dispatchable); + void releaseObject(const v8_crdtp::Dispatchable& dispatchable); + void releaseObjectGroup(const v8_crdtp::Dispatchable& dispatchable); + void runIfWaitingForDebugger(const v8_crdtp::Dispatchable& dispatchable); + void runScript(const v8_crdtp::Dispatchable& dispatchable); + void setCustomObjectFormatterEnabled(const v8_crdtp::Dispatchable& dispatchable); + void setMaxCallStackSizeToCapture(const v8_crdtp::Dispatchable& dispatchable); + void terminateExecution(const v8_crdtp::Dispatchable& dispatchable); + void addBinding(const v8_crdtp::Dispatchable& dispatchable); + void removeBinding(const v8_crdtp::Dispatchable& dispatchable); + void getExceptionDetails(const v8_crdtp::Dispatchable& dispatchable); + protected: + Backend* m_backend; +}; + +namespace { +// This helper method with a static map of command methods (instance methods +// of DomainDispatcherImpl declared just above) by their name is used immediately below, +// in the DomainDispatcherImpl::Dispatch method. +DomainDispatcherImpl::CallHandler CommandByName(v8_crdtp::span command_name) { + static auto* commands = [](){ + auto* commands = new std::vector, + DomainDispatcherImpl::CallHandler>>{ + { + v8_crdtp::SpanFrom("addBinding"), + &DomainDispatcherImpl::addBinding + }, + { + v8_crdtp::SpanFrom("awaitPromise"), + &DomainDispatcherImpl::awaitPromise + }, + { + v8_crdtp::SpanFrom("callFunctionOn"), + &DomainDispatcherImpl::callFunctionOn + }, + { + v8_crdtp::SpanFrom("compileScript"), + &DomainDispatcherImpl::compileScript + }, + { + v8_crdtp::SpanFrom("disable"), + &DomainDispatcherImpl::disable + }, + { + v8_crdtp::SpanFrom("discardConsoleEntries"), + &DomainDispatcherImpl::discardConsoleEntries + }, + { + v8_crdtp::SpanFrom("enable"), + &DomainDispatcherImpl::enable + }, + { + v8_crdtp::SpanFrom("evaluate"), + &DomainDispatcherImpl::evaluate + }, + { + v8_crdtp::SpanFrom("getExceptionDetails"), + &DomainDispatcherImpl::getExceptionDetails + }, + { + v8_crdtp::SpanFrom("getHeapUsage"), + &DomainDispatcherImpl::getHeapUsage + }, + { + v8_crdtp::SpanFrom("getIsolateId"), + &DomainDispatcherImpl::getIsolateId + }, + { + v8_crdtp::SpanFrom("getProperties"), + &DomainDispatcherImpl::getProperties + }, + { + v8_crdtp::SpanFrom("globalLexicalScopeNames"), + &DomainDispatcherImpl::globalLexicalScopeNames + }, + { + v8_crdtp::SpanFrom("queryObjects"), + &DomainDispatcherImpl::queryObjects + }, + { + v8_crdtp::SpanFrom("releaseObject"), + &DomainDispatcherImpl::releaseObject + }, + { + v8_crdtp::SpanFrom("releaseObjectGroup"), + &DomainDispatcherImpl::releaseObjectGroup + }, + { + v8_crdtp::SpanFrom("removeBinding"), + &DomainDispatcherImpl::removeBinding + }, + { + v8_crdtp::SpanFrom("runIfWaitingForDebugger"), + &DomainDispatcherImpl::runIfWaitingForDebugger + }, + { + v8_crdtp::SpanFrom("runScript"), + &DomainDispatcherImpl::runScript + }, + { + v8_crdtp::SpanFrom("setCustomObjectFormatterEnabled"), + &DomainDispatcherImpl::setCustomObjectFormatterEnabled + }, + { + v8_crdtp::SpanFrom("setMaxCallStackSizeToCapture"), + &DomainDispatcherImpl::setMaxCallStackSizeToCapture + }, + { + v8_crdtp::SpanFrom("terminateExecution"), + &DomainDispatcherImpl::terminateExecution + }, + }; + return commands; + }(); + return v8_crdtp::FindByFirst(*commands, command_name, nullptr); +} +} // namespace + +std::function DomainDispatcherImpl::Dispatch(v8_crdtp::span command_name) { + CallHandler handler = CommandByName(command_name); + if (!handler) return nullptr; + + return [this, handler](const v8_crdtp::Dispatchable& dispatchable) { + (this->*handler)(dispatchable); + }; +} + + +class AwaitPromiseCallbackImpl : public Backend::AwaitPromiseCallback, public DomainDispatcher::Callback { +public: + AwaitPromiseCallbackImpl(std::unique_ptr backendImpl, int callId, v8_crdtp::span message) + : DomainDispatcher::Callback(std::move(backendImpl), callId, +v8_crdtp::SpanFrom("Runtime.awaitPromise"), message) { } + + void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) override + { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), result); + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), exceptionDetails); + sendIfActive(serializer.Finish(), DispatchResponse::Success()); + } + + void fallThrough() override + { + fallThroughIfActive(); + } + + void sendFailure(const DispatchResponse& response) override + { + DCHECK(response.IsError()); + sendIfActive(nullptr, response); + } +}; + +namespace { + +struct awaitPromiseParams : public v8_crdtp::DeserializableProtocolObject { + String promiseObjectId; + Maybe returnByValue; + Maybe generatePreview; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(awaitPromiseParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("generatePreview", generatePreview), + V8_CRDTP_DESERIALIZE_FIELD("promiseObjectId", promiseObjectId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("returnByValue", returnByValue), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::awaitPromise(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + awaitPromiseParams params; + if (!awaitPromiseParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + m_backend->awaitPromise(params.promiseObjectId, std::move(params.returnByValue), std::move(params.generatePreview), std::make_unique(weakPtr(), dispatchable.CallId(), dispatchable.Serialized())); +} + +class CallFunctionOnCallbackImpl : public Backend::CallFunctionOnCallback, public DomainDispatcher::Callback { +public: + CallFunctionOnCallbackImpl(std::unique_ptr backendImpl, int callId, v8_crdtp::span message) + : DomainDispatcher::Callback(std::move(backendImpl), callId, +v8_crdtp::SpanFrom("Runtime.callFunctionOn"), message) { } + + void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) override + { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), result); + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), exceptionDetails); + sendIfActive(serializer.Finish(), DispatchResponse::Success()); + } + + void fallThrough() override + { + fallThroughIfActive(); + } + + void sendFailure(const DispatchResponse& response) override + { + DCHECK(response.IsError()); + sendIfActive(nullptr, response); + } +}; + +namespace { + +struct callFunctionOnParams : public v8_crdtp::DeserializableProtocolObject { + String functionDeclaration; + Maybe objectId; + Maybe> arguments; + Maybe silent; + Maybe returnByValue; + Maybe generatePreview; + Maybe userGesture; + Maybe awaitPromise; + Maybe executionContextId; + Maybe objectGroup; + Maybe throwOnSideEffect; + Maybe uniqueContextId; + Maybe generateWebDriverValue; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(callFunctionOnParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("arguments", arguments), + V8_CRDTP_DESERIALIZE_FIELD_OPT("awaitPromise", awaitPromise), + V8_CRDTP_DESERIALIZE_FIELD_OPT("executionContextId", executionContextId), + V8_CRDTP_DESERIALIZE_FIELD("functionDeclaration", functionDeclaration), + V8_CRDTP_DESERIALIZE_FIELD_OPT("generatePreview", generatePreview), + V8_CRDTP_DESERIALIZE_FIELD_OPT("generateWebDriverValue", generateWebDriverValue), + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectGroup", objectGroup), + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectId", objectId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("returnByValue", returnByValue), + V8_CRDTP_DESERIALIZE_FIELD_OPT("silent", silent), + V8_CRDTP_DESERIALIZE_FIELD_OPT("throwOnSideEffect", throwOnSideEffect), + V8_CRDTP_DESERIALIZE_FIELD_OPT("uniqueContextId", uniqueContextId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("userGesture", userGesture), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::callFunctionOn(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + callFunctionOnParams params; + if (!callFunctionOnParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + m_backend->callFunctionOn(params.functionDeclaration, std::move(params.objectId), std::move(params.arguments), std::move(params.silent), std::move(params.returnByValue), std::move(params.generatePreview), std::move(params.userGesture), std::move(params.awaitPromise), std::move(params.executionContextId), std::move(params.objectGroup), std::move(params.throwOnSideEffect), std::move(params.uniqueContextId), std::move(params.generateWebDriverValue), std::make_unique(weakPtr(), dispatchable.CallId(), dispatchable.Serialized())); +} + +namespace { + +struct compileScriptParams : public v8_crdtp::DeserializableProtocolObject { + String expression; + String sourceURL; + bool persistScript; + Maybe executionContextId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(compileScriptParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("executionContextId", executionContextId), + V8_CRDTP_DESERIALIZE_FIELD("expression", expression), + V8_CRDTP_DESERIALIZE_FIELD("persistScript", persistScript), + V8_CRDTP_DESERIALIZE_FIELD("sourceURL", sourceURL), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::compileScript(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + compileScriptParams params; + if (!compileScriptParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + Maybe out_scriptId; + Maybe out_exceptionDetails; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->compileScript(params.expression, params.sourceURL, params.persistScript, std::move(params.executionContextId), &out_scriptId, &out_exceptionDetails); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.compileScript"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("scriptId"), out_scriptId); + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), out_exceptionDetails); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::disable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->disable(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.disable"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::discardConsoleEntries(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->discardConsoleEntries(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.discardConsoleEntries"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::enable(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->enable(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.enable"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +class EvaluateCallbackImpl : public Backend::EvaluateCallback, public DomainDispatcher::Callback { +public: + EvaluateCallbackImpl(std::unique_ptr backendImpl, int callId, v8_crdtp::span message) + : DomainDispatcher::Callback(std::move(backendImpl), callId, +v8_crdtp::SpanFrom("Runtime.evaluate"), message) { } + + void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) override + { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), result); + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), exceptionDetails); + sendIfActive(serializer.Finish(), DispatchResponse::Success()); + } + + void fallThrough() override + { + fallThroughIfActive(); + } + + void sendFailure(const DispatchResponse& response) override + { + DCHECK(response.IsError()); + sendIfActive(nullptr, response); + } +}; + +namespace { + +struct evaluateParams : public v8_crdtp::DeserializableProtocolObject { + String expression; + Maybe objectGroup; + Maybe includeCommandLineAPI; + Maybe silent; + Maybe contextId; + Maybe returnByValue; + Maybe generatePreview; + Maybe userGesture; + Maybe awaitPromise; + Maybe throwOnSideEffect; + Maybe timeout; + Maybe disableBreaks; + Maybe replMode; + Maybe allowUnsafeEvalBlockedByCSP; + Maybe uniqueContextId; + Maybe generateWebDriverValue; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(evaluateParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("allowUnsafeEvalBlockedByCSP", allowUnsafeEvalBlockedByCSP), + V8_CRDTP_DESERIALIZE_FIELD_OPT("awaitPromise", awaitPromise), + V8_CRDTP_DESERIALIZE_FIELD_OPT("contextId", contextId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("disableBreaks", disableBreaks), + V8_CRDTP_DESERIALIZE_FIELD("expression", expression), + V8_CRDTP_DESERIALIZE_FIELD_OPT("generatePreview", generatePreview), + V8_CRDTP_DESERIALIZE_FIELD_OPT("generateWebDriverValue", generateWebDriverValue), + V8_CRDTP_DESERIALIZE_FIELD_OPT("includeCommandLineAPI", includeCommandLineAPI), + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectGroup", objectGroup), + V8_CRDTP_DESERIALIZE_FIELD_OPT("replMode", replMode), + V8_CRDTP_DESERIALIZE_FIELD_OPT("returnByValue", returnByValue), + V8_CRDTP_DESERIALIZE_FIELD_OPT("silent", silent), + V8_CRDTP_DESERIALIZE_FIELD_OPT("throwOnSideEffect", throwOnSideEffect), + V8_CRDTP_DESERIALIZE_FIELD_OPT("timeout", timeout), + V8_CRDTP_DESERIALIZE_FIELD_OPT("uniqueContextId", uniqueContextId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("userGesture", userGesture), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::evaluate(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + evaluateParams params; + if (!evaluateParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + m_backend->evaluate(params.expression, std::move(params.objectGroup), std::move(params.includeCommandLineAPI), std::move(params.silent), std::move(params.contextId), std::move(params.returnByValue), std::move(params.generatePreview), std::move(params.userGesture), std::move(params.awaitPromise), std::move(params.throwOnSideEffect), std::move(params.timeout), std::move(params.disableBreaks), std::move(params.replMode), std::move(params.allowUnsafeEvalBlockedByCSP), std::move(params.uniqueContextId), std::move(params.generateWebDriverValue), std::make_unique(weakPtr(), dispatchable.CallId(), dispatchable.Serialized())); +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::getIsolateId(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + // Declare output parameters. + String out_id; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getIsolateId(&out_id); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.getIsolateId"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("id"), out_id); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::getHeapUsage(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + // Declare output parameters. + double out_usedSize; + double out_totalSize; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getHeapUsage(&out_usedSize, &out_totalSize); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.getHeapUsage"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("usedSize"), out_usedSize); + serializer.AddField(v8_crdtp::MakeSpan("totalSize"), out_totalSize); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct getPropertiesParams : public v8_crdtp::DeserializableProtocolObject { + String objectId; + Maybe ownProperties; + Maybe accessorPropertiesOnly; + Maybe generatePreview; + Maybe nonIndexedPropertiesOnly; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(getPropertiesParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("accessorPropertiesOnly", accessorPropertiesOnly), + V8_CRDTP_DESERIALIZE_FIELD_OPT("generatePreview", generatePreview), + V8_CRDTP_DESERIALIZE_FIELD_OPT("nonIndexedPropertiesOnly", nonIndexedPropertiesOnly), + V8_CRDTP_DESERIALIZE_FIELD("objectId", objectId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("ownProperties", ownProperties), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::getProperties(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + getPropertiesParams params; + if (!getPropertiesParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr> out_result; + Maybe> out_internalProperties; + Maybe> out_privateProperties; + Maybe out_exceptionDetails; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getProperties(params.objectId, std::move(params.ownProperties), std::move(params.accessorPropertiesOnly), std::move(params.generatePreview), std::move(params.nonIndexedPropertiesOnly), &out_result, &out_internalProperties, &out_privateProperties, &out_exceptionDetails); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.getProperties"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), out_result); + serializer.AddField(v8_crdtp::MakeSpan("internalProperties"), out_internalProperties); + serializer.AddField(v8_crdtp::MakeSpan("privateProperties"), out_privateProperties); + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), out_exceptionDetails); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct globalLexicalScopeNamesParams : public v8_crdtp::DeserializableProtocolObject { + Maybe executionContextId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(globalLexicalScopeNamesParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("executionContextId", executionContextId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::globalLexicalScopeNames(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + globalLexicalScopeNamesParams params; + if (!globalLexicalScopeNamesParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr> out_names; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->globalLexicalScopeNames(std::move(params.executionContextId), &out_names); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.globalLexicalScopeNames"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("names"), out_names); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct queryObjectsParams : public v8_crdtp::DeserializableProtocolObject { + String prototypeObjectId; + Maybe objectGroup; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(queryObjectsParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectGroup", objectGroup), + V8_CRDTP_DESERIALIZE_FIELD("prototypeObjectId", prototypeObjectId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::queryObjects(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + queryObjectsParams params; + if (!queryObjectsParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + std::unique_ptr out_objects; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->queryObjects(params.prototypeObjectId, std::move(params.objectGroup), &out_objects); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.queryObjects"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("objects"), out_objects); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { + +struct releaseObjectParams : public v8_crdtp::DeserializableProtocolObject { + String objectId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(releaseObjectParams) + V8_CRDTP_DESERIALIZE_FIELD("objectId", objectId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::releaseObject(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + releaseObjectParams params; + if (!releaseObjectParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->releaseObject(params.objectId); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.releaseObject"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct releaseObjectGroupParams : public v8_crdtp::DeserializableProtocolObject { + String objectGroup; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(releaseObjectGroupParams) + V8_CRDTP_DESERIALIZE_FIELD("objectGroup", objectGroup), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::releaseObjectGroup(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + releaseObjectGroupParams params; + if (!releaseObjectGroupParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->releaseObjectGroup(params.objectGroup); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.releaseObjectGroup"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + + +} // namespace + +void DomainDispatcherImpl::runIfWaitingForDebugger(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->runIfWaitingForDebugger(); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.runIfWaitingForDebugger"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +class RunScriptCallbackImpl : public Backend::RunScriptCallback, public DomainDispatcher::Callback { +public: + RunScriptCallbackImpl(std::unique_ptr backendImpl, int callId, v8_crdtp::span message) + : DomainDispatcher::Callback(std::move(backendImpl), callId, +v8_crdtp::SpanFrom("Runtime.runScript"), message) { } + + void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) override + { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("result"), result); + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), exceptionDetails); + sendIfActive(serializer.Finish(), DispatchResponse::Success()); + } + + void fallThrough() override + { + fallThroughIfActive(); + } + + void sendFailure(const DispatchResponse& response) override + { + DCHECK(response.IsError()); + sendIfActive(nullptr, response); + } +}; + +namespace { + +struct runScriptParams : public v8_crdtp::DeserializableProtocolObject { + String scriptId; + Maybe executionContextId; + Maybe objectGroup; + Maybe silent; + Maybe includeCommandLineAPI; + Maybe returnByValue; + Maybe generatePreview; + Maybe awaitPromise; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(runScriptParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("awaitPromise", awaitPromise), + V8_CRDTP_DESERIALIZE_FIELD_OPT("executionContextId", executionContextId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("generatePreview", generatePreview), + V8_CRDTP_DESERIALIZE_FIELD_OPT("includeCommandLineAPI", includeCommandLineAPI), + V8_CRDTP_DESERIALIZE_FIELD_OPT("objectGroup", objectGroup), + V8_CRDTP_DESERIALIZE_FIELD_OPT("returnByValue", returnByValue), + V8_CRDTP_DESERIALIZE_FIELD("scriptId", scriptId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("silent", silent), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::runScript(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + runScriptParams params; + if (!runScriptParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + m_backend->runScript(params.scriptId, std::move(params.executionContextId), std::move(params.objectGroup), std::move(params.silent), std::move(params.includeCommandLineAPI), std::move(params.returnByValue), std::move(params.generatePreview), std::move(params.awaitPromise), std::make_unique(weakPtr(), dispatchable.CallId(), dispatchable.Serialized())); +} + +namespace { + +struct setCustomObjectFormatterEnabledParams : public v8_crdtp::DeserializableProtocolObject { + bool enabled; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setCustomObjectFormatterEnabledParams) + V8_CRDTP_DESERIALIZE_FIELD("enabled", enabled), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setCustomObjectFormatterEnabled(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setCustomObjectFormatterEnabledParams params; + if (!setCustomObjectFormatterEnabledParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setCustomObjectFormatterEnabled(params.enabled); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.setCustomObjectFormatterEnabled"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct setMaxCallStackSizeToCaptureParams : public v8_crdtp::DeserializableProtocolObject { + int size; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(setMaxCallStackSizeToCaptureParams) + V8_CRDTP_DESERIALIZE_FIELD("size", size), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::setMaxCallStackSizeToCapture(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + setMaxCallStackSizeToCaptureParams params; + if (!setMaxCallStackSizeToCaptureParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->setMaxCallStackSizeToCapture(params.size); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.setMaxCallStackSizeToCapture"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +class TerminateExecutionCallbackImpl : public Backend::TerminateExecutionCallback, public DomainDispatcher::Callback { +public: + TerminateExecutionCallbackImpl(std::unique_ptr backendImpl, int callId, v8_crdtp::span message) + : DomainDispatcher::Callback(std::move(backendImpl), callId, +v8_crdtp::SpanFrom("Runtime.terminateExecution"), message) { } + + void sendSuccess() override + { + v8_crdtp::ObjectSerializer serializer; + sendIfActive(serializer.Finish(), DispatchResponse::Success()); + } + + void fallThrough() override + { + fallThroughIfActive(); + } + + void sendFailure(const DispatchResponse& response) override + { + DCHECK(response.IsError()); + sendIfActive(nullptr, response); + } +}; + +namespace { + + +} // namespace + +void DomainDispatcherImpl::terminateExecution(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + + m_backend->terminateExecution(std::make_unique(weakPtr(), dispatchable.CallId(), dispatchable.Serialized())); +} + +namespace { + +struct addBindingParams : public v8_crdtp::DeserializableProtocolObject { + String name; + Maybe executionContextId; + Maybe executionContextName; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(addBindingParams) + V8_CRDTP_DESERIALIZE_FIELD_OPT("executionContextId", executionContextId), + V8_CRDTP_DESERIALIZE_FIELD_OPT("executionContextName", executionContextName), + V8_CRDTP_DESERIALIZE_FIELD("name", name), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::addBinding(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + addBindingParams params; + if (!addBindingParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->addBinding(params.name, std::move(params.executionContextId), std::move(params.executionContextName)); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.addBinding"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct removeBindingParams : public v8_crdtp::DeserializableProtocolObject { + String name; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(removeBindingParams) + V8_CRDTP_DESERIALIZE_FIELD("name", name), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::removeBinding(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + removeBindingParams params; + if (!removeBindingParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->removeBinding(params.name); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.removeBinding"), dispatchable.Serialized()); + return; + } + if (weak->get()) + weak->get()->sendResponse(dispatchable.CallId(), response); + return; +} + +namespace { + +struct getExceptionDetailsParams : public v8_crdtp::DeserializableProtocolObject { + String errorObjectId; + DECLARE_DESERIALIZATION_SUPPORT(); +}; + +V8_CRDTP_BEGIN_DESERIALIZER(getExceptionDetailsParams) + V8_CRDTP_DESERIALIZE_FIELD("errorObjectId", errorObjectId), +V8_CRDTP_END_DESERIALIZER() + +} // namespace + +void DomainDispatcherImpl::getExceptionDetails(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + auto deserializer = v8_crdtp::DeferredMessage::FromSpan(dispatchable.Params())->MakeDeserializer(); + getExceptionDetailsParams params; + if (!getExceptionDetailsParams::Deserialize(&deserializer, ¶ms)) { + ReportInvalidParams(dispatchable, deserializer); + return; + } + // Declare output parameters. + Maybe out_exceptionDetails; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getExceptionDetails(params.errorObjectId, &out_exceptionDetails); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Runtime.getExceptionDetails"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("exceptionDetails"), out_exceptionDetails); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { +// This helper method (with a static map of redirects) is used from Dispatcher::wire +// immediately below. +const std::vector, v8_crdtp::span>>& SortedRedirects() { + static auto* redirects = [](){ + auto* redirects = new std::vector, v8_crdtp::span>>{ + { v8_crdtp::SpanFrom("Runtime.setAsyncCallStackDepth"), v8_crdtp::SpanFrom("Debugger.setAsyncCallStackDepth") }, + }; + return redirects; + }(); + return *redirects; +} +} // namespace + +// static +void Dispatcher::wire(UberDispatcher* uber, Backend* backend) +{ + auto dispatcher = std::make_unique(uber->channel(), backend); + uber->WireBackend(v8_crdtp::SpanFrom("Runtime"), SortedRedirects(), std::move(dispatcher)); +} + +} // Runtime +} // namespace v8_inspector +} // namespace protocol diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Runtime.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Runtime.h new file mode 100644 index 000000000..86efaff65 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Runtime.h @@ -0,0 +1,1825 @@ +// This file is generated by TypeBuilder_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Runtime_h +#define v8_inspector_protocol_Runtime_h + +#include "src/inspector/protocol/Protocol.h" +// For each imported domain we generate a ValueConversions struct instead of a full domain definition +// and include Domain::API version from there. +#include "include/inspector/Runtime.h" + +namespace v8_inspector { +namespace protocol { +namespace Runtime { +using ScriptId = String; +class WebDriverValue; +using RemoteObjectId = String; +using UnserializableValue = String; +class RemoteObject; +class CustomPreview; +class ObjectPreview; +class PropertyPreview; +class EntryPreview; +class PropertyDescriptor; +class InternalPropertyDescriptor; +class PrivatePropertyDescriptor; +class CallArgument; +using ExecutionContextId = int; +class ExecutionContextDescription; +class ExceptionDetails; +using Timestamp = double; +using TimeDelta = double; +class CallFrame; +class StackTrace; +using UniqueDebuggerId = String; +class StackTraceId; + +// ------------- Forward and enum declarations. + +namespace ConsoleAPICalled { +namespace TypeEnum { + extern const char* Log; + extern const char* Debug; + extern const char* Info; + extern const char* Error; + extern const char* Warning; + extern const char* Dir; + extern const char* Dirxml; + extern const char* Table; + extern const char* Trace; + extern const char* Clear; + extern const char* StartGroup; + extern const char* StartGroupCollapsed; + extern const char* EndGroup; + extern const char* Assert; + extern const char* Profile; + extern const char* ProfileEnd; + extern const char* Count; + extern const char* TimeEnd; +} // TypeEnum +} // ConsoleAPICalled + +// ------------- Type and builder declarations. + +class WebDriverValue : public ::v8_crdtp::ProtocolObject { +public: + ~WebDriverValue() override { } + + struct TypeEnum { + static const char* Undefined; + static const char* Null; + static const char* String; + static const char* Number; + static const char* Boolean; + static const char* Bigint; + static const char* Regexp; + static const char* Date; + static const char* Symbol; + static const char* Array; + static const char* Object; + static const char* Function; + static const char* Map; + static const char* Set; + static const char* Weakmap; + static const char* Weakset; + static const char* Error; + static const char* Proxy; + static const char* Promise; + static const char* Typedarray; + static const char* Arraybuffer; + static const char* Node; + static const char* Window; + }; // TypeEnum + + String getType() { return m_type; } + void setType(const String& value) { m_type = value; } + + bool hasValue() { return m_value.isJust(); } + protocol::Value* getValue(protocol::Value* defaultValue) { return m_value.isJust() ? m_value.fromJust() : defaultValue; } + void setValue(std::unique_ptr value) { m_value = std::move(value); } + + bool hasObjectId() { return m_objectId.isJust(); } + String getObjectId(const String& defaultValue) { return m_objectId.isJust() ? m_objectId.fromJust() : defaultValue; } + void setObjectId(const String& value) { m_objectId = value; } + + template + class WebDriverValueBuilder { + public: + enum { + NoFieldsSet = 0, + TypeSet = 1 << 1, + AllFieldsSet = (TypeSet | 0)}; + + + WebDriverValueBuilder& setType(const String& value) + { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + WebDriverValueBuilder& setValue(std::unique_ptr value) + { + m_result->setValue(std::move(value)); + return *this; + } + + WebDriverValueBuilder& setObjectId(const String& value) + { + m_result->setObjectId(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class WebDriverValue; + WebDriverValueBuilder() : m_result(new WebDriverValue()) { } + + template WebDriverValueBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static WebDriverValueBuilder<0> create() + { + return WebDriverValueBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + WebDriverValue() + { + } + + String m_type; + Maybe m_value; + Maybe m_objectId; +}; + + +class RemoteObject : public ::v8_crdtp::ProtocolObject, + public API::RemoteObject { +public: + ~RemoteObject() override { } + + struct TypeEnum { + static const char* Object; + static const char* Function; + static const char* Undefined; + static const char* String; + static const char* Number; + static const char* Boolean; + static const char* Symbol; + static const char* Bigint; + }; // TypeEnum + + String getType() { return m_type; } + void setType(const String& value) { m_type = value; } + + struct SubtypeEnum { + static const char* Array; + static const char* Null; + static const char* Node; + static const char* Regexp; + static const char* Date; + static const char* Map; + static const char* Set; + static const char* Weakmap; + static const char* Weakset; + static const char* Iterator; + static const char* Generator; + static const char* Error; + static const char* Proxy; + static const char* Promise; + static const char* Typedarray; + static const char* Arraybuffer; + static const char* Dataview; + static const char* Webassemblymemory; + static const char* Wasmvalue; + }; // SubtypeEnum + + bool hasSubtype() { return m_subtype.isJust(); } + String getSubtype(const String& defaultValue) { return m_subtype.isJust() ? m_subtype.fromJust() : defaultValue; } + void setSubtype(const String& value) { m_subtype = value; } + + bool hasClassName() { return m_className.isJust(); } + String getClassName(const String& defaultValue) { return m_className.isJust() ? m_className.fromJust() : defaultValue; } + void setClassName(const String& value) { m_className = value; } + + bool hasValue() { return m_value.isJust(); } + protocol::Value* getValue(protocol::Value* defaultValue) { return m_value.isJust() ? m_value.fromJust() : defaultValue; } + void setValue(std::unique_ptr value) { m_value = std::move(value); } + + bool hasUnserializableValue() { return m_unserializableValue.isJust(); } + String getUnserializableValue(const String& defaultValue) { return m_unserializableValue.isJust() ? m_unserializableValue.fromJust() : defaultValue; } + void setUnserializableValue(const String& value) { m_unserializableValue = value; } + + bool hasDescription() { return m_description.isJust(); } + String getDescription(const String& defaultValue) { return m_description.isJust() ? m_description.fromJust() : defaultValue; } + void setDescription(const String& value) { m_description = value; } + + bool hasWebDriverValue() { return m_webDriverValue.isJust(); } + protocol::Runtime::WebDriverValue* getWebDriverValue(protocol::Runtime::WebDriverValue* defaultValue) { return m_webDriverValue.isJust() ? m_webDriverValue.fromJust() : defaultValue; } + void setWebDriverValue(std::unique_ptr value) { m_webDriverValue = std::move(value); } + + bool hasObjectId() { return m_objectId.isJust(); } + String getObjectId(const String& defaultValue) { return m_objectId.isJust() ? m_objectId.fromJust() : defaultValue; } + void setObjectId(const String& value) { m_objectId = value; } + + bool hasPreview() { return m_preview.isJust(); } + protocol::Runtime::ObjectPreview* getPreview(protocol::Runtime::ObjectPreview* defaultValue) { return m_preview.isJust() ? m_preview.fromJust() : defaultValue; } + void setPreview(std::unique_ptr value) { m_preview = std::move(value); } + + bool hasCustomPreview() { return m_customPreview.isJust(); } + protocol::Runtime::CustomPreview* getCustomPreview(protocol::Runtime::CustomPreview* defaultValue) { return m_customPreview.isJust() ? m_customPreview.fromJust() : defaultValue; } + void setCustomPreview(std::unique_ptr value) { m_customPreview = std::move(value); } + + template + class RemoteObjectBuilder { + public: + enum { + NoFieldsSet = 0, + TypeSet = 1 << 1, + AllFieldsSet = (TypeSet | 0)}; + + + RemoteObjectBuilder& setType(const String& value) + { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + RemoteObjectBuilder& setSubtype(const String& value) + { + m_result->setSubtype(value); + return *this; + } + + RemoteObjectBuilder& setClassName(const String& value) + { + m_result->setClassName(value); + return *this; + } + + RemoteObjectBuilder& setValue(std::unique_ptr value) + { + m_result->setValue(std::move(value)); + return *this; + } + + RemoteObjectBuilder& setUnserializableValue(const String& value) + { + m_result->setUnserializableValue(value); + return *this; + } + + RemoteObjectBuilder& setDescription(const String& value) + { + m_result->setDescription(value); + return *this; + } + + RemoteObjectBuilder& setWebDriverValue(std::unique_ptr value) + { + m_result->setWebDriverValue(std::move(value)); + return *this; + } + + RemoteObjectBuilder& setObjectId(const String& value) + { + m_result->setObjectId(value); + return *this; + } + + RemoteObjectBuilder& setPreview(std::unique_ptr value) + { + m_result->setPreview(std::move(value)); + return *this; + } + + RemoteObjectBuilder& setCustomPreview(std::unique_ptr value) + { + m_result->setCustomPreview(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class RemoteObject; + RemoteObjectBuilder() : m_result(new RemoteObject()) { } + + template RemoteObjectBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static RemoteObjectBuilder<0> create() + { + return RemoteObjectBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + RemoteObject() + { + } + + String m_type; + Maybe m_subtype; + Maybe m_className; + Maybe m_value; + Maybe m_unserializableValue; + Maybe m_description; + Maybe m_webDriverValue; + Maybe m_objectId; + Maybe m_preview; + Maybe m_customPreview; +}; + + +class CustomPreview : public ::v8_crdtp::ProtocolObject { +public: + ~CustomPreview() override { } + + String getHeader() { return m_header; } + void setHeader(const String& value) { m_header = value; } + + bool hasBodyGetterId() { return m_bodyGetterId.isJust(); } + String getBodyGetterId(const String& defaultValue) { return m_bodyGetterId.isJust() ? m_bodyGetterId.fromJust() : defaultValue; } + void setBodyGetterId(const String& value) { m_bodyGetterId = value; } + + template + class CustomPreviewBuilder { + public: + enum { + NoFieldsSet = 0, + HeaderSet = 1 << 1, + AllFieldsSet = (HeaderSet | 0)}; + + + CustomPreviewBuilder& setHeader(const String& value) + { + static_assert(!(STATE & HeaderSet), "property header should not be set yet"); + m_result->setHeader(value); + return castState(); + } + + CustomPreviewBuilder& setBodyGetterId(const String& value) + { + m_result->setBodyGetterId(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class CustomPreview; + CustomPreviewBuilder() : m_result(new CustomPreview()) { } + + template CustomPreviewBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static CustomPreviewBuilder<0> create() + { + return CustomPreviewBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + CustomPreview() + { + } + + String m_header; + Maybe m_bodyGetterId; +}; + + +class ObjectPreview : public ::v8_crdtp::ProtocolObject { +public: + ~ObjectPreview() override { } + + struct TypeEnum { + static const char* Object; + static const char* Function; + static const char* Undefined; + static const char* String; + static const char* Number; + static const char* Boolean; + static const char* Symbol; + static const char* Bigint; + }; // TypeEnum + + String getType() { return m_type; } + void setType(const String& value) { m_type = value; } + + struct SubtypeEnum { + static const char* Array; + static const char* Null; + static const char* Node; + static const char* Regexp; + static const char* Date; + static const char* Map; + static const char* Set; + static const char* Weakmap; + static const char* Weakset; + static const char* Iterator; + static const char* Generator; + static const char* Error; + static const char* Proxy; + static const char* Promise; + static const char* Typedarray; + static const char* Arraybuffer; + static const char* Dataview; + static const char* Webassemblymemory; + static const char* Wasmvalue; + }; // SubtypeEnum + + bool hasSubtype() { return m_subtype.isJust(); } + String getSubtype(const String& defaultValue) { return m_subtype.isJust() ? m_subtype.fromJust() : defaultValue; } + void setSubtype(const String& value) { m_subtype = value; } + + bool hasDescription() { return m_description.isJust(); } + String getDescription(const String& defaultValue) { return m_description.isJust() ? m_description.fromJust() : defaultValue; } + void setDescription(const String& value) { m_description = value; } + + bool getOverflow() { return m_overflow; } + void setOverflow(bool value) { m_overflow = value; } + + protocol::Array* getProperties() { return m_properties.get(); } + void setProperties(std::unique_ptr> value) { m_properties = std::move(value); } + + bool hasEntries() { return m_entries.isJust(); } + protocol::Array* getEntries(protocol::Array* defaultValue) { return m_entries.isJust() ? m_entries.fromJust() : defaultValue; } + void setEntries(std::unique_ptr> value) { m_entries = std::move(value); } + + template + class ObjectPreviewBuilder { + public: + enum { + NoFieldsSet = 0, + TypeSet = 1 << 1, + OverflowSet = 1 << 2, + PropertiesSet = 1 << 3, + AllFieldsSet = (TypeSet | OverflowSet | PropertiesSet | 0)}; + + + ObjectPreviewBuilder& setType(const String& value) + { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + ObjectPreviewBuilder& setSubtype(const String& value) + { + m_result->setSubtype(value); + return *this; + } + + ObjectPreviewBuilder& setDescription(const String& value) + { + m_result->setDescription(value); + return *this; + } + + ObjectPreviewBuilder& setOverflow(bool value) + { + static_assert(!(STATE & OverflowSet), "property overflow should not be set yet"); + m_result->setOverflow(value); + return castState(); + } + + ObjectPreviewBuilder& setProperties(std::unique_ptr> value) + { + static_assert(!(STATE & PropertiesSet), "property properties should not be set yet"); + m_result->setProperties(std::move(value)); + return castState(); + } + + ObjectPreviewBuilder& setEntries(std::unique_ptr> value) + { + m_result->setEntries(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ObjectPreview; + ObjectPreviewBuilder() : m_result(new ObjectPreview()) { } + + template ObjectPreviewBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static ObjectPreviewBuilder<0> create() + { + return ObjectPreviewBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + ObjectPreview() + { + m_overflow = false; + } + + String m_type; + Maybe m_subtype; + Maybe m_description; + bool m_overflow; + std::unique_ptr> m_properties; + Maybe> m_entries; +}; + + +class PropertyPreview : public ::v8_crdtp::ProtocolObject { +public: + ~PropertyPreview() override { } + + String getName() { return m_name; } + void setName(const String& value) { m_name = value; } + + struct TypeEnum { + static const char* Object; + static const char* Function; + static const char* Undefined; + static const char* String; + static const char* Number; + static const char* Boolean; + static const char* Symbol; + static const char* Accessor; + static const char* Bigint; + }; // TypeEnum + + String getType() { return m_type; } + void setType(const String& value) { m_type = value; } + + bool hasValue() { return m_value.isJust(); } + String getValue(const String& defaultValue) { return m_value.isJust() ? m_value.fromJust() : defaultValue; } + void setValue(const String& value) { m_value = value; } + + bool hasValuePreview() { return m_valuePreview.isJust(); } + protocol::Runtime::ObjectPreview* getValuePreview(protocol::Runtime::ObjectPreview* defaultValue) { return m_valuePreview.isJust() ? m_valuePreview.fromJust() : defaultValue; } + void setValuePreview(std::unique_ptr value) { m_valuePreview = std::move(value); } + + struct SubtypeEnum { + static const char* Array; + static const char* Null; + static const char* Node; + static const char* Regexp; + static const char* Date; + static const char* Map; + static const char* Set; + static const char* Weakmap; + static const char* Weakset; + static const char* Iterator; + static const char* Generator; + static const char* Error; + static const char* Proxy; + static const char* Promise; + static const char* Typedarray; + static const char* Arraybuffer; + static const char* Dataview; + static const char* Webassemblymemory; + static const char* Wasmvalue; + }; // SubtypeEnum + + bool hasSubtype() { return m_subtype.isJust(); } + String getSubtype(const String& defaultValue) { return m_subtype.isJust() ? m_subtype.fromJust() : defaultValue; } + void setSubtype(const String& value) { m_subtype = value; } + + template + class PropertyPreviewBuilder { + public: + enum { + NoFieldsSet = 0, + NameSet = 1 << 1, + TypeSet = 1 << 2, + AllFieldsSet = (NameSet | TypeSet | 0)}; + + + PropertyPreviewBuilder& setName(const String& value) + { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + PropertyPreviewBuilder& setType(const String& value) + { + static_assert(!(STATE & TypeSet), "property type should not be set yet"); + m_result->setType(value); + return castState(); + } + + PropertyPreviewBuilder& setValue(const String& value) + { + m_result->setValue(value); + return *this; + } + + PropertyPreviewBuilder& setValuePreview(std::unique_ptr value) + { + m_result->setValuePreview(std::move(value)); + return *this; + } + + PropertyPreviewBuilder& setSubtype(const String& value) + { + m_result->setSubtype(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class PropertyPreview; + PropertyPreviewBuilder() : m_result(new PropertyPreview()) { } + + template PropertyPreviewBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static PropertyPreviewBuilder<0> create() + { + return PropertyPreviewBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + PropertyPreview() + { + } + + String m_name; + String m_type; + Maybe m_value; + Maybe m_valuePreview; + Maybe m_subtype; +}; + + +class EntryPreview : public ::v8_crdtp::ProtocolObject { +public: + ~EntryPreview() override { } + + bool hasKey() { return m_key.isJust(); } + protocol::Runtime::ObjectPreview* getKey(protocol::Runtime::ObjectPreview* defaultValue) { return m_key.isJust() ? m_key.fromJust() : defaultValue; } + void setKey(std::unique_ptr value) { m_key = std::move(value); } + + protocol::Runtime::ObjectPreview* getValue() { return m_value.get(); } + void setValue(std::unique_ptr value) { m_value = std::move(value); } + + template + class EntryPreviewBuilder { + public: + enum { + NoFieldsSet = 0, + ValueSet = 1 << 1, + AllFieldsSet = (ValueSet | 0)}; + + + EntryPreviewBuilder& setKey(std::unique_ptr value) + { + m_result->setKey(std::move(value)); + return *this; + } + + EntryPreviewBuilder& setValue(std::unique_ptr value) + { + static_assert(!(STATE & ValueSet), "property value should not be set yet"); + m_result->setValue(std::move(value)); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class EntryPreview; + EntryPreviewBuilder() : m_result(new EntryPreview()) { } + + template EntryPreviewBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static EntryPreviewBuilder<0> create() + { + return EntryPreviewBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + EntryPreview() + { + } + + Maybe m_key; + std::unique_ptr m_value; +}; + + +class PropertyDescriptor : public ::v8_crdtp::ProtocolObject { +public: + ~PropertyDescriptor() override { } + + String getName() { return m_name; } + void setName(const String& value) { m_name = value; } + + bool hasValue() { return m_value.isJust(); } + protocol::Runtime::RemoteObject* getValue(protocol::Runtime::RemoteObject* defaultValue) { return m_value.isJust() ? m_value.fromJust() : defaultValue; } + void setValue(std::unique_ptr value) { m_value = std::move(value); } + + bool hasWritable() { return m_writable.isJust(); } + bool getWritable(bool defaultValue) { return m_writable.isJust() ? m_writable.fromJust() : defaultValue; } + void setWritable(bool value) { m_writable = value; } + + bool hasGet() { return m_get.isJust(); } + protocol::Runtime::RemoteObject* getGet(protocol::Runtime::RemoteObject* defaultValue) { return m_get.isJust() ? m_get.fromJust() : defaultValue; } + void setGet(std::unique_ptr value) { m_get = std::move(value); } + + bool hasSet() { return m_set.isJust(); } + protocol::Runtime::RemoteObject* getSet(protocol::Runtime::RemoteObject* defaultValue) { return m_set.isJust() ? m_set.fromJust() : defaultValue; } + void setSet(std::unique_ptr value) { m_set = std::move(value); } + + bool getConfigurable() { return m_configurable; } + void setConfigurable(bool value) { m_configurable = value; } + + bool getEnumerable() { return m_enumerable; } + void setEnumerable(bool value) { m_enumerable = value; } + + bool hasWasThrown() { return m_wasThrown.isJust(); } + bool getWasThrown(bool defaultValue) { return m_wasThrown.isJust() ? m_wasThrown.fromJust() : defaultValue; } + void setWasThrown(bool value) { m_wasThrown = value; } + + bool hasIsOwn() { return m_isOwn.isJust(); } + bool getIsOwn(bool defaultValue) { return m_isOwn.isJust() ? m_isOwn.fromJust() : defaultValue; } + void setIsOwn(bool value) { m_isOwn = value; } + + bool hasSymbol() { return m_symbol.isJust(); } + protocol::Runtime::RemoteObject* getSymbol(protocol::Runtime::RemoteObject* defaultValue) { return m_symbol.isJust() ? m_symbol.fromJust() : defaultValue; } + void setSymbol(std::unique_ptr value) { m_symbol = std::move(value); } + + template + class PropertyDescriptorBuilder { + public: + enum { + NoFieldsSet = 0, + NameSet = 1 << 1, + ConfigurableSet = 1 << 2, + EnumerableSet = 1 << 3, + AllFieldsSet = (NameSet | ConfigurableSet | EnumerableSet | 0)}; + + + PropertyDescriptorBuilder& setName(const String& value) + { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + PropertyDescriptorBuilder& setValue(std::unique_ptr value) + { + m_result->setValue(std::move(value)); + return *this; + } + + PropertyDescriptorBuilder& setWritable(bool value) + { + m_result->setWritable(value); + return *this; + } + + PropertyDescriptorBuilder& setGet(std::unique_ptr value) + { + m_result->setGet(std::move(value)); + return *this; + } + + PropertyDescriptorBuilder& setSet(std::unique_ptr value) + { + m_result->setSet(std::move(value)); + return *this; + } + + PropertyDescriptorBuilder& setConfigurable(bool value) + { + static_assert(!(STATE & ConfigurableSet), "property configurable should not be set yet"); + m_result->setConfigurable(value); + return castState(); + } + + PropertyDescriptorBuilder& setEnumerable(bool value) + { + static_assert(!(STATE & EnumerableSet), "property enumerable should not be set yet"); + m_result->setEnumerable(value); + return castState(); + } + + PropertyDescriptorBuilder& setWasThrown(bool value) + { + m_result->setWasThrown(value); + return *this; + } + + PropertyDescriptorBuilder& setIsOwn(bool value) + { + m_result->setIsOwn(value); + return *this; + } + + PropertyDescriptorBuilder& setSymbol(std::unique_ptr value) + { + m_result->setSymbol(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class PropertyDescriptor; + PropertyDescriptorBuilder() : m_result(new PropertyDescriptor()) { } + + template PropertyDescriptorBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static PropertyDescriptorBuilder<0> create() + { + return PropertyDescriptorBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + PropertyDescriptor() + { + m_configurable = false; + m_enumerable = false; + } + + String m_name; + Maybe m_value; + Maybe m_writable; + Maybe m_get; + Maybe m_set; + bool m_configurable; + bool m_enumerable; + Maybe m_wasThrown; + Maybe m_isOwn; + Maybe m_symbol; +}; + + +class InternalPropertyDescriptor : public ::v8_crdtp::ProtocolObject { +public: + ~InternalPropertyDescriptor() override { } + + String getName() { return m_name; } + void setName(const String& value) { m_name = value; } + + bool hasValue() { return m_value.isJust(); } + protocol::Runtime::RemoteObject* getValue(protocol::Runtime::RemoteObject* defaultValue) { return m_value.isJust() ? m_value.fromJust() : defaultValue; } + void setValue(std::unique_ptr value) { m_value = std::move(value); } + + template + class InternalPropertyDescriptorBuilder { + public: + enum { + NoFieldsSet = 0, + NameSet = 1 << 1, + AllFieldsSet = (NameSet | 0)}; + + + InternalPropertyDescriptorBuilder& setName(const String& value) + { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + InternalPropertyDescriptorBuilder& setValue(std::unique_ptr value) + { + m_result->setValue(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class InternalPropertyDescriptor; + InternalPropertyDescriptorBuilder() : m_result(new InternalPropertyDescriptor()) { } + + template InternalPropertyDescriptorBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static InternalPropertyDescriptorBuilder<0> create() + { + return InternalPropertyDescriptorBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + InternalPropertyDescriptor() + { + } + + String m_name; + Maybe m_value; +}; + + +class PrivatePropertyDescriptor : public ::v8_crdtp::ProtocolObject { +public: + ~PrivatePropertyDescriptor() override { } + + String getName() { return m_name; } + void setName(const String& value) { m_name = value; } + + bool hasValue() { return m_value.isJust(); } + protocol::Runtime::RemoteObject* getValue(protocol::Runtime::RemoteObject* defaultValue) { return m_value.isJust() ? m_value.fromJust() : defaultValue; } + void setValue(std::unique_ptr value) { m_value = std::move(value); } + + bool hasGet() { return m_get.isJust(); } + protocol::Runtime::RemoteObject* getGet(protocol::Runtime::RemoteObject* defaultValue) { return m_get.isJust() ? m_get.fromJust() : defaultValue; } + void setGet(std::unique_ptr value) { m_get = std::move(value); } + + bool hasSet() { return m_set.isJust(); } + protocol::Runtime::RemoteObject* getSet(protocol::Runtime::RemoteObject* defaultValue) { return m_set.isJust() ? m_set.fromJust() : defaultValue; } + void setSet(std::unique_ptr value) { m_set = std::move(value); } + + template + class PrivatePropertyDescriptorBuilder { + public: + enum { + NoFieldsSet = 0, + NameSet = 1 << 1, + AllFieldsSet = (NameSet | 0)}; + + + PrivatePropertyDescriptorBuilder& setName(const String& value) + { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + PrivatePropertyDescriptorBuilder& setValue(std::unique_ptr value) + { + m_result->setValue(std::move(value)); + return *this; + } + + PrivatePropertyDescriptorBuilder& setGet(std::unique_ptr value) + { + m_result->setGet(std::move(value)); + return *this; + } + + PrivatePropertyDescriptorBuilder& setSet(std::unique_ptr value) + { + m_result->setSet(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class PrivatePropertyDescriptor; + PrivatePropertyDescriptorBuilder() : m_result(new PrivatePropertyDescriptor()) { } + + template PrivatePropertyDescriptorBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static PrivatePropertyDescriptorBuilder<0> create() + { + return PrivatePropertyDescriptorBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + PrivatePropertyDescriptor() + { + } + + String m_name; + Maybe m_value; + Maybe m_get; + Maybe m_set; +}; + + +class CallArgument : public ::v8_crdtp::ProtocolObject { +public: + ~CallArgument() override { } + + bool hasValue() { return m_value.isJust(); } + protocol::Value* getValue(protocol::Value* defaultValue) { return m_value.isJust() ? m_value.fromJust() : defaultValue; } + void setValue(std::unique_ptr value) { m_value = std::move(value); } + + bool hasUnserializableValue() { return m_unserializableValue.isJust(); } + String getUnserializableValue(const String& defaultValue) { return m_unserializableValue.isJust() ? m_unserializableValue.fromJust() : defaultValue; } + void setUnserializableValue(const String& value) { m_unserializableValue = value; } + + bool hasObjectId() { return m_objectId.isJust(); } + String getObjectId(const String& defaultValue) { return m_objectId.isJust() ? m_objectId.fromJust() : defaultValue; } + void setObjectId(const String& value) { m_objectId = value; } + + template + class CallArgumentBuilder { + public: + enum { + NoFieldsSet = 0, + AllFieldsSet = (0)}; + + + CallArgumentBuilder& setValue(std::unique_ptr value) + { + m_result->setValue(std::move(value)); + return *this; + } + + CallArgumentBuilder& setUnserializableValue(const String& value) + { + m_result->setUnserializableValue(value); + return *this; + } + + CallArgumentBuilder& setObjectId(const String& value) + { + m_result->setObjectId(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class CallArgument; + CallArgumentBuilder() : m_result(new CallArgument()) { } + + template CallArgumentBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static CallArgumentBuilder<0> create() + { + return CallArgumentBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + CallArgument() + { + } + + Maybe m_value; + Maybe m_unserializableValue; + Maybe m_objectId; +}; + + +class ExecutionContextDescription : public ::v8_crdtp::ProtocolObject { +public: + ~ExecutionContextDescription() override { } + + int getId() { return m_id; } + void setId(int value) { m_id = value; } + + String getOrigin() { return m_origin; } + void setOrigin(const String& value) { m_origin = value; } + + String getName() { return m_name; } + void setName(const String& value) { m_name = value; } + + String getUniqueId() { return m_uniqueId; } + void setUniqueId(const String& value) { m_uniqueId = value; } + + bool hasAuxData() { return m_auxData.isJust(); } + protocol::DictionaryValue* getAuxData(protocol::DictionaryValue* defaultValue) { return m_auxData.isJust() ? m_auxData.fromJust() : defaultValue; } + void setAuxData(std::unique_ptr value) { m_auxData = std::move(value); } + + template + class ExecutionContextDescriptionBuilder { + public: + enum { + NoFieldsSet = 0, + IdSet = 1 << 1, + OriginSet = 1 << 2, + NameSet = 1 << 3, + UniqueIdSet = 1 << 4, + AllFieldsSet = (IdSet | OriginSet | NameSet | UniqueIdSet | 0)}; + + + ExecutionContextDescriptionBuilder& setId(int value) + { + static_assert(!(STATE & IdSet), "property id should not be set yet"); + m_result->setId(value); + return castState(); + } + + ExecutionContextDescriptionBuilder& setOrigin(const String& value) + { + static_assert(!(STATE & OriginSet), "property origin should not be set yet"); + m_result->setOrigin(value); + return castState(); + } + + ExecutionContextDescriptionBuilder& setName(const String& value) + { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + ExecutionContextDescriptionBuilder& setUniqueId(const String& value) + { + static_assert(!(STATE & UniqueIdSet), "property uniqueId should not be set yet"); + m_result->setUniqueId(value); + return castState(); + } + + ExecutionContextDescriptionBuilder& setAuxData(std::unique_ptr value) + { + m_result->setAuxData(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ExecutionContextDescription; + ExecutionContextDescriptionBuilder() : m_result(new ExecutionContextDescription()) { } + + template ExecutionContextDescriptionBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static ExecutionContextDescriptionBuilder<0> create() + { + return ExecutionContextDescriptionBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + ExecutionContextDescription() + { + m_id = 0; + } + + int m_id; + String m_origin; + String m_name; + String m_uniqueId; + Maybe m_auxData; +}; + + +class ExceptionDetails : public ::v8_crdtp::ProtocolObject { +public: + ~ExceptionDetails() override { } + + int getExceptionId() { return m_exceptionId; } + void setExceptionId(int value) { m_exceptionId = value; } + + String getText() { return m_text; } + void setText(const String& value) { m_text = value; } + + int getLineNumber() { return m_lineNumber; } + void setLineNumber(int value) { m_lineNumber = value; } + + int getColumnNumber() { return m_columnNumber; } + void setColumnNumber(int value) { m_columnNumber = value; } + + bool hasScriptId() { return m_scriptId.isJust(); } + String getScriptId(const String& defaultValue) { return m_scriptId.isJust() ? m_scriptId.fromJust() : defaultValue; } + void setScriptId(const String& value) { m_scriptId = value; } + + bool hasUrl() { return m_url.isJust(); } + String getUrl(const String& defaultValue) { return m_url.isJust() ? m_url.fromJust() : defaultValue; } + void setUrl(const String& value) { m_url = value; } + + bool hasStackTrace() { return m_stackTrace.isJust(); } + protocol::Runtime::StackTrace* getStackTrace(protocol::Runtime::StackTrace* defaultValue) { return m_stackTrace.isJust() ? m_stackTrace.fromJust() : defaultValue; } + void setStackTrace(std::unique_ptr value) { m_stackTrace = std::move(value); } + + bool hasException() { return m_exception.isJust(); } + protocol::Runtime::RemoteObject* getException(protocol::Runtime::RemoteObject* defaultValue) { return m_exception.isJust() ? m_exception.fromJust() : defaultValue; } + void setException(std::unique_ptr value) { m_exception = std::move(value); } + + bool hasExecutionContextId() { return m_executionContextId.isJust(); } + int getExecutionContextId(int defaultValue) { return m_executionContextId.isJust() ? m_executionContextId.fromJust() : defaultValue; } + void setExecutionContextId(int value) { m_executionContextId = value; } + + bool hasExceptionMetaData() { return m_exceptionMetaData.isJust(); } + protocol::DictionaryValue* getExceptionMetaData(protocol::DictionaryValue* defaultValue) { return m_exceptionMetaData.isJust() ? m_exceptionMetaData.fromJust() : defaultValue; } + void setExceptionMetaData(std::unique_ptr value) { m_exceptionMetaData = std::move(value); } + + template + class ExceptionDetailsBuilder { + public: + enum { + NoFieldsSet = 0, + ExceptionIdSet = 1 << 1, + TextSet = 1 << 2, + LineNumberSet = 1 << 3, + ColumnNumberSet = 1 << 4, + AllFieldsSet = (ExceptionIdSet | TextSet | LineNumberSet | ColumnNumberSet | 0)}; + + + ExceptionDetailsBuilder& setExceptionId(int value) + { + static_assert(!(STATE & ExceptionIdSet), "property exceptionId should not be set yet"); + m_result->setExceptionId(value); + return castState(); + } + + ExceptionDetailsBuilder& setText(const String& value) + { + static_assert(!(STATE & TextSet), "property text should not be set yet"); + m_result->setText(value); + return castState(); + } + + ExceptionDetailsBuilder& setLineNumber(int value) + { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + ExceptionDetailsBuilder& setColumnNumber(int value) + { + static_assert(!(STATE & ColumnNumberSet), "property columnNumber should not be set yet"); + m_result->setColumnNumber(value); + return castState(); + } + + ExceptionDetailsBuilder& setScriptId(const String& value) + { + m_result->setScriptId(value); + return *this; + } + + ExceptionDetailsBuilder& setUrl(const String& value) + { + m_result->setUrl(value); + return *this; + } + + ExceptionDetailsBuilder& setStackTrace(std::unique_ptr value) + { + m_result->setStackTrace(std::move(value)); + return *this; + } + + ExceptionDetailsBuilder& setException(std::unique_ptr value) + { + m_result->setException(std::move(value)); + return *this; + } + + ExceptionDetailsBuilder& setExecutionContextId(int value) + { + m_result->setExecutionContextId(value); + return *this; + } + + ExceptionDetailsBuilder& setExceptionMetaData(std::unique_ptr value) + { + m_result->setExceptionMetaData(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class ExceptionDetails; + ExceptionDetailsBuilder() : m_result(new ExceptionDetails()) { } + + template ExceptionDetailsBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static ExceptionDetailsBuilder<0> create() + { + return ExceptionDetailsBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + ExceptionDetails() + { + m_exceptionId = 0; + m_lineNumber = 0; + m_columnNumber = 0; + } + + int m_exceptionId; + String m_text; + int m_lineNumber; + int m_columnNumber; + Maybe m_scriptId; + Maybe m_url; + Maybe m_stackTrace; + Maybe m_exception; + Maybe m_executionContextId; + Maybe m_exceptionMetaData; +}; + + +class CallFrame : public ::v8_crdtp::ProtocolObject { +public: + ~CallFrame() override { } + + String getFunctionName() { return m_functionName; } + void setFunctionName(const String& value) { m_functionName = value; } + + String getScriptId() { return m_scriptId; } + void setScriptId(const String& value) { m_scriptId = value; } + + String getUrl() { return m_url; } + void setUrl(const String& value) { m_url = value; } + + int getLineNumber() { return m_lineNumber; } + void setLineNumber(int value) { m_lineNumber = value; } + + int getColumnNumber() { return m_columnNumber; } + void setColumnNumber(int value) { m_columnNumber = value; } + + template + class CallFrameBuilder { + public: + enum { + NoFieldsSet = 0, + FunctionNameSet = 1 << 1, + ScriptIdSet = 1 << 2, + UrlSet = 1 << 3, + LineNumberSet = 1 << 4, + ColumnNumberSet = 1 << 5, + AllFieldsSet = (FunctionNameSet | ScriptIdSet | UrlSet | LineNumberSet | ColumnNumberSet | 0)}; + + + CallFrameBuilder& setFunctionName(const String& value) + { + static_assert(!(STATE & FunctionNameSet), "property functionName should not be set yet"); + m_result->setFunctionName(value); + return castState(); + } + + CallFrameBuilder& setScriptId(const String& value) + { + static_assert(!(STATE & ScriptIdSet), "property scriptId should not be set yet"); + m_result->setScriptId(value); + return castState(); + } + + CallFrameBuilder& setUrl(const String& value) + { + static_assert(!(STATE & UrlSet), "property url should not be set yet"); + m_result->setUrl(value); + return castState(); + } + + CallFrameBuilder& setLineNumber(int value) + { + static_assert(!(STATE & LineNumberSet), "property lineNumber should not be set yet"); + m_result->setLineNumber(value); + return castState(); + } + + CallFrameBuilder& setColumnNumber(int value) + { + static_assert(!(STATE & ColumnNumberSet), "property columnNumber should not be set yet"); + m_result->setColumnNumber(value); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class CallFrame; + CallFrameBuilder() : m_result(new CallFrame()) { } + + template CallFrameBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static CallFrameBuilder<0> create() + { + return CallFrameBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + CallFrame() + { + m_lineNumber = 0; + m_columnNumber = 0; + } + + String m_functionName; + String m_scriptId; + String m_url; + int m_lineNumber; + int m_columnNumber; +}; + + +class StackTrace : public ::v8_crdtp::ProtocolObject, + public API::StackTrace { +public: + ~StackTrace() override { } + + bool hasDescription() { return m_description.isJust(); } + String getDescription(const String& defaultValue) { return m_description.isJust() ? m_description.fromJust() : defaultValue; } + void setDescription(const String& value) { m_description = value; } + + protocol::Array* getCallFrames() { return m_callFrames.get(); } + void setCallFrames(std::unique_ptr> value) { m_callFrames = std::move(value); } + + bool hasParent() { return m_parent.isJust(); } + protocol::Runtime::StackTrace* getParent(protocol::Runtime::StackTrace* defaultValue) { return m_parent.isJust() ? m_parent.fromJust() : defaultValue; } + void setParent(std::unique_ptr value) { m_parent = std::move(value); } + + bool hasParentId() { return m_parentId.isJust(); } + protocol::Runtime::StackTraceId* getParentId(protocol::Runtime::StackTraceId* defaultValue) { return m_parentId.isJust() ? m_parentId.fromJust() : defaultValue; } + void setParentId(std::unique_ptr value) { m_parentId = std::move(value); } + + template + class StackTraceBuilder { + public: + enum { + NoFieldsSet = 0, + CallFramesSet = 1 << 1, + AllFieldsSet = (CallFramesSet | 0)}; + + + StackTraceBuilder& setDescription(const String& value) + { + m_result->setDescription(value); + return *this; + } + + StackTraceBuilder& setCallFrames(std::unique_ptr> value) + { + static_assert(!(STATE & CallFramesSet), "property callFrames should not be set yet"); + m_result->setCallFrames(std::move(value)); + return castState(); + } + + StackTraceBuilder& setParent(std::unique_ptr value) + { + m_result->setParent(std::move(value)); + return *this; + } + + StackTraceBuilder& setParentId(std::unique_ptr value) + { + m_result->setParentId(std::move(value)); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class StackTrace; + StackTraceBuilder() : m_result(new StackTrace()) { } + + template StackTraceBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static StackTraceBuilder<0> create() + { + return StackTraceBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + StackTrace() + { + } + + Maybe m_description; + std::unique_ptr> m_callFrames; + Maybe m_parent; + Maybe m_parentId; +}; + + +class StackTraceId : public ::v8_crdtp::ProtocolObject, + public API::StackTraceId { +public: + ~StackTraceId() override { } + + String getId() { return m_id; } + void setId(const String& value) { m_id = value; } + + bool hasDebuggerId() { return m_debuggerId.isJust(); } + String getDebuggerId(const String& defaultValue) { return m_debuggerId.isJust() ? m_debuggerId.fromJust() : defaultValue; } + void setDebuggerId(const String& value) { m_debuggerId = value; } + + template + class StackTraceIdBuilder { + public: + enum { + NoFieldsSet = 0, + IdSet = 1 << 1, + AllFieldsSet = (IdSet | 0)}; + + + StackTraceIdBuilder& setId(const String& value) + { + static_assert(!(STATE & IdSet), "property id should not be set yet"); + m_result->setId(value); + return castState(); + } + + StackTraceIdBuilder& setDebuggerId(const String& value) + { + m_result->setDebuggerId(value); + return *this; + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class StackTraceId; + StackTraceIdBuilder() : m_result(new StackTraceId()) { } + + template StackTraceIdBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static StackTraceIdBuilder<0> create() + { + return StackTraceIdBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + StackTraceId() + { + } + + String m_id; + Maybe m_debuggerId; +}; + + +// ------------- Backend interface. + +class Backend { +public: + virtual ~Backend() { } + + class AwaitPromiseCallback { + public: + virtual void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) = 0; + virtual void sendFailure(const DispatchResponse&) = 0; + virtual void fallThrough() = 0; + virtual ~AwaitPromiseCallback() { } + }; + virtual void awaitPromise(const String& in_promiseObjectId, Maybe in_returnByValue, Maybe in_generatePreview, std::unique_ptr callback) = 0; + class CallFunctionOnCallback { + public: + virtual void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) = 0; + virtual void sendFailure(const DispatchResponse&) = 0; + virtual void fallThrough() = 0; + virtual ~CallFunctionOnCallback() { } + }; + virtual void callFunctionOn(const String& in_functionDeclaration, Maybe in_objectId, Maybe> in_arguments, Maybe in_silent, Maybe in_returnByValue, Maybe in_generatePreview, Maybe in_userGesture, Maybe in_awaitPromise, Maybe in_executionContextId, Maybe in_objectGroup, Maybe in_throwOnSideEffect, Maybe in_uniqueContextId, Maybe in_generateWebDriverValue, std::unique_ptr callback) = 0; + virtual DispatchResponse compileScript(const String& in_expression, const String& in_sourceURL, bool in_persistScript, Maybe in_executionContextId, Maybe* out_scriptId, Maybe* out_exceptionDetails) = 0; + virtual DispatchResponse disable() = 0; + virtual DispatchResponse discardConsoleEntries() = 0; + virtual DispatchResponse enable() = 0; + class EvaluateCallback { + public: + virtual void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) = 0; + virtual void sendFailure(const DispatchResponse&) = 0; + virtual void fallThrough() = 0; + virtual ~EvaluateCallback() { } + }; + virtual void evaluate(const String& in_expression, Maybe in_objectGroup, Maybe in_includeCommandLineAPI, Maybe in_silent, Maybe in_contextId, Maybe in_returnByValue, Maybe in_generatePreview, Maybe in_userGesture, Maybe in_awaitPromise, Maybe in_throwOnSideEffect, Maybe in_timeout, Maybe in_disableBreaks, Maybe in_replMode, Maybe in_allowUnsafeEvalBlockedByCSP, Maybe in_uniqueContextId, Maybe in_generateWebDriverValue, std::unique_ptr callback) = 0; + virtual DispatchResponse getIsolateId(String* out_id) = 0; + virtual DispatchResponse getHeapUsage(double* out_usedSize, double* out_totalSize) = 0; + virtual DispatchResponse getProperties(const String& in_objectId, Maybe in_ownProperties, Maybe in_accessorPropertiesOnly, Maybe in_generatePreview, Maybe in_nonIndexedPropertiesOnly, std::unique_ptr>* out_result, Maybe>* out_internalProperties, Maybe>* out_privateProperties, Maybe* out_exceptionDetails) = 0; + virtual DispatchResponse globalLexicalScopeNames(Maybe in_executionContextId, std::unique_ptr>* out_names) = 0; + virtual DispatchResponse queryObjects(const String& in_prototypeObjectId, Maybe in_objectGroup, std::unique_ptr* out_objects) = 0; + virtual DispatchResponse releaseObject(const String& in_objectId) = 0; + virtual DispatchResponse releaseObjectGroup(const String& in_objectGroup) = 0; + virtual DispatchResponse runIfWaitingForDebugger() = 0; + class RunScriptCallback { + public: + virtual void sendSuccess(std::unique_ptr result, Maybe exceptionDetails) = 0; + virtual void sendFailure(const DispatchResponse&) = 0; + virtual void fallThrough() = 0; + virtual ~RunScriptCallback() { } + }; + virtual void runScript(const String& in_scriptId, Maybe in_executionContextId, Maybe in_objectGroup, Maybe in_silent, Maybe in_includeCommandLineAPI, Maybe in_returnByValue, Maybe in_generatePreview, Maybe in_awaitPromise, std::unique_ptr callback) = 0; + virtual DispatchResponse setCustomObjectFormatterEnabled(bool in_enabled) = 0; + virtual DispatchResponse setMaxCallStackSizeToCapture(int in_size) = 0; + class TerminateExecutionCallback { + public: + virtual void sendSuccess() = 0; + virtual void sendFailure(const DispatchResponse&) = 0; + virtual void fallThrough() = 0; + virtual ~TerminateExecutionCallback() { } + }; + virtual void terminateExecution(std::unique_ptr callback) = 0; + virtual DispatchResponse addBinding(const String& in_name, Maybe in_executionContextId, Maybe in_executionContextName) = 0; + virtual DispatchResponse removeBinding(const String& in_name) = 0; + virtual DispatchResponse getExceptionDetails(const String& in_errorObjectId, Maybe* out_exceptionDetails) = 0; + +}; + +// ------------- Frontend interface. + +class Frontend { +public: + explicit Frontend(FrontendChannel* frontend_channel) : frontend_channel_(frontend_channel) {} + void bindingCalled(const String& name, const String& payload, int executionContextId); + void consoleAPICalled(const String& type, std::unique_ptr> args, int executionContextId, double timestamp, Maybe stackTrace = Maybe(), Maybe context = Maybe()); + void exceptionRevoked(const String& reason, int exceptionId); + void exceptionThrown(double timestamp, std::unique_ptr exceptionDetails); + void executionContextCreated(std::unique_ptr context); + void executionContextDestroyed(int executionContextId, const String& executionContextUniqueId); + void executionContextsCleared(); + void inspectRequested(std::unique_ptr object, std::unique_ptr hints, Maybe executionContextId = Maybe()); + + void flush(); + void sendRawNotification(std::unique_ptr); + private: + FrontendChannel* frontend_channel_; +}; + +// ------------- Dispatcher. + +class Dispatcher { +public: + static void wire(UberDispatcher*, Backend*); + +private: + Dispatcher() { } +}; + +// ------------- Metainfo. + +class Metainfo { +public: + using BackendClass = Backend; + using FrontendClass = Frontend; + using DispatcherClass = Dispatcher; + static const char domainName[]; + static const char commandPrefix[]; + static const char version[]; +}; + +} // namespace Runtime +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Runtime_h) diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Schema.cpp b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Schema.cpp new file mode 100644 index 000000000..79f559622 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Schema.cpp @@ -0,0 +1,159 @@ +// This file is generated by TypeBuilder_cpp.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/inspector/protocol/Schema.h" + +#include "src/inspector/protocol/Protocol.h" + +#include "third_party/inspector_protocol/crdtp/cbor.h" +#include "third_party/inspector_protocol/crdtp/find_by_first.h" +#include "third_party/inspector_protocol/crdtp/span.h" + +namespace v8_inspector { +namespace protocol { +namespace Schema { + +using v8_crdtp::DeserializerState; +using v8_crdtp::ProtocolTypeTraits; + +// ------------- Enum values from types. + +const char Metainfo::domainName[] = "Schema"; +const char Metainfo::commandPrefix[] = "Schema."; +const char Metainfo::version[] = "1.3"; + +V8_CRDTP_BEGIN_DESERIALIZER(Domain) + V8_CRDTP_DESERIALIZE_FIELD("name", m_name), + V8_CRDTP_DESERIALIZE_FIELD("version", m_version), +V8_CRDTP_END_DESERIALIZER() + +V8_CRDTP_BEGIN_SERIALIZER(Domain) + V8_CRDTP_SERIALIZE_FIELD("name", m_name); + V8_CRDTP_SERIALIZE_FIELD("version", m_version); +V8_CRDTP_END_SERIALIZER(); + +// static +std::unique_ptr API::Domain::fromBinary(const uint8_t* data, size_t length) +{ + return protocol::Schema::Domain::FromBinary(data, length); +} + +// ------------- Enum values from params. + + +// ------------- Frontend notifications. + +void Frontend::flush() +{ + frontend_channel_->FlushProtocolNotifications(); +} + +void Frontend::sendRawNotification(std::unique_ptr notification) +{ + frontend_channel_->SendProtocolNotification(std::move(notification)); +} + +// --------------------- Dispatcher. + +class DomainDispatcherImpl : public protocol::DomainDispatcher { +public: + DomainDispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) + : DomainDispatcher(frontendChannel) + , m_backend(backend) {} + ~DomainDispatcherImpl() override { } + + using CallHandler = void (DomainDispatcherImpl::*)(const v8_crdtp::Dispatchable& dispatchable); + + std::function Dispatch(v8_crdtp::span command_name) override; + + void getDomains(const v8_crdtp::Dispatchable& dispatchable); + protected: + Backend* m_backend; +}; + +namespace { +// This helper method with a static map of command methods (instance methods +// of DomainDispatcherImpl declared just above) by their name is used immediately below, +// in the DomainDispatcherImpl::Dispatch method. +DomainDispatcherImpl::CallHandler CommandByName(v8_crdtp::span command_name) { + static auto* commands = [](){ + auto* commands = new std::vector, + DomainDispatcherImpl::CallHandler>>{ + { + v8_crdtp::SpanFrom("getDomains"), + &DomainDispatcherImpl::getDomains + }, + }; + return commands; + }(); + return v8_crdtp::FindByFirst(*commands, command_name, nullptr); +} +} // namespace + +std::function DomainDispatcherImpl::Dispatch(v8_crdtp::span command_name) { + CallHandler handler = CommandByName(command_name); + if (!handler) return nullptr; + + return [this, handler](const v8_crdtp::Dispatchable& dispatchable) { + (this->*handler)(dispatchable); + }; +} + + +namespace { + + +} // namespace + +void DomainDispatcherImpl::getDomains(const v8_crdtp::Dispatchable& dispatchable) +{ + // Prepare input parameters. + // Declare output parameters. + std::unique_ptr> out_domains; + + std::unique_ptr weak = weakPtr(); + DispatchResponse response = m_backend->getDomains(&out_domains); + if (response.IsFallThrough()) { + channel()->FallThrough(dispatchable.CallId(), v8_crdtp::SpanFrom("Schema.getDomains"), dispatchable.Serialized()); + return; + } + if (weak->get()) { + std::unique_ptr result; + if (response.IsSuccess()) { + v8_crdtp::ObjectSerializer serializer; + serializer.AddField(v8_crdtp::MakeSpan("domains"), out_domains); + result = serializer.Finish(); + } else { + result = Serializable::From({}); + } + weak->get()->sendResponse(dispatchable.CallId(), response, std::move(result)); + } + return; +} + +namespace { +// This helper method (with a static map of redirects) is used from Dispatcher::wire +// immediately below. +const std::vector, v8_crdtp::span>>& SortedRedirects() { + static auto* redirects = [](){ + auto* redirects = new std::vector, v8_crdtp::span>>{ + }; + return redirects; + }(); + return *redirects; +} +} // namespace + +// static +void Dispatcher::wire(UberDispatcher* uber, Backend* backend) +{ + auto dispatcher = std::make_unique(uber->channel(), backend); + uber->WireBackend(v8_crdtp::SpanFrom("Schema"), SortedRedirects(), std::move(dispatcher)); +} + +} // Schema +} // namespace v8_inspector +} // namespace protocol diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Schema.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Schema.h new file mode 100644 index 000000000..409d49483 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/protocol/Schema.h @@ -0,0 +1,146 @@ +// This file is generated by TypeBuilder_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Schema_h +#define v8_inspector_protocol_Schema_h + +#include "src/inspector/protocol/Protocol.h" +// For each imported domain we generate a ValueConversions struct instead of a full domain definition +// and include Domain::API version from there. +#include "include/inspector/Schema.h" + +namespace v8_inspector { +namespace protocol { +namespace Schema { +class Domain; + +// ------------- Forward and enum declarations. + +// ------------- Type and builder declarations. + +class Domain : public ::v8_crdtp::ProtocolObject, + public API::Domain { +public: + ~Domain() override { } + + String getName() { return m_name; } + void setName(const String& value) { m_name = value; } + + String getVersion() { return m_version; } + void setVersion(const String& value) { m_version = value; } + + template + class DomainBuilder { + public: + enum { + NoFieldsSet = 0, + NameSet = 1 << 1, + VersionSet = 1 << 2, + AllFieldsSet = (NameSet | VersionSet | 0)}; + + + DomainBuilder& setName(const String& value) + { + static_assert(!(STATE & NameSet), "property name should not be set yet"); + m_result->setName(value); + return castState(); + } + + DomainBuilder& setVersion(const String& value) + { + static_assert(!(STATE & VersionSet), "property version should not be set yet"); + m_result->setVersion(value); + return castState(); + } + + std::unique_ptr build() + { + static_assert(STATE == AllFieldsSet, "state should be AllFieldsSet"); + return std::move(m_result); + } + + private: + friend class Domain; + DomainBuilder() : m_result(new Domain()) { } + + template DomainBuilder& castState() + { + return *reinterpret_cast*>(this); + } + + std::unique_ptr m_result; + }; + + static DomainBuilder<0> create() + { + return DomainBuilder<0>(); + } + +private: + DECLARE_SERIALIZATION_SUPPORT(); + + Domain() + { + } + + String m_name; + String m_version; +}; + + +// ------------- Backend interface. + +class Backend { +public: + virtual ~Backend() { } + + virtual DispatchResponse getDomains(std::unique_ptr>* out_domains) = 0; + + virtual DispatchResponse disable() + { + return DispatchResponse::Success(); + } +}; + +// ------------- Frontend interface. + +class Frontend { +public: + explicit Frontend(FrontendChannel* frontend_channel) : frontend_channel_(frontend_channel) {} + + void flush(); + void sendRawNotification(std::unique_ptr); + private: + FrontendChannel* frontend_channel_; +}; + +// ------------- Dispatcher. + +class Dispatcher { +public: + static void wire(UberDispatcher*, Backend*); + +private: + Dispatcher() { } +}; + +// ------------- Metainfo. + +class Metainfo { +public: + using BackendClass = Backend; + using FrontendClass = Frontend; + using DispatcherClass = Dispatcher; + static const char domainName[]; + static const char commandPrefix[]; + static const char version[]; +}; + +} // namespace Schema +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Schema_h) diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/remote-object-id.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/remote-object-id.h new file mode 100644 index 000000000..1c6012412 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/remote-object-id.h @@ -0,0 +1,54 @@ +// Copyright 2015 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_REMOTE_OBJECT_ID_H_ +#define V8_INSPECTOR_REMOTE_OBJECT_ID_H_ + +#include + +#include "src/inspector/protocol/Forward.h" + +namespace v8_inspector { + +using protocol::Response; + +class RemoteObjectIdBase { + public: + uint64_t isolateId() const { return m_isolateId; } + int contextId() const { return m_injectedScriptId; } + + protected: + RemoteObjectIdBase(); + ~RemoteObjectIdBase() = default; + + bool parseId(const String16&); + + uint64_t m_isolateId; + int m_injectedScriptId; + int m_id; +}; + +class RemoteObjectId final : public RemoteObjectIdBase { + public: + static Response parse(const String16&, std::unique_ptr*); + ~RemoteObjectId() = default; + int id() const { return m_id; } + + static String16 serialize(uint64_t isolateId, int injectedScriptId, int id); +}; + +class RemoteCallFrameId final : public RemoteObjectIdBase { + public: + static Response parse(const String16&, std::unique_ptr*); + ~RemoteCallFrameId() = default; + + int frameOrdinal() const { return m_id; } + + static String16 serialize(uint64_t isolateId, int injectedScriptId, + int frameOrdinal); +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_REMOTE_OBJECT_ID_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/search-util.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/search-util.h new file mode 100644 index 000000000..11d511a30 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/search-util.h @@ -0,0 +1,26 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_SEARCH_UTIL_H_ +#define V8_INSPECTOR_SEARCH_UTIL_H_ + +#include + +#include "src/inspector/protocol/Debugger.h" +#include "src/inspector/string-util.h" + +namespace v8_inspector { + +class V8InspectorSession; + +String16 findSourceURL(const String16& content, bool multiline); +String16 findSourceMapURL(const String16& content, bool multiline); +std::vector> +searchInTextByLinesImpl(V8InspectorSession*, const String16& text, + const String16& query, bool caseSensitive, + bool isRegex); + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_SEARCH_UTIL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/string-16.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/string-16.h new file mode 100644 index 000000000..29651ac95 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/string-16.h @@ -0,0 +1,172 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_STRING_16_H_ +#define V8_INSPECTOR_STRING_16_H_ + +#include + +#include +#include +#include +#include +#include + +#include "src/base/compiler-specific.h" + +namespace v8_inspector { + +using UChar = uint16_t; + +class String16 { + public: + static const size_t kNotFound = static_cast(-1); + + String16() = default; + String16(const String16&) V8_NOEXCEPT = default; + String16(String16&&) V8_NOEXCEPT = default; + String16(const UChar* characters, size_t size); + V8_EXPORT String16(const UChar* characters); + V8_EXPORT String16(const char* characters); + String16(const char* characters, size_t size); + explicit String16(const std::basic_string& impl); + explicit String16(std::basic_string&& impl); + + String16& operator=(const String16&) V8_NOEXCEPT = default; + String16& operator=(String16&&) V8_NOEXCEPT = default; + + static String16 fromInteger(int); + static String16 fromInteger(size_t); + static String16 fromInteger64(int64_t); + static String16 fromUInt64(uint64_t); + static String16 fromDouble(double); + static String16 fromDouble(double, int precision); + + int64_t toInteger64(bool* ok = nullptr) const; + uint64_t toUInt64(bool* ok = nullptr) const; + int toInteger(bool* ok = nullptr) const; + std::pair getTrimmedOffsetAndLength() const; + String16 stripWhiteSpace() const; + const UChar* characters16() const { return m_impl.c_str(); } + size_t length() const { return m_impl.length(); } + bool isEmpty() const { return !m_impl.length(); } + UChar operator[](size_t index) const { return m_impl[index]; } + String16 substring(size_t pos, size_t len = UINT_MAX) const { + return String16(m_impl.substr(pos, len)); + } + size_t find(const String16& str, size_t start = 0) const { + return m_impl.find(str.m_impl, start); + } + size_t reverseFind(const String16& str, size_t start = UINT_MAX) const { + return m_impl.rfind(str.m_impl, start); + } + size_t find(UChar c, size_t start = 0) const { return m_impl.find(c, start); } + size_t reverseFind(UChar c, size_t start = UINT_MAX) const { + return m_impl.rfind(c, start); + } + void swap(String16& other) { + m_impl.swap(other.m_impl); + std::swap(hash_code, other.hash_code); + } + + // Convenience methods. + V8_EXPORT std::string utf8() const; + V8_EXPORT static String16 fromUTF8(const char* stringStart, size_t length); + + // Instantiates a String16 in native endianness from UTF16 LE. + // On Big endian architectures, byte order needs to be flipped. + V8_EXPORT static String16 fromUTF16LE(const UChar* stringStart, + size_t length); + + std::size_t hash() const { + if (!hash_code) { + for (char c : m_impl) hash_code = 31 * hash_code + c; + // Map hash code 0 to 1. This double the number of hash collisions for 1, + // but avoids recomputing the hash code. + if (!hash_code) ++hash_code; + } + return hash_code; + } + + inline bool operator==(const String16& other) const { + return m_impl == other.m_impl; + } + inline bool operator<(const String16& other) const { + return m_impl < other.m_impl; + } + inline bool operator!=(const String16& other) const { + return m_impl != other.m_impl; + } + inline String16 operator+(const String16& other) const { + return String16(m_impl + other.m_impl); + } + inline String16& operator+=(const String16& other) { + m_impl += other.m_impl; + return *this; + } + + // Defined later, since it uses the String16Builder. + template + static String16 concat(T... args); + + private: + std::basic_string m_impl; + mutable std::size_t hash_code = 0; +}; + +inline String16 operator+(const char* a, const String16& b) { + return String16(a) + b; +} + +class String16Builder { + public: + String16Builder(); + void append(const String16&); + void append(UChar); + void append(char); + void append(const UChar*, size_t); + void append(const char*, size_t); + void appendNumber(int); + void appendNumber(size_t); + void appendUnsignedAsHex(uint64_t); + void appendUnsignedAsHex(uint32_t); + void appendUnsignedAsHex(uint8_t); + String16 toString(); + void reserveCapacity(size_t); + + template + void appendAll(T first, R... rest) { + append(first); + appendAll(rest...); + } + void appendAll() {} + + private: + std::vector m_buffer; +}; + +template +String16 String16::concat(T... args) { + String16Builder builder; + builder.appendAll(args...); + return builder.toString(); +} + +} // namespace v8_inspector + +#if !defined(__APPLE__) || defined(_LIBCPP_VERSION) + +namespace std { +template <> +struct hash { + std::size_t operator()(const v8_inspector::String16& string) const { + return string.hash(); + } +}; + +} // namespace std + +#endif // !defined(__APPLE__) || defined(_LIBCPP_VERSION) + +#endif // V8_INSPECTOR_STRING_16_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/string-util.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/string-util.h new file mode 100644 index 000000000..7de6206ab --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/string-util.h @@ -0,0 +1,126 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_STRING_UTIL_H_ +#define V8_INSPECTOR_STRING_UTIL_H_ + +#include + +#include + +#include "../../third_party/inspector_protocol/crdtp/protocol_core.h" +#include "include/v8-inspector.h" +#include "src/base/logging.h" +#include "src/base/macros.h" +#include "src/inspector/string-16.h" + +namespace v8_inspector { + +namespace protocol { + +class Value; + +using String = v8_inspector::String16; + +class StringUtil { + public: + static String fromUTF8(const uint8_t* data, size_t length) { + return String16::fromUTF8(reinterpret_cast(data), length); + } + + static String fromUTF16LE(const uint16_t* data, size_t length) { + return String16::fromUTF16LE(data, length); + } + + static const uint8_t* CharactersLatin1(const String& s) { return nullptr; } + static const uint8_t* CharactersUTF8(const String& s) { return nullptr; } + static const uint16_t* CharactersUTF16(const String& s) { + return s.characters16(); + } + static size_t CharacterCount(const String& s) { return s.length(); } +}; + +// A read-only sequence of uninterpreted bytes with reference-counted storage. +class V8_EXPORT Binary { + public: + Binary() = default; + + const uint8_t* data() const { return bytes_->data(); } + size_t size() const { return bytes_->size(); } + String toBase64() const; + static Binary fromBase64(const String& base64, bool* success); + static Binary fromSpan(const uint8_t* data, size_t size) { + return Binary(std::make_shared>(data, data + size)); + } + + private: + std::shared_ptr> bytes_; + + explicit Binary(std::shared_ptr> bytes) + : bytes_(bytes) {} +}; +} // namespace protocol + +v8::Local toV8String(v8::Isolate*, const String16&); +v8::Local toV8StringInternalized(v8::Isolate*, const String16&); +v8::Local toV8StringInternalized(v8::Isolate*, const char*); +v8::Local toV8String(v8::Isolate*, const StringView&); +// TODO(dgozman): rename to toString16. +String16 toProtocolString(v8::Isolate*, v8::Local); +String16 toProtocolStringWithTypeCheck(v8::Isolate*, v8::Local); +V8_EXPORT_PRIVATE String16 toString16(const StringView&); +V8_EXPORT_PRIVATE StringView toStringView(const String16&); +template +StringView toStringView(const char* str[N]) { + return StringView(reinterpret_cast(str), N); +} +bool stringViewStartsWith(const StringView&, const char*); + +// Creates a string buffer instance which owns |str|, a 16 bit string. +std::unique_ptr StringBufferFrom(String16 str); + +// Creates a string buffer instance which owns |str|, an 8 bit string. +// 8 bit strings are used for LATIN1 text (which subsumes 7 bit ASCII, e.g. +// our generated JSON), as well as for CBOR encoded binary messages. +std::unique_ptr StringBufferFrom(std::vector str); + +String16 stackTraceIdToString(uintptr_t id); + +} // namespace v8_inspector + +// See third_party/inspector_protocol/crdtp/serializer_traits.h. +namespace v8_crdtp { + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, + v8_inspector::String16* value); + static void Serialize(const v8_inspector::String16& value, + std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, + v8_inspector::protocol::Binary* value); + static void Serialize(const v8_inspector::protocol::Binary& value, + std::vector* bytes); +}; + +namespace detail { +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +} // namespace detail + +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_STRING_UTIL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/test-interface.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/test-interface.h new file mode 100644 index 000000000..406ba02fa --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/test-interface.h @@ -0,0 +1,19 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_TEST_INTERFACE_H_ +#define V8_INSPECTOR_TEST_INTERFACE_H_ + +#include "include/v8config.h" + +namespace v8_inspector { + +class V8Inspector; + +V8_EXPORT void SetMaxAsyncTaskStacksForTest(V8Inspector* inspector, int limit); +V8_EXPORT void DumpAsyncTaskStacksStateForTest(V8Inspector* inspector); + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_TEST_INTERFACE_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-console-agent-impl.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-console-agent-impl.h new file mode 100644 index 000000000..009375b04 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-console-agent-impl.h @@ -0,0 +1,48 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_CONSOLE_AGENT_IMPL_H_ +#define V8_INSPECTOR_V8_CONSOLE_AGENT_IMPL_H_ + +#include "src/base/macros.h" +#include "src/inspector/protocol/Console.h" +#include "src/inspector/protocol/Forward.h" + +namespace v8_inspector { + +class V8ConsoleMessage; +class V8InspectorSessionImpl; + +using protocol::Response; + +class V8ConsoleAgentImpl : public protocol::Console::Backend { + public: + V8ConsoleAgentImpl(V8InspectorSessionImpl*, protocol::FrontendChannel*, + protocol::DictionaryValue* state); + ~V8ConsoleAgentImpl() override; + V8ConsoleAgentImpl(const V8ConsoleAgentImpl&) = delete; + V8ConsoleAgentImpl& operator=(const V8ConsoleAgentImpl&) = delete; + + Response enable() override; + Response disable() override; + Response clearMessages() override; + + void restore(); + void messageAdded(V8ConsoleMessage*); + void reset(); + bool enabled(); + + private: + void reportAllMessages(); + bool reportMessage(V8ConsoleMessage*, bool generatePreview); + + V8InspectorSessionImpl* m_session; + protocol::DictionaryValue* m_state; + protocol::Console::Frontend m_frontend; + bool m_enabled; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_CONSOLE_AGENT_IMPL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-console-message.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-console-message.h new file mode 100644 index 000000000..cd960cf79 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-console-message.h @@ -0,0 +1,150 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_CONSOLE_MESSAGE_H_ +#define V8_INSPECTOR_V8_CONSOLE_MESSAGE_H_ + +#include +#include +#include +#include + +#include "include/v8-local-handle.h" +#include "include/v8-persistent-handle.h" +#include "src/inspector/protocol/Console.h" +#include "src/inspector/protocol/Forward.h" +#include "src/inspector/protocol/Runtime.h" + +namespace v8_inspector { + +class InspectedContext; +class V8InspectorImpl; +class V8InspectorSessionImpl; +class V8StackTraceImpl; + +enum class V8MessageOrigin { kConsole, kException, kRevokedException }; + +enum class ConsoleAPIType { + kLog, + kDebug, + kInfo, + kError, + kWarning, + kDir, + kDirXML, + kTable, + kTrace, + kStartGroup, + kStartGroupCollapsed, + kEndGroup, + kClear, + kAssert, + kTimeEnd, + kCount +}; + +class V8ConsoleMessage { + public: + ~V8ConsoleMessage(); + + static std::unique_ptr createForConsoleAPI( + v8::Local v8Context, int contextId, int groupId, + V8InspectorImpl* inspector, double timestamp, ConsoleAPIType, + const std::vector>& arguments, + const String16& consoleContext, std::unique_ptr); + + static std::unique_ptr createForException( + double timestamp, const String16& detailedMessage, const String16& url, + unsigned lineNumber, unsigned columnNumber, + std::unique_ptr, int scriptId, v8::Isolate*, + const String16& message, int contextId, v8::Local exception, + unsigned exceptionId); + + static std::unique_ptr createForRevokedException( + double timestamp, const String16& message, unsigned revokedExceptionId); + + V8MessageOrigin origin() const; + void reportToFrontend(protocol::Console::Frontend*) const; + void reportToFrontend(protocol::Runtime::Frontend*, V8InspectorSessionImpl*, + bool generatePreview) const; + ConsoleAPIType type() const; + void contextDestroyed(int contextId); + + int estimatedSize() const { + return m_v8Size + static_cast(m_message.length() * sizeof(UChar)); + } + + private: + V8ConsoleMessage(V8MessageOrigin, double timestamp, const String16& message); + + using Arguments = std::vector>>; + std::unique_ptr> + wrapArguments(V8InspectorSessionImpl*, bool generatePreview) const; + std::unique_ptr wrapException( + V8InspectorSessionImpl*, bool generatePreview) const; + void setLocation(const String16& url, unsigned lineNumber, + unsigned columnNumber, std::unique_ptr, + int scriptId); + std::unique_ptr getAssociatedExceptionData( + V8InspectorImpl* inspector, V8InspectorSessionImpl* session) const; + + V8MessageOrigin m_origin; + double m_timestamp; + String16 m_message; + String16 m_url; + unsigned m_lineNumber; + unsigned m_columnNumber; + std::unique_ptr m_stackTrace; + int m_scriptId; + int m_contextId; + ConsoleAPIType m_type; + unsigned m_exceptionId; + unsigned m_revokedExceptionId; + int m_v8Size = 0; + Arguments m_arguments; + String16 m_detailedMessage; + String16 m_consoleContext; +}; + +class V8ConsoleMessageStorage { + public: + V8ConsoleMessageStorage(V8InspectorImpl*, int contextGroupId); + ~V8ConsoleMessageStorage(); + + int contextGroupId() { return m_contextGroupId; } + const std::deque>& messages() const { + return m_messages; + } + + void addMessage(std::unique_ptr); + void contextDestroyed(int contextId); + void clear(); + + bool shouldReportDeprecationMessage(int contextId, const String16& method); + int count(int contextId, const String16& id); + bool countReset(int contextId, const String16& id); + void time(int contextId, const String16& id); + double timeLog(int contextId, const String16& id); + double timeEnd(int contextId, const String16& id); + bool hasTimer(int contextId, const String16& id); + + private: + V8InspectorImpl* m_inspector; + int m_contextGroupId; + int m_estimatedSize = 0; + std::deque> m_messages; + + struct PerContextData { + std::set m_reportedDeprecationMessages; + // Corresponds to https://console.spec.whatwg.org/#count-map + std::map m_count; + // Corresponds to https://console.spec.whatwg.org/#timer-table + std::map m_time; + }; + std::map m_data; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_CONSOLE_MESSAGE_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-console.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-console.h new file mode 100644 index 000000000..8d2258f71 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-console.h @@ -0,0 +1,249 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_CONSOLE_H_ +#define V8_INSPECTOR_V8_CONSOLE_H_ + +#include + +#include "include/v8-array-buffer.h" +#include "include/v8-external.h" +#include "include/v8-local-handle.h" +#include "src/base/macros.h" +#include "src/debug/interface-types.h" + +namespace v8 { +class ObjectTemplate; +class Set; +} // namespace v8 + +namespace v8_inspector { + +class InspectedContext; +class TaskInfo; +class V8InspectorImpl; + +// Console API +// https://console.spec.whatwg.org/#console-namespace +class V8Console : public v8::debug::ConsoleDelegate { + public: + v8::Local createCommandLineAPI(v8::Local context, + int sessionId); + void installMemoryGetter(v8::Local context, + v8::Local console); + void installAsyncStackTaggingAPI(v8::Local context, + v8::Local console); + void cancelConsoleTask(TaskInfo* taskInfo); + + std::map>& AllConsoleTasksForTest() { + return m_tasks; + } + + class V8_NODISCARD CommandLineAPIScope { + public: + CommandLineAPIScope(v8::Local, + v8::Local commandLineAPI, + v8::Local global); + ~CommandLineAPIScope(); + CommandLineAPIScope(const CommandLineAPIScope&) = delete; + CommandLineAPIScope& operator=(const CommandLineAPIScope&) = delete; + + private: + static void accessorGetterCallback( + v8::Local, const v8::PropertyCallbackInfo&); + static void accessorSetterCallback(v8::Local, + v8::Local, + const v8::PropertyCallbackInfo&); + + v8::Local m_context; + v8::Local m_commandLineAPI; + v8::Local m_global; + v8::Local m_installedMethods; + v8::Local m_thisReference; + }; + + explicit V8Console(V8InspectorImpl* inspector); + + private: + friend class TaskInfo; + + void Debug(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Error(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Info(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Log(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Warn(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Dir(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void DirXml(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Table(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Trace(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Group(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void GroupCollapsed(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void GroupEnd(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Clear(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Count(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void CountReset(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Assert(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Profile(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void ProfileEnd(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void Time(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void TimeLog(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void TimeEnd(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + void TimeStamp(const v8::debug::ConsoleCallArguments&, + const v8::debug::ConsoleContext& consoleContext) override; + + template &)> + static void call(const v8::FunctionCallbackInfo& info) { + V8Console* console = + static_cast(info.Data().As()->Value()); + (console->*func)(info); + } + using CommandLineAPIData = std::pair; + template &, + int)> + static void call(const v8::FunctionCallbackInfo& info) { + CommandLineAPIData* data = static_cast( + info.Data().As()->GetBackingStore()->Data()); + (data->first->*func)(info, data->second); + } + template + static void call(const v8::FunctionCallbackInfo& info) { + CommandLineAPIData* data = static_cast( + info.Data().As()->GetBackingStore()->Data()); + v8::debug::ConsoleCallArguments args(info); + (data->first->*func)(args, v8::debug::ConsoleContext()); + } + + // TODO(foolip): There is no spec for the Memory Info API, see blink-dev: + // https://groups.google.com/a/chromium.org/d/msg/blink-dev/g5YRCGpC9vs/b4OJz71NmPwJ + void memoryGetterCallback(const v8::FunctionCallbackInfo&); + void memorySetterCallback(const v8::FunctionCallbackInfo&); + + void createTask(const v8::FunctionCallbackInfo&); + void runTask(const v8::FunctionCallbackInfo&); + + // CommandLineAPI + void keysCallback(const v8::FunctionCallbackInfo&, int sessionId); + void valuesCallback(const v8::FunctionCallbackInfo&, + int sessionId); + void debugFunctionCallback(const v8::FunctionCallbackInfo&, + int sessionId); + void undebugFunctionCallback(const v8::FunctionCallbackInfo&, + int sessionId); + void monitorFunctionCallback(const v8::FunctionCallbackInfo&, + int sessionId); + void unmonitorFunctionCallback(const v8::FunctionCallbackInfo&, + int sessionId); + void lastEvaluationResultCallback(const v8::FunctionCallbackInfo&, + int sessionId); + void inspectCallback(const v8::FunctionCallbackInfo&, + int sessionId); + void copyCallback(const v8::FunctionCallbackInfo&, int sessionId); + void inspectedObject(const v8::FunctionCallbackInfo&, + int sessionId, unsigned num); + void inspectedObject0(const v8::FunctionCallbackInfo& info, + int sessionId) { + inspectedObject(info, sessionId, 0); + } + void inspectedObject1(const v8::FunctionCallbackInfo& info, + int sessionId) { + inspectedObject(info, sessionId, 1); + } + void inspectedObject2(const v8::FunctionCallbackInfo& info, + int sessionId) { + inspectedObject(info, sessionId, 2); + } + void inspectedObject3(const v8::FunctionCallbackInfo& info, + int sessionId) { + inspectedObject(info, sessionId, 3); + } + void inspectedObject4(const v8::FunctionCallbackInfo& info, + int sessionId) { + inspectedObject(info, sessionId, 4); + } + void queryObjectsCallback(const v8::FunctionCallbackInfo& info, + int sessionId); + + // Lazily creates m_taskInfoKey and returns a local handle to it. We can't + // initialize m_taskInfoKey in the constructor as it would be part of + // Chromium's context snapshot. + v8::Local taskInfoKey(); + + // Lazily creates m_taskTemplate and returns a local handle to it. + // Similarly to m_taskInfoKey, we can't create the template upfront as to not + // be part of Chromium's context snapshot. + v8::Local taskTemplate(); + + V8InspectorImpl* m_inspector; + + // All currently alive tasks. We mark tasks immediately as weak when created + // but we need the finalizer to cancel the task when GC cleans them up. + std::map> m_tasks; + + // We use a private symbol to stash the `TaskInfo` as an v8::External on the + // JS task objects created by `console.createTask`. + v8::Global m_taskInfoKey; + + // We cache the task template for the async stack tagging API for faster + // instantiation. Use `taskTemplate()` to retrieve the lazily created + // template. + v8::Global m_taskTemplate; +}; + +/** + * Each JS task object created via `console.createTask` has a corresponding + * `TaskInfo` object on the C++ side (in a 1:1 relationship). + * + * The `TaskInfo` holds on weakly to the JS task object. + * The JS task objects uses a private symbol to store a pointer to the + * `TaskInfo` object (via v8::External). + * + * The `TaskInfo` objects holds all the necessary information we need to + * properly cancel the corresponding async task then the JS task object + * gets GC'ed. + */ +class TaskInfo { + public: + TaskInfo(v8::Isolate* isolate, V8Console* console, + v8::Local task); + + // For these task IDs we duplicate the ID logic from blink and use even + // pointers compared to the odd IDs we use for promises. This guarantees that + // we don't have any conflicts between task IDs. + void* Id() const { + return reinterpret_cast(reinterpret_cast(this) << 1); + } + + // After calling `Cancel` the `TaskInfo` instance is destroyed. + void Cancel() { m_console->cancelConsoleTask(this); } + + private: + v8::Global m_task; + V8Console* m_console = nullptr; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_CONSOLE_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-agent-impl.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-agent-impl.h new file mode 100644 index 000000000..9853312bc --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-agent-impl.h @@ -0,0 +1,282 @@ +// Copyright 2015 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_DEBUGGER_AGENT_IMPL_H_ +#define V8_INSPECTOR_V8_DEBUGGER_AGENT_IMPL_H_ + +#include +#include +#include +#include + +#include "src/base/enum-set.h" +#include "src/base/macros.h" +#include "src/debug/debug-interface.h" +#include "src/inspector/protocol/Debugger.h" +#include "src/inspector/protocol/Forward.h" + +namespace v8_inspector { + +struct ScriptBreakpoint; +class DisassemblyCollectorImpl; +class V8Debugger; +class V8DebuggerScript; +class V8InspectorImpl; +class V8InspectorSessionImpl; +class V8Regex; + +using protocol::Maybe; +using protocol::Response; + +class V8DebuggerAgentImpl : public protocol::Debugger::Backend { + public: + enum BreakpointSource { + UserBreakpointSource, + DebugCommandBreakpointSource, + MonitorCommandBreakpointSource + }; + + V8DebuggerAgentImpl(V8InspectorSessionImpl*, protocol::FrontendChannel*, + protocol::DictionaryValue* state); + ~V8DebuggerAgentImpl() override; + V8DebuggerAgentImpl(const V8DebuggerAgentImpl&) = delete; + V8DebuggerAgentImpl& operator=(const V8DebuggerAgentImpl&) = delete; + void restore(); + void stop(); + + // Part of the protocol. + Response enable(Maybe maxScriptsCacheSize, + String16* outDebuggerId) override; + Response disable() override; + Response setBreakpointsActive(bool active) override; + Response setSkipAllPauses(bool skip) override; + Response setBreakpointByUrl( + int lineNumber, Maybe optionalURL, + Maybe optionalURLRegex, Maybe optionalScriptHash, + Maybe optionalColumnNumber, Maybe optionalCondition, + String16*, + std::unique_ptr>* locations) + override; + Response setBreakpoint( + std::unique_ptr, + Maybe optionalCondition, String16*, + std::unique_ptr* actualLocation) override; + Response setBreakpointOnFunctionCall(const String16& functionObjectId, + Maybe optionalCondition, + String16* outBreakpointId) override; + Response setInstrumentationBreakpoint(const String16& instrumentation, + String16* outBreakpointId) override; + Response removeBreakpoint(const String16& breakpointId) override; + Response continueToLocation(std::unique_ptr, + Maybe targetCallFrames) override; + Response getStackTrace( + std::unique_ptr inStackTraceId, + std::unique_ptr* outStackTrace) override; + Response searchInContent( + const String16& scriptId, const String16& query, + Maybe optionalCaseSensitive, Maybe optionalIsRegex, + std::unique_ptr>*) + override; + Response getPossibleBreakpoints( + std::unique_ptr start, + Maybe end, Maybe restrictToFunction, + std::unique_ptr>* + locations) override; + Response setScriptSource( + const String16& inScriptId, const String16& inScriptSource, + Maybe dryRun, Maybe allowTopFrameEditing, + Maybe>* optOutCallFrames, + Maybe* optOutStackChanged, + Maybe* optOutAsyncStackTrace, + Maybe* optOutAsyncStackTraceId, + String16* outStatus, + Maybe* optOutCompileError) override; + Response restartFrame( + const String16& callFrameId, Maybe mode, + std::unique_ptr>* + newCallFrames, + Maybe* asyncStackTrace, + Maybe* asyncStackTraceId) override; + Response getScriptSource(const String16& scriptId, String16* scriptSource, + Maybe* bytecode) override; + Response disassembleWasmModule( + const String16& in_scriptId, Maybe* out_streamId, + int* out_totalNumberOfLines, + std::unique_ptr>* out_functionBodyOffsets, + std::unique_ptr* out_chunk) + override; + Response nextWasmDisassemblyChunk( + const String16& in_streamId, + std::unique_ptr* out_chunk) + override; + Response getWasmBytecode(const String16& scriptId, + protocol::Binary* bytecode) override; + Response pause() override; + Response resume(Maybe terminateOnResume) override; + Response stepOver(Maybe> + inSkipList) override; + Response stepInto(Maybe inBreakOnAsyncCall, + Maybe> + inSkipList) override; + Response stepOut() override; + Response pauseOnAsyncCall(std::unique_ptr + inParentStackTraceId) override; + Response setPauseOnExceptions(const String16& pauseState) override; + Response evaluateOnCallFrame( + const String16& callFrameId, const String16& expression, + Maybe objectGroup, Maybe includeCommandLineAPI, + Maybe silent, Maybe returnByValue, + Maybe generatePreview, Maybe throwOnSideEffect, + Maybe timeout, + std::unique_ptr* result, + Maybe*) override; + Response setVariableValue( + int scopeNumber, const String16& variableName, + std::unique_ptr newValue, + const String16& callFrame) override; + Response setReturnValue( + std::unique_ptr newValue) override; + Response setAsyncCallStackDepth(int depth) override; + Response setBlackboxPatterns( + std::unique_ptr> patterns) override; + Response setBlackboxedRanges( + const String16& scriptId, + std::unique_ptr> + positions) override; + + bool enabled() const { return m_enableState == kEnabled; } + + void setBreakpointFor(v8::Local function, + v8::Local condition, + BreakpointSource source); + void removeBreakpointFor(v8::Local function, + BreakpointSource source); + void schedulePauseOnNextStatement( + const String16& breakReason, + std::unique_ptr data); + void cancelPauseOnNextStatement(); + void breakProgram(const String16& breakReason, + std::unique_ptr data); + + void reset(); + + bool instrumentationFinished() { return m_instrumentationFinished; } + // Interface for V8InspectorImpl + void didPauseOnInstrumentation(v8::debug::BreakpointId instrumentationId); + + void didPause(int contextId, v8::Local exception, + const std::vector& hitBreakpoints, + v8::debug::ExceptionType exceptionType, bool isUncaught, + v8::debug::BreakReasons breakReasons); + void didContinue(); + void didParseSource(std::unique_ptr, bool success); + + bool isFunctionBlackboxed(const String16& scriptId, + const v8::debug::Location& start, + const v8::debug::Location& end); + bool shouldBeSkipped(const String16& scriptId, int line, int column); + + bool acceptsPause(bool isOOMBreak) const; + + void ScriptCollected(const V8DebuggerScript* script); + + v8::Isolate* isolate() { return m_isolate; } + + void clearBreakDetails(); + + private: + void enableImpl(); + + Response currentCallFrames( + std::unique_ptr>*); + std::unique_ptr currentAsyncStackTrace(); + std::unique_ptr currentExternalStackTrace(); + + void setPauseOnExceptionsImpl(int); + + std::unique_ptr setBreakpointImpl( + const String16& breakpointId, const String16& scriptId, + const String16& condition, int lineNumber, int columnNumber); + void setBreakpointImpl(const String16& breakpointId, + v8::Local function, + v8::Local condition); + void removeBreakpointImpl(const String16& breakpointId, + const std::vector& scripts); + + void internalSetAsyncCallStackDepth(int); + void increaseCachedSkipStackGeneration(); + + Response setBlackboxPattern(const String16& pattern); + void resetBlackboxedStateCache(); + + bool isPaused() const; + + void setScriptInstrumentationBreakpointIfNeeded(V8DebuggerScript* script); + + Response processSkipList( + protocol::Array* skipList); + + using ScriptsMap = + std::unordered_map>; + using BreakpointIdToDebuggerBreakpointIdsMap = + std::unordered_map>; + using DebuggerBreakpointIdToBreakpointIdMap = + std::unordered_map; + + enum EnableState { + kDisabled, + kEnabled, + kStopping, // This is the same as 'disabled', but it cannot become enabled + // again. + }; + + V8InspectorImpl* m_inspector; + V8Debugger* m_debugger; + V8InspectorSessionImpl* m_session; + EnableState m_enableState; + protocol::DictionaryValue* m_state; + protocol::Debugger::Frontend m_frontend; + v8::Isolate* m_isolate; + ScriptsMap m_scripts; + BreakpointIdToDebuggerBreakpointIdsMap m_breakpointIdToDebuggerBreakpointIds; + DebuggerBreakpointIdToBreakpointIdMap m_debuggerBreakpointIdToBreakpointId; + std::map> + m_wasmDisassemblies; + size_t m_nextWasmDisassemblyStreamId = 0; + + size_t m_maxScriptCacheSize = 0; + size_t m_cachedScriptSize = 0; + struct CachedScript { + String16 scriptId; + String16 source; + std::vector bytecode; + + size_t size() const { + return source.length() * sizeof(UChar) + bytecode.size(); + } + }; + std::deque m_cachedScripts; + + using BreakReason = + std::pair>; + std::vector m_breakReason; + + void pushBreakDetails( + const String16& breakReason, + std::unique_ptr breakAuxData); + void popBreakDetails(); + + bool m_skipAllPauses = false; + bool m_breakpointsActive = false; + bool m_instrumentationFinished = true; + + std::unique_ptr m_blackboxPattern; + std::unordered_map>> + m_blackboxedPositions; + std::unordered_map>> m_skipList; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_DEBUGGER_AGENT_IMPL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-barrier.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-barrier.h new file mode 100644 index 000000000..f4dc29611 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-barrier.h @@ -0,0 +1,28 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_DEBUGGER_BARRIER_H_ +#define V8_INSPECTOR_V8_DEBUGGER_BARRIER_H_ + +namespace v8_inspector { + +class V8InspectorClient; + +// This class is used to synchronize multiple sessions issuing +// `Runtime.runIfWaitingForDebbuger` so that the global client +// `runIfWaitingForDebugger` method is only invoked when all +// sessions have invoked `Runtime.runIfWaitingForDebugger`. +class V8DebuggerBarrier { + public: + V8DebuggerBarrier(V8InspectorClient* client, int contextGroupId); + ~V8DebuggerBarrier(); + + private: + V8InspectorClient* const m_client; + int m_contextGroupId; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_DEBUGGER_BARRIER_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-id.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-id.h new file mode 100644 index 000000000..757976d5b --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-id.h @@ -0,0 +1,41 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_DEBUGGER_ID_H_ +#define V8_INSPECTOR_V8_DEBUGGER_ID_H_ + +#include + +#include "include/v8-inspector.h" +#include "src/base/macros.h" +#include "src/inspector/protocol/Forward.h" + +namespace v8_inspector { +class V8InspectorImpl; + +namespace internal { + +class V8DebuggerId { + public: + V8DebuggerId() = default; + explicit V8DebuggerId(std::pair); + explicit V8DebuggerId(const String16&); + V8DebuggerId(const V8DebuggerId&) V8_NOEXCEPT = default; + V8DebuggerId& operator=(const V8DebuggerId&) V8_NOEXCEPT = default; + + static V8DebuggerId generate(V8InspectorImpl*); + + v8_inspector::V8DebuggerId toV8DebuggerId() const { return m_debugger_id; } + String16 toString() const; + bool isValid() const; + std::pair pair() const; + + private: + v8_inspector::V8DebuggerId m_debugger_id; +}; + +} // namespace internal +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_DEBUGGER_ID_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-script.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-script.h new file mode 100644 index 000000000..80b900c9b --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger-script.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef V8_INSPECTOR_V8_DEBUGGER_SCRIPT_H_ +#define V8_INSPECTOR_V8_DEBUGGER_SCRIPT_H_ + +#include + +#include "include/v8-local-handle.h" +#include "include/v8-maybe.h" +#include "src/base/macros.h" +#include "src/debug/debug-interface.h" +#include "src/inspector/string-16.h" +#include "src/inspector/string-util.h" + +namespace v8 { +class Isolate; +} + +namespace v8_inspector { + +class V8DebuggerAgentImpl; +class V8InspectorClient; + +class V8DebuggerScript { + public: + enum class Language { JavaScript, WebAssembly }; + static std::unique_ptr Create( + v8::Isolate* isolate, v8::Local script, + bool isLiveEdit, V8DebuggerAgentImpl* agent, V8InspectorClient* client); + + virtual ~V8DebuggerScript(); + V8DebuggerScript(const V8DebuggerScript&) = delete; + V8DebuggerScript& operator=(const V8DebuggerScript&) = delete; + + v8::Local scriptSource(); + const String16& scriptId() const { return m_id; } + bool hasSourceURLComment() const { return m_hasSourceURLComment; } + const String16& sourceURL() const { return m_url; } + const String16& embedderName() const { return m_embedderName; } + + virtual const String16& sourceMappingURL() const = 0; + virtual String16 source(size_t pos, size_t len = UINT_MAX) const = 0; + virtual Language getLanguage() const = 0; + virtual const String16& hash() const = 0; + virtual int startLine() const = 0; + virtual int startColumn() const = 0; + virtual int endLine() const = 0; + virtual int endColumn() const = 0; + virtual int codeOffset() const = 0; + int executionContextId() const { return m_executionContextId; } + virtual bool isLiveEdit() const = 0; + virtual bool isModule() const = 0; + virtual int length() const = 0; + + void setSourceURL(const String16&); + virtual void setSourceMappingURL(const String16&) = 0; + virtual void setSource(const String16& source, bool preview, + bool allowTopFrameLiveEditing, + v8::debug::LiveEditResult* result) = 0; + + virtual bool getPossibleBreakpoints( + const v8::debug::Location& start, const v8::debug::Location& end, + bool ignoreNestedFunctions, + std::vector* locations) = 0; + virtual void resetBlackboxedStateCache() = 0; + + virtual v8::Maybe offset(int lineNumber, int columnNumber) const = 0; + virtual v8::debug::Location location(int offset) const = 0; + + virtual bool setBreakpoint(const String16& condition, + v8::debug::Location* location, int* id) const = 0; + virtual void MakeWeak() = 0; + virtual bool setInstrumentationBreakpoint(int* id) const = 0; + +#if V8_ENABLE_WEBASSEMBLY + virtual v8::Maybe> wasmBytecode() const = 0; + virtual v8::Maybe + getDebugSymbolsType() const = 0; + virtual v8::Maybe getExternalDebugSymbolsURL() const = 0; + void removeWasmBreakpoint(int id); + virtual void Disassemble(v8::debug::DisassemblyCollector* collector, + std::vector* function_body_offsets) const = 0; +#endif // V8_ENABLE_WEBASSEMBLY + + protected: + V8DebuggerScript(v8::Isolate*, String16 id, String16 url, + String16 embedderName); + + virtual v8::Local script() const = 0; + + String16 m_id; + String16 m_url; + bool m_hasSourceURLComment = false; + int m_executionContextId = 0; + + v8::Isolate* m_isolate; + String16 m_embedderName; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_DEBUGGER_SCRIPT_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger.h new file mode 100644 index 000000000..7ec698d3e --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-debugger.h @@ -0,0 +1,308 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_DEBUGGER_H_ +#define V8_INSPECTOR_V8_DEBUGGER_H_ + +#include +#include +#include +#include +#include + +#include "include/v8-inspector.h" +#include "src/base/macros.h" +#include "src/inspector/inspected-context.h" +#include "src/inspector/protocol/Debugger.h" +#include "src/inspector/protocol/Forward.h" +#include "src/inspector/protocol/Runtime.h" +#include "src/inspector/v8-debugger-id.h" +#include "src/inspector/v8-debugger-script.h" + +namespace v8_inspector { + +class AsyncStackTrace; +class StackFrame; +class V8Debugger; +class V8DebuggerAgentImpl; +class V8InspectorImpl; +class V8RuntimeAgentImpl; +class V8StackTraceImpl; +struct V8StackTraceId; + +enum class WrapMode { + kForceValue, + kNoPreview, + kWithPreview, + kGenerateWebDriverValue +}; + +using protocol::Response; +using TerminateExecutionCallback = + protocol::Runtime::Backend::TerminateExecutionCallback; + +class V8Debugger : public v8::debug::DebugDelegate, + public v8::debug::AsyncEventDelegate { + public: + V8Debugger(v8::Isolate*, V8InspectorImpl*); + ~V8Debugger() override; + V8Debugger(const V8Debugger&) = delete; + V8Debugger& operator=(const V8Debugger&) = delete; + + bool enabled() const; + v8::Isolate* isolate() const { return m_isolate; } + + void setBreakpointsActive(bool); + + v8::debug::ExceptionBreakState getPauseOnExceptionsState(); + void setPauseOnExceptionsState(v8::debug::ExceptionBreakState); + bool canBreakProgram(); + bool isInInstrumentationPause() const; + void breakProgram(int targetContextGroupId); + void interruptAndBreak(int targetContextGroupId); + void requestPauseAfterInstrumentation(); + void continueProgram(int targetContextGroupId, + bool terminateOnResume = false); + void breakProgramOnAssert(int targetContextGroupId); + + void setPauseOnNextCall(bool, int targetContextGroupId); + void stepIntoStatement(int targetContextGroupId, bool breakOnAsyncCall); + void stepOverStatement(int targetContextGroupId); + void stepOutOfFunction(int targetContextGroupId); + + void terminateExecution(v8::Local context, + std::unique_ptr callback); + + Response continueToLocation(int targetContextGroupId, + V8DebuggerScript* script, + std::unique_ptr, + const String16& targetCallFramess); + bool restartFrame(int targetContextGroupId, int callFrameOrdinal); + + // Each script inherits debug data from v8::Context where it has been + // compiled. + // Only scripts whose debug data matches |contextGroupId| will be reported. + // Passing 0 will result in reporting all scripts. + std::vector> getCompiledScripts( + int contextGroupId, V8DebuggerAgentImpl* agent); + void enable(); + void disable(); + + bool isPaused() const { return m_pausedContextGroupId; } + bool isPausedInContextGroup(int contextGroupId) const; + + int maxAsyncCallChainDepth() { return m_maxAsyncCallStackDepth; } + void setAsyncCallStackDepth(V8DebuggerAgentImpl*, int); + + int maxCallStackSizeToCapture() const { return m_maxCallStackSizeToCapture; } + void setMaxCallStackSizeToCapture(V8RuntimeAgentImpl*, int); + + std::shared_ptr currentAsyncParent(); + V8StackTraceId currentExternalParent(); + + std::shared_ptr symbolize(v8::Local v8Frame); + + std::unique_ptr createStackTrace(v8::Local); + std::unique_ptr captureStackTrace(bool fullStack); + + v8::MaybeLocal internalProperties(v8::Local, + v8::Local); + + v8::Local queryObjects(v8::Local context, + v8::Local prototype); + + void asyncTaskScheduled(const StringView& taskName, void* task, + bool recurring); + void asyncTaskCanceled(void* task); + void asyncTaskStarted(void* task); + void asyncTaskFinished(void* task); + void allAsyncTasksCanceled(); + + V8StackTraceId storeCurrentStackTrace(const StringView& description); + void externalAsyncTaskStarted(const V8StackTraceId& parent); + void externalAsyncTaskFinished(const V8StackTraceId& parent); + + uintptr_t storeStackTrace(std::shared_ptr stack); + + void muteScriptParsedEvents(); + void unmuteScriptParsedEvents(); + + V8InspectorImpl* inspector() { return m_inspector; } + + void setMaxAsyncTaskStacksForTest(int limit); + void dumpAsyncTaskStacksStateForTest(); + + internal::V8DebuggerId debuggerIdFor(int contextGroupId); + std::shared_ptr stackTraceFor(int contextGroupId, + const V8StackTraceId& id); + + void reportTermination(); + + private: + bool addInternalObject(v8::Local context, + v8::Local object, + V8InternalValueType type); + + void clearContinueToLocation(); + bool shouldContinueToCurrentLocation(); + + static size_t nearHeapLimitCallback(void* data, size_t current_heap_limit, + size_t initial_heap_limit); + static void terminateExecutionCompletedCallback(v8::Isolate* isolate); + static void terminateExecutionCompletedCallbackIgnoringData( + v8::Isolate* isolate, void*); + void handleProgramBreak( + v8::Local pausedContext, v8::Local exception, + const std::vector& hitBreakpoints, + v8::debug::BreakReasons break_reasons, + v8::debug::ExceptionType exception_type = v8::debug::kException, + bool isUncaught = false); + + enum ScopeTargetKind { + FUNCTION, + GENERATOR, + }; + v8::MaybeLocal getTargetScopes(v8::Local, + v8::Local, + ScopeTargetKind); + + v8::MaybeLocal functionScopes(v8::Local, + v8::Local); + v8::MaybeLocal generatorScopes(v8::Local, + v8::Local); + v8::MaybeLocal collectionsEntries(v8::Local context, + v8::Local value); + + void asyncTaskScheduledForStack(const StringView& taskName, void* task, + bool recurring, bool skipTopFrame = false); + void asyncTaskCanceledForStack(void* task); + void asyncTaskStartedForStack(void* task); + void asyncTaskFinishedForStack(void* task); + + void asyncTaskCandidateForStepping(void* task); + void asyncTaskStartedForStepping(void* task); + void asyncTaskFinishedForStepping(void* task); + void asyncTaskCanceledForStepping(void* task); + + // v8::debug::DebugEventListener implementation. + void AsyncEventOccurred(v8::debug::DebugAsyncActionType type, int id, + bool isBlackboxed) override; + void ScriptCompiled(v8::Local script, bool is_live_edited, + bool has_compile_error) override; + void BreakProgramRequested( + v8::Local paused_context, + const std::vector& break_points_hit, + v8::debug::BreakReasons break_reasons) override; + ActionAfterInstrumentation BreakOnInstrumentation( + v8::Local paused_context, v8::debug::BreakpointId) override; + void ExceptionThrown(v8::Local paused_context, + v8::Local exception, + v8::Local promise, bool is_uncaught, + v8::debug::ExceptionType exception_type) override; + bool IsFunctionBlackboxed(v8::Local script, + const v8::debug::Location& start, + const v8::debug::Location& end) override; + + bool ShouldBeSkipped(v8::Local script, int line, + int column) override; + + int currentContextGroupId(); + + bool hasScheduledBreakOnNextFunctionCall() const; + + void quitMessageLoopIfAgentsFinishedInstrumentation(); + + v8::Isolate* m_isolate; + V8InspectorImpl* m_inspector; + int m_enableCount; + + int m_breakpointsActiveCount = 0; + int m_ignoreScriptParsedEventsCounter; + size_t m_originalHeapLimit = 0; + bool m_scheduledOOMBreak = false; + int m_targetContextGroupId = 0; + int m_pausedContextGroupId = 0; + bool m_instrumentationPause = false; + bool m_requestedPauseAfterInstrumentation = false; + int m_continueToLocationBreakpointId; + String16 m_continueToLocationTargetCallFrames; + std::unique_ptr m_continueToLocationStack; + + // We cache symbolized stack frames by (scriptId,lineNumber,columnNumber) + // to reduce memory pressure for huge web apps with lots of deep async + // stacks. + struct CachedStackFrameKey { + int scriptId; + int lineNumber; + int columnNumber; + + struct Equal { + bool operator()(CachedStackFrameKey const& a, + CachedStackFrameKey const& b) const { + return a.scriptId == b.scriptId && a.lineNumber == b.lineNumber && + a.columnNumber == b.columnNumber; + } + }; + + struct Hash { + size_t operator()(CachedStackFrameKey const& key) const { + size_t code = 0; + code = code * 31 + key.scriptId; + code = code * 31 + key.lineNumber; + code = code * 31 + key.columnNumber; + return code; + } + }; + }; + std::unordered_map, + CachedStackFrameKey::Hash, CachedStackFrameKey::Equal> + m_cachedStackFrames; + + using AsyncTaskToStackTrace = + std::unordered_map>; + AsyncTaskToStackTrace m_asyncTaskStacks; + std::unordered_set m_recurringTasks; + + size_t m_maxAsyncCallStacks; + int m_maxAsyncCallStackDepth; + int m_maxCallStackSizeToCapture; + + std::vector m_currentTasks; + std::vector> m_currentAsyncParent; + std::vector m_currentExternalParent; + + void collectOldAsyncStacksIfNeeded(); + // V8Debugger owns all the async stacks, while most of the other references + // are weak, which allows to collect some stacks when there are too many. + std::list> m_allAsyncStacks; + + std::unordered_map m_maxAsyncCallStackDepthMap; + std::unordered_map m_maxCallStackSizeToCaptureMap; + void* m_taskWithScheduledBreak = nullptr; + + // If any of the following three is true, we schedule pause on next JS + // execution using SetBreakOnNextFunctionCall. + bool m_externalAsyncTaskPauseRequested = false; // External async task. + bool m_taskWithScheduledBreakPauseRequested = false; // Local async task. + bool m_pauseOnNextCallRequested = false; // setPauseOnNextCall API call. + + v8::debug::ExceptionBreakState m_pauseOnExceptionsState; + // Whether we should pause on async call execution (if any) while stepping in. + // See Debugger.stepInto for details. + bool m_pauseOnAsyncCall = false; + + using StackTraceIdToStackTrace = + std::unordered_map>; + StackTraceIdToStackTrace m_storedStackTraces; + uintptr_t m_lastStackTraceId = 0; + + std::unordered_map m_contextGroupIdToDebuggerId; + + std::unique_ptr m_terminateExecutionCallback; + v8::Global m_terminateExecutionCallbackContext; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_DEBUGGER_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-heap-profiler-agent-impl.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-heap-profiler-agent-impl.h new file mode 100644 index 000000000..61c6b6af5 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-heap-profiler-agent-impl.h @@ -0,0 +1,86 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_HEAP_PROFILER_AGENT_IMPL_H_ +#define V8_INSPECTOR_V8_HEAP_PROFILER_AGENT_IMPL_H_ + +#include + +#include "src/base/macros.h" +#include "src/inspector/protocol/Forward.h" +#include "src/inspector/protocol/HeapProfiler.h" + +namespace v8 { +class Isolate; +} + +namespace v8_inspector { + +class V8InspectorSessionImpl; + +using protocol::Maybe; +using protocol::Response; + +class V8HeapProfilerAgentImpl : public protocol::HeapProfiler::Backend { + public: + V8HeapProfilerAgentImpl(V8InspectorSessionImpl*, protocol::FrontendChannel*, + protocol::DictionaryValue* state); + ~V8HeapProfilerAgentImpl() override; + V8HeapProfilerAgentImpl(const V8HeapProfilerAgentImpl&) = delete; + V8HeapProfilerAgentImpl& operator=(const V8HeapProfilerAgentImpl&) = delete; + void restore(); + + void collectGarbage( + std::unique_ptr callback) override; + + Response enable() override; + Response startTrackingHeapObjects(Maybe trackAllocations) override; + Response stopTrackingHeapObjects(Maybe reportProgress, + Maybe treatGlobalObjectsAsRoots, + Maybe captureNumericValue, + Maybe exposeInternals) override; + + Response disable() override; + + Response takeHeapSnapshot(Maybe reportProgress, + Maybe treatGlobalObjectsAsRoots, + Maybe captureNumericValue, + Maybe exposeInternals) override; + + Response getObjectByHeapObjectId( + const String16& heapSnapshotObjectId, Maybe objectGroup, + std::unique_ptr* result) override; + Response addInspectedHeapObject( + const String16& inspectedHeapObjectId) override; + Response getHeapObjectId(const String16& objectId, + String16* heapSnapshotObjectId) override; + + Response startSampling(Maybe samplingInterval, + Maybe includeObjectsCollectedByMajorGC, + Maybe includeObjectsCollectedByMinorGC) override; + Response stopSampling( + std::unique_ptr*) override; + Response getSamplingProfile( + std::unique_ptr*) override; + + private: + struct AsyncGC; + class GCTask; + + void startTrackingHeapObjectsInternal(bool trackAllocations); + void stopTrackingHeapObjectsInternal(); + void requestHeapStatsUpdate(); + static void onTimer(void*); + + V8InspectorSessionImpl* m_session; + v8::Isolate* m_isolate; + protocol::HeapProfiler::Frontend m_frontend; + protocol::DictionaryValue* m_state; + bool m_hasTimer; + std::shared_ptr m_async_gc; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_HEAP_PROFILER_AGENT_IMPL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-inspector-impl.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-inspector-impl.h new file mode 100644 index 000000000..0d30cc59f --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-inspector-impl.h @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2010, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef V8_INSPECTOR_V8_INSPECTOR_IMPL_H_ +#define V8_INSPECTOR_V8_INSPECTOR_IMPL_H_ + +#include +#include +#include +#include + +#include "include/v8-inspector.h" +#include "src/base/macros.h" +#include "src/base/platform/mutex.h" +#include "src/inspector/injected-script.h" +#include "src/inspector/protocol/Protocol.h" + +namespace v8_inspector { + +class InspectedContext; +class V8Console; +class V8ConsoleMessageStorage; +class V8Debugger; +class V8DebuggerAgentImpl; +class V8DebuggerBarrier; +class V8InspectorSessionImpl; +class V8ProfilerAgentImpl; +class V8RuntimeAgentImpl; +class V8StackTraceImpl; + +class V8InspectorImpl : public V8Inspector { + public: + V8_EXPORT_PRIVATE V8InspectorImpl(v8::Isolate*, V8InspectorClient*); + ~V8InspectorImpl() override; + V8InspectorImpl(const V8InspectorImpl&) = delete; + V8InspectorImpl& operator=(const V8InspectorImpl&) = delete; + + v8::Isolate* isolate() const { return m_isolate; } + V8InspectorClient* client() { return m_client; } + V8Debugger* debugger() { return m_debugger.get(); } + int contextGroupId(v8::Local) const; + int contextGroupId(int contextId) const; + uint64_t isolateId() const { return m_isolateId; } + int resolveUniqueContextId(internal::V8DebuggerId uniqueId) const; + + v8::MaybeLocal compileAndRunInternalScript(v8::Local, + v8::Local); + v8::MaybeLocal compileScript(v8::Local, + const String16& code, + const String16& fileName); + v8::MaybeLocal regexContext(); + v8::MaybeLocal exceptionMetaDataContext(); + + // V8Inspector implementation. + std::unique_ptr connect(int contextGroupId, + V8Inspector::Channel*, + StringView state, + ClientTrustLevel, + SessionPauseState) override; + void contextCreated(const V8ContextInfo&) override; + void contextDestroyed(v8::Local) override; + v8::MaybeLocal contextById(int contextId) override; + V8DebuggerId uniqueDebuggerId(int contextId) override; + void contextCollected(int contextGroupId, int contextId); + void resetContextGroup(int contextGroupId) override; + void idleStarted() override; + void idleFinished() override; + unsigned exceptionThrown(v8::Local, StringView message, + v8::Local exception, + StringView detailedMessage, StringView url, + unsigned lineNumber, unsigned columnNumber, + std::unique_ptr, + int scriptId) override; + void exceptionRevoked(v8::Local, unsigned exceptionId, + StringView message) override; + std::unique_ptr createStackTrace( + v8::Local) override; + std::unique_ptr captureStackTrace(bool fullStack) override; + void asyncTaskScheduled(StringView taskName, void* task, + bool recurring) override; + void asyncTaskCanceled(void* task) override; + void asyncTaskStarted(void* task) override; + void asyncTaskFinished(void* task) override; + void allAsyncTasksCanceled() override; + + V8StackTraceId storeCurrentStackTrace(StringView description) override; + void externalAsyncTaskStarted(const V8StackTraceId& parent) override; + void externalAsyncTaskFinished(const V8StackTraceId& parent) override; + + V8_EXPORT_PRIVATE bool associateExceptionData( + v8::Local, v8::Local exception, + v8::Local key, v8::Local value) override; + + unsigned nextExceptionId() { return ++m_lastExceptionId; } + void muteExceptions(int contextGroupId); + void unmuteExceptions(int contextGroupId); + V8ConsoleMessageStorage* ensureConsoleMessageStorage(int contextGroupId); + bool hasConsoleMessageStorage(int contextGroupId); + void discardInspectedContext(int contextGroupId, int contextId); + void disconnect(V8InspectorSessionImpl*); + V8InspectorSessionImpl* sessionById(int contextGroupId, int sessionId); + InspectedContext* getContext(int groupId, int contextId) const; + InspectedContext* getContext(int contextId) const; + V8_EXPORT_PRIVATE V8Console* console(); + void forEachContext(int contextGroupId, + const std::function& callback); + void forEachSession( + int contextGroupId, + const std::function& callback); + int64_t generateUniqueId(); + V8_EXPORT_PRIVATE v8::MaybeLocal getAssociatedExceptionData( + v8::Local exception); + std::unique_ptr + getAssociatedExceptionDataForProtocol(v8::Local exception); + + class EvaluateScope { + public: + explicit EvaluateScope(const InjectedScript::Scope& scope); + ~EvaluateScope(); + + protocol::Response setTimeout(double timeout); + + private: + class TerminateTask; + struct CancelToken; + + const InjectedScript::Scope& m_scope; + v8::Isolate* m_isolate; + std::shared_ptr m_cancelToken; + v8::Isolate::SafeForTerminationScope m_safeForTerminationScope; + }; + + private: + v8::Isolate* m_isolate; + V8InspectorClient* m_client; + std::unique_ptr m_debugger; + v8::Global m_regexContext; + v8::Global m_exceptionMetaDataContext; + v8::Global m_exceptionMetaData; + unsigned m_lastExceptionId; + int m_lastContextId; + int m_lastSessionId = 0; + uint64_t m_isolateId; + + using MuteExceptionsMap = std::unordered_map; + MuteExceptionsMap m_muteExceptionsMap; + + using ContextByIdMap = + std::unordered_map>; + using ContextsByGroupMap = + std::unordered_map>; + ContextsByGroupMap m_contexts; + + // contextGroupId -> sessionId -> session + std::unordered_map> m_sessions; + // contextGroupId -> debugger barrier + std::unordered_map> m_debuggerBarriers; + + using ConsoleStorageMap = + std::unordered_map>; + ConsoleStorageMap m_consoleStorageMap; + + std::unordered_map m_contextIdToGroupIdMap; + std::map, int> m_uniqueIdToContextId; + + std::unique_ptr m_console; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_INSPECTOR_IMPL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-inspector-session-impl.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-inspector-session-impl.h new file mode 100644 index 000000000..9e443161c --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-inspector-session-impl.h @@ -0,0 +1,150 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_INSPECTOR_SESSION_IMPL_H_ +#define V8_INSPECTOR_V8_INSPECTOR_SESSION_IMPL_H_ + +#include +#include + +#include "src/base/macros.h" +#include "src/inspector/protocol/Forward.h" +#include "src/inspector/protocol/Runtime.h" +#include "src/inspector/protocol/Schema.h" + +#include "include/v8-inspector.h" + +namespace v8_inspector { + +class InjectedScript; +class RemoteObjectIdBase; +class V8ConsoleAgentImpl; +class V8DebuggerAgentImpl; +class V8DebuggerBarrier; +class V8InspectorImpl; +class V8HeapProfilerAgentImpl; +class V8ProfilerAgentImpl; +class V8RuntimeAgentImpl; +class V8SchemaAgentImpl; + +using protocol::Response; + +class V8InspectorSessionImpl : public V8InspectorSession, + public protocol::FrontendChannel { + public: + static std::unique_ptr create( + V8InspectorImpl*, int contextGroupId, int sessionId, + V8Inspector::Channel*, StringView state, + v8_inspector::V8Inspector::ClientTrustLevel, + std::shared_ptr); + ~V8InspectorSessionImpl() override; + V8InspectorSessionImpl(const V8InspectorSessionImpl&) = delete; + V8InspectorSessionImpl& operator=(const V8InspectorSessionImpl&) = delete; + + V8InspectorImpl* inspector() const { return m_inspector; } + V8ConsoleAgentImpl* consoleAgent() { return m_consoleAgent.get(); } + V8DebuggerAgentImpl* debuggerAgent() { return m_debuggerAgent.get(); } + V8SchemaAgentImpl* schemaAgent() { return m_schemaAgent.get(); } + V8ProfilerAgentImpl* profilerAgent() { return m_profilerAgent.get(); } + V8RuntimeAgentImpl* runtimeAgent() { return m_runtimeAgent.get(); } + int contextGroupId() const { return m_contextGroupId; } + int sessionId() const { return m_sessionId; } + + std::unique_ptr + initializeCommandLineAPIScope(int executionContextId) override; + + Response findInjectedScript(int contextId, InjectedScript*&); + Response findInjectedScript(RemoteObjectIdBase*, InjectedScript*&); + void reset(); + void discardInjectedScripts(); + void reportAllContexts(V8RuntimeAgentImpl*); + void setCustomObjectFormatterEnabled(bool); + std::unique_ptr wrapObject( + v8::Local, v8::Local, const String16& groupName, + bool generatePreview); + std::unique_ptr wrapTable( + v8::Local, v8::Local table, + v8::MaybeLocal columns); + std::vector> supportedDomainsImpl(); + Response unwrapObject(const String16& objectId, v8::Local*, + v8::Local*, String16* objectGroup); + void releaseObjectGroup(const String16& objectGroup); + + // V8InspectorSession implementation. + void dispatchProtocolMessage(StringView message) override; + std::vector state() override; + std::vector> supportedDomains() + override; + void addInspectedObject( + std::unique_ptr) override; + void schedulePauseOnNextStatement(StringView breakReason, + StringView breakDetails) override; + void cancelPauseOnNextStatement() override; + void breakProgram(StringView breakReason, StringView breakDetails) override; + void setSkipAllPauses(bool) override; + void resume(bool terminateOnResume = false) override; + void stepOver() override; + std::vector> + searchInTextByLines(StringView text, StringView query, bool caseSensitive, + bool isRegex) override; + void releaseObjectGroup(StringView objectGroup) override; + bool unwrapObject(std::unique_ptr*, StringView objectId, + v8::Local*, v8::Local*, + std::unique_ptr* objectGroup) override; + std::unique_ptr wrapObject( + v8::Local, v8::Local, StringView groupName, + bool generatePreview) override; + + V8InspectorSession::Inspectable* inspectedObject(unsigned num); + static const unsigned kInspectedObjectBufferSize = 5; + + void triggerPreciseCoverageDeltaUpdate(StringView occasion) override; + void stop() override; + + V8Inspector::ClientTrustLevel clientTrustLevel() { + return m_clientTrustLevel; + } + + private: + V8InspectorSessionImpl(V8InspectorImpl*, int contextGroupId, int sessionId, + V8Inspector::Channel*, StringView state, + V8Inspector::ClientTrustLevel, + std::shared_ptr); + protocol::DictionaryValue* agentState(const String16& name); + + // protocol::FrontendChannel implementation. + void SendProtocolResponse( + int callId, std::unique_ptr message) override; + void SendProtocolNotification( + std::unique_ptr message) override; + void FallThrough(int callId, v8_crdtp::span method, + v8_crdtp::span message) override; + void FlushProtocolNotifications() override; + + std::unique_ptr serializeForFrontend( + std::unique_ptr message); + int m_contextGroupId; + int m_sessionId; + V8InspectorImpl* m_inspector; + V8Inspector::Channel* m_channel; + bool m_customObjectFormatterEnabled; + + protocol::UberDispatcher m_dispatcher; + std::unique_ptr m_state; + + std::unique_ptr m_runtimeAgent; + std::unique_ptr m_debuggerAgent; + std::unique_ptr m_heapProfilerAgent; + std::unique_ptr m_profilerAgent; + std::unique_ptr m_consoleAgent; + std::unique_ptr m_schemaAgent; + std::vector> + m_inspectedObjects; + bool use_binary_protocol_ = false; + V8Inspector::ClientTrustLevel m_clientTrustLevel = V8Inspector::kUntrusted; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_INSPECTOR_SESSION_IMPL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-profiler-agent-impl.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-profiler-agent-impl.h new file mode 100644 index 000000000..6e76ff57f --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-profiler-agent-impl.h @@ -0,0 +1,83 @@ +// Copyright 2015 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_PROFILER_AGENT_IMPL_H_ +#define V8_INSPECTOR_V8_PROFILER_AGENT_IMPL_H_ + +#include +#include + +#include "src/base/macros.h" +#include "src/inspector/protocol/Forward.h" +#include "src/inspector/protocol/Profiler.h" + +namespace v8 { +class CpuProfiler; +class Isolate; +} // namespace v8 + +namespace v8_inspector { + +class V8InspectorSessionImpl; + +using protocol::Maybe; +using protocol::Response; + +class V8ProfilerAgentImpl : public protocol::Profiler::Backend { + public: + V8ProfilerAgentImpl(V8InspectorSessionImpl*, protocol::FrontendChannel*, + protocol::DictionaryValue* state); + ~V8ProfilerAgentImpl() override; + V8ProfilerAgentImpl(const V8ProfilerAgentImpl&) = delete; + V8ProfilerAgentImpl& operator=(const V8ProfilerAgentImpl&) = delete; + + bool enabled() const { return m_enabled; } + void restore(); + + Response enable() override; + Response disable() override; + Response setSamplingInterval(int) override; + Response start() override; + Response stop(std::unique_ptr*) override; + + Response startPreciseCoverage(Maybe binary, Maybe detailed, + Maybe allow_triggered_updates, + double* out_timestamp) override; + Response stopPreciseCoverage() override; + Response takePreciseCoverage( + std::unique_ptr>* + out_result, + double* out_timestamp) override; + Response getBestEffortCoverage( + std::unique_ptr>* + out_result) override; + + void consoleProfile(const String16& title); + void consoleProfileEnd(const String16& title); + + void triggerPreciseCoverageDeltaUpdate(const String16& occasion); + + private: + String16 nextProfileId(); + + void startProfiling(const String16& title); + std::unique_ptr stopProfiling( + const String16& title, bool serialize); + + V8InspectorSessionImpl* m_session; + v8::Isolate* m_isolate; + v8::CpuProfiler* m_profiler = nullptr; + protocol::DictionaryValue* m_state; + protocol::Profiler::Frontend m_frontend; + bool m_enabled = false; + bool m_recordingCPUProfile = false; + class ProfileDescriptor; + std::vector m_startedProfiles; + String16 m_frontendInitiatedProfileId; + int m_startedProfilesCount = 0; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_PROFILER_AGENT_IMPL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-regex.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-regex.h new file mode 100644 index 000000000..75d972f15 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-regex.h @@ -0,0 +1,41 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_REGEX_H_ +#define V8_INSPECTOR_V8_REGEX_H_ + +#include "include/v8-persistent-handle.h" +#include "src/base/macros.h" +#include "src/inspector/string-16.h" + +namespace v8 { +class RegExp; +} + +namespace v8_inspector { + +class V8InspectorImpl; + +enum MultilineMode { MultilineDisabled, MultilineEnabled }; + +class V8Regex { + public: + V8Regex(V8InspectorImpl*, const String16&, bool caseSensitive, + bool multiline = false); + V8Regex(const V8Regex&) = delete; + V8Regex& operator=(const V8Regex&) = delete; + int match(const String16&, int startFrom = 0, + int* matchLength = nullptr) const; + bool isValid() const { return !m_regex.IsEmpty(); } + const String16& errorMessage() const { return m_errorMessage; } + + private: + V8InspectorImpl* m_inspector; + v8::Global m_regex; + String16 m_errorMessage; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_REGEX_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-runtime-agent-impl.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-runtime-agent-impl.h new file mode 100644 index 000000000..4bb0e8711 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-runtime-agent-impl.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef V8_INSPECTOR_V8_RUNTIME_AGENT_IMPL_H_ +#define V8_INSPECTOR_V8_RUNTIME_AGENT_IMPL_H_ + +#include +#include + +#include "include/v8-persistent-handle.h" +#include "src/base/macros.h" +#include "src/inspector/protocol/Forward.h" +#include "src/inspector/protocol/Runtime.h" + +namespace v8 { +class Script; +} // namespace v8 + +namespace v8_inspector { + +class InjectedScript; +class InspectedContext; +class RemoteObjectIdBase; +class V8ConsoleMessage; +class V8DebuggerBarrier; +class V8InspectorImpl; +class V8InspectorSessionImpl; + +using protocol::Response; +using protocol::Maybe; + +class V8RuntimeAgentImpl : public protocol::Runtime::Backend { + public: + V8RuntimeAgentImpl(V8InspectorSessionImpl*, protocol::FrontendChannel*, + protocol::DictionaryValue* state, + std::shared_ptr); + ~V8RuntimeAgentImpl() override; + V8RuntimeAgentImpl(const V8RuntimeAgentImpl&) = delete; + V8RuntimeAgentImpl& operator=(const V8RuntimeAgentImpl&) = delete; + void restore(); + + // Part of the protocol. + Response enable() override; + Response disable() override; + void evaluate(const String16& expression, Maybe objectGroup, + Maybe includeCommandLineAPI, Maybe silent, + Maybe executionContextId, Maybe returnByValue, + Maybe generatePreview, Maybe userGesture, + Maybe awaitPromise, Maybe throwOnSideEffect, + Maybe timeout, Maybe disableBreaks, + Maybe replMode, Maybe allowUnsafeEvalBlockedByCSP, + Maybe uniqueContextId, + Maybe generateWebDriverValue, + std::unique_ptr) override; + void awaitPromise(const String16& promiseObjectId, Maybe returnByValue, + Maybe generatePreview, + std::unique_ptr) override; + void callFunctionOn( + const String16& expression, Maybe objectId, + Maybe> optionalArguments, + Maybe silent, Maybe returnByValue, + Maybe generatePreview, Maybe userGesture, + Maybe awaitPromise, Maybe executionContextId, + Maybe objectGroup, Maybe throwOnSideEffect, + Maybe uniqueContextId, Maybe generateWebDriverValue, + std::unique_ptr) override; + Response releaseObject(const String16& objectId) override; + Response getProperties( + const String16& objectId, Maybe ownProperties, + Maybe accessorPropertiesOnly, Maybe generatePreview, + Maybe nonIndexedPropertiesOnly, + std::unique_ptr>* + result, + Maybe>* + internalProperties, + Maybe>* + privateProperties, + Maybe*) override; + Response releaseObjectGroup(const String16& objectGroup) override; + Response runIfWaitingForDebugger() override; + Response setCustomObjectFormatterEnabled(bool) override; + Response setMaxCallStackSizeToCapture(int) override; + Response discardConsoleEntries() override; + Response compileScript(const String16& expression, const String16& sourceURL, + bool persistScript, Maybe executionContextId, + Maybe*, + Maybe*) override; + void runScript(const String16&, Maybe executionContextId, + Maybe objectGroup, Maybe silent, + Maybe includeCommandLineAPI, Maybe returnByValue, + Maybe generatePreview, Maybe awaitPromise, + std::unique_ptr) override; + Response queryObjects( + const String16& prototypeObjectId, Maybe objectGroup, + std::unique_ptr* objects) override; + Response globalLexicalScopeNames( + Maybe executionContextId, + std::unique_ptr>* outNames) override; + Response getIsolateId(String16* outIsolateId) override; + Response getHeapUsage(double* out_usedSize, double* out_totalSize) override; + void terminateExecution( + std::unique_ptr callback) override; + + Response addBinding(const String16& name, Maybe executionContextId, + Maybe executionContextName) override; + Response removeBinding(const String16& name) override; + void addBindings(InspectedContext* context); + Response getExceptionDetails(const String16& errorObjectId, + Maybe* + out_exceptionDetails) override; + + void reset(); + void reportExecutionContextCreated(InspectedContext*); + void reportExecutionContextDestroyed(InspectedContext*); + void inspect(std::unique_ptr objectToInspect, + std::unique_ptr hints, + int executionContextId); + void messageAdded(V8ConsoleMessage*); + bool enabled() const { return m_enabled; } + + private: + bool reportMessage(V8ConsoleMessage*, bool generatePreview); + + static void bindingCallback(const v8::FunctionCallbackInfo& args); + void bindingCalled(const String16& name, const String16& payload, + int executionContextId); + void addBinding(InspectedContext* context, const String16& name); + + V8InspectorSessionImpl* m_session; + protocol::DictionaryValue* m_state; + protocol::Runtime::Frontend m_frontend; + V8InspectorImpl* m_inspector; + std::shared_ptr m_debuggerBarrier; + bool m_enabled; + std::unordered_map>> + m_compiledScripts; + // Binding name -> executionContextIds mapping. + std::unordered_map> m_activeBindings; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_RUNTIME_AGENT_IMPL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-schema-agent-impl.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-schema-agent-impl.h new file mode 100644 index 000000000..448529514 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-schema-agent-impl.h @@ -0,0 +1,38 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_SCHEMA_AGENT_IMPL_H_ +#define V8_INSPECTOR_V8_SCHEMA_AGENT_IMPL_H_ + +#include + +#include "src/base/macros.h" +#include "src/inspector/protocol/Forward.h" +#include "src/inspector/protocol/Schema.h" + +namespace v8_inspector { + +class V8InspectorSessionImpl; + +using protocol::Response; + +class V8SchemaAgentImpl : public protocol::Schema::Backend { + public: + V8SchemaAgentImpl(V8InspectorSessionImpl*, protocol::FrontendChannel*, + protocol::DictionaryValue* state); + ~V8SchemaAgentImpl() override; + V8SchemaAgentImpl(const V8SchemaAgentImpl&) = delete; + V8SchemaAgentImpl& operator=(const V8SchemaAgentImpl&) = delete; + + Response getDomains( + std::unique_ptr>*) override; + + private: + V8InspectorSessionImpl* m_session; + protocol::Schema::Frontend m_frontend; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_SCHEMA_AGENT_IMPL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-stack-trace-impl.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-stack-trace-impl.h new file mode 100644 index 000000000..bf793b0b5 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-stack-trace-impl.h @@ -0,0 +1,153 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_STACK_TRACE_IMPL_H_ +#define V8_INSPECTOR_V8_STACK_TRACE_IMPL_H_ + +#include +#include + +#include "include/v8-inspector.h" +#include "include/v8-local-handle.h" +#include "src/base/macros.h" +#include "src/inspector/protocol/Runtime.h" +#include "src/inspector/string-16.h" + +namespace v8 { +class StackFrame; +class StackTrace; +} // namespace v8 + +namespace v8_inspector { + +class AsyncStackTrace; +class V8Debugger; +struct V8StackTraceId; + +class StackFrame { + public: + StackFrame(String16&& functionName, int scriptId, String16&& sourceURL, + int lineNumber, int columnNumber, bool hasSourceURLComment); + ~StackFrame() = default; + + const String16& functionName() const; + int scriptId() const; + const String16& sourceURL() const; + int lineNumber() const; // 0-based. + int columnNumber() const; // 0-based. + std::unique_ptr buildInspectorObject( + V8InspectorClient* client) const; + bool isEqual(StackFrame* frame) const; + + private: + String16 m_functionName; + int m_scriptId; + String16 m_sourceURL; + int m_lineNumber; // 0-based. + int m_columnNumber; // 0-based. + bool m_hasSourceURLComment; +}; + +class V8StackTraceImpl : public V8StackTrace { + public: + static constexpr int kDefaultMaxCallStackSizeToCapture = 200; + + static std::unique_ptr create(V8Debugger*, + v8::Local, + int maxStackSize); + static std::unique_ptr capture(V8Debugger*, + int maxStackSize); + + ~V8StackTraceImpl() override; + V8StackTraceImpl(const V8StackTraceImpl&) = delete; + V8StackTraceImpl& operator=(const V8StackTraceImpl&) = delete; + std::unique_ptr buildInspectorObjectImpl( + V8Debugger* debugger) const; + + std::unique_ptr buildInspectorObjectImpl( + V8Debugger* debugger, int maxAsyncDepth) const; + + // V8StackTrace implementation. + // This method drops the async stack trace. + std::unique_ptr clone() override; + StringView firstNonEmptySourceURL() const override; + bool isEmpty() const override; + StringView topSourceURL() const override; + int topLineNumber() const override; // 1-based. + int topColumnNumber() const override; // 1-based. + int topScriptId() const override; + StringView topFunctionName() const override; + std::unique_ptr buildInspectorObject( + int maxAsyncDepth) const override; + std::unique_ptr toString() const override; + + bool isEqualIgnoringTopFrame(V8StackTraceImpl* stackTrace) const; + + std::vector frames() const override; + + private: + V8StackTraceImpl(std::vector> frames, + int maxAsyncDepth, + std::shared_ptr asyncParent, + const V8StackTraceId& externalParent); + + class StackFrameIterator { + public: + explicit StackFrameIterator(const V8StackTraceImpl* stackTrace); + + void next(); + StackFrame* frame(); + bool done(); + + private: + std::vector>::const_iterator m_currentIt; + std::vector>::const_iterator m_currentEnd; + AsyncStackTrace* m_parent; + }; + + std::vector> m_frames; + int m_maxAsyncDepth; + std::weak_ptr m_asyncParent; + V8StackTraceId m_externalParent; +}; + +class AsyncStackTrace { + public: + AsyncStackTrace(const AsyncStackTrace&) = delete; + AsyncStackTrace& operator=(const AsyncStackTrace&) = delete; + static std::shared_ptr capture(V8Debugger*, + const String16& description, + bool skipTopFrame = false); + static uintptr_t store(V8Debugger* debugger, + std::shared_ptr stack); + + std::unique_ptr buildInspectorObject( + V8Debugger* debugger, int maxAsyncDepth) const; + + const String16& description() const; + std::weak_ptr parent() const; + bool isEmpty() const; + const V8StackTraceId& externalParent() const { return m_externalParent; } + + const std::vector>& frames() const { + return m_frames; + } + + private: + AsyncStackTrace(const String16& description, + std::vector> frames, + std::shared_ptr asyncParent, + const V8StackTraceId& externalParent); + + uintptr_t m_id; + String16 m_description; + + std::vector> m_frames; + std::weak_ptr m_asyncParent; + V8StackTraceId m_externalParent; +}; + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_STACK_TRACE_IMPL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-string-conversions.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-string-conversions.h new file mode 100644 index 000000000..eb33c6816 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-string-conversions.h @@ -0,0 +1,18 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_STRING_CONVERSIONS_H_ +#define V8_INSPECTOR_V8_STRING_CONVERSIONS_H_ + +#include +#include + +// Conversion routines between UT8 and UTF16, used by string-16.{h,cc}. You may +// want to use string-16.h directly rather than these. +namespace v8_inspector { +std::basic_string UTF8ToUTF16(const char* stringStart, size_t length); +std::string UTF16ToUTF8(const uint16_t* stringStart, size_t length); +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_STRING_CONVERSIONS_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-value-utils.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-value-utils.h new file mode 100644 index 000000000..7eae23d9b --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-value-utils.h @@ -0,0 +1,22 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_VALUE_UTILS_H_ +#define V8_INSPECTOR_V8_VALUE_UTILS_H_ + +#include "include/v8-local-handle.h" +#include "src/inspector/protocol/Protocol.h" + +namespace v8_inspector { + +v8::Maybe createDataProperty(v8::Local, + v8::Local, + v8::Local key, + v8::Local); +v8::Maybe createDataProperty(v8::Local, v8::Local, + int index, v8::Local); + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_VALUE_UTILS_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-webdriver-serializer.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-webdriver-serializer.h new file mode 100644 index 000000000..1a9c2396a --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/v8-webdriver-serializer.h @@ -0,0 +1,24 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_V8_WEBDRIVER_SERIALIZER_H_ +#define V8_INSPECTOR_V8_WEBDRIVER_SERIALIZER_H_ + +#include "include/v8-container.h" +#include "include/v8-context.h" +#include "include/v8-exception.h" +#include "include/v8-regexp.h" +#include "src/inspector/protocol/Runtime.h" +#include "src/inspector/v8-value-utils.h" + +namespace v8_inspector { +class V8WebDriverSerializer { + public: + static std::unique_ptr serializeV8Value( + v8::Local value, v8::Local context, + int max_depth); +}; +} // namespace v8_inspector + +#endif // V8_INSPECTOR_V8_WEBDRIVER_SERIALIZER_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/value-mirror.h b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/value-mirror.h new file mode 100644 index 000000000..71555680a --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/src/inspector/value-mirror.h @@ -0,0 +1,102 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INSPECTOR_VALUE_MIRROR_H_ +#define V8_INSPECTOR_VALUE_MIRROR_H_ + +#include + +#include "include/v8-inspector.h" +#include "include/v8-local-handle.h" +#include "src/base/macros.h" +#include "src/inspector/protocol/Protocol.h" +#include "src/inspector/protocol/Runtime.h" +#include "src/inspector/string-16.h" + +namespace v8_inspector { + +class ValueMirror; +enum class WrapMode; + +struct PrivatePropertyMirror { + String16 name; + std::unique_ptr value; + std::unique_ptr getter; + std::unique_ptr setter; +}; + +struct InternalPropertyMirror { + String16 name; + std::unique_ptr value; +}; + +struct PropertyMirror { + String16 name; + bool writable; + bool configurable; + bool enumerable; + bool isOwn; + bool isIndex; + bool isSynthetic; + std::unique_ptr value; + std::unique_ptr getter; + std::unique_ptr setter; + std::unique_ptr symbol; + std::unique_ptr exception; +}; + +class ValueMirror { + public: + virtual ~ValueMirror(); + + static std::unique_ptr create(v8::Local context, + v8::Local value); + virtual protocol::Response buildRemoteObject( + v8::Local context, WrapMode mode, + std::unique_ptr* result) const = 0; + virtual void buildPropertyPreview( + v8::Local context, const String16& name, + std::unique_ptr*) const {} + virtual void buildObjectPreview( + v8::Local context, bool generatePreviewForTable, + int* nameLimit, int* indexLimit, + std::unique_ptr*) const {} + virtual void buildEntryPreview( + v8::Local context, int* nameLimit, int* indexLimit, + std::unique_ptr*) const {} + virtual v8::Local v8Value() const = 0; + virtual std::unique_ptr + buildWebDriverValue(v8::Local context, int max_depth) const = 0; + + class PropertyAccumulator { + public: + virtual ~PropertyAccumulator() = default; + virtual bool Add(PropertyMirror mirror) = 0; + }; + static bool getProperties(v8::Local context, + v8::Local object, bool ownProperties, + bool accessorPropertiesOnly, + bool nonIndexedPropertiesOnly, + PropertyAccumulator* accumulator); + static void getInternalProperties( + v8::Local context, v8::Local object, + std::vector* mirrors); + static std::vector getPrivateProperties( + v8::Local context, v8::Local object, + bool accessorPropertiesOnly); +}; + +protocol::Response toProtocolValue(v8::Local context, + v8::Local value, int maxDepth, + std::unique_ptr* result); +protocol::Response arrayToProtocolValue( + v8::Local context, v8::Local array, int maxDepth, + std::unique_ptr* result); +protocol::Response objectToProtocolValue( + v8::Local context, v8::Local object, int maxDepth, + std::unique_ptr* result); + +} // namespace v8_inspector + +#endif // V8_INSPECTOR_VALUE_MIRROR_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/cbor.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/cbor.h new file mode 100644 index 000000000..9e71e0716 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/cbor.h @@ -0,0 +1,326 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_CBOR_H_ +#define V8_CRDTP_CBOR_H_ + +#include +#include +#include +#include +#include + +#include "export.h" +#include "parser_handler.h" +#include "span.h" + +namespace v8_crdtp { +namespace cbor { +// The binary encoding for the inspector protocol follows the CBOR specification +// (RFC 7049). Additional constraints: +// - Only indefinite length maps and arrays are supported. +// - Maps and arrays are wrapped with an envelope, that is, a +// CBOR tag with value 24 followed by a byte string specifying +// the byte length of the enclosed map / array. The byte string +// must use a 32 bit wide length. +// - At the top level, a message must be an indefinite length map +// wrapped by an envelope. +// - Maximal size for messages is 2^32 (4 GB). +// - For scalars, we support only the int32_t range, encoded as +// UNSIGNED/NEGATIVE (major types 0 / 1). +// - UTF16 strings, including with unbalanced surrogate pairs, are encoded +// as CBOR BYTE_STRING (major type 2). For such strings, the number of +// bytes encoded must be even. +// - UTF8 strings (major type 3) are supported. +// - 7 bit US-ASCII strings must always be encoded as UTF8 strings, never +// as UTF16 strings. +// - Arbitrary byte arrays, in the inspector protocol called 'binary', +// are encoded as BYTE_STRING (major type 2), prefixed with a byte +// indicating base64 when rendered as JSON. + +// ============================================================================= +// Detecting CBOR content +// ============================================================================= + +// Checks whether |msg| is a cbor message. +bool IsCBORMessage(span msg); + +// Performs a leightweight check of |msg|. +// Disallows: +// - Empty message +// - Not starting with the two bytes 0xd8, 0x5a +// - Empty envelope (all length bytes are 0) +// - Not starting with a map after the envelope stanza +// DevTools messages should pass this check. +Status CheckCBORMessage(span msg); + +// ============================================================================= +// Encoding individual CBOR items +// ============================================================================= + +// Some constants for CBOR tokens that only take a single byte on the wire. +uint8_t EncodeTrue(); +uint8_t EncodeFalse(); +uint8_t EncodeNull(); +uint8_t EncodeIndefiniteLengthArrayStart(); +uint8_t EncodeIndefiniteLengthMapStart(); +uint8_t EncodeStop(); + +// Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE| +// (major type 1) iff < 0. +void EncodeInt32(int32_t value, std::vector* out); + +// Encodes a UTF16 string as a BYTE_STRING (major type 2). Each utf16 +// character in |in| is emitted with most significant byte first, +// appending to |out|. +void EncodeString16(span in, std::vector* out); + +// Encodes a UTF8 string |in| as STRING (major type 3). +void EncodeString8(span in, std::vector* out); + +// Encodes the given |latin1| string as STRING8. +// If any non-ASCII character is present, it will be represented +// as a 2 byte UTF8 sequence. +void EncodeFromLatin1(span latin1, std::vector* out); + +// Encodes the given |utf16| string as STRING8 if it's entirely US-ASCII. +// Otherwise, encodes as STRING16. +void EncodeFromUTF16(span utf16, std::vector* out); + +// Encodes arbitrary binary data in |in| as a BYTE_STRING (major type 2) with +// definitive length, prefixed with tag 22 indicating expected conversion to +// base64 (see RFC 7049, Table 3 and Section 2.4.4.2). +void EncodeBinary(span in, std::vector* out); + +// Encodes / decodes a double as Major type 7 (SIMPLE_VALUE), +// with additional info = 27, followed by 8 bytes in big endian. +void EncodeDouble(double value, std::vector* out); + +// ============================================================================= +// cbor::EnvelopeEncoder - for wrapping submessages +// ============================================================================= + +// An envelope indicates the byte length of a wrapped item. +// We use this for maps and array, which allows the decoder +// to skip such (nested) values whole sale. +// It's implemented as a CBOR tag (major type 6) with additional +// info = 24, followed by a byte string with a 32 bit length value; +// so the maximal structure that we can wrap is 2^32 bits long. +// See also: https://tools.ietf.org/html/rfc7049#section-2.4.4.1 +class EnvelopeEncoder { + public: + // Emits the envelope start bytes and records the position for the + // byte size in |byte_size_pos_|. Also emits empty bytes for the + // byte sisze so that encoding can continue. + void EncodeStart(std::vector* out); + // This records the current size in |out| at position byte_size_pos_. + // Returns true iff successful. + bool EncodeStop(std::vector* out); + + private: + size_t byte_size_pos_ = 0; +}; + +class EnvelopeHeader { + public: + EnvelopeHeader() = default; + ~EnvelopeHeader() = default; + + // Parse envelope. Implies that `in` accomodates the entire size of envelope. + static StatusOr Parse(span in); + // Parse envelope, but allow `in` to only include the beginning of the + // envelope. + static StatusOr ParseFromFragment(span in); + + size_t header_size() const { return header_size_; } + size_t content_size() const { return content_size_; } + size_t outer_size() const { return header_size_ + content_size_; } + + private: + EnvelopeHeader(size_t header_size, size_t content_size) + : header_size_(header_size), content_size_(content_size) {} + + size_t header_size_ = 0; + size_t content_size_ = 0; +}; + +// ============================================================================= +// cbor::NewCBOREncoder - for encoding from a streaming parser +// ============================================================================= + +// This can be used to convert to CBOR, by passing the return value to a parser +// that drives it. The handler will encode into |out|, and iff an error occurs +// it will set |status| to an error and clear |out|. Otherwise, |status.ok()| +// will be |true|. +std::unique_ptr NewCBOREncoder(std::vector* out, + Status* status); + +// ============================================================================= +// cbor::CBORTokenizer - for parsing individual CBOR items +// ============================================================================= + +// Tags for the tokens within a CBOR message that CBORTokenizer understands. +// Note that this is not the same terminology as the CBOR spec (RFC 7049), +// but rather, our adaptation. For instance, we lump unsigned and signed +// major type into INT32 here (and disallow values outside the int32_t range). +enum class CBORTokenTag { + // Encountered an error in the structure of the message. Consult + // status() for details. + ERROR_VALUE, + // Booleans and NULL. + TRUE_VALUE, + FALSE_VALUE, + NULL_VALUE, + // An int32_t (signed 32 bit integer). + INT32, + // A double (64 bit floating point). + DOUBLE, + // A UTF8 string. + STRING8, + // A UTF16 string. + STRING16, + // A binary string. + BINARY, + // Starts an indefinite length map; after the map start we expect + // alternating keys and values, followed by STOP. + MAP_START, + // Starts an indefinite length array; after the array start we + // expect values, followed by STOP. + ARRAY_START, + // Ends a map or an array. + STOP, + // An envelope indicator, wrapping a map or array. + // Internally this carries the byte length of the wrapped + // map or array. While CBORTokenizer::Next() will read / skip the entire + // envelope, CBORTokenizer::EnterEnvelope() reads the tokens + // inside of it. + ENVELOPE, + // We've reached the end there is nothing else to read. + DONE, +}; + +// The major types from RFC 7049 Section 2.1. +enum class MajorType { + UNSIGNED = 0, + NEGATIVE = 1, + BYTE_STRING = 2, + STRING = 3, + ARRAY = 4, + MAP = 5, + TAG = 6, + SIMPLE_VALUE = 7 +}; + +// CBORTokenizer segments a CBOR message, presenting the tokens therein as +// numbers, strings, etc. This is not a complete CBOR parser, but makes it much +// easier to implement one (e.g. ParseCBOR, above). It can also be used to parse +// messages partially. +class CBORTokenizer { + public: + explicit CBORTokenizer(span bytes); + ~CBORTokenizer(); + + // Identifies the current token that we're looking at, + // or ERROR_VALUE (in which ase ::Status() has details) + // or DONE (if we're past the last token). + CBORTokenTag TokenTag() const; + + // Advances to the next token. + void Next(); + // Can only be called if TokenTag() == CBORTokenTag::ENVELOPE. + // While Next() would skip past the entire envelope / what it's + // wrapping, EnterEnvelope positions the cursor inside of the envelope, + // letting the client explore the nested structure. + void EnterEnvelope(); + + // If TokenTag() is CBORTokenTag::ERROR_VALUE, then Status().error describes + // the error more precisely; otherwise it'll be set to Error::OK. + // In either case, Status().pos is the current position. + struct Status Status() const; + + // The following methods retrieve the token values. They can only + // be called if TokenTag() matches. + + // To be called only if ::TokenTag() == CBORTokenTag::INT32. + int32_t GetInt32() const; + + // To be called only if ::TokenTag() == CBORTokenTag::DOUBLE. + double GetDouble() const; + + // To be called only if ::TokenTag() == CBORTokenTag::STRING8. + span GetString8() const; + + // Wire representation for STRING16 is low byte first (little endian). + // To be called only if ::TokenTag() == CBORTokenTag::STRING16. + span GetString16WireRep() const; + + // To be called only if ::TokenTag() == CBORTokenTag::BINARY. + span GetBinary() const; + + // To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE. + // Returns the envelope including its payload; message which + // can be passed to the CBORTokenizer constructor, which will + // then see the envelope token first (looking at it a second time, + // basically). + span GetEnvelope() const; + + // To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE. + // Returns only the payload inside the envelope, e.g., a map + // or an array. This is not a complete message by our + // IsCBORMessage definition, since it doesn't include the + // enclosing envelope (the header, basically). + span GetEnvelopeContents() const; + + // To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE. + // Returns the envelope header. + const EnvelopeHeader& GetEnvelopeHeader() const; + + private: + void ReadNextToken(); + void SetToken(CBORTokenTag token, size_t token_byte_length); + void SetError(Error error); + + const span bytes_; + CBORTokenTag token_tag_; + struct Status status_; + size_t token_byte_length_ = 0; + MajorType token_start_type_; + uint64_t token_start_internal_value_; + EnvelopeHeader envelope_header_; +}; + +// ============================================================================= +// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages +// ============================================================================= + +// Parses a CBOR encoded message from |bytes|, sending events to +// |out|. If an error occurs, sends |out->HandleError|, and parsing stops. +// The client is responsible for discarding the already received information in +// that case. +void ParseCBOR(span bytes, ParserHandler* out); + +// ============================================================================= +// cbor::AppendString8EntryToMap - for limited in-place editing of messages +// ============================================================================= + +// Modifies the |cbor| message by appending a new key/value entry at the end +// of the map. Patches up the envelope size; Status.ok() iff successful. +// If not successful, |cbor| may be corrupted after this call. +Status AppendString8EntryToCBORMap(span string8_key, + span string8_value, + std::vector* cbor); + +namespace internals { // Exposed only for writing tests. +size_t ReadTokenStart(span bytes, + cbor::MajorType* type, + uint64_t* value); + +void WriteTokenStart(cbor::MajorType type, + uint64_t value, + std::vector* encoded); +} // namespace internals +} // namespace cbor +} // namespace v8_crdtp + +#endif // V8_CRDTP_CBOR_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/dispatch.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/dispatch.h new file mode 100644 index 000000000..b035e2193 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/dispatch.h @@ -0,0 +1,313 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_DISPATCH_H_ +#define V8_CRDTP_DISPATCH_H_ + +#include +#include +#include +#include +#include +#include "export.h" +#include "serializable.h" +#include "span.h" +#include "status.h" + +namespace v8_crdtp { +class DeserializerState; +class ErrorSupport; +class FrontendChannel; +namespace cbor { +class CBORTokenizer; +} // namespace cbor + +// ============================================================================= +// DispatchResponse - Error status and chaining / fall through +// ============================================================================= +enum class DispatchCode { + SUCCESS = 1, + FALL_THROUGH = 2, + // For historical reasons, these error codes correspond to commonly used + // XMLRPC codes (e.g. see METHOD_NOT_FOUND in + // https://github.com/python/cpython/blob/main/Lib/xmlrpc/client.py). + PARSE_ERROR = -32700, + INVALID_REQUEST = -32600, + METHOD_NOT_FOUND = -32601, + INVALID_PARAMS = -32602, + INTERNAL_ERROR = -32603, + SERVER_ERROR = -32000, + SESSION_NOT_FOUND = SERVER_ERROR - 1, +}; + +// Information returned by command handlers. Usually returned after command +// execution attempts. +class DispatchResponse { + public: + const std::string& Message() const { return message_; } + + DispatchCode Code() const { return code_; } + + bool IsSuccess() const { return code_ == DispatchCode::SUCCESS; } + bool IsFallThrough() const { return code_ == DispatchCode::FALL_THROUGH; } + bool IsError() const { return code_ < DispatchCode::SUCCESS; } + + static DispatchResponse Success(); + static DispatchResponse FallThrough(); + + // Indicates that a message could not be parsed. E.g., malformed JSON. + static DispatchResponse ParseError(std::string message); + + // Indicates that a request is lacking required top-level properties + // ('id', 'method'), has top-level properties of the wrong type, or has + // unknown top-level properties. + static DispatchResponse InvalidRequest(std::string message); + + // Indicates that a protocol method such as "Page.bringToFront" could not be + // dispatched because it's not known to the (domain) dispatcher. + static DispatchResponse MethodNotFound(std::string message); + + // Indicates that the params sent to a domain handler are invalid. + static DispatchResponse InvalidParams(std::string message); + + // Used for application level errors, e.g. within protocol agents. + static DispatchResponse InternalError(); + + // Used for application level errors, e.g. within protocol agents. + static DispatchResponse ServerError(std::string message); + + // Indicate that session with the id specified in the protocol message + // was not found (e.g. because it has already been detached). + static DispatchResponse SessionNotFound(std::string message); + + private: + DispatchResponse() = default; + DispatchCode code_; + std::string message_; +}; + +// ============================================================================= +// Dispatchable - a shallow parser for CBOR encoded DevTools messages +// ============================================================================= + +// This parser extracts only the known top-level fields from a CBOR encoded map; +// method, id, sessionId, and params. +class Dispatchable { + public: + // This constructor parses the |serialized| message. If successful, + // |ok()| will yield |true|, and |Method()|, |SessionId()|, |CallId()|, + // |Params()| can be used to access, the extracted contents. Otherwise, + // |ok()| will yield |false|, and |DispatchError()| can be + // used to send a response or notification to the client. + explicit Dispatchable(span serialized); + + // The serialized message that we just parsed. + span Serialized() const { return serialized_; } + + // Yields true if parsing was successful. This is cheaper than calling + // ::DispatchError(). + bool ok() const; + + // If !ok(), returns a DispatchResponse with appropriate code and error + // which can be sent to the client as a response or notification. + DispatchResponse DispatchError() const; + + // Top level field: the command to be executed, fully qualified by + // domain. E.g. "Page.createIsolatedWorld". + span Method() const { return method_; } + // Used to identify protocol connections attached to a specific + // target. See Target.attachToTarget, Target.setAutoAttach. + span SessionId() const { return session_id_; } + // The call id, a sequence number that's used in responses to indicate + // the request to which the response belongs. + int32_t CallId() const { return call_id_; } + bool HasCallId() const { return has_call_id_; } + // The payload of the request in CBOR format. The |Dispatchable| parser does + // not parse into this; it only provides access to its raw contents here. + span Params() const { return params_; } + + private: + bool MaybeParseProperty(cbor::CBORTokenizer* tokenizer); + bool MaybeParseCallId(cbor::CBORTokenizer* tokenizer); + bool MaybeParseMethod(cbor::CBORTokenizer* tokenizer); + bool MaybeParseParams(cbor::CBORTokenizer* tokenizer); + bool MaybeParseSessionId(cbor::CBORTokenizer* tokenizer); + + span serialized_; + + Status status_; + + bool has_call_id_ = false; + int32_t call_id_; + span method_; + bool params_seen_ = false; + span params_; + span session_id_; +}; + +// ============================================================================= +// Helpers for creating protocol cresponses and notifications. +// ============================================================================= + +// The resulting notifications can be sent to a protocol client, +// usually via a FrontendChannel (see frontend_channel.h). + +std::unique_ptr CreateErrorResponse( + int callId, + DispatchResponse dispatch_response); + +std::unique_ptr CreateErrorNotification( + DispatchResponse dispatch_response); + +std::unique_ptr CreateResponse( + int callId, + std::unique_ptr params); + +std::unique_ptr CreateNotification( + const char* method, + std::unique_ptr params = nullptr); + +// ============================================================================= +// DomainDispatcher - Dispatching betwen protocol methods within a domain. +// ============================================================================= + +// This class is subclassed by |DomainDispatcherImpl|, which we generate per +// DevTools domain. It contains routines called from the generated code, +// e.g. ::MaybeReportInvalidParams, which are optimized for small code size. +// The most important method is ::Dispatch, which implements method dispatch +// by command name lookup. +class DomainDispatcher { + public: + class WeakPtr { + public: + explicit WeakPtr(DomainDispatcher*); + ~WeakPtr(); + DomainDispatcher* get() { return dispatcher_; } + void dispose() { dispatcher_ = nullptr; } + + private: + DomainDispatcher* dispatcher_; + }; + + class Callback { + public: + virtual ~Callback(); + void dispose(); + + protected: + // |method| must point at static storage (a C++ string literal in practice). + Callback(std::unique_ptr backend_impl, + int call_id, + span method, + span message); + + void sendIfActive(std::unique_ptr partialMessage, + const DispatchResponse& response); + void fallThroughIfActive(); + + private: + std::unique_ptr backend_impl_; + int call_id_; + // Subclasses of this class are instantiated from generated code which + // passes a string literal for the method name to the constructor. So the + // storage for |method| is the binary of the running process. + span method_; + std::vector message_; + }; + + explicit DomainDispatcher(FrontendChannel*); + virtual ~DomainDispatcher(); + + // Given a |command_name| without domain qualification, looks up the + // corresponding method. If the method is not found, returns nullptr. + // Otherwise, Returns a closure that will parse the provided + // Dispatchable.params() to a protocol object and execute the + // apprpropriate method. If the parsing fails it will issue an + // error response on the frontend channel, otherwise it will execute the + // command. + virtual std::function Dispatch( + span command_name) = 0; + + // Sends a response to the client via the channel. + void sendResponse(int call_id, + const DispatchResponse&, + std::unique_ptr result = nullptr); + + void ReportInvalidParams(const Dispatchable& dispatchable, + const DeserializerState& state); + + FrontendChannel* channel() { return frontend_channel_; } + + void clearFrontend(); + + std::unique_ptr weakPtr(); + + private: + FrontendChannel* frontend_channel_; + std::unordered_set weak_ptrs_; +}; + +// ============================================================================= +// UberDispatcher - dispatches between domains (backends). +// ============================================================================= +class UberDispatcher { + public: + // Return type for ::Dispatch. + class DispatchResult { + public: + DispatchResult(bool method_found, std::function runnable); + + // Indicates whether the method was found, that is, it could be dispatched + // to a backend registered with this dispatcher. + bool MethodFound() const { return method_found_; } + + // Runs the dispatched result. This will send the appropriate error + // responses if the method wasn't found or if something went wrong during + // parameter parsing. + void Run(); + + private: + bool method_found_; + std::function runnable_; + }; + + // |frontend_hannel| can't be nullptr. + explicit UberDispatcher(FrontendChannel* frontend_channel); + virtual ~UberDispatcher(); + + // Dispatches the provided |dispatchable| considering all redirects and domain + // handlers registered with this uber dispatcher. Also see |DispatchResult|. + // |dispatchable.ok()| must hold - callers must check this separately and + // deal with errors. + DispatchResult Dispatch(const Dispatchable& dispatchable) const; + + // Invoked from generated code for wiring domain backends; that is, + // connecting domain handlers to an uber dispatcher. + // See ::Dispatcher::Wire(UberDispatcher*,Backend*). + FrontendChannel* channel() const { + assert(frontend_channel_); + return frontend_channel_; + } + + // Invoked from generated code for wiring domain backends; that is, + // connecting domain handlers to an uber dispatcher. + // See ::Dispatcher::Wire(UberDispatcher*,Backend*). + void WireBackend(span domain, + const std::vector, span>>&, + std::unique_ptr dispatcher); + + private: + DomainDispatcher* findDispatcher(span method); + FrontendChannel* const frontend_channel_; + // Pairs of ascii strings of the form ("Domain1.method1","Domain2.method2") + // indicating that the first element of each pair redirects to the second. + // Sorted by first element. + std::vector, span>> redirects_; + // Domain dispatcher instances, sorted by their domain name. + std::vector, std::unique_ptr>> + dispatchers_; +}; +} // namespace v8_crdtp + +#endif // V8_CRDTP_DISPATCH_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/error_support.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/error_support.h new file mode 100644 index 000000000..45d2ef7d9 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/error_support.h @@ -0,0 +1,62 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_ERROR_SUPPORT_H_ +#define V8_CRDTP_ERROR_SUPPORT_H_ + +#include +#include +#include +#include "export.h" +#include "span.h" + +namespace v8_crdtp { +// ============================================================================= +// ErrorSupport - For tracking errors in tree structures. +// ============================================================================= + +// This abstraction is used when converting between Values and inspector +// objects, e.g. in lib/ValueConversions_{h,cc}.template. As the processing +// enters and exits a branch, we call Push / Pop. Within the branch, +// we either set the name or an index (in case we're processing the element of a +// list/vector). Only once an error is seen, the path which is now on the +// stack is materialized and prefixes the error message. E.g., +// "foo.bar.2: some error". After error collection, ::Errors() is used to +// access the message. +class ErrorSupport { + public: + // Push / Pop operations for the path segments; after Push, either SetName or + // SetIndex must be called exactly once. + void Push(); + void Pop(); + + // Sets the name of the current segment on the stack; e.g. a field name. + // |name| must be a C++ string literal in 7 bit US-ASCII. + void SetName(const char* name); + // Sets the index of the current segment on the stack; e.g. an array index. + void SetIndex(size_t index); + + // Materializes the error internally. |error| must be a C++ string literal + // in 7 bit US-ASCII. + void AddError(const char* error); + + // Returns the semicolon-separated list of errors as in 7 bit ASCII. + span Errors() const; + + private: + enum SegmentType { EMPTY, NAME, INDEX }; + struct Segment { + SegmentType type = EMPTY; + union { + const char* name; + size_t index; + }; + }; + std::vector stack_; + std::string errors_; +}; + +} // namespace v8_crdtp + +#endif // V8_CRDTP_ERROR_SUPPORT_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/export.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/export.h new file mode 100644 index 000000000..b814e16e5 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/export.h @@ -0,0 +1,6 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is V8 specific. It's not rolled from the upstream project. +// CRDTP doesn't export symbols from V8, so it's empty. diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/find_by_first.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/find_by_first.h new file mode 100644 index 000000000..2469bd765 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/find_by_first.h @@ -0,0 +1,58 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_FIND_BY_FIRST_H_ +#define V8_CRDTP_FIND_BY_FIRST_H_ + +#include +#include +#include +#include + +#include "export.h" +#include "span.h" + +namespace v8_crdtp { +// ============================================================================= +// FindByFirst - Retrieval from a sorted vector that's keyed by span. +// ============================================================================= + +// Given a vector of pairs sorted by the first element of each pair, find +// the corresponding value given a key to be compared to the first element. +// Together with std::inplace_merge and pre-sorting or std::sort, this can +// be used to implement a minimalistic equivalent of Chromium's flat_map. + +// In this variant, the template parameter |T| is a value type and a +// |default_value| is provided. +template +T FindByFirst(const std::vector, T>>& sorted_by_first, + span key, + T default_value) { + auto it = std::lower_bound( + sorted_by_first.begin(), sorted_by_first.end(), key, + [](const std::pair, T>& left, span right) { + return SpanLessThan(left.first, right); + }); + return (it != sorted_by_first.end() && SpanEquals(it->first, key)) + ? it->second + : default_value; +} + +// In this variant, the template parameter |T| is a class or struct that's +// instantiated in std::unique_ptr, and we return either a T* or a nullptr. +template +T* FindByFirst(const std::vector, std::unique_ptr>>& + sorted_by_first, + span key) { + auto it = std::lower_bound( + sorted_by_first.begin(), sorted_by_first.end(), key, + [](const std::pair, std::unique_ptr>& left, + span right) { return SpanLessThan(left.first, right); }); + return (it != sorted_by_first.end() && SpanEquals(it->first, key)) + ? it->second.get() + : nullptr; +} +} // namespace v8_crdtp + +#endif // V8_CRDTP_FIND_BY_FIRST_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/frontend_channel.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/frontend_channel.h new file mode 100644 index 000000000..9f12930a1 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/frontend_channel.h @@ -0,0 +1,47 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_FRONTEND_CHANNEL_H_ +#define V8_CRDTP_FRONTEND_CHANNEL_H_ + +#include +#include +#include "export.h" +#include "serializable.h" +#include "span.h" + +namespace v8_crdtp { +// ============================================================================= +// FrontendChannel - For sending notifications and responses to protocol clients +// ============================================================================= +class FrontendChannel { + public: + virtual ~FrontendChannel() = default; + + // Sends protocol responses and notifications. The |call_id| parameter is + // seemingly redundant because it's also included in the message, but + // responses may be sent from an untrusted source to a trusted process (e.g. + // from Chromium's renderer (blink) to the browser process), which needs + // to be able to match the response to an earlier request without parsing the + // messsage. + virtual void SendProtocolResponse(int call_id, + std::unique_ptr message) = 0; + virtual void SendProtocolNotification( + std::unique_ptr message) = 0; + + // FallThrough indicates that |message| should be handled in another layer. + // Usually this means the layer responding to the message didn't handle it, + // but in some cases messages are handled by multiple layers (e.g. both + // the embedder and the content layer in Chromium). + virtual void FallThrough(int call_id, + span method, + span message) = 0; + + // Session implementations may queue notifications for performance or + // other considerations; this is a hook for domain handlers to manually flush. + virtual void FlushProtocolNotifications() = 0; +}; +} // namespace v8_crdtp + +#endif // V8_CRDTP_FRONTEND_CHANNEL_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/glue.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/glue.h new file mode 100644 index 000000000..3a69a5409 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/glue.h @@ -0,0 +1,80 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_GLUE_H_ +#define V8_CRDTP_GLUE_H_ + +#include +#include + +namespace v8_crdtp { +namespace glue { +// ============================================================================= +// glue::detail::PtrMaybe, glue::detail::ValueMaybe, templates for optional +// pointers / values which are used in ../lib/Forward_h.template. +// ============================================================================= +namespace detail { +template +class PtrMaybe { + public: + PtrMaybe() = default; + PtrMaybe(std::unique_ptr value) : value_(std::move(value)) {} + PtrMaybe(PtrMaybe&& other) noexcept : value_(std::move(other.value_)) {} + void operator=(std::unique_ptr value) { value_ = std::move(value); } + T* fromJust() const { + assert(value_); + return value_.get(); + } + T* fromMaybe(T* default_value) const { + return value_ ? value_.get() : default_value; + } + bool isJust() const { return value_ != nullptr; } + std::unique_ptr takeJust() { + assert(value_); + return std::move(value_); + } + + private: + std::unique_ptr value_; +}; + +template +class ValueMaybe { + public: + ValueMaybe() : is_just_(false), value_() {} + ValueMaybe(T value) : is_just_(true), value_(std::move(value)) {} + ValueMaybe(ValueMaybe&& other) noexcept + : is_just_(other.is_just_), value_(std::move(other.value_)) {} + void operator=(T value) { + value_ = value; + is_just_ = true; + } + const T& fromJust() const { + assert(is_just_); + return value_; + } + const T& fromMaybe(const T& default_value) const { + return is_just_ ? value_ : default_value; + } + bool isJust() const { return is_just_; } + T takeJust() { + assert(is_just_); + is_just_ = false; + return std::move(value_); + } + + private: + bool is_just_; + T value_; +}; +} // namespace detail +} // namespace glue +} // namespace v8_crdtp + +#define PROTOCOL_DISALLOW_COPY(ClassName) \ + private: \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(const ClassName&) = delete + +#endif // V8_CRDTP_GLUE_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/json.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/json.h new file mode 100644 index 000000000..3600b9038 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/json.h @@ -0,0 +1,52 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_JSON_H_ +#define V8_CRDTP_JSON_H_ + +#include +#include +#include "export.h" +#include "parser_handler.h" + +namespace v8_crdtp { +namespace json { +// ============================================================================= +// json::NewJSONEncoder - for encoding streaming parser events as JSON +// ============================================================================= + +// Returns a handler object which will write ascii characters to |out|. +// |status->ok()| will be false iff the handler routine HandleError() is called. +// In that case, we'll stop emitting output. +// Except for calling the HandleError routine at any time, the client +// code must call the Handle* methods in an order in which they'd occur +// in valid JSON; otherwise we may crash (the code uses assert). +std::unique_ptr NewJSONEncoder(std::vector* out, + Status* status); + +std::unique_ptr NewJSONEncoder(std::string* out, Status* status); + +// ============================================================================= +// json::ParseJSON - for receiving streaming parser events for JSON +// ============================================================================= + +void ParseJSON(span chars, ParserHandler* handler); + +void ParseJSON(span chars, ParserHandler* handler); + +// ============================================================================= +// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding +// ============================================================================= + +Status ConvertCBORToJSON(span cbor, std::string* json); + +Status ConvertCBORToJSON(span cbor, std::vector* json); + +Status ConvertJSONToCBOR(span json, std::vector* cbor); + +Status ConvertJSONToCBOR(span json, std::vector* cbor); +} // namespace json +} // namespace v8_crdtp + +#endif // V8_CRDTP_JSON_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/json_platform.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/json_platform.h new file mode 100644 index 000000000..80e57df25 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/json_platform.h @@ -0,0 +1,26 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_JSON_PLATFORM_H_ +#define V8_CRDTP_JSON_PLATFORM_H_ + +#include + +namespace v8_crdtp { +namespace json { +// These routines are implemented in json_platform.cc, or in a +// platform-dependent (code-base dependent) custom replacement. +// E.g., json_platform_chromium.cc, json_platform_v8.cc. +namespace platform { +// Parses |str| into |result|. Returns false iff there are +// leftover characters or parsing errors. +bool StrToD(const char* str, double* result); + +// Prints |value| in a format suitable for JSON. +std::string DToStr(double value); +} // namespace platform +} // namespace json +} // namespace v8_crdtp + +#endif // V8_CRDTP_JSON_PLATFORM_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/maybe.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/maybe.h new file mode 100644 index 000000000..a476dd581 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/maybe.h @@ -0,0 +1,104 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef V8_CRDTP_MAYBE_H_ +#define V8_CRDTP_MAYBE_H_ + +#include +#include + +namespace v8_crdtp { + +// ============================================================================= +// detail::PtrMaybe, detail::ValueMaybe, templates for optional +// pointers / values which are used in ../lib/Forward_h.template. +// ============================================================================= + +namespace detail { +template +class PtrMaybe { + public: + PtrMaybe() = default; + PtrMaybe(std::unique_ptr value) : value_(std::move(value)) {} + PtrMaybe(PtrMaybe&& other) noexcept : value_(std::move(other.value_)) {} + void operator=(std::unique_ptr value) { value_ = std::move(value); } + T* fromJust() const { + assert(value_); + return value_.get(); + } + T* fromMaybe(T* default_value) const { + return value_ ? value_.get() : default_value; + } + bool isJust() const { return value_ != nullptr; } + std::unique_ptr takeJust() { + assert(value_); + return std::move(value_); + } + + private: + std::unique_ptr value_; +}; + +template +class ValueMaybe { + public: + ValueMaybe() : is_just_(false), value_() {} + ValueMaybe(T value) : is_just_(true), value_(std::move(value)) {} + ValueMaybe(ValueMaybe&& other) noexcept + : is_just_(other.is_just_), value_(std::move(other.value_)) {} + void operator=(T value) { + value_ = value; + is_just_ = true; + } + const T& fromJust() const { + assert(is_just_); + return value_; + } + const T& fromMaybe(const T& default_value) const { + return is_just_ ? value_ : default_value; + } + bool isJust() const { return is_just_; } + T takeJust() { + assert(is_just_); + is_just_ = false; + return std::move(value_); + } + + private: + bool is_just_; + T value_; +}; + +template +struct MaybeTypedef { + typedef PtrMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +template <> +struct MaybeTypedef { + typedef ValueMaybe type; +}; + +} // namespace detail + +template +using Maybe = typename detail::MaybeTypedef::type; + +} // namespace v8_crdtp + +#endif // V8_CRDTP_MAYBE_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/parser_handler.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/parser_handler.h new file mode 100644 index 000000000..c583e35ad --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/parser_handler.h @@ -0,0 +1,39 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_PARSER_HANDLER_H_ +#define V8_CRDTP_PARSER_HANDLER_H_ + +#include +#include "span.h" +#include "status.h" + +namespace v8_crdtp { +// Handler interface for parser events emitted by a streaming parser. +// See cbor::NewCBOREncoder, cbor::ParseCBOR, json::NewJSONEncoder, +// json::ParseJSON. +class ParserHandler { + public: + virtual ~ParserHandler() = default; + virtual void HandleMapBegin() = 0; + virtual void HandleMapEnd() = 0; + virtual void HandleArrayBegin() = 0; + virtual void HandleArrayEnd() = 0; + virtual void HandleString8(span chars) = 0; + virtual void HandleString16(span chars) = 0; + virtual void HandleBinary(span bytes) = 0; + virtual void HandleDouble(double value) = 0; + virtual void HandleInt32(int32_t value) = 0; + virtual void HandleBool(bool value) = 0; + virtual void HandleNull() = 0; + + // The parser may send one error even after other events have already + // been received. Client code is reponsible to then discard the + // already processed events. + // |error| must be an eror, as in, |error.is_ok()| can't be true. + virtual void HandleError(Status error) = 0; +}; +} // namespace v8_crdtp + +#endif // V8_CRDTP_PARSER_HANDLER_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/protocol_core.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/protocol_core.h new file mode 100644 index 000000000..486bf7b2d --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/protocol_core.h @@ -0,0 +1,417 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_PROTOCOL_CORE_H_ +#define V8_CRDTP_PROTOCOL_CORE_H_ + +#include + +#include +#include +#include +#include + +#include "cbor.h" +#include "maybe.h" +#include "serializable.h" +#include "span.h" +#include "status.h" + +namespace v8_crdtp { + +class DeserializerState { + public: + using Storage = std::shared_ptr>; + + // Creates a state from the raw bytes received from the peer. + explicit DeserializerState(std::vector bytes); + // Creates the state from the part of another message. + DeserializerState(Storage storage, span span); + DeserializerState(const DeserializerState& r) = delete; + DeserializerState(DeserializerState&& r) = default; + + // Registers |error|, unless the tokenizer's status is already an error. + void RegisterError(Error error); + // Registers |name| as a segment of the field path. + void RegisterFieldPath(span name); + + // Produces an error message considering |tokenizer.Status()|, + // status_, and field_path_. + std::string ErrorMessage(span message_name) const; + Status status() const; + const Storage& storage() const { return storage_; } + cbor::CBORTokenizer* tokenizer() { return &tokenizer_; } + + private: + const Storage storage_; + cbor::CBORTokenizer tokenizer_; + Status status_; + std::vector> field_path_; +}; + +template +struct ProtocolTypeTraits {}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, bool* value); + static void Serialize(bool value, std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, int* value); + static void Serialize(int value, std::vector* bytes); +}; + +template <> +struct ProtocolTypeTraits { + static bool Deserialize(DeserializerState* state, double* value); + static void Serialize(double value, std::vector* bytes); +}; + +class ContainerSerializer { + public: + ContainerSerializer(std::vector* bytes, uint8_t tag); + + template + void AddField(span field_name, const T& value) { + cbor::EncodeString8( + span(reinterpret_cast(field_name.data()), + field_name.size()), + bytes_); + ProtocolTypeTraits::Serialize(value, bytes_); + } + template + void AddField(span field_name, const detail::ValueMaybe& value) { + if (!value.isJust()) + return; + AddField(field_name, value.fromJust()); + } + template + void AddField(span field_name, const detail::PtrMaybe& value) { + if (!value.isJust()) + return; + AddField(field_name, *value.fromJust()); + } + + void EncodeStop(); + + private: + std::vector* const bytes_; + cbor::EnvelopeEncoder envelope_; +}; + +class ObjectSerializer { + public: + ObjectSerializer(); + ~ObjectSerializer(); + + template + void AddField(span name, const T& field) { + serializer_.AddField(name, field); + } + std::unique_ptr Finish(); + + private: + std::vector owned_bytes_; + ContainerSerializer serializer_; +}; + +class DeserializerDescriptor { + public: + struct Field { + span name; + bool is_optional; + bool (*deserializer)(DeserializerState* state, void* obj); + }; + + DeserializerDescriptor(const Field* fields, size_t field_count); + + bool Deserialize(DeserializerState* state, void* obj) const; + + private: + bool DeserializeField(DeserializerState* state, + span name, + int* seen_mandatory_fields, + void* obj) const; + + const Field* const fields_; + const size_t field_count_; + const int mandatory_field_mask_; +}; + +template +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, std::vector* value) { + auto* tokenizer = state->tokenizer(); + if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE) + tokenizer->EnterEnvelope(); + if (tokenizer->TokenTag() != cbor::CBORTokenTag::ARRAY_START) { + state->RegisterError(Error::CBOR_ARRAY_START_EXPECTED); + return false; + } + assert(value->empty()); + tokenizer->Next(); + for (; tokenizer->TokenTag() != cbor::CBORTokenTag::STOP; + tokenizer->Next()) { + value->emplace_back(); + if (!ProtocolTypeTraits::Deserialize(state, &value->back())) + return false; + } + return true; + } + + static void Serialize(const std::vector& value, + std::vector* bytes) { + ContainerSerializer container_serializer( + bytes, cbor::EncodeIndefiniteLengthArrayStart()); + for (const auto& item : value) + ProtocolTypeTraits::Serialize(item, bytes); + container_serializer.EncodeStop(); + } +}; + +template +struct ProtocolTypeTraits>> { + static bool Deserialize(DeserializerState* state, + std::unique_ptr>* value) { + auto res = std::make_unique>(); + if (!ProtocolTypeTraits>::Deserialize(state, res.get())) + return false; + *value = std::move(res); + return true; + } + static void Serialize(const std::unique_ptr>& value, + std::vector* bytes) { + ProtocolTypeTraits>::Serialize(*value, bytes); + } +}; + +class DeferredMessage : public Serializable { + public: + static std::unique_ptr FromSerializable( + std::unique_ptr serializeable); + static std::unique_ptr FromSpan(span bytes); + + ~DeferredMessage() override = default; + virtual DeserializerState MakeDeserializer() const = 0; + + protected: + DeferredMessage() = default; +}; + +template <> +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, + std::unique_ptr* value); + static void Serialize(const std::unique_ptr& value, + std::vector* bytes); +}; + +template +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, + detail::ValueMaybe* value) { + T res; + if (!ProtocolTypeTraits::Deserialize(state, &res)) + return false; + *value = std::move(res); + return true; + } + + static void Serialize(const detail::ValueMaybe& value, + std::vector* bytes) { + ProtocolTypeTraits::Serialize(value.fromJust(), bytes); + } +}; + +template +struct ProtocolTypeTraits> { + static bool Deserialize(DeserializerState* state, + detail::PtrMaybe* value) { + std::unique_ptr res; + if (!ProtocolTypeTraits>::Deserialize(state, &res)) + return false; + *value = std::move(res); + return true; + } + + static void Serialize(const detail::PtrMaybe& value, + std::vector* bytes) { + ProtocolTypeTraits::Serialize(*value.fromJust(), bytes); + } +}; + +template +class DeserializableProtocolObject { + public: + static StatusOr> ReadFrom( + const DeferredMessage& deferred_message) { + auto state = deferred_message.MakeDeserializer(); + if (auto res = Deserialize(&state)) + return StatusOr>(std::move(res)); + return StatusOr>(state.status()); + } + + static StatusOr> ReadFrom(std::vector bytes) { + auto state = DeserializerState(std::move(bytes)); + if (auto res = Deserialize(&state)) + return StatusOr>(std::move(res)); + return StatusOr>(state.status()); + } + + // Short-hand for legacy clients. This would swallow any errors, consider + // using ReadFrom. + static std::unique_ptr FromBinary(const uint8_t* bytes, size_t size) { + std::unique_ptr value(new T()); + auto deserializer = DeferredMessage::FromSpan(span(bytes, size)) + ->MakeDeserializer(); + std::ignore = Deserialize(&deserializer, value.get()); + return value; + } + + [[nodiscard]] static bool Deserialize(DeserializerState* state, T* value) { + return T::deserializer_descriptor().Deserialize(state, value); + } + + protected: + // This is for the sake of the macros used by derived classes thay may be in + // a different namespace; + using ProtocolType = T; + using DeserializerDescriptorType = DeserializerDescriptor; + template + using DeserializableBase = DeserializableProtocolObject; + + DeserializableProtocolObject() = default; + ~DeserializableProtocolObject() = default; + + private: + friend struct ProtocolTypeTraits>; + + static std::unique_ptr Deserialize(DeserializerState* state) { + std::unique_ptr value(new T()); + if (Deserialize(state, value.get())) + return value; + return nullptr; + } +}; + +template +class ProtocolObject : public Serializable, + public DeserializableProtocolObject { + public: + std::unique_ptr Clone() const { + std::vector serialized; + AppendSerialized(&serialized); + return T::ReadFrom(std::move(serialized)).value(); + } + // TODO(caseq): compatibility only, remove. + std::unique_ptr clone() const { return Clone(); } + + protected: + using ProtocolType = T; + + ProtocolObject() = default; +}; + +template +struct ProtocolTypeTraits< + T, + typename std::enable_if< + std::is_base_of, T>::value>::type> { + static bool Deserialize(DeserializerState* state, T* value) { + return T::Deserialize(state, value); + } + + static void Serialize(const T& value, std::vector* bytes) { + value.AppendSerialized(bytes); + } +}; + +template +struct ProtocolTypeTraits< + std::unique_ptr, + typename std::enable_if< + std::is_base_of, T>::value>::type> { + static bool Deserialize(DeserializerState* state, std::unique_ptr* value) { + std::unique_ptr res = T::Deserialize(state); + if (!res) + return false; + *value = std::move(res); + return true; + } + + static void Serialize(const std::unique_ptr& value, + std::vector* bytes) { + ProtocolTypeTraits::Serialize(*value, bytes); + } +}; + +template +bool ConvertProtocolValue(const F& from, T* to) { + std::vector bytes; + ProtocolTypeTraits::Serialize(from, &bytes); + auto deserializer = + DeferredMessage::FromSpan(span(bytes.data(), bytes.size())) + ->MakeDeserializer(); + return ProtocolTypeTraits::Deserialize(&deserializer, to); +} + +#define DECLARE_DESERIALIZATION_SUPPORT() \ + friend DeserializableBase; \ + static const DeserializerDescriptorType& deserializer_descriptor() + +#define DECLARE_SERIALIZATION_SUPPORT() \ + public: \ + void AppendSerialized(std::vector* bytes) const override; \ + \ + private: \ + friend DeserializableBase; \ + static const DeserializerDescriptorType& deserializer_descriptor() + +#define V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, is_optional) \ + { \ + MakeSpan(name), is_optional, \ + [](DeserializerState* __state, void* __obj) -> bool { \ + return ProtocolTypeTraits::Deserialize( \ + __state, &static_cast(__obj)->field); \ + } \ + } + +// clang-format off +#define V8_CRDTP_BEGIN_DESERIALIZER(type) \ + const type::DeserializerDescriptorType& type::deserializer_descriptor() { \ + using namespace v8_crdtp; \ + static const DeserializerDescriptorType::Field fields[] = { + +#define V8_CRDTP_END_DESERIALIZER() \ + }; \ + static const DeserializerDescriptorType s_desc( \ + fields, sizeof fields / sizeof fields[0]); \ + return s_desc; \ + } + +#define V8_CRDTP_DESERIALIZE_FIELD(name, field) \ + V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, false) +#define V8_CRDTP_DESERIALIZE_FIELD_OPT(name, field) \ + V8_CRDTP_DESERIALIZE_FILED_IMPL(name, field, true) + +#define V8_CRDTP_BEGIN_SERIALIZER(type) \ + void type::AppendSerialized(std::vector* bytes) const { \ + using namespace v8_crdtp; \ + ContainerSerializer __serializer(bytes, \ + cbor::EncodeIndefiniteLengthMapStart()); + +#define V8_CRDTP_SERIALIZE_FIELD(name, field) \ + __serializer.AddField(MakeSpan(name), field) + +#define V8_CRDTP_END_SERIALIZER() \ + __serializer.EncodeStop(); \ + } class __cddtp_dummy_name +// clang-format on + +} // namespace v8_crdtp + +#endif // V8_CRDTP_PROTOCOL_CORE_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/serializable.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/serializable.h new file mode 100644 index 000000000..01c1f8b46 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/serializable.h @@ -0,0 +1,32 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_SERIALIZABLE_H_ +#define V8_CRDTP_SERIALIZABLE_H_ + +#include +#include +#include +#include "export.h" + +namespace v8_crdtp { +// ============================================================================= +// Serializable - An object to be emitted as a sequence of bytes. +// ============================================================================= +class Serializable { + public: + // Convenience: Invokes |AppendSerialized| on an empty vector. + std::vector Serialize() const; + + virtual void AppendSerialized(std::vector* out) const = 0; + + virtual ~Serializable() = default; + + // Wraps a vector of |bytes| into a Serializable for situations in which we + // eagerly serialize a structure. + static std::unique_ptr From(std::vector bytes); +}; +} // namespace v8_crdtp + +#endif // V8_CRDTP_SERIALIZABLE_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/span.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/span.h new file mode 100644 index 000000000..8f132636f --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/span.h @@ -0,0 +1,99 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_SPAN_H_ +#define V8_CRDTP_SPAN_H_ + +#include +#include +#include + +#include "export.h" + +namespace v8_crdtp { +// ============================================================================= +// span - sequence of bytes +// ============================================================================= + +// This template is similar to std::span, which will be included in C++20. +template +class span { + public: + using index_type = size_t; + + constexpr span() : data_(nullptr), size_(0) {} + constexpr span(const T* data, index_type size) : data_(data), size_(size) {} + + constexpr const T* data() const { return data_; } + + constexpr const T* begin() const { return data_; } + constexpr const T* end() const { return data_ + size_; } + + constexpr const T& operator[](index_type idx) const { return data_[idx]; } + + constexpr span subspan(index_type offset, index_type count) const { + return span(data_ + offset, count); + } + + constexpr span subspan(index_type offset) const { + return span(data_ + offset, size_ - offset); + } + + constexpr bool empty() const { return size_ == 0; } + + constexpr index_type size() const { return size_; } + constexpr index_type size_bytes() const { return size_ * sizeof(T); } + + private: + const T* data_; + index_type size_; +}; + +template +constexpr span MakeSpan(const char (&str)[N]) { + return span(str, N - 1); +} + +template +constexpr span SpanFrom(const char (&str)[N]) { + return span(reinterpret_cast(str), N - 1); +} + +constexpr inline span SpanFrom(const char* str) { + return str ? span(reinterpret_cast(str), strlen(str)) + : span(); +} + +inline span SpanFrom(const std::string& v) { + return span(reinterpret_cast(v.data()), v.size()); +} + +// This SpanFrom routine works for std::vector and +// std::vector, but also for base::span in Chromium. +template {} && + std::is_member_function_pointer{}>> +inline span SpanFrom(const C& v) { + return span(v.data(), v.size()); +} + +// Less than / equality comparison functions for sorting / searching for byte +// spans. +bool SpanLessThan(span x, span y) noexcept; +bool SpanEquals(span x, span y) noexcept; + +// Less than / equality comparison functions for sorting / searching for byte +// spans. +bool SpanLessThan(span x, span y) noexcept; +bool SpanEquals(span x, span y) noexcept; + +struct SpanLt { + bool operator()(span l, span r) const { + return SpanLessThan(l, r); + } +}; +} // namespace v8_crdtp + +#endif // V8_CRDTP_SPAN_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/status.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/status.h new file mode 100644 index 000000000..10745f83e --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/status.h @@ -0,0 +1,140 @@ +// Copyright 2019 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_STATUS_H_ +#define V8_CRDTP_STATUS_H_ + +#include +#include +#include +#include + +#include "export.h" + +namespace v8_crdtp { +// ============================================================================= +// Status and Error codes +// ============================================================================= + +enum class Error { + OK = 0, + + // JSON parsing errors; checked when parsing / converting from JSON. + // See json.{h,cc}. + JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01, + JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02, + JSON_PARSER_NO_INPUT = 0x03, + JSON_PARSER_INVALID_TOKEN = 0x04, + JSON_PARSER_INVALID_NUMBER = 0x05, + JSON_PARSER_INVALID_STRING = 0x06, + JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07, + JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08, + JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09, + JSON_PARSER_COLON_EXPECTED = 0x0a, + JSON_PARSER_UNEXPECTED_MAP_END = 0x0b, + JSON_PARSER_COMMA_OR_MAP_END_EXPECTED = 0x0c, + JSON_PARSER_VALUE_EXPECTED = 0x0d, + + // CBOR parsing errors; checked when parsing / converting from CBOR. + CBOR_INVALID_INT32 = 0x0e, + CBOR_INVALID_DOUBLE = 0x0f, + CBOR_INVALID_ENVELOPE = 0x10, + CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH = 0x11, + CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE = 0x12, + CBOR_INVALID_STRING8 = 0x13, + CBOR_INVALID_STRING16 = 0x14, + CBOR_INVALID_BINARY = 0x15, + CBOR_UNSUPPORTED_VALUE = 0x16, + CBOR_UNEXPECTED_EOF_IN_ENVELOPE = 0x17, + CBOR_INVALID_START_BYTE = 0x18, + CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x19, + CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x1a, + CBOR_UNEXPECTED_EOF_IN_MAP = 0x1b, + CBOR_INVALID_MAP_KEY = 0x1c, + CBOR_DUPLICATE_MAP_KEY = 0x1d, + CBOR_STACK_LIMIT_EXCEEDED = 0x1e, + CBOR_TRAILING_JUNK = 0x1f, + CBOR_MAP_START_EXPECTED = 0x20, + CBOR_MAP_STOP_EXPECTED = 0x21, + CBOR_ARRAY_START_EXPECTED = 0x22, + CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x23, + + // Message errors are constraints we place on protocol messages coming + // from a protocol client; these are checked in crdtp::Dispatchable + // (see dispatch.h) as it performs a shallow parse. + MESSAGE_MUST_BE_AN_OBJECT = 0x24, + MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY = 0x25, + MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY = 0x26, + MESSAGE_MAY_HAVE_STRING_SESSION_ID_PROPERTY = 0x27, + MESSAGE_MAY_HAVE_OBJECT_PARAMS_PROPERTY = 0x28, + MESSAGE_HAS_UNKNOWN_PROPERTY = 0x29, + + BINDINGS_MANDATORY_FIELD_MISSING = 0x30, + BINDINGS_BOOL_VALUE_EXPECTED = 0x31, + BINDINGS_INT32_VALUE_EXPECTED = 0x32, + BINDINGS_DOUBLE_VALUE_EXPECTED = 0x33, + BINDINGS_STRING_VALUE_EXPECTED = 0x34, + BINDINGS_STRING8_VALUE_EXPECTED = 0x35, + BINDINGS_BINARY_VALUE_EXPECTED = 0x36, + BINDINGS_DICTIONARY_VALUE_EXPECTED = 0x37, + BINDINGS_INVALID_BASE64_STRING = 0x38, +}; + +// A status value with position that can be copied. The default status +// is OK. Usually, error status values should come with a valid position. +struct Status { + static constexpr size_t npos() { return std::numeric_limits::max(); } + + bool ok() const { return error == Error::OK; } + + Error error = Error::OK; + size_t pos = npos(); + Status(Error error, size_t pos) : error(error), pos(pos) {} + Status() = default; + + bool IsMessageError() const { + return error >= Error::MESSAGE_MUST_BE_AN_OBJECT && + error <= Error::MESSAGE_HAS_UNKNOWN_PROPERTY; + } + + // Returns 7 bit US-ASCII string, either "OK" or an error message without + // position. + std::string Message() const; + + // Returns a 7 bit US-ASCII string, either "OK" or an error message that + // includes the position. + std::string ToASCIIString() const; +}; + +template +class StatusOr { + public: + explicit StatusOr(const T& value) : value_(value) {} + explicit StatusOr(T&& value) : value_(std::move(value)) {} + explicit StatusOr(const Status& status) : status_(status) {} + + bool ok() const { return status_.ok(); } + T& operator*() & { + assert(ok()); + return value_; + } + const T& operator*() const& { return value(); } + T&& operator*() && { return value(); } + const Status& status() const { return status_; } + + T& value() & { return *this; } + T&& value() && { + assert(ok()); + return std::move(value_); + } + const T& value() const& { return *this; } + + private: + Status status_; + T value_; +}; + +} // namespace v8_crdtp + +#endif // V8_CRDTP_STATUS_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/status_test_support.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/status_test_support.h new file mode 100644 index 000000000..3ae8eaa3d --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/status_test_support.h @@ -0,0 +1,32 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_CRDTP_STATUS_TEST_SUPPORT_H_ +#define V8_CRDTP_STATUS_TEST_SUPPORT_H_ + +#include +#include "status.h" +#include "test_platform.h" + +namespace v8_crdtp { +// Supports gtest, to conveniently match Status objects and +// get useful error messages when tests fail. +// Typically used with EXPECT_THAT, e.g. +// +// EXPECT_THAT(status, StatusIs(Error::JSON_PARSER_COLON_EXPECTED, 42)); +// +// EXPECT_THAT(status, StatusIsOk()); + +// Prints a |status|, including its generated error message, error code, and +// position. This is used by gtest for pretty printing actual vs. expected. +void PrintTo(const Status& status, std::ostream* os); + +// Matches any status with |status.ok()|. +testing::Matcher StatusIsOk(); + +// Matches any status with |error| and |pos|. +testing::Matcher StatusIs(Error error, size_t pos); +} // namespace v8_crdtp + +#endif // V8_CRDTP_STATUS_TEST_SUPPORT_H_ diff --git a/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/test_platform.h b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/test_platform.h new file mode 100644 index 000000000..e6698f252 --- /dev/null +++ b/NativeScript/napi/android/v8-11/v8_inspector/third_party/inspector_protocol/crdtp/test_platform.h @@ -0,0 +1,26 @@ +// Copyright 2019 The V8 Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is V8 specific. It's not rolled from the upstream project. + +#ifndef V8_INSPECTOR_PROTOCOL_CRDTP_TEST_PLATFORM_H_ +#define V8_INSPECTOR_PROTOCOL_CRDTP_TEST_PLATFORM_H_ + +#include +#include + +#include "span.h" +#include "src/base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace v8_crdtp { + +std::string UTF16ToUTF8(span in); + +std::vector UTF8ToUTF16(span in); + +} // namespace v8_crdtp + +#endif // V8_INSPECTOR_PROTOCOL_CRDTP_TEST_PLATFORM_H_ diff --git a/NativeScript/napi/android/v8-13/include/APIDesign.md b/NativeScript/napi/android/v8-13/include/APIDesign.md new file mode 100644 index 000000000..fe42c8ed5 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/APIDesign.md @@ -0,0 +1,72 @@ +# The V8 public C++ API + +# Overview + +The V8 public C++ API aims to support four use cases: + +1. Enable applications that embed V8 (called the embedder) to configure and run + one or more instances of V8. +2. Expose ECMAScript-like capabilities to the embedder. +3. Enable the embedder to interact with ECMAScript by exposing API objects. +4. Provide access to the V8 debugger (inspector). + +# Configuring and running an instance of V8 + +V8 requires access to certain OS-level primitives such as the ability to +schedule work on threads, or allocate memory. + +The embedder can define how to access those primitives via the v8::Platform +interface. While V8 bundles a basic implementation, embedders are highly +encouraged to implement v8::Platform themselves. + +Currently, the v8::ArrayBuffer::Allocator is passed to the v8::Isolate factory +method, however, conceptually it should also be part of the v8::Platform since +all instances of V8 should share one allocator. + +Once the v8::Platform is configured, an v8::Isolate can be created. All +further interactions with V8 should explicitly reference the v8::Isolate they +refer to. All API methods should eventually take an v8::Isolate parameter. + +When a given instance of V8 is no longer needed, it can be destroyed by +disposing the respective v8::Isolate. If the embedder wishes to free all memory +associated with the v8::Isolate, it has to first clear all global handles +associated with that v8::Isolate. + +# ECMAScript-like capabilities + +In general, the C++ API shouldn't enable capabilities that aren't available to +scripts running in V8. Experience has shown that it's not possible to maintain +such API methods in the long term. However, capabilities also available to +scripts, i.e., ones that are defined in the ECMAScript standard are there to +stay, and we can safely expose them to embedders. + +The C++ API should also be pleasant to use, and not require learning new +paradigms. Similarly to how the API exposed to scripts aims to provide good +ergonomics, we should aim to provide a reasonable developer experience for this +API surface. + +ECMAScript makes heavy use of exceptions, however, V8's C++ code doesn't use +C++ exceptions. Therefore, all API methods that can throw exceptions should +indicate so by returning a v8::Maybe<> or v8::MaybeLocal<> result, +and by taking a v8::Local<v8::Context> parameter that indicates in which +context a possible exception should be thrown. + +# API objects + +V8 allows embedders to define special objects that expose additional +capabilities and APIs to scripts. The most prominent example is exposing the +HTML DOM in Blink. Other examples are e.g. node.js. It is less clear what kind +of capabilities we want to expose via this API surface. As a rule of thumb, we +want to expose operations as defined in the WebIDL and HTML spec: we +assume that those requirements are somewhat stable, and that they are a +superset of the requirements of other embedders including node.js. + +Ideally, the API surfaces defined in those specs hook into the ECMAScript spec +which in turn guarantees long-term stability of the API. + +# The V8 inspector + +All debugging capabilities of V8 should be exposed via the inspector protocol. +The exception to this are profiling features exposed via v8-profiler.h. +Changes to the inspector protocol need to ensure backwards compatibility and +commitment to maintain. diff --git a/NativeScript/napi/android/v8-13/include/DEPS b/NativeScript/napi/android/v8-13/include/DEPS new file mode 100644 index 000000000..c6d10cd4d --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/DEPS @@ -0,0 +1,13 @@ +include_rules = [ + # v8-inspector-protocol.h depends on generated files under include/inspector. + "+inspector", + "+cppgc/common.h", + # Used by v8-cppgc.h to bridge to cppgc. + "+cppgc/custom-space.h", + "+cppgc/heap-statistics.h", + "+cppgc/internal/conditional-stack-allocated.h", + "+cppgc/internal/write-barrier.h", + "+cppgc/type-traits.h", + "+cppgc/visitor.h", + "+perfetto", +] diff --git a/NativeScript/napi/android/v8-13/include/DIR_METADATA b/NativeScript/napi/android/v8-13/include/DIR_METADATA new file mode 100644 index 000000000..d65ff2a39 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/DIR_METADATA @@ -0,0 +1,14 @@ +# Metadata information for this directory. +# +# For more information on DIR_METADATA files, see: +# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md +# +# For the schema of this file, see Metadata message: +# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto + +monorail { + component: "Blink>JavaScript>API" +} +buganizer_public: { + component_id: 1456124 +} diff --git a/NativeScript/napi/android/v8-13/include/OWNERS b/NativeScript/napi/android/v8-13/include/OWNERS new file mode 100644 index 000000000..be9fad30a --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/OWNERS @@ -0,0 +1,24 @@ +adamk@chromium.org +cbruni@chromium.org +leszeks@chromium.org +mlippautz@chromium.org +verwaest@chromium.org +yangguo@chromium.org + +per-file *DEPS=file:../COMMON_OWNERS +per-file v8-internal.h=file:../COMMON_OWNERS + +per-file v8-debug.h=file:../src/debug/OWNERS + +per-file js_protocol.pdl=file:../src/inspector/OWNERS +per-file v8-inspector*=file:../src/inspector/OWNERS +per-file v8-inspector*=file:../src/inspector/OWNERS + +per-file v8-profiler.h=file:../src/profiler/OWNERS + +# Needed by the auto_tag builder +per-file v8-version.h=v8-ci-autoroll-builder@chops-service-accounts.iam.gserviceaccount.com + +# For branch updates: +per-file v8-version.h=file:../INFRA_OWNERS +per-file v8-version.h=vahl@chromium.org diff --git a/NativeScript/napi/android/v8-13/include/cppgc/DEPS b/NativeScript/napi/android/v8-13/include/cppgc/DEPS new file mode 100644 index 000000000..2ec7ebbd4 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "-include", + "+v8config.h", + "+v8-platform.h", + "+v8-source-location.h", + "+cppgc", + "-src", + "+libplatform/libplatform.h", +] diff --git a/NativeScript/napi/android/v8-13/include/cppgc/OWNERS b/NativeScript/napi/android/v8-13/include/cppgc/OWNERS new file mode 100644 index 000000000..6ccabf60b --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/OWNERS @@ -0,0 +1,2 @@ +bikineev@chromium.org +omerkatz@chromium.org \ No newline at end of file diff --git a/NativeScript/napi/android/v8-13/include/cppgc/README.md b/NativeScript/napi/android/v8-13/include/cppgc/README.md new file mode 100644 index 000000000..d825ea5bd --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/README.md @@ -0,0 +1,135 @@ +# Oilpan: C++ Garbage Collection + +Oilpan is an open-source garbage collection library for C++ that can be used stand-alone or in collaboration with V8's JavaScript garbage collector. +Oilpan implements mark-and-sweep garbage collection (GC) with limited compaction (for a subset of objects). + +**Key properties** + +- Trace-based garbage collection; +- Incremental and concurrent marking; +- Incremental and concurrent sweeping; +- Precise on-heap memory layout; +- Conservative on-stack memory layout; +- Allows for collection with and without considering stack; +- Non-incremental and non-concurrent compaction for selected spaces; + +See the [Hello World](https://chromium.googlesource.com/v8/v8/+/main/samples/cppgc/hello-world.cc) example on how to get started using Oilpan to manage C++ code. + +Oilpan follows V8's project organization, see e.g. on how we accept [contributions](https://v8.dev/docs/contribute) and [provide a stable API](https://v8.dev/docs/api). + +## Threading model + +Oilpan features thread-local garbage collection and assumes heaps are not shared among threads. +In other words, objects are accessed and ultimately reclaimed by the garbage collector on the same thread that allocates them. +This allows Oilpan to run garbage collection in parallel with mutators running in other threads. + +References to objects belonging to another thread's heap are modeled using cross-thread roots. +This is even true for on-heap to on-heap references. + +Oilpan heaps may generally not be accessed from different threads unless otherwise noted. + +## Heap partitioning + +Oilpan's heaps are partitioned into spaces. +The space for an object is chosen depending on a number of criteria, e.g.: + +- Objects over 64KiB are allocated in a large object space +- Objects can be assigned to a dedicated custom space. + Custom spaces can also be marked as compactable. +- Other objects are allocated in one of the normal page spaces bucketed depending on their size. + +## Precise and conservative garbage collection + +Oilpan supports two kinds of GCs: + +1. **Conservative GC.** +A GC is called conservative when it is executed while the regular native stack is not empty. +In this case, the native stack might contain references to objects in Oilpan's heap, which should be kept alive. +The GC scans the native stack and treats the pointers discovered via the native stack as part of the root set. +This kind of GC is considered imprecise because values on stack other than references may accidentally appear as references to on-heap object, which means these objects will be kept alive despite being in practice unreachable from the application as an actual reference. + +2. **Precise GC.** +A precise GC is triggered at the end of an event loop, which is controlled by an embedder via a platform. +At this point, it is guaranteed that there are no on-stack references pointing to Oilpan's heap. +This means there is no risk of confusing other value types with references. +Oilpan has precise knowledge of on-heap object layouts, and so it knows exactly where pointers lie in memory. +Oilpan can just start marking from the regular root set and collect all garbage precisely. + +## Atomic, incremental and concurrent garbage collection + +Oilpan has three modes of operation: + +1. **Atomic GC.** +The entire GC cycle, including all its phases (e.g. see [Marking](#Marking-phase) and [Sweeping](#Sweeping-phase)), are executed back to back in a single pause. +This mode of operation is also known as Stop-The-World (STW) garbage collection. +It results in the most jank (due to a single long pause), but is overall the most efficient (e.g. no need for write barriers). + +2. **Incremental GC.** +Garbage collection work is split up into multiple steps which are interleaved with the mutator, i.e. user code chunked into tasks. +Each step is a small chunk of work that is executed either as dedicated tasks between mutator tasks or, as needed, during mutator tasks. +Using incremental GC introduces the need for write barriers that record changes to the object graph so that a consistent state is observed and no objects are accidentally considered dead and reclaimed. +The incremental steps are followed by a smaller atomic pause to finalize garbage collection. +The smaller pause times, due to smaller chunks of work, helps with reducing jank. + +3. **Concurrent GC.** +This is the most common type of GC. +It builds on top of incremental GC and offloads much of the garbage collection work away from the mutator thread and on to background threads. +Using concurrent GC allows the mutator thread to spend less time on GC and more on the actual mutator. + +## Marking phase + +The marking phase consists of the following steps: + +1. Mark all objects in the root set. + +2. Mark all objects transitively reachable from the root set by calling `Trace()` methods defined on each object. + +3. Clear out all weak handles to unreachable objects and run weak callbacks. + +The marking phase can be executed atomically in a stop-the-world manner, in which all 3 steps are executed one after the other. + +Alternatively, it can also be executed incrementally/concurrently. +With incremental/concurrent marking, step 1 is executed in a short pause after which the mutator regains control. +Step 2 is repeatedly executed in an interleaved manner with the mutator. +When the GC is ready to finalize, i.e. step 2 is (almost) finished, another short pause is triggered in which step 2 is finished and step 3 is performed. + +To prevent a user-after-free (UAF) issues it is required for Oilpan to know about all edges in the object graph. +This means that all pointers except on-stack pointers must be wrapped with Oilpan's handles (i.e., Persistent<>, Member<>, WeakMember<>). +Raw pointers to on-heap objects create an edge that Oilpan cannot observe and cause UAF issues +Thus, raw pointers shall not be used to reference on-heap objects (except for raw pointers on native stacks). + +## Sweeping phase + +The sweeping phase consists of the following steps: + +1. Invoke pre-finalizers. +At this point, no destructors have been invoked and no memory has been reclaimed. +Pre-finalizers are allowed to access any other on-heap objects, even those that may get destructed. + +2. Sweeping invokes destructors of the dead (unreachable) objects and reclaims memory to be reused by future allocations. + +Assumptions should not be made about the order and the timing of their execution. +There is no guarantee on the order in which the destructors are invoked. +That's why destructors must not access any other on-heap objects (which might have already been destructed). +If some destructor unavoidably needs to access other on-heap objects, it will have to be converted to a pre-finalizer. +The pre-finalizer is allowed to access other on-heap objects. + +The mutator is resumed before all destructors have ran. +For example, imagine a case where X is a client of Y, and Y holds a list of clients. +If the code relies on X's destructor removing X from the list, there is a risk that Y iterates the list and calls some method of X which may touch other on-heap objects. +This causes a use-after-free. +Care must be taken to make sure that X is explicitly removed from the list before the mutator resumes its execution in a way that doesn't rely on X's destructor (e.g. a pre-finalizer). + +Similar to marking, sweeping can be executed in either an atomic stop-the-world manner or incrementally/concurrently. +With incremental/concurrent sweeping, step 2 is interleaved with mutator. +Incremental/concurrent sweeping can be atomically finalized in case it is needed to trigger another GC cycle. +Even with concurrent sweeping, destructors are guaranteed to run on the thread the object has been allocated on to preserve C++ semantics. + +Notes: + +* Weak processing runs only when the holder object of the WeakMember outlives the pointed object. +If the holder object and the pointed object die at the same time, weak processing doesn't run. +It is wrong to write code assuming that the weak processing always runs. + +* Pre-finalizers are heavy because the thread needs to scan all pre-finalizers at each sweeping phase to determine which pre-finalizers should be invoked (the thread needs to invoke pre-finalizers of dead objects). +Adding pre-finalizers to frequently created objects should be avoided. diff --git a/NativeScript/napi/android/v8-13/include/cppgc/allocation.h b/NativeScript/napi/android/v8-13/include/cppgc/allocation.h new file mode 100644 index 000000000..a7955fd10 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/allocation.h @@ -0,0 +1,308 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_ALLOCATION_H_ +#define INCLUDE_CPPGC_ALLOCATION_H_ + +#include +#include +#include +#include +#include +#include + +#include "cppgc/custom-space.h" +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/gc-info.h" +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +#if defined(__has_attribute) +#if __has_attribute(assume_aligned) +#define CPPGC_DEFAULT_ALIGNED \ + __attribute__((assume_aligned(api_constants::kDefaultAlignment))) +#define CPPGC_DOUBLE_WORD_ALIGNED \ + __attribute__((assume_aligned(2 * api_constants::kDefaultAlignment))) +#endif // __has_attribute(assume_aligned) +#endif // defined(__has_attribute) + +#if !defined(CPPGC_DEFAULT_ALIGNED) +#define CPPGC_DEFAULT_ALIGNED +#endif + +#if !defined(CPPGC_DOUBLE_WORD_ALIGNED) +#define CPPGC_DOUBLE_WORD_ALIGNED +#endif + +namespace cppgc { + +/** + * AllocationHandle is used to allocate garbage-collected objects. + */ +class AllocationHandle; + +namespace internal { + +using AlignVal = std::align_val_t; + +class MakeGarbageCollectedTraitInternal { + protected: + static inline void MarkObjectAsFullyConstructed(const void* payload) { + // See api_constants for an explanation of the constants. + std::atomic* atomic_mutable_bitfield = + reinterpret_cast*>( + const_cast(reinterpret_cast( + reinterpret_cast(payload) - + api_constants::kFullyConstructedBitFieldOffsetFromPayload))); + // It's safe to split use load+store here (instead of a read-modify-write + // operation), since it's guaranteed that this 16-bit bitfield is only + // modified by a single thread. This is cheaper in terms of code bloat (on + // ARM) and performance. + uint16_t value = atomic_mutable_bitfield->load(std::memory_order_relaxed); + value |= api_constants::kFullyConstructedBitMask; + atomic_mutable_bitfield->store(value, std::memory_order_release); + } + + // Dispatch based on compile-time information. + // + // Default implementation is for a custom space with >`kDefaultAlignment` byte + // alignment. + template + struct AllocationDispatcher final { + static void* Invoke(AllocationHandle& handle, size_t size) { + static_assert(std::is_base_of_v, + "Custom space must inherit from CustomSpaceBase."); + static_assert( + !CustomSpace::kSupportsCompaction, + "Custom spaces that support compaction do not support allocating " + "objects with non-default (i.e. word-sized) alignment."); + return MakeGarbageCollectedTraitInternal::Allocate( + handle, size, static_cast(alignment), + internal::GCInfoTrait::Index(), CustomSpace::kSpaceIndex); + } + }; + + // Fast path for regular allocations for the default space with + // `kDefaultAlignment` byte alignment. + template + struct AllocationDispatcher + final { + static void* Invoke(AllocationHandle& handle, size_t size) { + return MakeGarbageCollectedTraitInternal::Allocate( + handle, size, internal::GCInfoTrait::Index()); + } + }; + + // Default space with >`kDefaultAlignment` byte alignment. + template + struct AllocationDispatcher final { + static void* Invoke(AllocationHandle& handle, size_t size) { + return MakeGarbageCollectedTraitInternal::Allocate( + handle, size, static_cast(alignment), + internal::GCInfoTrait::Index()); + } + }; + + // Custom space with `kDefaultAlignment` byte alignment. + template + struct AllocationDispatcher + final { + static void* Invoke(AllocationHandle& handle, size_t size) { + static_assert(std::is_base_of_v, + "Custom space must inherit from CustomSpaceBase."); + return MakeGarbageCollectedTraitInternal::Allocate( + handle, size, internal::GCInfoTrait::Index(), + CustomSpace::kSpaceIndex); + } + }; + + private: + V8_EXPORT static void* CPPGC_DEFAULT_ALIGNED + Allocate(cppgc::AllocationHandle&, size_t, GCInfoIndex); + V8_EXPORT static void* CPPGC_DOUBLE_WORD_ALIGNED + Allocate(cppgc::AllocationHandle&, size_t, AlignVal, GCInfoIndex); + V8_EXPORT static void* CPPGC_DEFAULT_ALIGNED + Allocate(cppgc::AllocationHandle&, size_t, GCInfoIndex, CustomSpaceIndex); + V8_EXPORT static void* CPPGC_DOUBLE_WORD_ALIGNED + Allocate(cppgc::AllocationHandle&, size_t, AlignVal, GCInfoIndex, + CustomSpaceIndex); + + friend class HeapObjectHeader; +}; + +} // namespace internal + +/** + * Base trait that provides utilities for advancers users that have custom + * allocation needs (e.g., overriding size). It's expected that users override + * MakeGarbageCollectedTrait (see below) and inherit from + * MakeGarbageCollectedTraitBase and make use of the low-level primitives + * offered to allocate and construct an object. + */ +template +class MakeGarbageCollectedTraitBase + : private internal::MakeGarbageCollectedTraitInternal { + private: + static_assert(internal::IsGarbageCollectedType::value, + "T needs to be a garbage collected object"); + static_assert(!IsGarbageCollectedWithMixinTypeV || + sizeof(T) <= + internal::api_constants::kLargeObjectSizeThreshold, + "GarbageCollectedMixin may not be a large object"); + + protected: + /** + * Allocates memory for an object of type T. + * + * \param handle AllocationHandle identifying the heap to allocate the object + * on. + * \param size The size that should be reserved for the object. + * \returns the memory to construct an object of type T on. + */ + V8_INLINE static void* Allocate(AllocationHandle& handle, size_t size) { + static_assert( + std::is_base_of_v, + "U of GarbageCollected must be a base of T. Check " + "GarbageCollected base class inheritance."); + static constexpr size_t kWantedAlignment = + alignof(T) < internal::api_constants::kDefaultAlignment + ? internal::api_constants::kDefaultAlignment + : alignof(T); + static_assert( + kWantedAlignment <= internal::api_constants::kMaxSupportedAlignment, + "Requested alignment larger than alignof(std::max_align_t) bytes. " + "Please file a bug to possibly get this restriction lifted."); + return AllocationDispatcher< + typename internal::GCInfoFolding< + T, typename T::ParentMostGarbageCollectedType>::ResultType, + typename SpaceTrait::Space, kWantedAlignment>::Invoke(handle, size); + } + + /** + * Marks an object as fully constructed, resulting in precise handling by the + * garbage collector. + * + * \param payload The base pointer the object is allocated at. + */ + V8_INLINE static void MarkObjectAsFullyConstructed(const void* payload) { + internal::MakeGarbageCollectedTraitInternal::MarkObjectAsFullyConstructed( + payload); + } +}; + +/** + * Passed to MakeGarbageCollected to specify how many bytes should be appended + * to the allocated object. + * + * Example: + * \code + * class InlinedArray final : public GarbageCollected { + * public: + * explicit InlinedArray(size_t bytes) : size(bytes), byte_array(this + 1) {} + * void Trace(Visitor*) const {} + + * size_t size; + * char* byte_array; + * }; + * + * auto* inlined_array = MakeGarbageCollectedbyte_array[i]); + * } + * \endcode + */ +struct AdditionalBytes { + constexpr explicit AdditionalBytes(size_t bytes) : value(bytes) {} + const size_t value; +}; + +/** + * Default trait class that specifies how to construct an object of type T. + * Advanced users may override how an object is constructed using the utilities + * that are provided through MakeGarbageCollectedTraitBase. + * + * Any trait overriding construction must + * - allocate through `MakeGarbageCollectedTraitBase::Allocate`; + * - mark the object as fully constructed using + * `MakeGarbageCollectedTraitBase::MarkObjectAsFullyConstructed`; + */ +template +class MakeGarbageCollectedTrait : public MakeGarbageCollectedTraitBase { + public: + template + static T* Call(AllocationHandle& handle, Args&&... args) { + void* memory = + MakeGarbageCollectedTraitBase::Allocate(handle, sizeof(T)); + T* object = ::new (memory) T(std::forward(args)...); + MakeGarbageCollectedTraitBase::MarkObjectAsFullyConstructed(object); + return object; + } + + template + static T* Call(AllocationHandle& handle, AdditionalBytes additional_bytes, + Args&&... args) { + void* memory = MakeGarbageCollectedTraitBase::Allocate( + handle, sizeof(T) + additional_bytes.value); + T* object = ::new (memory) T(std::forward(args)...); + MakeGarbageCollectedTraitBase::MarkObjectAsFullyConstructed(object); + return object; + } +}; + +/** + * Allows users to specify a post-construction callback for specific types. The + * callback is invoked on the instance of type T right after it has been + * constructed. This can be useful when the callback requires a + * fully-constructed object to be able to dispatch to virtual methods. + */ +template +struct PostConstructionCallbackTrait { + static void Call(T*) {} +}; + +/** + * Constructs a managed object of type T where T transitively inherits from + * GarbageCollected. + * + * \param args List of arguments with which an instance of T will be + * constructed. + * \returns an instance of type T. + */ +template +V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle, Args&&... args) { + T* object = + MakeGarbageCollectedTrait::Call(handle, std::forward(args)...); + PostConstructionCallbackTrait::Call(object); + return object; +} + +/** + * Constructs a managed object of type T where T transitively inherits from + * GarbageCollected. Created objects will have additional bytes appended to + * it. Allocated memory would suffice for `sizeof(T) + additional_bytes`. + * + * \param additional_bytes Denotes how many bytes to append to T. + * \param args List of arguments with which an instance of T will be + * constructed. + * \returns an instance of type T. + */ +template +V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle, + AdditionalBytes additional_bytes, + Args&&... args) { + T* object = MakeGarbageCollectedTrait::Call(handle, additional_bytes, + std::forward(args)...); + PostConstructionCallbackTrait::Call(object); + return object; +} + +} // namespace cppgc + +#undef CPPGC_DEFAULT_ALIGNED +#undef CPPGC_DOUBLE_WORD_ALIGNED + +#endif // INCLUDE_CPPGC_ALLOCATION_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/common.h b/NativeScript/napi/android/v8-13/include/cppgc/common.h new file mode 100644 index 000000000..961038360 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/common.h @@ -0,0 +1,28 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_COMMON_H_ +#define INCLUDE_CPPGC_COMMON_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +/** + * Indicator for the stack state of the embedder. + */ +enum class EmbedderStackState { + /** + * Stack may contain interesting heap pointers. + */ + kMayContainHeapPointers, + /** + * Stack does not contain any interesting heap pointers. + */ + kNoHeapPointers, +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_COMMON_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/cross-thread-persistent.h b/NativeScript/napi/android/v8-13/include/cppgc/cross-thread-persistent.h new file mode 100644 index 000000000..df172b3a4 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/cross-thread-persistent.h @@ -0,0 +1,466 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ +#define INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ + +#include + +#include "cppgc/internal/persistent-node.h" +#include "cppgc/internal/pointer-policies.h" +#include "cppgc/persistent.h" +#include "cppgc/visitor.h" + +namespace cppgc { +namespace internal { + +// Wrapper around PersistentBase that allows accessing poisoned memory when +// using ASAN. This is needed as the GC of the heap that owns the value +// of a CTP, may clear it (heap termination, weakness) while the object +// holding the CTP may be poisoned as itself may be deemed dead. +class CrossThreadPersistentBase : public PersistentBase { + public: + CrossThreadPersistentBase() = default; + explicit CrossThreadPersistentBase(const void* raw) : PersistentBase(raw) {} + + V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const { + return raw_; + } + + V8_CLANG_NO_SANITIZE("address") + PersistentNode* GetNodeFromGC() const { return node_; } + + V8_CLANG_NO_SANITIZE("address") + void ClearFromGC() const { + raw_ = nullptr; + SetNodeSafe(nullptr); + } + + // GetNodeSafe() can be used for a thread-safe IsValid() check in a + // double-checked locking pattern. See ~BasicCrossThreadPersistent. + PersistentNode* GetNodeSafe() const { + return reinterpret_cast*>(&node_)->load( + std::memory_order_acquire); + } + + // The GC writes using SetNodeSafe() while holding the lock. + V8_CLANG_NO_SANITIZE("address") + void SetNodeSafe(PersistentNode* value) const { +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define V8_IS_ASAN 1 +#endif +#endif + +#ifdef V8_IS_ASAN + __atomic_store(&node_, &value, __ATOMIC_RELEASE); +#else // !V8_IS_ASAN + // Non-ASAN builds can use atomics. This also covers MSVC which does not + // have the __atomic_store intrinsic. + reinterpret_cast*>(&node_)->store( + value, std::memory_order_release); +#endif // !V8_IS_ASAN + +#undef V8_IS_ASAN + } +}; + +template +class BasicCrossThreadPersistent final : public CrossThreadPersistentBase, + public LocationPolicy, + private WeaknessPolicy, + private CheckingPolicy { + public: + using typename WeaknessPolicy::IsStrongPersistent; + using PointeeType = T; + + ~BasicCrossThreadPersistent() { + // This implements fast path for destroying empty/sentinel. + // + // Simplified version of `AssignUnsafe()` to allow calling without a + // complete type `T`. Uses double-checked locking with a simple thread-safe + // check for a valid handle based on a node. + if (GetNodeSafe()) { + PersistentRegionLock guard; + const void* old_value = GetValue(); + // The fast path check (GetNodeSafe()) does not acquire the lock. Recheck + // validity while holding the lock to ensure the reference has not been + // cleared. + if (IsValid(old_value)) { + CrossThreadPersistentRegion& region = + this->GetPersistentRegion(old_value); + region.FreeNode(GetNode()); + SetNode(nullptr); + } else { + CPPGC_DCHECK(!GetNode()); + } + } + // No need to call SetValue() as the handle is not used anymore. This can + // leave behind stale sentinel values but will always destroy the underlying + // node. + } + + BasicCrossThreadPersistent( + const SourceLocation& loc = SourceLocation::Current()) + : LocationPolicy(loc) {} + + BasicCrossThreadPersistent( + std::nullptr_t, const SourceLocation& loc = SourceLocation::Current()) + : LocationPolicy(loc) {} + + BasicCrossThreadPersistent( + SentinelPointer s, const SourceLocation& loc = SourceLocation::Current()) + : CrossThreadPersistentBase(s), LocationPolicy(loc) {} + + BasicCrossThreadPersistent( + T* raw, const SourceLocation& loc = SourceLocation::Current()) + : CrossThreadPersistentBase(raw), LocationPolicy(loc) { + if (!IsValid(raw)) return; + PersistentRegionLock guard; + CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); + SetNode(region.AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(raw); + } + + class UnsafeCtorTag { + private: + UnsafeCtorTag() = default; + template + friend class BasicCrossThreadPersistent; + }; + + BasicCrossThreadPersistent( + UnsafeCtorTag, T* raw, + const SourceLocation& loc = SourceLocation::Current()) + : CrossThreadPersistentBase(raw), LocationPolicy(loc) { + if (!IsValid(raw)) return; + CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); + SetNode(region.AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(raw); + } + + BasicCrossThreadPersistent( + T& raw, const SourceLocation& loc = SourceLocation::Current()) + : BasicCrossThreadPersistent(&raw, loc) {} + + template >> + BasicCrossThreadPersistent( + internal::BasicMember + member, + const SourceLocation& loc = SourceLocation::Current()) + : BasicCrossThreadPersistent(member.Get(), loc) {} + + BasicCrossThreadPersistent( + const BasicCrossThreadPersistent& other, + const SourceLocation& loc = SourceLocation::Current()) + : BasicCrossThreadPersistent(loc) { + // Invoke operator=. + *this = other; + } + + // Heterogeneous ctor. + template >> + BasicCrossThreadPersistent( + const BasicCrossThreadPersistent& other, + const SourceLocation& loc = SourceLocation::Current()) + : BasicCrossThreadPersistent(loc) { + *this = other; + } + + BasicCrossThreadPersistent( + BasicCrossThreadPersistent&& other, + const SourceLocation& loc = SourceLocation::Current()) noexcept { + // Invoke operator=. + *this = std::move(other); + } + + BasicCrossThreadPersistent& operator=( + const BasicCrossThreadPersistent& other) { + PersistentRegionLock guard; + AssignSafe(guard, other.Get()); + return *this; + } + + template >> + BasicCrossThreadPersistent& operator=( + const BasicCrossThreadPersistent& other) { + PersistentRegionLock guard; + AssignSafe(guard, other.Get()); + return *this; + } + + BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) { + if (this == &other) return *this; + Clear(); + PersistentRegionLock guard; + PersistentBase::operator=(std::move(other)); + LocationPolicy::operator=(std::move(other)); + if (!IsValid(GetValue())) return *this; + GetNode()->UpdateOwner(this); + other.SetValue(nullptr); + other.SetNode(nullptr); + this->CheckPointer(Get()); + return *this; + } + + /** + * Assigns a raw pointer. + * + * Note: **Not thread-safe.** + */ + BasicCrossThreadPersistent& operator=(T* other) { + AssignUnsafe(other); + return *this; + } + + // Assignment from member. + template >> + BasicCrossThreadPersistent& operator=( + internal::BasicMember + member) { + return operator=(member.Get()); + } + + /** + * Assigns a nullptr. + * + * \returns the handle. + */ + BasicCrossThreadPersistent& operator=(std::nullptr_t) { + Clear(); + return *this; + } + + /** + * Assigns the sentinel pointer. + * + * \returns the handle. + */ + BasicCrossThreadPersistent& operator=(SentinelPointer s) { + PersistentRegionLock guard; + AssignSafe(guard, s); + return *this; + } + + /** + * Returns a pointer to the stored object. + * + * Note: **Not thread-safe.** + * + * \returns a pointer to the stored object. + */ + // CFI cast exemption to allow passing SentinelPointer through T* and support + // heterogeneous assignments between different Member and Persistent handles + // based on their actual types. + V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { + return static_cast(const_cast(GetValue())); + } + + /** + * Clears the stored object. + */ + void Clear() { + PersistentRegionLock guard; + AssignSafe(guard, nullptr); + } + + /** + * Returns a pointer to the stored object and releases it. + * + * Note: **Not thread-safe.** + * + * \returns a pointer to the stored object. + */ + T* Release() { + T* result = Get(); + Clear(); + return result; + } + + /** + * Conversio to boolean. + * + * Note: **Not thread-safe.** + * + * \returns true if an actual object has been stored and false otherwise. + */ + explicit operator bool() const { return Get(); } + + /** + * Conversion to object of type T. + * + * Note: **Not thread-safe.** + * + * \returns the object. + */ + operator T*() const { return Get(); } + + /** + * Dereferences the stored object. + * + * Note: **Not thread-safe.** + */ + T* operator->() const { return Get(); } + T& operator*() const { return *Get(); } + + template + BasicCrossThreadPersistent + To() const { + using OtherBasicCrossThreadPersistent = + BasicCrossThreadPersistent; + PersistentRegionLock guard; + return OtherBasicCrossThreadPersistent( + typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(), + static_cast(Get())); + } + + template ::IsStrongPersistent::value>> + BasicCrossThreadPersistent + Lock() const { + return BasicCrossThreadPersistent< + U, internal::StrongCrossThreadPersistentPolicy>(*this); + } + + private: + static bool IsValid(const void* ptr) { + return ptr && ptr != kSentinelPointer; + } + + static void TraceAsRoot(RootVisitor& root_visitor, const void* ptr) { + root_visitor.Trace(*static_cast(ptr)); + } + + void AssignUnsafe(T* ptr) { + const void* old_value = GetValue(); + if (IsValid(old_value)) { + PersistentRegionLock guard; + old_value = GetValue(); + // The fast path check (IsValid()) does not acquire the lock. Reload + // the value to ensure the reference has not been cleared. + if (IsValid(old_value)) { + CrossThreadPersistentRegion& region = + this->GetPersistentRegion(old_value); + if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { + SetValue(ptr); + this->CheckPointer(ptr); + return; + } + region.FreeNode(GetNode()); + SetNode(nullptr); + } else { + CPPGC_DCHECK(!GetNode()); + } + } + SetValue(ptr); + if (!IsValid(ptr)) return; + PersistentRegionLock guard; + SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(ptr); + } + + void AssignSafe(PersistentRegionLock&, T* ptr) { + PersistentRegionLock::AssertLocked(); + const void* old_value = GetValue(); + if (IsValid(old_value)) { + CrossThreadPersistentRegion& region = + this->GetPersistentRegion(old_value); + if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { + SetValue(ptr); + this->CheckPointer(ptr); + return; + } + region.FreeNode(GetNode()); + SetNode(nullptr); + } + SetValue(ptr); + if (!IsValid(ptr)) return; + SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(ptr); + } + + void ClearFromGC() const { + if (IsValid(GetValueFromGC())) { + WeaknessPolicy::GetPersistentRegion(GetValueFromGC()) + .FreeNode(GetNodeFromGC()); + CrossThreadPersistentBase::ClearFromGC(); + } + } + + // See Get() for details. + V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") + T* GetFromGC() const { + return static_cast(const_cast(GetValueFromGC())); + } + + friend class internal::RootVisitor; +}; + +template +struct IsWeak< + BasicCrossThreadPersistent> + : std::true_type {}; + +} // namespace internal + +namespace subtle { + +/** + * **DO NOT USE: Has known caveats, see below.** + * + * CrossThreadPersistent allows retaining objects from threads other than the + * thread the owning heap is operating on. + * + * Known caveats: + * - Does not protect the heap owning an object from terminating. + * - Reaching transitively through the graph is unsupported as objects may be + * moved concurrently on the thread owning the object. + */ +template +using CrossThreadPersistent = internal::BasicCrossThreadPersistent< + T, internal::StrongCrossThreadPersistentPolicy>; + +/** + * **DO NOT USE: Has known caveats, see below.** + * + * CrossThreadPersistent allows weakly retaining objects from threads other than + * the thread the owning heap is operating on. + * + * Known caveats: + * - Does not protect the heap owning an object from terminating. + * - Reaching transitively through the graph is unsupported as objects may be + * moved concurrently on the thread owning the object. + */ +template +using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent< + T, internal::WeakCrossThreadPersistentPolicy>; + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/custom-space.h b/NativeScript/napi/android/v8-13/include/cppgc/custom-space.h new file mode 100644 index 000000000..757c4fde1 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/custom-space.h @@ -0,0 +1,97 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_CUSTOM_SPACE_H_ +#define INCLUDE_CPPGC_CUSTOM_SPACE_H_ + +#include + +namespace cppgc { + +/** + * Index identifying a custom space. + */ +struct CustomSpaceIndex { + constexpr CustomSpaceIndex(size_t value) : value(value) {} // NOLINT + size_t value; +}; + +/** + * Top-level base class for custom spaces. Users must inherit from CustomSpace + * below. + */ +class CustomSpaceBase { + public: + virtual ~CustomSpaceBase() = default; + virtual CustomSpaceIndex GetCustomSpaceIndex() const = 0; + virtual bool IsCompactable() const = 0; +}; + +/** + * Base class custom spaces should directly inherit from. The class inheriting + * from `CustomSpace` must define `kSpaceIndex` as unique space index. These + * indices need for form a sequence starting at 0. + * + * Example: + * \code + * class CustomSpace1 : public CustomSpace { + * public: + * static constexpr CustomSpaceIndex kSpaceIndex = 0; + * }; + * class CustomSpace2 : public CustomSpace { + * public: + * static constexpr CustomSpaceIndex kSpaceIndex = 1; + * }; + * \endcode + */ +template +class CustomSpace : public CustomSpaceBase { + public: + /** + * Compaction is only supported on spaces that manually manage slots + * recording. + */ + static constexpr bool kSupportsCompaction = false; + + CustomSpaceIndex GetCustomSpaceIndex() const final { + return ConcreteCustomSpace::kSpaceIndex; + } + bool IsCompactable() const final { + return ConcreteCustomSpace::kSupportsCompaction; + } +}; + +/** + * User-overridable trait that allows pinning types to custom spaces. + */ +template +struct SpaceTrait { + using Space = void; +}; + +namespace internal { + +template +struct IsAllocatedOnCompactableSpaceImpl { + static constexpr bool value = CustomSpace::kSupportsCompaction; +}; + +template <> +struct IsAllocatedOnCompactableSpaceImpl { + // Non-custom spaces are by default not compactable. + static constexpr bool value = false; +}; + +template +struct IsAllocatedOnCompactableSpace { + public: + static constexpr bool value = + IsAllocatedOnCompactableSpaceImpl::Space>::value; +}; + +} // namespace internal + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_CUSTOM_SPACE_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/default-platform.h b/NativeScript/napi/android/v8-13/include/cppgc/default-platform.h new file mode 100644 index 000000000..07ce55bbf --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/default-platform.h @@ -0,0 +1,68 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_DEFAULT_PLATFORM_H_ +#define INCLUDE_CPPGC_DEFAULT_PLATFORM_H_ + +#include + +#include "cppgc/platform.h" +#include "libplatform/libplatform.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +/** + * Platform provided by cppgc. Uses V8's DefaultPlatform provided by + * libplatform internally. Exception: `GetForegroundTaskRunner()`, see below. + */ +class V8_EXPORT DefaultPlatform : public Platform { + public: + using IdleTaskSupport = v8::platform::IdleTaskSupport; + explicit DefaultPlatform( + int thread_pool_size = 0, + IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled, + std::unique_ptr tracing_controller = {}) + : v8_platform_(v8::platform::NewDefaultPlatform( + thread_pool_size, idle_task_support, + v8::platform::InProcessStackDumping::kDisabled, + std::move(tracing_controller))) {} + + cppgc::PageAllocator* GetPageAllocator() override { + return v8_platform_->GetPageAllocator(); + } + + double MonotonicallyIncreasingTime() override { + return v8_platform_->MonotonicallyIncreasingTime(); + } + + std::shared_ptr GetForegroundTaskRunner( + TaskPriority priority) override { + // V8's default platform creates a new task runner when passed the + // `v8::Isolate` pointer the first time. For non-default platforms this will + // require getting the appropriate task runner. + return v8_platform_->GetForegroundTaskRunner(kNoIsolate, priority); + } + + std::unique_ptr PostJob( + cppgc::TaskPriority priority, + std::unique_ptr job_task) override { + return v8_platform_->PostJob(priority, std::move(job_task)); + } + + TracingController* GetTracingController() override { + return v8_platform_->GetTracingController(); + } + + v8::Platform* GetV8Platform() const { return v8_platform_.get(); } + + protected: + static constexpr v8::Isolate* kNoIsolate = nullptr; + + std::unique_ptr v8_platform_; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_DEFAULT_PLATFORM_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/explicit-management.h b/NativeScript/napi/android/v8-13/include/cppgc/explicit-management.h new file mode 100644 index 000000000..0290328dc --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/explicit-management.h @@ -0,0 +1,100 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ +#define INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ + +#include + +#include "cppgc/allocation.h" +#include "cppgc/internal/logging.h" +#include "cppgc/type-traits.h" + +namespace cppgc { + +class HeapHandle; + +namespace subtle { + +template +void FreeUnreferencedObject(HeapHandle& heap_handle, T& object); +template +bool Resize(T& object, AdditionalBytes additional_bytes); + +} // namespace subtle + +namespace internal { + +class ExplicitManagementImpl final { + private: + V8_EXPORT static void FreeUnreferencedObject(HeapHandle&, void*); + V8_EXPORT static bool Resize(void*, size_t); + + template + friend void subtle::FreeUnreferencedObject(HeapHandle&, T&); + template + friend bool subtle::Resize(T&, AdditionalBytes); +}; +} // namespace internal + +namespace subtle { + +/** + * Informs the garbage collector that `object` can be immediately reclaimed. The + * destructor may not be invoked immediately but only on next garbage + * collection. + * + * It is up to the embedder to guarantee that no other object holds a reference + * to `object` after calling `FreeUnreferencedObject()`. In case such a + * reference exists, it's use results in a use-after-free. + * + * To aid in using the API, `FreeUnreferencedObject()` may be called from + * destructors on objects that would be reclaimed in the same garbage collection + * cycle. + * + * \param heap_handle The corresponding heap. + * \param object Reference to an object that is of type `GarbageCollected` and + * should be immediately reclaimed. + */ +template +void FreeUnreferencedObject(HeapHandle& heap_handle, T& object) { + static_assert(IsGarbageCollectedTypeV, + "Object must be of type GarbageCollected."); + internal::ExplicitManagementImpl::FreeUnreferencedObject(heap_handle, + &object); +} + +/** + * Tries to resize `object` of type `T` with additional bytes on top of + * sizeof(T). Resizing is only useful with trailing inlined storage, see e.g. + * `MakeGarbageCollected(AllocationHandle&, AdditionalBytes)`. + * + * `Resize()` performs growing or shrinking as needed and may skip the operation + * for internal reasons, see return value. + * + * It is up to the embedder to guarantee that in case of shrinking a larger + * object down, the reclaimed area is not used anymore. Any subsequent use + * results in a use-after-free. + * + * The `object` must be live when calling `Resize()`. + * + * \param object Reference to an object that is of type `GarbageCollected` and + * should be resized. + * \param additional_bytes Bytes in addition to sizeof(T) that the object should + * provide. + * \returns true when the operation was successful and the result can be relied + * on, and false otherwise. + */ +template +bool Resize(T& object, AdditionalBytes additional_bytes) { + static_assert(IsGarbageCollectedTypeV, + "Object must be of type GarbageCollected."); + return internal::ExplicitManagementImpl::Resize( + &object, sizeof(T) + additional_bytes.value); +} + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/garbage-collected.h b/NativeScript/napi/android/v8-13/include/cppgc/garbage-collected.h new file mode 100644 index 000000000..dfd758a35 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/garbage-collected.h @@ -0,0 +1,114 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_GARBAGE_COLLECTED_H_ +#define INCLUDE_CPPGC_GARBAGE_COLLECTED_H_ + +#include "cppgc/internal/api-constants.h" +#include "cppgc/platform.h" +#include "cppgc/trace-trait.h" +#include "cppgc/type-traits.h" + +namespace cppgc { + +class Visitor; + +/** + * Base class for managed objects. Only descendent types of `GarbageCollected` + * can be constructed using `MakeGarbageCollected()`. Must be inherited from as + * left-most base class. + * + * Types inheriting from GarbageCollected must provide a method of + * signature `void Trace(cppgc::Visitor*) const` that dispatchs all managed + * pointers to the visitor and delegates to garbage-collected base classes. + * The method must be virtual if the type is not directly a child of + * GarbageCollected and marked as final. + * + * \code + * // Example using final class. + * class FinalType final : public GarbageCollected { + * public: + * void Trace(cppgc::Visitor* visitor) const { + * // Dispatch using visitor->Trace(...); + * } + * }; + * + * // Example using non-final base class. + * class NonFinalBase : public GarbageCollected { + * public: + * virtual void Trace(cppgc::Visitor*) const {} + * }; + * + * class FinalChild final : public NonFinalBase { + * public: + * void Trace(cppgc::Visitor* visitor) const final { + * // Dispatch using visitor->Trace(...); + * NonFinalBase::Trace(visitor); + * } + * }; + * \endcode + */ +template +class GarbageCollected { + public: + using IsGarbageCollectedTypeMarker = void; + using ParentMostGarbageCollectedType = T; + + // Must use MakeGarbageCollected. + void* operator new(size_t) = delete; + void* operator new[](size_t) = delete; + // The garbage collector is taking care of reclaiming the object. Also, + // virtual destructor requires an unambiguous, accessible 'operator delete'. + void operator delete(void*) { +#ifdef V8_ENABLE_CHECKS + internal::Fatal( + "Manually deleting a garbage collected object is not allowed"); +#endif // V8_ENABLE_CHECKS + } + void operator delete[](void*) = delete; + + protected: + GarbageCollected() = default; +}; + +/** + * Base class for managed mixin objects. Such objects cannot be constructed + * directly but must be mixed into the inheritance hierarchy of a + * GarbageCollected object. + * + * Types inheriting from GarbageCollectedMixin must override a virtual method + * of signature `void Trace(cppgc::Visitor*) const` that dispatchs all managed + * pointers to the visitor and delegates to base classes. + * + * \code + * class Mixin : public GarbageCollectedMixin { + * public: + * void Trace(cppgc::Visitor* visitor) const override { + * // Dispatch using visitor->Trace(...); + * } + * }; + * \endcode + */ +class GarbageCollectedMixin { + public: + using IsGarbageCollectedMixinTypeMarker = void; + + // Must use MakeGarbageCollected. + void* operator new(size_t) = delete; + void* operator new[](size_t) = delete; + // The garbage collector is taking care of reclaiming the object. + // Not override the non-array varaint of `delete` to not conflict with the + // operator in GarbageCollected above. + void operator delete[](void*) = delete; + + /** + * This Trace method must be overriden by objects inheriting from + * GarbageCollectedMixin. + */ + virtual void Trace(cppgc::Visitor*) const {} +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_GARBAGE_COLLECTED_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/heap-consistency.h b/NativeScript/napi/android/v8-13/include/cppgc/heap-consistency.h new file mode 100644 index 000000000..23b5d9099 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/heap-consistency.h @@ -0,0 +1,309 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_HEAP_CONSISTENCY_H_ +#define INCLUDE_CPPGC_HEAP_CONSISTENCY_H_ + +#include + +#include "cppgc/internal/write-barrier.h" +#include "cppgc/macros.h" +#include "cppgc/member.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +class HeapHandle; + +namespace subtle { + +/** + * **DO NOT USE: Use the appropriate managed types.** + * + * Consistency helpers that aid in maintaining a consistent internal state of + * the garbage collector. + */ +class HeapConsistency final { + public: + using WriteBarrierParams = internal::WriteBarrier::Params; + using WriteBarrierType = internal::WriteBarrier::Type; + + /** + * Gets the required write barrier type for a specific write. + * + * \param slot Slot containing the pointer to the object. The slot itself + * must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + * \param value The pointer to the object. May be an interior pointer to an + * interface of the actual object. + * \param params Parameters that may be used for actual write barrier calls. + * Only filled if return value indicates that a write barrier is needed. The + * contents of the `params` are an implementation detail. + * \returns whether a write barrier is needed and which barrier to invoke. + */ + static V8_INLINE WriteBarrierType GetWriteBarrierType( + const void* slot, const void* value, WriteBarrierParams& params) { + return internal::WriteBarrier::GetWriteBarrierType(slot, value, params); + } + + /** + * Gets the required write barrier type for a specific write. This override is + * only used for all the BasicMember types. + * + * \param slot Slot containing the pointer to the object. The slot itself + * must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + * \param value The pointer to the object held via `BasicMember`. + * \param params Parameters that may be used for actual write barrier calls. + * Only filled if return value indicates that a write barrier is needed. The + * contents of the `params` are an implementation detail. + * \returns whether a write barrier is needed and which barrier to invoke. + */ + template + static V8_INLINE WriteBarrierType GetWriteBarrierType( + const internal::BasicMember& value, + WriteBarrierParams& params) { + return internal::WriteBarrier::GetWriteBarrierType( + value.GetRawSlot(), value.GetRawStorage(), params); + } + + /** + * Gets the required write barrier type for a specific write. + * + * \param slot Slot to some part of an object. The object must not necessarily + have been allocated using `MakeGarbageCollected()` but can also live + off-heap or on stack. + * \param params Parameters that may be used for actual write barrier calls. + * Only filled if return value indicates that a write barrier is needed. The + * contents of the `params` are an implementation detail. + * \param callback Callback returning the corresponding heap handle. The + * callback is only invoked if the heap cannot otherwise be figured out. The + * callback must not allocate. + * \returns whether a write barrier is needed and which barrier to invoke. + */ + template + static V8_INLINE WriteBarrierType + GetWriteBarrierType(const void* slot, WriteBarrierParams& params, + HeapHandleCallback callback) { + return internal::WriteBarrier::GetWriteBarrierType(slot, params, callback); + } + + /** + * Gets the required write barrier type for a specific write. + * This version is meant to be used in conjunction with with a marking write + * barrier barrier which doesn't consider the slot. + * + * \param value The pointer to the object. May be an interior pointer to an + * interface of the actual object. + * \param params Parameters that may be used for actual write barrier calls. + * Only filled if return value indicates that a write barrier is needed. The + * contents of the `params` are an implementation detail. + * \returns whether a write barrier is needed and which barrier to invoke. + */ + static V8_INLINE WriteBarrierType + GetWriteBarrierType(const void* value, WriteBarrierParams& params) { + return internal::WriteBarrier::GetWriteBarrierType(value, params); + } + + /** + * Conservative Dijkstra-style write barrier that processes an object if it + * has not yet been processed. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param object The pointer to the object. May be an interior pointer to + * an interface of the actual object. + */ + static V8_INLINE void DijkstraWriteBarrier(const WriteBarrierParams& params, + const void* object) { + internal::WriteBarrier::DijkstraMarkingBarrier(params, object); + } + + /** + * Conservative Dijkstra-style write barrier that processes a range of + * elements if they have not yet been processed. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param first_element Pointer to the first element that should be processed. + * The slot itself must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + * \param element_size Size of the element in bytes. + * \param number_of_elements Number of elements that should be processed, + * starting with `first_element`. + * \param trace_callback The trace callback that should be invoked for each + * element if necessary. + */ + static V8_INLINE void DijkstraWriteBarrierRange( + const WriteBarrierParams& params, const void* first_element, + size_t element_size, size_t number_of_elements, + TraceCallback trace_callback) { + internal::WriteBarrier::DijkstraMarkingBarrierRange( + params, first_element, element_size, number_of_elements, + trace_callback); + } + + /** + * Steele-style write barrier that re-processes an object if it has already + * been processed. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param object The pointer to the object which must point to an object that + * has been allocated using `MakeGarbageCollected()`. Interior pointers are + * not supported. + */ + static V8_INLINE void SteeleWriteBarrier(const WriteBarrierParams& params, + const void* object) { + internal::WriteBarrier::SteeleMarkingBarrier(params, object); + } + + /** + * Generational barrier for maintaining consistency when running with multiple + * generations. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param slot Slot containing the pointer to the object. The slot itself + * must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + */ + static V8_INLINE void GenerationalBarrier(const WriteBarrierParams& params, + const void* slot) { + internal::WriteBarrier::GenerationalBarrier< + internal::WriteBarrier::GenerationalBarrierType::kPreciseSlot>(params, + slot); + } + + /** + * Generational barrier for maintaining consistency when running with multiple + * generations. This version is used when slot contains uncompressed pointer. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param slot Uncompressed slot containing the direct pointer to the object. + * The slot itself must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + */ + static V8_INLINE void GenerationalBarrierForUncompressedSlot( + const WriteBarrierParams& params, const void* uncompressed_slot) { + internal::WriteBarrier::GenerationalBarrier< + internal::WriteBarrier::GenerationalBarrierType:: + kPreciseUncompressedSlot>(params, uncompressed_slot); + } + + /** + * Generational barrier for source object that may contain outgoing pointers + * to objects in young generation. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param inner_pointer Pointer to the source object. + */ + static V8_INLINE void GenerationalBarrierForSourceObject( + const WriteBarrierParams& params, const void* inner_pointer) { + internal::WriteBarrier::GenerationalBarrier< + internal::WriteBarrier::GenerationalBarrierType::kImpreciseSlot>( + params, inner_pointer); + } + + private: + HeapConsistency() = delete; +}; + +/** + * Disallows garbage collection finalizations. Any garbage collection triggers + * result in a crash when in this scope. + * + * Note that the garbage collector already covers paths that can lead to garbage + * collections, so user code does not require checking + * `IsGarbageCollectionAllowed()` before allocations. + */ +class V8_EXPORT V8_NODISCARD DisallowGarbageCollectionScope final { + CPPGC_STACK_ALLOCATED(); + + public: + /** + * \returns whether garbage collections are currently allowed. + */ + static bool IsGarbageCollectionAllowed(HeapHandle& heap_handle); + + /** + * Enters a disallow garbage collection scope. Must be paired with `Leave()`. + * Prefer a scope instance of `DisallowGarbageCollectionScope`. + * + * \param heap_handle The corresponding heap. + */ + static void Enter(HeapHandle& heap_handle); + + /** + * Leaves a disallow garbage collection scope. Must be paired with `Enter()`. + * Prefer a scope instance of `DisallowGarbageCollectionScope`. + * + * \param heap_handle The corresponding heap. + */ + static void Leave(HeapHandle& heap_handle); + + /** + * Constructs a scoped object that automatically enters and leaves a disallow + * garbage collection scope based on its lifetime. + * + * \param heap_handle The corresponding heap. + */ + explicit DisallowGarbageCollectionScope(HeapHandle& heap_handle); + ~DisallowGarbageCollectionScope(); + + DisallowGarbageCollectionScope(const DisallowGarbageCollectionScope&) = + delete; + DisallowGarbageCollectionScope& operator=( + const DisallowGarbageCollectionScope&) = delete; + + private: + HeapHandle& heap_handle_; +}; + +/** + * Avoids invoking garbage collection finalizations. Already running garbage + * collection phase are unaffected by this scope. + * + * Should only be used temporarily as the scope has an impact on memory usage + * and follow up garbage collections. + */ +class V8_EXPORT V8_NODISCARD NoGarbageCollectionScope final { + CPPGC_STACK_ALLOCATED(); + + public: + /** + * Enters a no garbage collection scope. Must be paired with `Leave()`. Prefer + * a scope instance of `NoGarbageCollectionScope`. + * + * \param heap_handle The corresponding heap. + */ + static void Enter(HeapHandle& heap_handle); + + /** + * Leaves a no garbage collection scope. Must be paired with `Enter()`. Prefer + * a scope instance of `NoGarbageCollectionScope`. + * + * \param heap_handle The corresponding heap. + */ + static void Leave(HeapHandle& heap_handle); + + /** + * Constructs a scoped object that automatically enters and leaves a no + * garbage collection scope based on its lifetime. + * + * \param heap_handle The corresponding heap. + */ + explicit NoGarbageCollectionScope(HeapHandle& heap_handle); + ~NoGarbageCollectionScope(); + + NoGarbageCollectionScope(const NoGarbageCollectionScope&) = delete; + NoGarbageCollectionScope& operator=(const NoGarbageCollectionScope&) = delete; + + private: + HeapHandle& heap_handle_; +}; + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_CONSISTENCY_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/heap-handle.h b/NativeScript/napi/android/v8-13/include/cppgc/heap-handle.h new file mode 100644 index 000000000..0d1d21e65 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/heap-handle.h @@ -0,0 +1,48 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_HEAP_HANDLE_H_ +#define INCLUDE_CPPGC_HEAP_HANDLE_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +namespace internal { +class HeapBase; +class WriteBarrierTypeForCagedHeapPolicy; +class WriteBarrierTypeForNonCagedHeapPolicy; +} // namespace internal + +/** + * Opaque handle used for additional heap APIs. + */ +class HeapHandle { + public: + // Deleted copy ctor to avoid treating the type by value. + HeapHandle(const HeapHandle&) = delete; + HeapHandle& operator=(const HeapHandle&) = delete; + + private: + HeapHandle() = default; + + V8_INLINE bool is_incremental_marking_in_progress() const { + return is_incremental_marking_in_progress_; + } + + V8_INLINE bool is_young_generation_enabled() const { + return is_young_generation_enabled_; + } + + bool is_incremental_marking_in_progress_ = false; + bool is_young_generation_enabled_ = false; + + friend class internal::HeapBase; + friend class internal::WriteBarrierTypeForCagedHeapPolicy; + friend class internal::WriteBarrierTypeForNonCagedHeapPolicy; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_HANDLE_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/heap-state.h b/NativeScript/napi/android/v8-13/include/cppgc/heap-state.h new file mode 100644 index 000000000..28212589f --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/heap-state.h @@ -0,0 +1,82 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_HEAP_STATE_H_ +#define INCLUDE_CPPGC_HEAP_STATE_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +class HeapHandle; + +namespace subtle { + +/** + * Helpers to peek into heap-internal state. + */ +class V8_EXPORT HeapState final { + public: + /** + * Returns whether the garbage collector is marking. This API is experimental + * and is expected to be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the garbage collector is currently marking, and false + * otherwise. + */ + static bool IsMarking(const HeapHandle& heap_handle); + + /* + * Returns whether the garbage collector is sweeping. This API is experimental + * and is expected to be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the garbage collector is currently sweeping, and false + * otherwise. + */ + static bool IsSweeping(const HeapHandle& heap_handle); + + /* + * Returns whether the garbage collector is currently sweeping on the thread + * owning this heap. This API allows the caller to determine whether it has + * been called from a destructor of a managed object. This API is experimental + * and may be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the garbage collector is currently sweeping on this + * thread, and false otherwise. + */ + static bool IsSweepingOnOwningThread(const HeapHandle& heap_handle); + + /** + * Returns whether the garbage collector is in the atomic pause, i.e., the + * mutator is stopped from running. This API is experimental and is expected + * to be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the garbage collector is currently in the atomic pause, + * and false otherwise. + */ + static bool IsInAtomicPause(const HeapHandle& heap_handle); + + /** + * Returns whether the last garbage collection was finalized conservatively + * (i.e., with a non-empty stack). This API is experimental and is expected to + * be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the last garbage collection was finalized conservatively, + * and false otherwise. + */ + static bool PreviousGCWasConservative(const HeapHandle& heap_handle); + + private: + HeapState() = delete; +}; + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_STATE_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/heap-statistics.h b/NativeScript/napi/android/v8-13/include/cppgc/heap-statistics.h new file mode 100644 index 000000000..c357f916f --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/heap-statistics.h @@ -0,0 +1,122 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_HEAP_STATISTICS_H_ +#define INCLUDE_CPPGC_HEAP_STATISTICS_H_ + +#include +#include +#include +#include + +namespace cppgc { + +/** + * `HeapStatistics` contains memory consumption and utilization statistics for a + * cppgc heap. + */ +struct HeapStatistics final { + /** + * Specifies the detail level of the heap statistics. Brief statistics contain + * only the top-level allocated and used memory statistics for the entire + * heap. Detailed statistics also contain a break down per space and page, as + * well as freelist statistics and object type histograms. Note that used + * memory reported by brief statistics and detailed statistics might differ + * slightly. + */ + enum DetailLevel : uint8_t { + kBrief, + kDetailed, + }; + + /** + * Object statistics for a single type. + */ + struct ObjectStatsEntry { + /** + * Number of allocated bytes. + */ + size_t allocated_bytes; + /** + * Number of allocated objects. + */ + size_t object_count; + }; + + /** + * Page granularity statistics. For each page the statistics record the + * allocated memory size and overall used memory size for the page. + */ + struct PageStatistics { + /** Overall committed amount of memory for the page. */ + size_t committed_size_bytes = 0; + /** Resident amount of memory held by the page. */ + size_t resident_size_bytes = 0; + /** Amount of memory actually used on the page. */ + size_t used_size_bytes = 0; + /** Statistics for object allocated on the page. Filled only when + * NameProvider::SupportsCppClassNamesAsObjectNames() is true. */ + std::vector object_statistics; + }; + + /** + * Statistics of the freelist (used only in non-large object spaces). For + * each bucket in the freelist the statistics record the bucket size, the + * number of freelist entries in the bucket, and the overall allocated memory + * consumed by these freelist entries. + */ + struct FreeListStatistics { + /** bucket sizes in the freelist. */ + std::vector bucket_size; + /** number of freelist entries per bucket. */ + std::vector free_count; + /** memory size consumed by freelist entries per size. */ + std::vector free_size; + }; + + /** + * Space granularity statistics. For each space the statistics record the + * space name, the amount of allocated memory and overall used memory for the + * space. The statistics also contain statistics for each of the space's + * pages, its freelist and the objects allocated on the space. + */ + struct SpaceStatistics { + /** The space name */ + std::string name; + /** Overall committed amount of memory for the heap. */ + size_t committed_size_bytes = 0; + /** Resident amount of memory held by the heap. */ + size_t resident_size_bytes = 0; + /** Amount of memory actually used on the space. */ + size_t used_size_bytes = 0; + /** Statistics for each of the pages in the space. */ + std::vector page_stats; + /** Statistics for the freelist of the space. */ + FreeListStatistics free_list_stats; + }; + + /** Overall committed amount of memory for the heap. */ + size_t committed_size_bytes = 0; + /** Resident amount of memory held by the heap. */ + size_t resident_size_bytes = 0; + /** Amount of memory actually used on the heap. */ + size_t used_size_bytes = 0; + /** Memory retained in the page pool, not used directly by the heap. */ + size_t pooled_memory_size_bytes = 0; + /** Detail level of this HeapStatistics. */ + DetailLevel detail_level; + + /** Statistics for each of the spaces in the heap. Filled only when + * `detail_level` is `DetailLevel::kDetailed`. */ + std::vector space_stats; + + /** + * Vector of `cppgc::GarbageCollected` type names. + */ + std::vector type_names; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_STATISTICS_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/heap.h b/NativeScript/napi/android/v8-13/include/cppgc/heap.h new file mode 100644 index 000000000..02ee12eab --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/heap.h @@ -0,0 +1,202 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_HEAP_H_ +#define INCLUDE_CPPGC_HEAP_H_ + +#include +#include +#include +#include + +#include "cppgc/common.h" +#include "cppgc/custom-space.h" +#include "cppgc/platform.h" +#include "v8config.h" // NOLINT(build/include_directory) + +/** + * cppgc - A C++ garbage collection library. + */ +namespace cppgc { + +class AllocationHandle; +class HeapHandle; + +/** + * Implementation details of cppgc. Those details are considered internal and + * may change at any point in time without notice. Users should never rely on + * the contents of this namespace. + */ +namespace internal { +class Heap; +} // namespace internal + +class V8_EXPORT Heap { + public: + /** + * Specifies the stack state the embedder is in. + */ + using StackState = EmbedderStackState; + + /** + * Specifies whether conservative stack scanning is supported. + */ + enum class StackSupport : uint8_t { + /** + * Conservative stack scan is supported. + */ + kSupportsConservativeStackScan, + /** + * Conservative stack scan is not supported. Embedders may use this option + * when using custom infrastructure that is unsupported by the library. + */ + kNoConservativeStackScan, + }; + + /** + * Specifies supported marking types. + */ + enum class MarkingType : uint8_t { + /** + * Atomic stop-the-world marking. This option does not require any write + * barriers but is the most intrusive in terms of jank. + */ + kAtomic, + /** + * Incremental marking interleaves marking with the rest of the application + * workload on the same thread. + */ + kIncremental, + /** + * Incremental and concurrent marking. + */ + kIncrementalAndConcurrent + }; + + /** + * Specifies supported sweeping types. + */ + enum class SweepingType : uint8_t { + /** + * Atomic stop-the-world sweeping. All of sweeping is performed at once. + */ + kAtomic, + /** + * Incremental sweeping interleaves sweeping with the rest of the + * application workload on the same thread. + */ + kIncremental, + /** + * Incremental and concurrent sweeping. Sweeping is split and interleaved + * with the rest of the application. + */ + kIncrementalAndConcurrent + }; + + /** + * Constraints for a Heap setup. + */ + struct ResourceConstraints { + /** + * Allows the heap to grow to some initial size in bytes before triggering + * garbage collections. This is useful when it is known that applications + * need a certain minimum heap to run to avoid repeatedly invoking the + * garbage collector when growing the heap. + */ + size_t initial_heap_size_bytes = 0; + }; + + /** + * Options specifying Heap properties (e.g. custom spaces) when initializing a + * heap through `Heap::Create()`. + */ + struct HeapOptions { + /** + * Creates reasonable defaults for instantiating a Heap. + * + * \returns the HeapOptions that can be passed to `Heap::Create()`. + */ + static HeapOptions Default() { return {}; } + + /** + * Custom spaces added to heap are required to have indices forming a + * numbered sequence starting at 0, i.e., their `kSpaceIndex` must + * correspond to the index they reside in the vector. + */ + std::vector> custom_spaces; + + /** + * Specifies whether conservative stack scan is supported. When conservative + * stack scan is not supported, the collector may try to invoke + * garbage collections using non-nestable task, which are guaranteed to have + * no interesting stack, through the provided Platform. If such tasks are + * not supported by the Platform, the embedder must take care of invoking + * the GC through `ForceGarbageCollectionSlow()`. + */ + StackSupport stack_support = StackSupport::kSupportsConservativeStackScan; + + /** + * Specifies which types of marking are supported by the heap. + */ + MarkingType marking_support = MarkingType::kIncrementalAndConcurrent; + + /** + * Specifies which types of sweeping are supported by the heap. + */ + SweepingType sweeping_support = SweepingType::kIncrementalAndConcurrent; + + /** + * Resource constraints specifying various properties that the internal + * GC scheduler follows. + */ + ResourceConstraints resource_constraints; + }; + + /** + * Creates a new heap that can be used for object allocation. + * + * \param platform implemented and provided by the embedder. + * \param options HeapOptions specifying various properties for the Heap. + * \returns a new Heap instance. + */ + static std::unique_ptr Create( + std::shared_ptr platform, + HeapOptions options = HeapOptions::Default()); + + virtual ~Heap() = default; + + /** + * Forces garbage collection. + * + * \param source String specifying the source (or caller) triggering a + * forced garbage collection. + * \param reason String specifying the reason for the forced garbage + * collection. + * \param stack_state The embedder stack state, see StackState. + */ + void ForceGarbageCollectionSlow( + const char* source, const char* reason, + StackState stack_state = StackState::kMayContainHeapPointers); + + /** + * \returns the opaque handle for allocating objects using + * `MakeGarbageCollected()`. + */ + AllocationHandle& GetAllocationHandle(); + + /** + * \returns the opaque heap handle which may be used to refer to this heap in + * other APIs. Valid as long as the underlying `Heap` is alive. + */ + HeapHandle& GetHeapHandle(); + + private: + Heap() = default; + + friend class internal::Heap; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/api-constants.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/api-constants.h new file mode 100644 index 000000000..f3b0d8571 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/api-constants.h @@ -0,0 +1,75 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_ +#define INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_ + +#include +#include + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +// Embedders should not rely on this code! + +// Internal constants to avoid exposing internal types on the API surface. +namespace api_constants { + +constexpr size_t kKB = 1024; +constexpr size_t kMB = kKB * 1024; +constexpr size_t kGB = kMB * 1024; + +// Offset of the uint16_t bitfield from the payload contaning the +// in-construction bit. This is subtracted from the payload pointer to get +// to the right bitfield. +static constexpr size_t kFullyConstructedBitFieldOffsetFromPayload = + 2 * sizeof(uint16_t); +// Mask for in-construction bit. +static constexpr uint16_t kFullyConstructedBitMask = uint16_t{1}; + +static constexpr size_t kPageSizeBits = 17; +static constexpr size_t kPageSize = size_t{1} << kPageSizeBits; + +static constexpr size_t kLargeObjectSizeThreshold = kPageSize / 2; + +#if defined(CPPGC_POINTER_COMPRESSION) +#if defined(CPPGC_ENABLE_LARGER_CAGE) +constexpr unsigned kPointerCompressionShift = 3; +#else // !defined(CPPGC_ENABLE_LARGER_CAGE) +constexpr unsigned kPointerCompressionShift = 1; +#endif // !defined(CPPGC_ENABLE_LARGER_CAGE) +#endif // !defined(CPPGC_POINTER_COMPRESSION) + +#if defined(CPPGC_CAGED_HEAP) +constexpr size_t kCagedHeapDefaultReservationSize = + static_cast(4) * kGB; +#if defined(CPPGC_POINTER_COMPRESSION) +constexpr size_t kCagedHeapMaxReservationSize = + size_t{1} << (31 + kPointerCompressionShift); +#else // !defined(CPPGC_POINTER_COMPRESSION) +constexpr size_t kCagedHeapMaxReservationSize = + kCagedHeapDefaultReservationSize; +#endif // !defined(CPPGC_POINTER_COMPRESSION) +constexpr size_t kCagedHeapReservationAlignment = kCagedHeapMaxReservationSize; +#endif // defined(CPPGC_CAGED_HEAP) + +static constexpr size_t kDefaultAlignment = sizeof(void*); + +// Maximum support alignment for a type as in `alignof(T)`. +static constexpr size_t kMaxSupportedAlignment = 2 * kDefaultAlignment; + +// Granularity of heap allocations. +constexpr size_t kAllocationGranularity = sizeof(void*); + +// Default cacheline size. +constexpr size_t kCachelineSize = 64; + +} // namespace api_constants + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/atomic-entry-flag.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/atomic-entry-flag.h new file mode 100644 index 000000000..5a7d3b8f8 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/atomic-entry-flag.h @@ -0,0 +1,48 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_ +#define INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_ + +#include + +namespace cppgc { +namespace internal { + +// A flag which provides a fast check whether a scope may be entered on the +// current thread, without needing to access thread-local storage or mutex. Can +// have false positives (i.e., spuriously report that it might be entered), so +// it is expected that this will be used in tandem with a precise check that the +// scope is in fact entered on that thread. +// +// Example: +// g_frobnicating_flag.MightBeEntered() && +// ThreadLocalFrobnicator().IsFrobnicating() +// +// Relaxed atomic operations are sufficient, since: +// - all accesses remain atomic +// - each thread must observe its own operations in order +// - no thread ever exits the flag more times than it enters (if used correctly) +// And so if a thread observes zero, it must be because it has observed an equal +// number of exits as entries. +class AtomicEntryFlag final { + public: + void Enter() { entries_.fetch_add(1, std::memory_order_relaxed); } + void Exit() { entries_.fetch_sub(1, std::memory_order_relaxed); } + + // Returns false only if the current thread is not between a call to Enter + // and a call to Exit. Returns true if this thread or another thread may + // currently be in the scope guarded by this flag. + bool MightBeEntered() const { + return entries_.load(std::memory_order_relaxed) != 0; + } + + private: + std::atomic_int entries_{0}; +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/base-page-handle.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/base-page-handle.h new file mode 100644 index 000000000..a5308dc34 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/base-page-handle.h @@ -0,0 +1,43 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_BASE_PAGE_HANDLE_H_ +#define INCLUDE_CPPGC_INTERNAL_BASE_PAGE_HANDLE_H_ + +#include "cppgc/heap-handle.h" +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/logging.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +// The class is needed in the header to allow for fast access to HeapHandle in +// the write barrier. +class BasePageHandle { + public: + static V8_INLINE BasePageHandle* FromPayload(void* payload) { + return reinterpret_cast( + reinterpret_cast(payload) & ~(api_constants::kPageSize - 1)); + } + static V8_INLINE const BasePageHandle* FromPayload(const void* payload) { + return FromPayload(const_cast(payload)); + } + + HeapHandle& heap_handle() { return heap_handle_; } + const HeapHandle& heap_handle() const { return heap_handle_; } + + protected: + explicit BasePageHandle(HeapHandle& heap_handle) : heap_handle_(heap_handle) { + CPPGC_DCHECK(reinterpret_cast(this) % api_constants::kPageSize == + 0); + } + + HeapHandle& heap_handle_; +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_BASE_PAGE_HANDLE_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/caged-heap-local-data.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/caged-heap-local-data.h new file mode 100644 index 000000000..ad717d744 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/caged-heap-local-data.h @@ -0,0 +1,117 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_ +#define INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_ + +#include +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/caged-heap.h" +#include "cppgc/internal/logging.h" +#include "cppgc/platform.h" +#include "v8config.h" // NOLINT(build/include_directory) + +#if __cpp_lib_bitopts +#include +#endif // __cpp_lib_bitopts + +#if defined(CPPGC_CAGED_HEAP) + +namespace cppgc { +namespace internal { + +class HeapBase; +class HeapBaseHandle; + +#if defined(CPPGC_YOUNG_GENERATION) + +// AgeTable is the bytemap needed for the fast generation check in the write +// barrier. AgeTable contains entries that correspond to 4096 bytes memory +// regions (cards). Each entry in the table represents generation of the objects +// that reside on the corresponding card (young, old or mixed). +class V8_EXPORT AgeTable final { + static constexpr size_t kRequiredSize = 1 * api_constants::kMB; + static constexpr size_t kAllocationGranularity = + api_constants::kAllocationGranularity; + + public: + // Represents age of the objects living on a single card. + enum class Age : uint8_t { kOld, kYoung, kMixed }; + // When setting age for a range, consider or ignore ages of the adjacent + // cards. + enum class AdjacentCardsPolicy : uint8_t { kConsider, kIgnore }; + + static constexpr size_t kCardSizeInBytes = + api_constants::kCagedHeapDefaultReservationSize / kRequiredSize; + + static constexpr size_t CalculateAgeTableSizeForHeapSize(size_t heap_size) { + return heap_size / kCardSizeInBytes; + } + + void SetAge(uintptr_t cage_offset, Age age) { + table_[card(cage_offset)] = age; + } + + V8_INLINE Age GetAge(uintptr_t cage_offset) const { + return table_[card(cage_offset)]; + } + + void SetAgeForRange(uintptr_t cage_offset_begin, uintptr_t cage_offset_end, + Age age, AdjacentCardsPolicy adjacent_cards_policy); + + Age GetAgeForRange(uintptr_t cage_offset_begin, + uintptr_t cage_offset_end) const; + + void ResetForTesting(); + + private: + V8_INLINE size_t card(uintptr_t offset) const { + constexpr size_t kGranularityBits = +#if __cpp_lib_bitopts + std::countr_zero(static_cast(kCardSizeInBytes)); +#elif V8_HAS_BUILTIN_CTZ + __builtin_ctz(static_cast(kCardSizeInBytes)); +#else //! V8_HAS_BUILTIN_CTZ + // Hardcode and check with assert. + 12; +#endif // !V8_HAS_BUILTIN_CTZ + static_assert((1 << kGranularityBits) == kCardSizeInBytes); + const size_t entry = offset >> kGranularityBits; + CPPGC_DCHECK(CagedHeapBase::GetAgeTableSize() > entry); + return entry; + } + +#if defined(V8_CC_GNU) + // gcc disallows flexible arrays in otherwise empty classes. + Age table_[0]; +#else // !defined(V8_CC_GNU) + Age table_[]; +#endif // !defined(V8_CC_GNU) +}; + +#endif // CPPGC_YOUNG_GENERATION + +struct CagedHeapLocalData final { + V8_INLINE static CagedHeapLocalData& Get() { + return *reinterpret_cast(CagedHeapBase::GetBase()); + } + + static constexpr size_t CalculateLocalDataSizeForHeapSize(size_t heap_size) { + return AgeTable::CalculateAgeTableSizeForHeapSize(heap_size); + } + +#if defined(CPPGC_YOUNG_GENERATION) + AgeTable age_table; +#endif +}; + +} // namespace internal +} // namespace cppgc + +#endif // defined(CPPGC_CAGED_HEAP) + +#endif // INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/caged-heap.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/caged-heap.h new file mode 100644 index 000000000..46fb529f6 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/caged-heap.h @@ -0,0 +1,64 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_H_ +#define INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_H_ + +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/base-page-handle.h" +#include "v8config.h" // NOLINT(build/include_directory) + +#if defined(CPPGC_CAGED_HEAP) + +namespace cppgc { +namespace internal { + +class V8_EXPORT CagedHeapBase { + public: + V8_INLINE static uintptr_t OffsetFromAddress(const void* address) { + return reinterpret_cast(address) & + (api_constants::kCagedHeapReservationAlignment - 1); + } + + V8_INLINE static bool IsWithinCage(const void* address) { + CPPGC_DCHECK(g_heap_base_); + return (reinterpret_cast(address) & + ~(api_constants::kCagedHeapReservationAlignment - 1)) == + g_heap_base_; + } + + V8_INLINE static bool AreWithinCage(const void* addr1, const void* addr2) { +#if defined(CPPGC_POINTER_COMPRESSION) + static constexpr size_t kHeapBaseShift = + 31 + api_constants::kPointerCompressionShift; +#else // !defined(CPPGC_POINTER_COMPRESSION) + static constexpr size_t kHeapBaseShift = sizeof(uint32_t) * CHAR_BIT; +#endif // !defined(CPPGC_POINTER_COMPRESSION) + static_assert((static_cast(1) << kHeapBaseShift) == + api_constants::kCagedHeapMaxReservationSize); + CPPGC_DCHECK(g_heap_base_); + return !(((reinterpret_cast(addr1) ^ g_heap_base_) | + (reinterpret_cast(addr2) ^ g_heap_base_)) >> + kHeapBaseShift); + } + + V8_INLINE static uintptr_t GetBase() { return g_heap_base_; } + V8_INLINE static size_t GetAgeTableSize() { return g_age_table_size_; } + + private: + friend class CagedHeap; + + static uintptr_t g_heap_base_; + static size_t g_age_table_size_; +}; + +} // namespace internal +} // namespace cppgc + +#endif // defined(CPPGC_CAGED_HEAP) + +#endif // INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/compiler-specific.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/compiler-specific.h new file mode 100644 index 000000000..175156ca6 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/compiler-specific.h @@ -0,0 +1,46 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_ +#define INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +#if defined(__has_attribute) +#define CPPGC_HAS_ATTRIBUTE(FEATURE) __has_attribute(FEATURE) +#else +#define CPPGC_HAS_ATTRIBUTE(FEATURE) 0 +#endif + +#if defined(__has_cpp_attribute) +#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) __has_cpp_attribute(FEATURE) +#else +#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) 0 +#endif + +// [[no_unique_address]] comes in C++20 but supported in clang with -std >= +// c++11. +#if defined(V8_CC_MSVC) && CPPGC_HAS_CPP_ATTRIBUTE(msvc::no_unique_address) +// Unfortunately MSVC ignores [[no_unique_address]] (see +// https://devblogs.microsoft.com/cppblog/msvc-cpp20-and-the-std-cpp20-switch/#msvc-extensions-and-abi), +// and clang-cl matches it for ABI compatibility reasons. We need to prefer +// [[msvc::no_unique_address]] when available if we actually want any effect. +#define CPPGC_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#elif CPPGC_HAS_CPP_ATTRIBUTE(no_unique_address) +#define CPPGC_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else +#define CPPGC_NO_UNIQUE_ADDRESS +#endif + +#if CPPGC_HAS_ATTRIBUTE(unused) +#define CPPGC_UNUSED __attribute__((unused)) +#else +#define CPPGC_UNUSED +#endif + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/conditional-stack-allocated.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/conditional-stack-allocated.h new file mode 100644 index 000000000..21c89378f --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/conditional-stack-allocated.h @@ -0,0 +1,41 @@ +// Copyright 2025 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_CONDITIONAL_STACK_ALLOCATED_H_ +#define INCLUDE_CPPGC_INTERNAL_CONDITIONAL_STACK_ALLOCATED_H_ + +#include + +#include "cppgc/macros.h" // NOLINT(build/include_directory) +#include "cppgc/type-traits.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +// Base class that is marked as stack allocated if T is either marked as stack +// allocated or a traceable type. +template +class ConditionalStackAllocatedBase; + +template +concept RequiresStackAllocated = + !std::is_void_v && + (cppgc::IsStackAllocatedType || cppgc::internal::IsTraceableV || + cppgc::IsGarbageCollectedOrMixinTypeV); + +template + requires(RequiresStackAllocated) +class ConditionalStackAllocatedBase { + public: + CPPGC_STACK_ALLOCATED(); +}; + +template + requires(!RequiresStackAllocated) +class ConditionalStackAllocatedBase {}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_CONDITIONAL_STACK_ALLOCATED_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/finalizer-trait.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/finalizer-trait.h new file mode 100644 index 000000000..b62da059f --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/finalizer-trait.h @@ -0,0 +1,93 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_ +#define INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_ + +#include + +#include "cppgc/type-traits.h" + +namespace cppgc { +namespace internal { + +using FinalizationCallback = void (*)(void*); + +template +struct HasFinalizeGarbageCollectedObject : std::false_type {}; + +template +struct HasFinalizeGarbageCollectedObject< + T, + std::void_t().FinalizeGarbageCollectedObject())>> + : std::true_type {}; + +// The FinalizerTraitImpl specifies how to finalize objects. +template +struct FinalizerTraitImpl; + +template +struct FinalizerTraitImpl { + private: + // Dispatch to custom FinalizeGarbageCollectedObject(). + struct Custom { + static void Call(void* obj) { + static_cast(obj)->FinalizeGarbageCollectedObject(); + } + }; + + // Dispatch to regular destructor. + struct Destructor { + static void Call(void* obj) { static_cast(obj)->~T(); } + }; + + using FinalizeImpl = + std::conditional_t::value, Custom, + Destructor>; + + public: + static void Finalize(void* obj) { + static_assert(sizeof(T), "T must be fully defined"); + FinalizeImpl::Call(obj); + } +}; + +template +struct FinalizerTraitImpl { + static void Finalize(void* obj) { + static_assert(sizeof(T), "T must be fully defined"); + } +}; + +// The FinalizerTrait is used to determine if a type requires finalization and +// what finalization means. +template +struct FinalizerTrait { + private: + // Object has a finalizer if it has + // - a custom FinalizeGarbageCollectedObject method, or + // - a destructor. + static constexpr bool kNonTrivialFinalizer = + internal::HasFinalizeGarbageCollectedObject::value || + !std::is_trivially_destructible_v>; + + static void Finalize(void* obj) { + internal::FinalizerTraitImpl::Finalize(obj); + } + + public: + static constexpr bool HasFinalizer() { return kNonTrivialFinalizer; } + + // The callback used to finalize an object of type T. + static constexpr FinalizationCallback kCallback = + kNonTrivialFinalizer ? Finalize : nullptr; +}; + +template +constexpr FinalizationCallback FinalizerTrait::kCallback; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/gc-info.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/gc-info.h new file mode 100644 index 000000000..b2fbd1fee --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/gc-info.h @@ -0,0 +1,150 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_GC_INFO_H_ +#define INCLUDE_CPPGC_INTERNAL_GC_INFO_H_ + +#include +#include +#include + +#include "cppgc/internal/finalizer-trait.h" +#include "cppgc/internal/logging.h" +#include "cppgc/internal/name-trait.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +using GCInfoIndex = uint16_t; + +struct V8_EXPORT EnsureGCInfoIndexTrait final { + // Acquires a new GC info object and updates `registered_index` with the index + // that identifies that new info accordingly. + template + V8_INLINE static GCInfoIndex EnsureIndex( + std::atomic& registered_index) { + return EnsureGCInfoIndexTraitDispatch{}(registered_index); + } + + private: + template ::HasFinalizer(), + bool = NameTrait::HasNonHiddenName()> + struct EnsureGCInfoIndexTraitDispatch; + + static GCInfoIndex V8_PRESERVE_MOST + EnsureGCInfoIndex(std::atomic&, TraceCallback, + FinalizationCallback, NameCallback); + static GCInfoIndex V8_PRESERVE_MOST EnsureGCInfoIndex( + std::atomic&, TraceCallback, FinalizationCallback); + static GCInfoIndex V8_PRESERVE_MOST + EnsureGCInfoIndex(std::atomic&, TraceCallback, NameCallback); + static GCInfoIndex V8_PRESERVE_MOST + EnsureGCInfoIndex(std::atomic&, TraceCallback); +}; + +#define DISPATCH(has_finalizer, has_non_hidden_name, function) \ + template \ + struct EnsureGCInfoIndexTrait::EnsureGCInfoIndexTraitDispatch< \ + T, has_finalizer, has_non_hidden_name> { \ + V8_INLINE GCInfoIndex \ + operator()(std::atomic& registered_index) { \ + return function; \ + } \ + }; + +// ------------------------------------------------------- // +// DISPATCH(has_finalizer, has_non_hidden_name, function) // +// ------------------------------------------------------- // +DISPATCH(true, true, // + EnsureGCInfoIndex(registered_index, // + TraceTrait::Trace, // + FinalizerTrait::kCallback, // + NameTrait::GetName)) // +DISPATCH(true, false, // + EnsureGCInfoIndex(registered_index, // + TraceTrait::Trace, // + FinalizerTrait::kCallback)) // +DISPATCH(false, true, // + EnsureGCInfoIndex(registered_index, // + TraceTrait::Trace, // + NameTrait::GetName)) // +DISPATCH(false, false, // + EnsureGCInfoIndex(registered_index, // + TraceTrait::Trace)) // + +#undef DISPATCH + +// Trait determines how the garbage collector treats objects wrt. to traversing, +// finalization, and naming. +template +struct GCInfoTrait final { + V8_INLINE static GCInfoIndex Index() { + static_assert(sizeof(T), "T must be fully defined"); + static std::atomic + registered_index; // Uses zero initialization. + GCInfoIndex index = registered_index.load(std::memory_order_acquire); + if (V8_UNLIKELY(!index)) { + index = EnsureGCInfoIndexTrait::EnsureIndex(registered_index); + CPPGC_DCHECK(index != 0); + CPPGC_DCHECK(index == registered_index.load(std::memory_order_acquire)); + } + return index; + } + + static constexpr void CheckCallbacksAreDefined() { + // No USE() macro available. + (void)static_cast(TraceTrait::Trace); + (void)static_cast(FinalizerTrait::kCallback); + (void)static_cast(NameTrait::GetName); + } +}; + +// Fold types based on finalizer behavior. Note that finalizer characteristics +// align with trace behavior, i.e., destructors are virtual when trace methods +// are and vice versa. +template +struct GCInfoFolding final { + static constexpr bool kHasVirtualDestructorAtBase = + std::has_virtual_destructor_v; + static constexpr bool kBothTypesAreTriviallyDestructible = + std::is_trivially_destructible_v && + std::is_trivially_destructible_v; + static constexpr bool kHasCustomFinalizerDispatchAtBase = + internal::HasFinalizeGarbageCollectedObject< + ParentMostGarbageCollectedType>::value; +#ifdef CPPGC_SUPPORTS_OBJECT_NAMES + static constexpr bool kWantsDetailedObjectNames = true; +#else // !CPPGC_SUPPORTS_OBJECT_NAMES + static constexpr bool kWantsDetailedObjectNames = false; +#endif // !CPPGC_SUPPORTS_OBJECT_NAMES + + // Always true. Forces the compiler to resolve callbacks which ensures that + // both modes don't break without requiring compiling a separate + // configuration. Only a single GCInfo (for `ResultType` below) will actually + // be instantiated but existence (and well-formedness) of all callbacks is + // checked. + static constexpr bool WantToFold() { + if constexpr ((kHasVirtualDestructorAtBase || + kBothTypesAreTriviallyDestructible || + kHasCustomFinalizerDispatchAtBase) && + !kWantsDetailedObjectNames) { + GCInfoTrait::CheckCallbacksAreDefined(); + GCInfoTrait::CheckCallbacksAreDefined(); + return true; + } + return false; + } + + // Folding would regress name resolution when deriving names from C++ + // class names as it would just folds a name to the base class name. + using ResultType = + std::conditional_t; +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_GC_INFO_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/logging.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/logging.h new file mode 100644 index 000000000..2129e85f2 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/logging.h @@ -0,0 +1,50 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_LOGGING_H_ +#define INCLUDE_CPPGC_INTERNAL_LOGGING_H_ + +#include "cppgc/source-location.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +void V8_EXPORT DCheckImpl(const char*, + const SourceLocation& = SourceLocation::Current()); +[[noreturn]] void V8_EXPORT +FatalImpl(const char*, const SourceLocation& = SourceLocation::Current()); + +// Used to ignore -Wunused-variable. +template +struct EatParams {}; + +#ifdef CPPGC_ENABLE_API_CHECKS +#define CPPGC_DCHECK_MSG(condition, message) \ + do { \ + if (V8_UNLIKELY(!(condition))) { \ + ::cppgc::internal::DCheckImpl(message); \ + } \ + } while (false) +#else // !CPPGC_ENABLE_API_CHECKS +#define CPPGC_DCHECK_MSG(condition, message) \ + (static_cast(::cppgc::internal::EatParams(condition), message)>{})) +#endif // !CPPGC_ENABLE_API_CHECKS + +#define CPPGC_DCHECK(condition) CPPGC_DCHECK_MSG(condition, #condition) + +#define CPPGC_CHECK_MSG(condition, message) \ + do { \ + if (V8_UNLIKELY(!(condition))) { \ + ::cppgc::internal::FatalImpl(message); \ + } \ + } while (false) + +#define CPPGC_CHECK(condition) CPPGC_CHECK_MSG(condition, #condition) + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_LOGGING_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/member-storage.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/member-storage.h new file mode 100644 index 000000000..f862740db --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/member-storage.h @@ -0,0 +1,305 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_ +#define INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_ + +#include +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/caged-heap.h" +#include "cppgc/internal/logging.h" +#include "cppgc/sentinel-pointer.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +enum class WriteBarrierSlotType { + kCompressed, + kUncompressed, +}; + +#if defined(CPPGC_POINTER_COMPRESSION) + +#if defined(__clang__) +// Attribute const allows the compiler to assume that CageBaseGlobal::g_base_ +// doesn't change (e.g. across calls) and thereby avoid redundant loads. +#define CPPGC_CONST __attribute__((const)) +#define CPPGC_REQUIRE_CONSTANT_INIT \ + __attribute__((require_constant_initialization)) +#else // defined(__clang__) +#define CPPGC_CONST +#define CPPGC_REQUIRE_CONSTANT_INIT +#endif // defined(__clang__) + +class V8_EXPORT CageBaseGlobal final { + public: + V8_INLINE CPPGC_CONST static uintptr_t Get() { + CPPGC_DCHECK(IsBaseConsistent()); + return g_base_.base; + } + + V8_INLINE CPPGC_CONST static bool IsSet() { + CPPGC_DCHECK(IsBaseConsistent()); + return (g_base_.base & ~kLowerHalfWordMask) != 0; + } + + private: + // We keep the lower halfword as ones to speed up decompression. + static constexpr uintptr_t kLowerHalfWordMask = + (api_constants::kCagedHeapReservationAlignment - 1); + + static union alignas(api_constants::kCachelineSize) Base { + uintptr_t base; + char cache_line[api_constants::kCachelineSize]; + } g_base_ CPPGC_REQUIRE_CONSTANT_INIT; + + CageBaseGlobal() = delete; + + V8_INLINE static bool IsBaseConsistent() { + return kLowerHalfWordMask == (g_base_.base & kLowerHalfWordMask); + } + + friend class CageBaseGlobalUpdater; +}; + +#undef CPPGC_REQUIRE_CONSTANT_INIT +#undef CPPGC_CONST + +class V8_TRIVIAL_ABI CompressedPointer final { + public: + struct AtomicInitializerTag {}; + + using IntegralType = uint32_t; + static constexpr auto kWriteBarrierSlotType = + WriteBarrierSlotType::kCompressed; + + V8_INLINE CompressedPointer() : value_(0u) {} + V8_INLINE explicit CompressedPointer(const void* value, + AtomicInitializerTag) { + StoreAtomic(value); + } + V8_INLINE explicit CompressedPointer(const void* ptr) + : value_(Compress(ptr)) {} + V8_INLINE explicit CompressedPointer(std::nullptr_t) : value_(0u) {} + V8_INLINE explicit CompressedPointer(SentinelPointer) + : value_(kCompressedSentinel) {} + + V8_INLINE const void* Load() const { return Decompress(value_); } + V8_INLINE const void* LoadAtomic() const { + return Decompress( + reinterpret_cast&>(value_).load( + std::memory_order_relaxed)); + } + + V8_INLINE void Store(const void* ptr) { value_ = Compress(ptr); } + V8_INLINE void StoreAtomic(const void* value) { + reinterpret_cast&>(value_).store( + Compress(value), std::memory_order_relaxed); + } + + V8_INLINE void Clear() { value_ = 0u; } + V8_INLINE bool IsCleared() const { return !value_; } + + V8_INLINE bool IsSentinel() const { return value_ == kCompressedSentinel; } + + V8_INLINE uint32_t GetAsInteger() const { return value_; } + + V8_INLINE friend bool operator==(CompressedPointer a, CompressedPointer b) { + return a.value_ == b.value_; + } + V8_INLINE friend bool operator!=(CompressedPointer a, CompressedPointer b) { + return a.value_ != b.value_; + } + V8_INLINE friend bool operator<(CompressedPointer a, CompressedPointer b) { + return a.value_ < b.value_; + } + V8_INLINE friend bool operator<=(CompressedPointer a, CompressedPointer b) { + return a.value_ <= b.value_; + } + V8_INLINE friend bool operator>(CompressedPointer a, CompressedPointer b) { + return a.value_ > b.value_; + } + V8_INLINE friend bool operator>=(CompressedPointer a, CompressedPointer b) { + return a.value_ >= b.value_; + } + + static V8_INLINE IntegralType Compress(const void* ptr) { + static_assert(SentinelPointer::kSentinelValue == + 1 << api_constants::kPointerCompressionShift, + "The compression scheme relies on the sentinel encoded as 1 " + "<< kPointerCompressionShift"); + static constexpr size_t kGigaCageMask = + ~(api_constants::kCagedHeapReservationAlignment - 1); + static constexpr size_t kPointerCompressionShiftMask = + (1 << api_constants::kPointerCompressionShift) - 1; + + CPPGC_DCHECK(CageBaseGlobal::IsSet()); + const uintptr_t base = CageBaseGlobal::Get(); + CPPGC_DCHECK(!ptr || ptr == kSentinelPointer || + (base & kGigaCageMask) == + (reinterpret_cast(ptr) & kGigaCageMask)); + CPPGC_DCHECK( + (reinterpret_cast(ptr) & kPointerCompressionShiftMask) == 0); + + const auto uptr = reinterpret_cast(ptr); + // Shift the pointer and truncate. + auto compressed = static_cast( + uptr >> api_constants::kPointerCompressionShift); + // Normal compressed pointers must have the MSB set. This is guaranteed by + // the cage alignment. + CPPGC_DCHECK((!compressed || compressed == kCompressedSentinel) || + (compressed & (1 << 31))); + return compressed; + } + + static V8_INLINE void* Decompress(IntegralType ptr) { + CPPGC_DCHECK(CageBaseGlobal::IsSet()); + const uintptr_t base = CageBaseGlobal::Get(); + return Decompress(ptr, base); + } + + static V8_INLINE void* Decompress(IntegralType ptr, uintptr_t base) { + CPPGC_DCHECK(CageBaseGlobal::IsSet()); + CPPGC_DCHECK(base == CageBaseGlobal::Get()); + // Sign-extend compressed pointer to full width. This ensure that normal + // pointers have only 1s in the base part of the address. It's also + // important to shift the unsigned value, as otherwise it would result in + // undefined behavior. + const uint64_t mask = static_cast(static_cast(ptr)) + << api_constants::kPointerCompressionShift; + // Set the base part of the address for normal compressed pointers. Note + // that nullptr and the sentinel value do not have 1s in the base part and + // remain as-is in this operation. + return reinterpret_cast(mask & base); + } + + // For a given memory `address`, this method iterates all possible pointers + // that can be reasonably recovered with the current compression scheme and + // passes them to `callback`. + template + static V8_INLINE void VisitPossiblePointers(const void* address, + Callback callback); + + private: + static constexpr IntegralType kCompressedSentinel = + SentinelPointer::kSentinelValue >> + api_constants::kPointerCompressionShift; + // All constructors initialize `value_`. Do not add a default value here as it + // results in a non-atomic write on some builds, even when the atomic version + // of the constructor is used. + IntegralType value_; +}; + +template +// static +void CompressedPointer::VisitPossiblePointers(const void* address, + Callback callback) { + const uintptr_t base = CageBaseGlobal::Get(); + CPPGC_DCHECK(base); + // We may have random compressed pointers on stack (e.g. due to inlined + // collections). These could be present in both halfwords. + const uint32_t compressed_low = + static_cast(reinterpret_cast(address)); + callback(CompressedPointer::Decompress(compressed_low, base)); + const uint32_t compressed_high = static_cast( + reinterpret_cast(address) >> (sizeof(uint32_t) * CHAR_BIT)); + callback(CompressedPointer::Decompress(compressed_high, base)); + // Iterate possible intermediate values, see `Decompress()`. The intermediate + // value of decompressing is a 64-bit value where 35 bits are the offset. We + // don't assume sign extension is stored and recover that part. + // + // Note that this case conveniently also recovers the full pointer. + static constexpr uintptr_t kBitForIntermediateValue = + (sizeof(uint32_t) * CHAR_BIT) + api_constants::kPointerCompressionShift; + static constexpr uintptr_t kSignExtensionMask = + ~((uintptr_t{1} << kBitForIntermediateValue) - 1); + const uintptr_t intermediate_sign_extended = + reinterpret_cast(address) | kSignExtensionMask; + callback(reinterpret_cast(intermediate_sign_extended & base)); +} + +#endif // defined(CPPGC_POINTER_COMPRESSION) + +class V8_TRIVIAL_ABI RawPointer final { + public: + struct AtomicInitializerTag {}; + + using IntegralType = uintptr_t; + static constexpr auto kWriteBarrierSlotType = + WriteBarrierSlotType::kUncompressed; + + V8_INLINE RawPointer() : ptr_(nullptr) {} + V8_INLINE explicit RawPointer(const void* ptr, AtomicInitializerTag) { + StoreAtomic(ptr); + } + V8_INLINE explicit RawPointer(const void* ptr) : ptr_(ptr) {} + + V8_INLINE const void* Load() const { return ptr_; } + V8_INLINE const void* LoadAtomic() const { + return reinterpret_cast&>(ptr_).load( + std::memory_order_relaxed); + } + + V8_INLINE void Store(const void* ptr) { ptr_ = ptr; } + V8_INLINE void StoreAtomic(const void* ptr) { + reinterpret_cast&>(ptr_).store( + ptr, std::memory_order_relaxed); + } + + V8_INLINE void Clear() { ptr_ = nullptr; } + V8_INLINE bool IsCleared() const { return !ptr_; } + + V8_INLINE bool IsSentinel() const { return ptr_ == kSentinelPointer; } + + V8_INLINE uintptr_t GetAsInteger() const { + return reinterpret_cast(ptr_); + } + + V8_INLINE friend bool operator==(RawPointer a, RawPointer b) { + return a.ptr_ == b.ptr_; + } + V8_INLINE friend bool operator!=(RawPointer a, RawPointer b) { + return a.ptr_ != b.ptr_; + } + V8_INLINE friend bool operator<(RawPointer a, RawPointer b) { + return a.ptr_ < b.ptr_; + } + V8_INLINE friend bool operator<=(RawPointer a, RawPointer b) { + return a.ptr_ <= b.ptr_; + } + V8_INLINE friend bool operator>(RawPointer a, RawPointer b) { + return a.ptr_ > b.ptr_; + } + V8_INLINE friend bool operator>=(RawPointer a, RawPointer b) { + return a.ptr_ >= b.ptr_; + } + + template + static V8_INLINE void VisitPossiblePointers(const void* address, + Callback callback) { + // Pass along the full pointer. + return callback(const_cast(address)); + } + + private: + // All constructors initialize `ptr_`. Do not add a default value here as it + // results in a non-atomic write on some builds, even when the atomic version + // of the constructor is used. + const void* ptr_; +}; + +#if defined(CPPGC_POINTER_COMPRESSION) +using DefaultMemberStorage = CompressedPointer; +#else // !defined(CPPGC_POINTER_COMPRESSION) +using DefaultMemberStorage = RawPointer; +#endif // !defined(CPPGC_POINTER_COMPRESSION) + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/name-trait.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/name-trait.h new file mode 100644 index 000000000..6e63a7282 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/name-trait.h @@ -0,0 +1,141 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_ +#define INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_ + +#include +#include +#include + +#include "cppgc/name-provider.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +#if CPPGC_SUPPORTS_OBJECT_NAMES && defined(__clang__) +#define CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME 1 + +// Provides constexpr c-string storage for a name of fixed |Size| characters. +// Automatically appends terminating 0 byte. +template +struct NameBuffer { + char name[Size + 1]{}; + + static constexpr NameBuffer FromCString(const char* str) { + NameBuffer result; + for (size_t i = 0; i < Size; ++i) result.name[i] = str[i]; + result.name[Size] = 0; + return result; + } +}; + +template +const char* GetTypename() { + static constexpr char kSelfPrefix[] = + "const char *cppgc::internal::GetTypename() [T ="; + static_assert(__builtin_strncmp(__PRETTY_FUNCTION__, kSelfPrefix, + sizeof(kSelfPrefix) - 1) == 0, + "The prefix must match"); + static constexpr const char* kTypenameStart = + __PRETTY_FUNCTION__ + sizeof(kSelfPrefix); + static constexpr size_t kTypenameSize = + __builtin_strlen(__PRETTY_FUNCTION__) - sizeof(kSelfPrefix) - 1; + // NameBuffer is an indirection that is needed to make sure that only a + // substring of __PRETTY_FUNCTION__ gets materialized in the binary. + static constexpr auto buffer = + NameBuffer::FromCString(kTypenameStart); + return buffer.name; +} + +#else +#define CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME 0 +#endif + +struct HeapObjectName { + const char* value; + bool name_was_hidden; +}; + +enum class HeapObjectNameForUnnamedObject : uint8_t { + kUseClassNameIfSupported, + kUseHiddenName, +}; + +class V8_EXPORT NameTraitBase { + protected: + static HeapObjectName GetNameFromTypeSignature(const char*); +}; + +// Trait that specifies how the garbage collector retrieves the name for a +// given object. +template +class NameTrait final : public NameTraitBase { + public: + static constexpr bool HasNonHiddenName() { +#if CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME + return true; +#elif CPPGC_SUPPORTS_OBJECT_NAMES + return true; +#else // !CPPGC_SUPPORTS_OBJECT_NAMES + return std::is_base_of_v; +#endif // !CPPGC_SUPPORTS_OBJECT_NAMES + } + + static HeapObjectName GetName( + const void* obj, HeapObjectNameForUnnamedObject name_retrieval_mode) { + return GetNameFor(static_cast(obj), name_retrieval_mode); + } + + private: + static HeapObjectName GetNameFor(const NameProvider* name_provider, + HeapObjectNameForUnnamedObject) { + // Objects inheriting from `NameProvider` are not considered unnamed as + // users already provided a name for them. + return {name_provider->GetHumanReadableName(), false}; + } + + static HeapObjectName GetNameFor( + const void*, HeapObjectNameForUnnamedObject name_retrieval_mode) { + if (name_retrieval_mode == HeapObjectNameForUnnamedObject::kUseHiddenName) + return {NameProvider::kHiddenName, true}; + +#if CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME + return {GetTypename(), false}; +#elif CPPGC_SUPPORTS_OBJECT_NAMES + +#if defined(V8_CC_GNU) +#define PRETTY_FUNCTION_VALUE __PRETTY_FUNCTION__ +#elif defined(V8_CC_MSVC) +#define PRETTY_FUNCTION_VALUE __FUNCSIG__ +#else +#define PRETTY_FUNCTION_VALUE nullptr +#endif + + static const HeapObjectName leaky_name = + GetNameFromTypeSignature(PRETTY_FUNCTION_VALUE); + return leaky_name; + +#undef PRETTY_FUNCTION_VALUE + +#else // !CPPGC_SUPPORTS_OBJECT_NAMES + // We wanted to use a class name but were unable to provide one due to + // compiler limitations or build configuration. As such, return the hidden + // name with name_was_hidden=false, which will cause this object to be + // visible in the snapshot. + return {NameProvider::kHiddenName, false}; +#endif // !CPPGC_SUPPORTS_OBJECT_NAMES + } +}; + +using NameCallback = HeapObjectName (*)(const void*, + HeapObjectNameForUnnamedObject); + +} // namespace internal +} // namespace cppgc + +#undef CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME + +#endif // INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/persistent-node.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/persistent-node.h new file mode 100644 index 000000000..413ddd918 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/persistent-node.h @@ -0,0 +1,219 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_ +#define INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_ + +#include +#include +#include + +#include "cppgc/internal/logging.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +class CrossThreadPersistentRegion; +class FatalOutOfMemoryHandler; +class HeapBase; +class RootVisitor; + +// PersistentNode represents a variant of two states: +// 1) traceable node with a back pointer to the Persistent object; +// 2) freelist entry. +class PersistentNode final { + public: + PersistentNode() = default; + + PersistentNode(const PersistentNode&) = delete; + PersistentNode& operator=(const PersistentNode&) = delete; + + void InitializeAsUsedNode(void* owner, TraceRootCallback trace) { + CPPGC_DCHECK(trace); + owner_ = owner; + trace_ = trace; + } + + void InitializeAsFreeNode(PersistentNode* next) { + next_ = next; + trace_ = nullptr; + } + + void UpdateOwner(void* owner) { + CPPGC_DCHECK(IsUsed()); + owner_ = owner; + } + + PersistentNode* FreeListNext() const { + CPPGC_DCHECK(!IsUsed()); + return next_; + } + + void Trace(RootVisitor& root_visitor) const { + CPPGC_DCHECK(IsUsed()); + trace_(root_visitor, owner_); + } + + bool IsUsed() const { return trace_; } + + void* owner() const { + CPPGC_DCHECK(IsUsed()); + return owner_; + } + + private: + // PersistentNode acts as a designated union: + // If trace_ != nullptr, owner_ points to the corresponding Persistent handle. + // Otherwise, next_ points to the next freed PersistentNode. + union { + void* owner_ = nullptr; + PersistentNode* next_; + }; + TraceRootCallback trace_ = nullptr; +}; + +class V8_EXPORT PersistentRegionBase { + using PersistentNodeSlots = std::array; + + public: + // Clears Persistent fields to avoid stale pointers after heap teardown. + ~PersistentRegionBase(); + + PersistentRegionBase(const PersistentRegionBase&) = delete; + PersistentRegionBase& operator=(const PersistentRegionBase&) = delete; + + void Iterate(RootVisitor&); + + size_t NodesInUse() const; + + void ClearAllUsedNodes(); + + protected: + explicit PersistentRegionBase(const FatalOutOfMemoryHandler& oom_handler); + + PersistentNode* TryAllocateNodeFromFreeList(void* owner, + TraceRootCallback trace) { + PersistentNode* node = nullptr; + if (V8_LIKELY(free_list_head_)) { + node = free_list_head_; + free_list_head_ = free_list_head_->FreeListNext(); + CPPGC_DCHECK(!node->IsUsed()); + node->InitializeAsUsedNode(owner, trace); + nodes_in_use_++; + } + return node; + } + + void FreeNode(PersistentNode* node) { + CPPGC_DCHECK(node); + CPPGC_DCHECK(node->IsUsed()); + node->InitializeAsFreeNode(free_list_head_); + free_list_head_ = node; + CPPGC_DCHECK(nodes_in_use_ > 0); + nodes_in_use_--; + } + + PersistentNode* RefillFreeListAndAllocateNode(void* owner, + TraceRootCallback trace); + + private: + template + void ClearAllUsedNodes(); + + void RefillFreeList(); + + std::vector> nodes_; + PersistentNode* free_list_head_ = nullptr; + size_t nodes_in_use_ = 0; + const FatalOutOfMemoryHandler& oom_handler_; + + friend class CrossThreadPersistentRegion; +}; + +// Variant of PersistentRegionBase that checks whether the allocation and +// freeing happens only on the thread that created the heap. +class V8_EXPORT PersistentRegion final : public PersistentRegionBase { + public: + V8_INLINE PersistentRegion(const HeapBase& heap, + const FatalOutOfMemoryHandler& oom_handler) + : PersistentRegionBase(oom_handler), heap_(heap) { + CPPGC_DCHECK(IsCreationThread()); + } + // Clears Persistent fields to avoid stale pointers after heap teardown. + ~PersistentRegion() = default; + + PersistentRegion(const PersistentRegion&) = delete; + PersistentRegion& operator=(const PersistentRegion&) = delete; + + V8_INLINE PersistentNode* AllocateNode(void* owner, TraceRootCallback trace) { + CPPGC_DCHECK(IsCreationThread()); + auto* node = TryAllocateNodeFromFreeList(owner, trace); + if (V8_LIKELY(node)) return node; + + // Slow path allocation allows for checking thread correspondence. + CPPGC_CHECK(IsCreationThread()); + return RefillFreeListAndAllocateNode(owner, trace); + } + + V8_INLINE void FreeNode(PersistentNode* node) { + CPPGC_DCHECK(IsCreationThread()); + PersistentRegionBase::FreeNode(node); + } + + private: + bool IsCreationThread(); + + const HeapBase& heap_; +}; + +// CrossThreadPersistent uses PersistentRegionBase but protects it using this +// lock when needed. +class V8_EXPORT PersistentRegionLock final { + public: + PersistentRegionLock(); + ~PersistentRegionLock(); + + static void AssertLocked(); +}; + +// Variant of PersistentRegionBase that checks whether the PersistentRegionLock +// is locked. +class V8_EXPORT CrossThreadPersistentRegion final + : protected PersistentRegionBase { + public: + explicit CrossThreadPersistentRegion(const FatalOutOfMemoryHandler&); + // Clears Persistent fields to avoid stale pointers after heap teardown. + ~CrossThreadPersistentRegion(); + + CrossThreadPersistentRegion(const CrossThreadPersistentRegion&) = delete; + CrossThreadPersistentRegion& operator=(const CrossThreadPersistentRegion&) = + delete; + + V8_INLINE PersistentNode* AllocateNode(void* owner, TraceRootCallback trace) { + PersistentRegionLock::AssertLocked(); + auto* node = TryAllocateNodeFromFreeList(owner, trace); + if (V8_LIKELY(node)) return node; + + return RefillFreeListAndAllocateNode(owner, trace); + } + + V8_INLINE void FreeNode(PersistentNode* node) { + PersistentRegionLock::AssertLocked(); + PersistentRegionBase::FreeNode(node); + } + + void Iterate(RootVisitor&); + + size_t NodesInUse() const; + + void ClearAllUsedNodes(); +}; + +} // namespace internal + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/pointer-policies.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/pointer-policies.h new file mode 100644 index 000000000..bba484237 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/pointer-policies.h @@ -0,0 +1,280 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_ +#define INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_ + +#include +#include + +#include "cppgc/internal/member-storage.h" +#include "cppgc/internal/write-barrier.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/source-location.h" +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +class HeapBase; +class PersistentRegion; +class CrossThreadPersistentRegion; + +// Tags to distinguish between strong and weak member types. +class StrongMemberTag; +class WeakMemberTag; +class UntracedMemberTag; + +struct DijkstraWriteBarrierPolicy { + // Since in initializing writes the source object is always white, having no + // barrier doesn't break the tri-color invariant. + V8_INLINE static void InitializingBarrier(const void*, const void*) {} + V8_INLINE static void InitializingBarrier(const void*, RawPointer storage) { + } +#if defined(CPPGC_POINTER_COMPRESSION) + V8_INLINE static void InitializingBarrier(const void*, + CompressedPointer storage) {} +#endif + + template + V8_INLINE static void AssigningBarrier(const void* slot, + const void* value) { +#ifdef CPPGC_SLIM_WRITE_BARRIER + if (V8_UNLIKELY(WriteBarrier::IsEnabled())) + WriteBarrier::CombinedWriteBarrierSlow(slot); +#else // !CPPGC_SLIM_WRITE_BARRIER + WriteBarrier::Params params; + const WriteBarrier::Type type = + WriteBarrier::GetWriteBarrierType(slot, value, params); + WriteBarrier(type, params, slot, value); +#endif // !CPPGC_SLIM_WRITE_BARRIER + } + + template + V8_INLINE static void AssigningBarrier(const void* slot, RawPointer storage) { + static_assert( + SlotType == WriteBarrierSlotType::kUncompressed, + "Assigning storages of Member and UncompressedMember is not supported"); +#ifdef CPPGC_SLIM_WRITE_BARRIER + if (V8_UNLIKELY(WriteBarrier::IsEnabled())) + WriteBarrier::CombinedWriteBarrierSlow(slot); +#else // !CPPGC_SLIM_WRITE_BARRIER + WriteBarrier::Params params; + const WriteBarrier::Type type = + WriteBarrier::GetWriteBarrierType(slot, storage, params); + WriteBarrier(type, params, slot, storage.Load()); +#endif // !CPPGC_SLIM_WRITE_BARRIER + } + +#if defined(CPPGC_POINTER_COMPRESSION) + template + V8_INLINE static void AssigningBarrier(const void* slot, + CompressedPointer storage) { + static_assert( + SlotType == WriteBarrierSlotType::kCompressed, + "Assigning storages of Member and UncompressedMember is not supported"); +#ifdef CPPGC_SLIM_WRITE_BARRIER + if (V8_UNLIKELY(WriteBarrier::IsEnabled())) + WriteBarrier::CombinedWriteBarrierSlow(slot); +#else // !CPPGC_SLIM_WRITE_BARRIER + WriteBarrier::Params params; + const WriteBarrier::Type type = + WriteBarrier::GetWriteBarrierType(slot, storage, params); + WriteBarrier(type, params, slot, storage.Load()); +#endif // !CPPGC_SLIM_WRITE_BARRIER + } +#endif // defined(CPPGC_POINTER_COMPRESSION) + + private: + V8_INLINE static void WriteBarrier(WriteBarrier::Type type, + const WriteBarrier::Params& params, + const void* slot, const void* value) { + switch (type) { + case WriteBarrier::Type::kGenerational: + WriteBarrier::GenerationalBarrier< + WriteBarrier::GenerationalBarrierType::kPreciseSlot>(params, slot); + break; + case WriteBarrier::Type::kMarking: + WriteBarrier::DijkstraMarkingBarrier(params, value); + break; + case WriteBarrier::Type::kNone: + break; + } + } +}; + +struct NoWriteBarrierPolicy { + V8_INLINE static void InitializingBarrier(const void*, const void*) {} + V8_INLINE static void InitializingBarrier(const void*, RawPointer storage) {} +#if defined(CPPGC_POINTER_COMPRESSION) + V8_INLINE static void InitializingBarrier(const void*, + CompressedPointer storage) {} +#endif + template + V8_INLINE static void AssigningBarrier(const void*, const void*) {} + template + V8_INLINE static void AssigningBarrier(const void*, MemberStorage) {} +}; + +class V8_EXPORT SameThreadEnabledCheckingPolicyBase { + protected: + void CheckPointerImpl(const void* ptr, bool points_to_payload, + bool check_off_heap_assignments); + + const HeapBase* heap_ = nullptr; +}; + +template +class V8_EXPORT SameThreadEnabledCheckingPolicy + : private SameThreadEnabledCheckingPolicyBase { + protected: + template + V8_INLINE void CheckPointer(RawPointer raw_pointer) { + if (raw_pointer.IsCleared() || raw_pointer.IsSentinel()) { + return; + } + CheckPointersImplTrampoline::Call( + this, static_cast(raw_pointer.Load())); + } +#if defined(CPPGC_POINTER_COMPRESSION) + template + V8_INLINE void CheckPointer(CompressedPointer compressed_pointer) { + if (compressed_pointer.IsCleared() || compressed_pointer.IsSentinel()) { + return; + } + CheckPointersImplTrampoline::Call( + this, static_cast(compressed_pointer.Load())); + } +#endif + template + void CheckPointer(const T* ptr) { + if (!ptr || (kSentinelPointer == ptr)) { + return; + } + CheckPointersImplTrampoline::Call(this, ptr); + } + + private: + template > + struct CheckPointersImplTrampoline { + static void Call(SameThreadEnabledCheckingPolicy* policy, const T* ptr) { + policy->CheckPointerImpl(ptr, false, kCheckOffHeapAssignments); + } + }; + + template + struct CheckPointersImplTrampoline { + static void Call(SameThreadEnabledCheckingPolicy* policy, const T* ptr) { + policy->CheckPointerImpl(ptr, IsGarbageCollectedTypeV, + kCheckOffHeapAssignments); + } + }; +}; + +class DisabledCheckingPolicy { + protected: + template + V8_INLINE void CheckPointer(T*) {} + template + V8_INLINE void CheckPointer(RawPointer) {} +#if defined(CPPGC_POINTER_COMPRESSION) + template + V8_INLINE void CheckPointer(CompressedPointer) {} +#endif +}; + +#ifdef CPPGC_ENABLE_SLOW_API_CHECKS +// Off heap members are not connected to object graph and thus cannot ressurect +// dead objects. +using DefaultMemberCheckingPolicy = + SameThreadEnabledCheckingPolicy; +using DefaultPersistentCheckingPolicy = + SameThreadEnabledCheckingPolicy; +#else // !CPPGC_ENABLE_SLOW_API_CHECKS +using DefaultMemberCheckingPolicy = DisabledCheckingPolicy; +using DefaultPersistentCheckingPolicy = DisabledCheckingPolicy; +#endif // !CPPGC_ENABLE_SLOW_API_CHECKS +// For CT(W)P neither marking information (for value), nor objectstart bitmap +// (for slot) are guaranteed to be present because there's no synchronization +// between heaps after marking. +using DefaultCrossThreadPersistentCheckingPolicy = DisabledCheckingPolicy; + +class KeepLocationPolicy { + public: + constexpr const SourceLocation& Location() const { return location_; } + + protected: + constexpr KeepLocationPolicy() = default; + constexpr explicit KeepLocationPolicy(const SourceLocation& location) + : location_(location) {} + + // KeepLocationPolicy must not copy underlying source locations. + KeepLocationPolicy(const KeepLocationPolicy&) = delete; + KeepLocationPolicy& operator=(const KeepLocationPolicy&) = delete; + + // Location of the original moved from object should be preserved. + KeepLocationPolicy(KeepLocationPolicy&&) = default; + KeepLocationPolicy& operator=(KeepLocationPolicy&&) = default; + + private: + SourceLocation location_; +}; + +class IgnoreLocationPolicy { + public: + constexpr SourceLocation Location() const { return {}; } + + protected: + constexpr IgnoreLocationPolicy() = default; + constexpr explicit IgnoreLocationPolicy(const SourceLocation&) {} +}; + +#if CPPGC_SUPPORTS_OBJECT_NAMES +using DefaultLocationPolicy = KeepLocationPolicy; +#else +using DefaultLocationPolicy = IgnoreLocationPolicy; +#endif + +struct StrongPersistentPolicy { + using IsStrongPersistent = std::true_type; + static V8_EXPORT PersistentRegion& GetPersistentRegion(const void* object); +}; + +struct WeakPersistentPolicy { + using IsStrongPersistent = std::false_type; + static V8_EXPORT PersistentRegion& GetPersistentRegion(const void* object); +}; + +struct StrongCrossThreadPersistentPolicy { + using IsStrongPersistent = std::true_type; + static V8_EXPORT CrossThreadPersistentRegion& GetPersistentRegion( + const void* object); +}; + +struct WeakCrossThreadPersistentPolicy { + using IsStrongPersistent = std::false_type; + static V8_EXPORT CrossThreadPersistentRegion& GetPersistentRegion( + const void* object); +}; + +// Forward declarations setting up the default policies. +template +class BasicCrossThreadPersistent; +template +class BasicPersistent; +template +class BasicMember; + +} // namespace internal + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/internal/write-barrier.h b/NativeScript/napi/android/v8-13/include/cppgc/internal/write-barrier.h new file mode 100644 index 000000000..566724d30 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/internal/write-barrier.h @@ -0,0 +1,487 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_ +#define INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_ + +#include +#include + +#include "cppgc/heap-handle.h" +#include "cppgc/heap-state.h" +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/atomic-entry-flag.h" +#include "cppgc/internal/base-page-handle.h" +#include "cppgc/internal/member-storage.h" +#include "cppgc/platform.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +#if defined(CPPGC_CAGED_HEAP) +#include "cppgc/internal/caged-heap-local-data.h" +#include "cppgc/internal/caged-heap.h" +#endif + +namespace cppgc { + +class HeapHandle; + +namespace internal { + +#if defined(CPPGC_CAGED_HEAP) +class WriteBarrierTypeForCagedHeapPolicy; +#else // !CPPGC_CAGED_HEAP +class WriteBarrierTypeForNonCagedHeapPolicy; +#endif // !CPPGC_CAGED_HEAP + +class V8_EXPORT WriteBarrier final { + public: + enum class Type : uint8_t { + kNone, + kMarking, + kGenerational, + }; + + enum class GenerationalBarrierType : uint8_t { + kPreciseSlot, + kPreciseUncompressedSlot, + kImpreciseSlot, + }; + + struct Params { + HeapHandle* heap = nullptr; +#if V8_ENABLE_CHECKS + Type type = Type::kNone; +#endif // !V8_ENABLE_CHECKS +#if defined(CPPGC_CAGED_HEAP) + uintptr_t slot_offset = 0; + uintptr_t value_offset = 0; +#endif // CPPGC_CAGED_HEAP + }; + + enum class ValueMode { + kValuePresent, + kNoValuePresent, + }; + + // Returns the required write barrier for a given `slot` and `value`. + static V8_INLINE Type GetWriteBarrierType(const void* slot, const void* value, + Params& params); + // Returns the required write barrier for a given `slot` and `value`. + template + static V8_INLINE Type GetWriteBarrierType(const void* slot, MemberStorage, + Params& params); + // Returns the required write barrier for a given `slot`. + template + static V8_INLINE Type GetWriteBarrierType(const void* slot, Params& params, + HeapHandleCallback callback); + // Returns the required write barrier for a given `value`. + static V8_INLINE Type GetWriteBarrierType(const void* value, Params& params); + +#ifdef CPPGC_SLIM_WRITE_BARRIER + // A write barrier that combines `GenerationalBarrier()` and + // `DijkstraMarkingBarrier()`. We only pass a single parameter here to clobber + // as few registers as possible. + template + static V8_NOINLINE void V8_PRESERVE_MOST + CombinedWriteBarrierSlow(const void* slot); +#endif // CPPGC_SLIM_WRITE_BARRIER + + static V8_INLINE void DijkstraMarkingBarrier(const Params& params, + const void* object); + static V8_INLINE void DijkstraMarkingBarrierRange( + const Params& params, const void* first_element, size_t element_size, + size_t number_of_elements, TraceCallback trace_callback); + static V8_INLINE void SteeleMarkingBarrier(const Params& params, + const void* object); +#if defined(CPPGC_YOUNG_GENERATION) + template + static V8_INLINE void GenerationalBarrier(const Params& params, + const void* slot); +#else // !CPPGC_YOUNG_GENERATION + template + static V8_INLINE void GenerationalBarrier(const Params& params, + const void* slot){} +#endif // CPPGC_YOUNG_GENERATION + +#if V8_ENABLE_CHECKS + static void CheckParams(Type expected_type, const Params& params); +#else // !V8_ENABLE_CHECKS + static void CheckParams(Type expected_type, const Params& params) {} +#endif // !V8_ENABLE_CHECKS + + // The FlagUpdater class allows cppgc internal to update + // |write_barrier_enabled_|. + class FlagUpdater; + static bool IsEnabled() { return write_barrier_enabled_.MightBeEntered(); } + + private: + WriteBarrier() = delete; + +#if defined(CPPGC_CAGED_HEAP) + using WriteBarrierTypePolicy = WriteBarrierTypeForCagedHeapPolicy; +#else // !CPPGC_CAGED_HEAP + using WriteBarrierTypePolicy = WriteBarrierTypeForNonCagedHeapPolicy; +#endif // !CPPGC_CAGED_HEAP + + static void DijkstraMarkingBarrierSlow(const void* value); + static void DijkstraMarkingBarrierSlowWithSentinelCheck(const void* value); + static void DijkstraMarkingBarrierRangeSlow(HeapHandle& heap_handle, + const void* first_element, + size_t element_size, + size_t number_of_elements, + TraceCallback trace_callback); + static void SteeleMarkingBarrierSlow(const void* value); + static void SteeleMarkingBarrierSlowWithSentinelCheck(const void* value); + +#if defined(CPPGC_YOUNG_GENERATION) + static CagedHeapLocalData& GetLocalData(HeapHandle&); + static void GenerationalBarrierSlow(const CagedHeapLocalData& local_data, + const AgeTable& age_table, + const void* slot, uintptr_t value_offset, + HeapHandle* heap_handle); + static void GenerationalBarrierForUncompressedSlotSlow( + const CagedHeapLocalData& local_data, const AgeTable& age_table, + const void* slot, uintptr_t value_offset, HeapHandle* heap_handle); + static void GenerationalBarrierForSourceObjectSlow( + const CagedHeapLocalData& local_data, const void* object, + HeapHandle* heap_handle); +#endif // CPPGC_YOUNG_GENERATION + + static AtomicEntryFlag write_barrier_enabled_; +}; + +template +V8_INLINE WriteBarrier::Type SetAndReturnType(WriteBarrier::Params& params) { + if constexpr (type == WriteBarrier::Type::kNone) + return WriteBarrier::Type::kNone; +#if V8_ENABLE_CHECKS + params.type = type; +#endif // !V8_ENABLE_CHECKS + return type; +} + +#if defined(CPPGC_CAGED_HEAP) +class V8_EXPORT WriteBarrierTypeForCagedHeapPolicy final { + public: + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return ValueModeDispatch::Get(slot, value, params, callback); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, MemberStorage value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return ValueModeDispatch::Get(slot, value, params, callback); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return GetNoSlot(value, params, callback); + } + + private: + WriteBarrierTypeForCagedHeapPolicy() = delete; + + template + static V8_INLINE WriteBarrier::Type GetNoSlot(const void* value, + WriteBarrier::Params& params, + HeapHandleCallback) { + const bool within_cage = CagedHeapBase::IsWithinCage(value); + if (!within_cage) return WriteBarrier::Type::kNone; + + // We know that |value| points either within the normal page or to the + // beginning of large-page, so extract the page header by bitmasking. + BasePageHandle* page = + BasePageHandle::FromPayload(const_cast(value)); + + HeapHandle& heap_handle = page->heap_handle(); + if (V8_UNLIKELY(heap_handle.is_incremental_marking_in_progress())) { + return SetAndReturnType(params); + } + + return SetAndReturnType(params); + } + + template + struct ValueModeDispatch; +}; + +template <> +struct WriteBarrierTypeForCagedHeapPolicy::ValueModeDispatch< + WriteBarrier::ValueMode::kValuePresent> { + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, + MemberStorage storage, + WriteBarrier::Params& params, + HeapHandleCallback) { + if (V8_LIKELY(!WriteBarrier::IsEnabled())) + return SetAndReturnType(params); + + return BarrierEnabledGet(slot, storage.Load(), params); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value, + WriteBarrier::Params& params, + HeapHandleCallback) { + if (V8_LIKELY(!WriteBarrier::IsEnabled())) + return SetAndReturnType(params); + + return BarrierEnabledGet(slot, value, params); + } + + private: + static V8_INLINE WriteBarrier::Type BarrierEnabledGet( + const void* slot, const void* value, WriteBarrier::Params& params) { + const bool within_cage = CagedHeapBase::AreWithinCage(slot, value); + if (!within_cage) return WriteBarrier::Type::kNone; + + // We know that |value| points either within the normal page or to the + // beginning of large-page, so extract the page header by bitmasking. + BasePageHandle* page = + BasePageHandle::FromPayload(const_cast(value)); + + HeapHandle& heap_handle = page->heap_handle(); + if (V8_LIKELY(!heap_handle.is_incremental_marking_in_progress())) { +#if defined(CPPGC_YOUNG_GENERATION) + if (!heap_handle.is_young_generation_enabled()) + return WriteBarrier::Type::kNone; + params.heap = &heap_handle; + params.slot_offset = CagedHeapBase::OffsetFromAddress(slot); + params.value_offset = CagedHeapBase::OffsetFromAddress(value); + return SetAndReturnType(params); +#else // !CPPGC_YOUNG_GENERATION + return SetAndReturnType(params); +#endif // !CPPGC_YOUNG_GENERATION + } + + // Use marking barrier. + params.heap = &heap_handle; + return SetAndReturnType(params); + } +}; + +template <> +struct WriteBarrierTypeForCagedHeapPolicy::ValueModeDispatch< + WriteBarrier::ValueMode::kNoValuePresent> { + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, const void*, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + if (V8_LIKELY(!WriteBarrier::IsEnabled())) + return SetAndReturnType(params); + + HeapHandle& handle = callback(); +#if defined(CPPGC_YOUNG_GENERATION) + if (V8_LIKELY(!handle.is_incremental_marking_in_progress())) { + if (!handle.is_young_generation_enabled()) { + return WriteBarrier::Type::kNone; + } + params.heap = &handle; + // Check if slot is on stack. + if (V8_UNLIKELY(!CagedHeapBase::IsWithinCage(slot))) { + return SetAndReturnType(params); + } + params.slot_offset = CagedHeapBase::OffsetFromAddress(slot); + return SetAndReturnType(params); + } +#else // !defined(CPPGC_YOUNG_GENERATION) + if (V8_UNLIKELY(!handle.is_incremental_marking_in_progress())) { + return SetAndReturnType(params); + } +#endif // !defined(CPPGC_YOUNG_GENERATION) + params.heap = &handle; + return SetAndReturnType(params); + } +}; + +#endif // CPPGC_CAGED_HEAP + +class V8_EXPORT WriteBarrierTypeForNonCagedHeapPolicy final { + public: + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return ValueModeDispatch::Get(slot, value, params, callback); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, RawPointer value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return ValueModeDispatch::Get(slot, value.Load(), params, + callback); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + // The slot will never be used in `Get()` below. + return Get(nullptr, value, params, + callback); + } + + private: + template + struct ValueModeDispatch; + + WriteBarrierTypeForNonCagedHeapPolicy() = delete; +}; + +template <> +struct WriteBarrierTypeForNonCagedHeapPolicy::ValueModeDispatch< + WriteBarrier::ValueMode::kValuePresent> { + template + static V8_INLINE WriteBarrier::Type Get(const void*, const void* object, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + // The following check covers nullptr as well as sentinel pointer. + if (object <= static_cast(kSentinelPointer)) { + return SetAndReturnType(params); + } + if (V8_LIKELY(!WriteBarrier::IsEnabled())) { + return SetAndReturnType(params); + } + // We know that |object| is within the normal page or in the beginning of a + // large page, so extract the page header by bitmasking. + BasePageHandle* page = + BasePageHandle::FromPayload(const_cast(object)); + + HeapHandle& heap_handle = page->heap_handle(); + if (V8_LIKELY(heap_handle.is_incremental_marking_in_progress())) { + return SetAndReturnType(params); + } + return SetAndReturnType(params); + } +}; + +template <> +struct WriteBarrierTypeForNonCagedHeapPolicy::ValueModeDispatch< + WriteBarrier::ValueMode::kNoValuePresent> { + template + static V8_INLINE WriteBarrier::Type Get(const void*, const void*, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + if (V8_UNLIKELY(WriteBarrier::IsEnabled())) { + HeapHandle& handle = callback(); + if (V8_LIKELY(handle.is_incremental_marking_in_progress())) { + params.heap = &handle; + return SetAndReturnType(params); + } + } + return WriteBarrier::Type::kNone; + } +}; + +// static +WriteBarrier::Type WriteBarrier::GetWriteBarrierType( + const void* slot, const void* value, WriteBarrier::Params& params) { + return WriteBarrierTypePolicy::Get(slot, value, + params, []() {}); +} + +// static +template +WriteBarrier::Type WriteBarrier::GetWriteBarrierType( + const void* slot, MemberStorage value, WriteBarrier::Params& params) { + return WriteBarrierTypePolicy::Get(slot, value, + params, []() {}); +} + +// static +template +WriteBarrier::Type WriteBarrier::GetWriteBarrierType( + const void* slot, WriteBarrier::Params& params, + HeapHandleCallback callback) { + return WriteBarrierTypePolicy::Get( + slot, nullptr, params, callback); +} + +// static +WriteBarrier::Type WriteBarrier::GetWriteBarrierType( + const void* value, WriteBarrier::Params& params) { + return WriteBarrierTypePolicy::Get(value, params, + []() {}); +} + +// static +void WriteBarrier::DijkstraMarkingBarrier(const Params& params, + const void* object) { + CheckParams(Type::kMarking, params); +#if defined(CPPGC_CAGED_HEAP) + // Caged heap already filters out sentinels. + DijkstraMarkingBarrierSlow(object); +#else // !CPPGC_CAGED_HEAP + DijkstraMarkingBarrierSlowWithSentinelCheck(object); +#endif // !CPPGC_CAGED_HEAP +} + +// static +void WriteBarrier::DijkstraMarkingBarrierRange(const Params& params, + const void* first_element, + size_t element_size, + size_t number_of_elements, + TraceCallback trace_callback) { + CheckParams(Type::kMarking, params); + DijkstraMarkingBarrierRangeSlow(*params.heap, first_element, element_size, + number_of_elements, trace_callback); +} + +// static +void WriteBarrier::SteeleMarkingBarrier(const Params& params, + const void* object) { + CheckParams(Type::kMarking, params); +#if defined(CPPGC_CAGED_HEAP) + // Caged heap already filters out sentinels. + SteeleMarkingBarrierSlow(object); +#else // !CPPGC_CAGED_HEAP + SteeleMarkingBarrierSlowWithSentinelCheck(object); +#endif // !CPPGC_CAGED_HEAP +} + +#if defined(CPPGC_YOUNG_GENERATION) + +// static +template +void WriteBarrier::GenerationalBarrier(const Params& params, const void* slot) { + CheckParams(Type::kGenerational, params); + + const CagedHeapLocalData& local_data = CagedHeapLocalData::Get(); + const AgeTable& age_table = local_data.age_table; + + // Bail out if the slot (precise or imprecise) is in young generation. + if (V8_LIKELY(age_table.GetAge(params.slot_offset) == AgeTable::Age::kYoung)) + return; + + // Dispatch between different types of barriers. + // TODO(chromium:1029379): Consider reload local_data in the slow path to + // reduce register pressure. + if constexpr (type == GenerationalBarrierType::kPreciseSlot) { + GenerationalBarrierSlow(local_data, age_table, slot, params.value_offset, + params.heap); + } else if constexpr (type == + GenerationalBarrierType::kPreciseUncompressedSlot) { + GenerationalBarrierForUncompressedSlotSlow( + local_data, age_table, slot, params.value_offset, params.heap); + } else { + GenerationalBarrierForSourceObjectSlow(local_data, slot, params.heap); + } +} + +#endif // !CPPGC_YOUNG_GENERATION + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/liveness-broker.h b/NativeScript/napi/android/v8-13/include/cppgc/liveness-broker.h new file mode 100644 index 000000000..2c94f1c0f --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/liveness-broker.h @@ -0,0 +1,78 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_LIVENESS_BROKER_H_ +#define INCLUDE_CPPGC_LIVENESS_BROKER_H_ + +#include "cppgc/heap.h" +#include "cppgc/member.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +namespace internal { +class LivenessBrokerFactory; +} // namespace internal + +/** + * The broker is passed to weak callbacks to allow (temporarily) querying + * the liveness state of an object. References to non-live objects must be + * cleared when `IsHeapObjectAlive()` returns false. + * + * \code + * class GCedWithCustomWeakCallback final + * : public GarbageCollected { + * public: + * UntracedMember bar; + * + * void CustomWeakCallbackMethod(const LivenessBroker& broker) { + * if (!broker.IsHeapObjectAlive(bar)) + * bar = nullptr; + * } + * + * void Trace(cppgc::Visitor* visitor) const { + * visitor->RegisterWeakCallbackMethod< + * GCedWithCustomWeakCallback, + * &GCedWithCustomWeakCallback::CustomWeakCallbackMethod>(this); + * } + * }; + * \endcode + */ +class V8_EXPORT LivenessBroker final { + public: + template + bool IsHeapObjectAlive(const T* object) const { + // - nullptr objects are considered alive to allow weakness to be used from + // stack while running into a conservative GC. Treating nullptr as dead + // would mean that e.g. custom collections could not be strongified on + // stack. + // - Sentinel pointers are also preserved in weakness and not cleared. + return !object || object == kSentinelPointer || + IsHeapObjectAliveImpl( + TraceTrait::GetTraceDescriptor(object).base_object_payload); + } + + template + bool IsHeapObjectAlive(const WeakMember& weak_member) const { + return IsHeapObjectAlive(weak_member.Get()); + } + + template + bool IsHeapObjectAlive(const UntracedMember& untraced_member) const { + return IsHeapObjectAlive(untraced_member.Get()); + } + + private: + LivenessBroker() = default; + + bool IsHeapObjectAliveImpl(const void*) const; + + friend class internal::LivenessBrokerFactory; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_LIVENESS_BROKER_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/macros.h b/NativeScript/napi/android/v8-13/include/cppgc/macros.h new file mode 100644 index 000000000..10a6f8eba --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/macros.h @@ -0,0 +1,56 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_MACROS_H_ +#define INCLUDE_CPPGC_MACROS_H_ + +#include + +#include "cppgc/internal/compiler-specific.h" + +namespace cppgc { + +#define CPPGC_DISALLOW_NEW() \ + public: \ + using IsDisallowNewMarker CPPGC_UNUSED = int; \ + void* operator new(size_t, void* location) { return location; } \ + void* operator new(size_t) = delete; \ + static_assert(true, "Force semicolon.") + +// Use CPPGC_STACK_ALLOCATED if the object is only stack allocated. +// Add the CPPGC_STACK_ALLOCATED_IGNORE annotation on a case-by-case basis when +// enforcement of CPPGC_STACK_ALLOCATED should be suppressed. +#if defined(__clang__) + +#define CPPGC_STACK_ALLOCATED() \ + public: \ + using IsStackAllocatedTypeMarker CPPGC_UNUSED = int; \ + \ + private: \ + void* operator new(size_t) = delete; \ + void* operator new(size_t, void*) = delete; \ + static_assert(true, "Force semicolon.") + +#define CPPGC_STACK_ALLOCATED_IGNORE(bug_or_reason) \ + __attribute__((annotate("stack_allocated_ignore"))) + +#define CPPGC_PLUGIN_IGNORE(bug_or_reason) \ + __attribute__((annotate("blink_gc_plugin_ignore"), \ + annotate("stack_allocated_ignore"))) + +#else // !defined(__clang__) + +#define CPPGC_STACK_ALLOCATED() static_assert(true, "Force semicolon.") +#define CPPGC_STACK_ALLOCATED_IGNORE(bug_or_reason) +#define CPPGC_PLUGIN_IGNORE(bug_or_reason) + +#endif // !defined(__clang__) + +template +concept IsStackAllocatedType = + requires { typename T::IsStackAllocatedTypeMarker; }; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_MACROS_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/member.h b/NativeScript/napi/android/v8-13/include/cppgc/member.h new file mode 100644 index 000000000..fb457dd58 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/member.h @@ -0,0 +1,663 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_MEMBER_H_ +#define INCLUDE_CPPGC_MEMBER_H_ + +#include +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/member-storage.h" +#include "cppgc/internal/pointer-policies.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +namespace subtle { +class HeapConsistency; +} // namespace subtle + +class Visitor; + +namespace internal { + +// MemberBase always refers to the object as const object and defers to +// BasicMember on casting to the right type as needed. +template +class V8_TRIVIAL_ABI MemberBase { + public: + using RawStorage = StorageType; + + protected: + struct AtomicInitializerTag {}; + + V8_INLINE MemberBase() = default; + V8_INLINE explicit MemberBase(const void* value) : raw_(value) {} + V8_INLINE MemberBase(const void* value, AtomicInitializerTag) + : raw_(value, typename RawStorage::AtomicInitializerTag{}) {} + + V8_INLINE explicit MemberBase(RawStorage raw) : raw_(raw) {} + V8_INLINE explicit MemberBase(std::nullptr_t) : raw_(nullptr) {} + V8_INLINE explicit MemberBase(SentinelPointer s) : raw_(s) {} + + V8_INLINE const void** GetRawSlot() const { + return reinterpret_cast(const_cast(this)); + } + V8_INLINE const void* GetRaw() const { return raw_.Load(); } + V8_INLINE void SetRaw(void* value) { raw_.Store(value); } + + V8_INLINE const void* GetRawAtomic() const { return raw_.LoadAtomic(); } + V8_INLINE void SetRawAtomic(const void* value) { raw_.StoreAtomic(value); } + + V8_INLINE RawStorage GetRawStorage() const { return raw_; } + V8_INLINE void SetRawStorageAtomic(RawStorage other) { + reinterpret_cast&>(raw_).store( + other, std::memory_order_relaxed); + } + + V8_INLINE bool IsCleared() const { return raw_.IsCleared(); } + + V8_INLINE void ClearFromGC() const { raw_.Clear(); } + + private: + friend class MemberDebugHelper; + + mutable RawStorage raw_; +}; + +// The basic class from which all Member classes are 'generated'. +template +class V8_TRIVIAL_ABI BasicMember final : private MemberBase, + private CheckingPolicy { + using Base = MemberBase; + + public: + using PointeeType = T; + using RawStorage = typename Base::RawStorage; + + V8_INLINE constexpr BasicMember() = default; + V8_INLINE constexpr BasicMember(std::nullptr_t) {} // NOLINT + V8_INLINE BasicMember(SentinelPointer s) : Base(s) {} // NOLINT + V8_INLINE BasicMember(T* raw) : Base(raw) { // NOLINT + InitializingWriteBarrier(raw); + CheckPointer(raw); + } + V8_INLINE BasicMember(T& raw) // NOLINT + : BasicMember(&raw) {} + + // Atomic ctor. Using the AtomicInitializerTag forces BasicMember to + // initialize using atomic assignments. This is required for preventing + // data races with concurrent marking. + using AtomicInitializerTag = typename Base::AtomicInitializerTag; + V8_INLINE BasicMember(std::nullptr_t, AtomicInitializerTag atomic) + : Base(nullptr, atomic) {} + V8_INLINE BasicMember(SentinelPointer s, AtomicInitializerTag atomic) + : Base(s, atomic) {} + V8_INLINE BasicMember(T* raw, AtomicInitializerTag atomic) + : Base(raw, atomic) { + InitializingWriteBarrier(raw); + CheckPointer(raw); + } + V8_INLINE BasicMember(T& raw, AtomicInitializerTag atomic) + : BasicMember(&raw, atomic) {} + + // Copy ctor. + V8_INLINE BasicMember(const BasicMember& other) + : BasicMember(other.GetRawStorage()) {} + + // Heterogeneous copy constructors. When the source pointer have a different + // type, perform a compress-decompress round, because the source pointer may + // need to be adjusted. + template >* = nullptr> + V8_INLINE BasicMember( // NOLINT + const BasicMember& other) + : BasicMember(other.GetRawStorage()) {} + + template >* = nullptr> + V8_INLINE BasicMember( // NOLINT + const BasicMember& other) + : BasicMember(other.Get()) {} + + // Move ctor. + V8_INLINE BasicMember(BasicMember&& other) noexcept + : BasicMember(other.GetRawStorage()) { + other.Clear(); + } + + // Heterogeneous move constructors. When the source pointer have a different + // type, perform a compress-decompress round, because the source pointer may + // need to be adjusted. + template >* = nullptr> + V8_INLINE BasicMember( + BasicMember&& other) noexcept + : BasicMember(other.GetRawStorage()) { + other.Clear(); + } + + template >* = nullptr> + V8_INLINE BasicMember( + BasicMember&& other) noexcept + : BasicMember(other.Get()) { + other.Clear(); + } + + // Construction from Persistent. + template >> + V8_INLINE BasicMember(const BasicPersistent& p) + : BasicMember(p.Get()) {} + + // Copy assignment. + V8_INLINE BasicMember& operator=(const BasicMember& other) { + return operator=(other.GetRawStorage()); + } + + // Heterogeneous copy assignment. When the source pointer have a different + // type, perform a compress-decompress round, because the source pointer may + // need to be adjusted. + template + V8_INLINE BasicMember& operator=( + const BasicMember& other) { + if constexpr (IsDecayedSameV) { + return operator=(other.GetRawStorage()); + } else { + static_assert(IsStrictlyBaseOfV); + return operator=(other.Get()); + } + } + + // Move assignment. + V8_INLINE BasicMember& operator=(BasicMember&& other) noexcept { + operator=(other.GetRawStorage()); + other.Clear(); + return *this; + } + + // Heterogeneous move assignment. When the source pointer have a different + // type, perform a compress-decompress round, because the source pointer may + // need to be adjusted. + template + V8_INLINE BasicMember& operator=( + BasicMember&& other) noexcept { + if constexpr (IsDecayedSameV) { + operator=(other.GetRawStorage()); + } else { + static_assert(IsStrictlyBaseOfV); + operator=(other.Get()); + } + other.Clear(); + return *this; + } + + // Assignment from Persistent. + template >> + V8_INLINE BasicMember& operator=( + const BasicPersistent& + other) { + return operator=(other.Get()); + } + + V8_INLINE BasicMember& operator=(T* other) { + Base::SetRawAtomic(other); + AssigningWriteBarrier(other); + CheckPointer(other); + return *this; + } + + V8_INLINE BasicMember& operator=(std::nullptr_t) { + Clear(); + return *this; + } + V8_INLINE BasicMember& operator=(SentinelPointer s) { + Base::SetRawAtomic(s); + return *this; + } + + template + V8_INLINE void Swap(BasicMember& other) { + auto tmp = GetRawStorage(); + *this = other; + other = tmp; + } + + V8_INLINE explicit operator bool() const { return !Base::IsCleared(); } + V8_INLINE operator T*() const { return Get(); } + V8_INLINE T* operator->() const { return Get(); } + V8_INLINE T& operator*() const { return *Get(); } + + // CFI cast exemption to allow passing SentinelPointer through T* and support + // heterogeneous assignments between different Member and Persistent handles + // based on their actual types. + V8_INLINE V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { + // Executed by the mutator, hence non atomic load. + // + // The const_cast below removes the constness from MemberBase storage. The + // following static_cast re-adds any constness if specified through the + // user-visible template parameter T. + return static_cast(const_cast(Base::GetRaw())); + } + + V8_INLINE void Clear() { Base::SetRawStorageAtomic(RawStorage{}); } + + V8_INLINE T* Release() { + T* result = Get(); + Clear(); + return result; + } + + V8_INLINE const T** GetSlotForTesting() const { + return reinterpret_cast(Base::GetRawSlot()); + } + + V8_INLINE RawStorage GetRawStorage() const { return Base::GetRawStorage(); } + + private: + V8_INLINE explicit BasicMember(RawStorage raw) : Base(raw) { + InitializingWriteBarrier(); + CheckPointer(); + } + + V8_INLINE BasicMember& operator=(RawStorage other) { + Base::SetRawStorageAtomic(other); + AssigningWriteBarrier(); + CheckPointer(); + return *this; + } + + V8_INLINE const T* GetRawAtomic() const { + return static_cast(Base::GetRawAtomic()); + } + + V8_INLINE void InitializingWriteBarrier(T* value) const { + WriteBarrierPolicy::InitializingBarrier(Base::GetRawSlot(), value); + } + V8_INLINE void InitializingWriteBarrier() const { + WriteBarrierPolicy::InitializingBarrier(Base::GetRawSlot(), + Base::GetRawStorage()); + } + V8_INLINE void AssigningWriteBarrier(T* value) const { + WriteBarrierPolicy::template AssigningBarrier< + StorageType::kWriteBarrierSlotType>(Base::GetRawSlot(), value); + } + V8_INLINE void AssigningWriteBarrier() const { + WriteBarrierPolicy::template AssigningBarrier< + StorageType::kWriteBarrierSlotType>(Base::GetRawSlot(), + Base::GetRawStorage()); + } + V8_INLINE void CheckPointer(T* value) { + CheckingPolicy::template CheckPointer(value); + } + V8_INLINE void CheckPointer() { + CheckingPolicy::template CheckPointer(Base::GetRawStorage()); + } + + V8_INLINE void ClearFromGC() const { Base::ClearFromGC(); } + + V8_INLINE T* GetFromGC() const { return Get(); } + + friend class cppgc::subtle::HeapConsistency; + friend class cppgc::Visitor; + template + friend struct cppgc::TraceTrait; + template + friend class BasicMember; +}; + +// Member equality operators. +template +V8_INLINE bool operator==( + const BasicMember& member1, + const BasicMember& member2) { + if constexpr (IsDecayedSameV) { + // Check compressed pointers if types are the same. + return member1.GetRawStorage() == member2.GetRawStorage(); + } else { + static_assert(IsStrictlyBaseOfV || IsStrictlyBaseOfV); + // Otherwise, check decompressed pointers. + return member1.Get() == member2.Get(); + } +} + +template +V8_INLINE bool operator!=( + const BasicMember& member1, + const BasicMember& member2) { + return !(member1 == member2); +} + +// Equality with raw pointers. +template +V8_INLINE bool operator==( + const BasicMember& member, + U* raw) { + // Never allow comparison with erased pointers. + static_assert(!IsDecayedSameV); + + if constexpr (IsDecayedSameV) { + // Check compressed pointers if types are the same. + return member.GetRawStorage() == StorageType(raw); + } else if constexpr (IsStrictlyBaseOfV) { + // Cast the raw pointer to T, which may adjust the pointer. + return member.GetRawStorage() == StorageType(static_cast(raw)); + } else { + // Otherwise, decompressed the member. + return member.Get() == raw; + } +} + +template +V8_INLINE bool operator!=( + const BasicMember& member, + U* raw) { + return !(member == raw); +} + +template +V8_INLINE bool operator==( + T* raw, const BasicMember& member) { + return member == raw; +} + +template +V8_INLINE bool operator!=( + T* raw, const BasicMember& member) { + return !(raw == member); +} + +// Equality with sentinel. +template +V8_INLINE bool operator==( + const BasicMember& member, + SentinelPointer) { + return member.GetRawStorage().IsSentinel(); +} + +template +V8_INLINE bool operator!=( + const BasicMember& member, + SentinelPointer s) { + return !(member == s); +} + +template +V8_INLINE bool operator==( + SentinelPointer s, const BasicMember& member) { + return member == s; +} + +template +V8_INLINE bool operator!=( + SentinelPointer s, const BasicMember& member) { + return !(s == member); +} + +// Equality with nullptr. +template +V8_INLINE bool operator==( + const BasicMember& member, + std::nullptr_t) { + return !static_cast(member); +} + +template +V8_INLINE bool operator!=( + const BasicMember& member, + std::nullptr_t n) { + return !(member == n); +} + +template +V8_INLINE bool operator==( + std::nullptr_t n, const BasicMember& member) { + return member == n; +} + +template +V8_INLINE bool operator!=( + std::nullptr_t n, const BasicMember& member) { + return !(n == member); +} + +// Relational operators. +template +V8_INLINE bool operator<( + const BasicMember& member1, + const BasicMember& member2) { + static_assert( + IsDecayedSameV, + "Comparison works only for same pointer type modulo cv-qualifiers"); + return member1.GetRawStorage() < member2.GetRawStorage(); +} + +template +V8_INLINE bool operator<=( + const BasicMember& member1, + const BasicMember& member2) { + static_assert( + IsDecayedSameV, + "Comparison works only for same pointer type modulo cv-qualifiers"); + return member1.GetRawStorage() <= member2.GetRawStorage(); +} + +template +V8_INLINE bool operator>( + const BasicMember& member1, + const BasicMember& member2) { + static_assert( + IsDecayedSameV, + "Comparison works only for same pointer type modulo cv-qualifiers"); + return member1.GetRawStorage() > member2.GetRawStorage(); +} + +template +V8_INLINE bool operator>=( + const BasicMember& member1, + const BasicMember& member2) { + static_assert( + IsDecayedSameV, + "Comparison works only for same pointer type modulo cv-qualifiers"); + return member1.GetRawStorage() >= member2.GetRawStorage(); +} + +template +struct IsWeak> : std::true_type {}; + +} // namespace internal + +/** + * Members are used in classes to contain strong pointers to other garbage + * collected objects. All Member fields of a class must be traced in the class' + * trace method. + */ +template +using Member = internal::BasicMember< + T, internal::StrongMemberTag, internal::DijkstraWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::DefaultMemberStorage>; + +/** + * WeakMember is similar to Member in that it is used to point to other garbage + * collected objects. However instead of creating a strong pointer to the + * object, the WeakMember creates a weak pointer, which does not keep the + * pointee alive. Hence if all pointers to to a heap allocated object are weak + * the object will be garbage collected. At the time of GC the weak pointers + * will automatically be set to null. + */ +template +using WeakMember = internal::BasicMember< + T, internal::WeakMemberTag, internal::DijkstraWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::DefaultMemberStorage>; + +/** + * UntracedMember is a pointer to an on-heap object that is not traced for some + * reason. Do not use this unless you know what you are doing. Keeping raw + * pointers to on-heap objects is prohibited unless used from stack. Pointee + * must be kept alive through other means. + */ +template +using UntracedMember = internal::BasicMember< + T, internal::UntracedMemberTag, internal::NoWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::DefaultMemberStorage>; + +namespace subtle { + +/** + * UncompressedMember. Use with care in hot paths that would otherwise cause + * many decompression cycles. + */ +template +using UncompressedMember = internal::BasicMember< + T, internal::StrongMemberTag, internal::DijkstraWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::RawPointer>; + +#if defined(CPPGC_POINTER_COMPRESSION) +/** + * CompressedMember. Default implementation of cppgc::Member on builds with + * pointer compression. + */ +template +using CompressedMember = internal::BasicMember< + T, internal::StrongMemberTag, internal::DijkstraWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::CompressedPointer>; +#endif // defined(CPPGC_POINTER_COMPRESSION) + +} // namespace subtle + +namespace internal { + +struct Dummy; + +static constexpr size_t kSizeOfMember = sizeof(Member); +static constexpr size_t kSizeOfUncompressedMember = + sizeof(subtle::UncompressedMember); +#if defined(CPPGC_POINTER_COMPRESSION) +static constexpr size_t kSizeofCompressedMember = + sizeof(subtle::CompressedMember); +#endif // defined(CPPGC_POINTER_COMPRESSION) + +} // namespace internal + +} // namespace cppgc + +// Mark `BasicMember` and `T*` as having a common reference type of `T*` (the +// type to which both can be converted or bound). This makes them satisfy +// `std::equality_comparable`, which allows usage like the following: +// ``` +// HeapVector> v; +// T* e; +// auto it = std::ranges::find(v, e); +// ``` +// Without this, the `find()` call above would fail to compile with an error +// about being unable to invoke `std::ranges::equal_to()`. +template typename TQ, template typename UQ> +struct std::basic_common_reference< + cppgc::internal::BasicMember, + T*, TQ, UQ> { + using type = T*; +}; + +template typename TQ, template typename UQ> +struct std::basic_common_reference< + T*, + cppgc::internal::BasicMember, + TQ, UQ> { + using type = T*; +}; + +#endif // INCLUDE_CPPGC_MEMBER_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/name-provider.h b/NativeScript/napi/android/v8-13/include/cppgc/name-provider.h new file mode 100644 index 000000000..849176fde --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/name-provider.h @@ -0,0 +1,75 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_NAME_PROVIDER_H_ +#define INCLUDE_CPPGC_NAME_PROVIDER_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +/** + * NameProvider allows for providing a human-readable name for garbage-collected + * objects. + * + * There's two cases of names to distinguish: + * a. Explicitly specified names via using NameProvider. Such names are always + * preserved in the system. + * b. Internal names that Oilpan infers from a C++ type on the class hierarchy + * of the object. This is not necessarily the type of the actually + * instantiated object. + * + * Depending on the build configuration, Oilpan may hide names, i.e., represent + * them with kHiddenName, of case b. to avoid exposing internal details. + */ +class V8_EXPORT NameProvider { + public: + /** + * Name that is used when hiding internals. + */ + static constexpr const char kHiddenName[] = "InternalNode"; + + /** + * Name that is used in case compiler support is missing for composing a name + * from C++ types. + */ + static constexpr const char kNoNameDeducible[] = ""; + + /** + * Indicating whether the build supports extracting C++ names as object names. + * + * @returns true if C++ names should be hidden and represented by kHiddenName. + */ + static constexpr bool SupportsCppClassNamesAsObjectNames() { +#if CPPGC_SUPPORTS_OBJECT_NAMES + return true; +#else // !CPPGC_SUPPORTS_OBJECT_NAMES + return false; +#endif // !CPPGC_SUPPORTS_OBJECT_NAMES + } + + virtual ~NameProvider() = default; + + /** + * Specifies a name for the garbage-collected object. Such names will never + * be hidden, as they are explicitly specified by the user of this API. + * + * Implementations of this function must not allocate garbage-collected + * objects or otherwise modify the cppgc heap. + * + * V8 may call this function while generating a heap snapshot or at other + * times. If V8 is currently generating a heap snapshot (according to + * HeapProfiler::IsTakingSnapshot), then the returned string must stay alive + * until the snapshot generation has completed. Otherwise, the returned string + * must stay alive forever. If you need a place to store a temporary string + * during snapshot generation, use HeapProfiler::CopyNameForHeapSnapshot. + * + * @returns a human readable name for the object. + */ + virtual const char* GetHumanReadableName() const = 0; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_NAME_PROVIDER_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/object-size-trait.h b/NativeScript/napi/android/v8-13/include/cppgc/object-size-trait.h new file mode 100644 index 000000000..35795596d --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/object-size-trait.h @@ -0,0 +1,58 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_ +#define INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_ + +#include + +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +namespace internal { + +struct V8_EXPORT BaseObjectSizeTrait { + protected: + static size_t GetObjectSizeForGarbageCollected(const void*); + static size_t GetObjectSizeForGarbageCollectedMixin(const void*); +}; + +} // namespace internal + +namespace subtle { + +/** + * Trait specifying how to get the size of an object that was allocated using + * `MakeGarbageCollected()`. Also supports querying the size with an inner + * pointer to a mixin. + */ +template > +struct ObjectSizeTrait; + +template +struct ObjectSizeTrait : cppgc::internal::BaseObjectSizeTrait { + static_assert(sizeof(T), "T must be fully defined"); + static_assert(IsGarbageCollectedTypeV, + "T must be of type GarbageCollected or GarbageCollectedMixin"); + + static size_t GetSize(const T& object) { + return GetObjectSizeForGarbageCollected(&object); + } +}; + +template +struct ObjectSizeTrait : cppgc::internal::BaseObjectSizeTrait { + static_assert(sizeof(T), "T must be fully defined"); + + static size_t GetSize(const T& object) { + return GetObjectSizeForGarbageCollectedMixin(&object); + } +}; + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/persistent.h b/NativeScript/napi/android/v8-13/include/cppgc/persistent.h new file mode 100644 index 000000000..b330e87fe --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/persistent.h @@ -0,0 +1,377 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_PERSISTENT_H_ +#define INCLUDE_CPPGC_PERSISTENT_H_ + +#include + +#include "cppgc/internal/persistent-node.h" +#include "cppgc/internal/pointer-policies.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/source-location.h" +#include "cppgc/type-traits.h" +#include "cppgc/visitor.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +// PersistentBase always refers to the object as const object and defers to +// BasicPersistent on casting to the right type as needed. +class PersistentBase { + protected: + PersistentBase() = default; + explicit PersistentBase(const void* raw) : raw_(raw) {} + + const void* GetValue() const { return raw_; } + void SetValue(const void* value) { raw_ = value; } + + PersistentNode* GetNode() const { return node_; } + void SetNode(PersistentNode* node) { node_ = node; } + + // Performs a shallow clear which assumes that internal persistent nodes are + // destroyed elsewhere. + void ClearFromGC() const { + raw_ = nullptr; + node_ = nullptr; + } + + protected: + mutable const void* raw_ = nullptr; + mutable PersistentNode* node_ = nullptr; + + friend class PersistentRegionBase; +}; + +// The basic class from which all Persistent classes are generated. +template +class BasicPersistent final : public PersistentBase, + public LocationPolicy, + private WeaknessPolicy, + private CheckingPolicy { + public: + using typename WeaknessPolicy::IsStrongPersistent; + using PointeeType = T; + + // Null-state/sentinel constructors. + BasicPersistent( // NOLINT + const SourceLocation& loc = SourceLocation::Current()) + : LocationPolicy(loc) {} + + BasicPersistent(std::nullptr_t, // NOLINT + const SourceLocation& loc = SourceLocation::Current()) + : LocationPolicy(loc) {} + + BasicPersistent( // NOLINT + SentinelPointer s, const SourceLocation& loc = SourceLocation::Current()) + : PersistentBase(s), LocationPolicy(loc) {} + + // Raw value constructors. + BasicPersistent(T* raw, // NOLINT + const SourceLocation& loc = SourceLocation::Current()) + : PersistentBase(raw), LocationPolicy(loc) { + if (!IsValid()) return; + SetNode(WeaknessPolicy::GetPersistentRegion(GetValue()) + .AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(Get()); + } + + BasicPersistent(T& raw, // NOLINT + const SourceLocation& loc = SourceLocation::Current()) + : BasicPersistent(&raw, loc) {} + + // Copy ctor. + BasicPersistent(const BasicPersistent& other, + const SourceLocation& loc = SourceLocation::Current()) + : BasicPersistent(other.Get(), loc) {} + + // Heterogeneous ctor. + template >> + // NOLINTNEXTLINE + BasicPersistent( + const BasicPersistent& other, + const SourceLocation& loc = SourceLocation::Current()) + : BasicPersistent(other.Get(), loc) {} + + // Move ctor. The heterogeneous move ctor is not supported since e.g. + // persistent can't reuse persistent node from weak persistent. + BasicPersistent( + BasicPersistent&& other, + const SourceLocation& loc = SourceLocation::Current()) noexcept + : PersistentBase(std::move(other)), LocationPolicy(std::move(other)) { + if (!IsValid()) return; + GetNode()->UpdateOwner(this); + other.SetValue(nullptr); + other.SetNode(nullptr); + this->CheckPointer(Get()); + } + + // Constructor from member. + template >> + // NOLINTNEXTLINE + BasicPersistent(const internal::BasicMember< + U, MemberBarrierPolicy, MemberWeaknessTag, + MemberCheckingPolicy, MemberStorageType>& member, + const SourceLocation& loc = SourceLocation::Current()) + : BasicPersistent(member.Get(), loc) {} + + ~BasicPersistent() { Clear(); } + + // Copy assignment. + BasicPersistent& operator=(const BasicPersistent& other) { + return operator=(other.Get()); + } + + template >> + BasicPersistent& operator=( + const BasicPersistent& other) { + return operator=(other.Get()); + } + + // Move assignment. + BasicPersistent& operator=(BasicPersistent&& other) noexcept { + if (this == &other) return *this; + Clear(); + PersistentBase::operator=(std::move(other)); + LocationPolicy::operator=(std::move(other)); + if (!IsValid()) return *this; + GetNode()->UpdateOwner(this); + other.SetValue(nullptr); + other.SetNode(nullptr); + this->CheckPointer(Get()); + return *this; + } + + // Assignment from member. + template >> + BasicPersistent& operator=( + const internal::BasicMember& + member) { + return operator=(member.Get()); + } + + BasicPersistent& operator=(T* other) { + Assign(other); + return *this; + } + + BasicPersistent& operator=(std::nullptr_t) { + Clear(); + return *this; + } + + BasicPersistent& operator=(SentinelPointer s) { + Assign(s); + return *this; + } + + explicit operator bool() const { return Get(); } + // Historically we allow implicit conversions to T*. + // NOLINTNEXTLINE + operator T*() const { return Get(); } + T* operator->() const { return Get(); } + T& operator*() const { return *Get(); } + + // CFI cast exemption to allow passing SentinelPointer through T* and support + // heterogeneous assignments between different Member and Persistent handles + // based on their actual types. + V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { + // The const_cast below removes the constness from PersistentBase storage. + // The following static_cast re-adds any constness if specified through the + // user-visible template parameter T. + return static_cast(const_cast(GetValue())); + } + + void Clear() { + // Simplified version of `Assign()` to allow calling without a complete type + // `T`. + if (IsValid()) { + WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); + SetNode(nullptr); + } + SetValue(nullptr); + } + + T* Release() { + T* result = Get(); + Clear(); + return result; + } + + template + BasicPersistent + To() const { + return BasicPersistent(static_cast(Get())); + } + + private: + static void TraceAsRoot(RootVisitor& root_visitor, const void* ptr) { + root_visitor.Trace(*static_cast(ptr)); + } + + bool IsValid() const { + // Ideally, handling kSentinelPointer would be done by the embedder. On the + // other hand, having Persistent aware of it is beneficial since no node + // gets wasted. + return GetValue() != nullptr && GetValue() != kSentinelPointer; + } + + void Assign(T* ptr) { + if (IsValid()) { + if (ptr && ptr != kSentinelPointer) { + // Simply assign the pointer reusing the existing node. + SetValue(ptr); + this->CheckPointer(ptr); + return; + } + WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); + SetNode(nullptr); + } + SetValue(ptr); + if (!IsValid()) return; + SetNode(WeaknessPolicy::GetPersistentRegion(GetValue()) + .AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(Get()); + } + + void ClearFromGC() const { + if (IsValid()) { + WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); + PersistentBase::ClearFromGC(); + } + } + + // Set Get() for details. + V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") + T* GetFromGC() const { + return static_cast(const_cast(GetValue())); + } + + friend class internal::RootVisitor; +}; + +template +bool operator==(const BasicPersistent& p1, + const BasicPersistent& p2) { + return p1.Get() == p2.Get(); +} + +template +bool operator!=(const BasicPersistent& p1, + const BasicPersistent& p2) { + return !(p1 == p2); +} + +template +bool operator==( + const BasicPersistent& + p, + const BasicMember& m) { + return p.Get() == m.Get(); +} + +template +bool operator!=( + const BasicPersistent& + p, + const BasicMember& m) { + return !(p == m); +} + +template +bool operator==( + const BasicMember& m, + const BasicPersistent& + p) { + return m.Get() == p.Get(); +} + +template +bool operator!=( + const BasicMember& m, + const BasicPersistent& + p) { + return !(m == p); +} + +template +struct IsWeak> : std::true_type {}; +} // namespace internal + +/** + * Persistent is a way to create a strong pointer from an off-heap object to + * another on-heap object. As long as the Persistent handle is alive the GC will + * keep the object pointed to alive. The Persistent handle is always a GC root + * from the point of view of the GC. Persistent must be constructed and + * destructed in the same thread. + */ +template +using Persistent = + internal::BasicPersistent; + +/** + * WeakPersistent is a way to create a weak pointer from an off-heap object to + * an on-heap object. The pointer is automatically cleared when the pointee gets + * collected. WeakPersistent must be constructed and destructed in the same + * thread. + */ +template +using WeakPersistent = + internal::BasicPersistent; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_PERSISTENT_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/platform.h b/NativeScript/napi/android/v8-13/include/cppgc/platform.h new file mode 100644 index 000000000..917fa25e0 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/platform.h @@ -0,0 +1,174 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_PLATFORM_H_ +#define INCLUDE_CPPGC_PLATFORM_H_ + +#include + +#include "cppgc/source-location.h" +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +// TODO(v8:10346): Create separate includes for concepts that are not +// V8-specific. +using IdleTask = v8::IdleTask; +using JobHandle = v8::JobHandle; +using JobDelegate = v8::JobDelegate; +using JobTask = v8::JobTask; +using PageAllocator = v8::PageAllocator; +using Task = v8::Task; +using TaskPriority = v8::TaskPriority; +using TaskRunner = v8::TaskRunner; +using TracingController = v8::TracingController; + +/** + * Platform interface used by Heap. Contains allocators and executors. + */ +class V8_EXPORT Platform { + public: + virtual ~Platform() = default; + + /** + * \returns the allocator used by cppgc to allocate its heap and various + * support structures. Returning nullptr results in using the `PageAllocator` + * provided by `cppgc::InitializeProcess()` instead. + */ + virtual PageAllocator* GetPageAllocator() = 0; + + /** + * Monotonically increasing time in seconds from an arbitrary fixed point in + * the past. This function is expected to return at least + * millisecond-precision values. For this reason, + * it is recommended that the fixed point be no further in the past than + * the epoch. + **/ + virtual double MonotonicallyIncreasingTime() = 0; + + /** + * Foreground task runner that should be used by a Heap. + */ + virtual std::shared_ptr GetForegroundTaskRunner() { + return GetForegroundTaskRunner(TaskPriority::kUserBlocking); + } + + /** + * Returns a TaskRunner with a specific |priority| which can be used to post a + * task on the foreground thread. + */ + virtual std::shared_ptr GetForegroundTaskRunner( + TaskPriority priority) { + return nullptr; + } + + /** + * Posts `job_task` to run in parallel. Returns a `JobHandle` associated with + * the `Job`, which can be joined or canceled. + * This avoids degenerate cases: + * - Calling `CallOnWorkerThread()` for each work item, causing significant + * overhead. + * - Fixed number of `CallOnWorkerThread()` calls that split the work and + * might run for a long time. This is problematic when many components post + * "num cores" tasks and all expect to use all the cores. In these cases, + * the scheduler lacks context to be fair to multiple same-priority requests + * and/or ability to request lower priority work to yield when high priority + * work comes in. + * A canonical implementation of `job_task` looks like: + * \code + * class MyJobTask : public JobTask { + * public: + * MyJobTask(...) : worker_queue_(...) {} + * // JobTask implementation. + * void Run(JobDelegate* delegate) override { + * while (!delegate->ShouldYield()) { + * // Smallest unit of work. + * auto work_item = worker_queue_.TakeWorkItem(); // Thread safe. + * if (!work_item) return; + * ProcessWork(work_item); + * } + * } + * + * size_t GetMaxConcurrency() const override { + * return worker_queue_.GetSize(); // Thread safe. + * } + * }; + * + * // ... + * auto handle = PostJob(TaskPriority::kUserVisible, + * std::make_unique(...)); + * handle->Join(); + * \endcode + * + * `PostJob()` and methods of the returned JobHandle/JobDelegate, must never + * be called while holding a lock that could be acquired by `JobTask::Run()` + * or `JobTask::GetMaxConcurrency()` -- that could result in a deadlock. This + * is because (1) `JobTask::GetMaxConcurrency()` may be invoked while holding + * internal lock (A), hence `JobTask::GetMaxConcurrency()` can only use a lock + * (B) if that lock is *never* held while calling back into `JobHandle` from + * any thread (A=>B/B=>A deadlock) and (2) `JobTask::Run()` or + * `JobTask::GetMaxConcurrency()` may be invoked synchronously from + * `JobHandle` (B=>JobHandle::foo=>B deadlock). + * + * A sufficient `PostJob()` implementation that uses the default Job provided + * in libplatform looks like: + * \code + * std::unique_ptr PostJob( + * TaskPriority priority, std::unique_ptr job_task) override { + * return std::make_unique( + * std::make_shared( + * this, std::move(job_task), kNumThreads)); + * } + * \endcode + */ + virtual std::unique_ptr PostJob( + TaskPriority priority, std::unique_ptr job_task) { + return nullptr; + } + + /** + * Returns an instance of a `TracingController`. This must be non-nullptr. The + * default implementation returns an empty `TracingController` that consumes + * trace data without effect. + */ + virtual TracingController* GetTracingController(); +}; + +V8_EXPORT bool IsInitialized(); + +/** + * Process-global initialization of the garbage collector. Must be called before + * creating a Heap. + * + * Can be called multiple times when paired with `ShutdownProcess()`. + * + * \param page_allocator The allocator used for maintaining meta data. Must stay + * always alive and not change between multiple calls to InitializeProcess. If + * no allocator is provided, a default internal version will be used. + * \param desired_heap_size Desired amount of virtual address space to reserve + * for the heap, in bytes. Actual size will be clamped to minimum and maximum + * values based on compile-time settings and may be rounded up. If this + * parameter is zero, a default value will be used. + */ +V8_EXPORT void InitializeProcess(PageAllocator* page_allocator = nullptr, + size_t desired_heap_size = 0); + +/** + * Must be called after destroying the last used heap. Some process-global + * metadata may not be returned and reused upon a subsequent + * `InitializeProcess()` call. + */ +V8_EXPORT void ShutdownProcess(); + +namespace internal { + +V8_EXPORT void Fatal(const std::string& reason = std::string(), + const SourceLocation& = SourceLocation::Current()); + +} // namespace internal + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_PLATFORM_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/prefinalizer.h b/NativeScript/napi/android/v8-13/include/cppgc/prefinalizer.h new file mode 100644 index 000000000..51f2eac8e --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/prefinalizer.h @@ -0,0 +1,75 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_PREFINALIZER_H_ +#define INCLUDE_CPPGC_PREFINALIZER_H_ + +#include "cppgc/internal/compiler-specific.h" +#include "cppgc/liveness-broker.h" + +namespace cppgc { + +namespace internal { + +class V8_EXPORT PrefinalizerRegistration final { + public: + using Callback = bool (*)(const cppgc::LivenessBroker&, void*); + + PrefinalizerRegistration(void*, Callback); + + void* operator new(size_t, void* location) = delete; + void* operator new(size_t) = delete; +}; + +} // namespace internal + +/** + * Macro must be used in the private section of `Class` and registers a + * prefinalization callback `void Class::PreFinalizer()`. The callback is + * invoked on garbage collection after the collector has found an object to be + * dead. + * + * Callback properties: + * - The callback is invoked before a possible destructor for the corresponding + * object. + * - The callback may access the whole object graph, irrespective of whether + * objects are considered dead or alive. + * - The callback is invoked on the same thread as the object was created on. + * + * Example: + * \code + * class WithPrefinalizer : public GarbageCollected { + * CPPGC_USING_PRE_FINALIZER(WithPrefinalizer, Dispose); + * + * public: + * void Trace(Visitor*) const {} + * void Dispose() { prefinalizer_called = true; } + * ~WithPrefinalizer() { + * // prefinalizer_called == true + * } + * private: + * bool prefinalizer_called = false; + * }; + * \endcode + */ +#define CPPGC_USING_PRE_FINALIZER(Class, PreFinalizer) \ + public: \ + static bool InvokePreFinalizer(const cppgc::LivenessBroker& liveness_broker, \ + void* object) { \ + static_assert(cppgc::IsGarbageCollectedOrMixinTypeV, \ + "Only garbage collected objects can have prefinalizers"); \ + Class* self = static_cast(object); \ + if (liveness_broker.IsHeapObjectAlive(self)) return false; \ + self->PreFinalizer(); \ + return true; \ + } \ + \ + private: \ + CPPGC_NO_UNIQUE_ADDRESS cppgc::internal::PrefinalizerRegistration \ + prefinalizer_dummy_{this, Class::InvokePreFinalizer}; \ + static_assert(true, "Force semicolon.") + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_PREFINALIZER_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/process-heap-statistics.h b/NativeScript/napi/android/v8-13/include/cppgc/process-heap-statistics.h new file mode 100644 index 000000000..774cc92f4 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/process-heap-statistics.h @@ -0,0 +1,36 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_ +#define INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_ + +#include +#include + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { +class ProcessHeapStatisticsUpdater; +} // namespace internal + +class V8_EXPORT ProcessHeapStatistics final { + public: + static size_t TotalAllocatedObjectSize() { + return total_allocated_object_size_.load(std::memory_order_relaxed); + } + static size_t TotalAllocatedSpace() { + return total_allocated_space_.load(std::memory_order_relaxed); + } + + private: + static std::atomic_size_t total_allocated_space_; + static std::atomic_size_t total_allocated_object_size_; + + friend class internal::ProcessHeapStatisticsUpdater; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/sentinel-pointer.h b/NativeScript/napi/android/v8-13/include/cppgc/sentinel-pointer.h new file mode 100644 index 000000000..bee96c776 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/sentinel-pointer.h @@ -0,0 +1,39 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_SENTINEL_POINTER_H_ +#define INCLUDE_CPPGC_SENTINEL_POINTER_H_ + +#include + +#include "cppgc/internal/api-constants.h" + +namespace cppgc { +namespace internal { + +// Special tag type used to denote some sentinel member. The semantics of the +// sentinel is defined by the embedder. +struct SentinelPointer { +#if defined(CPPGC_POINTER_COMPRESSION) + static constexpr intptr_t kSentinelValue = + 1 << api_constants::kPointerCompressionShift; +#else // !defined(CPPGC_POINTER_COMPRESSION) + static constexpr intptr_t kSentinelValue = 0b10; +#endif // !defined(CPPGC_POINTER_COMPRESSION) + template + operator T*() const { + return reinterpret_cast(kSentinelValue); + } + // Hidden friends. + friend bool operator==(SentinelPointer, SentinelPointer) { return true; } + friend bool operator!=(SentinelPointer, SentinelPointer) { return false; } +}; + +} // namespace internal + +constexpr internal::SentinelPointer kSentinelPointer; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_SENTINEL_POINTER_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/source-location.h b/NativeScript/napi/android/v8-13/include/cppgc/source-location.h new file mode 100644 index 000000000..0dc28aedd --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/source-location.h @@ -0,0 +1,16 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_SOURCE_LOCATION_H_ +#define INCLUDE_CPPGC_SOURCE_LOCATION_H_ + +#include "v8-source-location.h" + +namespace cppgc { + +using SourceLocation = v8::SourceLocation; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_SOURCE_LOCATION_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/tagged-member.h b/NativeScript/napi/android/v8-13/include/cppgc/tagged-member.h new file mode 100644 index 000000000..7a2dfbb4c --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/tagged-member.h @@ -0,0 +1,111 @@ +// Copyright 2025 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_TAGGED_MEMBER_H_ +#define INCLUDE_CPPGC_TAGGED_MEMBER_H_ + +#include +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/macros.h" +#include "cppgc/member.h" +#include "cppgc/visitor.h" + +namespace cppgc::subtle { + +// The class allows to store a Member along with a single bit tag. It uses +// distinct tag types, Tag1 and Tag2, to represent the two states of the tag. +// The tag is stored in the least significant bit of the pointer. +// +// Example usage: +// struct ParentTag {}; +// struct ShadowHostTag {}; +// +// /* Constructs a member with the pointer to parent tag: */ +// TaggedUncompressedMember +// m(ParentTag{}, parent); +template +struct TaggedUncompressedMember final { + CPPGC_DISALLOW_NEW(); + static constexpr uintptr_t kTagBit = 0b1; + static_assert(kTagBit < internal::api_constants::kAllocationGranularity, + "The tag must live in the alignment bits of the pointer."); + + public: + TaggedUncompressedMember(Tag1, Pointee* ptr) : ptr_(ptr) {} + TaggedUncompressedMember(Tag2, Pointee* ptr) + : ptr_(reinterpret_cast(reinterpret_cast(ptr) | + kTagBit)) {} + + template + Pointee* GetAs() const { + auto* raw = ptr_.Get(); + if constexpr (std::same_as) { + CPPGC_DCHECK(Is()); + return raw; + } else { + static_assert(std::same_as); + CPPGC_DCHECK(Is()); + return GetUntagged(); + } + } + + template + Pointee* TryGetAs() const { + auto* raw = ptr_.Get(); + if constexpr (std::same_as) { + return (reinterpret_cast(raw) & kTagBit) ? nullptr : raw; + } else { + static_assert(std::same_as); + return (reinterpret_cast(raw) & kTagBit) + ? reinterpret_cast(reinterpret_cast(raw) & + ~kTagBit) + : nullptr; + } + } + + Pointee* GetUntagged() const { + return reinterpret_cast(reinterpret_cast(ptr_.Get()) & + ~kTagBit); + } + + template + void SetAs(Pointee* pointee) { + if constexpr (std::same_as) { + ptr_ = pointee; + } else { + static_assert(std::same_as); + ptr_ = reinterpret_cast(reinterpret_cast(pointee) | + kTagBit); + } + } + + template + bool Is() const { + const bool tag_set = reinterpret_cast(ptr_.Get()) & kTagBit; + if constexpr (std::same_as) { + return !tag_set; + } else { + static_assert(std::same_as); + return tag_set; + } + } + + void Trace(Visitor* v) const { + // Construct an untagged pointer and pass it to Visitor::Trace(). The plugin + // would warn that ptr_ is untraced, which is why CPPGC_PLUGIN_IGNORE is + // used. + UncompressedMember temp(GetUntagged()); + v->Trace(temp); + } + + private: + CPPGC_PLUGIN_IGNORE("See Trace()") UncompressedMember ptr_; +}; + +} // namespace cppgc::subtle + +#endif // INCLUDE_CPPGC_TAGGED_MEMBER_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/testing.h b/NativeScript/napi/android/v8-13/include/cppgc/testing.h new file mode 100644 index 000000000..bddd1fc16 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/testing.h @@ -0,0 +1,106 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_TESTING_H_ +#define INCLUDE_CPPGC_TESTING_H_ + +#include "cppgc/common.h" +#include "cppgc/macros.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +class HeapHandle; + +/** + * Namespace contains testing helpers. + */ +namespace testing { + +/** + * Overrides the state of the stack with the provided value. Parameters passed + * to explicit garbage collection calls still take precedence. Must not be + * nested. + * + * This scope is useful to make the garbage collector consider the stack when + * tasks that invoke garbage collection (through the provided platform) contain + * interesting pointers on its stack. + */ +class V8_EXPORT V8_NODISCARD OverrideEmbedderStackStateScope final { + CPPGC_STACK_ALLOCATED(); + + public: + /** + * Constructs a scoped object that automatically enters and leaves the scope. + * + * \param heap_handle The corresponding heap. + */ + explicit OverrideEmbedderStackStateScope(HeapHandle& heap_handle, + EmbedderStackState state); + ~OverrideEmbedderStackStateScope(); + + OverrideEmbedderStackStateScope(const OverrideEmbedderStackStateScope&) = + delete; + OverrideEmbedderStackStateScope& operator=( + const OverrideEmbedderStackStateScope&) = delete; + + private: + HeapHandle& heap_handle_; +}; + +/** + * Testing interface for managed heaps that allows for controlling garbage + * collection timings. Embedders should use this class when testing the + * interaction of their code with incremental/concurrent garbage collection. + */ +class V8_EXPORT StandaloneTestingHeap final { + public: + explicit StandaloneTestingHeap(HeapHandle&); + + /** + * Start an incremental garbage collection. + */ + void StartGarbageCollection(); + + /** + * Perform an incremental step. This will also schedule concurrent steps if + * needed. + * + * \param stack_state The state of the stack during the step. + */ + bool PerformMarkingStep(EmbedderStackState stack_state); + + /** + * Finalize the current garbage collection cycle atomically. + * Assumes that garbage collection is in progress. + * + * \param stack_state The state of the stack for finalizing the garbage + * collection cycle. + */ + void FinalizeGarbageCollection(EmbedderStackState stack_state); + + /** + * Toggle main thread marking on/off. Allows to stress concurrent marking + * (e.g. to better detect data races). + * + * \param should_mark Denotes whether the main thread should contribute to + * marking. Defaults to true. + */ + void ToggleMainThreadMarking(bool should_mark); + + /** + * Force enable compaction for the next garbage collection cycle. + */ + void ForceCompactionForNextGarbageCollection(); + + private: + HeapHandle& heap_handle_; +}; + +V8_EXPORT bool IsHeapObjectOld(void*); + +} // namespace testing +} // namespace cppgc + +#endif // INCLUDE_CPPGC_TESTING_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/trace-trait.h b/NativeScript/napi/android/v8-13/include/cppgc/trace-trait.h new file mode 100644 index 000000000..9b768a152 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/trace-trait.h @@ -0,0 +1,127 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_TRACE_TRAIT_H_ +#define INCLUDE_CPPGC_TRACE_TRAIT_H_ + +#include + +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +class Visitor; + +namespace internal { + +class RootVisitor; + +using TraceRootCallback = void (*)(RootVisitor&, const void* object); + +// Implementation of the default TraceTrait handling GarbageCollected and +// GarbageCollectedMixin. +template >> +struct TraceTraitImpl; + +} // namespace internal + +/** + * Callback for invoking tracing on a given object. + * + * \param visitor The visitor to dispatch to. + * \param object The object to invoke tracing on. + */ +using TraceCallback = void (*)(Visitor* visitor, const void* object); + +/** + * Describes how to trace an object, i.e., how to visit all Oilpan-relevant + * fields of an object. + */ +struct TraceDescriptor { + /** + * Adjusted base pointer, i.e., the pointer to the class inheriting directly + * from GarbageCollected, of the object that is being traced. + */ + const void* base_object_payload; + /** + * Callback for tracing the object. + */ + TraceCallback callback; +}; + +/** + * Callback for getting a TraceDescriptor for a given address. + * + * \param address Possibly inner address of an object. + * \returns a TraceDescriptor for the provided address. + */ +using TraceDescriptorCallback = TraceDescriptor (*)(const void* address); + +namespace internal { + +struct V8_EXPORT TraceTraitFromInnerAddressImpl { + static TraceDescriptor GetTraceDescriptor(const void* address); +}; + +/** + * Trait specifying how the garbage collector processes an object of type T. + * + * Advanced users may override handling by creating a specialization for their + * type. + */ +template +struct TraceTraitBase { + static_assert(internal::IsTraceableV, "T must have a Trace() method"); + + /** + * Accessor for retrieving a TraceDescriptor to process an object of type T. + * + * \param self The object to be processed. + * \returns a TraceDescriptor to process the object. + */ + static TraceDescriptor GetTraceDescriptor(const void* self) { + return internal::TraceTraitImpl::GetTraceDescriptor( + static_cast(self)); + } + + /** + * Function invoking the tracing for an object of type T. + * + * \param visitor The visitor to dispatch to. + * \param self The object to invoke tracing on. + */ + static void Trace(Visitor* visitor, const void* self) { + static_cast(self)->Trace(visitor); + } +}; + +} // namespace internal + +template +struct TraceTrait : public internal::TraceTraitBase {}; + +namespace internal { + +template +struct TraceTraitImpl { + static_assert(IsGarbageCollectedTypeV, + "T must be of type GarbageCollected or GarbageCollectedMixin"); + static TraceDescriptor GetTraceDescriptor(const void* self) { + return {self, TraceTrait::Trace}; + } +}; + +template +struct TraceTraitImpl { + static TraceDescriptor GetTraceDescriptor(const void* self) { + return internal::TraceTraitFromInnerAddressImpl::GetTraceDescriptor(self); + } +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_TRACE_TRAIT_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/type-traits.h b/NativeScript/napi/android/v8-13/include/cppgc/type-traits.h new file mode 100644 index 000000000..1685c31df --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/type-traits.h @@ -0,0 +1,272 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_TYPE_TRAITS_H_ +#define INCLUDE_CPPGC_TYPE_TRAITS_H_ + +// This file should stay with minimal dependencies to allow embedder to check +// against Oilpan types without including any other parts. +#include +#include +#include + +namespace cppgc { + +class Visitor; + +namespace internal { +template +class BasicMember; +struct DijkstraWriteBarrierPolicy; +struct NoWriteBarrierPolicy; +class StrongMemberTag; +class UntracedMemberTag; +class WeakMemberTag; + +// Not supposed to be specialized by the user. +template +struct IsWeak : std::false_type {}; + +// IsTraceMethodConst is used to verify that all Trace methods are marked as +// const. It is equivalent to IsTraceable but for a non-const object. +template +struct IsTraceMethodConst : std::false_type {}; + +template +struct IsTraceMethodConst().Trace( + std::declval()))>> : std::true_type { +}; + +template +struct IsTraceable : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsTraceable< + T, std::void_t().Trace(std::declval()))>> + : std::true_type { + // All Trace methods should be marked as const. If an object of type + // 'T' is traceable then any object of type 'const T' should also + // be traceable. + static_assert(IsTraceMethodConst(), + "Trace methods should be marked as const."); +}; + +template +constexpr bool IsTraceableV = IsTraceable::value; + +template +struct HasGarbageCollectedMixinTypeMarker : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct HasGarbageCollectedMixinTypeMarker< + T, std::void_t< + typename std::remove_const_t::IsGarbageCollectedMixinTypeMarker>> + : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct HasGarbageCollectedTypeMarker : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct HasGarbageCollectedTypeMarker< + T, + std::void_t::IsGarbageCollectedTypeMarker>> + : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template ::value, + bool = HasGarbageCollectedMixinTypeMarker::value> +struct IsGarbageCollectedMixinType : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedMixinType : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template ::value> +struct IsGarbageCollectedType : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedType : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedOrMixinType + : std::integral_constant::value || + IsGarbageCollectedMixinType::value> { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template ::value && + HasGarbageCollectedMixinTypeMarker::value)> +struct IsGarbageCollectedWithMixinType : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedWithMixinType : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsSubclassOfBasicMemberTemplate { + private: + template + static std::true_type SubclassCheck( + const BasicMember*); + static std::false_type SubclassCheck(...); + + public: + static constexpr bool value = decltype(SubclassCheck( + std::declval*>()))::value; +}; + +template ::value> +struct IsMemberType : std::false_type {}; + +template +struct IsMemberType : std::true_type {}; + +template ::value> +struct IsWeakMemberType : std::false_type {}; + +template +struct IsWeakMemberType : std::true_type {}; + +template ::value> +struct IsUntracedMemberType : std::false_type {}; + +template +struct IsUntracedMemberType : std::true_type {}; + +template +struct IsComplete { + private: + template + static std::true_type IsSizeOfKnown(U*); + static std::false_type IsSizeOfKnown(...); + + public: + static constexpr bool value = + decltype(IsSizeOfKnown(std::declval()))::value; +}; + +template +constexpr bool IsDecayedSameV = + std::is_same_v, std::decay_t>; + +template +constexpr bool IsStrictlyBaseOfV = + std::is_base_of_v, std::decay_t> && + !IsDecayedSameV; + +template +constexpr bool IsAnyMemberTypeV = false; + +template +constexpr bool IsAnyMemberTypeV> = true; + +} // namespace internal + +/** + * Value is true for types that inherit from `GarbageCollectedMixin` but not + * `GarbageCollected` (i.e., they are free mixins), and false otherwise. + */ +template +constexpr bool IsGarbageCollectedMixinTypeV = + internal::IsGarbageCollectedMixinType::value; + +/** + * Value is true for types that inherit from `GarbageCollected`, and false + * otherwise. + */ +template +constexpr bool IsGarbageCollectedTypeV = + internal::IsGarbageCollectedType::value; + +/** + * Value is true for types that inherit from either `GarbageCollected` or + * `GarbageCollectedMixin`, and false otherwise. + */ +template +constexpr bool IsGarbageCollectedOrMixinTypeV = + internal::IsGarbageCollectedOrMixinType::value; + +/** + * Value is true for types that inherit from `GarbageCollected` and + * `GarbageCollectedMixin`, and false otherwise. + */ +template +constexpr bool IsGarbageCollectedWithMixinTypeV = + internal::IsGarbageCollectedWithMixinType::value; + +/** + * Value is true for types of type `Member`, and false otherwise. + */ +template +constexpr bool IsMemberTypeV = internal::IsMemberType::value; + +/** + * Value is true for types of type `UntracedMember`, and false otherwise. + */ +template +constexpr bool IsUntracedMemberTypeV = internal::IsUntracedMemberType::value; + +/** + * Value is true for types of type `WeakMember`, and false otherwise. + */ +template +constexpr bool IsWeakMemberTypeV = internal::IsWeakMemberType::value; + +/** + * Value is true for types that are considered weak references, and false + * otherwise. + */ +template +constexpr bool IsWeakV = internal::IsWeak::value; + +/** + * Value is true for types that are complete, and false otherwise. + */ +template +constexpr bool IsCompleteV = internal::IsComplete::value; + +/** + * Value is true for member types `Member` and `WeakMember`. + */ +template +constexpr bool IsMemberOrWeakMemberTypeV = + IsMemberTypeV || IsWeakMemberTypeV; + +/** + * Value is true for any member type. + */ +template +constexpr bool IsAnyMemberTypeV = internal::IsAnyMemberTypeV>; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_TYPE_TRAITS_H_ diff --git a/NativeScript/napi/android/v8-13/include/cppgc/visitor.h b/NativeScript/napi/android/v8-13/include/cppgc/visitor.h new file mode 100644 index 000000000..e0b1d19be --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/cppgc/visitor.h @@ -0,0 +1,528 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_CPPGC_VISITOR_H_ +#define INCLUDE_CPPGC_VISITOR_H_ + +#include + +#include "cppgc/custom-space.h" +#include "cppgc/garbage-collected.h" +#include "cppgc/internal/logging.h" +#include "cppgc/internal/member-storage.h" +#include "cppgc/internal/pointer-policies.h" +#include "cppgc/liveness-broker.h" +#include "cppgc/macros.h" +#include "cppgc/member.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/source-location.h" +#include "cppgc/trace-trait.h" +#include "cppgc/type-traits.h" + +namespace cppgc { + +namespace internal { +template +class BasicCrossThreadPersistent; +template +class BasicPersistent; +class ConservativeTracingVisitor; +class VisitorBase; +class VisitorFactory; +} // namespace internal + +using WeakCallback = void (*)(const LivenessBroker&, const void*); + +/** + * An ephemeron pair is used to conditionally retain an object. + * The `value` will be kept alive only if the `key` is alive. + */ +template +struct EphemeronPair { + CPPGC_DISALLOW_NEW(); + + EphemeronPair(K* k, V* v) : key(k), value(v) {} + WeakMember key; + Member value; + + void ClearValueIfKeyIsDead(const LivenessBroker& broker) { + if (!broker.IsHeapObjectAlive(key)) value = nullptr; + } + + void Trace(Visitor* visitor) const; +}; + +/** + * Visitor passed to trace methods. All managed pointers must have called the + * Visitor's trace method on them. + * + * \code + * class Foo final : public GarbageCollected { + * public: + * void Trace(Visitor* visitor) const { + * visitor->Trace(foo_); + * visitor->Trace(weak_foo_); + * } + * private: + * Member foo_; + * WeakMember weak_foo_; + * }; + * \endcode + */ +class V8_EXPORT Visitor { + public: + class Key { + private: + Key() = default; + friend class internal::VisitorFactory; + }; + + explicit Visitor(Key) {} + + virtual ~Visitor() = default; + + /** + * Trace method for Member. + * + * \param member Member reference retaining an object. + */ + template + void Trace(const Member& member) { + const T* value = member.GetRawAtomic(); + CPPGC_DCHECK(value != kSentinelPointer); + TraceImpl(value); + } + + /** + * Trace method for WeakMember. + * + * \param weak_member WeakMember reference weakly retaining an object. + */ + template + void Trace(const WeakMember& weak_member) { + static_assert(sizeof(T), "Pointee type must be fully defined."); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "T must be GarbageCollected or GarbageCollectedMixin type"); + static_assert(!internal::IsAllocatedOnCompactableSpace::value, + "Weak references to compactable objects are not allowed"); + + const T* value = weak_member.GetRawAtomic(); + + // Bailout assumes that WeakMember emits write barrier. + if (!value) { + return; + } + + CPPGC_DCHECK(value != kSentinelPointer); + VisitWeak(value, TraceTrait::GetTraceDescriptor(value), + &HandleWeak>, &weak_member); + } + +#if defined(CPPGC_POINTER_COMPRESSION) + /** + * Trace method for UncompressedMember. + * + * \param member UncompressedMember reference retaining an object. + */ + template + void Trace(const subtle::UncompressedMember& member) { + const T* value = member.GetRawAtomic(); + CPPGC_DCHECK(value != kSentinelPointer); + TraceImpl(value); + } +#endif // defined(CPPGC_POINTER_COMPRESSION) + + template + void TraceMultiple(const subtle::UncompressedMember* start, size_t len) { + static_assert(sizeof(T), "Pointee type must be fully defined."); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "T must be GarbageCollected or GarbageCollectedMixin type"); + VisitMultipleUncompressedMember(start, len, + &TraceTrait::GetTraceDescriptor); + } + + template , subtle::UncompressedMember>>* = nullptr> + void TraceMultiple(const Member* start, size_t len) { + static_assert(sizeof(T), "Pointee type must be fully defined."); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "T must be GarbageCollected or GarbageCollectedMixin type"); +#if defined(CPPGC_POINTER_COMPRESSION) + static_assert(std::is_same_v, subtle::CompressedMember>, + "Member and CompressedMember must be the same."); + VisitMultipleCompressedMember(start, len, + &TraceTrait::GetTraceDescriptor); +#endif // defined(CPPGC_POINTER_COMPRESSION) + } + + /** + * Trace method for inlined objects that are not allocated themselves but + * otherwise follow managed heap layout and have a Trace() method. + * + * \param object reference of the inlined object. + */ + template + void Trace(const T& object) { +#if V8_ENABLE_CHECKS + // This object is embedded in potentially multiple nested objects. The + // outermost object must not be in construction as such objects are (a) not + // processed immediately, and (b) only processed conservatively if not + // otherwise possible. + CheckObjectNotInConstruction(&object); +#endif // V8_ENABLE_CHECKS + TraceTrait::Trace(this, &object); + } + + template + void TraceMultiple(const T* start, size_t len) { +#if V8_ENABLE_CHECKS + // This object is embedded in potentially multiple nested objects. The + // outermost object must not be in construction as such objects are (a) not + // processed immediately, and (b) only processed conservatively if not + // otherwise possible. + CheckObjectNotInConstruction(start); +#endif // V8_ENABLE_CHECKS + for (size_t i = 0; i < len; ++i) { + const T* object = &start[i]; + if constexpr (std::is_polymorphic_v) { + // The object's vtable may be uninitialized in which case the object is + // not traced. + if (*reinterpret_cast(object) == 0) continue; + } + TraceTrait::Trace(this, object); + } + } + + /** + * Registers a weak callback method on the object of type T. See + * LivenessBroker for an usage example. + * + * \param object of type T specifying a weak callback method. + */ + template + void RegisterWeakCallbackMethod(const T* object) { + RegisterWeakCallback(&WeakCallbackMethodDelegate, object); + } + + /** + * Trace method for EphemeronPair. + * + * \param ephemeron_pair EphemeronPair reference weakly retaining a key object + * and strongly retaining a value object in case the key object is alive. + */ + template + void Trace(const EphemeronPair& ephemeron_pair) { + TraceEphemeron(ephemeron_pair.key, &ephemeron_pair.value); + RegisterWeakCallbackMethod, + &EphemeronPair::ClearValueIfKeyIsDead>( + &ephemeron_pair); + } + + /** + * Trace method for a single ephemeron. Used for tracing a raw ephemeron in + * which the `key` and `value` are kept separately. + * + * \param weak_member_key WeakMember reference weakly retaining a key object. + * \param member_value Member reference with ephemeron semantics. + */ + template + void TraceEphemeron(const WeakMember& weak_member_key, + const Member* member_value) { + const KeyType* key = weak_member_key.GetRawAtomic(); + if (!key) return; + + // `value` must always be non-null. + CPPGC_DCHECK(member_value); + const ValueType* value = member_value->GetRawAtomic(); + if (!value) return; + + // KeyType and ValueType may refer to GarbageCollectedMixin. + TraceDescriptor value_desc = + TraceTrait::GetTraceDescriptor(value); + CPPGC_DCHECK(value_desc.base_object_payload); + const void* key_base_object_payload = + TraceTrait::GetTraceDescriptor(key).base_object_payload; + CPPGC_DCHECK(key_base_object_payload); + + VisitEphemeron(key_base_object_payload, value, value_desc); + } + + /** + * Trace method for a single ephemeron. Used for tracing a raw ephemeron in + * which the `key` and `value` are kept separately. Note that this overload + * is for non-GarbageCollected `value`s that can be traced though. + * + * \param key `WeakMember` reference weakly retaining a key object. + * \param value Reference weakly retaining a value object. Note that + * `ValueType` here should not be `Member`. It is expected that + * `TraceTrait::GetTraceDescriptor(value)` returns a + * `TraceDescriptor` with a null base pointer but a valid trace method. + */ + template + void TraceEphemeron(const WeakMember& weak_member_key, + const ValueType* value) { + static_assert(!IsGarbageCollectedOrMixinTypeV, + "garbage-collected types must use WeakMember and Member"); + const KeyType* key = weak_member_key.GetRawAtomic(); + if (!key) return; + + // `value` must always be non-null. + CPPGC_DCHECK(value); + TraceDescriptor value_desc = + TraceTrait::GetTraceDescriptor(value); + // `value_desc.base_object_payload` must be null as this override is only + // taken for non-garbage-collected values. + CPPGC_DCHECK(!value_desc.base_object_payload); + + // KeyType might be a GarbageCollectedMixin. + const void* key_base_object_payload = + TraceTrait::GetTraceDescriptor(key).base_object_payload; + CPPGC_DCHECK(key_base_object_payload); + + VisitEphemeron(key_base_object_payload, value, value_desc); + } + + /** + * Trace method that strongifies a WeakMember. + * + * \param weak_member WeakMember reference retaining an object. + */ + template + void TraceStrongly(const WeakMember& weak_member) { + const T* value = weak_member.GetRawAtomic(); + CPPGC_DCHECK(value != kSentinelPointer); + TraceImpl(value); + } + + /** + * Trace method for retaining containers strongly. + * + * \param object reference to the container. + */ + template + void TraceStrongContainer(const T* object) { + TraceImpl(object); + } + + /** + * Trace method for retaining containers weakly. Note that weak containers + * should emit write barriers. + * + * \param object reference to the container. + * \param callback to be invoked. + * \param callback_data custom data that is passed to the callback. + */ + template + void TraceWeakContainer(const T* object, WeakCallback callback, + const void* callback_data) { + if (!object) return; + VisitWeakContainer(object, TraceTrait::GetTraceDescriptor(object), + TraceTrait::GetWeakTraceDescriptor(object), callback, + callback_data); + } + + /** + * Registers a slot containing a reference to an object allocated on a + * compactable space. Such references maybe be arbitrarily moved by the GC. + * + * \param slot location of reference to object that might be moved by the GC. + * The slot must contain an uncompressed pointer. + */ + template + void RegisterMovableReference(const T** slot) { + static_assert(internal::IsAllocatedOnCompactableSpace::value, + "Only references to objects allocated on compactable spaces " + "should be registered as movable slots."); + static_assert(!IsGarbageCollectedMixinTypeV, + "Mixin types do not support compaction."); + HandleMovableReference(reinterpret_cast(slot)); + } + + /** + * Registers a weak callback that is invoked during garbage collection. + * + * \param callback to be invoked. + * \param data custom data that is passed to the callback. + */ + virtual void RegisterWeakCallback(WeakCallback callback, const void* data) {} + + /** + * Defers tracing an object from a concurrent thread to the mutator thread. + * Should be called by Trace methods of types that are not safe to trace + * concurrently. + * + * \param parameter tells the trace callback which object was deferred. + * \param callback to be invoked for tracing on the mutator thread. + * \param deferred_size size of deferred object. + * + * \returns false if the object does not need to be deferred (i.e. currently + * traced on the mutator thread) and true otherwise (i.e. currently traced on + * a concurrent thread). + */ + virtual V8_WARN_UNUSED_RESULT bool DeferTraceToMutatorThreadIfConcurrent( + const void* parameter, TraceCallback callback, size_t deferred_size) { + // By default tracing is not deferred. + return false; + } + + protected: + virtual void Visit(const void* self, TraceDescriptor) {} + virtual void VisitWeak(const void* self, TraceDescriptor, WeakCallback, + const void* weak_member) {} + virtual void VisitEphemeron(const void* key, const void* value, + TraceDescriptor value_desc) {} + virtual void VisitWeakContainer(const void* self, TraceDescriptor strong_desc, + TraceDescriptor weak_desc, + WeakCallback callback, const void* data) {} + virtual void HandleMovableReference(const void**) {} + + virtual void VisitMultipleUncompressedMember( + const void* start, size_t len, + TraceDescriptorCallback get_trace_descriptor) { + // Default implementation merely delegates to Visit(). + const char* it = static_cast(start); + const char* end = it + len * internal::kSizeOfUncompressedMember; + for (; it < end; it += internal::kSizeOfUncompressedMember) { + const auto* current = reinterpret_cast(it); + const void* object = current->LoadAtomic(); + if (!object) continue; + + Visit(object, get_trace_descriptor(object)); + } + } + +#if defined(CPPGC_POINTER_COMPRESSION) + virtual void VisitMultipleCompressedMember( + const void* start, size_t len, + TraceDescriptorCallback get_trace_descriptor) { + // Default implementation merely delegates to Visit(). + const char* it = static_cast(start); + const char* end = it + len * internal::kSizeofCompressedMember; + for (; it < end; it += internal::kSizeofCompressedMember) { + const auto* current = + reinterpret_cast(it); + const void* object = current->LoadAtomic(); + if (!object) continue; + + Visit(object, get_trace_descriptor(object)); + } + } +#endif // defined(CPPGC_POINTER_COMPRESSION) + + private: + template + static void WeakCallbackMethodDelegate(const LivenessBroker& info, + const void* self) { + // Callback is registered through a potential const Trace method but needs + // to be able to modify fields. See HandleWeak. + (const_cast(static_cast(self))->*method)(info); + } + + template + static void HandleWeak(const LivenessBroker& info, const void* object) { + const PointerType* weak = static_cast(object); + if (!info.IsHeapObjectAlive(weak->GetFromGC())) { + weak->ClearFromGC(); + } + } + + template + void TraceImpl(const T* t) { + static_assert(sizeof(T), "Pointee type must be fully defined."); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "T must be GarbageCollected or GarbageCollectedMixin type"); + if (!t) { + return; + } + Visit(t, TraceTrait::GetTraceDescriptor(t)); + } + +#if V8_ENABLE_CHECKS + void CheckObjectNotInConstruction(const void* address); +#endif // V8_ENABLE_CHECKS + + template + friend class internal::BasicCrossThreadPersistent; + template + friend class internal::BasicPersistent; + friend class internal::ConservativeTracingVisitor; + friend class internal::VisitorBase; +}; + +template +void EphemeronPair::Trace(Visitor* visitor) const { + visitor->TraceEphemeron(key, value); +} + +namespace internal { + +class V8_EXPORT RootVisitor { + public: + explicit RootVisitor(Visitor::Key) {} + + virtual ~RootVisitor() = default; + + template * = nullptr> + void Trace(const AnyStrongPersistentType& p) { + using PointeeType = typename AnyStrongPersistentType::PointeeType; + const void* object = Extract(p); + if (!object) { + return; + } + VisitRoot(object, TraceTrait::GetTraceDescriptor(object), + p.Location()); + } + + template * = nullptr> + void Trace(const AnyWeakPersistentType& p) { + using PointeeType = typename AnyWeakPersistentType::PointeeType; + static_assert(!internal::IsAllocatedOnCompactableSpace::value, + "Weak references to compactable objects are not allowed"); + const void* object = Extract(p); + if (!object) { + return; + } + VisitWeakRoot(object, TraceTrait::GetTraceDescriptor(object), + &HandleWeak, &p, p.Location()); + } + + protected: + virtual void VisitRoot(const void*, TraceDescriptor, const SourceLocation&) {} + virtual void VisitWeakRoot(const void* self, TraceDescriptor, WeakCallback, + const void* weak_root, const SourceLocation&) {} + + private: + template + static const void* Extract(AnyPersistentType& p) { + using PointeeType = typename AnyPersistentType::PointeeType; + static_assert(sizeof(PointeeType), + "Persistent's pointee type must be fully defined"); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "Persistent's pointee type must be GarbageCollected or " + "GarbageCollectedMixin"); + return p.GetFromGC(); + } + + template + static void HandleWeak(const LivenessBroker& info, const void* object) { + const PointerType* weak = static_cast(object); + if (!info.IsHeapObjectAlive(weak->GetFromGC())) { + weak->ClearFromGC(); + } + } +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_VISITOR_H_ diff --git a/NativeScript/napi/android/v8-13/include/inspector/Debugger.h b/NativeScript/napi/android/v8-13/include/inspector/Debugger.h new file mode 100644 index 000000000..e4ba9839f --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/inspector/Debugger.h @@ -0,0 +1,60 @@ +// This file is generated by Exported_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Debugger_api_h +#define v8_inspector_protocol_Debugger_api_h + +#include "v8-inspector.h" + +namespace v8_inspector { +namespace protocol { + +#ifndef v8_inspector_protocol_exported_api_h +#define v8_inspector_protocol_exported_api_h +class V8_EXPORT Exported { +public: + virtual void AppendSerialized(std::vector* out) const = 0; + + virtual ~Exported() { } +}; +#endif // !defined(v8_inspector_protocol_exported_api_h) + +namespace Debugger { +namespace API { + +// ------------- Enums. + +namespace Paused { +namespace ReasonEnum { +V8_EXPORT extern const char* Ambiguous; +V8_EXPORT extern const char* Assert; +V8_EXPORT extern const char* CSPViolation; +V8_EXPORT extern const char* DebugCommand; +V8_EXPORT extern const char* DOM; +V8_EXPORT extern const char* EventListener; +V8_EXPORT extern const char* Exception; +V8_EXPORT extern const char* Instrumentation; +V8_EXPORT extern const char* OOM; +V8_EXPORT extern const char* Other; +V8_EXPORT extern const char* PromiseRejection; +V8_EXPORT extern const char* XHR; +V8_EXPORT extern const char* Step; +} // ReasonEnum +} // Paused + +// ------------- Types. + +class V8_EXPORT SearchMatch : public Exported { +public: + static std::unique_ptr fromBinary(const uint8_t* data, size_t length); +}; + +} // namespace API +} // namespace Debugger +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Debugger_api_h) diff --git a/NativeScript/napi/android/v8-13/include/inspector/Runtime.h b/NativeScript/napi/android/v8-13/include/inspector/Runtime.h new file mode 100644 index 000000000..86471c2ea --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/inspector/Runtime.h @@ -0,0 +1,52 @@ +// This file is generated by Exported_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Runtime_api_h +#define v8_inspector_protocol_Runtime_api_h + +#include "v8-inspector.h" + +namespace v8_inspector { +namespace protocol { + +#ifndef v8_inspector_protocol_exported_api_h +#define v8_inspector_protocol_exported_api_h +class V8_EXPORT Exported { +public: + virtual void AppendSerialized(std::vector* out) const = 0; + + virtual ~Exported() { } +}; +#endif // !defined(v8_inspector_protocol_exported_api_h) + +namespace Runtime { +namespace API { + +// ------------- Enums. + +// ------------- Types. + +class V8_EXPORT RemoteObject : public Exported { +public: + static std::unique_ptr fromBinary(const uint8_t* data, size_t length); +}; + +class V8_EXPORT StackTrace : public Exported { +public: + static std::unique_ptr fromBinary(const uint8_t* data, size_t length); +}; + +class V8_EXPORT StackTraceId : public Exported { +public: + static std::unique_ptr fromBinary(const uint8_t* data, size_t length); +}; + +} // namespace API +} // namespace Runtime +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Runtime_api_h) diff --git a/NativeScript/napi/android/v8-13/include/inspector/Schema.h b/NativeScript/napi/android/v8-13/include/inspector/Schema.h new file mode 100644 index 000000000..ce23732ee --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/inspector/Schema.h @@ -0,0 +1,42 @@ +// This file is generated by Exported_h.template. + +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef v8_inspector_protocol_Schema_api_h +#define v8_inspector_protocol_Schema_api_h + +#include "v8-inspector.h" + +namespace v8_inspector { +namespace protocol { + +#ifndef v8_inspector_protocol_exported_api_h +#define v8_inspector_protocol_exported_api_h +class V8_EXPORT Exported { +public: + virtual void AppendSerialized(std::vector* out) const = 0; + + virtual ~Exported() { } +}; +#endif // !defined(v8_inspector_protocol_exported_api_h) + +namespace Schema { +namespace API { + +// ------------- Enums. + +// ------------- Types. + +class V8_EXPORT Domain : public Exported { +public: + static std::unique_ptr fromBinary(const uint8_t* data, size_t length); +}; + +} // namespace API +} // namespace Schema +} // namespace v8_inspector +} // namespace protocol + +#endif // !defined(v8_inspector_protocol_Schema_api_h) diff --git a/NativeScript/napi/android/v8-13/include/js_protocol-1.2.json b/NativeScript/napi/android/v8-13/include/js_protocol-1.2.json new file mode 100644 index 000000000..aff680622 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/js_protocol-1.2.json @@ -0,0 +1,997 @@ +{ + "version": { "major": "1", "minor": "2" }, + "domains": [ + { + "domain": "Schema", + "description": "Provides information about the protocol schema.", + "types": [ + { + "id": "Domain", + "type": "object", + "description": "Description of the protocol domain.", + "exported": true, + "properties": [ + { "name": "name", "type": "string", "description": "Domain name." }, + { "name": "version", "type": "string", "description": "Domain version." } + ] + } + ], + "commands": [ + { + "name": "getDomains", + "description": "Returns supported domains.", + "handlers": ["browser", "renderer"], + "returns": [ + { "name": "domains", "type": "array", "items": { "$ref": "Domain" }, "description": "List of supported domains." } + ] + } + ] + }, + { + "domain": "Runtime", + "description": "Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects. Evaluation results are returned as mirror object that expose object type, string representation and unique identifier that can be used for further object reference. Original objects are maintained in memory unless they are either explicitly released or are released along with the other objects in their object group.", + "types": [ + { + "id": "ScriptId", + "type": "string", + "description": "Unique script identifier." + }, + { + "id": "RemoteObjectId", + "type": "string", + "description": "Unique object identifier." + }, + { + "id": "UnserializableValue", + "type": "string", + "enum": ["Infinity", "NaN", "-Infinity", "-0"], + "description": "Primitive value which cannot be JSON-stringified." + }, + { + "id": "RemoteObject", + "type": "object", + "description": "Mirror object referencing original JavaScript object.", + "exported": true, + "properties": [ + { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "symbol"], "description": "Object type." }, + { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "map", "set", "iterator", "generator", "error", "proxy", "promise", "typedarray"], "description": "Object subtype hint. Specified for object type values only." }, + { "name": "className", "type": "string", "optional": true, "description": "Object class (constructor) name. Specified for object type values only." }, + { "name": "value", "type": "any", "optional": true, "description": "Remote object value in case of primitive values or JSON values (if it was requested)." }, + { "name": "unserializableValue", "$ref": "UnserializableValue", "optional": true, "description": "Primitive value which can not be JSON-stringified does not have value, but gets this property." }, + { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." }, + { "name": "objectId", "$ref": "RemoteObjectId", "optional": true, "description": "Unique object identifier (for non-primitive values)." }, + { "name": "preview", "$ref": "ObjectPreview", "optional": true, "description": "Preview containing abbreviated property values. Specified for object type values only.", "experimental": true }, + { "name": "customPreview", "$ref": "CustomPreview", "optional": true, "experimental": true} + ] + }, + { + "id": "CustomPreview", + "type": "object", + "experimental": true, + "properties": [ + { "name": "header", "type": "string"}, + { "name": "hasBody", "type": "boolean"}, + { "name": "formatterObjectId", "$ref": "RemoteObjectId"}, + { "name": "bindRemoteObjectFunctionId", "$ref": "RemoteObjectId" }, + { "name": "configObjectId", "$ref": "RemoteObjectId", "optional": true } + ] + }, + { + "id": "ObjectPreview", + "type": "object", + "experimental": true, + "description": "Object containing abbreviated remote object value.", + "properties": [ + { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "symbol"], "description": "Object type." }, + { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "map", "set", "iterator", "generator", "error"], "description": "Object subtype hint. Specified for object type values only." }, + { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." }, + { "name": "overflow", "type": "boolean", "description": "True iff some of the properties or entries of the original object did not fit." }, + { "name": "properties", "type": "array", "items": { "$ref": "PropertyPreview" }, "description": "List of the properties." }, + { "name": "entries", "type": "array", "items": { "$ref": "EntryPreview" }, "optional": true, "description": "List of the entries. Specified for map and set subtype values only." } + ] + }, + { + "id": "PropertyPreview", + "type": "object", + "experimental": true, + "properties": [ + { "name": "name", "type": "string", "description": "Property name." }, + { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "symbol", "accessor"], "description": "Object type. Accessor means that the property itself is an accessor property." }, + { "name": "value", "type": "string", "optional": true, "description": "User-friendly property value string." }, + { "name": "valuePreview", "$ref": "ObjectPreview", "optional": true, "description": "Nested value preview." }, + { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "map", "set", "iterator", "generator", "error"], "description": "Object subtype hint. Specified for object type values only." } + ] + }, + { + "id": "EntryPreview", + "type": "object", + "experimental": true, + "properties": [ + { "name": "key", "$ref": "ObjectPreview", "optional": true, "description": "Preview of the key. Specified for map-like collection entries." }, + { "name": "value", "$ref": "ObjectPreview", "description": "Preview of the value." } + ] + }, + { + "id": "PropertyDescriptor", + "type": "object", + "description": "Object property descriptor.", + "properties": [ + { "name": "name", "type": "string", "description": "Property name or symbol description." }, + { "name": "value", "$ref": "RemoteObject", "optional": true, "description": "The value associated with the property." }, + { "name": "writable", "type": "boolean", "optional": true, "description": "True if the value associated with the property may be changed (data descriptors only)." }, + { "name": "get", "$ref": "RemoteObject", "optional": true, "description": "A function which serves as a getter for the property, or undefined if there is no getter (accessor descriptors only)." }, + { "name": "set", "$ref": "RemoteObject", "optional": true, "description": "A function which serves as a setter for the property, or undefined if there is no setter (accessor descriptors only)." }, + { "name": "configurable", "type": "boolean", "description": "True if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object." }, + { "name": "enumerable", "type": "boolean", "description": "True if this property shows up during enumeration of the properties on the corresponding object." }, + { "name": "wasThrown", "type": "boolean", "optional": true, "description": "True if the result was thrown during the evaluation." }, + { "name": "isOwn", "optional": true, "type": "boolean", "description": "True if the property is owned for the object." }, + { "name": "symbol", "$ref": "RemoteObject", "optional": true, "description": "Property symbol object, if the property is of the symbol type." } + ] + }, + { + "id": "InternalPropertyDescriptor", + "type": "object", + "description": "Object internal property descriptor. This property isn't normally visible in JavaScript code.", + "properties": [ + { "name": "name", "type": "string", "description": "Conventional property name." }, + { "name": "value", "$ref": "RemoteObject", "optional": true, "description": "The value associated with the property." } + ] + }, + { + "id": "CallArgument", + "type": "object", + "description": "Represents function call argument. Either remote object id objectId, primitive value, unserializable primitive value or neither of (for undefined) them should be specified.", + "properties": [ + { "name": "value", "type": "any", "optional": true, "description": "Primitive value." }, + { "name": "unserializableValue", "$ref": "UnserializableValue", "optional": true, "description": "Primitive value which can not be JSON-stringified." }, + { "name": "objectId", "$ref": "RemoteObjectId", "optional": true, "description": "Remote object handle." } + ] + }, + { + "id": "ExecutionContextId", + "type": "integer", + "description": "Id of an execution context." + }, + { + "id": "ExecutionContextDescription", + "type": "object", + "description": "Description of an isolated world.", + "properties": [ + { "name": "id", "$ref": "ExecutionContextId", "description": "Unique id of the execution context. It can be used to specify in which execution context script evaluation should be performed." }, + { "name": "origin", "type": "string", "description": "Execution context origin." }, + { "name": "name", "type": "string", "description": "Human readable name describing given context." }, + { "name": "auxData", "type": "object", "optional": true, "description": "Embedder-specific auxiliary data." } + ] + }, + { + "id": "ExceptionDetails", + "type": "object", + "description": "Detailed information about exception (or error) that was thrown during script compilation or execution.", + "properties": [ + { "name": "exceptionId", "type": "integer", "description": "Exception id." }, + { "name": "text", "type": "string", "description": "Exception text, which should be used together with exception object when available." }, + { "name": "lineNumber", "type": "integer", "description": "Line number of the exception location (0-based)." }, + { "name": "columnNumber", "type": "integer", "description": "Column number of the exception location (0-based)." }, + { "name": "scriptId", "$ref": "ScriptId", "optional": true, "description": "Script ID of the exception location." }, + { "name": "url", "type": "string", "optional": true, "description": "URL of the exception location, to be used when the script was not reported." }, + { "name": "stackTrace", "$ref": "StackTrace", "optional": true, "description": "JavaScript stack trace if available." }, + { "name": "exception", "$ref": "RemoteObject", "optional": true, "description": "Exception object if available." }, + { "name": "executionContextId", "$ref": "ExecutionContextId", "optional": true, "description": "Identifier of the context where exception happened." } + ] + }, + { + "id": "Timestamp", + "type": "number", + "description": "Number of milliseconds since epoch." + }, + { + "id": "CallFrame", + "type": "object", + "description": "Stack entry for runtime errors and assertions.", + "properties": [ + { "name": "functionName", "type": "string", "description": "JavaScript function name." }, + { "name": "scriptId", "$ref": "ScriptId", "description": "JavaScript script id." }, + { "name": "url", "type": "string", "description": "JavaScript script name or url." }, + { "name": "lineNumber", "type": "integer", "description": "JavaScript script line number (0-based)." }, + { "name": "columnNumber", "type": "integer", "description": "JavaScript script column number (0-based)." } + ] + }, + { + "id": "StackTrace", + "type": "object", + "description": "Call frames for assertions or error messages.", + "exported": true, + "properties": [ + { "name": "description", "type": "string", "optional": true, "description": "String label of this stack trace. For async traces this may be a name of the function that initiated the async call." }, + { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "JavaScript function name." }, + { "name": "parent", "$ref": "StackTrace", "optional": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." } + ] + } + ], + "commands": [ + { + "name": "evaluate", + "async": true, + "parameters": [ + { "name": "expression", "type": "string", "description": "Expression to evaluate." }, + { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." }, + { "name": "includeCommandLineAPI", "type": "boolean", "optional": true, "description": "Determines whether Command Line API should be available during the evaluation." }, + { "name": "silent", "type": "boolean", "optional": true, "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state." }, + { "name": "contextId", "$ref": "ExecutionContextId", "optional": true, "description": "Specifies in which execution context to perform evaluation. If the parameter is omitted the evaluation will be performed in the context of the inspected page." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object that should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "experimental": true, "description": "Whether preview should be generated for the result." }, + { "name": "userGesture", "type": "boolean", "optional": true, "experimental": true, "description": "Whether execution should be treated as initiated by user in the UI." }, + { "name": "awaitPromise", "type": "boolean", "optional":true, "description": "Whether execution should wait for promise to be resolved. If the result of evaluation is not a Promise, it's considered to be an error." } + ], + "returns": [ + { "name": "result", "$ref": "RemoteObject", "description": "Evaluation result." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Evaluates expression on global object." + }, + { + "name": "awaitPromise", + "async": true, + "parameters": [ + { "name": "promiseObjectId", "$ref": "RemoteObjectId", "description": "Identifier of the promise." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object that should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "description": "Whether preview should be generated for the result." } + ], + "returns": [ + { "name": "result", "$ref": "RemoteObject", "description": "Promise result. Will contain rejected value if promise was rejected." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details if stack strace is available."} + ], + "description": "Add handler to promise with given promise object id." + }, + { + "name": "callFunctionOn", + "async": true, + "parameters": [ + { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to call function on." }, + { "name": "functionDeclaration", "type": "string", "description": "Declaration of the function to call." }, + { "name": "arguments", "type": "array", "items": { "$ref": "CallArgument", "description": "Call argument." }, "optional": true, "description": "Call arguments. All call arguments must belong to the same JavaScript world as the target object." }, + { "name": "silent", "type": "boolean", "optional": true, "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object which should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "experimental": true, "description": "Whether preview should be generated for the result." }, + { "name": "userGesture", "type": "boolean", "optional": true, "experimental": true, "description": "Whether execution should be treated as initiated by user in the UI." }, + { "name": "awaitPromise", "type": "boolean", "optional":true, "description": "Whether execution should wait for promise to be resolved. If the result of evaluation is not a Promise, it's considered to be an error." } + ], + "returns": [ + { "name": "result", "$ref": "RemoteObject", "description": "Call result." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Calls function with given declaration on the given object. Object group of the result is inherited from the target object." + }, + { + "name": "getProperties", + "parameters": [ + { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to return properties for." }, + { "name": "ownProperties", "optional": true, "type": "boolean", "description": "If true, returns properties belonging only to the element itself, not to its prototype chain." }, + { "name": "accessorPropertiesOnly", "optional": true, "type": "boolean", "description": "If true, returns accessor properties (with getter/setter) only; internal properties are not returned either.", "experimental": true }, + { "name": "generatePreview", "type": "boolean", "optional": true, "experimental": true, "description": "Whether preview should be generated for the results." } + ], + "returns": [ + { "name": "result", "type": "array", "items": { "$ref": "PropertyDescriptor" }, "description": "Object properties." }, + { "name": "internalProperties", "optional": true, "type": "array", "items": { "$ref": "InternalPropertyDescriptor" }, "description": "Internal object properties (only of the element itself)." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Returns properties of a given object. Object group of the result is inherited from the target object." + }, + { + "name": "releaseObject", + "parameters": [ + { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to release." } + ], + "description": "Releases remote object with given id." + }, + { + "name": "releaseObjectGroup", + "parameters": [ + { "name": "objectGroup", "type": "string", "description": "Symbolic object group name." } + ], + "description": "Releases all remote objects that belong to a given group." + }, + { + "name": "runIfWaitingForDebugger", + "description": "Tells inspected instance to run if it was waiting for debugger to attach." + }, + { + "name": "enable", + "description": "Enables reporting of execution contexts creation by means of executionContextCreated event. When the reporting gets enabled the event will be sent immediately for each existing execution context." + }, + { + "name": "disable", + "description": "Disables reporting of execution contexts creation." + }, + { + "name": "discardConsoleEntries", + "description": "Discards collected exceptions and console API calls." + }, + { + "name": "setCustomObjectFormatterEnabled", + "parameters": [ + { + "name": "enabled", + "type": "boolean" + } + ], + "experimental": true + }, + { + "name": "compileScript", + "parameters": [ + { "name": "expression", "type": "string", "description": "Expression to compile." }, + { "name": "sourceURL", "type": "string", "description": "Source url to be set for the script." }, + { "name": "persistScript", "type": "boolean", "description": "Specifies whether the compiled script should be persisted." }, + { "name": "executionContextId", "$ref": "ExecutionContextId", "optional": true, "description": "Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page." } + ], + "returns": [ + { "name": "scriptId", "$ref": "ScriptId", "optional": true, "description": "Id of the script." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Compiles expression." + }, + { + "name": "runScript", + "async": true, + "parameters": [ + { "name": "scriptId", "$ref": "ScriptId", "description": "Id of the script to run." }, + { "name": "executionContextId", "$ref": "ExecutionContextId", "optional": true, "description": "Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page." }, + { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." }, + { "name": "silent", "type": "boolean", "optional": true, "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state." }, + { "name": "includeCommandLineAPI", "type": "boolean", "optional": true, "description": "Determines whether Command Line API should be available during the evaluation." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object which should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "description": "Whether preview should be generated for the result." }, + { "name": "awaitPromise", "type": "boolean", "optional": true, "description": "Whether execution should wait for promise to be resolved. If the result of evaluation is not a Promise, it's considered to be an error." } + ], + "returns": [ + { "name": "result", "$ref": "RemoteObject", "description": "Run result." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Runs script with given id in a given context." + } + ], + "events": [ + { + "name": "executionContextCreated", + "parameters": [ + { "name": "context", "$ref": "ExecutionContextDescription", "description": "A newly created execution contex." } + ], + "description": "Issued when new execution context is created." + }, + { + "name": "executionContextDestroyed", + "parameters": [ + { "name": "executionContextId", "$ref": "ExecutionContextId", "description": "Id of the destroyed context" } + ], + "description": "Issued when execution context is destroyed." + }, + { + "name": "executionContextsCleared", + "description": "Issued when all executionContexts were cleared in browser" + }, + { + "name": "exceptionThrown", + "description": "Issued when exception was thrown and unhandled.", + "parameters": [ + { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp of the exception." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails" } + ] + }, + { + "name": "exceptionRevoked", + "description": "Issued when unhandled exception was revoked.", + "parameters": [ + { "name": "reason", "type": "string", "description": "Reason describing why exception was revoked." }, + { "name": "exceptionId", "type": "integer", "description": "The id of revoked exception, as reported in exceptionUnhandled." } + ] + }, + { + "name": "consoleAPICalled", + "description": "Issued when console API was called.", + "parameters": [ + { "name": "type", "type": "string", "enum": ["log", "debug", "info", "error", "warning", "dir", "dirxml", "table", "trace", "clear", "startGroup", "startGroupCollapsed", "endGroup", "assert", "profile", "profileEnd"], "description": "Type of the call." }, + { "name": "args", "type": "array", "items": { "$ref": "RemoteObject" }, "description": "Call arguments." }, + { "name": "executionContextId", "$ref": "ExecutionContextId", "description": "Identifier of the context where the call was made." }, + { "name": "timestamp", "$ref": "Timestamp", "description": "Call timestamp." }, + { "name": "stackTrace", "$ref": "StackTrace", "optional": true, "description": "Stack trace captured when the call was made." } + ] + }, + { + "name": "inspectRequested", + "description": "Issued when object should be inspected (for example, as a result of inspect() command line API call).", + "parameters": [ + { "name": "object", "$ref": "RemoteObject" }, + { "name": "hints", "type": "object" } + ] + } + ] + }, + { + "domain": "Debugger", + "description": "Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing breakpoints, stepping through execution, exploring stack traces, etc.", + "dependencies": ["Runtime"], + "types": [ + { + "id": "BreakpointId", + "type": "string", + "description": "Breakpoint identifier." + }, + { + "id": "CallFrameId", + "type": "string", + "description": "Call frame identifier." + }, + { + "id": "Location", + "type": "object", + "properties": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Script identifier as reported in the Debugger.scriptParsed." }, + { "name": "lineNumber", "type": "integer", "description": "Line number in the script (0-based)." }, + { "name": "columnNumber", "type": "integer", "optional": true, "description": "Column number in the script (0-based)." } + ], + "description": "Location in the source code." + }, + { + "id": "ScriptPosition", + "experimental": true, + "type": "object", + "properties": [ + { "name": "lineNumber", "type": "integer" }, + { "name": "columnNumber", "type": "integer" } + ], + "description": "Location in the source code." + }, + { + "id": "CallFrame", + "type": "object", + "properties": [ + { "name": "callFrameId", "$ref": "CallFrameId", "description": "Call frame identifier. This identifier is only valid while the virtual machine is paused." }, + { "name": "functionName", "type": "string", "description": "Name of the JavaScript function called on this call frame." }, + { "name": "functionLocation", "$ref": "Location", "optional": true, "experimental": true, "description": "Location in the source code." }, + { "name": "location", "$ref": "Location", "description": "Location in the source code." }, + { "name": "scopeChain", "type": "array", "items": { "$ref": "Scope" }, "description": "Scope chain for this call frame." }, + { "name": "this", "$ref": "Runtime.RemoteObject", "description": "this object for this call frame." }, + { "name": "returnValue", "$ref": "Runtime.RemoteObject", "optional": true, "description": "The value being returned, if the function is at return point." } + ], + "description": "JavaScript call frame. Array of call frames form the call stack." + }, + { + "id": "Scope", + "type": "object", + "properties": [ + { "name": "type", "type": "string", "enum": ["global", "local", "with", "closure", "catch", "block", "script"], "description": "Scope type." }, + { "name": "object", "$ref": "Runtime.RemoteObject", "description": "Object representing the scope. For global and with scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties." }, + { "name": "name", "type": "string", "optional": true }, + { "name": "startLocation", "$ref": "Location", "optional": true, "description": "Location in the source code where scope starts" }, + { "name": "endLocation", "$ref": "Location", "optional": true, "description": "Location in the source code where scope ends" } + ], + "description": "Scope description." + }, + { + "id": "SearchMatch", + "type": "object", + "description": "Search match for resource.", + "exported": true, + "properties": [ + { "name": "lineNumber", "type": "number", "description": "Line number in resource content." }, + { "name": "lineContent", "type": "string", "description": "Line with match content." } + ], + "experimental": true + } + ], + "commands": [ + { + "name": "enable", + "description": "Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received." + }, + { + "name": "disable", + "description": "Disables debugger for given page." + }, + { + "name": "setBreakpointsActive", + "parameters": [ + { "name": "active", "type": "boolean", "description": "New value for breakpoints active state." } + ], + "description": "Activates / deactivates all breakpoints on the page." + }, + { + "name": "setSkipAllPauses", + "parameters": [ + { "name": "skip", "type": "boolean", "description": "New value for skip pauses state." } + ], + "description": "Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc)." + }, + { + "name": "setBreakpointByUrl", + "parameters": [ + { "name": "lineNumber", "type": "integer", "description": "Line number to set breakpoint at." }, + { "name": "url", "type": "string", "optional": true, "description": "URL of the resources to set breakpoint on." }, + { "name": "urlRegex", "type": "string", "optional": true, "description": "Regex pattern for the URLs of the resources to set breakpoints on. Either url or urlRegex must be specified." }, + { "name": "columnNumber", "type": "integer", "optional": true, "description": "Offset in the line to set breakpoint at." }, + { "name": "condition", "type": "string", "optional": true, "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true." } + ], + "returns": [ + { "name": "breakpointId", "$ref": "BreakpointId", "description": "Id of the created breakpoint for further reference." }, + { "name": "locations", "type": "array", "items": { "$ref": "Location" }, "description": "List of the locations this breakpoint resolved into upon addition." } + ], + "description": "Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in locations property. Further matching script parsing will result in subsequent breakpointResolved events issued. This logical breakpoint will survive page reloads." + }, + { + "name": "setBreakpoint", + "parameters": [ + { "name": "location", "$ref": "Location", "description": "Location to set breakpoint in." }, + { "name": "condition", "type": "string", "optional": true, "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true." } + ], + "returns": [ + { "name": "breakpointId", "$ref": "BreakpointId", "description": "Id of the created breakpoint for further reference." }, + { "name": "actualLocation", "$ref": "Location", "description": "Location this breakpoint resolved into." } + ], + "description": "Sets JavaScript breakpoint at a given location." + }, + { + "name": "removeBreakpoint", + "parameters": [ + { "name": "breakpointId", "$ref": "BreakpointId" } + ], + "description": "Removes JavaScript breakpoint." + }, + { + "name": "continueToLocation", + "parameters": [ + { "name": "location", "$ref": "Location", "description": "Location to continue to." } + ], + "description": "Continues execution until specific location is reached." + }, + { + "name": "stepOver", + "description": "Steps over the statement." + }, + { + "name": "stepInto", + "description": "Steps into the function call." + }, + { + "name": "stepOut", + "description": "Steps out of the function call." + }, + { + "name": "pause", + "description": "Stops on the next JavaScript statement." + }, + { + "name": "resume", + "description": "Resumes JavaScript execution." + }, + { + "name": "searchInContent", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Id of the script to search in." }, + { "name": "query", "type": "string", "description": "String to search for." }, + { "name": "caseSensitive", "type": "boolean", "optional": true, "description": "If true, search is case sensitive." }, + { "name": "isRegex", "type": "boolean", "optional": true, "description": "If true, treats string parameter as regex." } + ], + "returns": [ + { "name": "result", "type": "array", "items": { "$ref": "SearchMatch" }, "description": "List of search matches." } + ], + "experimental": true, + "description": "Searches for given string in script content." + }, + { + "name": "setScriptSource", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Id of the script to edit." }, + { "name": "scriptSource", "type": "string", "description": "New content of the script." }, + { "name": "dryRun", "type": "boolean", "optional": true, "description": " If true the change will not actually be applied. Dry run may be used to get result description without actually modifying the code." } + ], + "returns": [ + { "name": "callFrames", "type": "array", "optional": true, "items": { "$ref": "CallFrame" }, "description": "New stack trace in case editing has happened while VM was stopped." }, + { "name": "stackChanged", "type": "boolean", "optional": true, "description": "Whether current call stack was modified after applying the changes." }, + { "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." }, + { "name": "exceptionDetails", "optional": true, "$ref": "Runtime.ExceptionDetails", "description": "Exception details if any." } + ], + "description": "Edits JavaScript source live." + }, + { + "name": "restartFrame", + "parameters": [ + { "name": "callFrameId", "$ref": "CallFrameId", "description": "Call frame identifier to evaluate on." } + ], + "returns": [ + { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "New stack trace." }, + { "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." } + ], + "description": "Restarts particular call frame from the beginning." + }, + { + "name": "getScriptSource", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Id of the script to get source for." } + ], + "returns": [ + { "name": "scriptSource", "type": "string", "description": "Script source." } + ], + "description": "Returns source for the script with given id." + }, + { + "name": "setPauseOnExceptions", + "parameters": [ + { "name": "state", "type": "string", "enum": ["none", "uncaught", "all"], "description": "Pause on exceptions mode." } + ], + "description": "Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or no exceptions. Initial pause on exceptions state is none." + }, + { + "name": "evaluateOnCallFrame", + "parameters": [ + { "name": "callFrameId", "$ref": "CallFrameId", "description": "Call frame identifier to evaluate on." }, + { "name": "expression", "type": "string", "description": "Expression to evaluate." }, + { "name": "objectGroup", "type": "string", "optional": true, "description": "String object group name to put result into (allows rapid releasing resulting object handles using releaseObjectGroup)." }, + { "name": "includeCommandLineAPI", "type": "boolean", "optional": true, "description": "Specifies whether command line API should be available to the evaluated expression, defaults to false." }, + { "name": "silent", "type": "boolean", "optional": true, "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object that should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "experimental": true, "description": "Whether preview should be generated for the result." } + ], + "returns": [ + { "name": "result", "$ref": "Runtime.RemoteObject", "description": "Object wrapper for the evaluation result." }, + { "name": "exceptionDetails", "$ref": "Runtime.ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Evaluates expression on a given call frame." + }, + { + "name": "setVariableValue", + "parameters": [ + { "name": "scopeNumber", "type": "integer", "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' scope types are allowed. Other scopes could be manipulated manually." }, + { "name": "variableName", "type": "string", "description": "Variable name." }, + { "name": "newValue", "$ref": "Runtime.CallArgument", "description": "New variable value." }, + { "name": "callFrameId", "$ref": "CallFrameId", "description": "Id of callframe that holds variable." } + ], + "description": "Changes value of variable in a callframe. Object-based scopes are not supported and must be mutated manually." + }, + { + "name": "setAsyncCallStackDepth", + "parameters": [ + { "name": "maxDepth", "type": "integer", "description": "Maximum depth of async call stacks. Setting to 0 will effectively disable collecting async call stacks (default)." } + ], + "description": "Enables or disables async call stacks tracking." + }, + { + "name": "setBlackboxPatterns", + "parameters": [ + { "name": "patterns", "type": "array", "items": { "type": "string" }, "description": "Array of regexps that will be used to check script url for blackbox state." } + ], + "experimental": true, + "description": "Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in scripts with url matching one of the patterns. VM will try to leave blackboxed script by performing 'step in' several times, finally resorting to 'step out' if unsuccessful." + }, + { + "name": "setBlackboxedRanges", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Id of the script." }, + { "name": "positions", "type": "array", "items": { "$ref": "ScriptPosition" } } + ], + "experimental": true, + "description": "Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted scripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. Positions array contains positions where blackbox state is changed. First interval isn't blackboxed. Array should be sorted." + } + ], + "events": [ + { + "name": "scriptParsed", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Identifier of the script parsed." }, + { "name": "url", "type": "string", "description": "URL or name of the script parsed (if any)." }, + { "name": "startLine", "type": "integer", "description": "Line offset of the script within the resource with given URL (for script tags)." }, + { "name": "startColumn", "type": "integer", "description": "Column offset of the script within the resource with given URL." }, + { "name": "endLine", "type": "integer", "description": "Last line of the script." }, + { "name": "endColumn", "type": "integer", "description": "Length of the last line of the script." }, + { "name": "executionContextId", "$ref": "Runtime.ExecutionContextId", "description": "Specifies script creation context." }, + { "name": "hash", "type": "string", "description": "Content hash of the script."}, + { "name": "executionContextAuxData", "type": "object", "optional": true, "description": "Embedder-specific auxiliary data." }, + { "name": "isLiveEdit", "type": "boolean", "optional": true, "description": "True, if this script is generated as a result of the live edit operation.", "experimental": true }, + { "name": "sourceMapURL", "type": "string", "optional": true, "description": "URL of source map associated with script (if any)." }, + { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "True, if this script has sourceURL.", "experimental": true } + ], + "description": "Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger." + }, + { + "name": "scriptFailedToParse", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Identifier of the script parsed." }, + { "name": "url", "type": "string", "description": "URL or name of the script parsed (if any)." }, + { "name": "startLine", "type": "integer", "description": "Line offset of the script within the resource with given URL (for script tags)." }, + { "name": "startColumn", "type": "integer", "description": "Column offset of the script within the resource with given URL." }, + { "name": "endLine", "type": "integer", "description": "Last line of the script." }, + { "name": "endColumn", "type": "integer", "description": "Length of the last line of the script." }, + { "name": "executionContextId", "$ref": "Runtime.ExecutionContextId", "description": "Specifies script creation context." }, + { "name": "hash", "type": "string", "description": "Content hash of the script."}, + { "name": "executionContextAuxData", "type": "object", "optional": true, "description": "Embedder-specific auxiliary data." }, + { "name": "sourceMapURL", "type": "string", "optional": true, "description": "URL of source map associated with script (if any)." }, + { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "True, if this script has sourceURL.", "experimental": true } + ], + "description": "Fired when virtual machine fails to parse the script." + }, + { + "name": "breakpointResolved", + "parameters": [ + { "name": "breakpointId", "$ref": "BreakpointId", "description": "Breakpoint unique identifier." }, + { "name": "location", "$ref": "Location", "description": "Actual breakpoint location." } + ], + "description": "Fired when breakpoint is resolved to an actual script and location." + }, + { + "name": "paused", + "parameters": [ + { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "Call stack the virtual machine stopped on." }, + { "name": "reason", "type": "string", "enum": [ "XHR", "DOM", "EventListener", "exception", "assert", "debugCommand", "promiseRejection", "other" ], "description": "Pause reason.", "exported": true }, + { "name": "data", "type": "object", "optional": true, "description": "Object containing break-specific auxiliary properties." }, + { "name": "hitBreakpoints", "type": "array", "optional": true, "items": { "type": "string" }, "description": "Hit breakpoints IDs" }, + { "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." } + ], + "description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria." + }, + { + "name": "resumed", + "description": "Fired when the virtual machine resumed execution." + } + ] + }, + { + "domain": "Console", + "description": "This domain is deprecated - use Runtime or Log instead.", + "dependencies": ["Runtime"], + "deprecated": true, + "types": [ + { + "id": "ConsoleMessage", + "type": "object", + "description": "Console message.", + "properties": [ + { "name": "source", "type": "string", "enum": ["xml", "javascript", "network", "console-api", "storage", "appcache", "rendering", "security", "other", "deprecation", "worker"], "description": "Message source." }, + { "name": "level", "type": "string", "enum": ["log", "warning", "error", "debug", "info"], "description": "Message severity." }, + { "name": "text", "type": "string", "description": "Message text." }, + { "name": "url", "type": "string", "optional": true, "description": "URL of the message origin." }, + { "name": "line", "type": "integer", "optional": true, "description": "Line number in the resource that generated this message (1-based)." }, + { "name": "column", "type": "integer", "optional": true, "description": "Column number in the resource that generated this message (1-based)." } + ] + } + ], + "commands": [ + { + "name": "enable", + "description": "Enables console domain, sends the messages collected so far to the client by means of the messageAdded notification." + }, + { + "name": "disable", + "description": "Disables console domain, prevents further console messages from being reported to the client." + }, + { + "name": "clearMessages", + "description": "Does nothing." + } + ], + "events": [ + { + "name": "messageAdded", + "parameters": [ + { "name": "message", "$ref": "ConsoleMessage", "description": "Console message that has been added." } + ], + "description": "Issued when new console message is added." + } + ] + }, + { + "domain": "Profiler", + "dependencies": ["Runtime", "Debugger"], + "types": [ + { + "id": "ProfileNode", + "type": "object", + "description": "Profile node. Holds callsite information, execution statistics and child nodes.", + "properties": [ + { "name": "id", "type": "integer", "description": "Unique id of the node." }, + { "name": "callFrame", "$ref": "Runtime.CallFrame", "description": "Function location." }, + { "name": "hitCount", "type": "integer", "optional": true, "experimental": true, "description": "Number of samples where this node was on top of the call stack." }, + { "name": "children", "type": "array", "items": { "type": "integer" }, "optional": true, "description": "Child node ids." }, + { "name": "deoptReason", "type": "string", "optional": true, "description": "The reason of being not optimized. The function may be deoptimized or marked as don't optimize."}, + { "name": "positionTicks", "type": "array", "items": { "$ref": "PositionTickInfo" }, "optional": true, "experimental": true, "description": "An array of source position ticks." } + ] + }, + { + "id": "Profile", + "type": "object", + "description": "Profile.", + "properties": [ + { "name": "nodes", "type": "array", "items": { "$ref": "ProfileNode" }, "description": "The list of profile nodes. First item is the root node." }, + { "name": "startTime", "type": "number", "description": "Profiling start timestamp in microseconds." }, + { "name": "endTime", "type": "number", "description": "Profiling end timestamp in microseconds." }, + { "name": "samples", "optional": true, "type": "array", "items": { "type": "integer" }, "description": "Ids of samples top nodes." }, + { "name": "timeDeltas", "optional": true, "type": "array", "items": { "type": "integer" }, "description": "Time intervals between adjacent samples in microseconds. The first delta is relative to the profile startTime." } + ] + }, + { + "id": "PositionTickInfo", + "type": "object", + "experimental": true, + "description": "Specifies a number of samples attributed to a certain source position.", + "properties": [ + { "name": "line", "type": "integer", "description": "Source line number (1-based)." }, + { "name": "ticks", "type": "integer", "description": "Number of samples attributed to the source line." } + ] + } + ], + "commands": [ + { + "name": "enable" + }, + { + "name": "disable" + }, + { + "name": "setSamplingInterval", + "parameters": [ + { "name": "interval", "type": "integer", "description": "New sampling interval in microseconds." } + ], + "description": "Changes CPU profiler sampling interval. Must be called before CPU profiles recording started." + }, + { + "name": "start" + }, + { + "name": "stop", + "returns": [ + { "name": "profile", "$ref": "Profile", "description": "Recorded profile." } + ] + } + ], + "events": [ + { + "name": "consoleProfileStarted", + "parameters": [ + { "name": "id", "type": "string" }, + { "name": "location", "$ref": "Debugger.Location", "description": "Location of console.profile()." }, + { "name": "title", "type": "string", "optional": true, "description": "Profile title passed as an argument to console.profile()." } + ], + "description": "Sent when new profile recodring is started using console.profile() call." + }, + { + "name": "consoleProfileFinished", + "parameters": [ + { "name": "id", "type": "string" }, + { "name": "location", "$ref": "Debugger.Location", "description": "Location of console.profileEnd()." }, + { "name": "profile", "$ref": "Profile" }, + { "name": "title", "type": "string", "optional": true, "description": "Profile title passed as an argument to console.profile()." } + ] + } + ] + }, + { + "domain": "HeapProfiler", + "dependencies": ["Runtime"], + "experimental": true, + "types": [ + { + "id": "HeapSnapshotObjectId", + "type": "string", + "description": "Heap snapshot object id." + }, + { + "id": "SamplingHeapProfileNode", + "type": "object", + "description": "Sampling Heap Profile node. Holds callsite information, allocation statistics and child nodes.", + "properties": [ + { "name": "callFrame", "$ref": "Runtime.CallFrame", "description": "Function location." }, + { "name": "selfSize", "type": "number", "description": "Allocations size in bytes for the node excluding children." }, + { "name": "children", "type": "array", "items": { "$ref": "SamplingHeapProfileNode" }, "description": "Child nodes." } + ] + }, + { + "id": "SamplingHeapProfile", + "type": "object", + "description": "Profile.", + "properties": [ + { "name": "head", "$ref": "SamplingHeapProfileNode" } + ] + } + ], + "commands": [ + { + "name": "enable" + }, + { + "name": "disable" + }, + { + "name": "startTrackingHeapObjects", + "parameters": [ + { "name": "trackAllocations", "type": "boolean", "optional": true } + ] + }, + { + "name": "stopTrackingHeapObjects", + "parameters": [ + { "name": "reportProgress", "type": "boolean", "optional": true, "description": "If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken when the tracking is stopped." } + ] + }, + { + "name": "takeHeapSnapshot", + "parameters": [ + { "name": "reportProgress", "type": "boolean", "optional": true, "description": "If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken." } + ] + }, + { + "name": "collectGarbage" + }, + { + "name": "getObjectByHeapObjectId", + "parameters": [ + { "name": "objectId", "$ref": "HeapSnapshotObjectId" }, + { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." } + ], + "returns": [ + { "name": "result", "$ref": "Runtime.RemoteObject", "description": "Evaluation result." } + ] + }, + { + "name": "addInspectedHeapObject", + "parameters": [ + { "name": "heapObjectId", "$ref": "HeapSnapshotObjectId", "description": "Heap snapshot object id to be accessible by means of $x command line API." } + ], + "description": "Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions)." + }, + { + "name": "getHeapObjectId", + "parameters": [ + { "name": "objectId", "$ref": "Runtime.RemoteObjectId", "description": "Identifier of the object to get heap object id for." } + ], + "returns": [ + { "name": "heapSnapshotObjectId", "$ref": "HeapSnapshotObjectId", "description": "Id of the heap snapshot object corresponding to the passed remote object id." } + ] + }, + { + "name": "startSampling", + "parameters": [ + { "name": "samplingInterval", "type": "number", "optional": true, "description": "Average sample interval in bytes. Poisson distribution is used for the intervals. The default value is 32768 bytes." } + ] + }, + { + "name": "stopSampling", + "returns": [ + { "name": "profile", "$ref": "SamplingHeapProfile", "description": "Recorded sampling heap profile." } + ] + } + ], + "events": [ + { + "name": "addHeapSnapshotChunk", + "parameters": [ + { "name": "chunk", "type": "string" } + ] + }, + { + "name": "resetProfiles" + }, + { + "name": "reportHeapSnapshotProgress", + "parameters": [ + { "name": "done", "type": "integer" }, + { "name": "total", "type": "integer" }, + { "name": "finished", "type": "boolean", "optional": true } + ] + }, + { + "name": "lastSeenObjectId", + "description": "If heap objects tracking has been started then backend regulary sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event.", + "parameters": [ + { "name": "lastSeenObjectId", "type": "integer" }, + { "name": "timestamp", "type": "number" } + ] + }, + { + "name": "heapStatsUpdate", + "description": "If heap objects tracking has been started then backend may send update for one or more fragments", + "parameters": [ + { "name": "statsUpdate", "type": "array", "items": { "type": "integer" }, "description": "An array of triplets. Each triplet describes a fragment. The first integer is the fragment index, the second integer is a total count of objects for the fragment, the third integer is a total size of the objects for the fragment."} + ] + } + ] + }] +} diff --git a/NativeScript/napi/android/v8-13/include/js_protocol-1.3.json b/NativeScript/napi/android/v8-13/include/js_protocol-1.3.json new file mode 100644 index 000000000..a998d4611 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/js_protocol-1.3.json @@ -0,0 +1,1159 @@ +{ + "version": { "major": "1", "minor": "3" }, + "domains": [ + { + "domain": "Schema", + "description": "This domain is deprecated.", + "deprecated": true, + "types": [ + { + "id": "Domain", + "type": "object", + "description": "Description of the protocol domain.", + "properties": [ + { "name": "name", "type": "string", "description": "Domain name." }, + { "name": "version", "type": "string", "description": "Domain version." } + ] + } + ], + "commands": [ + { + "name": "getDomains", + "description": "Returns supported domains.", + "handlers": ["browser", "renderer"], + "returns": [ + { "name": "domains", "type": "array", "items": { "$ref": "Domain" }, "description": "List of supported domains." } + ] + } + ] + }, + { + "domain": "Runtime", + "description": "Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects. Evaluation results are returned as mirror object that expose object type, string representation and unique identifier that can be used for further object reference. Original objects are maintained in memory unless they are either explicitly released or are released along with the other objects in their object group.", + "types": [ + { + "id": "ScriptId", + "type": "string", + "description": "Unique script identifier." + }, + { + "id": "RemoteObjectId", + "type": "string", + "description": "Unique object identifier." + }, + { + "id": "UnserializableValue", + "type": "string", + "enum": ["Infinity", "NaN", "-Infinity", "-0"], + "description": "Primitive value which cannot be JSON-stringified." + }, + { + "id": "RemoteObject", + "type": "object", + "description": "Mirror object referencing original JavaScript object.", + "properties": [ + { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "symbol"], "description": "Object type." }, + { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "map", "set", "weakmap", "weakset", "iterator", "generator", "error", "proxy", "promise", "typedarray"], "description": "Object subtype hint. Specified for object type values only." }, + { "name": "className", "type": "string", "optional": true, "description": "Object class (constructor) name. Specified for object type values only." }, + { "name": "value", "type": "any", "optional": true, "description": "Remote object value in case of primitive values or JSON values (if it was requested)." }, + { "name": "unserializableValue", "$ref": "UnserializableValue", "optional": true, "description": "Primitive value which can not be JSON-stringified does not have value, but gets this property." }, + { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." }, + { "name": "objectId", "$ref": "RemoteObjectId", "optional": true, "description": "Unique object identifier (for non-primitive values)." }, + { "name": "preview", "$ref": "ObjectPreview", "optional": true, "description": "Preview containing abbreviated property values. Specified for object type values only.", "experimental": true }, + { "name": "customPreview", "$ref": "CustomPreview", "optional": true, "experimental": true} + ] + }, + { + "id": "CustomPreview", + "type": "object", + "experimental": true, + "properties": [ + { "name": "header", "type": "string"}, + { "name": "hasBody", "type": "boolean"}, + { "name": "formatterObjectId", "$ref": "RemoteObjectId"}, + { "name": "bindRemoteObjectFunctionId", "$ref": "RemoteObjectId" }, + { "name": "configObjectId", "$ref": "RemoteObjectId", "optional": true } + ] + }, + { + "id": "ObjectPreview", + "type": "object", + "experimental": true, + "description": "Object containing abbreviated remote object value.", + "properties": [ + { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "symbol"], "description": "Object type." }, + { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "map", "set", "weakmap", "weakset", "iterator", "generator", "error"], "description": "Object subtype hint. Specified for object type values only." }, + { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." }, + { "name": "overflow", "type": "boolean", "description": "True iff some of the properties or entries of the original object did not fit." }, + { "name": "properties", "type": "array", "items": { "$ref": "PropertyPreview" }, "description": "List of the properties." }, + { "name": "entries", "type": "array", "items": { "$ref": "EntryPreview" }, "optional": true, "description": "List of the entries. Specified for map and set subtype values only." } + ] + }, + { + "id": "PropertyPreview", + "type": "object", + "experimental": true, + "properties": [ + { "name": "name", "type": "string", "description": "Property name." }, + { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "symbol", "accessor"], "description": "Object type. Accessor means that the property itself is an accessor property." }, + { "name": "value", "type": "string", "optional": true, "description": "User-friendly property value string." }, + { "name": "valuePreview", "$ref": "ObjectPreview", "optional": true, "description": "Nested value preview." }, + { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "map", "set", "weakmap", "weakset", "iterator", "generator", "error"], "description": "Object subtype hint. Specified for object type values only." } + ] + }, + { + "id": "EntryPreview", + "type": "object", + "experimental": true, + "properties": [ + { "name": "key", "$ref": "ObjectPreview", "optional": true, "description": "Preview of the key. Specified for map-like collection entries." }, + { "name": "value", "$ref": "ObjectPreview", "description": "Preview of the value." } + ] + }, + { + "id": "PropertyDescriptor", + "type": "object", + "description": "Object property descriptor.", + "properties": [ + { "name": "name", "type": "string", "description": "Property name or symbol description." }, + { "name": "value", "$ref": "RemoteObject", "optional": true, "description": "The value associated with the property." }, + { "name": "writable", "type": "boolean", "optional": true, "description": "True if the value associated with the property may be changed (data descriptors only)." }, + { "name": "get", "$ref": "RemoteObject", "optional": true, "description": "A function which serves as a getter for the property, or undefined if there is no getter (accessor descriptors only)." }, + { "name": "set", "$ref": "RemoteObject", "optional": true, "description": "A function which serves as a setter for the property, or undefined if there is no setter (accessor descriptors only)." }, + { "name": "configurable", "type": "boolean", "description": "True if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object." }, + { "name": "enumerable", "type": "boolean", "description": "True if this property shows up during enumeration of the properties on the corresponding object." }, + { "name": "wasThrown", "type": "boolean", "optional": true, "description": "True if the result was thrown during the evaluation." }, + { "name": "isOwn", "optional": true, "type": "boolean", "description": "True if the property is owned for the object." }, + { "name": "symbol", "$ref": "RemoteObject", "optional": true, "description": "Property symbol object, if the property is of the symbol type." } + ] + }, + { + "id": "InternalPropertyDescriptor", + "type": "object", + "description": "Object internal property descriptor. This property isn't normally visible in JavaScript code.", + "properties": [ + { "name": "name", "type": "string", "description": "Conventional property name." }, + { "name": "value", "$ref": "RemoteObject", "optional": true, "description": "The value associated with the property." } + ] + }, + { + "id": "CallArgument", + "type": "object", + "description": "Represents function call argument. Either remote object id objectId, primitive value, unserializable primitive value or neither of (for undefined) them should be specified.", + "properties": [ + { "name": "value", "type": "any", "optional": true, "description": "Primitive value or serializable javascript object." }, + { "name": "unserializableValue", "$ref": "UnserializableValue", "optional": true, "description": "Primitive value which can not be JSON-stringified." }, + { "name": "objectId", "$ref": "RemoteObjectId", "optional": true, "description": "Remote object handle." } + ] + }, + { + "id": "ExecutionContextId", + "type": "integer", + "description": "Id of an execution context." + }, + { + "id": "ExecutionContextDescription", + "type": "object", + "description": "Description of an isolated world.", + "properties": [ + { "name": "id", "$ref": "ExecutionContextId", "description": "Unique id of the execution context. It can be used to specify in which execution context script evaluation should be performed." }, + { "name": "origin", "type": "string", "description": "Execution context origin." }, + { "name": "name", "type": "string", "description": "Human readable name describing given context." }, + { "name": "auxData", "type": "object", "optional": true, "description": "Embedder-specific auxiliary data." } + ] + }, + { + "id": "ExceptionDetails", + "type": "object", + "description": "Detailed information about exception (or error) that was thrown during script compilation or execution.", + "properties": [ + { "name": "exceptionId", "type": "integer", "description": "Exception id." }, + { "name": "text", "type": "string", "description": "Exception text, which should be used together with exception object when available." }, + { "name": "lineNumber", "type": "integer", "description": "Line number of the exception location (0-based)." }, + { "name": "columnNumber", "type": "integer", "description": "Column number of the exception location (0-based)." }, + { "name": "scriptId", "$ref": "ScriptId", "optional": true, "description": "Script ID of the exception location." }, + { "name": "url", "type": "string", "optional": true, "description": "URL of the exception location, to be used when the script was not reported." }, + { "name": "stackTrace", "$ref": "StackTrace", "optional": true, "description": "JavaScript stack trace if available." }, + { "name": "exception", "$ref": "RemoteObject", "optional": true, "description": "Exception object if available." }, + { "name": "executionContextId", "$ref": "ExecutionContextId", "optional": true, "description": "Identifier of the context where exception happened." } + ] + }, + { + "id": "Timestamp", + "type": "number", + "description": "Number of milliseconds since epoch." + }, + { + "id": "CallFrame", + "type": "object", + "description": "Stack entry for runtime errors and assertions.", + "properties": [ + { "name": "functionName", "type": "string", "description": "JavaScript function name." }, + { "name": "scriptId", "$ref": "ScriptId", "description": "JavaScript script id." }, + { "name": "url", "type": "string", "description": "JavaScript script name or url." }, + { "name": "lineNumber", "type": "integer", "description": "JavaScript script line number (0-based)." }, + { "name": "columnNumber", "type": "integer", "description": "JavaScript script column number (0-based)." } + ] + }, + { + "id": "StackTrace", + "type": "object", + "description": "Call frames for assertions or error messages.", + "properties": [ + { "name": "description", "type": "string", "optional": true, "description": "String label of this stack trace. For async traces this may be a name of the function that initiated the async call." }, + { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "JavaScript function name." }, + { "name": "parent", "$ref": "StackTrace", "optional": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." }, + { "name": "parentId", "$ref": "StackTraceId", "optional": true, "experimental": true, "description": "Asynchronous JavaScript stack trace that preceded this stack, if available." } + ] + }, + { + "id": "UniqueDebuggerId", + "type": "string", + "description": "Unique identifier of current debugger.", + "experimental": true + }, + { + "id": "StackTraceId", + "type": "object", + "description": "If debuggerId is set stack trace comes from another debugger and can be resolved there. This allows to track cross-debugger calls. See Runtime.StackTrace and Debugger.paused for usages.", + "properties": [ + { "name": "id", "type": "string" }, + { "name": "debuggerId", "$ref": "UniqueDebuggerId", "optional": true } + ], + "experimental": true + } + ], + "commands": [ + { + "name": "evaluate", + "parameters": [ + { "name": "expression", "type": "string", "description": "Expression to evaluate." }, + { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." }, + { "name": "includeCommandLineAPI", "type": "boolean", "optional": true, "description": "Determines whether Command Line API should be available during the evaluation." }, + { "name": "silent", "type": "boolean", "optional": true, "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state." }, + { "name": "contextId", "$ref": "ExecutionContextId", "optional": true, "description": "Specifies in which execution context to perform evaluation. If the parameter is omitted the evaluation will be performed in the context of the inspected page." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object that should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "experimental": true, "description": "Whether preview should be generated for the result." }, + { "name": "userGesture", "type": "boolean", "optional": true, "description": "Whether execution should be treated as initiated by user in the UI." }, + { "name": "awaitPromise", "type": "boolean", "optional":true, "description": "Whether execution should await for resulting value and return once awaited promise is resolved." } + ], + "returns": [ + { "name": "result", "$ref": "RemoteObject", "description": "Evaluation result." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Evaluates expression on global object." + }, + { + "name": "awaitPromise", + "parameters": [ + { "name": "promiseObjectId", "$ref": "RemoteObjectId", "description": "Identifier of the promise." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object that should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "description": "Whether preview should be generated for the result." } + ], + "returns": [ + { "name": "result", "$ref": "RemoteObject", "description": "Promise result. Will contain rejected value if promise was rejected." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details if stack strace is available."} + ], + "description": "Add handler to promise with given promise object id." + }, + { + "name": "callFunctionOn", + "parameters": [ + { "name": "functionDeclaration", "type": "string", "description": "Declaration of the function to call." }, + { "name": "objectId", "$ref": "RemoteObjectId", "optional": true, "description": "Identifier of the object to call function on. Either objectId or executionContextId should be specified." }, + { "name": "arguments", "type": "array", "items": { "$ref": "CallArgument", "description": "Call argument." }, "optional": true, "description": "Call arguments. All call arguments must belong to the same JavaScript world as the target object." }, + { "name": "silent", "type": "boolean", "optional": true, "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object which should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "experimental": true, "description": "Whether preview should be generated for the result." }, + { "name": "userGesture", "type": "boolean", "optional": true, "description": "Whether execution should be treated as initiated by user in the UI." }, + { "name": "awaitPromise", "type": "boolean", "optional":true, "description": "Whether execution should await for resulting value and return once awaited promise is resolved." }, + { "name": "executionContextId", "$ref": "ExecutionContextId", "optional": true, "description": "Specifies execution context which global object will be used to call function on. Either executionContextId or objectId should be specified." }, + { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects. If objectGroup is not specified and objectId is, objectGroup will be inherited from object." } + ], + "returns": [ + { "name": "result", "$ref": "RemoteObject", "description": "Call result." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Calls function with given declaration on the given object. Object group of the result is inherited from the target object." + }, + { + "name": "getProperties", + "parameters": [ + { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to return properties for." }, + { "name": "ownProperties", "optional": true, "type": "boolean", "description": "If true, returns properties belonging only to the element itself, not to its prototype chain." }, + { "name": "accessorPropertiesOnly", "optional": true, "type": "boolean", "description": "If true, returns accessor properties (with getter/setter) only; internal properties are not returned either.", "experimental": true }, + { "name": "generatePreview", "type": "boolean", "optional": true, "experimental": true, "description": "Whether preview should be generated for the results." } + ], + "returns": [ + { "name": "result", "type": "array", "items": { "$ref": "PropertyDescriptor" }, "description": "Object properties." }, + { "name": "internalProperties", "optional": true, "type": "array", "items": { "$ref": "InternalPropertyDescriptor" }, "description": "Internal object properties (only of the element itself)." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Returns properties of a given object. Object group of the result is inherited from the target object." + }, + { + "name": "releaseObject", + "parameters": [ + { "name": "objectId", "$ref": "RemoteObjectId", "description": "Identifier of the object to release." } + ], + "description": "Releases remote object with given id." + }, + { + "name": "releaseObjectGroup", + "parameters": [ + { "name": "objectGroup", "type": "string", "description": "Symbolic object group name." } + ], + "description": "Releases all remote objects that belong to a given group." + }, + { + "name": "runIfWaitingForDebugger", + "description": "Tells inspected instance to run if it was waiting for debugger to attach." + }, + { + "name": "enable", + "description": "Enables reporting of execution contexts creation by means of executionContextCreated event. When the reporting gets enabled the event will be sent immediately for each existing execution context." + }, + { + "name": "disable", + "description": "Disables reporting of execution contexts creation." + }, + { + "name": "discardConsoleEntries", + "description": "Discards collected exceptions and console API calls." + }, + { + "name": "setCustomObjectFormatterEnabled", + "parameters": [ + { + "name": "enabled", + "type": "boolean" + } + ], + "experimental": true + }, + { + "name": "compileScript", + "parameters": [ + { "name": "expression", "type": "string", "description": "Expression to compile." }, + { "name": "sourceURL", "type": "string", "description": "Source url to be set for the script." }, + { "name": "persistScript", "type": "boolean", "description": "Specifies whether the compiled script should be persisted." }, + { "name": "executionContextId", "$ref": "ExecutionContextId", "optional": true, "description": "Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page." } + ], + "returns": [ + { "name": "scriptId", "$ref": "ScriptId", "optional": true, "description": "Id of the script." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Compiles expression." + }, + { + "name": "runScript", + "parameters": [ + { "name": "scriptId", "$ref": "ScriptId", "description": "Id of the script to run." }, + { "name": "executionContextId", "$ref": "ExecutionContextId", "optional": true, "description": "Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page." }, + { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." }, + { "name": "silent", "type": "boolean", "optional": true, "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state." }, + { "name": "includeCommandLineAPI", "type": "boolean", "optional": true, "description": "Determines whether Command Line API should be available during the evaluation." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object which should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "description": "Whether preview should be generated for the result." }, + { "name": "awaitPromise", "type": "boolean", "optional": true, "description": "Whether execution should await for resulting value and return once awaited promise is resolved." } + ], + "returns": [ + { "name": "result", "$ref": "RemoteObject", "description": "Run result." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Runs script with given id in a given context." + }, + { + "name": "queryObjects", + "parameters": [ + { "name": "prototypeObjectId", "$ref": "RemoteObjectId", "description": "Identifier of the prototype to return objects for." } + ], + "returns": [ + { "name": "objects", "$ref": "RemoteObject", "description": "Array with objects." } + ] + }, + { + "name": "globalLexicalScopeNames", + "parameters": [ + { "name": "executionContextId", "$ref": "ExecutionContextId", "optional": true, "description": "Specifies in which execution context to lookup global scope variables." } + ], + "returns": [ + { "name": "names", "type": "array", "items": { "type": "string" } } + ], + "description": "Returns all let, const and class variables from global scope." + } + ], + "events": [ + { + "name": "executionContextCreated", + "parameters": [ + { "name": "context", "$ref": "ExecutionContextDescription", "description": "A newly created execution context." } + ], + "description": "Issued when new execution context is created." + }, + { + "name": "executionContextDestroyed", + "parameters": [ + { "name": "executionContextId", "$ref": "ExecutionContextId", "description": "Id of the destroyed context" } + ], + "description": "Issued when execution context is destroyed." + }, + { + "name": "executionContextsCleared", + "description": "Issued when all executionContexts were cleared in browser" + }, + { + "name": "exceptionThrown", + "description": "Issued when exception was thrown and unhandled.", + "parameters": [ + { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp of the exception." }, + { "name": "exceptionDetails", "$ref": "ExceptionDetails" } + ] + }, + { + "name": "exceptionRevoked", + "description": "Issued when unhandled exception was revoked.", + "parameters": [ + { "name": "reason", "type": "string", "description": "Reason describing why exception was revoked." }, + { "name": "exceptionId", "type": "integer", "description": "The id of revoked exception, as reported in exceptionThrown." } + ] + }, + { + "name": "consoleAPICalled", + "description": "Issued when console API was called.", + "parameters": [ + { "name": "type", "type": "string", "enum": ["log", "debug", "info", "error", "warning", "dir", "dirxml", "table", "trace", "clear", "startGroup", "startGroupCollapsed", "endGroup", "assert", "profile", "profileEnd", "count", "timeEnd"], "description": "Type of the call." }, + { "name": "args", "type": "array", "items": { "$ref": "RemoteObject" }, "description": "Call arguments." }, + { "name": "executionContextId", "$ref": "ExecutionContextId", "description": "Identifier of the context where the call was made." }, + { "name": "timestamp", "$ref": "Timestamp", "description": "Call timestamp." }, + { "name": "stackTrace", "$ref": "StackTrace", "optional": true, "description": "Stack trace captured when the call was made." }, + { "name": "context", "type": "string", "optional": true, "experimental": true, "description": "Console context descriptor for calls on non-default console context (not console.*): 'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call on named context." } + ] + }, + { + "name": "inspectRequested", + "description": "Issued when object should be inspected (for example, as a result of inspect() command line API call).", + "parameters": [ + { "name": "object", "$ref": "RemoteObject" }, + { "name": "hints", "type": "object" } + ] + } + ] + }, + { + "domain": "Debugger", + "description": "Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing breakpoints, stepping through execution, exploring stack traces, etc.", + "dependencies": ["Runtime"], + "types": [ + { + "id": "BreakpointId", + "type": "string", + "description": "Breakpoint identifier." + }, + { + "id": "CallFrameId", + "type": "string", + "description": "Call frame identifier." + }, + { + "id": "Location", + "type": "object", + "properties": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Script identifier as reported in the Debugger.scriptParsed." }, + { "name": "lineNumber", "type": "integer", "description": "Line number in the script (0-based)." }, + { "name": "columnNumber", "type": "integer", "optional": true, "description": "Column number in the script (0-based)." } + ], + "description": "Location in the source code." + }, + { + "id": "ScriptPosition", + "experimental": true, + "type": "object", + "properties": [ + { "name": "lineNumber", "type": "integer" }, + { "name": "columnNumber", "type": "integer" } + ], + "description": "Location in the source code." + }, + { + "id": "CallFrame", + "type": "object", + "properties": [ + { "name": "callFrameId", "$ref": "CallFrameId", "description": "Call frame identifier. This identifier is only valid while the virtual machine is paused." }, + { "name": "functionName", "type": "string", "description": "Name of the JavaScript function called on this call frame." }, + { "name": "functionLocation", "$ref": "Location", "optional": true, "description": "Location in the source code." }, + { "name": "location", "$ref": "Location", "description": "Location in the source code." }, + { "name": "url", "type": "string", "description": "JavaScript script name or url." }, + { "name": "scopeChain", "type": "array", "items": { "$ref": "Scope" }, "description": "Scope chain for this call frame." }, + { "name": "this", "$ref": "Runtime.RemoteObject", "description": "this object for this call frame." }, + { "name": "returnValue", "$ref": "Runtime.RemoteObject", "optional": true, "description": "The value being returned, if the function is at return point." } + ], + "description": "JavaScript call frame. Array of call frames form the call stack." + }, + { + "id": "Scope", + "type": "object", + "properties": [ + { "name": "type", "type": "string", "enum": ["global", "local", "with", "closure", "catch", "block", "script", "eval", "module"], "description": "Scope type." }, + { "name": "object", "$ref": "Runtime.RemoteObject", "description": "Object representing the scope. For global and with scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties." }, + { "name": "name", "type": "string", "optional": true }, + { "name": "startLocation", "$ref": "Location", "optional": true, "description": "Location in the source code where scope starts" }, + { "name": "endLocation", "$ref": "Location", "optional": true, "description": "Location in the source code where scope ends" } + ], + "description": "Scope description." + }, + { + "id": "SearchMatch", + "type": "object", + "description": "Search match for resource.", + "properties": [ + { "name": "lineNumber", "type": "number", "description": "Line number in resource content." }, + { "name": "lineContent", "type": "string", "description": "Line with match content." } + ] + }, + { + "id": "BreakLocation", + "type": "object", + "properties": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Script identifier as reported in the Debugger.scriptParsed." }, + { "name": "lineNumber", "type": "integer", "description": "Line number in the script (0-based)." }, + { "name": "columnNumber", "type": "integer", "optional": true, "description": "Column number in the script (0-based)." }, + { "name": "type", "type": "string", "enum": [ "debuggerStatement", "call", "return" ], "optional": true } + ] + } + ], + "commands": [ + { + "name": "enable", + "returns": [ + { "name": "debuggerId", "$ref": "Runtime.UniqueDebuggerId", "experimental": true, "description": "Unique identifier of the debugger." } + ], + "description": "Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received." + }, + { + "name": "disable", + "description": "Disables debugger for given page." + }, + { + "name": "setBreakpointsActive", + "parameters": [ + { "name": "active", "type": "boolean", "description": "New value for breakpoints active state." } + ], + "description": "Activates / deactivates all breakpoints on the page." + }, + { + "name": "setSkipAllPauses", + "parameters": [ + { "name": "skip", "type": "boolean", "description": "New value for skip pauses state." } + ], + "description": "Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc)." + }, + { + "name": "setBreakpointByUrl", + "parameters": [ + { "name": "lineNumber", "type": "integer", "description": "Line number to set breakpoint at." }, + { "name": "url", "type": "string", "optional": true, "description": "URL of the resources to set breakpoint on." }, + { "name": "urlRegex", "type": "string", "optional": true, "description": "Regex pattern for the URLs of the resources to set breakpoints on. Either url or urlRegex must be specified." }, + { "name": "scriptHash", "type": "string", "optional": true, "description": "Script hash of the resources to set breakpoint on." }, + { "name": "columnNumber", "type": "integer", "optional": true, "description": "Offset in the line to set breakpoint at." }, + { "name": "condition", "type": "string", "optional": true, "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true." } + ], + "returns": [ + { "name": "breakpointId", "$ref": "BreakpointId", "description": "Id of the created breakpoint for further reference." }, + { "name": "locations", "type": "array", "items": { "$ref": "Location" }, "description": "List of the locations this breakpoint resolved into upon addition." } + ], + "description": "Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in locations property. Further matching script parsing will result in subsequent breakpointResolved events issued. This logical breakpoint will survive page reloads." + }, + { + "name": "setBreakpoint", + "parameters": [ + { "name": "location", "$ref": "Location", "description": "Location to set breakpoint in." }, + { "name": "condition", "type": "string", "optional": true, "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true." } + ], + "returns": [ + { "name": "breakpointId", "$ref": "BreakpointId", "description": "Id of the created breakpoint for further reference." }, + { "name": "actualLocation", "$ref": "Location", "description": "Location this breakpoint resolved into." } + ], + "description": "Sets JavaScript breakpoint at a given location." + }, + { + "name": "removeBreakpoint", + "parameters": [ + { "name": "breakpointId", "$ref": "BreakpointId" } + ], + "description": "Removes JavaScript breakpoint." + }, + { + "name": "getPossibleBreakpoints", + "parameters": [ + { "name": "start", "$ref": "Location", "description": "Start of range to search possible breakpoint locations in." }, + { "name": "end", "$ref": "Location", "optional": true, "description": "End of range to search possible breakpoint locations in (excluding). When not specified, end of scripts is used as end of range." }, + { "name": "restrictToFunction", "type": "boolean", "optional": true, "description": "Only consider locations which are in the same (non-nested) function as start." } + ], + "returns": [ + { "name": "locations", "type": "array", "items": { "$ref": "BreakLocation" }, "description": "List of the possible breakpoint locations." } + ], + "description": "Returns possible locations for breakpoint. scriptId in start and end range locations should be the same." + }, + { + "name": "continueToLocation", + "parameters": [ + { "name": "location", "$ref": "Location", "description": "Location to continue to." }, + { "name": "targetCallFrames", "type": "string", "enum": ["any", "current"], "optional": true } + ], + "description": "Continues execution until specific location is reached." + }, + { + "name": "pauseOnAsyncCall", + "parameters": [ + { "name": "parentStackTraceId", "$ref": "Runtime.StackTraceId", "description": "Debugger will pause when async call with given stack trace is started." } + ], + "experimental": true + }, + { + "name": "stepOver", + "description": "Steps over the statement." + }, + { + "name": "stepInto", + "parameters": [ + { "name": "breakOnAsyncCall", "type": "boolean", "optional": true, "experimental": true, "description": "Debugger will issue additional Debugger.paused notification if any async task is scheduled before next pause." } + ], + "description": "Steps into the function call." + }, + { + "name": "stepOut", + "description": "Steps out of the function call." + }, + { + "name": "pause", + "description": "Stops on the next JavaScript statement." + }, + { + "name": "scheduleStepIntoAsync", + "description": "This method is deprecated - use Debugger.stepInto with breakOnAsyncCall and Debugger.pauseOnAsyncTask instead. Steps into next scheduled async task if any is scheduled before next pause. Returns success when async task is actually scheduled, returns error if no task were scheduled or another scheduleStepIntoAsync was called.", + "experimental": true + }, + { + "name": "resume", + "description": "Resumes JavaScript execution." + }, + { + "name": "getStackTrace", + "parameters": [ + { "name": "stackTraceId", "$ref": "Runtime.StackTraceId" } + ], + "returns": [ + { "name": "stackTrace", "$ref": "Runtime.StackTrace" } + ], + "description": "Returns stack trace with given stackTraceId.", + "experimental": true + }, + { + "name": "searchInContent", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Id of the script to search in." }, + { "name": "query", "type": "string", "description": "String to search for." }, + { "name": "caseSensitive", "type": "boolean", "optional": true, "description": "If true, search is case sensitive." }, + { "name": "isRegex", "type": "boolean", "optional": true, "description": "If true, treats string parameter as regex." } + ], + "returns": [ + { "name": "result", "type": "array", "items": { "$ref": "SearchMatch" }, "description": "List of search matches." } + ], + "description": "Searches for given string in script content." + }, + { + "name": "setScriptSource", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Id of the script to edit." }, + { "name": "scriptSource", "type": "string", "description": "New content of the script." }, + { "name": "dryRun", "type": "boolean", "optional": true, "description": " If true the change will not actually be applied. Dry run may be used to get result description without actually modifying the code." } + ], + "returns": [ + { "name": "callFrames", "type": "array", "optional": true, "items": { "$ref": "CallFrame" }, "description": "New stack trace in case editing has happened while VM was stopped." }, + { "name": "stackChanged", "type": "boolean", "optional": true, "description": "Whether current call stack was modified after applying the changes." }, + { "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." }, + { "name": "asyncStackTraceId", "$ref": "Runtime.StackTraceId", "optional": true, "experimental": true, "description": "Async stack trace, if any." }, + { "name": "exceptionDetails", "optional": true, "$ref": "Runtime.ExceptionDetails", "description": "Exception details if any." } + ], + "description": "Edits JavaScript source live." + }, + { + "name": "restartFrame", + "parameters": [ + { "name": "callFrameId", "$ref": "CallFrameId", "description": "Call frame identifier to evaluate on." } + ], + "returns": [ + { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "New stack trace." }, + { "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." }, + { "name": "asyncStackTraceId", "$ref": "Runtime.StackTraceId", "optional": true, "experimental": true, "description": "Async stack trace, if any." } + ], + "description": "Restarts particular call frame from the beginning." + }, + { + "name": "getScriptSource", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Id of the script to get source for." } + ], + "returns": [ + { "name": "scriptSource", "type": "string", "description": "Script source." } + ], + "description": "Returns source for the script with given id." + }, + { + "name": "setPauseOnExceptions", + "parameters": [ + { "name": "state", "type": "string", "enum": ["none", "uncaught", "all"], "description": "Pause on exceptions mode." } + ], + "description": "Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or no exceptions. Initial pause on exceptions state is none." + }, + { + "name": "evaluateOnCallFrame", + "parameters": [ + { "name": "callFrameId", "$ref": "CallFrameId", "description": "Call frame identifier to evaluate on." }, + { "name": "expression", "type": "string", "description": "Expression to evaluate." }, + { "name": "objectGroup", "type": "string", "optional": true, "description": "String object group name to put result into (allows rapid releasing resulting object handles using releaseObjectGroup)." }, + { "name": "includeCommandLineAPI", "type": "boolean", "optional": true, "description": "Specifies whether command line API should be available to the evaluated expression, defaults to false." }, + { "name": "silent", "type": "boolean", "optional": true, "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state." }, + { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object that should be sent by value." }, + { "name": "generatePreview", "type": "boolean", "optional": true, "experimental": true, "description": "Whether preview should be generated for the result." }, + { "name": "throwOnSideEffect", "type": "boolean", "optional": true, "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation." } + ], + "returns": [ + { "name": "result", "$ref": "Runtime.RemoteObject", "description": "Object wrapper for the evaluation result." }, + { "name": "exceptionDetails", "$ref": "Runtime.ExceptionDetails", "optional": true, "description": "Exception details."} + ], + "description": "Evaluates expression on a given call frame." + }, + { + "name": "setVariableValue", + "parameters": [ + { "name": "scopeNumber", "type": "integer", "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' scope types are allowed. Other scopes could be manipulated manually." }, + { "name": "variableName", "type": "string", "description": "Variable name." }, + { "name": "newValue", "$ref": "Runtime.CallArgument", "description": "New variable value." }, + { "name": "callFrameId", "$ref": "CallFrameId", "description": "Id of callframe that holds variable." } + ], + "description": "Changes value of variable in a callframe. Object-based scopes are not supported and must be mutated manually." + }, + { + "name": "setReturnValue", + "parameters": [ + { "name": "newValue", "$ref": "Runtime.CallArgument", "description": "New return value." } + ], + "experimental": true, + "description": "Changes return value in top frame. Available only at return break position." + }, + { + "name": "setAsyncCallStackDepth", + "parameters": [ + { "name": "maxDepth", "type": "integer", "description": "Maximum depth of async call stacks. Setting to 0 will effectively disable collecting async call stacks (default)." } + ], + "description": "Enables or disables async call stacks tracking." + }, + { + "name": "setBlackboxPatterns", + "parameters": [ + { "name": "patterns", "type": "array", "items": { "type": "string" }, "description": "Array of regexps that will be used to check script url for blackbox state." } + ], + "experimental": true, + "description": "Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in scripts with url matching one of the patterns. VM will try to leave blackboxed script by performing 'step in' several times, finally resorting to 'step out' if unsuccessful." + }, + { + "name": "setBlackboxedRanges", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Id of the script." }, + { "name": "positions", "type": "array", "items": { "$ref": "ScriptPosition" } } + ], + "experimental": true, + "description": "Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted scripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. Positions array contains positions where blackbox state is changed. First interval isn't blackboxed. Array should be sorted." + } + ], + "events": [ + { + "name": "scriptParsed", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Identifier of the script parsed." }, + { "name": "url", "type": "string", "description": "URL or name of the script parsed (if any)." }, + { "name": "startLine", "type": "integer", "description": "Line offset of the script within the resource with given URL (for script tags)." }, + { "name": "startColumn", "type": "integer", "description": "Column offset of the script within the resource with given URL." }, + { "name": "endLine", "type": "integer", "description": "Last line of the script." }, + { "name": "endColumn", "type": "integer", "description": "Length of the last line of the script." }, + { "name": "executionContextId", "$ref": "Runtime.ExecutionContextId", "description": "Specifies script creation context." }, + { "name": "hash", "type": "string", "description": "Content hash of the script."}, + { "name": "executionContextAuxData", "type": "object", "optional": true, "description": "Embedder-specific auxiliary data." }, + { "name": "isLiveEdit", "type": "boolean", "optional": true, "description": "True, if this script is generated as a result of the live edit operation.", "experimental": true }, + { "name": "sourceMapURL", "type": "string", "optional": true, "description": "URL of source map associated with script (if any)." }, + { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "True, if this script has sourceURL." }, + { "name": "isModule", "type": "boolean", "optional": true, "description": "True, if this script is ES6 module." }, + { "name": "length", "type": "integer", "optional": true, "description": "This script length." }, + { "name": "stackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "JavaScript top stack frame of where the script parsed event was triggered if available.", "experimental": true } + ], + "description": "Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger." + }, + { + "name": "scriptFailedToParse", + "parameters": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "Identifier of the script parsed." }, + { "name": "url", "type": "string", "description": "URL or name of the script parsed (if any)." }, + { "name": "startLine", "type": "integer", "description": "Line offset of the script within the resource with given URL (for script tags)." }, + { "name": "startColumn", "type": "integer", "description": "Column offset of the script within the resource with given URL." }, + { "name": "endLine", "type": "integer", "description": "Last line of the script." }, + { "name": "endColumn", "type": "integer", "description": "Length of the last line of the script." }, + { "name": "executionContextId", "$ref": "Runtime.ExecutionContextId", "description": "Specifies script creation context." }, + { "name": "hash", "type": "string", "description": "Content hash of the script."}, + { "name": "executionContextAuxData", "type": "object", "optional": true, "description": "Embedder-specific auxiliary data." }, + { "name": "sourceMapURL", "type": "string", "optional": true, "description": "URL of source map associated with script (if any)." }, + { "name": "hasSourceURL", "type": "boolean", "optional": true, "description": "True, if this script has sourceURL." }, + { "name": "isModule", "type": "boolean", "optional": true, "description": "True, if this script is ES6 module." }, + { "name": "length", "type": "integer", "optional": true, "description": "This script length." }, + { "name": "stackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "JavaScript top stack frame of where the script parsed event was triggered if available.", "experimental": true } + ], + "description": "Fired when virtual machine fails to parse the script." + }, + { + "name": "breakpointResolved", + "parameters": [ + { "name": "breakpointId", "$ref": "BreakpointId", "description": "Breakpoint unique identifier." }, + { "name": "location", "$ref": "Location", "description": "Actual breakpoint location." } + ], + "description": "Fired when breakpoint is resolved to an actual script and location." + }, + { + "name": "paused", + "parameters": [ + { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "Call stack the virtual machine stopped on." }, + { "name": "reason", "type": "string", "enum": [ "XHR", "DOM", "EventListener", "exception", "assert", "debugCommand", "promiseRejection", "OOM", "other", "ambiguous" ], "description": "Pause reason." }, + { "name": "data", "type": "object", "optional": true, "description": "Object containing break-specific auxiliary properties." }, + { "name": "hitBreakpoints", "type": "array", "optional": true, "items": { "type": "string" }, "description": "Hit breakpoints IDs" }, + { "name": "asyncStackTrace", "$ref": "Runtime.StackTrace", "optional": true, "description": "Async stack trace, if any." }, + { "name": "asyncStackTraceId", "$ref": "Runtime.StackTraceId", "optional": true, "experimental": true, "description": "Async stack trace, if any." }, + { "name": "asyncCallStackTraceId", "$ref": "Runtime.StackTraceId", "optional": true, "experimental": true, "description": "Just scheduled async call will have this stack trace as parent stack during async execution. This field is available only after Debugger.stepInto call with breakOnAsynCall flag." } + ], + "description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria." + }, + { + "name": "resumed", + "description": "Fired when the virtual machine resumed execution." + } + ] + }, + { + "domain": "Console", + "description": "This domain is deprecated - use Runtime or Log instead.", + "dependencies": ["Runtime"], + "deprecated": true, + "types": [ + { + "id": "ConsoleMessage", + "type": "object", + "description": "Console message.", + "properties": [ + { "name": "source", "type": "string", "enum": ["xml", "javascript", "network", "console-api", "storage", "appcache", "rendering", "security", "other", "deprecation", "worker"], "description": "Message source." }, + { "name": "level", "type": "string", "enum": ["log", "warning", "error", "debug", "info"], "description": "Message severity." }, + { "name": "text", "type": "string", "description": "Message text." }, + { "name": "url", "type": "string", "optional": true, "description": "URL of the message origin." }, + { "name": "line", "type": "integer", "optional": true, "description": "Line number in the resource that generated this message (1-based)." }, + { "name": "column", "type": "integer", "optional": true, "description": "Column number in the resource that generated this message (1-based)." } + ] + } + ], + "commands": [ + { + "name": "enable", + "description": "Enables console domain, sends the messages collected so far to the client by means of the messageAdded notification." + }, + { + "name": "disable", + "description": "Disables console domain, prevents further console messages from being reported to the client." + }, + { + "name": "clearMessages", + "description": "Does nothing." + } + ], + "events": [ + { + "name": "messageAdded", + "parameters": [ + { "name": "message", "$ref": "ConsoleMessage", "description": "Console message that has been added." } + ], + "description": "Issued when new console message is added." + } + ] + }, + { + "domain": "Profiler", + "dependencies": ["Runtime", "Debugger"], + "types": [ + { + "id": "ProfileNode", + "type": "object", + "description": "Profile node. Holds callsite information, execution statistics and child nodes.", + "properties": [ + { "name": "id", "type": "integer", "description": "Unique id of the node." }, + { "name": "callFrame", "$ref": "Runtime.CallFrame", "description": "Function location." }, + { "name": "hitCount", "type": "integer", "optional": true, "description": "Number of samples where this node was on top of the call stack." }, + { "name": "children", "type": "array", "items": { "type": "integer" }, "optional": true, "description": "Child node ids." }, + { "name": "deoptReason", "type": "string", "optional": true, "description": "The reason of being not optimized. The function may be deoptimized or marked as don't optimize."}, + { "name": "positionTicks", "type": "array", "items": { "$ref": "PositionTickInfo" }, "optional": true, "description": "An array of source position ticks." } + ] + }, + { + "id": "Profile", + "type": "object", + "description": "Profile.", + "properties": [ + { "name": "nodes", "type": "array", "items": { "$ref": "ProfileNode" }, "description": "The list of profile nodes. First item is the root node." }, + { "name": "startTime", "type": "number", "description": "Profiling start timestamp in microseconds." }, + { "name": "endTime", "type": "number", "description": "Profiling end timestamp in microseconds." }, + { "name": "samples", "optional": true, "type": "array", "items": { "type": "integer" }, "description": "Ids of samples top nodes." }, + { "name": "timeDeltas", "optional": true, "type": "array", "items": { "type": "integer" }, "description": "Time intervals between adjacent samples in microseconds. The first delta is relative to the profile startTime." } + ] + }, + { + "id": "PositionTickInfo", + "type": "object", + "description": "Specifies a number of samples attributed to a certain source position.", + "properties": [ + { "name": "line", "type": "integer", "description": "Source line number (1-based)." }, + { "name": "ticks", "type": "integer", "description": "Number of samples attributed to the source line." } + ] + }, + { "id": "CoverageRange", + "type": "object", + "description": "Coverage data for a source range.", + "properties": [ + { "name": "startOffset", "type": "integer", "description": "JavaScript script source offset for the range start." }, + { "name": "endOffset", "type": "integer", "description": "JavaScript script source offset for the range end." }, + { "name": "count", "type": "integer", "description": "Collected execution count of the source range." } + ] + }, + { "id": "FunctionCoverage", + "type": "object", + "description": "Coverage data for a JavaScript function.", + "properties": [ + { "name": "functionName", "type": "string", "description": "JavaScript function name." }, + { "name": "ranges", "type": "array", "items": { "$ref": "CoverageRange" }, "description": "Source ranges inside the function with coverage data." }, + { "name": "isBlockCoverage", "type": "boolean", "description": "Whether coverage data for this function has block granularity." } + ] + }, + { + "id": "ScriptCoverage", + "type": "object", + "description": "Coverage data for a JavaScript script.", + "properties": [ + { "name": "scriptId", "$ref": "Runtime.ScriptId", "description": "JavaScript script id." }, + { "name": "url", "type": "string", "description": "JavaScript script name or url." }, + { "name": "functions", "type": "array", "items": { "$ref": "FunctionCoverage" }, "description": "Functions contained in the script that has coverage data." } + ] + } + ], + "commands": [ + { + "name": "enable" + }, + { + "name": "disable" + }, + { + "name": "setSamplingInterval", + "parameters": [ + { "name": "interval", "type": "integer", "description": "New sampling interval in microseconds." } + ], + "description": "Changes CPU profiler sampling interval. Must be called before CPU profiles recording started." + }, + { + "name": "start" + }, + { + "name": "stop", + "returns": [ + { "name": "profile", "$ref": "Profile", "description": "Recorded profile." } + ] + }, + { + "name": "startPreciseCoverage", + "parameters": [ + { "name": "callCount", "type": "boolean", "optional": true, "description": "Collect accurate call counts beyond simple 'covered' or 'not covered'." }, + { "name": "detailed", "type": "boolean", "optional": true, "description": "Collect block-based coverage." } + ], + "description": "Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code coverage may be incomplete. Enabling prevents running optimized code and resets execution counters." + }, + { + "name": "stopPreciseCoverage", + "description": "Disable precise code coverage. Disabling releases unnecessary execution count records and allows executing optimized code." + }, + { + "name": "takePreciseCoverage", + "returns": [ + { "name": "result", "type": "array", "items": { "$ref": "ScriptCoverage" }, "description": "Coverage data for the current isolate." } + ], + "description": "Collect coverage data for the current isolate, and resets execution counters. Precise code coverage needs to have started." + }, + { + "name": "getBestEffortCoverage", + "returns": [ + { "name": "result", "type": "array", "items": { "$ref": "ScriptCoverage" }, "description": "Coverage data for the current isolate." } + ], + "description": "Collect coverage data for the current isolate. The coverage data may be incomplete due to garbage collection." + } + ], + "events": [ + { + "name": "consoleProfileStarted", + "parameters": [ + { "name": "id", "type": "string" }, + { "name": "location", "$ref": "Debugger.Location", "description": "Location of console.profile()." }, + { "name": "title", "type": "string", "optional": true, "description": "Profile title passed as an argument to console.profile()." } + ], + "description": "Sent when new profile recording is started using console.profile() call." + }, + { + "name": "consoleProfileFinished", + "parameters": [ + { "name": "id", "type": "string" }, + { "name": "location", "$ref": "Debugger.Location", "description": "Location of console.profileEnd()." }, + { "name": "profile", "$ref": "Profile" }, + { "name": "title", "type": "string", "optional": true, "description": "Profile title passed as an argument to console.profile()." } + ] + } + ] + }, + { + "domain": "HeapProfiler", + "dependencies": ["Runtime"], + "experimental": true, + "types": [ + { + "id": "HeapSnapshotObjectId", + "type": "string", + "description": "Heap snapshot object id." + }, + { + "id": "SamplingHeapProfileNode", + "type": "object", + "description": "Sampling Heap Profile node. Holds callsite information, allocation statistics and child nodes.", + "properties": [ + { "name": "callFrame", "$ref": "Runtime.CallFrame", "description": "Function location." }, + { "name": "selfSize", "type": "number", "description": "Allocations size in bytes for the node excluding children." }, + { "name": "children", "type": "array", "items": { "$ref": "SamplingHeapProfileNode" }, "description": "Child nodes." } + ] + }, + { + "id": "SamplingHeapProfile", + "type": "object", + "description": "Profile.", + "properties": [ + { "name": "head", "$ref": "SamplingHeapProfileNode" } + ] + } + ], + "commands": [ + { + "name": "enable" + }, + { + "name": "disable" + }, + { + "name": "startTrackingHeapObjects", + "parameters": [ + { "name": "trackAllocations", "type": "boolean", "optional": true } + ] + }, + { + "name": "stopTrackingHeapObjects", + "parameters": [ + { "name": "reportProgress", "type": "boolean", "optional": true, "description": "If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken when the tracking is stopped." } + ] + }, + { + "name": "takeHeapSnapshot", + "parameters": [ + { "name": "reportProgress", "type": "boolean", "optional": true, "description": "If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken." } + ] + }, + { + "name": "collectGarbage" + }, + { + "name": "getObjectByHeapObjectId", + "parameters": [ + { "name": "objectId", "$ref": "HeapSnapshotObjectId" }, + { "name": "objectGroup", "type": "string", "optional": true, "description": "Symbolic group name that can be used to release multiple objects." } + ], + "returns": [ + { "name": "result", "$ref": "Runtime.RemoteObject", "description": "Evaluation result." } + ] + }, + { + "name": "addInspectedHeapObject", + "parameters": [ + { "name": "heapObjectId", "$ref": "HeapSnapshotObjectId", "description": "Heap snapshot object id to be accessible by means of $x command line API." } + ], + "description": "Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions)." + }, + { + "name": "getHeapObjectId", + "parameters": [ + { "name": "objectId", "$ref": "Runtime.RemoteObjectId", "description": "Identifier of the object to get heap object id for." } + ], + "returns": [ + { "name": "heapSnapshotObjectId", "$ref": "HeapSnapshotObjectId", "description": "Id of the heap snapshot object corresponding to the passed remote object id." } + ] + }, + { + "name": "startSampling", + "parameters": [ + { "name": "samplingInterval", "type": "number", "optional": true, "description": "Average sample interval in bytes. Poisson distribution is used for the intervals. The default value is 32768 bytes." } + ] + }, + { + "name": "stopSampling", + "returns": [ + { "name": "profile", "$ref": "SamplingHeapProfile", "description": "Recorded sampling heap profile." } + ] + }, + { + "name": "getSamplingProfile", + "returns": [ + { "name": "profile", "$ref": "SamplingHeapProfile", "description": "Return the sampling profile being collected." } + ] + } + ], + "events": [ + { + "name": "addHeapSnapshotChunk", + "parameters": [ + { "name": "chunk", "type": "string" } + ] + }, + { + "name": "resetProfiles" + }, + { + "name": "reportHeapSnapshotProgress", + "parameters": [ + { "name": "done", "type": "integer" }, + { "name": "total", "type": "integer" }, + { "name": "finished", "type": "boolean", "optional": true } + ] + }, + { + "name": "lastSeenObjectId", + "description": "If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event.", + "parameters": [ + { "name": "lastSeenObjectId", "type": "integer" }, + { "name": "timestamp", "type": "number" } + ] + }, + { + "name": "heapStatsUpdate", + "description": "If heap objects tracking has been started then backend may send update for one or more fragments", + "parameters": [ + { "name": "statsUpdate", "type": "array", "items": { "type": "integer" }, "description": "An array of triplets. Each triplet describes a fragment. The first integer is the fragment index, the second integer is a total count of objects for the fragment, the third integer is a total size of the objects for the fragment."} + ] + } + ] + }] +} diff --git a/NativeScript/napi/android/v8-13/include/js_protocol.pdl b/NativeScript/napi/android/v8-13/include/js_protocol.pdl new file mode 100644 index 000000000..73da9149b --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/js_protocol.pdl @@ -0,0 +1,1835 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +version + major 1 + minor 3 + +# This domain is deprecated - use Runtime or Log instead. +deprecated domain Console + depends on Runtime + + # Console message. + type ConsoleMessage extends object + properties + # Message source. + enum source + xml + javascript + network + console-api + storage + appcache + rendering + security + other + deprecation + worker + # Message severity. + enum level + log + warning + error + debug + info + # Message text. + string text + # URL of the message origin. + optional string url + # Line number in the resource that generated this message (1-based). + optional integer line + # Column number in the resource that generated this message (1-based). + optional integer column + + # Does nothing. + command clearMessages + + # Disables console domain, prevents further console messages from being reported to the client. + command disable + + # Enables console domain, sends the messages collected so far to the client by means of the + # `messageAdded` notification. + command enable + + # Issued when new console message is added. + event messageAdded + parameters + # Console message that has been added. + ConsoleMessage message + +# Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing +# breakpoints, stepping through execution, exploring stack traces, etc. +domain Debugger + depends on Runtime + + # Breakpoint identifier. + type BreakpointId extends string + + # Call frame identifier. + type CallFrameId extends string + + # Location in the source code. + type Location extends object + properties + # Script identifier as reported in the `Debugger.scriptParsed`. + Runtime.ScriptId scriptId + # Line number in the script (0-based). + integer lineNumber + # Column number in the script (0-based). + optional integer columnNumber + + # Location in the source code. + experimental type ScriptPosition extends object + properties + integer lineNumber + integer columnNumber + + # Location range within one script. + experimental type LocationRange extends object + properties + Runtime.ScriptId scriptId + ScriptPosition start + ScriptPosition end + + # JavaScript call frame. Array of call frames form the call stack. + type CallFrame extends object + properties + # Call frame identifier. This identifier is only valid while the virtual machine is paused. + CallFrameId callFrameId + # Name of the JavaScript function called on this call frame. + string functionName + # Location in the source code. + optional Location functionLocation + # Location in the source code. + Location location + # JavaScript script name or url. + # Deprecated in favor of using the `location.scriptId` to resolve the URL via a previously + # sent `Debugger.scriptParsed` event. + deprecated string url + # Scope chain for this call frame. + array of Scope scopeChain + # `this` object for this call frame. + Runtime.RemoteObject this + # The value being returned, if the function is at return point. + optional Runtime.RemoteObject returnValue + # Valid only while the VM is paused and indicates whether this frame + # can be restarted or not. Note that a `true` value here does not + # guarantee that Debugger#restartFrame with this CallFrameId will be + # successful, but it is very likely. + experimental optional boolean canBeRestarted + + # Scope description. + type Scope extends object + properties + # Scope type. + enum type + global + local + with + closure + catch + block + script + eval + module + wasm-expression-stack + # Object representing the scope. For `global` and `with` scopes it represents the actual + # object; for the rest of the scopes, it is artificial transient object enumerating scope + # variables as its properties. + Runtime.RemoteObject object + optional string name + # Location in the source code where scope starts + optional Location startLocation + # Location in the source code where scope ends + optional Location endLocation + + # Search match for resource. + type SearchMatch extends object + properties + # Line number in resource content. + number lineNumber + # Line with match content. + string lineContent + + type BreakLocation extends object + properties + # Script identifier as reported in the `Debugger.scriptParsed`. + Runtime.ScriptId scriptId + # Line number in the script (0-based). + integer lineNumber + # Column number in the script (0-based). + optional integer columnNumber + optional enum type + debuggerStatement + call + return + + # Continues execution until specific location is reached. + command continueToLocation + parameters + # Location to continue to. + Location location + optional enum targetCallFrames + any + current + + # Disables debugger for given page. + command disable + + # Enables debugger for the given page. Clients should not assume that the debugging has been + # enabled until the result for this command is received. + command enable + parameters + # The maximum size in bytes of collected scripts (not referenced by other heap objects) + # the debugger can hold. Puts no limit if parameter is omitted. + experimental optional number maxScriptsCacheSize + returns + # Unique identifier of the debugger. + experimental Runtime.UniqueDebuggerId debuggerId + + # Evaluates expression on a given call frame. + command evaluateOnCallFrame + parameters + # Call frame identifier to evaluate on. + CallFrameId callFrameId + # Expression to evaluate. + string expression + # String object group name to put result into (allows rapid releasing resulting object handles + # using `releaseObjectGroup`). + optional string objectGroup + # Specifies whether command line API should be available to the evaluated expression, defaults + # to false. + optional boolean includeCommandLineAPI + # In silent mode exceptions thrown during evaluation are not reported and do not pause + # execution. Overrides `setPauseOnException` state. + optional boolean silent + # Whether the result is expected to be a JSON object that should be sent by value. + optional boolean returnByValue + # Whether preview should be generated for the result. + experimental optional boolean generatePreview + # Whether to throw an exception if side effect cannot be ruled out during evaluation. + optional boolean throwOnSideEffect + # Terminate execution after timing out (number of milliseconds). + experimental optional Runtime.TimeDelta timeout + returns + # Object wrapper for the evaluation result. + Runtime.RemoteObject result + # Exception details. + optional Runtime.ExceptionDetails exceptionDetails + + # Returns possible locations for breakpoint. scriptId in start and end range locations should be + # the same. + command getPossibleBreakpoints + parameters + # Start of range to search possible breakpoint locations in. + Location start + # End of range to search possible breakpoint locations in (excluding). When not specified, end + # of scripts is used as end of range. + optional Location end + # Only consider locations which are in the same (non-nested) function as start. + optional boolean restrictToFunction + returns + # List of the possible breakpoint locations. + array of BreakLocation locations + + # Returns source for the script with given id. + command getScriptSource + parameters + # Id of the script to get source for. + Runtime.ScriptId scriptId + returns + # Script source (empty in case of Wasm bytecode). + string scriptSource + # Wasm bytecode. + optional binary bytecode + + experimental type WasmDisassemblyChunk extends object + properties + # The next chunk of disassembled lines. + array of string lines + # The bytecode offsets describing the start of each line. + array of integer bytecodeOffsets + + experimental command disassembleWasmModule + parameters + # Id of the script to disassemble + Runtime.ScriptId scriptId + returns + # For large modules, return a stream from which additional chunks of + # disassembly can be read successively. + optional string streamId + # The total number of lines in the disassembly text. + integer totalNumberOfLines + # The offsets of all function bodies, in the format [start1, end1, + # start2, end2, ...] where all ends are exclusive. + array of integer functionBodyOffsets + # The first chunk of disassembly. + WasmDisassemblyChunk chunk + + # Disassemble the next chunk of lines for the module corresponding to the + # stream. If disassembly is complete, this API will invalidate the streamId + # and return an empty chunk. Any subsequent calls for the now invalid stream + # will return errors. + experimental command nextWasmDisassemblyChunk + parameters + string streamId + returns + # The next chunk of disassembly. + WasmDisassemblyChunk chunk + + # This command is deprecated. Use getScriptSource instead. + deprecated command getWasmBytecode + parameters + # Id of the Wasm script to get source for. + Runtime.ScriptId scriptId + returns + # Script source. + binary bytecode + + # Returns stack trace with given `stackTraceId`. + experimental command getStackTrace + parameters + Runtime.StackTraceId stackTraceId + returns + Runtime.StackTrace stackTrace + + # Stops on the next JavaScript statement. + command pause + + experimental deprecated command pauseOnAsyncCall + parameters + # Debugger will pause when async call with given stack trace is started. + Runtime.StackTraceId parentStackTraceId + + # Removes JavaScript breakpoint. + command removeBreakpoint + parameters + BreakpointId breakpointId + + # Restarts particular call frame from the beginning. The old, deprecated + # behavior of `restartFrame` is to stay paused and allow further CDP commands + # after a restart was scheduled. This can cause problems with restarting, so + # we now continue execution immediatly after it has been scheduled until we + # reach the beginning of the restarted frame. + # + # To stay back-wards compatible, `restartFrame` now expects a `mode` + # parameter to be present. If the `mode` parameter is missing, `restartFrame` + # errors out. + # + # The various return values are deprecated and `callFrames` is always empty. + # Use the call frames from the `Debugger#paused` events instead, that fires + # once V8 pauses at the beginning of the restarted function. + command restartFrame + parameters + # Call frame identifier to evaluate on. + CallFrameId callFrameId + # The `mode` parameter must be present and set to 'StepInto', otherwise + # `restartFrame` will error out. + experimental optional enum mode + # Pause at the beginning of the restarted function + StepInto + returns + # New stack trace. + deprecated array of CallFrame callFrames + # Async stack trace, if any. + deprecated optional Runtime.StackTrace asyncStackTrace + # Async stack trace, if any. + deprecated optional Runtime.StackTraceId asyncStackTraceId + + # Resumes JavaScript execution. + command resume + parameters + # Set to true to terminate execution upon resuming execution. In contrast + # to Runtime.terminateExecution, this will allows to execute further + # JavaScript (i.e. via evaluation) until execution of the paused code + # is actually resumed, at which point termination is triggered. + # If execution is currently not paused, this parameter has no effect. + optional boolean terminateOnResume + + # Searches for given string in script content. + command searchInContent + parameters + # Id of the script to search in. + Runtime.ScriptId scriptId + # String to search for. + string query + # If true, search is case sensitive. + optional boolean caseSensitive + # If true, treats string parameter as regex. + optional boolean isRegex + returns + # List of search matches. + array of SearchMatch result + + # Enables or disables async call stacks tracking. + command setAsyncCallStackDepth + parameters + # Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async + # call stacks (default). + integer maxDepth + + # Replace previous blackbox execution contexts with passed ones. Forces backend to skip + # stepping/pausing in scripts in these execution contexts. VM will try to leave blackboxed script by + # performing 'step in' several times, finally resorting to 'step out' if unsuccessful. + experimental command setBlackboxExecutionContexts + parameters + # Array of execution context unique ids for the debugger to ignore. + array of string uniqueIds + + # Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in + # scripts with url matching one of the patterns. VM will try to leave blackboxed script by + # performing 'step in' several times, finally resorting to 'step out' if unsuccessful. + experimental command setBlackboxPatterns + parameters + # Array of regexps that will be used to check script url for blackbox state. + array of string patterns + # If true, also ignore scripts with no source url. + optional boolean skipAnonymous + + # Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted + # scripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. + # Positions array contains positions where blackbox state is changed. First interval isn't + # blackboxed. Array should be sorted. + experimental command setBlackboxedRanges + parameters + # Id of the script. + Runtime.ScriptId scriptId + array of ScriptPosition positions + + # Sets JavaScript breakpoint at a given location. + command setBreakpoint + parameters + # Location to set breakpoint in. + Location location + # Expression to use as a breakpoint condition. When specified, debugger will only stop on the + # breakpoint if this expression evaluates to true. + optional string condition + returns + # Id of the created breakpoint for further reference. + BreakpointId breakpointId + # Location this breakpoint resolved into. + Location actualLocation + + # Sets instrumentation breakpoint. + command setInstrumentationBreakpoint + parameters + # Instrumentation name. + enum instrumentation + beforeScriptExecution + beforeScriptWithSourceMapExecution + returns + # Id of the created breakpoint for further reference. + BreakpointId breakpointId + + # Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this + # command is issued, all existing parsed scripts will have breakpoints resolved and returned in + # `locations` property. Further matching script parsing will result in subsequent + # `breakpointResolved` events issued. This logical breakpoint will survive page reloads. + command setBreakpointByUrl + parameters + # Line number to set breakpoint at. + integer lineNumber + # URL of the resources to set breakpoint on. + optional string url + # Regex pattern for the URLs of the resources to set breakpoints on. Either `url` or + # `urlRegex` must be specified. + optional string urlRegex + # Script hash of the resources to set breakpoint on. + optional string scriptHash + # Offset in the line to set breakpoint at. + optional integer columnNumber + # Expression to use as a breakpoint condition. When specified, debugger will only stop on the + # breakpoint if this expression evaluates to true. + optional string condition + returns + # Id of the created breakpoint for further reference. + BreakpointId breakpointId + # List of the locations this breakpoint resolved into upon addition. + array of Location locations + + # Sets JavaScript breakpoint before each call to the given function. + # If another function was created from the same source as a given one, + # calling it will also trigger the breakpoint. + experimental command setBreakpointOnFunctionCall + parameters + # Function object id. + Runtime.RemoteObjectId objectId + # Expression to use as a breakpoint condition. When specified, debugger will + # stop on the breakpoint if this expression evaluates to true. + optional string condition + returns + # Id of the created breakpoint for further reference. + BreakpointId breakpointId + + # Activates / deactivates all breakpoints on the page. + command setBreakpointsActive + parameters + # New value for breakpoints active state. + boolean active + + # Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions, + # or caught exceptions, no exceptions. Initial pause on exceptions state is `none`. + command setPauseOnExceptions + parameters + # Pause on exceptions mode. + enum state + none + caught + uncaught + all + + # Changes return value in top frame. Available only at return break position. + experimental command setReturnValue + parameters + # New return value. + Runtime.CallArgument newValue + + # Edits JavaScript source live. + # + # In general, functions that are currently on the stack can not be edited with + # a single exception: If the edited function is the top-most stack frame and + # that is the only activation of that function on the stack. In this case + # the live edit will be successful and a `Debugger.restartFrame` for the + # top-most function is automatically triggered. + command setScriptSource + parameters + # Id of the script to edit. + Runtime.ScriptId scriptId + # New content of the script. + string scriptSource + # If true the change will not actually be applied. Dry run may be used to get result + # description without actually modifying the code. + optional boolean dryRun + # If true, then `scriptSource` is allowed to change the function on top of the stack + # as long as the top-most stack frame is the only activation of that function. + experimental optional boolean allowTopFrameEditing + returns + # New stack trace in case editing has happened while VM was stopped. + deprecated optional array of CallFrame callFrames + # Whether current call stack was modified after applying the changes. + deprecated optional boolean stackChanged + # Async stack trace, if any. + deprecated optional Runtime.StackTrace asyncStackTrace + # Async stack trace, if any. + deprecated optional Runtime.StackTraceId asyncStackTraceId + # Whether the operation was successful or not. Only `Ok` denotes a + # successful live edit while the other enum variants denote why + # the live edit failed. + experimental enum status + Ok + CompileError + BlockedByActiveGenerator + BlockedByActiveFunction + BlockedByTopLevelEsModuleChange + # Exception details if any. Only present when `status` is `CompileError`. + optional Runtime.ExceptionDetails exceptionDetails + + # Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc). + command setSkipAllPauses + parameters + # New value for skip pauses state. + boolean skip + + # Changes value of variable in a callframe. Object-based scopes are not supported and must be + # mutated manually. + command setVariableValue + parameters + # 0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' + # scope types are allowed. Other scopes could be manipulated manually. + integer scopeNumber + # Variable name. + string variableName + # New variable value. + Runtime.CallArgument newValue + # Id of callframe that holds variable. + CallFrameId callFrameId + + # Steps into the function call. + command stepInto + parameters + # Debugger will pause on the execution of the first async task which was scheduled + # before next pause. + experimental optional boolean breakOnAsyncCall + # The skipList specifies location ranges that should be skipped on step into. + experimental optional array of LocationRange skipList + + # Steps out of the function call. + command stepOut + + # Steps over the statement. + command stepOver + parameters + # The skipList specifies location ranges that should be skipped on step over. + experimental optional array of LocationRange skipList + + # Fired when breakpoint is resolved to an actual script and location. + # Deprecated in favor of `resolvedBreakpoints` in the `scriptParsed` event. + deprecated event breakpointResolved + parameters + # Breakpoint unique identifier. + BreakpointId breakpointId + # Actual breakpoint location. + Location location + + # Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + event paused + parameters + # Call stack the virtual machine stopped on. + array of CallFrame callFrames + # Pause reason. + enum reason + ambiguous + assert + CSPViolation + debugCommand + DOM + EventListener + exception + instrumentation + OOM + other + promiseRejection + XHR + step + # Object containing break-specific auxiliary properties. + optional object data + # Hit breakpoints IDs + optional array of string hitBreakpoints + # Async stack trace, if any. + optional Runtime.StackTrace asyncStackTrace + # Async stack trace, if any. + experimental optional Runtime.StackTraceId asyncStackTraceId + # Never present, will be removed. + experimental deprecated optional Runtime.StackTraceId asyncCallStackTraceId + + # Fired when the virtual machine resumed execution. + event resumed + + # Enum of possible script languages. + type ScriptLanguage extends string + enum + JavaScript + WebAssembly + + # Debug symbols available for a wasm script. + type DebugSymbols extends object + properties + # Type of the debug symbols. + enum type + SourceMap + EmbeddedDWARF + ExternalDWARF + # URL of the external symbol source. + optional string externalURL + + type ResolvedBreakpoint extends object + properties + # Breakpoint unique identifier. + BreakpointId breakpointId + # Actual breakpoint location. + Location location + + # Fired when virtual machine fails to parse the script. + event scriptFailedToParse + parameters + # Identifier of the script parsed. + Runtime.ScriptId scriptId + # URL or name of the script parsed (if any). + string url + # Line offset of the script within the resource with given URL (for script tags). + integer startLine + # Column offset of the script within the resource with given URL. + integer startColumn + # Last line of the script. + integer endLine + # Length of the last line of the script. + integer endColumn + # Specifies script creation context. + Runtime.ExecutionContextId executionContextId + # Content hash of the script, SHA-256. + string hash + # For Wasm modules, the content of the `build_id` custom section. For JavaScript the `debugId` magic comment. + string buildId + # Embedder-specific auxiliary data likely matching {isDefault: boolean, type: 'default'|'isolated'|'worker', frameId: string} + optional object executionContextAuxData + # URL of source map associated with script (if any). + optional string sourceMapURL + # True, if this script has sourceURL. + optional boolean hasSourceURL + # True, if this script is ES6 module. + optional boolean isModule + # This script length. + optional integer length + # JavaScript top stack frame of where the script parsed event was triggered if available. + experimental optional Runtime.StackTrace stackTrace + # If the scriptLanguage is WebAssembly, the code section offset in the module. + experimental optional integer codeOffset + # The language of the script. + experimental optional Debugger.ScriptLanguage scriptLanguage + # The name the embedder supplied for this script. + experimental optional string embedderName + + # Fired when virtual machine parses script. This event is also fired for all known and uncollected + # scripts upon enabling debugger. + event scriptParsed + parameters + # Identifier of the script parsed. + Runtime.ScriptId scriptId + # URL or name of the script parsed (if any). + string url + # Line offset of the script within the resource with given URL (for script tags). + integer startLine + # Column offset of the script within the resource with given URL. + integer startColumn + # Last line of the script. + integer endLine + # Length of the last line of the script. + integer endColumn + # Specifies script creation context. + Runtime.ExecutionContextId executionContextId + # Content hash of the script, SHA-256. + string hash + # For Wasm modules, the content of the `build_id` custom section. For JavaScript the `debugId` magic comment. + string buildId + # Embedder-specific auxiliary data likely matching {isDefault: boolean, type: 'default'|'isolated'|'worker', frameId: string} + optional object executionContextAuxData + # True, if this script is generated as a result of the live edit operation. + experimental optional boolean isLiveEdit + # URL of source map associated with script (if any). + optional string sourceMapURL + # True, if this script has sourceURL. + optional boolean hasSourceURL + # True, if this script is ES6 module. + optional boolean isModule + # This script length. + optional integer length + # JavaScript top stack frame of where the script parsed event was triggered if available. + experimental optional Runtime.StackTrace stackTrace + # If the scriptLanguage is WebAssembly, the code section offset in the module. + experimental optional integer codeOffset + # The language of the script. + experimental optional Debugger.ScriptLanguage scriptLanguage + # If the scriptLanguage is WebAssembly, the source of debug symbols for the module. + experimental optional array of Debugger.DebugSymbols debugSymbols + # The name the embedder supplied for this script. + experimental optional string embedderName + # The list of set breakpoints in this script if calls to `setBreakpointByUrl` + # matches this script's URL or hash. Clients that use this list can ignore the + # `breakpointResolved` event. They are equivalent. + experimental optional array of ResolvedBreakpoint resolvedBreakpoints + +experimental domain HeapProfiler + depends on Runtime + + # Heap snapshot object id. + type HeapSnapshotObjectId extends string + + # Sampling Heap Profile node. Holds callsite information, allocation statistics and child nodes. + type SamplingHeapProfileNode extends object + properties + # Function location. + Runtime.CallFrame callFrame + # Allocations size in bytes for the node excluding children. + number selfSize + # Node id. Ids are unique across all profiles collected between startSampling and stopSampling. + integer id + # Child nodes. + array of SamplingHeapProfileNode children + + # A single sample from a sampling profile. + type SamplingHeapProfileSample extends object + properties + # Allocation size in bytes attributed to the sample. + number size + # Id of the corresponding profile tree node. + integer nodeId + # Time-ordered sample ordinal number. It is unique across all profiles retrieved + # between startSampling and stopSampling. + number ordinal + + # Sampling profile. + type SamplingHeapProfile extends object + properties + SamplingHeapProfileNode head + array of SamplingHeapProfileSample samples + + # Enables console to refer to the node with given id via $x (see Command Line API for more details + # $x functions). + command addInspectedHeapObject + parameters + # Heap snapshot object id to be accessible by means of $x command line API. + HeapSnapshotObjectId heapObjectId + + command collectGarbage + + command disable + + command enable + + command getHeapObjectId + parameters + # Identifier of the object to get heap object id for. + Runtime.RemoteObjectId objectId + returns + # Id of the heap snapshot object corresponding to the passed remote object id. + HeapSnapshotObjectId heapSnapshotObjectId + + command getObjectByHeapObjectId + parameters + HeapSnapshotObjectId objectId + # Symbolic group name that can be used to release multiple objects. + optional string objectGroup + returns + # Evaluation result. + Runtime.RemoteObject result + + command getSamplingProfile + returns + # Return the sampling profile being collected. + SamplingHeapProfile profile + + command startSampling + parameters + # Average sample interval in bytes. Poisson distribution is used for the intervals. The + # default value is 32768 bytes. + optional number samplingInterval + # By default, the sampling heap profiler reports only objects which are + # still alive when the profile is returned via getSamplingProfile or + # stopSampling, which is useful for determining what functions contribute + # the most to steady-state memory usage. This flag instructs the sampling + # heap profiler to also include information about objects discarded by + # major GC, which will show which functions cause large temporary memory + # usage or long GC pauses. + optional boolean includeObjectsCollectedByMajorGC + # By default, the sampling heap profiler reports only objects which are + # still alive when the profile is returned via getSamplingProfile or + # stopSampling, which is useful for determining what functions contribute + # the most to steady-state memory usage. This flag instructs the sampling + # heap profiler to also include information about objects discarded by + # minor GC, which is useful when tuning a latency-sensitive application + # for minimal GC activity. + optional boolean includeObjectsCollectedByMinorGC + + command startTrackingHeapObjects + parameters + optional boolean trackAllocations + + command stopSampling + returns + # Recorded sampling heap profile. + SamplingHeapProfile profile + + command stopTrackingHeapObjects + parameters + # If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken + # when the tracking is stopped. + optional boolean reportProgress + # Deprecated in favor of `exposeInternals`. + deprecated optional boolean treatGlobalObjectsAsRoots + # If true, numerical values are included in the snapshot + optional boolean captureNumericValue + # If true, exposes internals of the snapshot. + experimental optional boolean exposeInternals + + command takeHeapSnapshot + parameters + # If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken. + optional boolean reportProgress + # If true, a raw snapshot without artificial roots will be generated. + # Deprecated in favor of `exposeInternals`. + deprecated optional boolean treatGlobalObjectsAsRoots + # If true, numerical values are included in the snapshot + optional boolean captureNumericValue + # If true, exposes internals of the snapshot. + experimental optional boolean exposeInternals + + event addHeapSnapshotChunk + parameters + string chunk + + # If heap objects tracking has been started then backend may send update for one or more fragments + event heapStatsUpdate + parameters + # An array of triplets. Each triplet describes a fragment. The first integer is the fragment + # index, the second integer is a total count of objects for the fragment, the third integer is + # a total size of the objects for the fragment. + array of integer statsUpdate + + # If heap objects tracking has been started then backend regularly sends a current value for last + # seen object id and corresponding timestamp. If the were changes in the heap since last event + # then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + event lastSeenObjectId + parameters + integer lastSeenObjectId + number timestamp + + event reportHeapSnapshotProgress + parameters + integer done + integer total + optional boolean finished + + event resetProfiles + +domain Profiler + depends on Runtime + depends on Debugger + + # Profile node. Holds callsite information, execution statistics and child nodes. + type ProfileNode extends object + properties + # Unique id of the node. + integer id + # Function location. + Runtime.CallFrame callFrame + # Number of samples where this node was on top of the call stack. + optional integer hitCount + # Child node ids. + optional array of integer children + # The reason of being not optimized. The function may be deoptimized or marked as don't + # optimize. + optional string deoptReason + # An array of source position ticks. + optional array of PositionTickInfo positionTicks + + # Profile. + type Profile extends object + properties + # The list of profile nodes. First item is the root node. + array of ProfileNode nodes + # Profiling start timestamp in microseconds. + number startTime + # Profiling end timestamp in microseconds. + number endTime + # Ids of samples top nodes. + optional array of integer samples + # Time intervals between adjacent samples in microseconds. The first delta is relative to the + # profile startTime. + optional array of integer timeDeltas + + # Specifies a number of samples attributed to a certain source position. + type PositionTickInfo extends object + properties + # Source line number (1-based). + integer line + # Number of samples attributed to the source line. + integer ticks + + # Coverage data for a source range. + type CoverageRange extends object + properties + # JavaScript script source offset for the range start. + integer startOffset + # JavaScript script source offset for the range end. + integer endOffset + # Collected execution count of the source range. + integer count + + # Coverage data for a JavaScript function. + type FunctionCoverage extends object + properties + # JavaScript function name. + string functionName + # Source ranges inside the function with coverage data. + array of CoverageRange ranges + # Whether coverage data for this function has block granularity. + boolean isBlockCoverage + + # Coverage data for a JavaScript script. + type ScriptCoverage extends object + properties + # JavaScript script id. + Runtime.ScriptId scriptId + # JavaScript script name or url. + string url + # Functions contained in the script that has coverage data. + array of FunctionCoverage functions + + command disable + + command enable + + # Collect coverage data for the current isolate. The coverage data may be incomplete due to + # garbage collection. + command getBestEffortCoverage + returns + # Coverage data for the current isolate. + array of ScriptCoverage result + + # Changes CPU profiler sampling interval. Must be called before CPU profiles recording started. + command setSamplingInterval + parameters + # New sampling interval in microseconds. + integer interval + + command start + + # Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code + # coverage may be incomplete. Enabling prevents running optimized code and resets execution + # counters. + command startPreciseCoverage + parameters + # Collect accurate call counts beyond simple 'covered' or 'not covered'. + optional boolean callCount + # Collect block-based coverage. + optional boolean detailed + # Allow the backend to send updates on its own initiative + optional boolean allowTriggeredUpdates + returns + # Monotonically increasing time (in seconds) when the coverage update was taken in the backend. + number timestamp + + command stop + returns + # Recorded profile. + Profile profile + + # Disable precise code coverage. Disabling releases unnecessary execution count records and allows + # executing optimized code. + command stopPreciseCoverage + + # Collect coverage data for the current isolate, and resets execution counters. Precise code + # coverage needs to have started. + command takePreciseCoverage + returns + # Coverage data for the current isolate. + array of ScriptCoverage result + # Monotonically increasing time (in seconds) when the coverage update was taken in the backend. + number timestamp + + event consoleProfileFinished + parameters + string id + # Location of console.profileEnd(). + Debugger.Location location + Profile profile + # Profile title passed as an argument to console.profile(). + optional string title + + # Sent when new profile recording is started using console.profile() call. + event consoleProfileStarted + parameters + string id + # Location of console.profile(). + Debugger.Location location + # Profile title passed as an argument to console.profile(). + optional string title + + # Reports coverage delta since the last poll (either from an event like this, or from + # `takePreciseCoverage` for the current isolate. May only be sent if precise code + # coverage has been started. This event can be trigged by the embedder to, for example, + # trigger collection of coverage data immediately at a certain point in time. + experimental event preciseCoverageDeltaUpdate + parameters + # Monotonically increasing time (in seconds) when the coverage update was taken in the backend. + number timestamp + # Identifier for distinguishing coverage events. + string occasion + # Coverage data for the current isolate. + array of ScriptCoverage result + +# Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects. +# Evaluation results are returned as mirror object that expose object type, string representation +# and unique identifier that can be used for further object reference. Original objects are +# maintained in memory unless they are either explicitly released or are released along with the +# other objects in their object group. +domain Runtime + + # Unique script identifier. + type ScriptId extends string + + # Represents options for serialization. Overrides `generatePreview` and `returnByValue`. + type SerializationOptions extends object + properties + enum serialization + # Whether the result should be deep-serialized. The result is put into + # `deepSerializedValue` and `ObjectId` is provided. + deep + # Whether the result is expected to be a JSON object which should be sent by value. + # The result is put either into `value` or into `unserializableValue`. Synonym of + # `returnByValue: true`. Overrides `returnByValue`. + json + # Only remote object id is put in the result. Same bahaviour as if no + # `serializationOptions`, `generatePreview` nor `returnByValue` are provided. + idOnly + + # Deep serialization depth. Default is full depth. Respected only in `deep` serialization mode. + optional integer maxDepth + + # Embedder-specific parameters. For example if connected to V8 in Chrome these control DOM + # serialization via `maxNodeDepth: integer` and `includeShadowTree: "none" | "open" | "all"`. + # Values can be only of type string or integer. + optional object additionalParameters + + # Represents deep serialized value. + type DeepSerializedValue extends object + properties + enum type + undefined + null + string + number + boolean + bigint + regexp + date + symbol + array + object + function + map + set + weakmap + weakset + error + proxy + promise + typedarray + arraybuffer + node + window + generator + optional any value + optional string objectId + # Set if value reference met more then once during serialization. In such + # case, value is provided only to one of the serialized values. Unique + # per value in the scope of one CDP call. + optional integer weakLocalObjectReference + + # Unique object identifier. + type RemoteObjectId extends string + + # Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`, + # `-Infinity`, and bigint literals. + type UnserializableValue extends string + + # Mirror object referencing original JavaScript object. + type RemoteObject extends object + properties + # Object type. + enum type + object + function + undefined + string + number + boolean + symbol + bigint + # Object subtype hint. Specified for `object` type values only. + # NOTE: If you change anything here, make sure to also update + # `subtype` in `ObjectPreview` and `PropertyPreview` below. + optional enum subtype + array + null + node + regexp + date + map + set + weakmap + weakset + iterator + generator + error + proxy + promise + typedarray + arraybuffer + dataview + webassemblymemory + wasmvalue + # Object class (constructor) name. Specified for `object` type values only. + optional string className + # Remote object value in case of primitive values or JSON values (if it was requested). + optional any value + # Primitive value which can not be JSON-stringified does not have `value`, but gets this + # property. + optional UnserializableValue unserializableValue + # String representation of the object. + optional string description + # Deep serialized value. + experimental optional DeepSerializedValue deepSerializedValue + # Unique object identifier (for non-primitive values). + optional RemoteObjectId objectId + # Preview containing abbreviated property values. Specified for `object` type values only. + experimental optional ObjectPreview preview + experimental optional CustomPreview customPreview + + experimental type CustomPreview extends object + properties + # The JSON-stringified result of formatter.header(object, config) call. + # It contains json ML array that represents RemoteObject. + string header + # If formatter returns true as a result of formatter.hasBody call then bodyGetterId will + # contain RemoteObjectId for the function that returns result of formatter.body(object, config) call. + # The result value is json ML array. + optional RemoteObjectId bodyGetterId + + # Object containing abbreviated remote object value. + experimental type ObjectPreview extends object + properties + # Object type. + enum type + object + function + undefined + string + number + boolean + symbol + bigint + # Object subtype hint. Specified for `object` type values only. + optional enum subtype + array + null + node + regexp + date + map + set + weakmap + weakset + iterator + generator + error + proxy + promise + typedarray + arraybuffer + dataview + webassemblymemory + wasmvalue + # String representation of the object. + optional string description + # True iff some of the properties or entries of the original object did not fit. + boolean overflow + # List of the properties. + array of PropertyPreview properties + # List of the entries. Specified for `map` and `set` subtype values only. + optional array of EntryPreview entries + + experimental type PropertyPreview extends object + properties + # Property name. + string name + # Object type. Accessor means that the property itself is an accessor property. + enum type + object + function + undefined + string + number + boolean + symbol + accessor + bigint + # User-friendly property value string. + optional string value + # Nested value preview. + optional ObjectPreview valuePreview + # Object subtype hint. Specified for `object` type values only. + optional enum subtype + array + null + node + regexp + date + map + set + weakmap + weakset + iterator + generator + error + proxy + promise + typedarray + arraybuffer + dataview + webassemblymemory + wasmvalue + + experimental type EntryPreview extends object + properties + # Preview of the key. Specified for map-like collection entries. + optional ObjectPreview key + # Preview of the value. + ObjectPreview value + + # Object property descriptor. + type PropertyDescriptor extends object + properties + # Property name or symbol description. + string name + # The value associated with the property. + optional RemoteObject value + # True if the value associated with the property may be changed (data descriptors only). + optional boolean writable + # A function which serves as a getter for the property, or `undefined` if there is no getter + # (accessor descriptors only). + optional RemoteObject get + # A function which serves as a setter for the property, or `undefined` if there is no setter + # (accessor descriptors only). + optional RemoteObject set + # True if the type of this property descriptor may be changed and if the property may be + # deleted from the corresponding object. + boolean configurable + # True if this property shows up during enumeration of the properties on the corresponding + # object. + boolean enumerable + # True if the result was thrown during the evaluation. + optional boolean wasThrown + # True if the property is owned for the object. + optional boolean isOwn + # Property symbol object, if the property is of the `symbol` type. + optional RemoteObject symbol + + # Object internal property descriptor. This property isn't normally visible in JavaScript code. + type InternalPropertyDescriptor extends object + properties + # Conventional property name. + string name + # The value associated with the property. + optional RemoteObject value + + # Object private field descriptor. + experimental type PrivatePropertyDescriptor extends object + properties + # Private property name. + string name + # The value associated with the private property. + optional RemoteObject value + # A function which serves as a getter for the private property, + # or `undefined` if there is no getter (accessor descriptors only). + optional RemoteObject get + # A function which serves as a setter for the private property, + # or `undefined` if there is no setter (accessor descriptors only). + optional RemoteObject set + + # Represents function call argument. Either remote object id `objectId`, primitive `value`, + # unserializable primitive value or neither of (for undefined) them should be specified. + type CallArgument extends object + properties + # Primitive value or serializable javascript object. + optional any value + # Primitive value which can not be JSON-stringified. + optional UnserializableValue unserializableValue + # Remote object handle. + optional RemoteObjectId objectId + + # Id of an execution context. + type ExecutionContextId extends integer + + # Description of an isolated world. + type ExecutionContextDescription extends object + properties + # Unique id of the execution context. It can be used to specify in which execution context + # script evaluation should be performed. + ExecutionContextId id + # Execution context origin. + string origin + # Human readable name describing given context. + string name + # A system-unique execution context identifier. Unlike the id, this is unique across + # multiple processes, so can be reliably used to identify specific context while backend + # performs a cross-process navigation. + experimental string uniqueId + # Embedder-specific auxiliary data likely matching {isDefault: boolean, type: 'default'|'isolated'|'worker', frameId: string} + optional object auxData + + # Detailed information about exception (or error) that was thrown during script compilation or + # execution. + type ExceptionDetails extends object + properties + # Exception id. + integer exceptionId + # Exception text, which should be used together with exception object when available. + string text + # Line number of the exception location (0-based). + integer lineNumber + # Column number of the exception location (0-based). + integer columnNumber + # Script ID of the exception location. + optional ScriptId scriptId + # URL of the exception location, to be used when the script was not reported. + optional string url + # JavaScript stack trace if available. + optional StackTrace stackTrace + # Exception object if available. + optional RemoteObject exception + # Identifier of the context where exception happened. + optional ExecutionContextId executionContextId + # Dictionary with entries of meta data that the client associated + # with this exception, such as information about associated network + # requests, etc. + experimental optional object exceptionMetaData + + # Number of milliseconds since epoch. + type Timestamp extends number + + # Number of milliseconds. + type TimeDelta extends number + + # Stack entry for runtime errors and assertions. + type CallFrame extends object + properties + # JavaScript function name. + string functionName + # JavaScript script id. + ScriptId scriptId + # JavaScript script name or url. + string url + # JavaScript script line number (0-based). + integer lineNumber + # JavaScript script column number (0-based). + integer columnNumber + + # Call frames for assertions or error messages. + type StackTrace extends object + properties + # String label of this stack trace. For async traces this may be a name of the function that + # initiated the async call. + optional string description + # JavaScript function name. + array of CallFrame callFrames + # Asynchronous JavaScript stack trace that preceded this stack, if available. + optional StackTrace parent + # Asynchronous JavaScript stack trace that preceded this stack, if available. + experimental optional StackTraceId parentId + + # Unique identifier of current debugger. + experimental type UniqueDebuggerId extends string + + # If `debuggerId` is set stack trace comes from another debugger and can be resolved there. This + # allows to track cross-debugger calls. See `Runtime.StackTrace` and `Debugger.paused` for usages. + experimental type StackTraceId extends object + properties + string id + optional UniqueDebuggerId debuggerId + + # Add handler to promise with given promise object id. + command awaitPromise + parameters + # Identifier of the promise. + RemoteObjectId promiseObjectId + # Whether the result is expected to be a JSON object that should be sent by value. + optional boolean returnByValue + # Whether preview should be generated for the result. + optional boolean generatePreview + returns + # Promise result. Will contain rejected value if promise was rejected. + RemoteObject result + # Exception details if stack strace is available. + optional ExceptionDetails exceptionDetails + + # Calls function with given declaration on the given object. Object group of the result is + # inherited from the target object. + command callFunctionOn + parameters + # Declaration of the function to call. + string functionDeclaration + # Identifier of the object to call function on. Either objectId or executionContextId should + # be specified. + optional RemoteObjectId objectId + # Call arguments. All call arguments must belong to the same JavaScript world as the target + # object. + optional array of CallArgument arguments + # In silent mode exceptions thrown during evaluation are not reported and do not pause + # execution. Overrides `setPauseOnException` state. + optional boolean silent + # Whether the result is expected to be a JSON object which should be sent by value. + # Can be overriden by `serializationOptions`. + optional boolean returnByValue + # Whether preview should be generated for the result. + experimental optional boolean generatePreview + # Whether execution should be treated as initiated by user in the UI. + optional boolean userGesture + # Whether execution should `await` for resulting value and return once awaited promise is + # resolved. + optional boolean awaitPromise + # Specifies execution context which global object will be used to call function on. Either + # executionContextId or objectId should be specified. + optional ExecutionContextId executionContextId + # Symbolic group name that can be used to release multiple objects. If objectGroup is not + # specified and objectId is, objectGroup will be inherited from object. + optional string objectGroup + # Whether to throw an exception if side effect cannot be ruled out during evaluation. + experimental optional boolean throwOnSideEffect + # An alternative way to specify the execution context to call function on. + # Compared to contextId that may be reused across processes, this is guaranteed to be + # system-unique, so it can be used to prevent accidental function call + # in context different than intended (e.g. as a result of navigation across process + # boundaries). + # This is mutually exclusive with `executionContextId`. + experimental optional string uniqueContextId + # Specifies the result serialization. If provided, overrides + # `generatePreview` and `returnByValue`. + experimental optional SerializationOptions serializationOptions + + returns + # Call result. + RemoteObject result + # Exception details. + optional ExceptionDetails exceptionDetails + + # Compiles expression. + command compileScript + parameters + # Expression to compile. + string expression + # Source url to be set for the script. + string sourceURL + # Specifies whether the compiled script should be persisted. + boolean persistScript + # Specifies in which execution context to perform script run. If the parameter is omitted the + # evaluation will be performed in the context of the inspected page. + optional ExecutionContextId executionContextId + returns + # Id of the script. + optional ScriptId scriptId + # Exception details. + optional ExceptionDetails exceptionDetails + + # Disables reporting of execution contexts creation. + command disable + + # Discards collected exceptions and console API calls. + command discardConsoleEntries + + # Enables reporting of execution contexts creation by means of `executionContextCreated` event. + # When the reporting gets enabled the event will be sent immediately for each existing execution + # context. + command enable + + # Evaluates expression on global object. + command evaluate + parameters + # Expression to evaluate. + string expression + # Symbolic group name that can be used to release multiple objects. + optional string objectGroup + # Determines whether Command Line API should be available during the evaluation. + optional boolean includeCommandLineAPI + # In silent mode exceptions thrown during evaluation are not reported and do not pause + # execution. Overrides `setPauseOnException` state. + optional boolean silent + # Specifies in which execution context to perform evaluation. If the parameter is omitted the + # evaluation will be performed in the context of the inspected page. + # This is mutually exclusive with `uniqueContextId`, which offers an + # alternative way to identify the execution context that is more reliable + # in a multi-process environment. + optional ExecutionContextId contextId + # Whether the result is expected to be a JSON object that should be sent by value. + optional boolean returnByValue + # Whether preview should be generated for the result. + experimental optional boolean generatePreview + # Whether execution should be treated as initiated by user in the UI. + optional boolean userGesture + # Whether execution should `await` for resulting value and return once awaited promise is + # resolved. + optional boolean awaitPromise + # Whether to throw an exception if side effect cannot be ruled out during evaluation. + # This implies `disableBreaks` below. + experimental optional boolean throwOnSideEffect + # Terminate execution after timing out (number of milliseconds). + experimental optional TimeDelta timeout + # Disable breakpoints during execution. + experimental optional boolean disableBreaks + # Setting this flag to true enables `let` re-declaration and top-level `await`. + # Note that `let` variables can only be re-declared if they originate from + # `replMode` themselves. + experimental optional boolean replMode + # The Content Security Policy (CSP) for the target might block 'unsafe-eval' + # which includes eval(), Function(), setTimeout() and setInterval() + # when called with non-callable arguments. This flag bypasses CSP for this + # evaluation and allows unsafe-eval. Defaults to true. + experimental optional boolean allowUnsafeEvalBlockedByCSP + # An alternative way to specify the execution context to evaluate in. + # Compared to contextId that may be reused across processes, this is guaranteed to be + # system-unique, so it can be used to prevent accidental evaluation of the expression + # in context different than intended (e.g. as a result of navigation across process + # boundaries). + # This is mutually exclusive with `contextId`. + experimental optional string uniqueContextId + # Specifies the result serialization. If provided, overrides + # `generatePreview` and `returnByValue`. + experimental optional SerializationOptions serializationOptions + returns + # Evaluation result. + RemoteObject result + # Exception details. + optional ExceptionDetails exceptionDetails + + # Returns the isolate id. + experimental command getIsolateId + returns + # The isolate id. + string id + + # Returns the JavaScript heap usage. + # It is the total usage of the corresponding isolate not scoped to a particular Runtime. + experimental command getHeapUsage + returns + # Used JavaScript heap size in bytes. + number usedSize + # Allocated JavaScript heap size in bytes. + number totalSize + # Used size in bytes in the embedder's garbage-collected heap. + number embedderHeapUsedSize + # Size in bytes of backing storage for array buffers and external strings. + number backingStorageSize + + # Returns properties of a given object. Object group of the result is inherited from the target + # object. + command getProperties + parameters + # Identifier of the object to return properties for. + RemoteObjectId objectId + # If true, returns properties belonging only to the element itself, not to its prototype + # chain. + optional boolean ownProperties + # If true, returns accessor properties (with getter/setter) only; internal properties are not + # returned either. + experimental optional boolean accessorPropertiesOnly + # Whether preview should be generated for the results. + experimental optional boolean generatePreview + # If true, returns non-indexed properties only. + experimental optional boolean nonIndexedPropertiesOnly + returns + # Object properties. + array of PropertyDescriptor result + # Internal object properties (only of the element itself). + optional array of InternalPropertyDescriptor internalProperties + # Object private properties. + experimental optional array of PrivatePropertyDescriptor privateProperties + # Exception details. + optional ExceptionDetails exceptionDetails + + # Returns all let, const and class variables from global scope. + command globalLexicalScopeNames + parameters + # Specifies in which execution context to lookup global scope variables. + optional ExecutionContextId executionContextId + returns + array of string names + + command queryObjects + parameters + # Identifier of the prototype to return objects for. + RemoteObjectId prototypeObjectId + # Symbolic group name that can be used to release the results. + optional string objectGroup + returns + # Array with objects. + RemoteObject objects + + # Releases remote object with given id. + command releaseObject + parameters + # Identifier of the object to release. + RemoteObjectId objectId + + # Releases all remote objects that belong to a given group. + command releaseObjectGroup + parameters + # Symbolic object group name. + string objectGroup + + # Tells inspected instance to run if it was waiting for debugger to attach. + command runIfWaitingForDebugger + + # Runs script with given id in a given context. + command runScript + parameters + # Id of the script to run. + ScriptId scriptId + # Specifies in which execution context to perform script run. If the parameter is omitted the + # evaluation will be performed in the context of the inspected page. + optional ExecutionContextId executionContextId + # Symbolic group name that can be used to release multiple objects. + optional string objectGroup + # In silent mode exceptions thrown during evaluation are not reported and do not pause + # execution. Overrides `setPauseOnException` state. + optional boolean silent + # Determines whether Command Line API should be available during the evaluation. + optional boolean includeCommandLineAPI + # Whether the result is expected to be a JSON object which should be sent by value. + optional boolean returnByValue + # Whether preview should be generated for the result. + optional boolean generatePreview + # Whether execution should `await` for resulting value and return once awaited promise is + # resolved. + optional boolean awaitPromise + returns + # Run result. + RemoteObject result + # Exception details. + optional ExceptionDetails exceptionDetails + + # Enables or disables async call stacks tracking. + command setAsyncCallStackDepth + redirect Debugger + parameters + # Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async + # call stacks (default). + integer maxDepth + + experimental command setCustomObjectFormatterEnabled + parameters + boolean enabled + + experimental command setMaxCallStackSizeToCapture + parameters + integer size + + # Terminate current or next JavaScript execution. + # Will cancel the termination when the outer-most script execution ends. + experimental command terminateExecution + + # If executionContextId is empty, adds binding with the given name on the + # global objects of all inspected contexts, including those created later, + # bindings survive reloads. + # Binding function takes exactly one argument, this argument should be string, + # in case of any other input, function throws an exception. + # Each binding function call produces Runtime.bindingCalled notification. + command addBinding + parameters + string name + # If specified, the binding would only be exposed to the specified + # execution context. If omitted and `executionContextName` is not set, + # the binding is exposed to all execution contexts of the target. + # This parameter is mutually exclusive with `executionContextName`. + # Deprecated in favor of `executionContextName` due to an unclear use case + # and bugs in implementation (crbug.com/1169639). `executionContextId` will be + # removed in the future. + experimental deprecated optional ExecutionContextId executionContextId + # If specified, the binding is exposed to the executionContext with + # matching name, even for contexts created after the binding is added. + # See also `ExecutionContext.name` and `worldName` parameter to + # `Page.addScriptToEvaluateOnNewDocument`. + # This parameter is mutually exclusive with `executionContextId`. + optional string executionContextName + + # This method does not remove binding function from global object but + # unsubscribes current runtime agent from Runtime.bindingCalled notifications. + command removeBinding + parameters + string name + + # This method tries to lookup and populate exception details for a + # JavaScript Error object. + # Note that the stackTrace portion of the resulting exceptionDetails will + # only be populated if the Runtime domain was enabled at the time when the + # Error was thrown. + experimental command getExceptionDetails + parameters + # The error object for which to resolve the exception details. + RemoteObjectId errorObjectId + returns + optional ExceptionDetails exceptionDetails + + # Notification is issued every time when binding is called. + experimental event bindingCalled + parameters + string name + string payload + # Identifier of the context where the call was made. + ExecutionContextId executionContextId + + # Issued when console API was called. + event consoleAPICalled + parameters + # Type of the call. + enum type + log + debug + info + error + warning + dir + dirxml + table + trace + clear + startGroup + startGroupCollapsed + endGroup + assert + profile + profileEnd + count + timeEnd + # Call arguments. + array of RemoteObject args + # Identifier of the context where the call was made. + ExecutionContextId executionContextId + # Call timestamp. + Timestamp timestamp + # Stack trace captured when the call was made. The async stack chain is automatically reported for + # the following call types: `assert`, `error`, `trace`, `warning`. For other types the async call + # chain can be retrieved using `Debugger.getStackTrace` and `stackTrace.parentId` field. + optional StackTrace stackTrace + # Console context descriptor for calls on non-default console context (not console.*): + # 'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call + # on named context. + experimental optional string context + + # Issued when unhandled exception was revoked. + event exceptionRevoked + parameters + # Reason describing why exception was revoked. + string reason + # The id of revoked exception, as reported in `exceptionThrown`. + integer exceptionId + + # Issued when exception was thrown and unhandled. + event exceptionThrown + parameters + # Timestamp of the exception. + Timestamp timestamp + ExceptionDetails exceptionDetails + + # Issued when new execution context is created. + event executionContextCreated + parameters + # A newly created execution context. + ExecutionContextDescription context + + # Issued when execution context is destroyed. + event executionContextDestroyed + parameters + # Id of the destroyed context + deprecated ExecutionContextId executionContextId + # Unique Id of the destroyed context + experimental string executionContextUniqueId + + # Issued when all executionContexts were cleared in browser + event executionContextsCleared + + # Issued when object should be inspected (for example, as a result of inspect() command line API + # call). + event inspectRequested + parameters + RemoteObject object + object hints + # Identifier of the context where the call was made. + experimental optional ExecutionContextId executionContextId + +# This domain is deprecated. +deprecated domain Schema + + # Description of the protocol domain. + type Domain extends object + properties + # Domain name. + string name + # Domain version. + string version + + # Returns supported domains. + command getDomains + returns + # List of supported domains. + array of Domain domains diff --git a/NativeScript/napi/android/v8-13/include/libplatform/DEPS b/NativeScript/napi/android/v8-13/include/libplatform/DEPS new file mode 100644 index 000000000..d8bcf9988 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/libplatform/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "+libplatform/libplatform-export.h", +] + +specific_include_rules = { + "libplatform\.h": [ + "+libplatform/v8-tracing.h", + ], +} diff --git a/NativeScript/napi/android/v8-13/include/libplatform/libplatform-export.h b/NativeScript/napi/android/v8-13/include/libplatform/libplatform-export.h new file mode 100644 index 000000000..285db86ab --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/libplatform/libplatform-export.h @@ -0,0 +1,29 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_LIBPLATFORM_LIBPLATFORM_EXPORT_H_ +#define V8_LIBPLATFORM_LIBPLATFORM_EXPORT_H_ + +#if defined(_WIN32) + +#ifdef BUILDING_V8_PLATFORM_SHARED +#define V8_PLATFORM_EXPORT __declspec(dllexport) +#elif USING_V8_PLATFORM_SHARED +#define V8_PLATFORM_EXPORT __declspec(dllimport) +#else +#define V8_PLATFORM_EXPORT +#endif // BUILDING_V8_PLATFORM_SHARED + +#else // defined(_WIN32) + +// Setup for Linux shared library export. +#if defined(BUILDING_V8_PLATFORM_SHARED) || USING_V8_PLATFORM_SHARED +#define V8_PLATFORM_EXPORT __attribute__((visibility("default"))) +#else +#define V8_PLATFORM_EXPORT +#endif // defined(BUILDING_V8_PLATFORM_SHARED) || ... + +#endif // defined(_WIN32) + +#endif // V8_LIBPLATFORM_LIBPLATFORM_EXPORT_H_ diff --git a/NativeScript/napi/android/v8-13/include/libplatform/libplatform.h b/NativeScript/napi/android/v8-13/include/libplatform/libplatform.h new file mode 100644 index 000000000..6a34f4324 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/libplatform/libplatform.h @@ -0,0 +1,112 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_LIBPLATFORM_LIBPLATFORM_H_ +#define V8_LIBPLATFORM_LIBPLATFORM_H_ + +#include + +#include "libplatform/libplatform-export.h" +#include "libplatform/v8-tracing.h" +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { +namespace platform { + +enum class IdleTaskSupport { kDisabled, kEnabled }; +enum class InProcessStackDumping { kDisabled, kEnabled }; + +enum class MessageLoopBehavior : bool { + kDoNotWait = false, + kWaitForWork = true +}; + +enum class PriorityMode : bool { kDontApply, kApply }; + +/** + * Returns a new instance of the default v8::Platform implementation. + * + * The caller will take ownership of the returned pointer. |thread_pool_size| + * is the number of worker threads to allocate for background jobs. If a value + * of zero is passed, a suitable default based on the current number of + * processors online will be chosen. + * If |idle_task_support| is enabled then the platform will accept idle + * tasks (IdleTasksEnabled will return true) and will rely on the embedder + * calling v8::platform::RunIdleTasks to process the idle tasks. + * If |tracing_controller| is nullptr, the default platform will create a + * v8::platform::TracingController instance and use it. + * If |priority_mode| is PriorityMode::kApply, the default platform will use + * multiple task queues executed by threads different system-level priorities + * (where available) to schedule tasks. + */ +V8_PLATFORM_EXPORT std::unique_ptr NewDefaultPlatform( + int thread_pool_size = 0, + IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled, + InProcessStackDumping in_process_stack_dumping = + InProcessStackDumping::kDisabled, + std::unique_ptr tracing_controller = {}, + PriorityMode priority_mode = PriorityMode::kDontApply); + +/** + * The same as NewDefaultPlatform but disables the worker thread pool. + * It must be used with the --single-threaded V8 flag. + */ +V8_PLATFORM_EXPORT std::unique_ptr +NewSingleThreadedDefaultPlatform( + IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled, + InProcessStackDumping in_process_stack_dumping = + InProcessStackDumping::kDisabled, + std::unique_ptr tracing_controller = {}); + +/** + * Returns a new instance of the default v8::JobHandle implementation. + * + * The job will be executed by spawning up to |num_worker_threads| many worker + * threads on the provided |platform| with the given |priority|. + */ +V8_PLATFORM_EXPORT std::unique_ptr NewDefaultJobHandle( + v8::Platform* platform, v8::TaskPriority priority, + std::unique_ptr job_task, size_t num_worker_threads); + +/** + * Pumps the message loop for the given isolate. + * + * The caller has to make sure that this is called from the right thread. + * Returns true if a task was executed, and false otherwise. If the call to + * PumpMessageLoop is nested within another call to PumpMessageLoop, only + * nestable tasks may run. Otherwise, any task may run. Unless requested through + * the |behavior| parameter, this call does not block if no task is pending. The + * |platform| has to be created using |NewDefaultPlatform|. + */ +V8_PLATFORM_EXPORT bool PumpMessageLoop( + v8::Platform* platform, v8::Isolate* isolate, + MessageLoopBehavior behavior = MessageLoopBehavior::kDoNotWait); + +/** + * Runs pending idle tasks for at most |idle_time_in_seconds| seconds. + * + * The caller has to make sure that this is called from the right thread. + * This call does not block if no task is pending. The |platform| has to be + * created using |NewDefaultPlatform|. + */ +V8_PLATFORM_EXPORT void RunIdleTasks(v8::Platform* platform, + v8::Isolate* isolate, + double idle_time_in_seconds); + +/** + * Notifies the given platform about the Isolate getting deleted soon. Has to be + * called for all Isolates which are deleted - unless we're shutting down the + * platform. + * + * The |platform| has to be created using |NewDefaultPlatform|. + * + */ +V8_PLATFORM_EXPORT void NotifyIsolateShutdown(v8::Platform* platform, + Isolate* isolate); + +} // namespace platform +} // namespace v8 + +#endif // V8_LIBPLATFORM_LIBPLATFORM_H_ diff --git a/NativeScript/napi/android/v8-13/include/libplatform/v8-tracing.h b/NativeScript/napi/android/v8-13/include/libplatform/v8-tracing.h new file mode 100644 index 000000000..227172924 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/libplatform/v8-tracing.h @@ -0,0 +1,332 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_LIBPLATFORM_V8_TRACING_H_ +#define V8_LIBPLATFORM_V8_TRACING_H_ + +#include +#include +#include +#include + +#include "libplatform/libplatform-export.h" +#include "v8-platform.h" // NOLINT(build/include_directory) + +namespace perfetto { +namespace trace_processor { +class TraceProcessorStorage; +} +class TracingSession; +} + +namespace v8 { + +namespace base { +class Mutex; +} // namespace base + +namespace platform { +namespace tracing { + +class TraceEventListener; + +const int kTraceMaxNumArgs = 2; + +class V8_PLATFORM_EXPORT TraceObject { + public: + union ArgValue { + uint64_t as_uint; + int64_t as_int; + double as_double; + const void* as_pointer; + const char* as_string; + }; + + TraceObject() = default; + ~TraceObject(); + void Initialize( + char phase, const uint8_t* category_enabled_flag, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags, int64_t timestamp, int64_t cpu_timestamp); + void UpdateDuration(int64_t timestamp, int64_t cpu_timestamp); + void InitializeForTesting( + char phase, const uint8_t* category_enabled_flag, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags, int pid, int tid, int64_t ts, int64_t tts, + uint64_t duration, uint64_t cpu_duration); + + int pid() const { return pid_; } + int tid() const { return tid_; } + char phase() const { return phase_; } + const uint8_t* category_enabled_flag() const { + return category_enabled_flag_; + } + const char* name() const { return name_; } + const char* scope() const { return scope_; } + uint64_t id() const { return id_; } + uint64_t bind_id() const { return bind_id_; } + int num_args() const { return num_args_; } + const char** arg_names() { return arg_names_; } + uint8_t* arg_types() { return arg_types_; } + ArgValue* arg_values() { return arg_values_; } + std::unique_ptr* arg_convertables() { + return arg_convertables_; + } + unsigned int flags() const { return flags_; } + int64_t ts() { return ts_; } + int64_t tts() { return tts_; } + uint64_t duration() { return duration_; } + uint64_t cpu_duration() { return cpu_duration_; } + + private: + int pid_; + int tid_; + char phase_; + const char* name_; + const char* scope_; + const uint8_t* category_enabled_flag_; + uint64_t id_; + uint64_t bind_id_; + int num_args_ = 0; + const char* arg_names_[kTraceMaxNumArgs]; + uint8_t arg_types_[kTraceMaxNumArgs]; + ArgValue arg_values_[kTraceMaxNumArgs]; + std::unique_ptr + arg_convertables_[kTraceMaxNumArgs]; + char* parameter_copy_storage_ = nullptr; + unsigned int flags_; + int64_t ts_; + int64_t tts_; + uint64_t duration_; + uint64_t cpu_duration_; + + // Disallow copy and assign + TraceObject(const TraceObject&) = delete; + void operator=(const TraceObject&) = delete; +}; + +class V8_PLATFORM_EXPORT TraceWriter { + public: + TraceWriter() = default; + virtual ~TraceWriter() = default; + virtual void AppendTraceEvent(TraceObject* trace_event) = 0; + virtual void Flush() = 0; + + static TraceWriter* CreateJSONTraceWriter(std::ostream& stream); + static TraceWriter* CreateJSONTraceWriter(std::ostream& stream, + const std::string& tag); + + static TraceWriter* CreateSystemInstrumentationTraceWriter(); + + private: + // Disallow copy and assign + TraceWriter(const TraceWriter&) = delete; + void operator=(const TraceWriter&) = delete; +}; + +class V8_PLATFORM_EXPORT TraceBufferChunk { + public: + explicit TraceBufferChunk(uint32_t seq); + + void Reset(uint32_t new_seq); + bool IsFull() const { return next_free_ == kChunkSize; } + TraceObject* AddTraceEvent(size_t* event_index); + TraceObject* GetEventAt(size_t index) { return &chunk_[index]; } + + uint32_t seq() const { return seq_; } + size_t size() const { return next_free_; } + + static const size_t kChunkSize = 64; + + private: + size_t next_free_ = 0; + TraceObject chunk_[kChunkSize]; + uint32_t seq_; + + // Disallow copy and assign + TraceBufferChunk(const TraceBufferChunk&) = delete; + void operator=(const TraceBufferChunk&) = delete; +}; + +class V8_PLATFORM_EXPORT TraceBuffer { + public: + TraceBuffer() = default; + virtual ~TraceBuffer() = default; + + virtual TraceObject* AddTraceEvent(uint64_t* handle) = 0; + virtual TraceObject* GetEventByHandle(uint64_t handle) = 0; + virtual bool Flush() = 0; + + static const size_t kRingBufferChunks = 1024; + + static TraceBuffer* CreateTraceBufferRingBuffer(size_t max_chunks, + TraceWriter* trace_writer); + + private: + // Disallow copy and assign + TraceBuffer(const TraceBuffer&) = delete; + void operator=(const TraceBuffer&) = delete; +}; + +// Options determines how the trace buffer stores data. +enum TraceRecordMode { + // Record until the trace buffer is full. + RECORD_UNTIL_FULL, + + // Record until the user ends the trace. The trace buffer is a fixed size + // and we use it as a ring buffer during recording. + RECORD_CONTINUOUSLY, + + // Record until the trace buffer is full, but with a huge buffer size. + RECORD_AS_MUCH_AS_POSSIBLE, + + // Echo to console. Events are discarded. + ECHO_TO_CONSOLE, +}; + +class V8_PLATFORM_EXPORT TraceConfig { + public: + typedef std::vector StringList; + + static TraceConfig* CreateDefaultTraceConfig(); + + TraceConfig() : enable_systrace_(false), enable_argument_filter_(false) {} + TraceRecordMode GetTraceRecordMode() const { return record_mode_; } + const StringList& GetEnabledCategories() const { + return included_categories_; + } + bool IsSystraceEnabled() const { return enable_systrace_; } + bool IsArgumentFilterEnabled() const { return enable_argument_filter_; } + + void SetTraceRecordMode(TraceRecordMode mode) { record_mode_ = mode; } + void EnableSystrace() { enable_systrace_ = true; } + void EnableArgumentFilter() { enable_argument_filter_ = true; } + + void AddIncludedCategory(const char* included_category); + + bool IsCategoryGroupEnabled(const char* category_group) const; + + private: + TraceRecordMode record_mode_; + bool enable_systrace_ : 1; + bool enable_argument_filter_ : 1; + StringList included_categories_; + + // Disallow copy and assign + TraceConfig(const TraceConfig&) = delete; + void operator=(const TraceConfig&) = delete; +}; + +#if defined(_MSC_VER) +#define V8_PLATFORM_NON_EXPORTED_BASE(code) \ + __pragma(warning(suppress : 4275)) code +#else +#define V8_PLATFORM_NON_EXPORTED_BASE(code) code +#endif // defined(_MSC_VER) + +class V8_PLATFORM_EXPORT TracingController + : public V8_PLATFORM_NON_EXPORTED_BASE(v8::TracingController) { + public: + TracingController(); + ~TracingController() override; + +#if defined(V8_USE_PERFETTO) + // Must be called before StartTracing() if V8_USE_PERFETTO is true. Provides + // the output stream for the JSON trace data. + void InitializeForPerfetto(std::ostream* output_stream); + // Provide an optional listener for testing that will receive trace events. + // Must be called before StartTracing(). + void SetTraceEventListenerForTesting(TraceEventListener* listener); +#else // defined(V8_USE_PERFETTO) + // The pointer returned from GetCategoryGroupEnabled() points to a value with + // zero or more of the following bits. Used in this class only. The + // TRACE_EVENT macros should only use the value as a bool. These values must + // be in sync with macro values in TraceEvent.h in Blink. + enum CategoryGroupEnabledFlags { + // Category group enabled for the recording mode. + ENABLED_FOR_RECORDING = 1 << 0, + // Category group enabled by SetEventCallbackEnabled(). + ENABLED_FOR_EVENT_CALLBACK = 1 << 2, + // Category group enabled to export events to ETW. + ENABLED_FOR_ETW_EXPORT = 1 << 3 + }; + + // Takes ownership of |trace_buffer|. + void Initialize(TraceBuffer* trace_buffer); + + // v8::TracingController implementation. + const uint8_t* GetCategoryGroupEnabled(const char* category_group) override; + uint64_t AddTraceEvent( + char phase, const uint8_t* category_enabled_flag, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags) override; + uint64_t AddTraceEventWithTimestamp( + char phase, const uint8_t* category_enabled_flag, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags, int64_t timestamp) override; + void UpdateTraceEventDuration(const uint8_t* category_enabled_flag, + const char* name, uint64_t handle) override; + + static const char* GetCategoryGroupName(const uint8_t* category_enabled_flag); + + void AddTraceStateObserver( + v8::TracingController::TraceStateObserver* observer) override; + void RemoveTraceStateObserver( + v8::TracingController::TraceStateObserver* observer) override; +#endif // !defined(V8_USE_PERFETTO) + + void StartTracing(TraceConfig* trace_config); + void StopTracing(); + + protected: +#if !defined(V8_USE_PERFETTO) + virtual int64_t CurrentTimestampMicroseconds(); + virtual int64_t CurrentCpuTimestampMicroseconds(); +#endif // !defined(V8_USE_PERFETTO) + + private: +#if !defined(V8_USE_PERFETTO) + void UpdateCategoryGroupEnabledFlag(size_t category_index); + void UpdateCategoryGroupEnabledFlags(); +#endif // !defined(V8_USE_PERFETTO) + + std::unique_ptr mutex_; + std::unique_ptr trace_config_; + std::atomic_bool recording_{false}; + +#if defined(V8_USE_PERFETTO) + std::ostream* output_stream_ = nullptr; + std::unique_ptr + trace_processor_; + TraceEventListener* listener_for_testing_ = nullptr; + std::unique_ptr tracing_session_; +#else // !defined(V8_USE_PERFETTO) + std::unordered_set observers_; + std::unique_ptr trace_buffer_; +#endif // !defined(V8_USE_PERFETTO) + + // Disallow copy and assign + TracingController(const TracingController&) = delete; + void operator=(const TracingController&) = delete; +}; + +#undef V8_PLATFORM_NON_EXPORTED_BASE + +} // namespace tracing +} // namespace platform +} // namespace v8 + +#endif // V8_LIBPLATFORM_V8_TRACING_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-array-buffer.h b/NativeScript/napi/android/v8-13/include/v8-array-buffer.h new file mode 100644 index 000000000..1d9345762 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-array-buffer.h @@ -0,0 +1,615 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_ARRAY_BUFFER_H_ +#define INCLUDE_V8_ARRAY_BUFFER_H_ + +#include + +#include + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-memory-span.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class SharedArrayBuffer; + +#if defined(V8_COMPRESS_POINTERS) && \ + !defined(V8_COMPRESS_POINTERS_IN_SHARED_CAGE) +class IsolateGroup; +#endif + +#ifndef V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT +// Defined using gn arg `v8_array_buffer_internal_field_count`. +#define V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT 2 +#endif + +enum class ArrayBufferCreationMode { kInternalized, kExternalized }; +enum class BackingStoreInitializationMode { kZeroInitialized, kUninitialized }; +enum class BackingStoreOnFailureMode { kReturnNull, kOutOfMemory }; + +/** + * A wrapper around the backing store (i.e. the raw memory) of an array buffer. + * See a document linked in http://crbug.com/v8/9908 for more information. + * + * The allocation and destruction of backing stores is generally managed by + * V8. Clients should always use standard C++ memory ownership types (i.e. + * std::unique_ptr and std::shared_ptr) to manage lifetimes of backing stores + * properly, since V8 internal objects may alias backing stores. + * + * This object does not keep the underlying |ArrayBuffer::Allocator| alive by + * default. Use Isolate::CreateParams::array_buffer_allocator_shared when + * creating the Isolate to make it hold a reference to the allocator itself. + */ +class V8_EXPORT BackingStore : public v8::internal::BackingStoreBase { + public: + ~BackingStore(); + + /** + * Return a pointer to the beginning of the memory block for this backing + * store. The pointer is only valid as long as this backing store object + * lives. + */ + void* Data() const; + + /** + * The length (in bytes) of this backing store. + */ + size_t ByteLength() const; + + /** + * The maximum length (in bytes) that this backing store may grow to. + * + * If this backing store was created for a resizable ArrayBuffer or a growable + * SharedArrayBuffer, it is >= ByteLength(). Otherwise it is == + * ByteLength(). + */ + size_t MaxByteLength() const; + + /** + * Indicates whether the backing store was created for an ArrayBuffer or + * a SharedArrayBuffer. + */ + bool IsShared() const; + + /** + * Indicates whether the backing store was created for a resizable ArrayBuffer + * or a growable SharedArrayBuffer, and thus may be resized by user JavaScript + * code. + */ + bool IsResizableByUserJavaScript() const; + + /** + * Prevent implicit instantiation of operator delete with size_t argument. + * The size_t argument would be incorrect because ptr points to the + * internal BackingStore object. + */ + void operator delete(void* ptr) { ::operator delete(ptr); } + + /** + * This callback is used only if the memory block for a BackingStore cannot be + * allocated with an ArrayBuffer::Allocator. In such cases the destructor of + * the BackingStore invokes the callback to free the memory block. + */ + using DeleterCallback = void (*)(void* data, size_t length, + void* deleter_data); + + /** + * If the memory block of a BackingStore is static or is managed manually, + * then this empty deleter along with nullptr deleter_data can be passed to + * ArrayBuffer::NewBackingStore to indicate that. + * + * The manually managed case should be used with caution and only when it + * is guaranteed that the memory block freeing happens after detaching its + * ArrayBuffer. + */ + static void EmptyDeleter(void* data, size_t length, void* deleter_data); + + private: + /** + * See [Shared]ArrayBuffer::GetBackingStore and + * [Shared]ArrayBuffer::NewBackingStore. + */ + BackingStore(); +}; + +#if !defined(V8_IMMINENT_DEPRECATION_WARNINGS) +// Use v8::BackingStore::DeleterCallback instead. +using BackingStoreDeleterCallback = void (*)(void* data, size_t length, + void* deleter_data); + +#endif + +/** + * An instance of the built-in ArrayBuffer constructor (ES6 draft 15.13.5). + */ +class V8_EXPORT ArrayBuffer : public Object { + public: + /** + * A thread-safe allocator that V8 uses to allocate |ArrayBuffer|'s memory. + * The allocator is a global V8 setting. It has to be set via + * Isolate::CreateParams. + * + * Memory allocated through this allocator by V8 is accounted for as external + * memory by V8. Note that V8 keeps track of the memory for all internalized + * |ArrayBuffer|s. Responsibility for tracking external memory (using + * Isolate::AdjustAmountOfExternalAllocatedMemory) is handed over to the + * embedder upon externalization and taken over upon internalization (creating + * an internalized buffer from an existing buffer). + * + * Note that it is unsafe to call back into V8 from any of the allocator + * functions. + */ + class V8_EXPORT Allocator { + public: + virtual ~Allocator() = default; + + /** + * Allocate |length| bytes. Return nullptr if allocation is not successful. + * Memory should be initialized to zeroes. + */ + virtual void* Allocate(size_t length) = 0; + + /** + * Allocate |length| bytes. Return nullptr if allocation is not successful. + * Memory does not have to be initialized. + */ + virtual void* AllocateUninitialized(size_t length) = 0; + + /** + * Free the memory block of size |length|, pointed to by |data|. + * That memory is guaranteed to be previously allocated by |Allocate|. + */ + virtual void Free(void* data, size_t length) = 0; + + /** + * Returns a size_t that determines the largest ArrayBuffer that can be + * allocated. Override if your Allocator is more restrictive than the + * default. Will only be called once, and the value returned will be + * cached. + * Should not return a value that is larger than kMaxByteLength. + */ + virtual size_t MaxAllocationSize() const { return kMaxByteLength; } + + /** + * ArrayBuffer allocation mode. kNormal is a malloc/free style allocation, + * while kReservation is for larger allocations with the ability to set + * access permissions. + */ + enum class AllocationMode { kNormal, kReservation }; + + /** + * Returns page allocator used by this Allocator instance. + * + * When the sandbox used by Allocator it is expected that this returns + * sandbox's page allocator. + * Otherwise, it should return system page allocator. + */ + virtual PageAllocator* GetPageAllocator() { return nullptr; } + +#if defined(V8_COMPRESS_POINTERS) && \ + !defined(V8_COMPRESS_POINTERS_IN_SHARED_CAGE) + /** + * Convenience allocator. + * + * When the sandbox is enabled, this allocator will allocate its backing + * memory inside the sandbox that belongs to passed isolate group. + * Otherwise, it will rely on malloc/free. + * + * Caller takes ownership, i.e. the returned object needs to be freed using + * |delete allocator| once it is no longer in use. + */ + static Allocator* NewDefaultAllocator(const IsolateGroup& group); +#endif // defined(V8_COMPRESS_POINTERS) && + // !defined(V8_COMPRESS_POINTERS_IN_SHARED_CAGE) + + /** + * Convenience allocator. + * + * When the sandbox is enabled, this allocator will allocate its backing + * memory inside the default global sandbox. Otherwise, it will rely on + * malloc/free. + * + * Caller takes ownership, i.e. the returned object needs to be freed using + * |delete allocator| once it is no longer in use. + */ + static Allocator* NewDefaultAllocator(); + }; + + /** + * Data length in bytes. + */ + size_t ByteLength() const; + + /** + * Maximum length in bytes. + */ + size_t MaxByteLength() const; + + /** + * Attempt to create a new ArrayBuffer. Allocate |byte_length| bytes. + * Allocated memory will be owned by a created ArrayBuffer and + * will be deallocated when it is garbage-collected, + * unless the object is externalized. If allocation fails, the Maybe + * returned will be empty. + */ + static MaybeLocal MaybeNew( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized); + + /** + * Create a new ArrayBuffer. Allocate |byte_length| bytes, which are either + * zero-initialized or uninitialized. Allocated memory will be owned by a + * created ArrayBuffer and will be deallocated when it is garbage-collected, + * unless the object is externalized. + */ + static Local New( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized); + + /** + * Create a new ArrayBuffer with an existing backing store. + * The created array keeps a reference to the backing store until the array + * is garbage collected. Note that the IsExternal bit does not affect this + * reference from the array to the backing store. + * + * In future IsExternal bit will be removed. Until then the bit is set as + * follows. If the backing store does not own the underlying buffer, then + * the array is created in externalized state. Otherwise, the array is created + * in internalized state. In the latter case the array can be transitioned + * to the externalized state using Externalize(backing_store). + */ + static Local New(Isolate* isolate, + std::shared_ptr backing_store); + + /** + * Returns a new standalone BackingStore that is allocated using the array + * buffer allocator of the isolate. The allocation can either be zero + * initialized, or uninitialized. The result can be later passed to + * ArrayBuffer::New. + * + * If the allocator returns nullptr, then the function may cause GCs in the + * given isolate and re-try the allocation. + * + * If GCs do not help and on_failure is kOutOfMemory, then the + * function will crash with an out-of-memory error. + * + * Otherwise if GCs do not help (or the allocation is too large for GCs to + * help) and on_failure is kReturnNull, then a null result is returned. + */ + static std::unique_ptr NewBackingStore( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized, + BackingStoreOnFailureMode on_failure = + BackingStoreOnFailureMode::kOutOfMemory); + + /** + * Returns a new standalone BackingStore that takes over the ownership of + * the given buffer. The destructor of the BackingStore invokes the given + * deleter callback. + * + * The result can be later passed to ArrayBuffer::New. The raw pointer + * to the buffer must not be passed again to any V8 API function. + */ + static std::unique_ptr NewBackingStore( + void* data, size_t byte_length, v8::BackingStore::DeleterCallback deleter, + void* deleter_data); + + /** + * Returns a new resizable standalone BackingStore that is allocated using the + * array buffer allocator of the isolate. The result can be later passed to + * ArrayBuffer::New. + * + * |byte_length| must be <= |max_byte_length|. + * + * This function is usable without an isolate. Unlike |NewBackingStore| calls + * with an isolate, GCs cannot be triggered, and there are no + * retries. Allocation failure will cause the function to crash with an + * out-of-memory error. + */ + static std::unique_ptr NewResizableBackingStore( + size_t byte_length, size_t max_byte_length); + + /** + * Returns true if this ArrayBuffer may be detached. + */ + bool IsDetachable() const; + + /** + * Returns true if this ArrayBuffer has been detached. + */ + bool WasDetached() const; + + /** + * Detaches this ArrayBuffer and all its views (typed arrays). + * Detaching sets the byte length of the buffer and all typed arrays to zero, + * preventing JavaScript from ever accessing underlying backing store. + * ArrayBuffer should have been externalized and must be detachable. + */ + V8_DEPRECATED( + "Use the version which takes a key parameter (passing a null handle is " + "ok).") + void Detach(); + + /** + * Detaches this ArrayBuffer and all its views (typed arrays). + * Detaching sets the byte length of the buffer and all typed arrays to zero, + * preventing JavaScript from ever accessing underlying backing store. + * ArrayBuffer should have been externalized and must be detachable. Returns + * Nothing if the key didn't pass the [[ArrayBufferDetachKey]] check, + * Just(true) otherwise. + */ + V8_WARN_UNUSED_RESULT Maybe Detach(v8::Local key); + + /** + * Sets the ArrayBufferDetachKey. + */ + void SetDetachKey(v8::Local key); + + /** + * Get a shared pointer to the backing store of this array buffer. This + * pointer coordinates the lifetime management of the internal storage + * with any live ArrayBuffers on the heap, even across isolates. The embedder + * should not attempt to manage lifetime of the storage through other means. + * + * The returned shared pointer will not be empty, even if the ArrayBuffer has + * been detached. Use |WasDetached| to tell if it has been detached instead. + */ + std::shared_ptr GetBackingStore(); + + /** + * More efficient shortcut for + * GetBackingStore()->IsResizableByUserJavaScript(). + */ + bool IsResizableByUserJavaScript() const; + + /** + * More efficient shortcut for GetBackingStore()->Data(). The returned pointer + * is valid as long as the ArrayBuffer is alive. + */ + void* Data() const; + + V8_INLINE static ArrayBuffer* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + static constexpr int kInternalFieldCount = + V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT; + static constexpr int kEmbedderFieldCount = kInternalFieldCount; + +#if V8_ENABLE_SANDBOX + static constexpr size_t kMaxByteLength = + internal::kMaxSafeBufferSizeForSandbox; +#elif V8_HOST_ARCH_32_BIT + static constexpr size_t kMaxByteLength = std::numeric_limits::max(); +#else + // The maximum safe integer (2^53 - 1). + static constexpr size_t kMaxByteLength = + static_cast((uint64_t{1} << 53) - 1); +#endif + + private: + ArrayBuffer(); + static void CheckCast(Value* obj); + friend class TypedArray; +}; + +#ifndef V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT +// Defined using gn arg `v8_array_buffer_view_internal_field_count`. +#define V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT 2 +#endif + +/** + * A base class for an instance of one of "views" over ArrayBuffer, + * including TypedArrays and DataView (ES6 draft 15.13). + */ +class V8_EXPORT ArrayBufferView : public Object { + public: + /** + * Returns underlying ArrayBuffer. + */ + Local Buffer(); + /** + * Byte offset in |Buffer|. + */ + size_t ByteOffset(); + /** + * Size of a view in bytes. + */ + size_t ByteLength(); + + /** + * Copy the contents of the ArrayBufferView's buffer to an embedder defined + * memory without additional overhead that calling ArrayBufferView::Buffer + * might incur. + * + * Will write at most min(|byte_length|, ByteLength) bytes starting at + * ByteOffset of the underlying buffer to the memory starting at |dest|. + * Returns the number of bytes actually written. + */ + size_t CopyContents(void* dest, size_t byte_length); + + /** + * Returns the contents of the ArrayBufferView's buffer as a MemorySpan. If + * the contents are on the V8 heap, they get copied into `storage`. Otherwise + * a view into the off-heap backing store is returned. The provided storage + * should be at least as large as the maximum on-heap size of a TypedArray, + * was defined in gn with `typed_array_max_size_in_heap`. The default value is + * 64 bytes. + */ + v8::MemorySpan GetContents(v8::MemorySpan storage); + + /** + * Returns true if ArrayBufferView's backing ArrayBuffer has already been + * allocated. + */ + bool HasBuffer() const; + + V8_INLINE static ArrayBufferView* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + static constexpr int kInternalFieldCount = + V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT; + static const int kEmbedderFieldCount = kInternalFieldCount; + + private: + ArrayBufferView(); + static void CheckCast(Value* obj); +}; + +/** + * An instance of DataView constructor (ES6 draft 15.13.7). + */ +class V8_EXPORT DataView : public ArrayBufferView { + public: + static Local New(Local array_buffer, + size_t byte_offset, size_t length); + static Local New(Local shared_array_buffer, + size_t byte_offset, size_t length); + V8_INLINE static DataView* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + private: + DataView(); + static void CheckCast(Value* obj); +}; + +/** + * An instance of the built-in SharedArrayBuffer constructor. + */ +class V8_EXPORT SharedArrayBuffer : public Object { + public: + /** + * Data length in bytes. + */ + size_t ByteLength() const; + + /** + * Maximum length in bytes. + */ + size_t MaxByteLength() const; + + /** + * Create a new SharedArrayBuffer. Allocate |byte_length| bytes, which are + * either zero-initialized or uninitialized. Allocated memory will be owned by + * a created SharedArrayBuffer and will be deallocated when it is + * garbage-collected, unless the object is externalized. + */ + static Local New( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized); + + /** + * Create a new SharedArrayBuffer. Allocate |byte_length| bytes, which are + * either zero-initialized or uninitialized. Allocated memory will be owned by + * a created SharedArrayBuffer and will be deallocated when it is + * garbage-collected, unless the object is externalized. If allocation + * fails, the Maybe returned will be empty. + */ + static MaybeLocal MaybeNew( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized); + + /** + * Create a new SharedArrayBuffer with an existing backing store. + * The created array keeps a reference to the backing store until the array + * is garbage collected. Note that the IsExternal bit does not affect this + * reference from the array to the backing store. + * + * In future IsExternal bit will be removed. Until then the bit is set as + * follows. If the backing store does not own the underlying buffer, then + * the array is created in externalized state. Otherwise, the array is created + * in internalized state. In the latter case the array can be transitioned + * to the externalized state using Externalize(backing_store). + */ + static Local New( + Isolate* isolate, std::shared_ptr backing_store); + + /** + * Returns a new standalone BackingStore that is allocated using the array + * buffer allocator of the isolate. The allocation can either be zero + * initialized, or uninitialized. The result can be later passed to + * SharedArrayBuffer::New. + * + * If the allocator returns nullptr, then the function may cause GCs in the + * given isolate and re-try the allocation. + * + * If on_failure is kOutOfMemory and GCs do not help, then the function will + * crash with an out-of-memory error. + * + * Otherwise, if on_failure is kReturnNull and GCs do not help (or the + * byte_length is so large that the allocation cannot succeed), then a null + * result is returned. + */ + static std::unique_ptr NewBackingStore( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized, + BackingStoreOnFailureMode on_failure = + BackingStoreOnFailureMode::kOutOfMemory); + + /** + * Returns a new standalone BackingStore that takes over the ownership of + * the given buffer. The destructor of the BackingStore invokes the given + * deleter callback. + * + * The result can be later passed to SharedArrayBuffer::New. The raw pointer + * to the buffer must not be passed again to any V8 functions. + */ + static std::unique_ptr NewBackingStore( + void* data, size_t byte_length, v8::BackingStore::DeleterCallback deleter, + void* deleter_data); + + /** + * Get a shared pointer to the backing store of this array buffer. This + * pointer coordinates the lifetime management of the internal storage + * with any live ArrayBuffers on the heap, even across isolates. The embedder + * should not attempt to manage lifetime of the storage through other means. + */ + std::shared_ptr GetBackingStore(); + + /** + * More efficient shortcut for GetBackingStore()->Data(). The returned pointer + * is valid as long as the ArrayBuffer is alive. + */ + void* Data() const; + + V8_INLINE static SharedArrayBuffer* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + static constexpr int kInternalFieldCount = + V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT; + + private: + SharedArrayBuffer(); + static void CheckCast(Value* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_ARRAY_BUFFER_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-callbacks.h b/NativeScript/napi/android/v8-13/include/v8-callbacks.h new file mode 100644 index 000000000..a9f380e38 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-callbacks.h @@ -0,0 +1,521 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_ISOLATE_CALLBACKS_H_ +#define INCLUDE_V8_ISOLATE_CALLBACKS_H_ + +#include + +#include +#include + +#include "cppgc/common.h" +#include "v8-data.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-promise.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +#if defined(V8_OS_WIN) +struct _EXCEPTION_POINTERS; +#endif + +namespace v8 { + +template +class FunctionCallbackInfo; +class Isolate; +class Message; +class Module; +class Object; +class Promise; +class ScriptOrModule; +class String; +class UnboundScript; +class Value; + +/** + * A JIT code event is issued each time code is added, moved or removed. + * + * \note removal events are not currently issued. + */ +struct JitCodeEvent { + enum EventType { + CODE_ADDED, + CODE_MOVED, + CODE_REMOVED, + CODE_ADD_LINE_POS_INFO, + CODE_START_LINE_INFO_RECORDING, + CODE_END_LINE_INFO_RECORDING + }; + // Definition of the code position type. The "POSITION" type means the place + // in the source code which are of interest when making stack traces to + // pin-point the source location of a stack frame as close as possible. + // The "STATEMENT_POSITION" means the place at the beginning of each + // statement, and is used to indicate possible break locations. + enum PositionType { POSITION, STATEMENT_POSITION }; + + // There are three different kinds of CodeType, one for JIT code generated + // by the optimizing compiler, one for byte code generated for the + // interpreter, and one for code generated from Wasm. For JIT_CODE and + // WASM_CODE, |code_start| points to the beginning of jitted assembly code, + // while for BYTE_CODE events, |code_start| points to the first bytecode of + // the interpreted function. + enum CodeType { BYTE_CODE, JIT_CODE, WASM_CODE }; + + // Type of event. + EventType type; + CodeType code_type; + // Start of the instructions. + void* code_start; + // Size of the instructions. + size_t code_len; + // Script info for CODE_ADDED event. + Local script; + // User-defined data for *_LINE_INFO_* event. It's used to hold the source + // code line information which is returned from the + // CODE_START_LINE_INFO_RECORDING event. And it's passed to subsequent + // CODE_ADD_LINE_POS_INFO and CODE_END_LINE_INFO_RECORDING events. + void* user_data; + + struct name_t { + // Name of the object associated with the code, note that the string is not + // zero-terminated. + const char* str; + // Number of chars in str. + size_t len; + }; + + struct line_info_t { + // PC offset + size_t offset; + // Code position + size_t pos; + // The position type. + PositionType position_type; + }; + + struct wasm_source_info_t { + // Source file name. + const char* filename; + // Length of filename. + size_t filename_size; + // Line number table, which maps offsets of JITted code to line numbers of + // source file. + const line_info_t* line_number_table; + // Number of entries in the line number table. + size_t line_number_table_size; + }; + + wasm_source_info_t* wasm_source_info = nullptr; + + union { + // Only valid for CODE_ADDED. + struct name_t name; + + // Only valid for CODE_ADD_LINE_POS_INFO + struct line_info_t line_info; + + // New location of instructions. Only valid for CODE_MOVED. + void* new_code_start; + }; + + Isolate* isolate; +}; + +/** + * Option flags passed to the SetJitCodeEventHandler function. + */ +enum JitCodeEventOptions { + kJitCodeEventDefault = 0, + // Generate callbacks for already existent code. + kJitCodeEventEnumExisting = 1, + + kLastJitCodeEventOption = kJitCodeEventEnumExisting +}; + +/** + * Callback function passed to SetJitCodeEventHandler. + * + * \param event code add, move or removal event. + */ +using JitCodeEventHandler = void (*)(const JitCodeEvent* event); + +// --- Garbage Collection Callbacks --- + +/** + * Applications can register callback functions which will be called before and + * after certain garbage collection operations. Allocations are not allowed in + * the callback functions, you therefore cannot manipulate objects (set or + * delete properties for example) since it is possible such operations will + * result in the allocation of objects. + * TODO(v8:12612): Deprecate kGCTypeMinorMarkSweep after updating blink. + */ +enum GCType { + kGCTypeScavenge = 1 << 0, + kGCTypeMinorMarkSweep = 1 << 1, + kGCTypeMarkSweepCompact = 1 << 2, + kGCTypeIncrementalMarking = 1 << 3, + kGCTypeProcessWeakCallbacks = 1 << 4, + kGCTypeAll = kGCTypeScavenge | kGCTypeMinorMarkSweep | + kGCTypeMarkSweepCompact | kGCTypeIncrementalMarking | + kGCTypeProcessWeakCallbacks +}; + +/** + * GCCallbackFlags is used to notify additional information about the GC + * callback. + * - kGCCallbackFlagConstructRetainedObjectInfos: The GC callback is for + * constructing retained object infos. + * - kGCCallbackFlagForced: The GC callback is for a forced GC for testing. + * - kGCCallbackFlagSynchronousPhantomCallbackProcessing: The GC callback + * is called synchronously without getting posted to an idle task. + * - kGCCallbackFlagCollectAllAvailableGarbage: The GC callback is called + * in a phase where V8 is trying to collect all available garbage + * (e.g., handling a low memory notification). + * - kGCCallbackScheduleIdleGarbageCollection: The GC callback is called to + * trigger an idle garbage collection. + */ +enum GCCallbackFlags { + kNoGCCallbackFlags = 0, + kGCCallbackFlagConstructRetainedObjectInfos = 1 << 1, + kGCCallbackFlagForced = 1 << 2, + kGCCallbackFlagSynchronousPhantomCallbackProcessing = 1 << 3, + kGCCallbackFlagCollectAllAvailableGarbage = 1 << 4, + kGCCallbackFlagCollectAllExternalMemory = 1 << 5, + kGCCallbackScheduleIdleGarbageCollection = 1 << 6, +}; + +using GCCallback = void (*)(GCType type, GCCallbackFlags flags); + +using InterruptCallback = void (*)(Isolate* isolate, void* data); + +using PrintCurrentStackTraceFilterCallback = + bool (*)(Isolate* isolate, Local script_name); + +/** + * This callback is invoked when the heap size is close to the heap limit and + * V8 is likely to abort with out-of-memory error. + * The callback can extend the heap limit by returning a value that is greater + * than the current_heap_limit. The initial heap limit is the limit that was + * set after heap setup. + */ +using NearHeapLimitCallback = size_t (*)(void* data, size_t current_heap_limit, + size_t initial_heap_limit); + +/** + * Callback function passed to SetUnhandledExceptionCallback. + */ +#if defined(V8_OS_WIN) +using UnhandledExceptionCallback = + int (*)(_EXCEPTION_POINTERS* exception_pointers); +#endif + +// --- Counters Callbacks --- + +using CounterLookupCallback = int* (*)(const char* name); + +using CreateHistogramCallback = void* (*)(const char* name, int min, int max, + size_t buckets); + +using AddHistogramSampleCallback = void (*)(void* histogram, int sample); + +// --- Exceptions --- + +using FatalErrorCallback = void (*)(const char* location, const char* message); + +struct OOMDetails { + bool is_heap_oom = false; + const char* detail = nullptr; +}; + +using OOMErrorCallback = void (*)(const char* location, + const OOMDetails& details); + +using MessageCallback = void (*)(Local message, Local data); + +// --- Tracing --- + +enum LogEventStatus : int { kStart = 0, kEnd = 1, kLog = 2 }; +using LogEventCallback = void (*)(const char* name, + int /* LogEventStatus */ status); + +// --- Crashkeys Callback --- +enum class CrashKeyId { + kIsolateAddress, + kReadonlySpaceFirstPageAddress, + kMapSpaceFirstPageAddress V8_ENUM_DEPRECATE_SOON("Map space got removed"), + kOldSpaceFirstPageAddress, + kCodeRangeBaseAddress, + kCodeSpaceFirstPageAddress, + kDumpType, + kSnapshotChecksumCalculated, + kSnapshotChecksumExpected, +}; + +using AddCrashKeyCallback = void (*)(CrashKeyId id, const std::string& value); + +// --- Enter/Leave Script Callback --- +using BeforeCallEnteredCallback = void (*)(Isolate*); +using CallCompletedCallback = void (*)(Isolate*); + +// --- Modify Code Generation From Strings Callback --- +struct ModifyCodeGenerationFromStringsResult { + // If true, proceed with the codegen algorithm. Otherwise, block it. + bool codegen_allowed = false; + // Overwrite the original source with this string, if present. + // Use the original source if empty. + // This field is considered only if codegen_allowed is true. + MaybeLocal modified_source; +}; + +/** + * Callback to check if codegen is allowed from a source object, and convert + * the source to string if necessary. See: ModifyCodeGenerationFromStrings. + */ +using ModifyCodeGenerationFromStringsCallback = + ModifyCodeGenerationFromStringsResult (*)(Local context, + Local source); +using ModifyCodeGenerationFromStringsCallback2 = + ModifyCodeGenerationFromStringsResult (*)(Local context, + Local source, + bool is_code_like); + +// --- Failed Access Check Callback --- + +/** + * Access type specification. + */ +enum AccessType { + ACCESS_GET, + ACCESS_SET, + ACCESS_HAS, + ACCESS_DELETE, + ACCESS_KEYS +}; + +using FailedAccessCheckCallback = void (*)(Local target, + AccessType type, Local data); + +// --- WebAssembly compilation callbacks --- +using ExtensionCallback = bool (*)(const FunctionCallbackInfo&); + +using AllowWasmCodeGenerationCallback = bool (*)(Local context, + Local source); + +// --- Callback for APIs defined on v8-supported objects, but implemented +// by the embedder. Example: WebAssembly.{compile|instantiate}Streaming --- +using ApiImplementationCallback = void (*)(const FunctionCallbackInfo&); + +// --- Callback for WebAssembly.compileStreaming --- +using WasmStreamingCallback = void (*)(const FunctionCallbackInfo&); + +enum class WasmAsyncSuccess { kSuccess, kFail }; + +// --- Callback called when async WebAssembly operations finish --- +using WasmAsyncResolvePromiseCallback = void (*)( + Isolate* isolate, Local context, Local resolver, + Local result, WasmAsyncSuccess success); + +// --- Callback for loading source map file for Wasm profiling support +using WasmLoadSourceMapCallback = Local (*)(Isolate* isolate, + const char* name); + +// --- Callback for checking if WebAssembly imported strings are enabled --- +using WasmImportedStringsEnabledCallback = bool (*)(Local context); + +// --- Callback for checking if the SharedArrayBuffer constructor is enabled --- +using SharedArrayBufferConstructorEnabledCallback = + bool (*)(Local context); + +// --- Callback for checking if WebAssembly JSPI is enabled --- +using WasmJSPIEnabledCallback = bool (*)(Local context); + +/** + * Import phases in import requests. + */ +enum class ModuleImportPhase { + kSource, + kEvaluation, +}; + +/** + * HostImportModuleDynamicallyCallback is called when we + * require the embedder to load a module. This is used as part of the dynamic + * import syntax. + * + * The referrer contains metadata about the script/module that calls + * import. + * + * The specifier is the name of the module that should be imported. + * + * The import_attributes are import attributes for this request in the form: + * [key1, value1, key2, value2, ...] where the keys and values are of type + * v8::String. Note, unlike the FixedArray passed to ResolveModuleCallback and + * returned from ModuleRequest::GetImportAttributes(), this array does not + * contain the source Locations of the attributes. + * + * The embedder must compile, instantiate, evaluate the Module, and + * obtain its namespace object. + * + * The Promise returned from this function is forwarded to userland + * JavaScript. The embedder must resolve this promise with the module + * namespace object. In case of an exception, the embedder must reject + * this promise with the exception. If the promise creation itself + * fails (e.g. due to stack overflow), the embedder must propagate + * that exception by returning an empty MaybeLocal. + */ +using HostImportModuleDynamicallyCallback = MaybeLocal (*)( + Local context, Local host_defined_options, + Local resource_name, Local specifier, + Local import_attributes); + +/** + * HostImportModuleWithPhaseDynamicallyCallback is called when we + * require the embedder to load a module with a specific phase. This is used + * as part of the dynamic import syntax. + * + * The referrer contains metadata about the script/module that calls + * import. + * + * The specifier is the name of the module that should be imported. + * + * The phase is the phase of the import requested. + * + * The import_attributes are import attributes for this request in the form: + * [key1, value1, key2, value2, ...] where the keys and values are of type + * v8::String. Note, unlike the FixedArray passed to ResolveModuleCallback and + * returned from ModuleRequest::GetImportAttributes(), this array does not + * contain the source Locations of the attributes. + * + * The Promise returned from this function is forwarded to userland + * JavaScript. The embedder must resolve this promise according to the phase + * requested: + * - For ModuleImportPhase::kSource, the promise must be resolved with a + * compiled ModuleSource object, or rejected with a SyntaxError if the + * module does not support source representation. + * - For ModuleImportPhase::kEvaluation, the promise must be resolved with a + * ModuleNamespace object of a module that has been compiled, instantiated, + * and evaluated. + * + * In case of an exception, the embedder must reject this promise with the + * exception. If the promise creation itself fails (e.g. due to stack + * overflow), the embedder must propagate that exception by returning an empty + * MaybeLocal. + * + * This callback is still experimental and is only invoked for source phase + * imports. + */ +using HostImportModuleWithPhaseDynamicallyCallback = MaybeLocal (*)( + Local context, Local host_defined_options, + Local resource_name, Local specifier, + ModuleImportPhase phase, Local import_attributes); + +/** + * Callback for requesting a compile hint for a function from the embedder. The + * first parameter is the position of the function in source code and the second + * parameter is embedder data to be passed back. + */ +using CompileHintCallback = bool (*)(int, void*); + +/** + * HostInitializeImportMetaObjectCallback is called the first time import.meta + * is accessed for a module. Subsequent access will reuse the same value. + * + * The method combines two implementation-defined abstract operations into one: + * HostGetImportMetaProperties and HostFinalizeImportMeta. + * + * The embedder should use v8::Object::CreateDataProperty to add properties on + * the meta object. + */ +using HostInitializeImportMetaObjectCallback = void (*)(Local context, + Local module, + Local meta); + +/** + * HostCreateShadowRealmContextCallback is called each time a ShadowRealm is + * being constructed in the initiator_context. + * + * The method combines Context creation and implementation defined abstract + * operation HostInitializeShadowRealm into one. + * + * The embedder should use v8::Context::New or v8::Context:NewFromSnapshot to + * create a new context. If the creation fails, the embedder must propagate + * that exception by returning an empty MaybeLocal. + */ +using HostCreateShadowRealmContextCallback = + MaybeLocal (*)(Local initiator_context); + +/** + * IsJSApiWrapperNativeErrorCallback is called on an JSApiWrapper object to + * determine if Error.isError should return true or false. For instance, in an + * HTML embedder, DOMExceptions return true when passed to Error.isError. + */ +using IsJSApiWrapperNativeErrorCallback = bool (*)(Isolate* isolate, + Local obj); + +/** + * PrepareStackTraceCallback is called when the stack property of an error is + * first accessed. The return value will be used as the stack value. If this + * callback is registed, the |Error.prepareStackTrace| API will be disabled. + * |sites| is an array of call sites, specified in + * https://v8.dev/docs/stack-trace-api + */ +using PrepareStackTraceCallback = MaybeLocal (*)(Local context, + Local error, + Local sites); + +#if defined(V8_OS_WIN) +/** + * Callback to selectively enable ETW tracing based on the document URL. + * Implemented by the embedder, it should never call back into V8. + * + * Windows allows passing additional data to the ETW EnableCallback: + * https://learn.microsoft.com/en-us/windows/win32/api/evntprov/nc-evntprov-penablecallback + * + * This data can be configured in a WPR (Windows Performance Recorder) + * profile, adding a CustomFilter to an EventProvider like the following: + * + * + * + * + * + * Where: + * - Name="57277741-3638-4A4B-BDBA-0AC6E45DA56C" is the GUID of the V8 + * ETW provider, (see src/libplatform/etw/etw-provider-win.h), + * - Type="0x80000000" is EVENT_FILTER_TYPE_SCHEMATIZED, + * - Value="AQABAAAAAA..." is a base64-encoded byte array that is + * base64-decoded by Windows and passed to the ETW enable callback in + * the 'PEVENT_FILTER_DESCRIPTOR FilterData' argument; see: + * https://learn.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_filter_descriptor. + * + * This array contains a struct EVENT_FILTER_HEADER followed by a + * variable length payload, and as payload we pass a string in JSON format, + * with a list of regular expressions that should match the document URL + * in order to enable ETW tracing: + * { + * "version": "2.0", + * "filtered_urls": [ + * "https:\/\/.*\.chromium\.org\/.*", "https://v8.dev/";, "..." + * ], + * "trace_interpreter_frames": true + * } + */ + +using FilterETWSessionByURLCallback = + bool (*)(Local context, const std::string& etw_filter_payload); + +struct FilterETWSessionByURLResult { + // If true, enable ETW tracing for the current isolate. + bool enable_etw_tracing; + + // If true, also enables ETW tracing for interpreter stack frames. + bool trace_interpreter_frames; +}; +using FilterETWSessionByURL2Callback = FilterETWSessionByURLResult (*)( + Local context, const std::string& etw_filter_payload); +#endif // V8_OS_WIN + +} // namespace v8 + +#endif // INCLUDE_V8_ISOLATE_CALLBACKS_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-container.h b/NativeScript/napi/android/v8-13/include/v8-container.h new file mode 100644 index 000000000..380999e5e --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-container.h @@ -0,0 +1,183 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_CONTAINER_H_ +#define INCLUDE_V8_CONTAINER_H_ + +#include +#include + +#include + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; +class Isolate; + +/** + * An instance of the built-in array constructor (ECMA-262, 15.4.2). + */ +class V8_EXPORT Array : public Object { + public: + uint32_t Length() const; + + /** + * Creates a JavaScript array with the given length. If the length + * is negative the returned array will have length 0. + */ + static Local New(Isolate* isolate, int length = 0); + + /** + * Creates a JavaScript array out of a Local array in C++ + * with a known length. + */ + static Local New(Isolate* isolate, Local* elements, + size_t length); + V8_INLINE static Array* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + /** + * Creates a JavaScript array from a provided callback. + * + * \param context The v8::Context to create the array in. + * \param length The length of the array to be created. + * \param next_value_callback The callback that is invoked to retrieve + * elements for the array. The embedder can signal that the array + * initialization should be aborted by throwing an exception and returning + * an empty MaybeLocal. + * \returns The v8::Array if all elements were constructed successfully and an + * empty MaybeLocal otherwise. + */ + static MaybeLocal New( + Local context, size_t length, + std::function()> next_value_callback); + + enum class CallbackResult { + kException, + kBreak, + kContinue, + }; + using IterationCallback = CallbackResult (*)(uint32_t index, + Local element, + void* data); + + /** + * Calls {callback} for every element of this array, passing {callback_data} + * as its {data} parameter. + * This function will typically be faster than calling {Get()} repeatedly. + * As a consequence of being optimized for low overhead, the provided + * callback must adhere to the following restrictions: + * - It must not allocate any V8 objects and continue iterating; it may + * allocate (e.g. an error message/object) and then immediately terminate + * the iteration. + * - It must not modify the array being iterated. + * - It must not call back into V8 (unless it can guarantee that such a + * call does not violate the above restrictions, which is difficult). + * - The {Local element} must not "escape", i.e. must not be assigned + * to any other {Local}. Creating a {Global} from it, or updating a + * v8::TypecheckWitness with it, is safe. + * These restrictions may be lifted in the future if use cases arise that + * justify a slower but more robust implementation. + * + * Returns {Nothing} on exception; use a {TryCatch} to catch and handle this + * exception. + * When the {callback} returns {kException}, iteration is terminated + * immediately, returning {Nothing}. By returning {kBreak}, the callback + * can request non-exceptional early termination of the iteration. + */ + Maybe Iterate(Local context, IterationCallback callback, + void* callback_data); + + private: + Array(); + static void CheckCast(Value* obj); +}; + +/** + * An instance of the built-in Map constructor (ECMA-262, 6th Edition, 23.1.1). + */ +class V8_EXPORT Map : public Object { + public: + size_t Size() const; + void Clear(); + V8_WARN_UNUSED_RESULT MaybeLocal Get(Local context, + Local key); + V8_WARN_UNUSED_RESULT MaybeLocal Set(Local context, + Local key, + Local value); + V8_WARN_UNUSED_RESULT Maybe Has(Local context, + Local key); + V8_WARN_UNUSED_RESULT Maybe Delete(Local context, + Local key); + + /** + * Returns an array of length Size() * 2, where index N is the Nth key and + * index N + 1 is the Nth value. + */ + Local AsArray() const; + + /** + * Creates a new empty Map. + */ + static Local New(Isolate* isolate); + + V8_INLINE static Map* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + private: + Map(); + static void CheckCast(Value* obj); +}; + +/** + * An instance of the built-in Set constructor (ECMA-262, 6th Edition, 23.2.1). + */ +class V8_EXPORT Set : public Object { + public: + size_t Size() const; + void Clear(); + V8_WARN_UNUSED_RESULT MaybeLocal Add(Local context, + Local key); + V8_WARN_UNUSED_RESULT Maybe Has(Local context, + Local key); + V8_WARN_UNUSED_RESULT Maybe Delete(Local context, + Local key); + + /** + * Returns an array of the keys in this Set. + */ + Local AsArray() const; + + /** + * Creates a new empty Set. + */ + static Local New(Isolate* isolate); + + V8_INLINE static Set* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + private: + Set(); + static void CheckCast(Value* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_CONTAINER_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-context.h b/NativeScript/napi/android/v8-13/include/v8-context.h new file mode 100644 index 000000000..43dadadea --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-context.h @@ -0,0 +1,518 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_CONTEXT_H_ +#define INCLUDE_V8_CONTEXT_H_ + +#include + +#include + +#include "v8-data.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-maybe.h" // NOLINT(build/include_directory) +#include "v8-snapshot.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Function; +class MicrotaskQueue; +class Object; +class ObjectTemplate; +class Value; +class String; + +/** + * A container for extension names. + */ +class V8_EXPORT ExtensionConfiguration { + public: + ExtensionConfiguration() : name_count_(0), names_(nullptr) {} + ExtensionConfiguration(int name_count, const char* names[]) + : name_count_(name_count), names_(names) {} + + const char** begin() const { return &names_[0]; } + const char** end() const { return &names_[name_count_]; } + + private: + const int name_count_; + const char** names_; +}; + +/** + * A sandboxed execution context with its own set of built-in objects + * and functions. + */ +class V8_EXPORT Context : public Data { + public: + /** + * Returns the global proxy object. + * + * Global proxy object is a thin wrapper whose prototype points to actual + * context's global object with the properties like Object, etc. This is done + * that way for security reasons (for more details see + * https://wiki.mozilla.org/Gecko:SplitWindow). + * + * Please note that changes to global proxy object prototype most probably + * would break VM---v8 expects only global object as a prototype of global + * proxy object. + */ + Local Global(); + + /** + * Detaches the global object from its context before + * the global object can be reused to create a new context. + */ + void DetachGlobal(); + + /** + * Creates a new context and returns a handle to the newly allocated + * context. + * + * \param isolate The isolate in which to create the context. + * + * \param extensions An optional extension configuration containing + * the extensions to be installed in the newly created context. + * + * \param global_template An optional object template from which the + * global object for the newly created context will be created. + * + * \param global_object An optional global object to be reused for + * the newly created context. This global object must have been + * created by a previous call to Context::New with the same global + * template. The state of the global object will be completely reset + * and only object identify will remain. + * + * \param internal_fields_deserializer An optional callback used + * to deserialize fields set by + * v8::Object::SetAlignedPointerInInternalField() in wrapper objects + * from the default context snapshot. It should match the + * SerializeInternalFieldsCallback() used by + * v8::SnapshotCreator::SetDefaultContext() when the default context + * snapshot is created. It does not need to be configured if the default + * context snapshot contains no wrapper objects with pointer internal + * fields, or if no custom startup snapshot is configured + * in the v8::CreateParams used to create the isolate. + * + * \param microtask_queue An optional microtask queue used to manage + * the microtasks created in this context. If not set the per-isolate + * default microtask queue would be used. + * + * \param context_data_deserializer An optional callback used + * to deserialize embedder data set by + * v8::Context::SetAlignedPointerInEmbedderData() in the default + * context from the default context snapshot. It does not need to be + * configured if the default context snapshot contains no pointer embedder + * data, or if no custom startup snapshot is configured in the + * v8::CreateParams used to create the isolate. + * + * \param api_wrapper_deserializer An optional callback used to deserialize + * API wrapper objects that was initially set with v8::Object::Wrap() and then + * serialized using SerializeAPIWrapperCallback. + */ + static Local New( + Isolate* isolate, ExtensionConfiguration* extensions = nullptr, + MaybeLocal global_template = MaybeLocal(), + MaybeLocal global_object = MaybeLocal(), + DeserializeInternalFieldsCallback internal_fields_deserializer = + DeserializeInternalFieldsCallback(), + MicrotaskQueue* microtask_queue = nullptr, + DeserializeContextDataCallback context_data_deserializer = + DeserializeContextDataCallback(), + DeserializeAPIWrapperCallback api_wrapper_deserializer = + DeserializeAPIWrapperCallback()); + + /** + * Create a new context from a (non-default) context snapshot. There + * is no way to provide a global object template since we do not create + * a new global object from template, but we can reuse a global object. + * + * \param isolate See v8::Context::New(). + * + * \param context_snapshot_index The index of the context snapshot to + * deserialize from. Use v8::Context::New() for the default snapshot. + * + * \param internal_fields_deserializer An optional callback used + * to deserialize fields set by + * v8::Object::SetAlignedPointerInInternalField() in wrapper objects + * from the default context snapshot. It does not need to be + * configured if there are no wrapper objects with no internal + * pointer fields in the default context snapshot or if no startup + * snapshot is configured when the isolate is created. + * + * \param extensions See v8::Context::New(). + * + * \param global_object See v8::Context::New(). + * + * \param internal_fields_deserializer Similar to + * internal_fields_deserializer in v8::Context::New() but applies to + * the context specified by the context_snapshot_index. + * + * \param microtask_queue See v8::Context::New(). + * + * \param context_data_deserializer Similar to + * context_data_deserializer in v8::Context::New() but applies to + * the context specified by the context_snapshot_index. + * + *\param api_wrapper_deserializer Similar to api_wrapper_deserializer in + * v8::Context::New() but applies to the context specified by the + * context_snapshot_index. + */ + static MaybeLocal FromSnapshot( + Isolate* isolate, size_t context_snapshot_index, + DeserializeInternalFieldsCallback internal_fields_deserializer = + DeserializeInternalFieldsCallback(), + ExtensionConfiguration* extensions = nullptr, + MaybeLocal global_object = MaybeLocal(), + MicrotaskQueue* microtask_queue = nullptr, + DeserializeContextDataCallback context_data_deserializer = + DeserializeContextDataCallback(), + DeserializeAPIWrapperCallback api_wrapper_deserializer = + DeserializeAPIWrapperCallback()); + + /** + * Returns an global object that isn't backed by an actual context. + * + * The global template needs to have access checks with handlers installed. + * If an existing global object is passed in, the global object is detached + * from its context. + * + * Note that this is different from a detached context where all accesses to + * the global proxy will fail. Instead, the access check handlers are invoked. + * + * It is also not possible to detach an object returned by this method. + * Instead, the access check handlers need to return nothing to achieve the + * same effect. + * + * It is possible, however, to create a new context from the global object + * returned by this method. + */ + static MaybeLocal NewRemoteContext( + Isolate* isolate, Local global_template, + MaybeLocal global_object = MaybeLocal()); + + /** + * Sets the security token for the context. To access an object in + * another context, the security tokens must match. + */ + void SetSecurityToken(Local token); + + /** Restores the security token to the default value. */ + void UseDefaultSecurityToken(); + + /** Returns the security token of this context.*/ + Local GetSecurityToken(); + + /** + * Enter this context. After entering a context, all code compiled + * and run is compiled and run in this context. If another context + * is already entered, this old context is saved so it can be + * restored when the new context is exited. + */ + void Enter(); + + /** + * Exit this context. Exiting the current context restores the + * context that was in place when entering the current context. + */ + void Exit(); + + /** + * Delegate to help with Deep freezing embedder-specific objects (such as + * JSApiObjects) that can not be frozen natively. + */ + class DeepFreezeDelegate { + public: + /** + * Performs embedder-specific operations to freeze the provided embedder + * object. The provided object *will* be frozen by DeepFreeze after this + * function returns, so only embedder-specific objects need to be frozen. + * This function *may not* create new JS objects or perform JS allocations. + * Any v8 objects reachable from the provided embedder object that should + * also be considered for freezing should be added to the children_out + * parameter. Returns true if the operation completed successfully. + */ + virtual bool FreezeEmbedderObjectAndGetChildren( + Local obj, LocalVector& children_out) = 0; + }; + + /** + * Attempts to recursively freeze all objects reachable from this context. + * Some objects (generators, iterators, non-const closures) can not be frozen + * and will cause this method to throw an error. An optional delegate can be + * provided to help freeze embedder-specific objects. + * + * Freezing occurs in two steps: + * 1. "Marking" where we iterate through all objects reachable by this + * context, accumulating a list of objects that need to be frozen and + * looking for objects that can't be frozen. This step is separated because + * it is more efficient when we can assume there is no garbage collection. + * 2. "Freezing" where we go through the list of objects and freezing them. + * This effectively requires copying them so it may trigger garbage + * collection. + */ + Maybe DeepFreeze(DeepFreezeDelegate* delegate = nullptr); + + /** Returns the isolate associated with a current context. */ + Isolate* GetIsolate(); + + /** Returns the microtask queue associated with a current context. */ + MicrotaskQueue* GetMicrotaskQueue(); + + /** Sets the microtask queue associated with the current context. */ + void SetMicrotaskQueue(MicrotaskQueue* queue); + + /** + * The field at kDebugIdIndex used to be reserved for the inspector. + * It now serves no purpose. + */ + enum EmbedderDataFields { kDebugIdIndex = 0 }; + + /** + * Return the number of fields allocated for embedder data. + */ + uint32_t GetNumberOfEmbedderDataFields(); + + /** + * Gets the embedder data with the given index, which must have been set by a + * previous call to SetEmbedderData with the same index. + */ + V8_INLINE Local GetEmbedderData(int index); + + /** + * Gets the binding object used by V8 extras. Extra natives get a reference + * to this object and can use it to "export" functionality by adding + * properties. Extra natives can also "import" functionality by accessing + * properties added by the embedder using the V8 API. + */ + Local GetExtrasBindingObject(); + + /** + * Sets the embedder data with the given index, growing the data as + * needed. Note that index 0 currently has a special meaning for Chrome's + * debugger. + */ + void SetEmbedderData(int index, Local value); + + /** + * Gets a 2-byte-aligned native pointer from the embedder data with the given + * index, which must have been set by a previous call to + * SetAlignedPointerInEmbedderData with the same index. Note that index 0 + * currently has a special meaning for Chrome's debugger. + */ + V8_INLINE void* GetAlignedPointerFromEmbedderData(Isolate* isolate, + int index); + V8_INLINE void* GetAlignedPointerFromEmbedderData(int index); + + /** + * Sets a 2-byte-aligned native pointer in the embedder data with the given + * index, growing the data as needed. Note that index 0 currently has a + * special meaning for Chrome's debugger. + */ + void SetAlignedPointerInEmbedderData(int index, void* value); + + /** + * Control whether code generation from strings is allowed. Calling + * this method with false will disable 'eval' and the 'Function' + * constructor for code running in this context. If 'eval' or the + * 'Function' constructor are used an exception will be thrown. + * + * If code generation from strings is not allowed the + * V8::ModifyCodeGenerationFromStringsCallback callback will be invoked if + * set before blocking the call to 'eval' or the 'Function' + * constructor. If that callback returns true, the call will be + * allowed, otherwise an exception will be thrown. If no callback is + * set an exception will be thrown. + */ + void AllowCodeGenerationFromStrings(bool allow); + + /** + * Returns true if code generation from strings is allowed for the context. + * For more details see AllowCodeGenerationFromStrings(bool) documentation. + */ + bool IsCodeGenerationFromStringsAllowed() const; + + /** + * Sets the error description for the exception that is thrown when + * code generation from strings is not allowed and 'eval' or the 'Function' + * constructor are called. + */ + void SetErrorMessageForCodeGenerationFromStrings(Local message); + + /** + * Sets the error description for the exception that is thrown when + * wasm code generation is not allowed. + */ + void SetErrorMessageForWasmCodeGeneration(Local message); + + /** + * Return data that was previously attached to the context snapshot via + * SnapshotCreator, and removes the reference to it. + * Repeated call with the same index returns an empty MaybeLocal. + */ + template + V8_INLINE MaybeLocal GetDataFromSnapshotOnce(size_t index); + + /** + * If callback is set, abort any attempt to execute JavaScript in this + * context, call the specified callback, and throw an exception. + * To unset abort, pass nullptr as callback. + */ + using AbortScriptExecutionCallback = void (*)(Isolate* isolate, + Local context); + void SetAbortScriptExecution(AbortScriptExecutionCallback callback); + + /** + * Set or clear hooks to be invoked for promise lifecycle operations. + * To clear a hook, set it to an empty v8::Function. Each function will + * receive the observed promise as the first argument. If a chaining + * operation is used on a promise, the init will additionally receive + * the parent promise as the second argument. + */ + void SetPromiseHooks(Local init_hook, Local before_hook, + Local after_hook, + Local resolve_hook); + + bool HasTemplateLiteralObject(Local object); + /** + * Stack-allocated class which sets the execution context for all + * operations executed within a local scope. + */ + class V8_NODISCARD Scope { + public: + explicit V8_INLINE Scope(Local context) : context_(context) { + context_->Enter(); + } + V8_INLINE ~Scope() { context_->Exit(); } + + private: + Local context_; + }; + + /** + * Stack-allocated class to support the backup incumbent settings object + * stack. + * https://html.spec.whatwg.org/multipage/webappapis.html#backup-incumbent-settings-object-stack + */ + class V8_EXPORT V8_NODISCARD BackupIncumbentScope final { + public: + /** + * |backup_incumbent_context| is pushed onto the backup incumbent settings + * object stack. + */ + explicit BackupIncumbentScope(Local backup_incumbent_context); + ~BackupIncumbentScope(); + + private: + friend class internal::Isolate; + + uintptr_t JSStackComparableAddressPrivate() const { + return js_stack_comparable_address_; + } + + Local backup_incumbent_context_; + uintptr_t js_stack_comparable_address_ = 0; + const BackupIncumbentScope* prev_ = nullptr; + }; + + V8_INLINE static Context* Cast(Data* data); + + private: + friend class Value; + friend class Script; + friend class Object; + friend class Function; + + static void CheckCast(Data* obj); + + internal::ValueHelper::InternalRepresentationType GetDataFromSnapshotOnce( + size_t index); + Local SlowGetEmbedderData(int index); + void* SlowGetAlignedPointerFromEmbedderData(int index); +}; + +// --- Implementation --- + +Local Context::GetEmbedderData(int index) { +#ifndef V8_ENABLE_CHECKS + using A = internal::Address; + using I = internal::Internals; + A ctx = internal::ValueHelper::ValueAsAddress(this); + A embedder_data = + I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset); + int value_offset = + I::kEmbedderDataArrayHeaderSize + (I::kEmbedderDataSlotSize * index); + A value = I::ReadRawField(embedder_data, value_offset); +#ifdef V8_COMPRESS_POINTERS + // We read the full pointer value and then decompress it in order to avoid + // dealing with potential endiannes issues. + value = I::DecompressTaggedField(embedder_data, static_cast(value)); +#endif + + auto isolate = reinterpret_cast( + internal::IsolateFromNeverReadOnlySpaceObject(ctx)); + return Local::New(isolate, value); +#else + return SlowGetEmbedderData(index); +#endif +} + +void* Context::GetAlignedPointerFromEmbedderData(Isolate* isolate, int index) { +#if !defined(V8_ENABLE_CHECKS) + using A = internal::Address; + using I = internal::Internals; + A ctx = internal::ValueHelper::ValueAsAddress(this); + A embedder_data = + I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset); + int value_offset = I::kEmbedderDataArrayHeaderSize + + (I::kEmbedderDataSlotSize * index) + + I::kEmbedderDataSlotExternalPointerOffset; + return reinterpret_cast( + I::ReadExternalPointerField( + isolate, embedder_data, value_offset)); +#else + return SlowGetAlignedPointerFromEmbedderData(index); +#endif +} + +void* Context::GetAlignedPointerFromEmbedderData(int index) { +#if !defined(V8_ENABLE_CHECKS) + using A = internal::Address; + using I = internal::Internals; + A ctx = internal::ValueHelper::ValueAsAddress(this); + A embedder_data = + I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset); + int value_offset = I::kEmbedderDataArrayHeaderSize + + (I::kEmbedderDataSlotSize * index) + + I::kEmbedderDataSlotExternalPointerOffset; + Isolate* isolate = I::GetIsolateForSandbox(ctx); + return reinterpret_cast( + I::ReadExternalPointerField( + isolate, embedder_data, value_offset)); +#else + return SlowGetAlignedPointerFromEmbedderData(index); +#endif +} + +template +MaybeLocal Context::GetDataFromSnapshotOnce(size_t index) { + if (auto repr = GetDataFromSnapshotOnce(index); + repr != internal::ValueHelper::kEmpty) { + internal::PerformCastCheck(internal::ValueHelper::ReprAsValue(repr)); + return Local::FromRepr(repr); + } + return {}; +} + +Context* Context::Cast(v8::Data* data) { +#ifdef V8_ENABLE_CHECKS + CheckCast(data); +#endif + return static_cast(data); +} + +} // namespace v8 + +#endif // INCLUDE_V8_CONTEXT_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-cpp-heap-external.h b/NativeScript/napi/android/v8-13/include/v8-cpp-heap-external.h new file mode 100644 index 000000000..92a89c455 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-cpp-heap-external.h @@ -0,0 +1,56 @@ +// Copyright 2025 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_HEAP_EXTERNAL_H_ +#define INCLUDE_V8_HEAP_EXTERNAL_H_ + +#include "cppgc/type-traits.h" // NOLINT(build/include_directory) +#include "v8-sandbox.h" // NOLINT(build/include_directory) +#include "v8-value.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Isolate; + +/** + * A JavaScript value that wraps a `cppgc::GarbageCollected` object allocated + * on the managed C++ heap (CppHeap). This type of value is mainly used to + * associate C++ data structures which aren't exposed to JavaScript with + * JavaScript objects. + */ +class V8_EXPORT CppHeapExternal : public Data { + public: + template + static Local New(Isolate* isolate, T* value, + CppHeapPointerTag tag) { + static_assert(cppgc::IsGarbageCollectedTypeV, + "Object must be of type GarbageCollected."); + return NewImpl(isolate, value, tag); + } + + V8_INLINE static CppHeapExternal* Cast(Data* data) { +#ifdef V8_ENABLE_CHECKS + CheckCast(data); +#endif + return static_cast(data); + } + + template + T* Value(Isolate* isolate, CppHeapPointerTagRange tag_range) const { + static_assert(cppgc::IsGarbageCollectedTypeV, + "Object must be of type GarbageCollected."); + return static_cast(ValueImpl(isolate, tag_range)); + } + + private: + static void CheckCast(v8::Data* obj); + static Local NewImpl(Isolate* isolate, void* value, + CppHeapPointerTag tag); + void* ValueImpl(Isolate*, CppHeapPointerTagRange tag_range) const; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_HEAP_EXTERNAL_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-cppgc.h b/NativeScript/napi/android/v8-13/include/v8-cppgc.h new file mode 100644 index 000000000..aa3813b95 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-cppgc.h @@ -0,0 +1,194 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_CPPGC_H_ +#define INCLUDE_V8_CPPGC_H_ + +#include +#include +#include + +#include "cppgc/common.h" +#include "cppgc/custom-space.h" +#include "cppgc/heap-statistics.h" +#include "cppgc/visitor.h" +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8-traced-handle.h" // NOLINT(build/include_directory) + +namespace cppgc { +class AllocationHandle; +class HeapHandle; +} // namespace cppgc + +namespace v8 { + +class Object; + +namespace internal { +class CppHeap; +} // namespace internal + +class CustomSpaceStatisticsReceiver; + +struct V8_EXPORT CppHeapCreateParams { + explicit CppHeapCreateParams( + std::vector> custom_spaces) + : custom_spaces(std::move(custom_spaces)) {} + + CppHeapCreateParams(const CppHeapCreateParams&) = delete; + CppHeapCreateParams& operator=(const CppHeapCreateParams&) = delete; + + std::vector> custom_spaces; + /** + * Specifies which kind of marking are supported by the heap. The type may be + * further reduced via runtime flags when attaching the heap to an Isolate. + */ + cppgc::Heap::MarkingType marking_support = + cppgc::Heap::MarkingType::kIncrementalAndConcurrent; + /** + * Specifies which kind of sweeping is supported by the heap. The type may be + * further reduced via runtime flags when attaching the heap to an Isolate. + */ + cppgc::Heap::SweepingType sweeping_support = + cppgc::Heap::SweepingType::kIncrementalAndConcurrent; +}; + +/** + * A heap for allocating managed C++ objects. + * + * Similar to v8::Isolate, the heap may only be accessed from one thread at a + * time. The heap may be used from different threads using the + * v8::Locker/v8::Unlocker APIs which is different from generic Oilpan. + */ +class V8_EXPORT CppHeap { + public: + static std::unique_ptr Create(v8::Platform* platform, + const CppHeapCreateParams& params); + + virtual ~CppHeap() = default; + + /** + * \returns the opaque handle for allocating objects using + * `MakeGarbageCollected()`. + */ + cppgc::AllocationHandle& GetAllocationHandle(); + + /** + * \returns the opaque heap handle which may be used to refer to this heap in + * other APIs. Valid as long as the underlying `CppHeap` is alive. + */ + cppgc::HeapHandle& GetHeapHandle(); + + /** + * Terminate clears all roots and performs multiple garbage collections to + * reclaim potentially newly created objects in destructors. + * + * After this call, object allocation is prohibited. + */ + V8_DEPRECATED("Terminate gets automatically called in the CppHeap destructor") + void Terminate(); + + /** + * \param detail_level specifies whether should return detailed + * statistics or only brief summary statistics. + * \returns current CppHeap statistics regarding memory consumption + * and utilization. + */ + cppgc::HeapStatistics CollectStatistics( + cppgc::HeapStatistics::DetailLevel detail_level); + + /** + * Collects statistics for the given spaces and reports them to the receiver. + * + * \param custom_spaces a collection of custom space indices. + * \param receiver an object that gets the results. + */ + void CollectCustomSpaceStatisticsAtLastGC( + std::vector custom_spaces, + std::unique_ptr receiver); + + /** + * Enables a detached mode that allows testing garbage collection using + * `cppgc::testing` APIs. Once used, the heap cannot be attached to an + * `Isolate` anymore. + */ + void EnableDetachedGarbageCollectionsForTesting(); + + /** + * Performs a stop-the-world garbage collection for testing purposes. + * + * \param stack_state The stack state to assume for the garbage collection. + */ + void CollectGarbageForTesting(cppgc::EmbedderStackState stack_state); + + /** + * Performs a stop-the-world minor garbage collection for testing purposes. + * + * \param stack_state The stack state to assume for the garbage collection. + */ + void CollectGarbageInYoungGenerationForTesting( + cppgc::EmbedderStackState stack_state); + + private: + CppHeap() = default; + + friend class internal::CppHeap; +}; + +class JSVisitor : public cppgc::Visitor { + public: + explicit JSVisitor(cppgc::Visitor::Key key) : cppgc::Visitor(key) {} + ~JSVisitor() override = default; + + void Trace(const TracedReferenceBase& ref) { + if (ref.IsEmptyThreadSafe()) return; + Visit(ref); + } + + protected: + using cppgc::Visitor::Visit; + + virtual void Visit(const TracedReferenceBase& ref) {} +}; + +/** + * Provided as input to `CppHeap::CollectCustomSpaceStatisticsAtLastGC()`. + * + * Its method is invoked with the results of the statistic collection. + */ +class CustomSpaceStatisticsReceiver { + public: + virtual ~CustomSpaceStatisticsReceiver() = default; + /** + * Reports the size of a space at the last GC. It is called for each space + * that was requested in `CollectCustomSpaceStatisticsAtLastGC()`. + * + * \param space_index The index of the space. + * \param bytes The total size of live objects in the space at the last GC. + * It is zero if there was no GC yet. + */ + virtual void AllocatedBytes(cppgc::CustomSpaceIndex space_index, + size_t bytes) = 0; +}; + +} // namespace v8 + +namespace cppgc { + +template +struct TraceTrait> { + static cppgc::TraceDescriptor GetTraceDescriptor(const void* self) { + return {nullptr, Trace}; + } + + static void Trace(Visitor* visitor, const void* self) { + static_cast(visitor)->Trace( + *static_cast*>(self)); + } +}; + +} // namespace cppgc + +#endif // INCLUDE_V8_CPPGC_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-data.h b/NativeScript/napi/android/v8-13/include/v8-data.h new file mode 100644 index 000000000..f9c60f4dc --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-data.h @@ -0,0 +1,90 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_DATA_H_ +#define INCLUDE_V8_DATA_H_ + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; + +/** + * The superclass of objects that can reside on V8's heap. + */ +class V8_EXPORT Data { + public: + /** + * Returns true if this data is a |v8::Value|. + */ + bool IsValue() const; + + /** + * Returns true if this data is a |v8::Module|. + */ + bool IsModule() const; + + /** + * Returns true if this data is a |v8::ModuleRequest|. + */ + bool IsModuleRequest() const; + + /** + * Returns tru if this data is a |v8::FixedArray| + */ + bool IsFixedArray() const; + + /** + * Returns true if this data is a |v8::Private|. + */ + bool IsPrivate() const; + + /** + * Returns true if this data is a |v8::ObjectTemplate|. + */ + bool IsObjectTemplate() const; + + /** + * Returns true if this data is a |v8::FunctionTemplate|. + */ + bool IsFunctionTemplate() const; + + /** + * Returns true if this data is a |v8::Context|. + */ + bool IsContext() const; + + /** + * Returns true if this value is a `CppHeapExternal` object. + */ + bool IsCppHeapExternal() const; + + private: + Data() = delete; +}; + +/** + * A fixed-sized array with elements of type Data. + */ +class V8_EXPORT FixedArray : public Data { + public: + int Length() const; + Local Get(Local context, int i) const; + + V8_INLINE static FixedArray* Cast(Data* data) { +#ifdef V8_ENABLE_CHECKS + CheckCast(data); +#endif + return reinterpret_cast(data); + } + + private: + static void CheckCast(Data* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_DATA_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-date.h b/NativeScript/napi/android/v8-13/include/v8-date.h new file mode 100644 index 000000000..5c3cbd91c --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-date.h @@ -0,0 +1,57 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_DATE_H_ +#define INCLUDE_V8_DATE_H_ + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; + +/** + * An instance of the built-in Date constructor (ECMA-262, 15.9). + */ +class V8_EXPORT Date : public Object { + public: + static V8_WARN_UNUSED_RESULT MaybeLocal New(Local context, + double time); + + static V8_WARN_UNUSED_RESULT MaybeLocal Parse( + Local context, + Local date_string); + + /** + * A specialization of Value::NumberValue that is more efficient + * because we know the structure of this object. + */ + double ValueOf() const; + + /** + * Generates ISO string representation. + */ + v8::Local ToISOString() const; + + /** + * Generates UTC string representation. + */ + v8::Local ToUTCString() const; + + V8_INLINE static Date* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + private: + static void CheckCast(Value* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_DATE_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-debug.h b/NativeScript/napi/android/v8-13/include/v8-debug.h new file mode 100644 index 000000000..23b392c7d --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-debug.h @@ -0,0 +1,179 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_DEBUG_H_ +#define INCLUDE_V8_DEBUG_H_ + +#include + +#include "v8-script.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Isolate; +class String; + +/** + * A single JavaScript stack frame. + */ +class V8_EXPORT StackFrame { + public: + /** + * Returns the source location, 0-based, for the associated function call. + */ + Location GetLocation() const; + + /** + * Returns the number, 1-based, of the line for the associate function call. + * This method will return Message::kNoLineNumberInfo if it is unable to + * retrieve the line number, or if kLineNumber was not passed as an option + * when capturing the StackTrace. + */ + int GetLineNumber() const { return GetLocation().GetLineNumber() + 1; } + + /** + * Returns the 1-based column offset on the line for the associated function + * call. + * This method will return Message::kNoColumnInfo if it is unable to retrieve + * the column number, or if kColumnOffset was not passed as an option when + * capturing the StackTrace. + */ + int GetColumn() const { return GetLocation().GetColumnNumber() + 1; } + + /** + * Returns zero based source position (character offset) for the associated + * function. + */ + int GetSourcePosition() const; + + /** + * Returns the id of the script for the function for this StackFrame. + * This method will return Message::kNoScriptIdInfo if it is unable to + * retrieve the script id, or if kScriptId was not passed as an option when + * capturing the StackTrace. + */ + int GetScriptId() const; + + /** + * Returns the name of the resource that contains the script for the + * function for this StackFrame. + */ + Local GetScriptName() const; + + /** + * Returns the name of the resource that contains the script for the + * function for this StackFrame or sourceURL value if the script name + * is undefined and its source ends with //# sourceURL=... string or + * deprecated //@ sourceURL=... string. + */ + Local GetScriptNameOrSourceURL() const; + + /** + * Returns the source of the script for the function for this StackFrame. + */ + Local GetScriptSource() const; + + /** + * Returns the source mapping URL (if one is present) of the script for + * the function for this StackFrame. + */ + Local GetScriptSourceMappingURL() const; + + /** + * Returns the name of the function associated with this stack frame. + */ + Local GetFunctionName() const; + + /** + * Returns whether or not the associated function is compiled via a call to + * eval(). + */ + bool IsEval() const; + + /** + * Returns whether or not the associated function is called as a + * constructor via "new". + */ + bool IsConstructor() const; + + /** + * Returns whether or not the associated functions is defined in wasm. + */ + bool IsWasm() const; + + /** + * Returns whether or not the associated function is defined by the user. + */ + bool IsUserJavaScript() const; +}; + +/** + * Representation of a JavaScript stack trace. The information collected is a + * snapshot of the execution stack and the information remains valid after + * execution continues. + */ +class V8_EXPORT StackTrace { + public: + /** + * Flags that determine what information is placed captured for each + * StackFrame when grabbing the current stack trace. + * Note: these options are deprecated and we always collect all available + * information (kDetailed). + */ + enum StackTraceOptions { + kLineNumber = 1, + kColumnOffset = 1 << 1 | kLineNumber, + kScriptName = 1 << 2, + kFunctionName = 1 << 3, + kIsEval = 1 << 4, + kIsConstructor = 1 << 5, + kScriptNameOrSourceURL = 1 << 6, + kScriptId = 1 << 7, + kExposeFramesAcrossSecurityOrigins = 1 << 8, + kOverview = kLineNumber | kColumnOffset | kScriptName | kFunctionName, + kDetailed = kOverview | kIsEval | kIsConstructor | kScriptNameOrSourceURL + }; + + /** + * Returns the (unique) ID of this stack trace. + */ + int GetID() const; + + /** + * Returns a StackFrame at a particular index. + */ + Local GetFrame(Isolate* isolate, uint32_t index) const; + + /** + * Returns the number of StackFrames. + */ + int GetFrameCount() const; + + /** + * Grab a snapshot of the current JavaScript execution stack. + * + * \param frame_limit The maximum number of stack frames we want to capture. + * \param options Enumerates the set of things we will capture for each + * StackFrame. + */ + static Local CurrentStackTrace( + Isolate* isolate, int frame_limit, StackTraceOptions options = kDetailed); + + /** + * Returns the first valid script name or source URL starting at the top of + * the JS stack. The returned string is either an empty handle if no script + * name/url was found or a non-zero-length string. + * + * This method is equivalent to calling StackTrace::CurrentStackTrace and + * walking the resulting frames from the beginning until a non-empty script + * name/url is found. The difference is that this method won't allocate + * a stack trace. + */ + static Local CurrentScriptNameOrSourceURL(Isolate* isolate); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_DEBUG_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-embedder-heap.h b/NativeScript/napi/android/v8-13/include/v8-embedder-heap.h new file mode 100644 index 000000000..929b3c396 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-embedder-heap.h @@ -0,0 +1,54 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_EMBEDDER_HEAP_H_ +#define INCLUDE_V8_EMBEDDER_HEAP_H_ + +#include "v8-traced-handle.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { +namespace internal { +class TracedHandles; +} // namespace internal + +class Isolate; +class Value; + +/** + * Handler for embedder roots on non-unified heap garbage collections. + */ +class V8_EXPORT EmbedderRootsHandler { + public: + virtual ~EmbedderRootsHandler() = default; + + EmbedderRootsHandler() = default; + + /** + * Used in combination with |IsRoot|. Called by V8 when an + * object that is backed by a handle is reclaimed by a non-tracing garbage + * collection. It is up to the embedder to reset the original handle. + * + * Note that the |handle| is different from the handle that the embedder holds + * for retaining the object. It is up to the embedder to find the original + * handle via the object or class id. + */ + virtual void ResetRoot(const v8::TracedReference& handle) = 0; + + /** + * Similar to |ResetRoot()|, but opportunistic. The function is called in + * parallel for different handles and as such must be thread-safe. In case, + * |false| is returned, |ResetRoot()| will be recalled for the same handle. + */ + virtual bool TryResetRoot(const v8::TracedReference& handle) { + return false; + } + + private: + friend class internal::TracedHandles; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_EMBEDDER_HEAP_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-embedder-state-scope.h b/NativeScript/napi/android/v8-13/include/v8-embedder-state-scope.h new file mode 100644 index 000000000..ec8da4573 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-embedder-state-scope.h @@ -0,0 +1,52 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_ +#define INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_ + +#include + +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; + +namespace internal { +class EmbedderState; +} // namespace internal + +// A StateTag represents a possible state of the embedder. +enum class EmbedderStateTag : uint8_t { + // reserved + EMPTY = 0, + OTHER = 1, + // embedder can define any state after +}; + +// A stack-allocated class that manages an embedder state on the isolate. +// After an EmbedderState scope has been created, a new embedder state will be +// pushed on the isolate stack. +class V8_EXPORT EmbedderStateScope { + public: + EmbedderStateScope(Isolate* isolate, Local context, + EmbedderStateTag tag); + + ~EmbedderStateScope(); + + private: + // Declaring operator new and delete as deleted is not spec compliant. + // Therefore declare them private instead to disable dynamic alloc + void* operator new(size_t size); + void* operator new[](size_t size); + void operator delete(void*, size_t); + void operator delete[](void*, size_t); + + std::unique_ptr embedder_state_; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-exception.h b/NativeScript/napi/android/v8-13/include/v8-exception.h new file mode 100644 index 000000000..5441a0ab6 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-exception.h @@ -0,0 +1,295 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_EXCEPTION_H_ +#define INCLUDE_V8_EXCEPTION_H_ + +#include + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; +class Isolate; +class Message; +class StackTrace; +class String; +class Value; + +namespace internal { +class Isolate; +class ThreadLocalTop; +} // namespace internal + +/** + * Create new error objects by calling the corresponding error object + * constructor with the message. + */ +class V8_EXPORT Exception { + public: + static Local RangeError(Local message, + Local options = {}); + static Local ReferenceError(Local message, + Local options = {}); + static Local SyntaxError(Local message, + Local options = {}); + static Local TypeError(Local message, + Local options = {}); + static Local WasmCompileError(Local message, + Local options = {}); + static Local WasmLinkError(Local message, + Local options = {}); + static Local WasmRuntimeError(Local message, + Local options = {}); + static Local WasmSuspendError(Local message, + Local options = {}); + static Local Error(Local message, Local options = {}); + + /** + * Creates an error message for the given exception. + * Will try to reconstruct the original stack trace from the exception value, + * or capture the current stack trace if not available. + */ + static Local CreateMessage(Isolate* isolate, Local exception); + + /** + * Returns the original stack trace that was captured at the creation time + * of a given exception, or an empty handle if not available. + */ + static Local GetStackTrace(Local exception); + + /** + * Captures the current stack trace and attaches it to the given object in the + * form of `stack` property. + */ + static Maybe CaptureStackTrace(Local context, + Local object); +}; + +/** + * This is a part of experimental Api and might be changed without further + * notice. + * Do not use it. + */ +enum class ExceptionContext : uint32_t { + kUnknown, + kConstructor, + kOperation, + kAttributeGet, + kAttributeSet, + kIndexedQuery, + kIndexedGetter, + kIndexedDescriptor, + kIndexedSetter, + kIndexedDefiner, + kIndexedDeleter, + kNamedQuery, + kNamedGetter, + kNamedDescriptor, + kNamedSetter, + kNamedDefiner, + kNamedDeleter, + kNamedEnumerator +}; + +/** + * This is a part of experimental Api and might be changed without further + * notice. + * Do not use it. + */ +class ExceptionPropagationMessage { + public: + ExceptionPropagationMessage(v8::Isolate* isolate, Local exception, + Local interface_name, + Local property_name, + ExceptionContext exception_context) + : isolate_(isolate), + exception_(exception), + interface_name_(interface_name), + property_name_(property_name), + exception_context_(exception_context) {} + + V8_INLINE Isolate* GetIsolate() const { return isolate_; } + V8_INLINE Local GetException() const { return exception_; } + V8_INLINE Local GetInterfaceName() const { return interface_name_; } + V8_INLINE Local GetPropertyName() const { return property_name_; } + V8_INLINE ExceptionContext GetExceptionContext() const { + return exception_context_; + } + + private: + Isolate* isolate_; + Local exception_; + Local interface_name_; + Local property_name_; + ExceptionContext exception_context_; +}; + +using ExceptionPropagationCallback = + void (*)(ExceptionPropagationMessage message); + +/** + * An external exception handler. + */ +class V8_EXPORT TryCatch { + public: + /** + * Creates a new try/catch block and registers it with v8. Note that + * all TryCatch blocks should be stack allocated because the memory + * location itself is compared against JavaScript try/catch blocks. + */ + explicit TryCatch(Isolate* isolate); + + /** + * Unregisters and deletes this try/catch block. + */ + ~TryCatch(); + + /** + * Returns true if an exception has been caught by this try/catch block. + */ + bool HasCaught() const; + + /** + * For certain types of exceptions, it makes no sense to continue execution. + * + * If CanContinue returns false, the correct action is to perform any C++ + * cleanup needed and then return. If CanContinue returns false and + * HasTerminated returns true, it is possible to call + * CancelTerminateExecution in order to continue calling into the engine. + */ + bool CanContinue() const; + + /** + * Returns true if an exception has been caught due to script execution + * being terminated. + * + * There is no JavaScript representation of an execution termination + * exception. Such exceptions are thrown when the TerminateExecution + * methods are called to terminate a long-running script. + * + * If such an exception has been thrown, HasTerminated will return true, + * indicating that it is possible to call CancelTerminateExecution in order + * to continue calling into the engine. + */ + bool HasTerminated() const; + + /** + * Throws the exception caught by this TryCatch in a way that avoids + * it being caught again by this same TryCatch. As with ThrowException + * it is illegal to execute any JavaScript operations after calling + * ReThrow; the caller must return immediately to where the exception + * is caught. + */ + Local ReThrow(); + + /** + * Returns the exception caught by this try/catch block. If no exception has + * been caught an empty handle is returned. + */ + Local Exception() const; + + /** + * Returns the .stack property of an object. If no .stack + * property is present an empty handle is returned. + */ + V8_WARN_UNUSED_RESULT static MaybeLocal StackTrace( + Local context, Local exception); + + /** + * Returns the .stack property of the thrown object. If no .stack property is + * present or if this try/catch block has not caught an exception, an empty + * handle is returned. + */ + V8_WARN_UNUSED_RESULT MaybeLocal StackTrace( + Local context) const; + + /** + * Returns the message associated with this exception. If there is + * no message associated an empty handle is returned. + */ + Local Message() const; + + /** + * Clears any exceptions that may have been caught by this try/catch block. + * After this method has been called, HasCaught() will return false. Cancels + * the scheduled exception if it is caught and ReThrow() is not called before. + * + * It is not necessary to clear a try/catch block before using it again; if + * another exception is thrown the previously caught exception will just be + * overwritten. However, it is often a good idea since it makes it easier + * to determine which operation threw a given exception. + */ + void Reset(); + + /** + * Set verbosity of the external exception handler. + * + * By default, exceptions that are caught by an external exception + * handler are not reported. Call SetVerbose with true on an + * external exception handler to have exceptions caught by the + * handler reported as if they were not caught. + */ + void SetVerbose(bool value); + + /** + * Returns true if verbosity is enabled. + */ + bool IsVerbose() const; + + /** + * Set whether or not this TryCatch should capture a Message object + * which holds source information about where the exception + * occurred. True by default. + */ + void SetCaptureMessage(bool value); + + TryCatch(const TryCatch&) = delete; + void operator=(const TryCatch&) = delete; + + private: + // Declaring operator new and delete as deleted is not spec compliant. + // Therefore declare them private instead to disable dynamic alloc + void* operator new(size_t size); + void* operator new[](size_t size); + void operator delete(void*, size_t); + void operator delete[](void*, size_t); + + /** + * There are cases when the raw address of C++ TryCatch object cannot be + * used for comparisons with addresses into the JS stack. The cases are: + * 1) ARM, ARM64 and MIPS simulators which have separate JS stack. + * 2) Address sanitizer allocates local C++ object in the heap when + * UseAfterReturn mode is enabled. + * This method returns address that can be used for comparisons with + * addresses into the JS stack. When neither simulator nor ASAN's + * UseAfterReturn is enabled, then the address returned will be the address + * of the C++ try catch handler itself. + */ + internal::Address JSStackComparableAddressPrivate() { + return js_stack_comparable_address_; + } + + void ResetInternal(); + + internal::Isolate* i_isolate_; + TryCatch* next_; + void* exception_; + void* message_obj_; + internal::Address js_stack_comparable_address_; + bool is_verbose_ : 1; + bool can_continue_ : 1; + bool capture_message_ : 1; + bool rethrow_ : 1; + + friend class internal::Isolate; + friend class internal::ThreadLocalTop; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_EXCEPTION_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-extension.h b/NativeScript/napi/android/v8-13/include/v8-extension.h new file mode 100644 index 000000000..0705e2afb --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-extension.h @@ -0,0 +1,62 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_EXTENSION_H_ +#define INCLUDE_V8_EXTENSION_H_ + +#include + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-primitive.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class FunctionTemplate; + +// --- Extensions --- + +/** + * Ignore + */ +class V8_EXPORT Extension { + public: + // Note that the strings passed into this constructor must live as long + // as the Extension itself. + Extension(const char* name, const char* source = nullptr, int dep_count = 0, + const char** deps = nullptr, int source_length = -1); + virtual ~Extension() { delete source_; } + virtual Local GetNativeFunctionTemplate( + Isolate* isolate, Local name) { + return Local(); + } + + const char* name() const { return name_; } + size_t source_length() const { return source_length_; } + const String::ExternalOneByteStringResource* source() const { + return source_; + } + int dependency_count() const { return dep_count_; } + const char** dependencies() const { return deps_; } + void set_auto_enable(bool value) { auto_enable_ = value; } + bool auto_enable() { return auto_enable_; } + + // Disallow copying and assigning. + Extension(const Extension&) = delete; + void operator=(const Extension&) = delete; + + private: + const char* name_; + size_t source_length_; // expected to initialize before source_ + String::ExternalOneByteStringResource* source_; + int dep_count_; + const char** deps_; + bool auto_enable_; +}; + +void V8_EXPORT RegisterExtension(std::unique_ptr); + +} // namespace v8 + +#endif // INCLUDE_V8_EXTENSION_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-external-memory-accounter.h b/NativeScript/napi/android/v8-13/include/v8-external-memory-accounter.h new file mode 100644 index 000000000..70df8f4a8 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-external-memory-accounter.h @@ -0,0 +1,60 @@ +// Copyright 2024 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_EXTERNAL_MEMORY_ACCOUNTER_H_ +#define INCLUDE_EXTERNAL_MEMORY_ACCOUNTER_H_ + +#include + +#include "v8-isolate.h" + +namespace v8 { + +/** + * This class is used to give V8 an indication of the amount of externally + * allocated memory that is kept alive by JavaScript objects. V8 uses this to + * decide when to perform garbage collections. Registering externally allocated + * memory will trigger garbage collections more often than it would otherwise in + * an attempt to garbage collect the JavaScript objects that keep the externally + * allocated memory alive. Instances of ExternalMemoryAccounter check that the + * reported external memory is back to 0 on destruction. + */ +class V8_EXPORT ExternalMemoryAccounter { + public: + /** + * Returns the amount of external memory registered for `isolate`. + */ + static int64_t GetTotalAmountOfExternalAllocatedMemoryForTesting( + const Isolate* isolate); + + ExternalMemoryAccounter() = default; + ~ExternalMemoryAccounter(); + ExternalMemoryAccounter(ExternalMemoryAccounter&&); + ExternalMemoryAccounter& operator=(ExternalMemoryAccounter&&); + ExternalMemoryAccounter(const ExternalMemoryAccounter&) = delete; + ExternalMemoryAccounter& operator=(const ExternalMemoryAccounter&) = delete; + + /** + * Reports an increase of `size` bytes of external memory. + */ + void Increase(Isolate* isolate, size_t size); + /** + * Reports an update of `delta` bytes of external memory. + */ + void Update(Isolate* isolate, int64_t delta); + /** + * Reports an decrease of `size` bytes of external memory. + */ + void Decrease(Isolate* isolate, size_t size); + + private: +#ifdef V8_ENABLE_MEMORY_ACCOUNTING_CHECKS + size_t amount_of_external_memory_ = 0; + v8::Isolate* isolate_ = nullptr; +#endif +}; + +} // namespace v8 + +#endif // INCLUDE_EXTERNAL_MEMORY_ACCOUNTER_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-external.h b/NativeScript/napi/android/v8-13/include/v8-external.h new file mode 100644 index 000000000..2e245036f --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-external.h @@ -0,0 +1,37 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_EXTERNAL_H_ +#define INCLUDE_V8_EXTERNAL_H_ + +#include "v8-value.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Isolate; + +/** + * A JavaScript value that wraps a C++ void*. This type of value is mainly used + * to associate C++ data structures with JavaScript objects. + */ +class V8_EXPORT External : public Value { + public: + static Local New(Isolate* isolate, void* value); + V8_INLINE static External* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + void* Value() const; + + private: + static void CheckCast(v8::Value* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_EXTERNAL_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-fast-api-calls.h b/NativeScript/napi/android/v8-13/include/v8-fast-api-calls.h new file mode 100644 index 000000000..c40594081 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-fast-api-calls.h @@ -0,0 +1,816 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_FAST_API_CALLS_H_ +#define INCLUDE_V8_FAST_API_CALLS_H_ + +/** + * This file provides additional API on top of the default one for making + * API calls, which come from embedder C++ functions. The functions are being + * called directly from optimized code, doing all the necessary typechecks + * in the compiler itself, instead of on the embedder side. Hence the "fast" + * in the name. Example usage might look like: + * + * \code + * void FastMethod(int param, bool another_param); + * + * v8::FunctionTemplate::New(isolate, SlowCallback, data, + * signature, length, constructor_behavior + * side_effect_type, + * &v8::CFunction::Make(FastMethod)); + * \endcode + * + * By design, fast calls are limited by the following requirements, which + * the embedder should enforce themselves: + * - they should not allocate on the JS heap; + * - they should not trigger JS execution. + * To enforce them, the embedder could use the existing + * v8::Isolate::DisallowJavascriptExecutionScope and a utility similar to + * Blink's NoAllocationScope: + * https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/heap/thread_state_scopes.h;l=16 + * + * Due to these limitations, it's not directly possible to report errors by + * throwing a JS exception or to otherwise do an allocation. There is an + * alternative way of creating fast calls that supports falling back to the + * slow call and then performing the necessary allocation. When one creates + * the fast method by using CFunction::MakeWithFallbackSupport instead of + * CFunction::Make, the fast callback gets as last parameter an output variable, + * through which it can request falling back to the slow call. So one might + * declare their method like: + * + * \code + * void FastMethodWithFallback(int param, FastApiCallbackOptions& options); + * \endcode + * + * If the callback wants to signal an error condition or to perform an + * allocation, it must set options.fallback to true and do an early return from + * the fast method. Then V8 checks the value of options.fallback and if it's + * true, falls back to executing the SlowCallback, which is capable of reporting + * the error (either by throwing a JS exception or logging to the console) or + * doing the allocation. It's the embedder's responsibility to ensure that the + * fast callback is idempotent up to the point where error and fallback + * conditions are checked, because otherwise executing the slow callback might + * produce visible side-effects twice. + * + * An example for custom embedder type support might employ a way to wrap/ + * unwrap various C++ types in JSObject instances, e.g: + * + * \code + * + * // Helper method with a check for field count. + * template + * inline T* GetInternalField(v8::Local wrapper) { + * assert(offset < wrapper->InternalFieldCount()); + * return reinterpret_cast( + * wrapper->GetAlignedPointerFromInternalField(offset)); + * } + * + * class CustomEmbedderType { + * public: + * // Returns the raw C object from a wrapper JS object. + * static CustomEmbedderType* Unwrap(v8::Local wrapper) { + * return GetInternalField(wrapper); + * } + * static void FastMethod(v8::Local receiver_obj, int param) { + * CustomEmbedderType* receiver = static_cast( + * receiver_obj->GetAlignedPointerFromInternalField( + * kV8EmbedderWrapperObjectIndex)); + * + * // Type checks are already done by the optimized code. + * // Then call some performance-critical method like: + * // receiver->Method(param); + * } + * + * static void SlowMethod( + * const v8::FunctionCallbackInfo& info) { + * v8::Local instance = + * v8::Local::Cast(info.Holder()); + * CustomEmbedderType* receiver = Unwrap(instance); + * // TODO: Do type checks and extract {param}. + * receiver->Method(param); + * } + * }; + * + * // TODO(mslekova): Clean-up these constants + * // The constants kV8EmbedderWrapperTypeIndex and + * // kV8EmbedderWrapperObjectIndex describe the offsets for the type info + * // struct and the native object, when expressed as internal field indices + * // within a JSObject. The existance of this helper function assumes that + * // all embedder objects have their JSObject-side type info at the same + * // offset, but this is not a limitation of the API itself. For a detailed + * // use case, see the third example. + * static constexpr int kV8EmbedderWrapperTypeIndex = 0; + * static constexpr int kV8EmbedderWrapperObjectIndex = 1; + * + * // The following setup function can be templatized based on + * // the {embedder_object} argument. + * void SetupCustomEmbedderObject(v8::Isolate* isolate, + * v8::Local context, + * CustomEmbedderType* embedder_object) { + * isolate->set_embedder_wrapper_type_index( + * kV8EmbedderWrapperTypeIndex); + * isolate->set_embedder_wrapper_object_index( + * kV8EmbedderWrapperObjectIndex); + * + * v8::CFunction c_func = + * MakeV8CFunction(CustomEmbedderType::FastMethod); + * + * Local method_template = + * v8::FunctionTemplate::New( + * isolate, CustomEmbedderType::SlowMethod, v8::Local(), + * v8::Local(), 1, v8::ConstructorBehavior::kAllow, + * v8::SideEffectType::kHasSideEffect, &c_func); + * + * v8::Local object_template = + * v8::ObjectTemplate::New(isolate); + * object_template->SetInternalFieldCount( + * kV8EmbedderWrapperObjectIndex + 1); + * object_template->Set(isolate, "method", method_template); + * + * // Instantiate the wrapper JS object. + * v8::Local object = + * object_template->NewInstance(context).ToLocalChecked(); + * object->SetAlignedPointerInInternalField( + * kV8EmbedderWrapperObjectIndex, + * reinterpret_cast(embedder_object)); + * + * // TODO: Expose {object} where it's necessary. + * } + * \endcode + * + * For instance if {object} is exposed via a global "obj" variable, + * one could write in JS: + * function hot_func() { + * obj.method(42); + * } + * and once {hot_func} gets optimized, CustomEmbedderType::FastMethod + * will be called instead of the slow version, with the following arguments: + * receiver := the {embedder_object} from above + * param := 42 + * + * Currently supported return types: + * - void + * - bool + * - int32_t + * - uint32_t + * - float32_t + * - float64_t + * Currently supported argument types: + * - pointer to an embedder type + * - JavaScript array of primitive types + * - bool + * - int32_t + * - uint32_t + * - int64_t + * - uint64_t + * - float32_t + * - float64_t + * + * The 64-bit integer types currently have the IDL (unsigned) long long + * semantics: https://heycam.github.io/webidl/#abstract-opdef-converttoint + * In the future we'll extend the API to also provide conversions from/to + * BigInt to preserve full precision. + * The floating point types currently have the IDL (unrestricted) semantics, + * which is the only one used by WebGL. We plan to add support also for + * restricted floats/doubles, similarly to the BigInt conversion policies. + * We also differ from the specific NaN bit pattern that WebIDL prescribes + * (https://heycam.github.io/webidl/#es-unrestricted-float) in that Blink + * passes NaN values as-is, i.e. doesn't normalize them. + * + * To be supported types: + * - TypedArrays and ArrayBuffers + * - arrays of embedder types + * + * + * The API offers a limited support for function overloads: + * + * \code + * void FastMethod_2Args(int param, bool another_param); + * void FastMethod_3Args(int param, bool another_param, int third_param); + * + * v8::CFunction fast_method_2args_c_func = + * MakeV8CFunction(FastMethod_2Args); + * v8::CFunction fast_method_3args_c_func = + * MakeV8CFunction(FastMethod_3Args); + * const v8::CFunction fast_method_overloads[] = {fast_method_2args_c_func, + * fast_method_3args_c_func}; + * Local method_template = + * v8::FunctionTemplate::NewWithCFunctionOverloads( + * isolate, SlowCallback, data, signature, length, + * constructor_behavior, side_effect_type, + * {fast_method_overloads, 2}); + * \endcode + * + * In this example a single FunctionTemplate is associated to multiple C++ + * functions. The overload resolution is currently only based on the number of + * arguments passed in a call. For example, if this method_template is + * registered with a wrapper JS object as described above, a call with two + * arguments: + * obj.method(42, true); + * will result in a fast call to FastMethod_2Args, while a call with three or + * more arguments: + * obj.method(42, true, 11); + * will result in a fast call to FastMethod_3Args. Instead a call with less than + * two arguments, like: + * obj.method(42); + * would not result in a fast call but would fall back to executing the + * associated SlowCallback. + */ + +#include +#include + +#include +#include + +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-typed-array.h" // NOLINT(build/include_directory) +#include "v8-value.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Isolate; + +START_ALLOW_USE_DEPRECATED() +class CTypeInfo { + public: + enum class Type : uint8_t { + kVoid, + kBool, + kUint8, + kInt32, + kUint32, + kInt64, + kUint64, + kFloat32, + kFloat64, + kPointer, + kV8Value, + kSeqOneByteString, + kApiObject, // This will be deprecated once all users have + // migrated from v8::ApiObject to v8::Local. + kAny, // This is added to enable untyped representation of fast + // call arguments for test purposes. It can represent any of + // the other types stored in the same memory as a union + // (see AnyCType declared below). This allows for + // uniform passing of arguments w.r.t. their location + // (in a register or on the stack), independent of their + // actual type. It's currently used by the arm64 simulator + // and can be added to the other simulators as well when fast + // calls having both GP and FP params need to be supported. + }; + + // kCallbackOptionsType is not part of the Type enum + // because it is only used internally. Use value 255 that is larger + // than any valid Type enum. + static constexpr Type kCallbackOptionsType = Type(255); + + enum class V8_DEPRECATE_SOON( + "There is no special support in V8 anymore, there is no need to" + "use a SequenceType") SequenceType : uint8_t { + kScalar, + kIsSequence, // sequence + kIsArrayBuffer // ArrayBuffer + }; + + enum class Flags : uint8_t { + kNone = 0, + kAllowSharedBit = 1 << 0, // Must be an ArrayBuffer or TypedArray + kEnforceRangeBit = 1 << 1, // T must be integral + kClampBit = 1 << 2, // T must be integral + kIsRestrictedBit = 1 << 3, // T must be float or double + }; + + explicit constexpr CTypeInfo(Type type, Flags flags = Flags::kNone) + : type_(type), sequence_type_(SequenceType::kScalar), flags_(flags) {} + + V8_DEPRECATE_SOON("Use CTypeInfo(Type, Flags) instead") + constexpr CTypeInfo(Type type, SequenceType sequence_type, + Flags flags = Flags::kNone) + : type_(type), sequence_type_(sequence_type), flags_(flags) {} + + typedef uint32_t Identifier; + explicit constexpr CTypeInfo(Identifier identifier) + : CTypeInfo(static_cast(identifier >> 16), + static_cast((identifier >> 8) & 255), + static_cast(identifier & 255)) {} + constexpr Identifier GetId() const { + return static_cast(type_) << 16 | + static_cast(sequence_type_) << 8 | + static_cast(flags_); + } + + constexpr Type GetType() const { return type_; } + V8_DEPRECATE_SOON("Use the constant SequenceType::kScalar instead") + constexpr SequenceType GetSequenceType() const { return sequence_type_; } + constexpr Flags GetFlags() const { return flags_; } + + static constexpr bool IsIntegralType(Type type) { + return type == Type::kUint8 || type == Type::kInt32 || + type == Type::kUint32 || type == Type::kInt64 || + type == Type::kUint64; + } + + static constexpr bool IsFloatingPointType(Type type) { + return type == Type::kFloat32 || type == Type::kFloat64; + } + + static constexpr bool IsPrimitive(Type type) { + return IsIntegralType(type) || IsFloatingPointType(type) || + type == Type::kBool; + } + + private: + Type type_; + SequenceType sequence_type_; + Flags flags_; +}; +END_ALLOW_USE_DEPRECATED() + +struct FastOneByteString { + const char* data; + uint32_t length; +}; + +class V8_EXPORT CFunctionInfo { + public: + enum class Int64Representation : uint8_t { + kNumber = 0, // Use numbers to represent 64 bit integers. + kBigInt = 1, // Use BigInts to represent 64 bit integers. + }; + + // Construct a struct to hold a CFunction's type information. + // |return_info| describes the function's return type. + // |arg_info| is an array of |arg_count| CTypeInfos describing the + // arguments. Only the last argument may be of the special type + // CTypeInfo::kCallbackOptionsType. + CFunctionInfo(const CTypeInfo& return_info, unsigned int arg_count, + const CTypeInfo* arg_info, + Int64Representation repr = Int64Representation::kNumber); + + const CTypeInfo& ReturnInfo() const { return return_info_; } + + // The argument count, not including the v8::FastApiCallbackOptions + // if present. + unsigned int ArgumentCount() const { + return HasOptions() ? arg_count_ - 1 : arg_count_; + } + + Int64Representation GetInt64Representation() const { return repr_; } + + // |index| must be less than ArgumentCount(). + // Note: if the last argument passed on construction of CFunctionInfo + // has type CTypeInfo::kCallbackOptionsType, it is not included in + // ArgumentCount(). + const CTypeInfo& ArgumentInfo(unsigned int index) const; + + bool HasOptions() const { + // The options arg is always the last one. + return arg_count_ > 0 && arg_info_[arg_count_ - 1].GetType() == + CTypeInfo::kCallbackOptionsType; + } + + private: + const CTypeInfo return_info_; + const Int64Representation repr_; + const unsigned int arg_count_; + const CTypeInfo* arg_info_; +}; + +struct FastApiCallbackOptions; + +// Provided for testing. +union V8_TRIVIAL_ABI AnyCType { + AnyCType() : int64_value(0) {} + +#if defined(V8_ENABLE_LOCAL_OFF_STACK_CHECK) && V8_HAS_ATTRIBUTE_TRIVIAL_ABI + // In this case, Local is not trivially copyable and the implicit + // copy constructor and copy assignment for the union are deleted. + AnyCType(const AnyCType& other) : int64_value(other.int64_value) {} + AnyCType& operator=(const AnyCType& other) { + int64_value = other.int64_value; + return *this; + } +#endif + + bool bool_value; + int32_t int32_value; + uint32_t uint32_value; + int64_t int64_value; + uint64_t uint64_value; + float float_value; + double double_value; + void* pointer_value; + Local object_value; + Local sequence_value; + const FastOneByteString* string_value; + FastApiCallbackOptions* options_value; +}; + +static_assert( + sizeof(AnyCType) == 8, + "The union AnyCType should have size == 64 bits, as this is assumed " + "by EffectControlLinearizer."); + +class V8_EXPORT CFunction { + public: + constexpr CFunction() : address_(nullptr), type_info_(nullptr) {} + + const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); } + + const CTypeInfo& ArgumentInfo(unsigned int index) const { + return type_info_->ArgumentInfo(index); + } + + unsigned int ArgumentCount() const { return type_info_->ArgumentCount(); } + + const void* GetAddress() const { return address_; } + CFunctionInfo::Int64Representation GetInt64Representation() const { + return type_info_->GetInt64Representation(); + } + const CFunctionInfo* GetTypeInfo() const { return type_info_; } + + enum class OverloadResolution { kImpossible, kAtRuntime, kAtCompileTime }; + + template + static CFunction Make(F* func, + CFunctionInfo::Int64Representation int64_rep = + CFunctionInfo::Int64Representation::kNumber) { + CFunction result = ArgUnwrap::Make(func, int64_rep); + result.GetInt64Representation(); + return result; + } + + // Provided for testing purposes. + template + static CFunction Make(R (*func)(Args...), + R_Patch (*patching_func)(Args_Patch...), + CFunctionInfo::Int64Representation int64_rep = + CFunctionInfo::Int64Representation::kNumber) { + CFunction c_func = ArgUnwrap::Make(func, int64_rep); + static_assert( + sizeof...(Args_Patch) == sizeof...(Args), + "The patching function must have the same number of arguments."); + c_func.address_ = reinterpret_cast(patching_func); + return c_func; + } + + CFunction(const void* address, const CFunctionInfo* type_info); + + private: + const void* address_; + const CFunctionInfo* type_info_; + + template + class ArgUnwrap { + static_assert(sizeof(F) != sizeof(F), + "CFunction must be created from a function pointer."); + }; + + template + class ArgUnwrap { + public: + static CFunction Make(R (*func)(Args...), + CFunctionInfo::Int64Representation int64_rep = + CFunctionInfo::Int64Representation::kNumber); + }; +}; + +/** + * A struct which may be passed to a fast call callback, like so: + * \code + * void FastMethodWithOptions(int param, FastApiCallbackOptions& options); + * \endcode + */ +struct FastApiCallbackOptions { + /** + * Creates a new instance of FastApiCallbackOptions for testing purpose. The + * returned instance may be filled with mock data. + */ + static FastApiCallbackOptions CreateForTesting(Isolate* isolate) { + return {}; + } + + v8::Isolate* isolate = nullptr; + + /** + * The `data` passed to the FunctionTemplate constructor, or `undefined`. + */ + v8::Local data; +}; + +namespace internal { + +// Helper to count the number of occurances of `T` in `List` +template +struct count : std::integral_constant {}; +template +struct count + : std::integral_constant::value> {}; +template +struct count : count {}; + +template +class CFunctionInfoImpl : public CFunctionInfo { + static constexpr int kOptionsArgCount = + count(); + static constexpr int kReceiverCount = 1; + + static_assert(kOptionsArgCount == 0 || kOptionsArgCount == 1, + "Only one options parameter is supported."); + + static_assert(sizeof...(ArgBuilders) >= kOptionsArgCount + kReceiverCount, + "The receiver or the options argument is missing."); + + public: + constexpr CFunctionInfoImpl() + : CFunctionInfo(RetBuilder::Build(), sizeof...(ArgBuilders), + arg_info_storage_, Representation), + arg_info_storage_{ArgBuilders::Build()...} { + constexpr CTypeInfo::Type kReturnType = RetBuilder::Build().GetType(); + static_assert(kReturnType == CTypeInfo::Type::kVoid || + kReturnType == CTypeInfo::Type::kBool || + kReturnType == CTypeInfo::Type::kInt32 || + kReturnType == CTypeInfo::Type::kUint32 || + kReturnType == CTypeInfo::Type::kInt64 || + kReturnType == CTypeInfo::Type::kUint64 || + kReturnType == CTypeInfo::Type::kFloat32 || + kReturnType == CTypeInfo::Type::kFloat64 || + kReturnType == CTypeInfo::Type::kPointer || + kReturnType == CTypeInfo::Type::kAny, + "String and api object values are not currently " + "supported return types."); + } + + private: + const CTypeInfo arg_info_storage_[sizeof...(ArgBuilders)]; +}; + +template +struct TypeInfoHelper { + static_assert(sizeof(T) != sizeof(T), "This type is not supported"); +}; + +#define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR(T, Enum) \ + template <> \ + struct TypeInfoHelper { \ + static constexpr CTypeInfo::Flags Flags() { \ + return CTypeInfo::Flags::kNone; \ + } \ + \ + static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \ + }; + +template +struct CTypeInfoTraits {}; + +#define DEFINE_TYPE_INFO_TRAITS(CType, Enum) \ + template <> \ + struct CTypeInfoTraits { \ + using ctype = CType; \ + }; + +#define PRIMITIVE_C_TYPES(V) \ + V(bool, kBool) \ + V(uint8_t, kUint8) \ + V(int32_t, kInt32) \ + V(uint32_t, kUint32) \ + V(int64_t, kInt64) \ + V(uint64_t, kUint64) \ + V(float, kFloat32) \ + V(double, kFloat64) \ + V(void*, kPointer) + +// Same as above, but includes deprecated types for compatibility. +#define ALL_C_TYPES(V) \ + PRIMITIVE_C_TYPES(V) \ + V(void, kVoid) \ + V(v8::Local, kV8Value) \ + V(v8::Local, kV8Value) \ + V(v8::Local, kV8Value) \ + V(AnyCType, kAny) + +// ApiObject was a temporary solution to wrap the pointer to the v8::Value. +// Please use v8::Local in new code for the arguments and +// v8::Local for the receiver, as ApiObject will be deprecated. + +ALL_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR) +PRIMITIVE_C_TYPES(DEFINE_TYPE_INFO_TRAITS) + +#undef PRIMITIVE_C_TYPES +#undef ALL_C_TYPES + +#undef TYPED_ARRAY_C_TYPES + +template <> +struct TypeInfoHelper { + static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; } + + static constexpr CTypeInfo::Type Type() { + return CTypeInfo::kCallbackOptionsType; + } +}; + +template <> +struct TypeInfoHelper { + static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; } + + static constexpr CTypeInfo::Type Type() { + return CTypeInfo::Type::kSeqOneByteString; + } +}; + +#define STATIC_ASSERT_IMPLIES(COND, ASSERTION, MSG) \ + static_assert(((COND) == 0) || (ASSERTION), MSG) + +} // namespace internal + +template +class V8_EXPORT CTypeInfoBuilder { + public: + using BaseType = T; + + static constexpr CTypeInfo Build() { + constexpr CTypeInfo::Flags kFlags = + MergeFlags(internal::TypeInfoHelper::Flags(), Flags...); + constexpr CTypeInfo::Type kType = internal::TypeInfoHelper::Type(); + + STATIC_ASSERT_IMPLIES( + uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kEnforceRangeBit), + CTypeInfo::IsIntegralType(kType), + "kEnforceRangeBit is only allowed for integral types."); + STATIC_ASSERT_IMPLIES( + uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kClampBit), + CTypeInfo::IsIntegralType(kType), + "kClampBit is only allowed for integral types."); + STATIC_ASSERT_IMPLIES( + uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kIsRestrictedBit), + CTypeInfo::IsFloatingPointType(kType), + "kIsRestrictedBit is only allowed for floating point types."); + + // Return the same type with the merged flags. + return CTypeInfo(internal::TypeInfoHelper::Type(), kFlags); + } + + private: + template + static constexpr CTypeInfo::Flags MergeFlags(CTypeInfo::Flags flags, + Rest... rest) { + return CTypeInfo::Flags(uint8_t(flags) | uint8_t(MergeFlags(rest...))); + } + static constexpr CTypeInfo::Flags MergeFlags() { return CTypeInfo::Flags(0); } +}; + +namespace internal { +template +class CFunctionBuilderWithFunction { + public: + explicit constexpr CFunctionBuilderWithFunction(const void* fn) : fn_(fn) {} + + template + constexpr auto Ret() { + return CFunctionBuilderWithFunction< + CTypeInfoBuilder, + ArgBuilders...>(fn_); + } + + template + constexpr auto Arg() { + // Return a copy of the builder with the Nth arg builder merged with + // template parameter pack Flags. + return ArgImpl( + std::make_index_sequence()); + } + + // Provided for testing purposes. + template + auto Patch(Ret (*patching_func)(Args...)) { + static_assert( + sizeof...(Args) == sizeof...(ArgBuilders), + "The patching function must have the same number of arguments."); + fn_ = reinterpret_cast(patching_func); + return *this; + } + + template + auto Build() { + static CFunctionInfoImpl + instance; + return CFunction(fn_, &instance); + } + + private: + template + struct GetArgBuilder; + + // Returns the same ArgBuilder as the one at index N, including its flags. + // Flags in the template parameter pack are ignored. + template + struct GetArgBuilder { + using type = std::tuple_element_t>; + }; + + // Returns an ArgBuilder with the same base type as the one at index N, + // but merges the flags with the flags in the template parameter pack. + template + struct GetArgBuilder { + using type = CTypeInfoBuilder< + typename std::tuple_element_t>::BaseType, + std::tuple_element_t>::Build().GetFlags(), + Flags...>; + }; + + // Return a copy of the CFunctionBuilder, but merges the Flags on + // ArgBuilder index N with the new Flags passed in the template parameter + // pack. + template + constexpr auto ArgImpl(std::index_sequence) { + return CFunctionBuilderWithFunction< + RetBuilder, typename GetArgBuilder::type...>(fn_); + } + + const void* fn_; +}; + +class CFunctionBuilder { + public: + constexpr CFunctionBuilder() {} + + template + constexpr auto Fn(R (*fn)(Args...)) { + return CFunctionBuilderWithFunction, + CTypeInfoBuilder...>( + reinterpret_cast(fn)); + } +}; + +} // namespace internal + +// static +template +CFunction CFunction::ArgUnwrap::Make( + R (*func)(Args...), CFunctionInfo::Int64Representation int64_rep) { + if (int64_rep == CFunctionInfo::Int64Representation::kNumber) { + return internal::CFunctionBuilder().Fn(func).Build(); + } + return internal::CFunctionBuilder() + .Fn(func) + .template Build(); +} + +using CFunctionBuilder = internal::CFunctionBuilder; + +static constexpr CTypeInfo kTypeInfoInt32 = CTypeInfo(CTypeInfo::Type::kInt32); +static constexpr CTypeInfo kTypeInfoFloat64 = + CTypeInfo(CTypeInfo::Type::kFloat64); + +/** + * Copies the contents of this JavaScript array to a C++ buffer with + * a given max_length. A CTypeInfo is passed as an argument, + * instructing different rules for conversion (e.g. restricted float/double). + * The element type T of the destination array must match the C type + * corresponding to the CTypeInfo (specified by CTypeInfoTraits). + * If the array length is larger than max_length or the array is of + * unsupported type, the operation will fail, returning false. Generally, an + * array which contains objects, undefined, null or anything not convertible + * to the requested destination type, is considered unsupported. The operation + * returns true on success. `type_info` will be used for conversions. + */ +template +bool V8_EXPORT V8_WARN_UNUSED_RESULT TryToCopyAndConvertArrayToCppBuffer( + Local src, T* dst, uint32_t max_length); + +template <> +bool V8_EXPORT V8_WARN_UNUSED_RESULT +TryToCopyAndConvertArrayToCppBuffer::Build().GetId(), + int32_t>(Local src, int32_t* dst, + uint32_t max_length); + +template <> +bool V8_EXPORT V8_WARN_UNUSED_RESULT +TryToCopyAndConvertArrayToCppBuffer::Build().GetId(), + uint32_t>(Local src, uint32_t* dst, + uint32_t max_length); + +template <> +bool V8_EXPORT V8_WARN_UNUSED_RESULT +TryToCopyAndConvertArrayToCppBuffer::Build().GetId(), + float>(Local src, float* dst, + uint32_t max_length); + +template <> +bool V8_EXPORT V8_WARN_UNUSED_RESULT +TryToCopyAndConvertArrayToCppBuffer::Build().GetId(), + double>(Local src, double* dst, + uint32_t max_length); + +} // namespace v8 + +#endif // INCLUDE_V8_FAST_API_CALLS_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-forward.h b/NativeScript/napi/android/v8-13/include/v8-forward.h new file mode 100644 index 000000000..435fe856d --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-forward.h @@ -0,0 +1,82 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_FORWARD_H_ +#define INCLUDE_V8_FORWARD_H_ + +// This header is intended to be used by headers that pass around V8 types, +// either by pointer or using Local. The full definitions can be included +// either via v8.h or the more fine-grained headers. + +#include "v8-local-handle.h" // NOLINT(build/include_directory) + +namespace v8 { + +class AccessorSignature; +class Array; +class ArrayBuffer; +class ArrayBufferView; +class BigInt; +class BigInt64Array; +class BigIntObject; +class BigUint64Array; +class Boolean; +class BooleanObject; +class Context; +class DataView; +class Data; +class Date; +class DictionaryTemplate; +class Extension; +class External; +class FixedArray; +class Float32Array; +class Float64Array; +class Function; +template +class FunctionCallbackInfo; +class FunctionTemplate; +class Int16Array; +class Int32; +class Int32Array; +class Int8Array; +class Integer; +class Isolate; +class Map; +class Module; +class Name; +class Number; +class NumberObject; +class Object; +class ObjectTemplate; +class Platform; +class Primitive; +class Private; +class Promise; +class Proxy; +class RegExp; +class Script; +class Set; +class SharedArrayBuffer; +class Signature; +class String; +class StringObject; +class Symbol; +class SymbolObject; +class Template; +class TryCatch; +class TypedArray; +class Uint16Array; +class Uint32; +class Uint32Array; +class Uint8Array; +class Uint8ClampedArray; +class UnboundModuleScript; +class Value; +class WasmMemoryObject; +class WasmModuleObject; + +} // namespace v8 + +#endif // INCLUDE_V8_FORWARD_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-function-callback.h b/NativeScript/napi/android/v8-13/include/v8-function-callback.h new file mode 100644 index 000000000..0a4de4647 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-function-callback.h @@ -0,0 +1,719 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_FUNCTION_CALLBACK_H_ +#define INCLUDE_V8_FUNCTION_CALLBACK_H_ + +#include +#include + +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-primitive.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +template +class BasicTracedReference; +template +class Global; +class Object; +class Value; + +namespace internal { +class FunctionCallbackArguments; +class PropertyCallbackArguments; +class Builtins; +} // namespace internal + +namespace debug { +class ConsoleCallArguments; +} // namespace debug + +namespace api_internal { +V8_EXPORT v8::Local GetFunctionTemplateData( + v8::Isolate* isolate, v8::Local raw_target); +} // namespace api_internal + +template +class ReturnValue { + public: + template + V8_INLINE ReturnValue(const ReturnValue& that) : value_(that.value_) { + static_assert(std::is_base_of_v, "type check"); + } + // Handle-based setters. + template + V8_INLINE void Set(const Global& handle); + template + V8_INLINE void SetNonEmpty(const Global& handle); + template + V8_INLINE void Set(const BasicTracedReference& handle); + template + V8_INLINE void SetNonEmpty(const BasicTracedReference& handle); + template + V8_INLINE void Set(const Local handle); + template + V8_INLINE void SetNonEmpty(const Local handle); + // Fast primitive number setters. + V8_INLINE void Set(bool value); + V8_INLINE void Set(double i); + V8_INLINE void Set(int16_t i); + V8_INLINE void Set(int32_t i); + V8_INLINE void Set(int64_t i); + V8_INLINE void Set(uint16_t i); + V8_INLINE void Set(uint32_t i); + V8_INLINE void Set(uint64_t i); + // Fast JS primitive setters + V8_INLINE void SetNull(); + V8_INLINE void SetUndefined(); + V8_INLINE void SetFalse(); + V8_INLINE void SetEmptyString(); + // Convenience getter for Isolate + V8_INLINE Isolate* GetIsolate() const; + + // Pointer setter: Uncompilable to prevent inadvertent misuse. + template + V8_INLINE void Set(S* whatever); + + // Getter. Creates a new Local<> so it comes with a certain performance + // hit. If the ReturnValue was not yet set, this will return the undefined + // value. + V8_INLINE Local Get() const; + + private: + template + friend class ReturnValue; + template + friend class FunctionCallbackInfo; + template + friend class PropertyCallbackInfo; + template + friend class PersistentValueMapBase; + V8_INLINE void SetInternal(internal::Address value); + // Default value depends on : + // - -> true_value, + // - -> true_value, + // - -> 0, + // - -> undefined_value, + // - -> undefined_value. + V8_INLINE void SetDefaultValue(); + V8_INLINE explicit ReturnValue(internal::Address* slot); + + // See FunctionCallbackInfo. + static constexpr int kIsolateValueIndex = -2; + + internal::Address* value_; +}; + +/** + * The argument information given to function call callbacks. This + * class provides access to information about the context of the call, + * including the receiver, the number and values of arguments, and + * the holder of the function. + */ +template +class FunctionCallbackInfo { + public: + /** The number of available arguments. */ + V8_INLINE int Length() const; + /** + * Accessor for the available arguments. Returns `undefined` if the index + * is out of bounds. + */ + V8_INLINE Local operator[](int i) const; + /** Returns the receiver. This corresponds to the "this" value. */ + V8_INLINE Local This() const; + /** For construct calls, this returns the "new.target" value. */ + V8_INLINE Local NewTarget() const; + /** Indicates whether this is a regular call or a construct call. */ + V8_INLINE bool IsConstructCall() const; + /** The data argument specified when creating the callback. */ + V8_INLINE Local Data() const; + /** The current Isolate. */ + V8_INLINE Isolate* GetIsolate() const; + /** The ReturnValue for the call. */ + V8_INLINE ReturnValue GetReturnValue() const; + + private: + friend class internal::FunctionCallbackArguments; + friend class internal::CustomArguments; + friend class debug::ConsoleCallArguments; + friend void internal::PrintFunctionCallbackInfo(void*); + + // TODO(ishell, http://crbug.com/326505377): in case of non-constructor + // call, don't pass kNewTarget and kUnused. Add IsConstructCall flag to + // kIsolate field. + static constexpr int kUnusedIndex = 0; + static constexpr int kIsolateIndex = 1; + static constexpr int kContextIndex = 2; + static constexpr int kReturnValueIndex = 3; + static constexpr int kTargetIndex = 4; + static constexpr int kNewTargetIndex = 5; + static constexpr int kArgsLength = 6; + + static constexpr int kArgsLengthWithReceiver = kArgsLength + 1; + + // Codegen constants: + static constexpr int kSize = 3 * internal::kApiSystemPointerSize; + static constexpr int kImplicitArgsOffset = 0; + static constexpr int kValuesOffset = + kImplicitArgsOffset + internal::kApiSystemPointerSize; + static constexpr int kLengthOffset = + kValuesOffset + internal::kApiSystemPointerSize; + + static constexpr int kThisValuesIndex = -1; + static_assert(ReturnValue::kIsolateValueIndex == + kIsolateIndex - kReturnValueIndex); + + V8_INLINE FunctionCallbackInfo(internal::Address* implicit_args, + internal::Address* values, int length); + + // TODO(https://crbug.com/326505377): flatten the v8::FunctionCallbackInfo + // object to avoid indirect loads through values_ and implicit_args_ and + // reduce the number of instructions in the CallApiCallback builtin. + internal::Address* implicit_args_; + internal::Address* values_; + internal::Address length_; +}; + +/** + * The information passed to a property callback about the context + * of the property access. + */ +template +class PropertyCallbackInfo { + public: + /** + * \return The isolate of the property access. + */ + V8_INLINE Isolate* GetIsolate() const; + + /** + * \return The data set in the configuration, i.e., in + * `NamedPropertyHandlerConfiguration` or + * `IndexedPropertyHandlerConfiguration.` + */ + V8_INLINE Local Data() const; + + /** + * \return The receiver. In many cases, this is the object on which the + * property access was intercepted. When using + * `Reflect.get`, `Function.prototype.call`, or similar functions, it is the + * object passed in as receiver or thisArg. + * + * \code + * void GetterCallback(Local name, + * const v8::PropertyCallbackInfo& info) { + * auto context = info.GetIsolate()->GetCurrentContext(); + * + * v8::Local a_this = + * info.This() + * ->GetRealNamedProperty(context, v8_str("a")) + * .ToLocalChecked(); + * v8::Local a_holder = + * info.Holder() + * ->GetRealNamedProperty(context, v8_str("a")) + * .ToLocalChecked(); + * + * CHECK(v8_str("r")->Equals(context, a_this).FromJust()); + * CHECK(v8_str("obj")->Equals(context, a_holder).FromJust()); + * + * info.GetReturnValue().Set(name); + * } + * + * v8::Local templ = + * v8::FunctionTemplate::New(isolate); + * templ->InstanceTemplate()->SetHandler( + * v8::NamedPropertyHandlerConfiguration(GetterCallback)); + * LocalContext env; + * env->Global() + * ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local()) + * .ToLocalChecked() + * ->NewInstance(env.local()) + * .ToLocalChecked()) + * .FromJust(); + * + * CompileRun("obj.a = 'obj'; var r = {a: 'r'}; Reflect.get(obj, 'x', r)"); + * \endcode + */ + V8_INLINE Local This() const; + + /** + * \return The object in the prototype chain of the receiver that has the + * interceptor. Suppose you have `x` and its prototype is `y`, and `y` + * has an interceptor. Then `info.This()` is `x` and `info.Holder()` is `y`. + * The Holder() could be a hidden object (the global object, rather + * than the global proxy). + * + * \note For security reasons, do not pass the object back into the runtime. + */ + V8_DEPRECATE_SOON( + "V8 will stop providing access to hidden prototype (i.e. " + "JSGlobalObject). Use HolderV2() instead. \n" + "DO NOT try to workaround this by accessing JSGlobalObject via " + "v8::Object::GetPrototype() - it'll be deprecated soon too. \n" + "See http://crbug.com/333672197. ") + V8_INLINE Local Holder() const; + + /** + * \return The object in the prototype chain of the receiver that has the + * interceptor. Suppose you have `x` and its prototype is `y`, and `y` + * has an interceptor. Then `info.This()` is `x` and `info.Holder()` is `y`. + * In case the property is installed on the global object the Holder() + * would return the global proxy. + */ + V8_INLINE Local HolderV2() const; + + /** + * \return The return value of the callback. + * Can be changed by calling Set(). + * \code + * info.GetReturnValue().Set(...) + * \endcode + * + */ + V8_INLINE ReturnValue GetReturnValue() const; + + /** + * \return True if the intercepted function should throw if an error occurs. + * Usually, `true` corresponds to `'use strict'`. + * + * \note Always `false` when intercepting `Reflect.set()` + * independent of the language mode. + */ + V8_INLINE bool ShouldThrowOnError() const; + + private: + template + friend class PropertyCallbackInfo; + friend class MacroAssembler; + friend class internal::PropertyCallbackArguments; + friend class internal::CustomArguments; + friend void internal::PrintPropertyCallbackInfo(void*); + + static constexpr int kPropertyKeyIndex = 0; + static constexpr int kShouldThrowOnErrorIndex = 1; + static constexpr int kHolderIndex = 2; + static constexpr int kIsolateIndex = 3; + static constexpr int kHolderV2Index = 4; + static constexpr int kReturnValueIndex = 5; + static constexpr int kDataIndex = 6; + static constexpr int kThisIndex = 7; + static constexpr int kArgsLength = 8; + + static constexpr int kSize = kArgsLength * internal::kApiSystemPointerSize; + + PropertyCallbackInfo() = default; + + mutable internal::Address args_[kArgsLength]; +}; + +using FunctionCallback = void (*)(const FunctionCallbackInfo& info); + +// --- Implementation --- + +template +ReturnValue::ReturnValue(internal::Address* slot) : value_(slot) {} + +template +void ReturnValue::SetInternal(internal::Address value) { +#if V8_STATIC_ROOTS_BOOL + using I = internal::Internals; + // Ensure that the upper 32-bits are not modified. Compiler should be + // able to optimize this to a store of a lower 32-bits of the value. + // This is fine since the callback can return only JavaScript values which + // are either Smis or heap objects allocated in the main cage. + *value_ = I::DecompressTaggedField(*value_, I::CompressTagged(value)); +#else + *value_ = value; +#endif // V8_STATIC_ROOTS_BOOL +} + +template +template +void ReturnValue::Set(const Global& handle) { + static_assert(std::is_base_of_v, "type check"); + if (V8_UNLIKELY(handle.IsEmpty())) { + SetDefaultValue(); + } else { + SetInternal(handle.ptr()); + } +} + +template +template +void ReturnValue::SetNonEmpty(const Global& handle) { + static_assert(std::is_base_of_v, "type check"); +#ifdef V8_ENABLE_CHECKS + internal::VerifyHandleIsNonEmpty(handle.IsEmpty()); +#endif // V8_ENABLE_CHECKS + SetInternal(handle.ptr()); +} + +template +template +void ReturnValue::Set(const BasicTracedReference& handle) { + static_assert(std::is_base_of_v, "type check"); + if (V8_UNLIKELY(handle.IsEmpty())) { + SetDefaultValue(); + } else { + SetInternal(handle.ptr()); + } +} + +template +template +void ReturnValue::SetNonEmpty(const BasicTracedReference& handle) { + static_assert(std::is_base_of_v, "type check"); +#ifdef V8_ENABLE_CHECKS + internal::VerifyHandleIsNonEmpty(handle.IsEmpty()); +#endif // V8_ENABLE_CHECKS + SetInternal(handle.ptr()); +} + +template +template +void ReturnValue::Set(const Local handle) { + // "V8_DEPRECATE_SOON" this method if |T| is |void|. +#ifdef V8_IMMINENT_DEPRECATION_WARNINGS + static constexpr bool is_allowed_void = false; + static_assert(!std::is_void_v, + "ReturnValue::Set(const Local) is deprecated. " + "Do nothing to indicate that the operation succeeded or use " + "SetFalse() to indicate that the operation failed (don't " + "forget to handle info.ShouldThrowOnError()). " + "See http://crbug.com/348660658 for details."); +#else + static constexpr bool is_allowed_void = std::is_void_v; +#endif // V8_IMMINENT_DEPRECATION_WARNINGS + static_assert(is_allowed_void || std::is_base_of_v, "type check"); + if (V8_UNLIKELY(handle.IsEmpty())) { + SetDefaultValue(); + } else if constexpr (is_allowed_void) { + // Simulate old behaviour for "v8::AccessorSetterCallback" for which + // it was possible to set the return value even for ReturnValue. + Set(handle->BooleanValue(GetIsolate())); + } else { + SetInternal(handle.ptr()); + } +} + +template +template +void ReturnValue::SetNonEmpty(const Local handle) { + // "V8_DEPRECATE_SOON" this method if |T| is |void|. +#ifdef V8_IMMINENT_DEPRECATION_WARNINGS + static constexpr bool is_allowed_void = false; + static_assert(!std::is_void_v, + "ReturnValue::SetNonEmpty(const Local) is deprecated. " + "Do nothing to indicate that the operation succeeded or use " + "SetFalse() to indicate that the operation failed (don't " + "forget to handle info.ShouldThrowOnError()). " + "See http://crbug.com/348660658 for details."); +#else + static constexpr bool is_allowed_void = std::is_void_v; +#endif // V8_IMMINENT_DEPRECATION_WARNINGS + static_assert(is_allowed_void || std::is_base_of_v, "type check"); +#ifdef V8_ENABLE_CHECKS + internal::VerifyHandleIsNonEmpty(handle.IsEmpty()); +#endif // V8_ENABLE_CHECKS + if constexpr (is_allowed_void) { + // Simulate old behaviour for "v8::AccessorSetterCallback" for which + // it was possible to set the return value even for ReturnValue. + Set(handle->BooleanValue(GetIsolate())); + } else { + SetInternal(handle.ptr()); + } +} + +template +void ReturnValue::Set(double i) { + static_assert(std::is_base_of_v, "type check"); + SetNonEmpty(Number::New(GetIsolate(), i)); +} + +template +void ReturnValue::Set(int16_t i) { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; + static_assert(I::IsValidSmi(std::numeric_limits::min())); + static_assert(I::IsValidSmi(std::numeric_limits::max())); + SetInternal(I::IntegralToSmi(i)); +} + +template +void ReturnValue::Set(int32_t i) { + static_assert(std::is_base_of_v, "type check"); + if (const auto result = internal::Internals::TryIntegralToSmi(i)) { + SetInternal(*result); + return; + } + SetNonEmpty(Integer::New(GetIsolate(), i)); +} + +template +void ReturnValue::Set(int64_t i) { + static_assert(std::is_base_of_v, "type check"); + if (const auto result = internal::Internals::TryIntegralToSmi(i)) { + SetInternal(*result); + return; + } + SetNonEmpty(Number::New(GetIsolate(), static_cast(i))); +} + +template +void ReturnValue::Set(uint16_t i) { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; + static_assert(I::IsValidSmi(std::numeric_limits::min())); + static_assert(I::IsValidSmi(std::numeric_limits::max())); + SetInternal(I::IntegralToSmi(i)); +} + +template +void ReturnValue::Set(uint32_t i) { + static_assert(std::is_base_of_v, "type check"); + if (const auto result = internal::Internals::TryIntegralToSmi(i)) { + SetInternal(*result); + return; + } + SetNonEmpty(Integer::NewFromUnsigned(GetIsolate(), i)); +} + +template +void ReturnValue::Set(uint64_t i) { + static_assert(std::is_base_of_v, "type check"); + if (const auto result = internal::Internals::TryIntegralToSmi(i)) { + SetInternal(*result); + return; + } + SetNonEmpty(Number::New(GetIsolate(), static_cast(i))); +} + +template +void ReturnValue::Set(bool value) { + static_assert(std::is_void_v || std::is_base_of_v, + "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(value ? I::StaticReadOnlyRoot::kTrueValue + : I::StaticReadOnlyRoot::kFalseValue); +#else + int root_index; + if (value) { + root_index = I::kTrueValueRootIndex; + } else { + root_index = I::kFalseValueRootIndex; + } + *value_ = I::GetRoot(GetIsolate(), root_index); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +void ReturnValue::SetDefaultValue() { + using I = internal::Internals; + if constexpr (std::is_same_v || std::is_same_v) { + Set(true); + } else if constexpr (std::is_same_v) { + SetInternal(I::IntegralToSmi(0)); + } else { + static_assert(std::is_same_v || std::is_same_v); +#if V8_STATIC_ROOTS_BOOL + SetInternal(I::StaticReadOnlyRoot::kUndefinedValue); +#else + *value_ = I::GetRoot(GetIsolate(), I::kUndefinedValueRootIndex); +#endif // V8_STATIC_ROOTS_BOOL + } +} + +template +void ReturnValue::SetNull() { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(I::StaticReadOnlyRoot::kNullValue); +#else + *value_ = I::GetRoot(GetIsolate(), I::kNullValueRootIndex); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +void ReturnValue::SetUndefined() { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(I::StaticReadOnlyRoot::kUndefinedValue); +#else + *value_ = I::GetRoot(GetIsolate(), I::kUndefinedValueRootIndex); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +void ReturnValue::SetFalse() { + static_assert(std::is_void_v || std::is_base_of_v, + "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(I::StaticReadOnlyRoot::kFalseValue); +#else + *value_ = I::GetRoot(GetIsolate(), I::kFalseValueRootIndex); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +void ReturnValue::SetEmptyString() { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(I::StaticReadOnlyRoot::kEmptyString); +#else + *value_ = I::GetRoot(GetIsolate(), I::kEmptyStringRootIndex); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +Isolate* ReturnValue::GetIsolate() const { + return *reinterpret_cast(&value_[kIsolateValueIndex]); +} + +template +Local ReturnValue::Get() const { + return Local::New(GetIsolate(), + internal::ValueHelper::SlotAsValue(value_)); +} + +template +template +void ReturnValue::Set(S* whatever) { + static_assert(sizeof(S) < 0, "incompilable to prevent inadvertent misuse"); +} + +template +FunctionCallbackInfo::FunctionCallbackInfo(internal::Address* implicit_args, + internal::Address* values, + int length) + : implicit_args_(implicit_args), values_(values), length_(length) {} + +template +Local FunctionCallbackInfo::operator[](int i) const { + // values_ points to the first argument (not the receiver). + if (i < 0 || Length() <= i) return Undefined(GetIsolate()); + return Local::FromSlot(values_ + i); +} + +template +Local FunctionCallbackInfo::This() const { + // values_ points to the first argument (not the receiver). + return Local::FromSlot(values_ + kThisValuesIndex); +} + +template +Local FunctionCallbackInfo::NewTarget() const { + return Local::FromSlot(&implicit_args_[kNewTargetIndex]); +} + +template +Local FunctionCallbackInfo::Data() const { + auto target = Local::FromSlot(&implicit_args_[kTargetIndex]); + return api_internal::GetFunctionTemplateData(GetIsolate(), target); +} + +template +Isolate* FunctionCallbackInfo::GetIsolate() const { + return *reinterpret_cast(&implicit_args_[kIsolateIndex]); +} + +template +ReturnValue FunctionCallbackInfo::GetReturnValue() const { + return ReturnValue(&implicit_args_[kReturnValueIndex]); +} + +template +bool FunctionCallbackInfo::IsConstructCall() const { + return !NewTarget()->IsUndefined(); +} + +template +int FunctionCallbackInfo::Length() const { + return static_cast(length_); +} + +template +Isolate* PropertyCallbackInfo::GetIsolate() const { + return *reinterpret_cast(&args_[kIsolateIndex]); +} + +template +Local PropertyCallbackInfo::Data() const { + return Local::FromSlot(&args_[kDataIndex]); +} + +template +Local PropertyCallbackInfo::This() const { + return Local::FromSlot(&args_[kThisIndex]); +} + +template +Local PropertyCallbackInfo::Holder() const { + return Local::FromSlot(&args_[kHolderIndex]); +} + +namespace api_internal { +// Returns JSGlobalProxy if holder is JSGlobalObject or unmodified holder +// otherwise. +V8_EXPORT internal::Address ConvertToJSGlobalProxyIfNecessary( + internal::Address holder); +} // namespace api_internal + +template +Local PropertyCallbackInfo::HolderV2() const { + using I = internal::Internals; + if (!I::HasHeapObjectTag(args_[kHolderV2Index])) { + args_[kHolderV2Index] = + api_internal::ConvertToJSGlobalProxyIfNecessary(args_[kHolderIndex]); + } + return Local::FromSlot(&args_[kHolderV2Index]); +} + +template +ReturnValue PropertyCallbackInfo::GetReturnValue() const { + return ReturnValue(&args_[kReturnValueIndex]); +} + +template +bool PropertyCallbackInfo::ShouldThrowOnError() const { + using I = internal::Internals; + if (args_[kShouldThrowOnErrorIndex] != + I::IntegralToSmi(I::kInferShouldThrowMode)) { + return args_[kShouldThrowOnErrorIndex] != I::IntegralToSmi(I::kDontThrow); + } + return v8::internal::ShouldThrowOnError( + reinterpret_cast(GetIsolate())); +} + +} // namespace v8 + +#endif // INCLUDE_V8_FUNCTION_CALLBACK_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-function.h b/NativeScript/napi/android/v8-13/include/v8-function.h new file mode 100644 index 000000000..9ee77596c --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-function.h @@ -0,0 +1,148 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_FUNCTION_H_ +#define INCLUDE_V8_FUNCTION_H_ + +#include +#include + +#include "v8-function-callback.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-message.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8-template.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; +class Location; +class UnboundScript; + +/** + * A JavaScript function object (ECMA-262, 15.3). + */ +class V8_EXPORT Function : public Object { + public: + /** + * Create a function in the current execution context + * for a given FunctionCallback. + */ + static MaybeLocal New( + Local context, FunctionCallback callback, + Local data = Local(), int length = 0, + ConstructorBehavior behavior = ConstructorBehavior::kAllow, + SideEffectType side_effect_type = SideEffectType::kHasSideEffect); + + V8_WARN_UNUSED_RESULT MaybeLocal NewInstance( + Local context, int argc, Local argv[]) const; + + V8_WARN_UNUSED_RESULT MaybeLocal NewInstance( + Local context) const { + return NewInstance(context, 0, nullptr); + } + + /** + * When side effect checks are enabled, passing kHasNoSideEffect allows the + * constructor to be invoked without throwing. Calls made within the + * constructor are still checked. + */ + V8_WARN_UNUSED_RESULT MaybeLocal NewInstanceWithSideEffectType( + Local context, int argc, Local argv[], + SideEffectType side_effect_type = SideEffectType::kHasSideEffect) const; + + V8_WARN_UNUSED_RESULT MaybeLocal Call(v8::Isolate* isolate, + Local context, + Local recv, int argc, + Local argv[]); + V8_WARN_UNUSED_RESULT MaybeLocal Call(Local context, + Local recv, int argc, + Local argv[]); + + void SetName(Local name); + Local GetName() const; + + /** + * Name inferred from variable or property assignment of this function. + * Used to facilitate debugging and profiling of JavaScript code written + * in an OO style, where many functions are anonymous but are assigned + * to object properties. + */ + Local GetInferredName() const; + + /** + * displayName if it is set, otherwise name if it is configured, otherwise + * function name, otherwise inferred name. + */ + Local GetDebugName() const; + + /** + * Returns zero based line number of function body and + * kLineOffsetNotFound if no information available. + */ + int GetScriptLineNumber() const; + /** + * Returns zero based column number of function body and + * kLineOffsetNotFound if no information available. + */ + int GetScriptColumnNumber() const; + + /** + * Returns zero based line and column number of function body, else returns + * {-1, -1}. + */ + Location GetScriptLocation() const; + + /** + * Returns zero based start position (character offset) of function body and + * kLineOffsetNotFound if no information available. + */ + int GetScriptStartPosition() const; + + /** + * Returns scriptId. + */ + int ScriptId() const; + + /** + * Returns the original function if this function is bound, else returns + * v8::Undefined. + */ + Local GetBoundFunction() const; + + /** + * Calls builtin Function.prototype.toString on this function. + * This is different from Value::ToString() that may call a user-defined + * toString() function, and different than Object::ObjectProtoToString() which + * always serializes "[object Function]". + */ + V8_WARN_UNUSED_RESULT MaybeLocal FunctionProtoToString( + Local context); + + /** + * Returns true if the function does nothing. + * The function returns false on error. + * Note that this function is experimental. Embedders should not rely on + * this existing. We may remove this function in the future. + */ + V8_WARN_UNUSED_RESULT bool Experimental_IsNopFunction() const; + + ScriptOrigin GetScriptOrigin() const; + V8_INLINE static Function* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + static const int kLineOffsetNotFound; + + private: + Function(); + static void CheckCast(Value* obj); +}; +} // namespace v8 + +#endif // INCLUDE_V8_FUNCTION_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-handle-base.h b/NativeScript/napi/android/v8-13/include/v8-handle-base.h new file mode 100644 index 000000000..9149f8fb0 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-handle-base.h @@ -0,0 +1,155 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_HANDLE_BASE_H_ +#define INCLUDE_V8_HANDLE_BASE_H_ + +#include "v8-internal.h" // NOLINT(build/include_directory) + +namespace v8::api_internal { + +template +class StackAllocated { + public: + V8_INLINE StackAllocated() = default; + + protected: + struct no_checking_tag {}; + static constexpr no_checking_tag do_not_check{}; + + V8_INLINE explicit StackAllocated(no_checking_tag) {} + V8_INLINE explicit StackAllocated(const StackAllocated& other, + no_checking_tag) {} + + V8_INLINE void VerifyOnStack() const {} +}; + +template <> +class V8_TRIVIAL_ABI StackAllocated : public StackAllocated { + public: + V8_INLINE StackAllocated() { VerifyOnStack(); } + +#if V8_HAS_ATTRIBUTE_TRIVIAL_ABI + // In this case, StackAllocated becomes not trivially copyable. + V8_INLINE StackAllocated(const StackAllocated& other) { VerifyOnStack(); } + StackAllocated& operator=(const StackAllocated&) = default; +#endif + + protected: + V8_INLINE explicit StackAllocated(no_checking_tag tag) + : StackAllocated(tag) {} + V8_INLINE explicit StackAllocated(const StackAllocated& other, + no_checking_tag tag) + : StackAllocated(other, tag) {} + +#ifdef ENABLE_SLOW_DCHECKS + V8_EXPORT void VerifyOnStack() const; +#else + V8_INLINE V8_EXPORT void VerifyOnStack() const {} +#endif +}; + +/** + * A base class for abstract handles containing indirect pointers. + * These are useful regardless of whether direct local support is enabled. + */ +class IndirectHandleBase { + public: + // Returns true if the handle is empty. + V8_INLINE bool IsEmpty() const { return location_ == nullptr; } + + // Sets the handle to be empty. IsEmpty() will then return true. + V8_INLINE void Clear() { location_ = nullptr; } + + protected: + friend class internal::ValueHelper; + friend class internal::HandleHelper; + + V8_INLINE IndirectHandleBase() = default; + V8_INLINE IndirectHandleBase(const IndirectHandleBase& other) = default; + V8_INLINE IndirectHandleBase& operator=(const IndirectHandleBase& that) = + default; + + V8_INLINE explicit IndirectHandleBase(internal::Address* location) + : location_(location) {} + + // Returns the address of the actual heap object (tagged). + // This method must be called only if the handle is not empty, otherwise it + // will crash. + V8_INLINE internal::Address ptr() const { return *location_; } + + // Returns a reference to the slot (indirect pointer). + V8_INLINE internal::Address* const& slot() const { return location_; } + V8_INLINE internal::Address*& slot() { return location_; } + + // Returns the handler's "value" (direct or indirect pointer, depending on + // whether direct local support is enabled). + template + V8_INLINE T* value() const { + return internal::ValueHelper::SlotAsValue(slot()); + } + +#ifdef V8_ENABLE_DIRECT_HANDLE + V8_INLINE internal::ValueHelper::InternalRepresentationType repr() const { + return location_ ? *location_ : internal::ValueHelper::kEmpty; + } +#else + V8_INLINE internal::ValueHelper::InternalRepresentationType repr() const { + return location_; + } +#endif // V8_ENABLE_DIRECT_HANDLE + + private: + internal::Address* location_ = nullptr; +}; + +#ifdef V8_ENABLE_DIRECT_HANDLE + +/** + * A base class for abstract handles containing direct pointers. + * These are only possible when conservative stack scanning is enabled. + */ +class DirectHandleBase { + public: + // Returns true if the handle is empty. + V8_INLINE bool IsEmpty() const { + return ptr_ == internal::ValueHelper::kEmpty; + } + + // Sets the handle to be empty. IsEmpty() will then return true. + V8_INLINE void Clear() { ptr_ = internal::ValueHelper::kEmpty; } + + protected: + friend class internal::ValueHelper; + friend class internal::HandleHelper; + + V8_INLINE DirectHandleBase() = default; + V8_INLINE DirectHandleBase(const DirectHandleBase& other) = default; + V8_INLINE DirectHandleBase& operator=(const DirectHandleBase& that) = default; + + V8_INLINE explicit DirectHandleBase(internal::Address ptr) : ptr_(ptr) {} + + // Returns the address of the referenced object. + V8_INLINE internal::Address ptr() const { return ptr_; } + + // Returns the handler's "value" (direct pointer, as direct local support + // is guaranteed to be enabled here). + template + V8_INLINE T* value() const { + return reinterpret_cast(ptr_); + } + + V8_INLINE internal::ValueHelper::InternalRepresentationType repr() const { + return ptr_; + } + + private: + internal::Address ptr_ = internal::ValueHelper::kEmpty; +}; + +#endif // V8_ENABLE_DIRECT_HANDLE + +} // namespace v8::api_internal + +#endif // INCLUDE_V8_HANDLE_BASE_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-initialization.h b/NativeScript/napi/android/v8-13/include/v8-initialization.h new file mode 100644 index 000000000..46a21a02c --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-initialization.h @@ -0,0 +1,314 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_INITIALIZATION_H_ +#define INCLUDE_V8_INITIALIZATION_H_ + +#include +#include + +#include "v8-callbacks.h" // NOLINT(build/include_directory) +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-isolate.h" // NOLINT(build/include_directory) +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +// We reserve the V8_* prefix for macros defined in V8 public API and +// assume there are no name conflicts with the embedder's code. + +/** + * The v8 JavaScript engine. + */ +namespace v8 { + +class PageAllocator; +class Platform; +template +class PersistentValueMapBase; + +/** + * EntropySource is used as a callback function when v8 needs a source + * of entropy. + */ +using EntropySource = bool (*)(unsigned char* buffer, size_t length); + +/** + * ReturnAddressLocationResolver is used as a callback function when v8 is + * resolving the location of a return address on the stack. Profilers that + * change the return address on the stack can use this to resolve the stack + * location to wherever the profiler stashed the original return address. + * + * \param return_addr_location A location on stack where a machine + * return address resides. + * \returns Either return_addr_location, or else a pointer to the profiler's + * copy of the original return address. + * + * \note The resolver function must not cause garbage collection. + */ +using ReturnAddressLocationResolver = + uintptr_t (*)(uintptr_t return_addr_location); + +using DcheckErrorCallback = void (*)(const char* file, int line, + const char* message); + +using V8FatalErrorCallback = void (*)(const char* file, int line, + const char* message); + +/** + * Container class for static utility functions. + */ +class V8_EXPORT V8 { + public: + /** + * Hand startup data to V8, in case the embedder has chosen to build + * V8 with external startup data. + * + * Note: + * - By default the startup data is linked into the V8 library, in which + * case this function is not meaningful. + * - If this needs to be called, it needs to be called before V8 + * tries to make use of its built-ins. + * - To avoid unnecessary copies of data, V8 will point directly into the + * given data blob, so pretty please keep it around until V8 exit. + * - Compression of the startup blob might be useful, but needs to + * handled entirely on the embedders' side. + * - The call will abort if the data is invalid. + */ + static void SetSnapshotDataBlob(StartupData* startup_blob); + + /** Set the callback to invoke in case of Dcheck failures. */ + static void SetDcheckErrorHandler(DcheckErrorCallback that); + + /** Set the callback to invoke in the case of CHECK failures or fatal + * errors. This is distinct from Isolate::SetFatalErrorHandler, which + * is invoked in response to API usage failures. + * */ + static void SetFatalErrorHandler(V8FatalErrorCallback that); + + /** + * Sets V8 flags from a string. + */ + static void SetFlagsFromString(const char* str); + static void SetFlagsFromString(const char* str, size_t length); + + /** + * Sets V8 flags from the command line. + */ + static void SetFlagsFromCommandLine(int* argc, char** argv, + bool remove_flags); + + /** Get the version string. */ + static const char* GetVersion(); + + /** + * Initializes V8. This function needs to be called before the first Isolate + * is created. It always returns true. + */ + V8_INLINE static bool Initialize() { +#ifdef V8_TARGET_OS_ANDROID + const bool kV8TargetOsIsAndroid = true; +#else + const bool kV8TargetOsIsAndroid = false; +#endif + +#ifdef V8_ENABLE_CHECKS + const bool kV8EnableChecks = true; +#else + const bool kV8EnableChecks = false; +#endif + + const int kBuildConfiguration = + (internal::PointerCompressionIsEnabled() ? kPointerCompression : 0) | + (internal::SmiValuesAre31Bits() ? k31BitSmis : 0) | + (internal::SandboxIsEnabled() ? kSandbox : 0) | + (kV8TargetOsIsAndroid ? kTargetOsIsAndroid : 0) | + (kV8EnableChecks ? kEnableChecks : 0); + return Initialize(kBuildConfiguration); + } + + /** + * Allows the host application to provide a callback which can be used + * as a source of entropy for random number generators. + */ + static void SetEntropySource(EntropySource source); + + /** + * Allows the host application to provide a callback that allows v8 to + * cooperate with a profiler that rewrites return addresses on stack. + */ + static void SetReturnAddressLocationResolver( + ReturnAddressLocationResolver return_address_resolver); + + /** + * Releases any resources used by v8 and stops any utility threads + * that may be running. Note that disposing v8 is permanent, it + * cannot be reinitialized. + * + * It should generally not be necessary to dispose v8 before exiting + * a process, this should happen automatically. It is only necessary + * to use if the process needs the resources taken up by v8. + */ + static bool Dispose(); + + /** + * Initialize the ICU library bundled with V8. The embedder should only + * invoke this method when using the bundled ICU. Returns true on success. + * + * If V8 was compiled with the ICU data in an external file, the location + * of the data file has to be provided. + */ + static bool InitializeICU(const char* icu_data_file = nullptr); + + /** + * Initialize the ICU library bundled with V8. The embedder should only + * invoke this method when using the bundled ICU. If V8 was compiled with + * the ICU data in an external file and when the default location of that + * file should be used, a path to the executable must be provided. + * Returns true on success. + * + * The default is a file called icudtl.dat side-by-side with the executable. + * + * Optionally, the location of the data file can be provided to override the + * default. + */ + static bool InitializeICUDefaultLocation(const char* exec_path, + const char* icu_data_file = nullptr); + + /** + * Initialize the external startup data. The embedder only needs to + * invoke this method when external startup data was enabled in a build. + * + * If V8 was compiled with the startup data in an external file, then + * V8 needs to be given those external files during startup. There are + * three ways to do this: + * - InitializeExternalStartupData(const char*) + * This will look in the given directory for the file "snapshot_blob.bin". + * - InitializeExternalStartupDataFromFile(const char*) + * As above, but will directly use the given file name. + * - Call SetSnapshotDataBlob. + * This will read the blobs from the given data structure and will + * not perform any file IO. + */ + static void InitializeExternalStartupData(const char* directory_path); + static void InitializeExternalStartupDataFromFile(const char* snapshot_blob); + + /** + * Sets the v8::Platform to use. This should be invoked before V8 is + * initialized. + */ + static void InitializePlatform(Platform* platform); + + /** + * Clears all references to the v8::Platform. This should be invoked after + * V8 was disposed. + */ + static void DisposePlatform(); + +#if defined(V8_ENABLE_SANDBOX) + /** + * Returns true if the sandbox is configured securely. + * + * If V8 cannot create a regular sandbox during initialization, for example + * because not enough virtual address space can be reserved, it will instead + * create a fallback sandbox that still allows it to function normally but + * does not have the same security properties as a regular sandbox. This API + * can be used to determine if such a fallback sandbox is being used, in + * which case it will return false. + */ + static bool IsSandboxConfiguredSecurely(); + + /** + * Provides access to the virtual address subspace backing the sandbox. + * + * This can be used to allocate pages inside the sandbox, for example to + * obtain virtual memory for ArrayBuffer backing stores, which must be + * located inside the sandbox. + * + * It should be assumed that an attacker can corrupt data inside the sandbox, + * and so in particular the contents of pages allocagted in this virtual + * address space, arbitrarily and concurrently. Due to this, it is + * recommended to to only place pure data buffers in them. + */ + static VirtualAddressSpace* GetSandboxAddressSpace(); + + /** + * Returns the size of the sandbox in bytes. + * + * This represents the size of the address space that V8 can directly address + * and in which it allocates its objects. + */ + static size_t GetSandboxSizeInBytes(); + + /** + * Returns the size of the address space reservation backing the sandbox. + * + * This may be larger than the sandbox (i.e. |GetSandboxSizeInBytes()|) due + * to surrounding guard regions, or may be smaller than the sandbox in case a + * fallback sandbox is being used, which will use a smaller virtual address + * space reservation. In the latter case this will also be different from + * |GetSandboxAddressSpace()->size()| as that will cover a larger part of the + * address space than what has actually been reserved. + */ + static size_t GetSandboxReservationSizeInBytes(); +#endif // V8_ENABLE_SANDBOX + + /** + * Activate trap-based bounds checking for WebAssembly. + * + * \param use_v8_signal_handler Whether V8 should install its own signal + * handler or rely on the embedder's. + */ + static bool EnableWebAssemblyTrapHandler(bool use_v8_signal_handler); + +#if defined(V8_OS_WIN) + /** + * On Win64, by default V8 does not emit unwinding data for jitted code, + * which means the OS cannot walk the stack frames and the system Structured + * Exception Handling (SEH) cannot unwind through V8-generated code: + * https://code.google.com/p/v8/issues/detail?id=3598. + * + * This function allows embedders to register a custom exception handler for + * exceptions in V8-generated code. + */ + static void SetUnhandledExceptionCallback( + UnhandledExceptionCallback callback); +#endif + + /** + * Allows the host application to provide a callback that will be called when + * v8 has encountered a fatal failure to allocate memory and is about to + * terminate. + */ + static void SetFatalMemoryErrorCallback(OOMErrorCallback callback); + + /** + * Get statistics about the shared memory usage. + */ + static void GetSharedMemoryStatistics(SharedMemoryStatistics* statistics); + + private: + V8(); + + enum BuildConfigurationFeatures { + kPointerCompression = 1 << 0, + k31BitSmis = 1 << 1, + kSandbox = 1 << 2, + kTargetOsIsAndroid = 1 << 3, + kEnableChecks = 1 << 4, + }; + + /** + * Checks that the embedder build configuration is compatible with + * the V8 binary and if so initializes V8. + */ + static bool Initialize(int build_config); + + friend class Context; + template + friend class PersistentValueMapBase; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_INITIALIZATION_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-inspector-protocol.h b/NativeScript/napi/android/v8-13/include/v8-inspector-protocol.h new file mode 100644 index 000000000..a5ffb7d69 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-inspector-protocol.h @@ -0,0 +1,13 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_V8_INSPECTOR_PROTOCOL_H_ +#define V8_V8_INSPECTOR_PROTOCOL_H_ + +#include "inspector/Debugger.h" // NOLINT(build/include_directory) +#include "inspector/Runtime.h" // NOLINT(build/include_directory) +#include "inspector/Schema.h" // NOLINT(build/include_directory) +#include "v8-inspector.h" // NOLINT(build/include_directory) + +#endif // V8_V8_INSPECTOR_PROTOCOL_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-inspector.h b/NativeScript/napi/android/v8-13/include/v8-inspector.h new file mode 100644 index 000000000..0992cb1c3 --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-inspector.h @@ -0,0 +1,434 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_V8_INSPECTOR_H_ +#define V8_V8_INSPECTOR_H_ + +#include + +#include +#include + +#include "v8-isolate.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) + +namespace v8 { +class Context; +class Name; +class Object; +class StackTrace; +class Value; +} // namespace v8 + +namespace v8_inspector { + +namespace internal { +class V8DebuggerId; +} // namespace internal + +namespace protocol { +namespace Debugger { +namespace API { +class SearchMatch; +} +} // namespace Debugger +namespace Runtime { +namespace API { +class RemoteObject; +class StackTrace; +class StackTraceId; +} // namespace API +} // namespace Runtime +namespace Schema { +namespace API { +class Domain; +} +} // namespace Schema +} // namespace protocol + +class V8_EXPORT StringView { + public: + StringView() : m_is8Bit(true), m_length(0), m_characters8(nullptr) {} + + StringView(const uint8_t* characters, size_t length) + : m_is8Bit(true), m_length(length), m_characters8(characters) {} + + StringView(const uint16_t* characters, size_t length) + : m_is8Bit(false), m_length(length), m_characters16(characters) {} + + bool is8Bit() const { return m_is8Bit; } + size_t length() const { return m_length; } + + // TODO(dgozman): add DCHECK(m_is8Bit) to accessors once platform can be used + // here. + const uint8_t* characters8() const { return m_characters8; } + const uint16_t* characters16() const { return m_characters16; } + + private: + bool m_is8Bit; + size_t m_length; + union { + const uint8_t* m_characters8; + const uint16_t* m_characters16; + }; +}; + +class V8_EXPORT StringBuffer { + public: + virtual ~StringBuffer() = default; + virtual StringView string() const = 0; + // This method copies contents. + static std::unique_ptr create(StringView); +}; + +class V8_EXPORT V8ContextInfo { + public: + V8ContextInfo(v8::Local context, int contextGroupId, + StringView humanReadableName) + : context(context), + contextGroupId(contextGroupId), + humanReadableName(humanReadableName), + hasMemoryOnConsole(false) {} + + v8::Local context; + // Each v8::Context is a part of a group. The group id must be non-zero. + int contextGroupId; + StringView humanReadableName; + StringView origin; + StringView auxData; + bool hasMemoryOnConsole; + + static int executionContextId(v8::Local context); + + // Disallow copying and allocating this one. + enum NotNullTagEnum { NotNullLiteral }; + void* operator new(size_t) = delete; + void* operator new(size_t, NotNullTagEnum, void*) = delete; + void* operator new(size_t, void*) = delete; + V8ContextInfo(const V8ContextInfo&) = delete; + V8ContextInfo& operator=(const V8ContextInfo&) = delete; +}; + +// This debugger id tries to be unique by generating two random +// numbers, which should most likely avoid collisions. +// Debugger id has a 1:1 mapping to context group. It is used to +// attribute stack traces to a particular debugging, when doing any +// cross-debugger operations (e.g. async step in). +// See also Runtime.UniqueDebuggerId in the protocol. +class V8_EXPORT V8DebuggerId { + public: + V8DebuggerId() = default; + V8DebuggerId(const V8DebuggerId&) = default; + V8DebuggerId& operator=(const V8DebuggerId&) = default; + + std::unique_ptr toString() const; + bool isValid() const; + std::pair pair() const; + + private: + friend class internal::V8DebuggerId; + explicit V8DebuggerId(std::pair); + + int64_t m_first = 0; + int64_t m_second = 0; +}; + +struct V8_EXPORT V8StackFrame { + StringView sourceURL; + StringView functionName; + int lineNumber; + int columnNumber; + int scriptId; +}; + +class V8_EXPORT V8StackTrace { + public: + virtual StringView firstNonEmptySourceURL() const = 0; + virtual bool isEmpty() const = 0; + virtual StringView topSourceURL() const = 0; + virtual int topLineNumber() const = 0; + virtual int topColumnNumber() const = 0; + virtual int topScriptId() const = 0; + virtual StringView topFunctionName() const = 0; + + virtual ~V8StackTrace() = default; + virtual std::unique_ptr + buildInspectorObject(int maxAsyncDepth) const = 0; + virtual std::unique_ptr toString() const = 0; + + // Safe to pass between threads, drops async chain. + virtual std::unique_ptr clone() = 0; + + virtual std::vector frames() const = 0; +}; + +class V8_EXPORT V8InspectorSession { + public: + virtual ~V8InspectorSession() = default; + + // Cross-context inspectable values (DOM nodes in different worlds, etc.). + class V8_EXPORT Inspectable { + public: + virtual v8::Local get(v8::Local) = 0; + virtual ~Inspectable() = default; + }; + virtual void addInspectedObject(std::unique_ptr) = 0; + + // Dispatching protocol messages. + static bool canDispatchMethod(StringView method); + virtual void dispatchProtocolMessage(StringView message) = 0; + virtual std::vector state() = 0; + virtual std::vector> + supportedDomains() = 0; + + // Debugger actions. + virtual void schedulePauseOnNextStatement(StringView breakReason, + StringView breakDetails) = 0; + virtual void cancelPauseOnNextStatement() = 0; + virtual void breakProgram(StringView breakReason, + StringView breakDetails) = 0; + virtual void setSkipAllPauses(bool) = 0; + virtual void resume(bool setTerminateOnResume = false) = 0; + virtual void stepOver() = 0; + virtual std::vector> + searchInTextByLines(StringView text, StringView query, bool caseSensitive, + bool isRegex) = 0; + + // Remote objects. + virtual std::unique_ptr wrapObject( + v8::Local, v8::Local, StringView groupName, + bool generatePreview) = 0; + + virtual bool unwrapObject(std::unique_ptr* error, + StringView objectId, v8::Local*, + v8::Local*, + std::unique_ptr* objectGroup) = 0; + virtual void releaseObjectGroup(StringView) = 0; + virtual void triggerPreciseCoverageDeltaUpdate(StringView occasion) = 0; + + struct V8_EXPORT EvaluateResult { + enum class ResultType { + kNotRun, + kSuccess, + kException, + }; + + ResultType type; + v8::Local value; + }; + // Evalaute 'expression' in the provided context. Does the same as + // Runtime#evaluate under-the-hood but exposed on the C++ side. + virtual EvaluateResult evaluate(v8::Local context, + StringView expression, + bool includeCommandLineAPI = false) = 0; + + // Prepare for shutdown (disables debugger pausing, etc.). + virtual void stop() = 0; +}; + +struct V8_EXPORT DeepSerializedValue { + explicit DeepSerializedValue(std::unique_ptr type, + v8::MaybeLocal value = {}) + : type(std::move(type)), value(value) {} + std::unique_ptr type; + v8::MaybeLocal value; +}; + +struct V8_EXPORT DeepSerializationResult { + explicit DeepSerializationResult( + std::unique_ptr serializedValue) + : serializedValue(std::move(serializedValue)), isSuccess(true) {} + explicit DeepSerializationResult(std::unique_ptr errorMessage) + : errorMessage(std::move(errorMessage)), isSuccess(false) {} + + // Use std::variant when available. + std::unique_ptr serializedValue; + std::unique_ptr errorMessage; + bool isSuccess; +}; + +class V8_EXPORT V8InspectorClient { + public: + virtual ~V8InspectorClient() = default; + + virtual void runMessageLoopOnPause(int contextGroupId) {} + virtual void runMessageLoopOnInstrumentationPause(int contextGroupId) { + runMessageLoopOnPause(contextGroupId); + } + virtual void quitMessageLoopOnPause() {} + virtual void runIfWaitingForDebugger(int contextGroupId) {} + + virtual void muteMetrics(int contextGroupId) {} + virtual void unmuteMetrics(int contextGroupId) {} + + virtual void beginUserGesture() {} + virtual void endUserGesture() {} + + virtual std::unique_ptr deepSerialize( + v8::Local v8Value, int maxDepth, + v8::Local additionalParameters) { + return nullptr; + } + virtual std::unique_ptr valueSubtype(v8::Local) { + return nullptr; + } + virtual std::unique_ptr descriptionForValueSubtype( + v8::Local, v8::Local) { + return nullptr; + } + virtual bool isInspectableHeapObject(v8::Local) { return true; } + + virtual v8::Local ensureDefaultContextInGroup( + int contextGroupId) { + return v8::Local(); + } + virtual void beginEnsureAllContextsInGroup(int contextGroupId) {} + virtual void endEnsureAllContextsInGroup(int contextGroupId) {} + + virtual void installAdditionalCommandLineAPI(v8::Local, + v8::Local) {} + virtual void consoleAPIMessage(int contextGroupId, + v8::Isolate::MessageErrorLevel level, + const StringView& message, + const StringView& url, unsigned lineNumber, + unsigned columnNumber, V8StackTrace*) {} + virtual v8::MaybeLocal memoryInfo(v8::Isolate*, + v8::Local) { + return v8::MaybeLocal(); + } + + virtual void consoleTime(v8::Isolate* isolate, v8::Local label) {} + virtual void consoleTimeEnd(v8::Isolate* isolate, + v8::Local label) {} + virtual void consoleTimeStamp(v8::Isolate* isolate, + v8::Local label) {} + virtual void consoleTimeStampWithArgs( + v8::Isolate* isolate, v8::Local label, + const v8::LocalVector& args) {} + virtual void consoleClear(int contextGroupId) {} + virtual double currentTimeMS() { return 0; } + typedef void (*TimerCallback)(void*); + virtual void startRepeatingTimer(double, TimerCallback, void* data) {} + virtual void cancelTimer(void* data) {} + + // TODO(dgozman): this was added to support service worker shadow page. We + // should not connect at all. + virtual bool canExecuteScripts(int contextGroupId) { return true; } + + virtual void maxAsyncCallStackDepthChanged(int depth) {} + + virtual std::unique_ptr resourceNameToUrl( + const StringView& resourceName) { + return nullptr; + } + + // The caller would defer to generating a random 64 bit integer if + // this method returns 0. + virtual int64_t generateUniqueId() { return 0; } + + virtual void dispatchError(v8::Local, v8::Local, + v8::Local) {} +}; + +// These stack trace ids are intended to be passed between debuggers and be +// resolved later. This allows to track cross-debugger calls and step between +// them if a single client connects to multiple debuggers. +struct V8_EXPORT V8StackTraceId { + uintptr_t id; + std::pair debugger_id; + bool should_pause = false; + + V8StackTraceId(); + V8StackTraceId(const V8StackTraceId&) = default; + V8StackTraceId(uintptr_t id, const std::pair debugger_id); + V8StackTraceId(uintptr_t id, const std::pair debugger_id, + bool should_pause); + explicit V8StackTraceId(StringView); + V8StackTraceId& operator=(const V8StackTraceId&) = default; + V8StackTraceId& operator=(V8StackTraceId&&) noexcept = default; + ~V8StackTraceId() = default; + + bool IsInvalid() const; + std::unique_ptr ToString(); +}; + +class V8_EXPORT V8Inspector { + public: + static std::unique_ptr create(v8::Isolate*, V8InspectorClient*); + virtual ~V8Inspector() = default; + + // Contexts instrumentation. + virtual void contextCreated(const V8ContextInfo&) = 0; + virtual void contextDestroyed(v8::Local) = 0; + virtual void resetContextGroup(int contextGroupId) = 0; + virtual v8::MaybeLocal contextById(int contextId) = 0; + virtual V8DebuggerId uniqueDebuggerId(int contextId) = 0; + virtual uint64_t isolateId() = 0; + + // Various instrumentation. + virtual void idleStarted() = 0; + virtual void idleFinished() = 0; + + // Async stack traces instrumentation. + virtual void asyncTaskScheduled(StringView taskName, void* task, + bool recurring) = 0; + virtual void asyncTaskCanceled(void* task) = 0; + virtual void asyncTaskStarted(void* task) = 0; + virtual void asyncTaskFinished(void* task) = 0; + virtual void allAsyncTasksCanceled() = 0; + + virtual V8StackTraceId storeCurrentStackTrace(StringView description) = 0; + virtual void externalAsyncTaskStarted(const V8StackTraceId& parent) = 0; + virtual void externalAsyncTaskFinished(const V8StackTraceId& parent) = 0; + + // Exceptions instrumentation. + virtual unsigned exceptionThrown(v8::Local, StringView message, + v8::Local exception, + StringView detailedMessage, StringView url, + unsigned lineNumber, unsigned columnNumber, + std::unique_ptr, + int scriptId) = 0; + virtual void exceptionRevoked(v8::Local, unsigned exceptionId, + StringView message) = 0; + virtual bool associateExceptionData(v8::Local, + v8::Local exception, + v8::Local key, + v8::Local value) = 0; + + // Connection. + class V8_EXPORT Channel { + public: + virtual ~Channel() = default; + virtual void sendResponse(int callId, + std::unique_ptr message) = 0; + virtual void sendNotification(std::unique_ptr message) = 0; + virtual void flushProtocolNotifications() = 0; + }; + enum ClientTrustLevel { kUntrusted, kFullyTrusted }; + enum SessionPauseState { kWaitingForDebugger, kNotWaitingForDebugger }; + // TODO(chromium:1352175): remove default value once downstream change lands. + // Deprecated: Use `connectShared` instead. + virtual std::unique_ptr connect( + int contextGroupId, Channel*, StringView state, + ClientTrustLevel client_trust_level, + SessionPauseState = kNotWaitingForDebugger) = 0; + + // Same as `connect` but returns a std::shared_ptr instead. + // Embedders should not deconstruct V8 sessions while the nested run loop + // (V8InspectorClient::runMessageLoopOnPause) is running. To partially ensure + // this, we defer session deconstruction until no "dispatchProtocolMessages" + // remains on the stack. + virtual std::shared_ptr connectShared( + int contextGroupId, Channel* channel, StringView state, + ClientTrustLevel clientTrustLevel, SessionPauseState pauseState) = 0; + + // API methods. + virtual std::unique_ptr createStackTrace( + v8::Local) = 0; + virtual std::unique_ptr captureStackTrace(bool fullStack) = 0; +}; + +} // namespace v8_inspector + +#endif // V8_V8_INSPECTOR_H_ diff --git a/NativeScript/napi/android/v8-13/include/v8-internal.h b/NativeScript/napi/android/v8-13/include/v8-internal.h new file mode 100644 index 000000000..95f6fa0dc --- /dev/null +++ b/NativeScript/napi/android/v8-13/include/v8-internal.h @@ -0,0 +1,1746 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_INTERNAL_H_ +#define INCLUDE_V8_INTERNAL_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "v8config.h" // NOLINT(build/include_directory) + +// TODO(pkasting): Use /spaceship unconditionally after dropping +// support for old libstdc++ versions. +#if __has_include() +#include +#endif +#if defined(__cpp_lib_three_way_comparison) && \ + __cpp_lib_three_way_comparison >= 201711L && \ + defined(__cpp_lib_concepts) && __cpp_lib_concepts >= 202002L +#include +#include + +#define V8_HAVE_SPACESHIP_OPERATOR 1 +#else +#define V8_HAVE_SPACESHIP_OPERATOR 0 +#endif + +namespace v8 { + +class Array; +class Context; +class Data; +class Isolate; + +namespace internal { + +class Heap; +class LocalHeap; +class Isolate; +class IsolateGroup; +class LocalIsolate; + +typedef uintptr_t Address; +static constexpr Address kNullAddress = 0; + +constexpr int KB = 1024; +constexpr int MB = KB * 1024; +constexpr int GB = MB * 1024; +#ifdef V8_TARGET_ARCH_X64 +constexpr size_t TB = size_t{GB} * 1024; +#endif + +/** + * Configuration of tagging scheme. + */ +const int kApiSystemPointerSize = sizeof(void*); +const int kApiDoubleSize = sizeof(double); +const int kApiInt32Size = sizeof(int32_t); +const int kApiInt64Size = sizeof(int64_t); +const int kApiSizetSize = sizeof(size_t); + +// Tag information for HeapObject. +const int kHeapObjectTag = 1; +const int kWeakHeapObjectTag = 3; +const int kHeapObjectTagSize = 2; +const intptr_t kHeapObjectTagMask = (1 << kHeapObjectTagSize) - 1; +const intptr_t kHeapObjectReferenceTagMask = 1 << (kHeapObjectTagSize - 1); + +// Tag information for fowarding pointers stored in object headers. +// 0b00 at the lowest 2 bits in the header indicates that the map word is a +// forwarding pointer. +const int kForwardingTag = 0; +const int kForwardingTagSize = 2; +const intptr_t kForwardingTagMask = (1 << kForwardingTagSize) - 1; + +// Tag information for Smi. +const int kSmiTag = 0; +const int kSmiTagSize = 1; +const intptr_t kSmiTagMask = (1 << kSmiTagSize) - 1; + +template +struct SmiTagging; + +constexpr intptr_t kIntptrAllBitsSet = intptr_t{-1}; +constexpr uintptr_t kUintptrAllBitsSet = + static_cast(kIntptrAllBitsSet); + +// Smi constants for systems where tagged pointer is a 32-bit value. +template <> +struct SmiTagging<4> { + enum { kSmiShiftSize = 0, kSmiValueSize = 31 }; + + static constexpr intptr_t kSmiMinValue = + static_cast(kUintptrAllBitsSet << (kSmiValueSize - 1)); + static constexpr intptr_t kSmiMaxValue = -(kSmiMinValue + 1); + + V8_INLINE static constexpr int SmiToInt(Address value) { + int shift_bits = kSmiTagSize + kSmiShiftSize; + // Truncate and shift down (requires >> to be sign extending). + return static_cast(static_cast(value)) >> shift_bits; + } + + template && + std::is_signed_v>* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + // Is value in range [kSmiMinValue, kSmiMaxValue]. + // Use unsigned operations in order to avoid undefined behaviour in case of + // signed integer overflow. + return (static_cast(value) - + static_cast(kSmiMinValue)) <= + (static_cast(kSmiMaxValue) - + static_cast(kSmiMinValue)); + } + + template && + std::is_unsigned_v>* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + static_assert(kSmiMaxValue <= std::numeric_limits::max()); + return value <= static_cast(kSmiMaxValue); + } + + // Same as the `intptr_t` version but works with int64_t on 32-bit builds + // without slowing down anything else. + V8_INLINE static constexpr bool IsValidSmi(int64_t value) { + return (static_cast(value) - + static_cast(kSmiMinValue)) <= + (static_cast(kSmiMaxValue) - + static_cast(kSmiMinValue)); + } + + V8_INLINE static constexpr bool IsValidSmi(uint64_t value) { + static_assert(kSmiMaxValue <= std::numeric_limits::max()); + return value <= static_cast(kSmiMaxValue); + } +}; + +// Smi constants for systems where tagged pointer is a 64-bit value. +template <> +struct SmiTagging<8> { + enum { kSmiShiftSize = 31, kSmiValueSize = 32 }; + + static constexpr intptr_t kSmiMinValue = + static_cast(kUintptrAllBitsSet << (kSmiValueSize - 1)); + static constexpr intptr_t kSmiMaxValue = -(kSmiMinValue + 1); + + V8_INLINE static constexpr int SmiToInt(Address value) { + int shift_bits = kSmiTagSize + kSmiShiftSize; + // Shift down and throw away top 32 bits. + return static_cast(static_cast(value) >> shift_bits); + } + + template && + std::is_signed_v>* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + // To be representable as a long smi, the value must be a 32-bit integer. + return std::numeric_limits::min() <= value && + value <= std::numeric_limits::max(); + } + + template && + std::is_unsigned_v>* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + return value <= std::numeric_limits::max(); + } +}; + +#ifdef V8_COMPRESS_POINTERS +// See v8:7703 or src/common/ptr-compr-inl.h for details about pointer +// compression. +constexpr size_t kPtrComprCageReservationSize = size_t{1} << 32; +constexpr size_t kPtrComprCageBaseAlignment = size_t{1} << 32; + +static_assert( + kApiSystemPointerSize == kApiInt64Size, + "Pointer compression can be enabled only for 64-bit architectures"); +const int kApiTaggedSize = kApiInt32Size; +#else +const int kApiTaggedSize = kApiSystemPointerSize; +#endif + +constexpr bool PointerCompressionIsEnabled() { + return kApiTaggedSize != kApiSystemPointerSize; +} + +#ifdef V8_31BIT_SMIS_ON_64BIT_ARCH +using PlatformSmiTagging = SmiTagging; +#else +using PlatformSmiTagging = SmiTagging; +#endif + +// TODO(ishell): Consinder adding kSmiShiftBits = kSmiShiftSize + kSmiTagSize +// since it's used much more often than the inividual constants. +const int kSmiShiftSize = PlatformSmiTagging::kSmiShiftSize; +const int kSmiValueSize = PlatformSmiTagging::kSmiValueSize; +const int kSmiMinValue = static_cast(PlatformSmiTagging::kSmiMinValue); +const int kSmiMaxValue = static_cast(PlatformSmiTagging::kSmiMaxValue); +constexpr bool SmiValuesAre31Bits() { return kSmiValueSize == 31; } +constexpr bool SmiValuesAre32Bits() { return kSmiValueSize == 32; } +constexpr bool Is64() { return kApiSystemPointerSize == sizeof(int64_t); } + +V8_INLINE static constexpr Address IntToSmi(int value) { + return (static_cast
(value) << (kSmiTagSize + kSmiShiftSize)) | + kSmiTag; +} + +/* + * Sandbox related types, constants, and functions. + */ +constexpr bool SandboxIsEnabled() { +#ifdef V8_ENABLE_SANDBOX + return true; +#else + return false; +#endif +} + +// SandboxedPointers are guaranteed to point into the sandbox. This is achieved +// for example by storing them as offset rather than as raw pointers. +using SandboxedPointer_t = Address; + +#ifdef V8_ENABLE_SANDBOX + +// Size of the sandbox, excluding the guard regions surrounding it. +#if defined(V8_TARGET_OS_ANDROID) +// On Android, most 64-bit devices seem to be configured with only 39 bits of +// virtual address space for userspace. As such, limit the sandbox to 128GB (a +// quarter of the total available address space). +constexpr size_t kSandboxSizeLog2 = 37; // 128 GB +#else +// Everywhere else use a 1TB sandbox. +constexpr size_t kSandboxSizeLog2 = 40; // 1 TB +#endif // V8_TARGET_OS_ANDROID +constexpr size_t kSandboxSize = 1ULL << kSandboxSizeLog2; + +// Required alignment of the sandbox. For simplicity, we require the +// size of the guard regions to be a multiple of this, so that this specifies +// the alignment of the sandbox including and excluding surrounding guard +// regions. The alignment requirement is due to the pointer compression cage +// being located at the start of the sandbox. +constexpr size_t kSandboxAlignment = kPtrComprCageBaseAlignment; + +// Sandboxed pointers are stored inside the heap as offset from the sandbox +// base shifted to the left. This way, it is guaranteed that the offset is +// smaller than the sandbox size after shifting it to the right again. This +// constant specifies the shift amount. +constexpr uint64_t kSandboxedPointerShift = 64 - kSandboxSizeLog2; + +// Size of the guard regions surrounding the sandbox. This assumes a worst-case +// scenario of a 32-bit unsigned index used to access an array of 64-bit values +// with an additional 4GB (compressed pointer) offset. In particular, accesses +// to TypedArrays are effectively computed as +// `entry_pointer = array->base + array->offset + index * array->element_size`. +// See also https://crbug.com/40070746 for more details. +constexpr size_t kSandboxGuardRegionSize = 32ULL * GB + 4ULL * GB; + +static_assert((kSandboxGuardRegionSize % kSandboxAlignment) == 0, + "The size of the guard regions around the sandbox must be a " + "multiple of its required alignment."); + +// On OSes where reserving virtual memory is too expensive to reserve the +// entire address space backing the sandbox, notably Windows pre 8.1, we create +// a partially reserved sandbox that doesn't actually reserve most of the +// memory, and so doesn't have the desired security properties as unrelated +// memory allocations could end up inside of it, but which still ensures that +// objects that should be located inside the sandbox are allocated within +// kSandboxSize bytes from the start of the sandbox. The minimum size of the +// region that is actually reserved for such a sandbox is specified by this +// constant and should be big enough to contain the pointer compression cage as +// well as the ArrayBuffer partition. +constexpr size_t kSandboxMinimumReservationSize = 8ULL * GB; + +static_assert(kSandboxMinimumReservationSize > kPtrComprCageReservationSize, + "The minimum reservation size for a sandbox must be larger than " + "the pointer compression cage contained within it."); + +// The maximum buffer size allowed inside the sandbox. This is mostly dependent +// on the size of the guard regions around the sandbox: an attacker must not be +// able to construct a buffer that appears larger than the guard regions and +// thereby "reach out of" the sandbox. +constexpr size_t kMaxSafeBufferSizeForSandbox = 32ULL * GB - 1; +static_assert(kMaxSafeBufferSizeForSandbox <= kSandboxGuardRegionSize, + "The maximum allowed buffer size must not be larger than the " + "sandbox's guard regions"); + +constexpr size_t kBoundedSizeShift = 29; +static_assert(1ULL << (64 - kBoundedSizeShift) == + kMaxSafeBufferSizeForSandbox + 1, + "The maximum size of a BoundedSize must be synchronized with the " + "kMaxSafeBufferSizeForSandbox"); + +#endif // V8_ENABLE_SANDBOX + +#ifdef V8_COMPRESS_POINTERS + +#ifdef V8_TARGET_OS_ANDROID +// The size of the virtual memory reservation for an external pointer table. +// This determines the maximum number of entries in a table. Using a maximum +// size allows omitting bounds checks on table accesses if the indices are +// guaranteed (e.g. through shifting) to be below the maximum index. This +// value must be a power of two. +constexpr size_t kExternalPointerTableReservationSize = 256 * MB; + +// The external pointer table indices stored in HeapObjects as external +// pointers are shifted to the left by this amount to guarantee that they are +// smaller than the maximum table size even after the C++ compiler multiplies +// them by 8 to be used as indexes into a table of 64 bit pointers. +constexpr uint32_t kExternalPointerIndexShift = 7; +#else +constexpr size_t kExternalPointerTableReservationSize = 512 * MB; +constexpr uint32_t kExternalPointerIndexShift = 6; +#endif // V8_TARGET_OS_ANDROID + +// The maximum number of entries in an external pointer table. +constexpr int kExternalPointerTableEntrySize = 8; +constexpr int kExternalPointerTableEntrySizeLog2 = 3; +constexpr size_t kMaxExternalPointers = + kExternalPointerTableReservationSize / kExternalPointerTableEntrySize; +static_assert((1 << (32 - kExternalPointerIndexShift)) == kMaxExternalPointers, + "kExternalPointerTableReservationSize and " + "kExternalPointerIndexShift don't match"); + +#else // !V8_COMPRESS_POINTERS + +// Needed for the V8.SandboxedExternalPointersCount histogram. +constexpr size_t kMaxExternalPointers = 0; + +#endif // V8_COMPRESS_POINTERS + +constexpr uint64_t kExternalPointerMarkBit = 1ULL << 48; +constexpr uint64_t kExternalPointerTagShift = 49; +constexpr uint64_t kExternalPointerTagMask = 0x00fe000000000000ULL; +constexpr uint64_t kExternalPointerShiftedTagMask = + kExternalPointerTagMask >> kExternalPointerTagShift; +static_assert(kExternalPointerShiftedTagMask << kExternalPointerTagShift == + kExternalPointerTagMask); +constexpr uint64_t kExternalPointerTagAndMarkbitMask = 0x00ff000000000000ULL; +constexpr uint64_t kExternalPointerPayloadMask = 0xff00ffffffffffffULL; + +// A ExternalPointerHandle represents a (opaque) reference to an external +// pointer that can be stored inside the sandbox. A ExternalPointerHandle has +// meaning only in combination with an (active) Isolate as it references an +// external pointer stored in the currently active Isolate's +// ExternalPointerTable. Internally, an ExternalPointerHandles is simply an +// index into an ExternalPointerTable that is shifted to the left to guarantee +// that it is smaller than the size of the table. +using ExternalPointerHandle = uint32_t; + +// ExternalPointers point to objects located outside the sandbox. When the V8 +// sandbox is enabled, these are stored on heap as ExternalPointerHandles, +// otherwise they are simply raw pointers. +#ifdef V8_ENABLE_SANDBOX +using ExternalPointer_t = ExternalPointerHandle; +#else +using ExternalPointer_t = Address; +#endif + +constexpr ExternalPointer_t kNullExternalPointer = 0; +constexpr ExternalPointerHandle kNullExternalPointerHandle = 0; + +// See `ExternalPointerHandle` for the main documentation. The difference to +// `ExternalPointerHandle` is that the handle does not represent an arbitrary +// external pointer but always refers to an object managed by `CppHeap`. The +// handles are using in combination with a dedicated table for `CppHeap` +// references. +using CppHeapPointerHandle = uint32_t; + +// The actual pointer to objects located on the `CppHeap`. When pointer +// compression is enabled these pointers are stored as `CppHeapPointerHandle`. +// In non-compressed configurations the pointers are simply stored as raw +// pointers. +#ifdef V8_COMPRESS_POINTERS +using CppHeapPointer_t = CppHeapPointerHandle; +#else +using CppHeapPointer_t = Address; +#endif + +constexpr CppHeapPointer_t kNullCppHeapPointer = 0; +constexpr CppHeapPointerHandle kNullCppHeapPointerHandle = 0; + +constexpr uint64_t kCppHeapPointerMarkBit = 1ULL; +constexpr uint64_t kCppHeapPointerTagShift = 1; +constexpr uint64_t kCppHeapPointerPayloadShift = 16; + +#ifdef V8_COMPRESS_POINTERS +// CppHeapPointers use a dedicated pointer table. These constants control the +// size and layout of the table. See the corresponding constants for the +// external pointer table for further details. +constexpr size_t kCppHeapPointerTableReservationSize = + kExternalPointerTableReservationSize; +constexpr uint32_t kCppHeapPointerIndexShift = kExternalPointerIndexShift; + +constexpr int kCppHeapPointerTableEntrySize = 8; +constexpr int kCppHeapPointerTableEntrySizeLog2 = 3; +constexpr size_t kMaxCppHeapPointers = + kCppHeapPointerTableReservationSize / kCppHeapPointerTableEntrySize; +static_assert((1 << (32 - kCppHeapPointerIndexShift)) == kMaxCppHeapPointers, + "kCppHeapPointerTableReservationSize and " + "kCppHeapPointerIndexShift don't match"); + +#else // !V8_COMPRESS_POINTERS + +// Needed for the V8.SandboxedCppHeapPointersCount histogram. +constexpr size_t kMaxCppHeapPointers = 0; + +#endif // V8_COMPRESS_POINTERS + +// Generic tag range struct to represent ranges of type tags. +// +// When referencing external objects via pointer tables, type tags are +// frequently necessary to guarantee type safety for the external objects. When +// support for subtyping is necessary, range-based type checks are used in +// which all subtypes of a given supertype use contiguous tags. This struct can +// then be used to represent such a type range. +// +// As an example, consider the following type hierarchy: +// +// A F +// / \ +// B E +// / \ +// C D +// +// A potential type id assignment for range-based type checks is +// {A: 0, B: 1, C: 2, D: 3, E: 4, F: 5}. With that, the type check for type A +// would check for the range [A, E], while the check for B would check range +// [B, D], and for F it would simply check [F, F]. +// +// In addition, there is an option for performance tweaks: if the size of the +// type range corresponding to a supertype is a power of two and starts at a +// power of two (e.g. [0x100, 0x13f]), then the compiler can often optimize +// the type check to use even fewer instructions (essentially replace a AND + +// SUB with a single AND). +// +template +struct TagRange { + static_assert(std::is_enum_v && + std::is_same_v, uint16_t>, + "Tag parameter must be an enum with base type uint16_t"); + + // Construct the inclusive tag range [first, last]. + constexpr TagRange(Tag first, Tag last) : first(first), last(last) {} + + // Construct a tag range consisting of a single tag. + // + // A single tag is always implicitly convertible to a tag range. This greatly + // increases readability as most of the time, the exact tag of a field is + // known and so no tag range needs to explicitly be created for it. + constexpr TagRange(Tag tag) // NOLINT(runtime/explicit) + : first(tag), last(tag) {} + + // Construct an empty tag range. + constexpr TagRange() : TagRange(static_cast(0)) {} + + // A tag range is considered empty if it only contains the null tag. + constexpr bool IsEmpty() const { return first == 0 && last == 0; } + + constexpr size_t Size() const { + if (IsEmpty()) { + return 0; + } else { + return last - first + 1; + } + } + + constexpr bool Contains(Tag tag) const { + // Need to perform the math with uint32_t. Otherwise, the uint16_ts would + // be promoted to (signed) int, allowing the compiler to (wrongly) assume + // that an underflow cannot happen as that would be undefined behavior. + return static_cast(tag) - first <= + static_cast(last) - first; + } + + constexpr bool Contains(TagRange tag_range) const { + return tag_range.first >= first && tag_range.last <= last; + } + + constexpr bool operator==(const TagRange other) const { + return first == other.first && last == other.last; + } + + constexpr size_t hash_value() const { + static_assert(std::is_same_v, uint16_t>); + return (static_cast(first) << 16) | last; + } + + // Internally we represent tag ranges as half-open ranges [first, last). + const Tag first; + const Tag last; +}; + +// +// External Pointers. +// +// When the sandbox is enabled, external pointers are stored in an external +// pointer table and are referenced from HeapObjects through an index (a +// "handle"). When stored in the table, the pointers are tagged with per-type +// tags to prevent type confusion attacks between different external objects. +// +// When loading an external pointer, a range of allowed tags can be specified. +// This way, type hierarchies can be supported. The main requirement for that +// is that all (transitive) child classes of a given parent class have type ids +// in the same range, and that there are no unrelated types in that range. For +// more details about how to assign type tags to types, see the TagRange class. +// +// The external pointer sandboxing mechanism ensures that every access to an +// external pointer field will result in a valid pointer of the expected type +// even in the presence of an attacker able to corrupt memory inside the +// sandbox. However, if any data related to the external object is stored +// inside the sandbox it may still be corrupted and so must be validated before +// use or moved into the external object. Further, an attacker will always be +// able to substitute different external pointers of the same type for each +// other. Therefore, code using external pointers must be written in a +// "substitution-safe" way, i.e. it must always be possible to substitute +// external pointers of the same type without causing memory corruption outside +// of the sandbox. Generally this is achieved by referencing any group of +// related external objects through a single external pointer. +// +// Currently we use bit 62 for the marking bit which should always be unused as +// it's part of the non-canonical address range. When Arm's top-byte ignore +// (TBI) is enabled, this bit will be part of the ignored byte, and we assume +// that the Embedder is not using this byte (really only this one bit) for any +// other purpose. This bit also does not collide with the memory tagging +// extension (MTE) which would use bits [56, 60). +// +// External pointer tables are also available even when the sandbox is off but +// pointer compression is on. In that case, the mechanism can be used to ease +// alignment requirements as it turns unaligned 64-bit raw pointers into +// aligned 32-bit indices. To "opt-in" to the external pointer table mechanism +// for this purpose, instead of using the ExternalPointer accessors one needs to +// use ExternalPointerHandles directly and use them to access the pointers in an +// ExternalPointerTable. +// +// The tag is currently in practice limited to 15 bits since it needs to fit +// together with a marking bit into the unused parts of a pointer. +enum ExternalPointerTag : uint16_t { + kFirstExternalPointerTag = 0, + kExternalPointerNullTag = 0, + + // When adding new tags, please ensure that the code using these tags is + // "substitution-safe", i.e. still operate safely if external pointers of the + // same type are swapped by an attacker. See comment above for more details. + + // Shared external pointers are owned by the shared Isolate and stored in the + // shared external pointer table associated with that Isolate, where they can + // be accessed from multiple threads at the same time. The objects referenced + // in this way must therefore always be thread-safe. + kFirstSharedExternalPointerTag, + kWaiterQueueNodeTag = kFirstSharedExternalPointerTag, + kExternalStringResourceTag, + kExternalStringResourceDataTag, + kLastSharedExternalPointerTag = kExternalStringResourceDataTag, + + // External pointers using these tags are kept in a per-Isolate external + // pointer table and can only be accessed when this Isolate is active. + kNativeContextMicrotaskQueueTag, + kEmbedderDataSlotPayloadTag, + // This tag essentially stands for a `void*` pointer in the V8 API, and it is + // the Embedder's responsibility to ensure type safety (against substitution) + // and lifetime validity of these objects. + kExternalObjectValueTag, + kFirstMaybeReadOnlyExternalPointerTag, + kFunctionTemplateInfoCallbackTag = kFirstMaybeReadOnlyExternalPointerTag, + kAccessorInfoGetterTag, + kAccessorInfoSetterTag, + + // InterceptorInfo external pointers. + kFirstInterceptorInfoExternalPointerTag, + kApiNamedPropertyQueryCallbackTag = kFirstInterceptorInfoExternalPointerTag, + kApiNamedPropertyGetterCallbackTag, + kApiNamedPropertySetterCallbackTag, + kApiNamedPropertyDescriptorCallbackTag, + kApiNamedPropertyDefinerCallbackTag, + kApiNamedPropertyDeleterCallbackTag, + kApiNamedPropertyEnumeratorCallbackTag, + kApiIndexedPropertyQueryCallbackTag, + kApiIndexedPropertyGetterCallbackTag, + kApiIndexedPropertySetterCallbackTag, + kApiIndexedPropertyDescriptorCallbackTag, + kApiIndexedPropertyDefinerCallbackTag, + kApiIndexedPropertyDeleterCallbackTag, + kApiIndexedPropertyEnumeratorCallbackTag, + kLastInterceptorInfoExternalPointerTag = + kApiIndexedPropertyEnumeratorCallbackTag, + + kLastMaybeReadOnlyExternalPointerTag = kLastInterceptorInfoExternalPointerTag, + + kWasmInternalFunctionCallTargetTag, + kWasmTypeInfoNativeTypeTag, + kWasmExportedFunctionDataSignatureTag, + kWasmStackMemoryTag, + kWasmIndirectFunctionTargetTag, + + // Foreigns + kFirstForeignExternalPointerTag, + kGenericForeignTag = kFirstForeignExternalPointerTag, + + kApiAccessCheckCallbackTag, + kApiAbortScriptExecutionCallbackTag, + kSyntheticModuleTag, + kMicrotaskCallbackTag, + kMicrotaskCallbackDataTag, + kCFunctionTag, + kCFunctionInfoTag, + kMessageListenerTag, + kWaiterQueueForeignTag, + + // Managed + kFirstManagedResourceTag, + kFirstManagedExternalPointerTag = kFirstManagedResourceTag, + kGenericManagedTag = kFirstManagedExternalPointerTag, + kWasmWasmStreamingTag, + kWasmFuncDataTag, + kWasmManagedDataTag, + kWasmNativeModuleTag, + kIcuBreakIteratorTag, + kIcuUnicodeStringTag, + kIcuListFormatterTag, + kIcuLocaleTag, + kIcuSimpleDateFormatTag, + kIcuDateIntervalFormatTag, + kIcuRelativeDateTimeFormatterTag, + kIcuLocalizedNumberFormatterTag, + kIcuPluralRulesTag, + kIcuCollatorTag, + kDisplayNamesInternalTag, + kD8WorkerTag, + kD8ModuleEmbedderDataTag, + kLastForeignExternalPointerTag = kD8ModuleEmbedderDataTag, + kLastManagedExternalPointerTag = kLastForeignExternalPointerTag, + // External resources whose lifetime is tied to their entry in the external + // pointer table but which are not referenced via a Managed + kArrayBufferExtensionTag, + kLastManagedResourceTag = kArrayBufferExtensionTag, + + kExternalPointerZappedEntryTag = 0x7d, + kExternalPointerEvacuationEntryTag = 0x7e, + kExternalPointerFreeEntryTag = 0x7f, + // The tags are limited to 7 bits, so the last tag is 0x7f. + kLastExternalPointerTag = 0x7f, +}; + +using ExternalPointerTagRange = TagRange; + +constexpr ExternalPointerTagRange kAnyExternalPointerTagRange( + kFirstExternalPointerTag, kLastExternalPointerTag); +constexpr ExternalPointerTagRange kAnySharedExternalPointerTagRange( + kFirstSharedExternalPointerTag, kLastSharedExternalPointerTag); +constexpr ExternalPointerTagRange kAnyForeignExternalPointerTagRange( + kFirstForeignExternalPointerTag, kLastForeignExternalPointerTag); +constexpr ExternalPointerTagRange kAnyInterceptorInfoExternalPointerTagRange( + kFirstInterceptorInfoExternalPointerTag, + kLastInterceptorInfoExternalPointerTag); +constexpr ExternalPointerTagRange kAnyManagedExternalPointerTagRange( + kFirstManagedExternalPointerTag, kLastManagedExternalPointerTag); +constexpr ExternalPointerTagRange kAnyMaybeReadOnlyExternalPointerTagRange( + kFirstMaybeReadOnlyExternalPointerTag, + kLastMaybeReadOnlyExternalPointerTag); +constexpr ExternalPointerTagRange kAnyManagedResourceExternalPointerTag( + kFirstManagedResourceTag, kLastManagedResourceTag); + +// True if the external pointer must be accessed from the shared isolate's +// external pointer table. +V8_INLINE static constexpr bool IsSharedExternalPointerType( + ExternalPointerTagRange tag_range) { + return kAnySharedExternalPointerTagRange.Contains(tag_range); +} + +// True if the external pointer may live in a read-only object, in which case +// the table entry will be in the shared read-only segment of the external +// pointer table. +V8_INLINE static constexpr bool IsMaybeReadOnlyExternalPointerType( + ExternalPointerTagRange tag_range) { + return kAnyMaybeReadOnlyExternalPointerTagRange.Contains(tag_range); +} + +// True if the external pointer references an external object whose lifetime is +// tied to the entry in the external pointer table. +// In this case, the entry in the ExternalPointerTable always points to an +// object derived from ExternalPointerTable::ManagedResource. +V8_INLINE static constexpr bool IsManagedExternalPointerType( + ExternalPointerTagRange tag_range) { + return kAnyManagedResourceExternalPointerTag.Contains(tag_range); +} + +// When an external poiner field can contain the null external pointer handle, +// the type checking mechanism needs to also check for null. +// TODO(saelo): this is mostly a temporary workaround to introduce range-based +// type checks. In the future, we should either (a) change the type tagging +// scheme so that null always passes or (b) (more likely) introduce dedicated +// null entries for those tags that need them (similar to other well-known +// empty value constants such as the empty fixed array). +V8_INLINE static constexpr bool ExternalPointerCanBeEmpty( + ExternalPointerTagRange tag_range) { + return tag_range.Contains(kArrayBufferExtensionTag) || + tag_range.Contains(kEmbedderDataSlotPayloadTag) || + kAnyInterceptorInfoExternalPointerTagRange.Contains(tag_range); +} + +// Indirect Pointers. +// +// When the sandbox is enabled, indirect pointers are used to reference +// HeapObjects that live outside of the sandbox (but are still managed by V8's +// garbage collector). When object A references an object B through an indirect +// pointer, object A will contain a IndirectPointerHandle, i.e. a shifted +// 32-bit index, which identifies an entry in a pointer table (either the +// trusted pointer table for TrustedObjects, or the code pointer table if it is +// a Code object). This table entry then contains the actual pointer to object +// B. Further, object B owns this pointer table entry, and it is responsible +// for updating the "self-pointer" in the entry when it is relocated in memory. +// This way, in contrast to "normal" pointers, indirect pointers never need to +// be tracked by the GC (i.e. there is no remembered set for them). +// These pointers do not exist when the sandbox is disabled. + +// An IndirectPointerHandle represents a 32-bit index into a pointer table. +using IndirectPointerHandle = uint32_t; + +// A null handle always references an entry that contains nullptr. +constexpr IndirectPointerHandle kNullIndirectPointerHandle = 0; + +// When the sandbox is enabled, indirect pointers are used to implement: +// - TrustedPointers: an indirect pointer using the trusted pointer table (TPT) +// and referencing a TrustedObject in one of the trusted heap spaces. +// - CodePointers, an indirect pointer using the code pointer table (CPT) and +// referencing a Code object together with its instruction stream. + +// +// Trusted Pointers. +// +// A pointer to a TrustedObject. +// When the sandbox is enabled, these are indirect pointers using the trusted +// pointer table (TPT). They are used to reference trusted objects (located in +// one of V8's trusted heap spaces, outside of the sandbox) from inside the +// sandbox in a memory-safe way. When the sandbox is disabled, these are +// regular tagged pointers. +using TrustedPointerHandle = IndirectPointerHandle; + +// The size of the virtual memory reservation for the trusted pointer table. +// As with the external pointer table, a maximum table size in combination with +// shifted indices allows omitting bounds checks. +constexpr size_t kTrustedPointerTableReservationSize = 64 * MB; + +// The trusted pointer handles are stored shifted to the left by this amount +// to guarantee that they are smaller than the maximum table size. +constexpr uint32_t kTrustedPointerHandleShift = 9; + +// A null handle always references an entry that contains nullptr. +constexpr TrustedPointerHandle kNullTrustedPointerHandle = + kNullIndirectPointerHandle; + +// The maximum number of entries in an trusted pointer table. +constexpr int kTrustedPointerTableEntrySize = 8; +constexpr int kTrustedPointerTableEntrySizeLog2 = 3; +constexpr size_t kMaxTrustedPointers = + kTrustedPointerTableReservationSize / kTrustedPointerTableEntrySize; +static_assert((1 << (32 - kTrustedPointerHandleShift)) == kMaxTrustedPointers, + "kTrustedPointerTableReservationSize and " + "kTrustedPointerHandleShift don't match"); + +// +// Code Pointers. +// +// A pointer to a Code object. +// Essentially a specialized version of a trusted pointer that (when the +// sandbox is enabled) uses the code pointer table (CPT) instead of the TPT. +// Each entry in the CPT contains both a pointer to a Code object as well as a +// pointer to the Code's entrypoint. This allows calling/jumping into Code with +// one fewer memory access (compared to the case where the entrypoint pointer +// first needs to be loaded from the Code object). As such, a CodePointerHandle +// can be used both to obtain the referenced Code object and to directly load +// its entrypoint. +// +// When the sandbox is disabled, these are regular tagged pointers. +using CodePointerHandle = IndirectPointerHandle; + +// The size of the virtual memory reservation for the code pointer table. +// As with the other tables, a maximum table size in combination with shifted +// indices allows omitting bounds checks. +constexpr size_t kCodePointerTableReservationSize = 128 * MB; + +// Code pointer handles are shifted by a different amount than indirect pointer +// handles as the tables have a different maximum size. +constexpr uint32_t kCodePointerHandleShift = 9; + +// A null handle always references an entry that contains nullptr. +constexpr CodePointerHandle kNullCodePointerHandle = kNullIndirectPointerHandle; + +// It can sometimes be necessary to distinguish a code pointer handle from a +// trusted pointer handle. A typical example would be a union trusted pointer +// field that can refer to both Code objects and other trusted objects. To +// support these use-cases, we use a simple marking scheme where some of the +// low bits of a code pointer handle are set, while they will be unset on a +// trusted pointer handle. This way, the correct table to resolve the handle +// can be determined even in the absence of a type tag. +constexpr uint32_t kCodePointerHandleMarker = 0x1; +static_assert(kCodePointerHandleShift > 0); +static_assert(kTrustedPointerHandleShift > 0); + +// The maximum number of entries in a code pointer table. +constexpr int kCodePointerTableEntrySize = 16; +constexpr int kCodePointerTableEntrySizeLog2 = 4; +constexpr size_t kMaxCodePointers = + kCodePointerTableReservationSize / kCodePointerTableEntrySize; +static_assert( + (1 << (32 - kCodePointerHandleShift)) == kMaxCodePointers, + "kCodePointerTableReservationSize and kCodePointerHandleShift don't match"); + +constexpr int kCodePointerTableEntryEntrypointOffset = 0; +constexpr int kCodePointerTableEntryCodeObjectOffset = 8; + +// Constants that can be used to mark places that should be modified once +// certain types of objects are moved out of the sandbox and into trusted space. +constexpr bool kRuntimeGeneratedCodeObjectsLiveInTrustedSpace = true; +constexpr bool kBuiltinCodeObjectsLiveInTrustedSpace = false; +constexpr bool kAllCodeObjectsLiveInTrustedSpace = + kRuntimeGeneratedCodeObjectsLiveInTrustedSpace && + kBuiltinCodeObjectsLiveInTrustedSpace; + +// {obj} must be the raw tagged pointer representation of a HeapObject +// that's guaranteed to never be in ReadOnlySpace. +V8_EXPORT internal::Isolate* IsolateFromNeverReadOnlySpaceObject(Address obj); + +// Returns if we need to throw when an error occurs. This infers the language +// mode based on the current context and the closure. This returns true if the +// language mode is strict. +V8_EXPORT bool ShouldThrowOnError(internal::Isolate* isolate); +/** + * This class exports constants and functionality from within v8 that + * is necessary to implement inline functions in the v8 api. Don't + * depend on functions and constants defined here. + */ +class Internals { +#ifdef V8_MAP_PACKING + V8_INLINE static constexpr Address UnpackMapWord(Address mapword) { + // TODO(wenyuzhao): Clear header metadata. + return mapword ^ kMapWordXorMask; + } +#endif + + public: + // These values match non-compiler-dependent values defined within + // the implementation of v8. + static const int kHeapObjectMapOffset = 0; + static const int kMapInstanceTypeOffset = 1 * kApiTaggedSize + kApiInt32Size; + static const int kStringResourceOffset = + 1 * kApiTaggedSize + 2 * kApiInt32Size; + + static const int kOddballKindOffset = 4 * kApiTaggedSize + kApiDoubleSize; + static const int kJSObjectHeaderSize = 3 * kApiTaggedSize; +#ifdef V8_COMPRESS_POINTERS + static const int kJSAPIObjectWithEmbedderSlotsHeaderSize = + kJSObjectHeaderSize + kApiInt32Size; +#else // !V8_COMPRESS_POINTERS + static const int kJSAPIObjectWithEmbedderSlotsHeaderSize = + kJSObjectHeaderSize + kApiTaggedSize; +#endif // !V8_COMPRESS_POINTERS + static const int kFixedArrayHeaderSize = 2 * kApiTaggedSize; + static const int kEmbedderDataArrayHeaderSize = 2 * kApiTaggedSize; + static const int kEmbedderDataSlotSize = kApiSystemPointerSize; +#ifdef V8_ENABLE_SANDBOX + static const int kEmbedderDataSlotExternalPointerOffset = kApiTaggedSize; +#else + static const int kEmbedderDataSlotExternalPointerOffset = 0; +#endif + static const int kNativeContextEmbedderDataOffset = 6 * kApiTaggedSize; + static const int kStringRepresentationAndEncodingMask = 0x0f; + static const int kStringEncodingMask = 0x8; + static const int kExternalTwoByteRepresentationTag = 0x02; + static const int kExternalOneByteRepresentationTag = 0x0a; + + static const uint32_t kNumIsolateDataSlots = 4; + static const int kStackGuardSize = 8 * kApiSystemPointerSize; + static const int kNumberOfBooleanFlags = 6; + static const int kErrorMessageParamSize = 1; + static const int kTablesAlignmentPaddingSize = 1; + static const int kRegExpStaticResultOffsetsVectorSize = kApiSystemPointerSize; + static const int kBuiltinTier0EntryTableSize = 7 * kApiSystemPointerSize; + static const int kBuiltinTier0TableSize = 7 * kApiSystemPointerSize; + static const int kLinearAllocationAreaSize = 3 * kApiSystemPointerSize; + static const int kThreadLocalTopSize = 30 * kApiSystemPointerSize; + static const int kHandleScopeDataSize = + 2 * kApiSystemPointerSize + 2 * kApiInt32Size; + + // ExternalPointerTable and TrustedPointerTable layout guarantees. + static const int kExternalPointerTableBasePointerOffset = 0; + static const int kExternalPointerTableSize = 2 * kApiSystemPointerSize; + static const int kTrustedPointerTableSize = 2 * kApiSystemPointerSize; + static const int kTrustedPointerTableBasePointerOffset = 0; + + // IsolateData layout guarantees. + static const int kIsolateCageBaseOffset = 0; + static const int kIsolateStackGuardOffset = + kIsolateCageBaseOffset + kApiSystemPointerSize; + static const int kVariousBooleanFlagsOffset = + kIsolateStackGuardOffset + kStackGuardSize; + static const int kErrorMessageParamOffset = + kVariousBooleanFlagsOffset + kNumberOfBooleanFlags; + static const int kBuiltinTier0EntryTableOffset = + kErrorMessageParamOffset + kErrorMessageParamSize + + kTablesAlignmentPaddingSize + kRegExpStaticResultOffsetsVectorSize; + static const int kBuiltinTier0TableOffset = + kBuiltinTier0EntryTableOffset + kBuiltinTier0EntryTableSize; + static const int kNewAllocationInfoOffset = + kBuiltinTier0TableOffset + kBuiltinTier0TableSize; + static const int kOldAllocationInfoOffset = + kNewAllocationInfoOffset + kLinearAllocationAreaSize; + + static const int kFastCCallAlignmentPaddingSize = + kApiSystemPointerSize == 8 ? 5 * kApiSystemPointerSize + : 1 * kApiSystemPointerSize; + static const int kIsolateFastCCallCallerPcOffset = + kOldAllocationInfoOffset + kLinearAllocationAreaSize + + kFastCCallAlignmentPaddingSize; + static const int kIsolateFastCCallCallerFpOffset = + kIsolateFastCCallCallerPcOffset + kApiSystemPointerSize; + static const int kIsolateFastApiCallTargetOffset = + kIsolateFastCCallCallerFpOffset + kApiSystemPointerSize; + static const int kIsolateLongTaskStatsCounterOffset = + kIsolateFastApiCallTargetOffset + kApiSystemPointerSize; + static const int kIsolateThreadLocalTopOffset = + kIsolateLongTaskStatsCounterOffset + kApiSizetSize; + static const int kIsolateHandleScopeDataOffset = + kIsolateThreadLocalTopOffset + kThreadLocalTopSize; + static const int kIsolateEmbedderDataOffset = + kIsolateHandleScopeDataOffset + kHandleScopeDataSize; +#ifdef V8_COMPRESS_POINTERS + static const int kIsolateExternalPointerTableOffset = + kIsolateEmbedderDataOffset + kNumIsolateDataSlots * kApiSystemPointerSize; + static const int kIsolateSharedExternalPointerTableAddressOffset = + kIsolateExternalPointerTableOffset + kExternalPointerTableSize; + static const int kIsolateCppHeapPointerTableOffset = + kIsolateSharedExternalPointerTableAddressOffset + kApiSystemPointerSize; +#ifdef V8_ENABLE_SANDBOX + static const int kIsolateTrustedCageBaseOffset = + kIsolateCppHeapPointerTableOffset + kExternalPointerTableSize; + static const int kIsolateTrustedPointerTableOffset = + kIsolateTrustedCageBaseOffset + kApiSystemPointerSize; + static const int kIsolateSharedTrustedPointerTableAddressOffset = + kIsolateTrustedPointerTableOffset + kTrustedPointerTableSize; + static const int kIsolateTrustedPointerPublishingScopeOffset = + kIsolateSharedTrustedPointerTableAddressOffset + kApiSystemPointerSize; + static const int kIsolateCodePointerTableBaseAddressOffset = + kIsolateTrustedPointerPublishingScopeOffset + kApiSystemPointerSize; + static const int kIsolateApiCallbackThunkArgumentOffset = + kIsolateCodePointerTableBaseAddressOffset + kApiSystemPointerSize; +#else + static const int kIsolateApiCallbackThunkArgumentOffset = + kIsolateCppHeapPointerTableOffset + kExternalPointerTableSize; +#endif // V8_ENABLE_SANDBOX +#else + static const int kIsolateApiCallbackThunkArgumentOffset = + kIsolateEmbedderDataOffset + kNumIsolateDataSlots * kApiSystemPointerSize; +#endif // V8_COMPRESS_POINTERS + static const int kIsolateRegexpExecVectorArgumentOffset = + kIsolateApiCallbackThunkArgumentOffset + kApiSystemPointerSize; + static const int kContinuationPreservedEmbedderDataOffset = + kIsolateRegexpExecVectorArgumentOffset + kApiSystemPointerSize; + static const int kIsolateRootsOffset = + kContinuationPreservedEmbedderDataOffset + kApiSystemPointerSize; + + // Assert scopes + static const int kDisallowGarbageCollectionAlign = alignof(uint32_t); + static const int kDisallowGarbageCollectionSize = sizeof(uint32_t); + +#if V8_STATIC_ROOTS_BOOL + +// These constants are copied from static-roots.h and guarded by static asserts. +#define EXPORTED_STATIC_ROOTS_PTR_LIST(V) \ + V(UndefinedValue, 0x11) \ + V(NullValue, 0x2d) \ + V(TrueValue, 0x71) \ + V(FalseValue, 0x55) \ + V(EmptyString, 0x49) \ + V(TheHoleValue, 0x761) + + using Tagged_t = uint32_t; + struct StaticReadOnlyRoot { +#define DEF_ROOT(name, value) static constexpr Tagged_t k##name = value; + EXPORTED_STATIC_ROOTS_PTR_LIST(DEF_ROOT) +#undef DEF_ROOT + + // Use 0 for kStringMapLowerBound since string maps are the first maps. + static constexpr Tagged_t kStringMapLowerBound = 0; + static constexpr Tagged_t kStringMapUpperBound = 0x425; + +#define PLUSONE(...) +1 + static constexpr size_t kNumberOfExportedStaticRoots = + 2 + EXPORTED_STATIC_ROOTS_PTR_LIST(PLUSONE); +#undef PLUSONE + }; + +#endif // V8_STATIC_ROOTS_BOOL + + static const int kUndefinedValueRootIndex = 4; + static const int kTheHoleValueRootIndex = 5; + static const int kNullValueRootIndex = 6; + static const int kTrueValueRootIndex = 7; + static const int kFalseValueRootIndex = 8; + static const int kEmptyStringRootIndex = 9; + + static const int kNodeClassIdOffset = 1 * kApiSystemPointerSize; + static const int kNodeFlagsOffset = 1 * kApiSystemPointerSize + 3; + static const int kNodeStateMask = 0x3; + static const int kNodeStateIsWeakValue = 2; + + static const int kFirstNonstringType = 0x80; + static const int kOddballType = 0x83; + static const int kForeignType = 0xcc; + static const int kJSSpecialApiObjectType = 0x410; + static const int kJSObjectType = 0x421; + static const int kFirstJSApiObjectType = 0x422; + static const int kLastJSApiObjectType = 0x80A; + // Defines a range [kFirstEmbedderJSApiObjectType, kJSApiObjectTypesCount] + // of JSApiObject instance type values that an embedder can use. + static const int kFirstEmbedderJSApiObjectType = 0; + static const int kLastEmbedderJSApiObjectType = + kLastJSApiObjectType - kFirstJSApiObjectType; + + static const int kUndefinedOddballKind = 4; + static const int kNullOddballKind = 3; + + // Constants used by PropertyCallbackInfo to check if we should throw when an + // error occurs. + static const int kDontThrow = 0; + static const int kThrowOnError = 1; + static const int kInferShouldThrowMode = 2; + + // Soft limit for AdjustAmountofExternalAllocatedMemory. Trigger an + // incremental GC once the external memory reaches this limit. + static constexpr size_t kExternalAllocationSoftLimit = 64 * 1024 * 1024; + +#ifdef V8_MAP_PACKING + static const uintptr_t kMapWordMetadataMask = 0xffffULL << 48; + // The lowest two bits of mapwords are always `0b10` + static const uintptr_t kMapWordSignature = 0b10; + // XORing a (non-compressed) map with this mask ensures that the two + // low-order bits are 0b10. The 0 at the end makes this look like a Smi, + // although real Smis have all lower 32 bits unset. We only rely on these + // values passing as Smis in very few places. + static const int kMapWordXorMask = 0b11; +#endif + + V8_EXPORT static void CheckInitializedImpl(v8::Isolate* isolate); + V8_INLINE static void CheckInitialized(v8::Isolate* isolate) { +#ifdef V8_ENABLE_CHECKS + CheckInitializedImpl(isolate); +#endif + } + + V8_INLINE static constexpr bool HasHeapObjectTag(Address value) { + return (value & kHeapObjectTagMask) == static_cast
(kHeapObjectTag); + } + + V8_INLINE static constexpr int SmiValue(Address value) { + return PlatformSmiTagging::SmiToInt(value); + } + + V8_INLINE static constexpr Address AddressToSmi(Address value) { + return (value << (kSmiTagSize + PlatformSmiTagging::kSmiShiftSize)) | + kSmiTag; + } + + V8_INLINE static constexpr Address IntToSmi(int value) { + return AddressToSmi(static_cast
(value)); + } + + template >* = nullptr> + V8_INLINE static constexpr Address IntegralToSmi(T value) { + return AddressToSmi(static_cast
(value)); + } + + template >* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + return PlatformSmiTagging::IsValidSmi(value); + } + + template >* = nullptr> + static constexpr std::optional
TryIntegralToSmi(T value) { + if (V8_LIKELY(PlatformSmiTagging::IsValidSmi(value))) { + return {AddressToSmi(static_cast
(value))}; + } + return {}; + } + +#if V8_STATIC_ROOTS_BOOL + V8_INLINE static bool is_identical(Address obj, Tagged_t constant) { + return static_cast(obj) == constant; + } + + V8_INLINE static bool CheckInstanceMapRange(Address obj, Tagged_t first_map, + Tagged_t last_map) { + auto map = ReadRawField(obj, kHeapObjectMapOffset); +#ifdef V8_MAP_PACKING + map = UnpackMapWord(map); +#endif + return map >= first_map && map <= last_map; + } +#endif + + V8_INLINE static int GetInstanceType(Address obj) { + Address map = ReadTaggedPointerField(obj, kHeapObjectMapOffset); +#ifdef V8_MAP_PACKING + map = UnpackMapWord(map); +#endif + return ReadRawField(map, kMapInstanceTypeOffset); + } + + V8_INLINE static Address LoadMap(Address obj) { + if (!HasHeapObjectTag(obj)) return kNullAddress; + Address map = ReadTaggedPointerField(obj, kHeapObjectMapOffset); +#ifdef V8_MAP_PACKING + map = UnpackMapWord(map); +#endif + return map; + } + + V8_INLINE static int GetOddballKind(Address obj) { + return SmiValue(ReadTaggedSignedField(obj, kOddballKindOffset)); + } + + V8_INLINE static bool IsExternalTwoByteString(int instance_type) { + int representation = (instance_type & kStringRepresentationAndEncodingMask); + return representation == kExternalTwoByteRepresentationTag; + } + + V8_INLINE static constexpr bool CanHaveInternalField(int instance_type) { + static_assert(kJSObjectType + 1 == kFirstJSApiObjectType); + static_assert(kJSObjectType < kLastJSApiObjectType); + static_assert(kFirstJSApiObjectType < kLastJSApiObjectType); + // Check for IsJSObject() || IsJSSpecialApiObject() || IsJSApiObject() + return instance_type == kJSSpecialApiObjectType || + // inlined version of base::IsInRange + (static_cast(static_cast(instance_type) - + static_cast(kJSObjectType)) <= + static_cast(kLastJSApiObjectType - kJSObjectType)); + } + + V8_INLINE static uint8_t GetNodeFlag(Address* obj, int shift) { + uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; + return *addr & static_cast(1U << shift); + } + + V8_INLINE static void UpdateNodeFlag(Address* obj, bool value, int shift) { + uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; + uint8_t mask = static_cast(1U << shift); + *addr = static_cast((*addr & ~mask) | (value << shift)); + } + + V8_INLINE static uint8_t GetNodeState(Address* obj) { + uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; + return *addr & kNodeStateMask; + } + + V8_INLINE static void UpdateNodeState(Address* obj, uint8_t value) { + uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; + *addr = static_cast((*addr & ~kNodeStateMask) | value); + } + + V8_INLINE static void SetEmbedderData(v8::Isolate* isolate, uint32_t slot, + void* data) { + Address addr = reinterpret_cast
(isolate) + + kIsolateEmbedderDataOffset + slot * kApiSystemPointerSize; + *reinterpret_cast(addr) = data; + } + + V8_INLINE static void* GetEmbedderData(const v8::Isolate* isolate, + uint32_t slot) { + Address addr = reinterpret_cast
(isolate) + + kIsolateEmbedderDataOffset + slot * kApiSystemPointerSize; + return *reinterpret_cast(addr); + } + + V8_INLINE static void IncrementLongTasksStatsCounter(v8::Isolate* isolate) { + Address addr = + reinterpret_cast
(isolate) + kIsolateLongTaskStatsCounterOffset; + ++(*reinterpret_cast(addr)); + } + + V8_INLINE static Address* GetRootSlot(v8::Isolate* isolate, int index) { + Address addr = reinterpret_cast
(isolate) + kIsolateRootsOffset + + index * kApiSystemPointerSize; + return reinterpret_cast(addr); + } + + V8_INLINE static Address GetRoot(v8::Isolate* isolate, int index) { +#if V8_STATIC_ROOTS_BOOL + Address base = *reinterpret_cast( + reinterpret_cast(isolate) + kIsolateCageBaseOffset); + switch (index) { +#define DECOMPRESS_ROOT(name, ...) \ + case k##name##RootIndex: \ + return base + StaticReadOnlyRoot::k##name; + EXPORTED_STATIC_ROOTS_PTR_LIST(DECOMPRESS_ROOT) +#undef DECOMPRESS_ROOT +#undef EXPORTED_STATIC_ROOTS_PTR_LIST + default: + break; + } +#endif // V8_STATIC_ROOTS_BOOL + return *GetRootSlot(isolate, index); + } + +#ifdef V8_ENABLE_SANDBOX + V8_INLINE static Address* GetExternalPointerTableBase(v8::Isolate* isolate) { + Address addr = reinterpret_cast
(isolate) + + kIsolateExternalPointerTableOffset + + kExternalPointerTableBasePointerOffset; + return *reinterpret_cast(addr); + } + + V8_INLINE static Address* GetSharedExternalPointerTableBase( + v8::Isolate* isolate) { + Address addr = reinterpret_cast
(isolate) + + kIsolateSharedExternalPointerTableAddressOffset; + addr = *reinterpret_cast(addr); + addr += kExternalPointerTableBasePointerOffset; + return *reinterpret_cast(addr); + } +#endif + + template + V8_INLINE static T ReadRawField(Address heap_object_ptr, int offset) { + Address addr = heap_object_ptr + offset - kHeapObjectTag; +#ifdef V8_COMPRESS_POINTERS + if (sizeof(T) > kApiTaggedSize) { + // TODO(ishell, v8:8875): When pointer compression is enabled 8-byte size + // fields (external pointers, doubles and BigInt data) are only + // kTaggedSize aligned so we have to use unaligned pointer friendly way of + // accessing them in order to avoid undefined behavior in C++ code. + T r; + memcpy(&r, reinterpret_cast(addr), sizeof(T)); + return r; + } +#endif + return *reinterpret_cast(addr); + } + + V8_INLINE static Address ReadTaggedPointerField(Address heap_object_ptr, + int offset) { +#ifdef V8_COMPRESS_POINTERS + uint32_t value = ReadRawField(heap_object_ptr, offset); + Address base = GetPtrComprCageBaseFromOnHeapAddress(heap_object_ptr); + return base + static_cast
(static_cast(value)); +#else + return ReadRawField
(heap_object_ptr, offset); +#endif + } + + V8_INLINE static Address ReadTaggedSignedField(Address heap_object_ptr, + int offset) { +#ifdef V8_COMPRESS_POINTERS + uint32_t value = ReadRawField(heap_object_ptr, offset); + return static_cast
(static_cast(value)); +#else + return ReadRawField
(heap_object_ptr, offset); +#endif + } + + V8_INLINE static v8::Isolate* GetIsolateForSandbox(Address obj) { +#ifdef V8_ENABLE_SANDBOX + return reinterpret_cast( + internal::IsolateFromNeverReadOnlySpaceObject(obj)); +#else + // Not used in non-sandbox mode. + return nullptr; +#endif + } + + template + V8_INLINE static Address ReadExternalPointerField(v8::Isolate* isolate, + Address heap_object_ptr, + int offset) { +#ifdef V8_ENABLE_SANDBOX + static_assert(!tag_range.IsEmpty()); + // See src/sandbox/external-pointer-table.h. Logic duplicated here so + // it can be inlined and doesn't require an additional call. + Address* table = IsSharedExternalPointerType(tag_range) + ? GetSharedExternalPointerTableBase(isolate) + : GetExternalPointerTableBase(isolate); + internal::ExternalPointerHandle handle = + ReadRawField(heap_object_ptr, offset); + uint32_t index = handle >> kExternalPointerIndexShift; + std::atomic
* ptr = + reinterpret_cast*>(&table[index]); + Address entry = std::atomic_load_explicit(ptr, std::memory_order_relaxed); + ExternalPointerTag actual_tag = static_cast( + (entry & kExternalPointerTagMask) >> kExternalPointerTagShift); + if (V8_LIKELY(tag_range.Contains(actual_tag))) { + return entry & kExternalPointerPayloadMask; + } else { + return 0; + } + return entry; +#else + return ReadRawField
(heap_object_ptr, offset); +#endif // V8_ENABLE_SANDBOX + } + +#ifdef V8_COMPRESS_POINTERS + V8_INLINE static Address GetPtrComprCageBaseFromOnHeapAddress(Address addr) { + return addr & -static_cast(kPtrComprCageBaseAlignment); + } + + V8_INLINE static uint32_t CompressTagged(Address value) { + return static_cast(value); + } + + V8_INLINE static Address DecompressTaggedField(Address heap_object_ptr, + uint32_t value) { + Address base = GetPtrComprCageBaseFromOnHeapAddress(heap_object_ptr); + return base + static_cast
(static_cast(value)); + } + +#endif // V8_COMPRESS_POINTERS +}; + +// Only perform cast check for types derived from v8::Data since +// other types do not implement the Cast method. +template +struct CastCheck { + template + static void Perform(T* data); +}; + +template <> +template +void CastCheck::Perform(T* data) { + T::Cast(data); +} + +template <> +template +void CastCheck::Perform(T* data) {} + +template +V8_INLINE void PerformCastCheck(T* data) { + CastCheck && + !std::is_same_v>>::Perform(data); +} + +// A base class for backing stores, which is needed due to vagaries of +// how static casts work with std::shared_ptr. +class BackingStoreBase {}; + +// The maximum value in enum GarbageCollectionReason, defined in heap.h. +// This is needed for histograms sampling garbage collection reasons. +constexpr int kGarbageCollectionReasonMaxValue = 30; + +// Base class for the address block allocator compatible with standard +// containers, which registers its allocated range as strong roots. +class V8_EXPORT StrongRootAllocatorBase { + public: + Heap* heap() const { return heap_; } + + friend bool operator==(const StrongRootAllocatorBase& a, + const StrongRootAllocatorBase& b) { + // TODO(pkasting): Replace this body with `= default` after dropping support + // for old gcc versions. + return a.heap_ == b.heap_; + } + + protected: + explicit StrongRootAllocatorBase(Heap* heap) : heap_(heap) {} + explicit StrongRootAllocatorBase(LocalHeap* heap); + explicit StrongRootAllocatorBase(Isolate* isolate); + explicit StrongRootAllocatorBase(v8::Isolate* isolate); + explicit StrongRootAllocatorBase(LocalIsolate* isolate); + + // Allocate/deallocate a range of n elements of type internal::Address. + Address* allocate_impl(size_t n); + void deallocate_impl(Address* p, size_t n) noexcept; + + private: + Heap* heap_; +}; + +// The general version of this template behaves just as std::allocator, with +// the exception that the constructor takes the isolate as parameter. Only +// specialized versions, e.g., internal::StrongRootAllocator +// and internal::StrongRootAllocator> register the allocated range +// as strong roots. +template +class StrongRootAllocator : private std::allocator { + public: + using value_type = T; + + template + explicit StrongRootAllocator(HeapOrIsolateT*) {} + template + StrongRootAllocator(const StrongRootAllocator& other) noexcept {} + + using std::allocator::allocate; + using std::allocator::deallocate; +}; + +// TODO(pkasting): Replace with `requires` clauses after dropping support for +// old gcc versions. +template +inline constexpr bool kHaveIteratorConcept = false; +template +inline constexpr bool kHaveIteratorConcept< + Iterator, std::void_t> = true; + +template +inline constexpr bool kHaveIteratorCategory = false; +template +inline constexpr bool kHaveIteratorCategory< + Iterator, std::void_t> = true; + +// Helper struct that contains an `iterator_concept` type alias only when either +// `Iterator` or `std::iterator_traits` do. +// Default: no alias. +template +struct MaybeDefineIteratorConcept {}; +// Use `Iterator::iterator_concept` if available. +template +struct MaybeDefineIteratorConcept< + Iterator, std::enable_if_t>> { + using iterator_concept = typename Iterator::iterator_concept; +}; +// Otherwise fall back to `std::iterator_traits` if possible. +template +struct MaybeDefineIteratorConcept< + Iterator, std::enable_if_t && + !kHaveIteratorConcept>> { + // There seems to be no feature-test macro covering this, so use the + // presence of `` as a crude proxy, since it was added to the + // standard as part of the Ranges papers. + // TODO(pkasting): Add this unconditionally after dropping support for old + // libstdc++ versions. +#if __has_include() + using iterator_concept = + typename std::iterator_traits::iterator_concept; +#endif +}; + +// A class of iterators that wrap some different iterator type. +// If specified, ElementType is the type of element accessed by the wrapper +// iterator; in this case, the actual reference and pointer types of Iterator +// must be convertible to ElementType& and ElementType*, respectively. +template +class WrappedIterator : public MaybeDefineIteratorConcept { + public: + static_assert( + std::is_void_v || + (std::is_convertible_v::pointer, + std::add_pointer_t> && + std::is_convertible_v::reference, + std::add_lvalue_reference_t>)); + + using difference_type = + typename std::iterator_traits::difference_type; + using value_type = + std::conditional_t, + typename std::iterator_traits::value_type, + ElementType>; + using pointer = + std::conditional_t, + typename std::iterator_traits::pointer, + std::add_pointer_t>; + using reference = + std::conditional_t, + typename std::iterator_traits::reference, + std::add_lvalue_reference_t>; + using iterator_category = + typename std::iterator_traits::iterator_category; + + constexpr WrappedIterator() noexcept = default; + constexpr explicit WrappedIterator(Iterator it) noexcept : it_(it) {} + + // TODO(pkasting): Switch to `requires` and concepts after dropping support + // for old gcc and libstdc++ versions. + template >> + constexpr WrappedIterator( + const WrappedIterator& other) noexcept + : it_(other.base()) {} + + [[nodiscard]] constexpr reference operator*() const noexcept { return *it_; } + [[nodiscard]] constexpr pointer operator->() const noexcept { + if constexpr (std::is_pointer_v) { + return it_; + } else { + return it_.operator->(); + } + } + + template + [[nodiscard]] constexpr bool operator==( + const WrappedIterator& other) + const noexcept { + return it_ == other.base(); + } +#if V8_HAVE_SPACESHIP_OPERATOR + template + [[nodiscard]] constexpr auto operator<=>( + const WrappedIterator& other) + const noexcept { + if constexpr (std::three_way_comparable_with) { + return it_ <=> other.base(); + } else if constexpr (std::totally_ordered_with) { + if (it_ < other.base()) { + return std::strong_ordering::less; + } + return (it_ > other.base()) ? std::strong_ordering::greater + : std::strong_ordering::equal; + } else { + if (it_ < other.base()) { + return std::partial_ordering::less; + } + if (other.base() < it_) { + return std::partial_ordering::greater; + } + return (it_ == other.base()) ? std::partial_ordering::equivalent + : std::partial_ordering::unordered; + } + } +#else + // Assume that if spaceship isn't present, operator rewriting might not be + // either. + template + [[nodiscard]] constexpr bool operator!=( + const WrappedIterator& other) + const noexcept { + return it_ != other.base(); + } + + template + [[nodiscard]] constexpr bool operator<( + const WrappedIterator& other) + const noexcept { + return it_ < other.base(); + } + template + [[nodiscard]] constexpr bool operator<=( + const WrappedIterator& other) + const noexcept { + return it_ <= other.base(); + } + template + [[nodiscard]] constexpr bool operator>( + const WrappedIterator& other) + const noexcept { + return it_ > other.base(); + } + template + [[nodiscard]] constexpr bool operator>=( + const WrappedIterator& other) + const noexcept { + return it_ >= other.base(); + } +#endif + + constexpr WrappedIterator& operator++() noexcept { + ++it_; + return *this; + } + constexpr WrappedIterator operator++(int) noexcept { + WrappedIterator result(*this); + ++(*this); + return result; + } + + constexpr WrappedIterator& operator--() noexcept { + --it_; + return *this; + } + constexpr WrappedIterator operator--(int) noexcept { + WrappedIterator result(*this); + --(*this); + return result; + } + [[nodiscard]] constexpr WrappedIterator operator+( + difference_type n) const noexcept { + WrappedIterator result(*this); + result += n; + return result; + } + [[nodiscard]] friend constexpr WrappedIterator operator+( + difference_type n, const WrappedIterator& x) noexcept { + return x + n; + } + constexpr WrappedIterator& operator+=(difference_type n) noexcept { + it_ += n; + return *this; + } + [[nodiscard]] constexpr WrappedIterator operator-( + difference_type n) const noexcept { + return *this + -n; + } + constexpr WrappedIterator& operator-=(difference_type n) noexcept { + return *this += -n; + } + template + [[nodiscard]] constexpr auto operator-( + const WrappedIterator& other) + const noexcept { + return it_ - other.base(); + } + [[nodiscard]] constexpr reference operator[]( + difference_type n) const noexcept { + return it_[n]; + } + + [[nodiscard]] constexpr const Iterator& base() const noexcept { return it_; } + + private: + Iterator it_; +}; + +// Helper functions about values contained in handles. +// A value is either an indirect pointer or a direct pointer, depending on +// whether direct local support is enabled. +class ValueHelper final { + public: + // ValueHelper::InternalRepresentationType is an abstract type that + // corresponds to the internal representation of v8::Local and essentially + // to what T* really is (these two are always in sync). This type is used in + // methods like GetDataFromSnapshotOnce that need access to a handle's + // internal representation. In particular, if `x` is a `v8::Local`, then + // `v8::Local::FromRepr(x.repr())` gives exactly the same handle as `x`. +#ifdef V8_ENABLE_DIRECT_HANDLE + static constexpr Address kTaggedNullAddress = 1; + + using InternalRepresentationType = internal::Address; + static constexpr InternalRepresentationType kEmpty = kTaggedNullAddress; +#else + using InternalRepresentationType = internal::Address*; + static constexpr InternalRepresentationType kEmpty = nullptr; +#endif // V8_ENABLE_DIRECT_HANDLE + + template + V8_INLINE static bool IsEmpty(T* value) { + return ValueAsRepr(value) == kEmpty; + } + + // Returns a handle's "value" for all kinds of abstract handles. For Local, + // it is equivalent to `*handle`. The variadic parameters support handle + // types with extra type parameters, like `Persistent`. + template