lib/VM/JSLib/HermesInternal.cpp (750 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "JSLibInternal.h" #include "hermes/BCGen/HBC/BytecodeFileFormat.h" #include "hermes/Support/Base64vlq.h" #include "hermes/Support/OSCompat.h" #include "hermes/VM/Callable.h" #include "hermes/VM/JSArray.h" #include "hermes/VM/JSArrayBuffer.h" #include "hermes/VM/JSLib.h" #include "hermes/VM/JSLib/RuntimeCommonStorage.h" #include "hermes/VM/JSTypedArray.h" #include "hermes/VM/JSWeakMapImpl.h" #include "hermes/VM/Operations.h" #include "hermes/VM/StackFrame-inline.h" #include "hermes/VM/StringView.h" #include <cstring> #include <random> namespace hermes { namespace vm { /// \return a SymbolID for a given C string \p s. static inline CallResult<Handle<SymbolID>> symbolForCStr( Runtime &rt, const char *s) { return rt.getIdentifierTable().getSymbolHandle(rt, ASCIIRef{s, strlen(s)}); } // ES7 24.1.1.3 CallResult<HermesValue> hermesInternalDetachArrayBuffer(void *, Runtime &runtime, NativeArgs args) { auto buffer = args.dyncastArg<JSArrayBuffer>(0); if (!buffer) { return runtime.raiseTypeError( "Cannot use detachArrayBuffer on something which " "is not an ArrayBuffer foo"); } buffer->detach(&runtime.getHeap()); // "void" return return HermesValue::encodeUndefinedValue(); } CallResult<HermesValue> hermesInternalGetEpilogues(void *, Runtime &runtime, NativeArgs args) { // Create outer array with one element per module. auto eps = runtime.getEpilogues(); auto outerLen = eps.size(); auto outerResult = JSArray::create(runtime, outerLen, outerLen); if (outerResult == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto outer = *outerResult; if (outer->setStorageEndIndex(outer, runtime, outerLen) == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } // Set each element to a Uint8Array holding the epilogue for that module. for (unsigned i = 0; i < outerLen; ++i) { auto innerLen = eps[i].size(); if (innerLen != 0) { auto result = Uint8Array::allocate(runtime, innerLen); if (result == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto ta = result.getValue(); std::memcpy(ta->begin(runtime), eps[i].begin(), innerLen); JSArray::unsafeSetExistingElementAt( *outer, runtime, i, ta.getHermesValue()); } } return HermesValue::encodeObjectValue(*outer); } /// Used for testing, determines how many live values /// are in the given WeakMap or WeakSet. CallResult<HermesValue> hermesInternalGetWeakSize(void *, Runtime &runtime, NativeArgs args) { if (auto M = args.dyncastArg<JSWeakMap>(0)) { return HermesValue::encodeNumberValue(JSWeakMap::debugFreeSlotsAndGetSize( static_cast<PointerBase &>(runtime), &runtime.getHeap(), *M)); } if (auto S = args.dyncastArg<JSWeakSet>(0)) { return HermesValue::encodeNumberValue(JSWeakSet::debugFreeSlotsAndGetSize( static_cast<PointerBase &>(runtime), &runtime.getHeap(), *S)); } return runtime.raiseTypeError( "getWeakSize can only be called on a WeakMap/WeakSet"); } /// \return an object containing various instrumented statistics. CallResult<HermesValue> hermesInternalGetInstrumentedStats(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope(runtime); auto resultHandle = runtime.makeHandle(JSObject::create(runtime)); // Printing the values would be unstable, so prevent that. if (runtime.shouldStabilizeInstructionCount()) return resultHandle.getHermesValue(); MutableHandle<> tmpHandle{runtime}; namespace P = Predefined; /// Adds a property to \c resultHandle. \p KEY provides its name as a \c /// Predefined enum value, and its value is rooted in \p VALUE. If property /// definition fails, the exceptional execution status will be propogated to the /// outer function. #define SET_PROP(KEY, VALUE) \ do { \ GCScopeMarkerRAII marker{gcScope}; \ double val = VALUE; \ if (statsTable) { \ auto array = runtime.getPredefinedString(KEY)->getStringRef<char>(); \ std::string key(array.data(), array.size()); \ if (statsTable->count(key.c_str())) { \ val = (*statsTable)[StringRef(key.c_str(), key.size())].num(); \ } \ } \ tmpHandle = HermesValue::encodeDoubleValue(val); \ auto status = JSObject::defineNewOwnProperty( \ resultHandle, \ runtime, \ Predefined::getSymbolID(KEY), \ PropertyFlags::defaultNewNamedPropertyFlags(), \ tmpHandle); \ if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) { \ return ExecutionStatus::EXCEPTION; \ } \ if (newStatsTable) { \ auto array = runtime.getPredefinedString(KEY)->getStringRef<char>(); \ std::string key(array.data(), array.size()); \ newStatsTable->try_emplace(key, val); \ } \ } while (false) auto &stats = runtime.getRuntimeStats(); MockedEnvironment::StatsTable *statsTable = nullptr; auto *const storage = runtime.getCommonStorage(); if (storage->env) { // For now, we'll allow replay to exhaust the recorded getInstrumentedStats // calls, and allow further calls to take values from the replay execution. // This allows backwards compatibility with existing traces that do not // record getInstrumentedStats. In the future, we might wish to disallow // this, and throw an exception. if (!storage->env->callsToHermesInternalGetInstrumentedStats.empty()) { statsTable = &storage->env->callsToHermesInternalGetInstrumentedStats.front(); } } std::unique_ptr<MockedEnvironment::StatsTable> newStatsTable; if (storage->shouldTrace) { newStatsTable.reset(new MockedEnvironment::StatsTable()); } // Ensure that the timers measuring the current execution are up to date. stats.flushPendingTimers(); SET_PROP(P::js_hostFunctionTime, stats.hostFunction.wallDuration); SET_PROP(P::js_hostFunctionCPUTime, stats.hostFunction.cpuDuration); SET_PROP(P::js_hostFunctionCount, stats.hostFunction.count); SET_PROP(P::js_evaluateJSTime, stats.evaluateJS.wallDuration); SET_PROP(P::js_evaluateJSCPUTime, stats.evaluateJS.cpuDuration); SET_PROP(P::js_evaluateJSCount, stats.evaluateJS.count); SET_PROP(P::js_incomingFunctionTime, stats.incomingFunction.wallDuration); SET_PROP(P::js_incomingFunctionCPUTime, stats.incomingFunction.cpuDuration); SET_PROP(P::js_incomingFunctionCount, stats.incomingFunction.count); SET_PROP(P::js_VMExperiments, runtime.getVMExperimentFlags()); auto makeHermesTime = [](double host, double eval, double incoming) { return eval - host + incoming; }; SET_PROP( P::js_hermesTime, makeHermesTime( stats.hostFunction.wallDuration, stats.evaluateJS.wallDuration, stats.incomingFunction.wallDuration)); SET_PROP( P::js_hermesCPUTime, makeHermesTime( stats.hostFunction.cpuDuration, stats.evaluateJS.cpuDuration, stats.incomingFunction.cpuDuration)); if (stats.shouldSample) { SET_PROP( P::js_hermesThreadMinorFaults, makeHermesTime( stats.hostFunction.sampled.threadMinorFaults, stats.evaluateJS.sampled.threadMinorFaults, stats.incomingFunction.sampled.threadMinorFaults)); SET_PROP( P::js_hermesThreadMajorFaults, makeHermesTime( stats.hostFunction.sampled.threadMajorFaults, stats.evaluateJS.sampled.threadMajorFaults, stats.incomingFunction.sampled.threadMajorFaults)); } auto &heap = runtime.getHeap(); SET_PROP(P::js_numGCs, heap.getNumGCs()); SET_PROP(P::js_gcCPUTime, heap.getGCCPUTime()); SET_PROP(P::js_gcTime, heap.getGCTime()); #undef SET_PROP /// Adds a property to \c resultHandle. \p KEY provides its name as a C string, /// and its value is rooted in \p VALUE. If property definition fails, the /// exceptional execution status will be propogated to the outer function. #define SET_PROP_NEW(KEY, VALUE) \ do { \ GCScopeMarkerRAII marker{gcScope}; \ auto keySym = symbolForCStr(runtime, KEY); \ if (LLVM_UNLIKELY(keySym == ExecutionStatus::EXCEPTION)) { \ return ExecutionStatus::EXCEPTION; \ } \ double val = VALUE; \ if (statsTable && statsTable->count(KEY)) { \ val = (*statsTable)[KEY].num(); \ } \ tmpHandle = HermesValue::encodeDoubleValue(val); \ auto status = JSObject::defineNewOwnProperty( \ resultHandle, \ runtime, \ **keySym, \ PropertyFlags::defaultNewNamedPropertyFlags(), \ tmpHandle); \ if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) { \ return ExecutionStatus::EXCEPTION; \ } \ if (newStatsTable) { \ newStatsTable->try_emplace(KEY, val); \ } \ } while (false) { GCBase::HeapInfo info; heap.getHeapInfo(info); SET_PROP_NEW("js_totalAllocatedBytes", info.totalAllocatedBytes); SET_PROP_NEW("js_allocatedBytes", info.allocatedBytes); SET_PROP_NEW("js_heapSize", info.heapSize); SET_PROP_NEW("js_mallocSizeEstimate", info.mallocSizeEstimate); SET_PROP_NEW("js_vaSize", info.va); SET_PROP_NEW("js_markStackOverflows", info.numMarkStackOverflows); } if (stats.shouldSample) { SET_PROP_NEW( "js_hermesVolCtxSwitches", makeHermesTime( stats.hostFunction.sampled.volCtxSwitches, stats.evaluateJS.sampled.volCtxSwitches, stats.incomingFunction.sampled.volCtxSwitches)); SET_PROP_NEW( "js_hermesInvolCtxSwitches", makeHermesTime( stats.hostFunction.sampled.involCtxSwitches, stats.evaluateJS.sampled.involCtxSwitches, stats.incomingFunction.sampled.involCtxSwitches)); // Sampled because it doesn't vary much, not because it's expensive to get. SET_PROP_NEW("js_pageSize", oscompat::page_size()); } /// Adds a property to \c resultHandle. \p KEY and \p VALUE provide its name and /// value as a C string and ASCIIRef respectively. If property definition fails, /// the exceptional execution status will be propogated to the outer function. #define SET_PROP_STR(KEY, VALUE) \ do { \ auto keySym = symbolForCStr(runtime, KEY); \ if (LLVM_UNLIKELY(keySym == ExecutionStatus::EXCEPTION)) { \ return ExecutionStatus::EXCEPTION; \ } \ ASCIIRef val = VALUE; \ std::string valStdStr(val.data(), val.size()); \ if (statsTable && statsTable->count(KEY)) { \ valStdStr = (*statsTable)[std::string(KEY)].str(); \ } \ val = ASCIIRef(valStdStr.c_str(), valStdStr.size()); \ auto valStr = StringPrimitive::create(runtime, val); \ if (LLVM_UNLIKELY(valStr == ExecutionStatus::EXCEPTION)) { \ return ExecutionStatus::EXCEPTION; \ } \ tmpHandle = *valStr; \ auto status = JSObject::defineNewOwnProperty( \ resultHandle, \ runtime, \ **keySym, \ PropertyFlags::defaultNewNamedPropertyFlags(), \ tmpHandle); \ if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) { \ return ExecutionStatus::EXCEPTION; \ } \ if (newStatsTable) { \ newStatsTable->try_emplace(KEY, valStdStr); \ } \ } while (false) if (runtime.getRuntimeStats().shouldSample) { // Build a string showing the set of cores on which we may run. std::string mask; for (auto b : oscompat::sched_getaffinity()) mask += b ? '1' : '0'; SET_PROP_STR("js_threadAffinityMask", ASCIIRef(mask.data(), mask.size())); SET_PROP_NEW("js_threadCPU", oscompat::sched_getcpu()); size_t bytecodePagesResident = 0; size_t bytecodePagesResidentRuns = 0; for (auto &module : runtime.getRuntimeModules()) { auto buf = module.getBytecode()->getRawBuffer(); if (buf.size()) { llvh::SmallVector<int, 64> runs; int pages = oscompat::pages_in_ram(buf.data(), buf.size(), &runs); if (pages >= 0) { bytecodePagesResident += pages; bytecodePagesResidentRuns += runs.size(); } } } SET_PROP_NEW("js_bytecodePagesResident", bytecodePagesResident); SET_PROP_NEW("js_bytecodePagesResidentRuns", bytecodePagesResidentRuns); // Stats for the module with most accesses. uint32_t bytecodePagesAccessed = 0; uint32_t bytecodeSize = 0; JenkinsHash bytecodePagesTraceHash = 0; double bytecodeIOus = 0; // Sample a small number of (position in access order, page id) pairs // encoded as a base64vlq stream. static constexpr unsigned NUM_SAMPLES = 32; std::string sample; for (auto &module : runtime.getRuntimeModules()) { auto tracker = module.getBytecode()->getPageAccessTracker(); if (tracker) { auto ids = tracker->getPagesAccessed(); if (ids.size() <= bytecodePagesAccessed) continue; bytecodePagesAccessed = ids.size(); bytecodeSize = module.getBytecode()->getRawBuffer().size(); bytecodePagesTraceHash = 0; for (auto id : ids) { // char16_t is at least 16 bits unsigned, so the quality of this hash // might degrade if accessing bytecode above 2^16 * 4 kB = 256 MB. bytecodePagesTraceHash = updateJenkinsHash( bytecodePagesTraceHash, static_cast<char16_t>(id)); } bytecodeIOus = 0; for (auto us : tracker->getMicros()) { bytecodeIOus += us; } sample.clear(); llvh::raw_string_ostream str(sample); std::random_device rng; for (unsigned sampleIdx = 0; sampleIdx < NUM_SAMPLES; ++sampleIdx) { int32_t accessOrderPos = rng() % ids.size(); base64vlq::encode(str, accessOrderPos); base64vlq::encode(str, ids[accessOrderPos]); } } } // If we have are replaying a trace, override the value. if (statsTable) { bytecodePagesAccessed = (*statsTable)["js_bytecodePagesAccessed"].num(); } if (bytecodePagesAccessed) { SET_PROP_NEW("js_bytecodePagesAccessed", bytecodePagesAccessed); SET_PROP_NEW("js_bytecodeSize", bytecodeSize); SET_PROP_NEW("js_bytecodePagesTraceHash", bytecodePagesTraceHash); SET_PROP_NEW("js_bytecodeIOTime", bytecodeIOus / 1e6); SET_PROP_STR( "js_bytecodePagesTraceSample", ASCIIRef(sample.data(), sample.size())); } } if (storage->env && statsTable) { storage->env->callsToHermesInternalGetInstrumentedStats.pop_front(); } if (LLVM_UNLIKELY(storage->shouldTrace)) { storage->tracedEnv.callsToHermesInternalGetInstrumentedStats.push_back( *newStatsTable); } return resultHandle.getHermesValue(); #undef SET_PROP_NEW } /// \return a static string summarising the presence and resolution type of /// CommonJS modules across all RuntimeModules that have been loaded into \c /// runtime. static const char *getCJSModuleModeDescription(Runtime &runtime) { bool hasCJSModulesDynamic = false; bool hasCJSModulesStatic = false; for (const auto &runtimeModule : runtime.getRuntimeModules()) { if (runtimeModule.hasCJSModules()) { hasCJSModulesDynamic = true; } if (runtimeModule.hasCJSModulesStatic()) { hasCJSModulesStatic = true; } } if (hasCJSModulesDynamic && hasCJSModulesStatic) { return "Mixed dynamic/static"; } if (hasCJSModulesDynamic) { return "Dynamically resolved"; } if (hasCJSModulesStatic) { return "Statically resolved"; } return "None"; } /// \return an object mapping keys to runtime property values. CallResult<HermesValue> hermesInternalGetRuntimeProperties(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope(runtime); auto resultHandle = runtime.makeHandle(JSObject::create(runtime)); MutableHandle<> tmpHandle{runtime}; /// Add a property \p value keyed under \p key to resultHandle. /// Return an ExecutionStatus. auto addProperty = [&](Handle<> value, const char *key) { auto keySym = symbolForCStr(runtime, key); if (LLVM_UNLIKELY(keySym == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return JSObject::defineNewOwnProperty( resultHandle, runtime, **keySym, PropertyFlags::defaultNewNamedPropertyFlags(), value); }; #ifdef HERMES_FACEBOOK_BUILD tmpHandle = HermesValue::encodeBoolValue(std::strstr(__FILE__, "hermes-snapshot")); if (LLVM_UNLIKELY( addProperty(tmpHandle, "Snapshot VM") == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } #endif tmpHandle = HermesValue::encodeDoubleValue(::hermes::hbc::BYTECODE_VERSION); if (LLVM_UNLIKELY( addProperty(tmpHandle, "Bytecode Version") == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = HermesValue::encodeBoolValue(runtime.builtinsAreFrozen()); if (LLVM_UNLIKELY( addProperty(tmpHandle, "Builtins Frozen") == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = HermesValue::encodeNumberValue(runtime.getVMExperimentFlags()); if (LLVM_UNLIKELY( addProperty(tmpHandle, "VM Experiments") == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } const char buildMode[] = #ifdef HERMES_SLOW_DEBUG "SlowDebug" #elif !defined(NDEBUG) "Debug" #else "Release" #endif ; auto buildModeRes = StringPrimitive::create( runtime, ASCIIRef(buildMode, sizeof(buildMode) - 1)); if (LLVM_UNLIKELY(buildModeRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = *buildModeRes; if (LLVM_UNLIKELY( addProperty(tmpHandle, "Build") == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } std::string gcKind = runtime.getHeap().getKindAsStr(); auto gcKindRes = StringPrimitive::create( runtime, ASCIIRef(gcKind.c_str(), gcKind.length())); if (LLVM_UNLIKELY(gcKindRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = *gcKindRes; if (LLVM_UNLIKELY( addProperty(tmpHandle, "GC") == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } #ifdef HERMES_RELEASE_VERSION auto relVerRes = StringPrimitive::create(runtime, createASCIIRef(HERMES_RELEASE_VERSION)); if (LLVM_UNLIKELY(relVerRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = *relVerRes; if (LLVM_UNLIKELY( addProperty(tmpHandle, "OSS Release Version") == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } #endif const char *cjsModuleMode = getCJSModuleModeDescription(runtime); auto cjsModuleModeRes = StringPrimitive::create(runtime, createASCIIRef(cjsModuleMode)); if (LLVM_UNLIKELY(cjsModuleModeRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = *cjsModuleModeRes; if (LLVM_UNLIKELY( addProperty(tmpHandle, "CommonJS Modules") == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return resultHandle.getHermesValue(); } #ifdef HERMESVM_PLATFORM_LOGGING static void logGCStats(Runtime &runtime, const char *msg) { // The GC stats can exceed the android logcat length limit, of // 1024 bytes. Break it up. std::string stats; { llvh::raw_string_ostream os(stats); runtime.printHeapStats(os); } auto copyRegionFrom = [&stats](size_t from) -> size_t { size_t rBrace = stats.find("},", from); if (rBrace == std::string::npos) { std::string portion = stats.substr(from); hermesLog("HermesVM", "%s", portion.c_str()); return stats.size(); } // Add 2 for the length of the search string, to get to the end. const size_t to = rBrace + 2; std::string portion = stats.substr(from, to - from); hermesLog("HermesVM", "%s", portion.c_str()); return to; }; hermesLog("HermesVM", "%s:", msg); for (size_t ind = 0; ind < stats.size(); ind = copyRegionFrom(ind)) ; } #endif CallResult<HermesValue> hermesInternalTTIReached(void *, Runtime &runtime, NativeArgs args) { runtime.ttiReached(); #ifdef HERMESVM_LLVM_PROFILE_DUMP __llvm_profile_dump(); throw jsi::JSINativeException("TTI reached; profiling done"); #endif #ifdef HERMESVM_PLATFORM_LOGGING logGCStats(runtime, "TTI call"); #endif return HermesValue::encodeUndefinedValue(); } CallResult<HermesValue> hermesInternalTTRCReached(void *, Runtime &runtime, NativeArgs args) { // Currently does nothing, but could change in the future. return HermesValue::encodeUndefinedValue(); } CallResult<HermesValue> hermesInternalIsProxy(void *, Runtime &runtime, NativeArgs args) { Handle<JSObject> obj = args.dyncastArg<JSObject>(0); return HermesValue::encodeBoolValue(obj && obj->isProxyObject()); } CallResult<HermesValue> hermesInternalHasPromise(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeBoolValue(runtime.hasES6Promise()); } CallResult<HermesValue> hermesInternalUseEngineQueue(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeBoolValue(runtime.useJobQueue()); } /// \code /// HermesInternal.enqueueJob = function (func) {} /// \endcode CallResult<HermesValue> hermesInternalEnqueueJob(void *, Runtime &runtime, NativeArgs args) { auto callable = args.dyncastArg<Callable>(0); if (!callable) { return runtime.raiseTypeError( "Argument to HermesInternal.enqueueJob must be callable"); } runtime.enqueueJob(callable.get()); return HermesValue::encodeUndefinedValue(); } /// \code /// HermesInternal.drainJobs = function () {} /// \endcode /// Throw if the drainJobs throws. CallResult<HermesValue> hermesInternalDrainJobs(void *, Runtime &runtime, NativeArgs args) { auto drainRes = runtime.drainJobs(); if (drainRes == ExecutionStatus::EXCEPTION) { // No need to rethrow since it's already throw. return ExecutionStatus::EXCEPTION; } return HermesValue::encodeUndefinedValue(); } #ifdef HERMESVM_EXCEPTION_ON_OOM /// Gets the current call stack as a JS String value. Intended (only) /// to allow testing of Runtime::callStack() from JS code. CallResult<HermesValue> hermesInternalGetCallStack(void *, Runtime &runtime, NativeArgs args) { std::string stack = runtime.getCallStackNoAlloc(); return StringPrimitive::create(runtime, ASCIIRef(stack.data(), stack.size())); } #endif // HERMESVM_EXCEPTION_ON_OOM /// \return the code block associated with \p callableHandle if it is a /// (possibly bound) JS function, or nullptr otherwise. static const CodeBlock *getLeafCodeBlock( Handle<Callable> callableHandle, Runtime &runtime) { const Callable *callable = callableHandle.get(); while (auto *bound = dyn_vmcast<BoundFunction>(callable)) { callable = bound->getTarget(runtime); } if (auto *asFunction = dyn_vmcast<const JSFunction>(callable)) { return asFunction->getCodeBlock(); } return nullptr; } /// \return the file name associated with \p codeBlock, if any. /// This mirrors the way we print file names for code blocks in JSError. static CallResult<HermesValue> getCodeBlockFileName( Runtime &runtime, const CodeBlock *codeBlock, OptValue<hbc::DebugSourceLocation> location) { RuntimeModule *runtimeModule = codeBlock->getRuntimeModule(); if (location) { auto debugInfo = runtimeModule->getBytecode()->getDebugInfo(); return StringPrimitive::createEfficient( runtime, debugInfo->getFilenameByID(location->filenameId)); } else { llvh::StringRef sourceURL = runtimeModule->getSourceURL(); if (!sourceURL.empty()) { return StringPrimitive::createEfficient(runtime, sourceURL); } } return HermesValue::encodeUndefinedValue(); } /// \code /// HermesInternal.getFunctionLocation function (func) {} /// \endcode /// Returns an object describing the source location of func. /// The following properties may be present: /// * fileName (string) /// * lineNumber (number) - 1 based /// * columnNumber (number) - 1 based /// * segmentID (number) - 0 based /// * virtualOffset (number) - 0 based /// * isNative (boolean) /// TypeError if func is not a function. CallResult<HermesValue> hermesInternalGetFunctionLocation(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope(runtime); auto callable = args.dyncastArg<Callable>(0); if (!callable) { return runtime.raiseTypeError( "Argument to HermesInternal.getFunctionLocation must be callable"); } auto resultHandle = runtime.makeHandle(JSObject::create(runtime)); MutableHandle<> tmpHandle{runtime}; auto codeBlock = getLeafCodeBlock(callable, runtime); bool isNative = !codeBlock; auto res = JSObject::defineOwnProperty( resultHandle, runtime, Predefined::getSymbolID(Predefined::isNative), DefinePropertyFlags::getDefaultNewPropertyFlags(), runtime.getBoolValue(isNative)); assert(res != ExecutionStatus::EXCEPTION && "Failed to set isNative"); (void)res; if (codeBlock) { OptValue<hbc::DebugSourceLocation> location = codeBlock->getSourceLocation(); if (location) { tmpHandle = HermesValue::encodeNumberValue(location->line); res = JSObject::defineOwnProperty( resultHandle, runtime, Predefined::getSymbolID(Predefined::lineNumber), DefinePropertyFlags::getDefaultNewPropertyFlags(), tmpHandle); assert(res != ExecutionStatus::EXCEPTION && "Failed to set lineNumber"); (void)res; tmpHandle = HermesValue::encodeNumberValue(location->column); res = JSObject::defineOwnProperty( resultHandle, runtime, Predefined::getSymbolID(Predefined::columnNumber), DefinePropertyFlags::getDefaultNewPropertyFlags(), tmpHandle); assert(res != ExecutionStatus::EXCEPTION && "Failed to set columnNumber"); (void)res; } else { tmpHandle = HermesValue::encodeNumberValue( codeBlock->getRuntimeModule()->getBytecode()->getSegmentID()); res = JSObject::defineOwnProperty( resultHandle, runtime, Predefined::getSymbolID(Predefined::segmentID), DefinePropertyFlags::getDefaultNewPropertyFlags(), tmpHandle); assert(res != ExecutionStatus::EXCEPTION && "Failed to set segmentID"); (void)res; tmpHandle = HermesValue::encodeNumberValue(codeBlock->getVirtualOffset()); res = JSObject::defineOwnProperty( resultHandle, runtime, Predefined::getSymbolID(Predefined::virtualOffset), DefinePropertyFlags::getDefaultNewPropertyFlags(), tmpHandle); assert( res != ExecutionStatus::EXCEPTION && "Failed to set virtualOffset"); (void)res; } auto fileNameRes = getCodeBlockFileName(runtime, codeBlock, location); if (LLVM_UNLIKELY(fileNameRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } tmpHandle = *fileNameRes; res = JSObject::defineOwnProperty( resultHandle, runtime, Predefined::getSymbolID(Predefined::fileName), DefinePropertyFlags::getDefaultNewPropertyFlags(), tmpHandle); assert(res != ExecutionStatus::EXCEPTION && "Failed to set fileName"); (void)res; } JSObject::preventExtensions(*resultHandle); return resultHandle.getHermesValue(); } /// \code /// HermesInternal.setPromiseRejectionTrackingHook = function (func) {} /// \endcode /// Register the function which can be used to *enable* Promise rejection /// tracking when the user calls it. /// For example, when using the npm `promise` polyfill: /// \code /// HermesInternal.setPromiseRejectionTrackingHook( /// require('./rejection-tracking.js').enable /// ); /// \endcode CallResult<HermesValue> hermesInternalSetPromiseRejectionTrackingHook( void *, Runtime &runtime, NativeArgs args) { runtime.promiseRejectionTrackingHook_ = args.getArg(0); return HermesValue::encodeUndefinedValue(); } /// \code /// HermesInternal.enablePromiseRejectionTracker = function (opts) {} /// \endcode /// Enable promise rejection tracking with the given opts. CallResult<HermesValue> hermesInternalEnablePromiseRejectionTracker( void *, Runtime &runtime, NativeArgs args) { auto opts = args.getArgHandle(0); auto func = Handle<Callable>::dyn_vmcast( Handle<>(&runtime.promiseRejectionTrackingHook_)); if (!func) { return runtime.raiseTypeError( "Promise rejection tracking hook was not registered"); } return Callable::executeCall1( func, runtime, Runtime::getUndefinedValue(), opts.getHermesValue()) .toCallResultHermesValue(); } Handle<JSObject> createHermesInternalObject( Runtime &runtime, const JSLibFlags &flags) { namespace P = Predefined; Handle<JSObject> intern = runtime.makeHandle(JSObject::create(runtime)); GCScope gcScope{runtime}; DefinePropertyFlags constantDPF = DefinePropertyFlags::getDefaultNewPropertyFlags(); constantDPF.enumerable = 0; constantDPF.writable = 0; constantDPF.configurable = 0; auto defineInternMethod = [&](Predefined::Str symID, NativeFunctionPtr func, uint8_t count = 0) { (void)defineMethod( runtime, intern, Predefined::getSymbolID(symID), nullptr /* context */, func, count, constantDPF); }; auto defineInternMethodAndSymbol = [&](const char *name, NativeFunctionPtr func, uint8_t count = 0) { ASCIIRef ref = createASCIIRef(name); Handle<SymbolID> symHandle = runtime.ignoreAllocationFailure( runtime.getIdentifierTable().getSymbolHandle(runtime, ref)); (void)defineMethod( runtime, intern, *symHandle, nullptr /* context */, func, count, constantDPF); }; // suppress unused-variable warning (void)defineInternMethodAndSymbol; // Make a copy of the original String.prototype.concat implementation that we // can use internally. // TODO: we can't make HermesInternal.concat a static builtin method now // because this method should be called with a meaningful `this`, but // CallBuiltin instruction does not support it. auto propRes = JSObject::getNamed_RJS( runtime.makeHandle<JSObject>(runtime.stringPrototype), runtime, Predefined::getSymbolID(Predefined::concat)); assert( propRes != ExecutionStatus::EXCEPTION && !(*propRes)->isUndefined() && "Failed to get String.prototype.concat."); auto putRes = JSObject::defineOwnProperty( intern, runtime, Predefined::getSymbolID(Predefined::concat), constantDPF, runtime.makeHandle(std::move(*propRes))); assert( putRes != ExecutionStatus::EXCEPTION && *putRes && "Failed to set HermesInternal.concat."); (void)putRes; // HermesInternal functions that are known to be safe and are required to be // present by the VM internals even under a security-sensitive environment // where HermesInternal might be explicitly disabled. defineInternMethod(P::hasPromise, hermesInternalHasPromise); defineInternMethod(P::enqueueJob, hermesInternalEnqueueJob); defineInternMethod( P::setPromiseRejectionTrackingHook, hermesInternalSetPromiseRejectionTrackingHook); defineInternMethod( P::enablePromiseRejectionTracker, hermesInternalEnablePromiseRejectionTracker); defineInternMethod(P::useEngineQueue, hermesInternalUseEngineQueue); // All functions are known to be safe can be defined above this flag check. if (!flags.enableHermesInternal) { JSObject::preventExtensions(*intern); return intern; } // HermesInternal functions that are not necessarily required but are // generally considered harmless to be exposed by default. defineInternMethod(P::getEpilogues, hermesInternalGetEpilogues); defineInternMethod( P::getInstrumentedStats, hermesInternalGetInstrumentedStats); defineInternMethod( P::getRuntimeProperties, hermesInternalGetRuntimeProperties); defineInternMethod(P::ttiReached, hermesInternalTTIReached); defineInternMethod(P::ttrcReached, hermesInternalTTRCReached); defineInternMethod(P::getFunctionLocation, hermesInternalGetFunctionLocation); // HermesInternal function that are only meant to be used for testing purpose. // They can change language semantics and are security risks. if (flags.enableHermesInternalTestMethods) { defineInternMethod( P::detachArrayBuffer, hermesInternalDetachArrayBuffer, 1); defineInternMethod(P::getWeakSize, hermesInternalGetWeakSize); defineInternMethod( P::copyDataProperties, hermesBuiltinCopyDataProperties, 3); defineInternMethodAndSymbol("isProxy", hermesInternalIsProxy); defineInternMethod(P::drainJobs, hermesInternalDrainJobs); } #ifdef HERMESVM_EXCEPTION_ON_OOM defineInternMethodAndSymbol("getCallStack", hermesInternalGetCallStack, 0); #endif // HERMESVM_EXCEPTION_ON_OOM JSObject::preventExtensions(*intern); return intern; } } // namespace vm } // namespace hermes