| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "builtin/TestingFunctions.h" |
| |
| #include "mozilla/Move.h" |
| #include "mozilla/UniquePtr.h" |
| |
| #include "jsapi.h" |
| #include "jscntxt.h" |
| #include "jsfriendapi.h" |
| #include "jsgc.h" |
| #include "jsobj.h" |
| #include "jsprf.h" |
| #include "jswrapper.h" |
| |
| #include "asmjs/AsmJSLink.h" |
| #include "asmjs/AsmJSValidate.h" |
| #include "jit/InlinableNatives.h" |
| #include "jit/JitFrameIterator.h" |
| #include "js/Debug.h" |
| #include "js/HashTable.h" |
| #include "js/StructuredClone.h" |
| #include "js/UbiNode.h" |
| #include "js/UbiNodeBreadthFirst.h" |
| #include "js/Vector.h" |
| #include "vm/GlobalObject.h" |
| #include "vm/Interpreter.h" |
| #include "vm/ProxyObject.h" |
| #include "vm/SavedStacks.h" |
| #include "vm/Stack.h" |
| #include "vm/TraceLogging.h" |
| |
| #include "jscntxtinlines.h" |
| #include "jsobjinlines.h" |
| |
| #include "vm/NativeObject-inl.h" |
| |
| // Unified leak fix: |
| #include "vm/ScopeObject-inl.h" |
| |
| using namespace js; |
| |
| using mozilla::ArrayLength; |
| using mozilla::Move; |
| using mozilla::UniquePtr; |
| |
| // If fuzzingSafe is set, remove functionality that could cause problems with |
| // fuzzers. Set this via the environment variable MOZ_FUZZING_SAFE. |
| static bool fuzzingSafe = false; |
| |
| // If disableOOMFunctions is set, disable functionality that causes artificial |
| // OOM conditions. |
| static bool disableOOMFunctions = false; |
| |
| static bool |
| EnvVarIsDefined(const char* name) |
| { |
| const char* value = js_sb_getenv(name); |
| return value && *value; |
| } |
| |
| #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) |
| static bool |
| EnvVarAsInt(const char* name, int* valueOut) |
| { |
| if (!EnvVarIsDefined(name)) |
| return false; |
| |
| *valueOut = atoi(js_sb_getenv(name)); |
| return true; |
| } |
| #endif |
| |
| static bool |
| GetBuildConfiguration(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject info(cx, JS_NewPlainObject(cx)); |
| if (!info) |
| return false; |
| |
| if (!JS_SetProperty(cx, info, "rooting-analysis", FalseHandleValue)) |
| return false; |
| |
| if (!JS_SetProperty(cx, info, "exact-rooting", TrueHandleValue)) |
| return false; |
| |
| if (!JS_SetProperty(cx, info, "trace-jscalls-api", FalseHandleValue)) |
| return false; |
| |
| if (!JS_SetProperty(cx, info, "incremental-gc", TrueHandleValue)) |
| return false; |
| |
| if (!JS_SetProperty(cx, info, "generational-gc", TrueHandleValue)) |
| return false; |
| |
| RootedValue value(cx); |
| #ifdef DEBUG |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "debug", value)) |
| return false; |
| |
| #ifdef RELEASE_BUILD |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "release", value)) |
| return false; |
| |
| #ifdef JS_HAS_CTYPES |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "has-ctypes", value)) |
| return false; |
| |
| #ifdef JS_CPU_X86 |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "x86", value)) |
| return false; |
| |
| #ifdef JS_CPU_X64 |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "x64", value)) |
| return false; |
| |
| #ifdef JS_SIMULATOR_ARM |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "arm-simulator", value)) |
| return false; |
| |
| #ifdef JS_SIMULATOR_ARM64 |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "arm64-simulator", value)) |
| return false; |
| |
| #ifdef MOZ_ASAN |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "asan", value)) |
| return false; |
| |
| #ifdef MOZ_TSAN |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "tsan", value)) |
| return false; |
| |
| #ifdef JS_GC_ZEAL |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "has-gczeal", value)) |
| return false; |
| |
| #ifdef JS_MORE_DETERMINISTIC |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "more-deterministic", value)) |
| return false; |
| |
| #ifdef MOZ_PROFILING |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "profiling", value)) |
| return false; |
| |
| #ifdef INCLUDE_MOZILLA_DTRACE |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "dtrace", value)) |
| return false; |
| |
| #ifdef MOZ_VALGRIND |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "valgrind", value)) |
| return false; |
| |
| #ifdef JS_OOM_DO_BACKTRACES |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "oom-backtraces", value)) |
| return false; |
| |
| #ifdef ENABLE_BINARYDATA |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "binary-data", value)) |
| return false; |
| |
| #ifdef EXPOSE_INTL_API |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "intl-api", value)) |
| return false; |
| |
| #if defined(XP_WIN) |
| value = BooleanValue(false); |
| #elif defined(SOLARIS) |
| value = BooleanValue(false); |
| #elif defined(XP_UNIX) |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "mapped-array-buffer", value)) |
| return false; |
| |
| #ifdef MOZ_MEMORY |
| value = BooleanValue(true); |
| #else |
| value = BooleanValue(false); |
| #endif |
| if (!JS_SetProperty(cx, info, "moz-memory", value)) |
| return false; |
| |
| value.setInt32(sizeof(void*)); |
| if (!JS_SetProperty(cx, info, "pointer-byte-size", value)) |
| return false; |
| |
| args.rval().setObject(*info); |
| return true; |
| } |
| |
| static bool |
| GC(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| /* |
| * If the first argument is 'compartment', we collect any compartments |
| * previously scheduled for GC via schedulegc. If the first argument is an |
| * object, we collect the object's compartment (and any other compartments |
| * scheduled for GC). Otherwise, we collect all compartments. |
| */ |
| bool compartment = false; |
| if (args.length() >= 1) { |
| Value arg = args[0]; |
| if (arg.isString()) { |
| if (!JS_StringEqualsAscii(cx, arg.toString(), "compartment", &compartment)) |
| return false; |
| } else if (arg.isObject()) { |
| PrepareZoneForGC(UncheckedUnwrap(&arg.toObject())->zone()); |
| compartment = true; |
| } |
| } |
| |
| bool shrinking = false; |
| if (args.length() >= 2) { |
| Value arg = args[1]; |
| if (arg.isString()) { |
| if (!JS_StringEqualsAscii(cx, arg.toString(), "shrinking", &shrinking)) |
| return false; |
| } |
| } |
| |
| #ifndef JS_MORE_DETERMINISTIC |
| size_t preBytes = cx->runtime()->gc.usage.gcBytes(); |
| #endif |
| |
| if (compartment) |
| PrepareForDebugGC(cx->runtime()); |
| else |
| JS::PrepareForFullGC(cx->runtime()); |
| |
| JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL; |
| JS::GCForReason(cx->runtime(), gckind, JS::gcreason::API); |
| |
| char buf[256] = { '\0' }; |
| #ifndef JS_MORE_DETERMINISTIC |
| JS_snprintf(buf, sizeof(buf), "before %lu, after %lu\n", |
| (unsigned long)preBytes, (unsigned long)cx->runtime()->gc.usage.gcBytes()); |
| #endif |
| JSString* str = JS_NewStringCopyZ(cx, buf); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static bool |
| MinorGC(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.get(0) == BooleanValue(true)) |
| cx->runtime()->gc.storeBuffer.setAboutToOverflow(); |
| |
| cx->minorGC(JS::gcreason::API); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static const struct ParamPair { |
| const char* name; |
| JSGCParamKey param; |
| } paramMap[] = { |
| {"maxBytes", JSGC_MAX_BYTES }, |
| {"maxMallocBytes", JSGC_MAX_MALLOC_BYTES}, |
| {"gcBytes", JSGC_BYTES}, |
| {"gcNumber", JSGC_NUMBER}, |
| {"sliceTimeBudget", JSGC_SLICE_TIME_BUDGET}, |
| {"markStackLimit", JSGC_MARK_STACK_LIMIT}, |
| {"minEmptyChunkCount", JSGC_MIN_EMPTY_CHUNK_COUNT}, |
| {"maxEmptyChunkCount", JSGC_MAX_EMPTY_CHUNK_COUNT} |
| }; |
| |
| // Keep this in sync with above params. |
| #define GC_PARAMETER_ARGS_LIST "maxBytes, maxMallocBytes, gcBytes, gcNumber, sliceTimeBudget, markStackLimit, minEmptyChunkCount or maxEmptyChunkCount" |
| |
| static bool |
| GCParameter(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| JSString* str = ToString(cx, args.get(0)); |
| if (!str) |
| return false; |
| |
| JSFlatString* flatStr = JS_FlattenString(cx, str); |
| if (!flatStr) |
| return false; |
| |
| size_t paramIndex = 0; |
| for (;; paramIndex++) { |
| if (paramIndex == ArrayLength(paramMap)) { |
| JS_ReportError(cx, |
| "the first argument must be one of " GC_PARAMETER_ARGS_LIST); |
| return false; |
| } |
| if (JS_FlatStringEqualsAscii(flatStr, paramMap[paramIndex].name)) |
| break; |
| } |
| JSGCParamKey param = paramMap[paramIndex].param; |
| |
| // Request mode. |
| if (args.length() == 1) { |
| uint32_t value = JS_GetGCParameter(cx->runtime(), param); |
| args.rval().setNumber(value); |
| return true; |
| } |
| |
| if (param == JSGC_NUMBER || param == JSGC_BYTES) { |
| JS_ReportError(cx, "Attempt to change read-only parameter %s", |
| paramMap[paramIndex].name); |
| return false; |
| } |
| |
| if (disableOOMFunctions && (param == JSGC_MAX_BYTES || param == JSGC_MAX_MALLOC_BYTES)) { |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| uint32_t value; |
| if (!ToUint32(cx, args[1], &value)) |
| return false; |
| |
| if (!value) { |
| JS_ReportError(cx, "the second argument must be convertable to uint32_t " |
| "with non-zero value"); |
| return false; |
| } |
| |
| if (param == JSGC_MARK_STACK_LIMIT && JS::IsIncrementalGCInProgress(cx->runtime())) { |
| JS_ReportError(cx, "attempt to set markStackLimit while a GC is in progress"); |
| return false; |
| } |
| |
| if (param == JSGC_MAX_BYTES) { |
| uint32_t gcBytes = JS_GetGCParameter(cx->runtime(), JSGC_BYTES); |
| if (value < gcBytes) { |
| JS_ReportError(cx, |
| "attempt to set maxBytes to the value less than the current " |
| "gcBytes (%u)", |
| gcBytes); |
| return false; |
| } |
| } |
| |
| JS_SetGCParameter(cx->runtime(), param, value); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static void |
| SetAllowRelazification(JSContext* cx, bool allow) |
| { |
| JSRuntime* rt = cx->runtime(); |
| MOZ_ASSERT(rt->allowRelazificationForTesting != allow); |
| rt->allowRelazificationForTesting = allow; |
| |
| for (AllFramesIter i(cx); !i.done(); ++i) |
| i.script()->setDoNotRelazify(allow); |
| } |
| |
| static bool |
| RelazifyFunctions(JSContext* cx, unsigned argc, Value* vp) |
| { |
| // Relazifying functions on GC is usually only done for compartments that are |
| // not active. To aid fuzzing, this testing function allows us to relazify |
| // even if the compartment is active. |
| |
| SetAllowRelazification(cx, true); |
| bool res = GC(cx, argc, vp); |
| SetAllowRelazification(cx, false); |
| return res; |
| } |
| |
| static bool |
| IsProxy(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 1) { |
| JS_ReportError(cx, "the function takes exactly one argument"); |
| return false; |
| } |
| if (!args[0].isObject()) { |
| args.rval().setBoolean(false); |
| return true; |
| } |
| args.rval().setBoolean(args[0].toObject().is<ProxyObject>()); |
| return true; |
| } |
| |
| static bool |
| IsLazyFunction(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 1) { |
| JS_ReportError(cx, "The function takes exactly one argument."); |
| return false; |
| } |
| if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) { |
| JS_ReportError(cx, "The first argument should be a function."); |
| return false; |
| } |
| args.rval().setBoolean(args[0].toObject().as<JSFunction>().isInterpretedLazy()); |
| return true; |
| } |
| |
| static bool |
| IsRelazifiableFunction(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 1) { |
| JS_ReportError(cx, "The function takes exactly one argument."); |
| return false; |
| } |
| if (!args[0].isObject() || |
| !args[0].toObject().is<JSFunction>()) |
| { |
| JS_ReportError(cx, "The first argument should be a function."); |
| return true; |
| } |
| |
| JSFunction* fun = &args[0].toObject().as<JSFunction>(); |
| args.rval().setBoolean(fun->hasScript() && fun->nonLazyScript()->isRelazifiable()); |
| return true; |
| } |
| |
| static bool |
| InternalConst(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() == 0) { |
| JS_ReportError(cx, "the function takes exactly one argument"); |
| return false; |
| } |
| |
| JSString* str = ToString(cx, args[0]); |
| if (!str) |
| return false; |
| JSFlatString* flat = JS_FlattenString(cx, str); |
| if (!flat) |
| return false; |
| |
| if (JS_FlatStringEqualsAscii(flat, "INCREMENTAL_MARK_STACK_BASE_CAPACITY")) { |
| args.rval().setNumber(uint32_t(js::INCREMENTAL_MARK_STACK_BASE_CAPACITY)); |
| } else { |
| JS_ReportError(cx, "unknown const name"); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool |
| GCPreserveCode(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 0) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| |
| cx->runtime()->gc.setAlwaysPreserveCode(); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| #ifdef JS_GC_ZEAL |
| static bool |
| GCZeal(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() > 2) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Too many arguments"); |
| return false; |
| } |
| |
| uint32_t zeal; |
| if (!ToUint32(cx, args.get(0), &zeal)) |
| return false; |
| |
| uint32_t frequency = JS_DEFAULT_ZEAL_FREQ; |
| if (args.length() >= 2) { |
| if (!ToUint32(cx, args.get(1), &frequency)) |
| return false; |
| } |
| |
| JS_SetGCZeal(cx, (uint8_t)zeal, frequency); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| ScheduleGC(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() > 1) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Too many arguments"); |
| return false; |
| } |
| |
| if (args.length() == 0) { |
| /* Fetch next zeal trigger only. */ |
| } else if (args[0].isInt32()) { |
| /* Schedule a GC to happen after |arg| allocations. */ |
| JS_ScheduleGC(cx, args[0].toInt32()); |
| } else if (args[0].isObject()) { |
| /* Ensure that |zone| is collected during the next GC. */ |
| Zone* zone = UncheckedUnwrap(&args[0].toObject())->zone(); |
| PrepareZoneForGC(zone); |
| } else if (args[0].isString()) { |
| /* This allows us to schedule atomsCompartment for GC. */ |
| PrepareZoneForGC(args[0].toString()->zone()); |
| } |
| |
| uint8_t zeal; |
| uint32_t freq; |
| uint32_t next; |
| JS_GetGCZeal(cx, &zeal, &freq, &next); |
| args.rval().setInt32(next); |
| return true; |
| } |
| |
| static bool |
| SelectForGC(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| /* |
| * The selectedForMarking set is intended to be manually marked at slice |
| * start to detect missing pre-barriers. It is invalid for nursery things |
| * to be in the set, so evict the nursery before adding items. |
| */ |
| JSRuntime* rt = cx->runtime(); |
| rt->gc.evictNursery(); |
| |
| for (unsigned i = 0; i < args.length(); i++) { |
| if (args[i].isObject()) { |
| if (!rt->gc.selectForMarking(&args[i].toObject())) |
| return false; |
| } |
| } |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| VerifyPreBarriers(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() > 0) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Too many arguments"); |
| return false; |
| } |
| |
| gc::VerifyBarriers(cx->runtime(), gc::PreBarrierVerifier); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| VerifyPostBarriers(JSContext* cx, unsigned argc, Value* vp) |
| { |
| // This is a no-op since the post barrier verifier was removed. |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length()) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Too many arguments"); |
| return false; |
| } |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| GCState(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 0) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Too many arguments"); |
| return false; |
| } |
| |
| const char* state; |
| gc::State globalState = cx->runtime()->gc.state(); |
| if (globalState == gc::NO_INCREMENTAL) |
| state = "none"; |
| else if (globalState == gc::MARK) |
| state = "mark"; |
| else if (globalState == gc::SWEEP) |
| state = "sweep"; |
| else if (globalState == gc::COMPACT) |
| state = "compact"; |
| else |
| MOZ_CRASH("Unobserveable global GC state"); |
| |
| JSString* str = JS_NewStringCopyZ(cx, state); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static bool |
| DeterministicGC(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 1) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| |
| cx->runtime()->gc.setDeterministic(ToBoolean(args[0])); |
| args.rval().setUndefined(); |
| return true; |
| } |
| #endif /* JS_GC_ZEAL */ |
| |
| static bool |
| StartGC(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() > 2) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| |
| auto budget = SliceBudget::unlimited(); |
| if (args.length() >= 1) { |
| uint32_t work = 0; |
| if (!ToUint32(cx, args[0], &work)) |
| return false; |
| budget = SliceBudget(WorkBudget(work)); |
| } |
| |
| bool shrinking = false; |
| if (args.length() >= 2) { |
| Value arg = args[1]; |
| if (arg.isString()) { |
| if (!JS_StringEqualsAscii(cx, arg.toString(), "shrinking", &shrinking)) |
| return false; |
| } |
| } |
| |
| JSRuntime* rt = cx->runtime(); |
| if (rt->gc.isIncrementalGCInProgress()) { |
| RootedObject callee(cx, &args.callee()); |
| JS_ReportError(cx, "Incremental GC already in progress"); |
| return false; |
| } |
| |
| JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL; |
| rt->gc.startDebugGC(gckind, budget); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| GCSlice(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() > 1) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| |
| auto budget = SliceBudget::unlimited(); |
| if (args.length() == 1) { |
| uint32_t work = 0; |
| if (!ToUint32(cx, args[0], &work)) |
| return false; |
| budget = SliceBudget(WorkBudget(work)); |
| } |
| |
| JSRuntime* rt = cx->runtime(); |
| if (!rt->gc.isIncrementalGCInProgress()) |
| rt->gc.startDebugGC(GC_NORMAL, budget); |
| else |
| rt->gc.debugGCSlice(budget); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| AbortGC(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 0) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| |
| cx->runtime()->gc.abortGC(); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| ValidateGC(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 1) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| |
| cx->runtime()->gc.setValidate(ToBoolean(args[0])); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| FullCompartmentChecks(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 1) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| |
| cx->runtime()->gc.setFullCompartmentChecks(ToBoolean(args[0])); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| NondeterministicGetWeakMapKeys(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 1) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| if (!args[0].isObject()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, |
| "nondeterministicGetWeakMapKeys", "WeakMap", |
| InformalValueTypeName(args[0])); |
| return false; |
| } |
| RootedObject arr(cx); |
| RootedObject mapObj(cx, &args[0].toObject()); |
| if (!JS_NondeterministicGetWeakMapKeys(cx, mapObj, &arr)) |
| return false; |
| if (!arr) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, |
| "nondeterministicGetWeakMapKeys", "WeakMap", |
| args[0].toObject().getClass()->name); |
| return false; |
| } |
| args.rval().setObject(*arr); |
| return true; |
| } |
| |
| class HasChildTracer : public JS::CallbackTracer |
| { |
| RootedValue child_; |
| bool found_; |
| |
| void onChild(const JS::GCCellPtr& thing) override { |
| if (thing.asCell() == child_.toGCThing()) |
| found_ = true; |
| } |
| |
| public: |
| HasChildTracer(JSRuntime* rt, HandleValue child) |
| : JS::CallbackTracer(rt, TraceWeakMapKeysValues), child_(rt, child), found_(false) |
| {} |
| |
| bool found() const { return found_; } |
| }; |
| |
| static bool |
| HasChild(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedValue parent(cx, args.get(0)); |
| RootedValue child(cx, args.get(1)); |
| |
| if (!parent.isMarkable() || !child.isMarkable()) { |
| args.rval().setBoolean(false); |
| return true; |
| } |
| |
| HasChildTracer trc(cx->runtime(), child); |
| TraceChildren(&trc, parent.toGCThing(), parent.traceKind()); |
| args.rval().setBoolean(trc.found()); |
| return true; |
| } |
| |
| static bool |
| SetSavedStacksRNGState(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.requireAtLeast(cx, "setSavedStacksRNGState", 1)) |
| return false; |
| |
| int32_t seed; |
| if (!ToInt32(cx, args[0], &seed)) |
| return false; |
| |
| // Either one or the other of the seed arguments must be non-zero; |
| // make this true no matter what value 'seed' has. |
| cx->compartment()->savedStacks().setRNGState(seed, (seed + 1) * 33); |
| return true; |
| } |
| |
| static bool |
| GetSavedFrameCount(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| args.rval().setNumber(cx->compartment()->savedStacks().count()); |
| return true; |
| } |
| |
| static bool |
| SaveStack(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| unsigned maxFrameCount = 0; |
| if (args.length() >= 1) { |
| double d; |
| if (!ToNumber(cx, args[0], &d)) |
| return false; |
| if (d < 0) { |
| ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, |
| JSDVG_SEARCH_STACK, args[0], nullptr, |
| "not a valid maximum frame count", NULL); |
| return false; |
| } |
| maxFrameCount = d; |
| } |
| |
| JSCompartment* targetCompartment = cx->compartment(); |
| if (args.length() >= 2) { |
| if (!args[1].isObject()) { |
| ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, |
| JSDVG_SEARCH_STACK, args[0], nullptr, |
| "not an object", NULL); |
| return false; |
| } |
| RootedObject obj(cx, UncheckedUnwrap(&args[1].toObject())); |
| if (!obj) |
| return false; |
| targetCompartment = obj->compartment(); |
| } |
| |
| RootedObject stack(cx); |
| { |
| AutoCompartment ac(cx, targetCompartment); |
| if (!JS::CaptureCurrentStack(cx, &stack, maxFrameCount)) |
| return false; |
| } |
| |
| if (stack && !cx->compartment()->wrap(cx, &stack)) |
| return false; |
| |
| args.rval().setObjectOrNull(stack); |
| return true; |
| } |
| |
| static bool |
| CallFunctionFromNativeFrame(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 1) { |
| JS_ReportError(cx, "The function takes exactly one argument."); |
| return false; |
| } |
| if (!args[0].isObject() || !IsCallable(args[0])) { |
| JS_ReportError(cx, "The first argument should be a function."); |
| return false; |
| } |
| |
| RootedObject function(cx, &args[0].toObject()); |
| return Call(cx, UndefinedHandleValue, function, |
| JS::HandleValueArray::empty(), args.rval()); |
| } |
| |
| static bool |
| CallFunctionWithAsyncStack(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 3) { |
| JS_ReportError(cx, "The function takes exactly three arguments."); |
| return false; |
| } |
| if (!args[0].isObject() || !IsCallable(args[0])) { |
| JS_ReportError(cx, "The first argument should be a function."); |
| return false; |
| } |
| if (!args[1].isObject() || !args[1].toObject().is<SavedFrame>()) { |
| JS_ReportError(cx, "The second argument should be a SavedFrame."); |
| return false; |
| } |
| if (!args[2].isString() || args[2].toString()->empty()) { |
| JS_ReportError(cx, "The third argument should be a non-empty string."); |
| return false; |
| } |
| |
| RootedObject function(cx, &args[0].toObject()); |
| RootedObject stack(cx, &args[1].toObject()); |
| RootedString asyncCause(cx, args[2].toString()); |
| |
| JS::AutoSetAsyncStackForNewCalls sas(cx, stack, asyncCause, |
| JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT); |
| return Call(cx, UndefinedHandleValue, function, |
| JS::HandleValueArray::empty(), args.rval()); |
| } |
| |
| static bool |
| EnableTrackAllocations(JSContext* cx, unsigned argc, Value* vp) |
| { |
| SetObjectMetadataCallback(cx, SavedStacksMetadataCallback); |
| return true; |
| } |
| |
| static bool |
| DisableTrackAllocations(JSContext* cx, unsigned argc, Value* vp) |
| { |
| SetObjectMetadataCallback(cx, nullptr); |
| return true; |
| } |
| |
| #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) |
| static bool |
| OOMThreadTypes(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| args.rval().setInt32(js::oom::THREAD_TYPE_MAX); |
| return true; |
| } |
| |
| static bool |
| SetupOOMFailure(JSContext* cx, bool failAlways, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (disableOOMFunctions) { |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| if (args.length() < 1) { |
| JS_ReportError(cx, "Count argument required"); |
| return false; |
| } |
| |
| if (args.length() > 2) { |
| JS_ReportError(cx, "Too many arguments"); |
| return false; |
| } |
| |
| int32_t count; |
| if (!JS::ToInt32(cx, args.get(0), &count)) |
| return false; |
| |
| if (count <= 0) { |
| JS_ReportError(cx, "OOM cutoff should be positive"); |
| return false; |
| } |
| |
| uint32_t targetThread = js::oom::THREAD_TYPE_MAIN; |
| if (args.length() > 1 && !ToUint32(cx, args[1], &targetThread)) |
| return false; |
| |
| if (targetThread == js::oom::THREAD_TYPE_NONE || targetThread >= js::oom::THREAD_TYPE_MAX) { |
| JS_ReportError(cx, "Invalid thread type specified"); |
| return false; |
| } |
| |
| HelperThreadState().waitForAllThreads(); |
| js::oom::targetThread = targetThread; |
| if (uint64_t(OOM_counter) + count >= UINT32_MAX) { |
| JS_ReportError(cx, "OOM cutoff out of range"); |
| return false; |
| } |
| OOM_maxAllocations = OOM_counter + count; |
| OOM_failAlways = failAlways; |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| OOMAfterAllocations(JSContext* cx, unsigned argc, Value* vp) |
| { |
| return SetupOOMFailure(cx, true, argc, vp); |
| } |
| |
| static bool |
| OOMAtAllocation(JSContext* cx, unsigned argc, Value* vp) |
| { |
| return SetupOOMFailure(cx, false, argc, vp); |
| } |
| |
| static bool |
| ResetOOMFailure(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| args.rval().setBoolean(OOM_counter >= OOM_maxAllocations); |
| js::oom::targetThread = js::oom::THREAD_TYPE_NONE; |
| OOM_maxAllocations = UINT32_MAX; |
| return true; |
| } |
| |
| static bool |
| OOMTest(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 1 || !args[0].isObject() || !args[0].toObject().is<JSFunction>()) { |
| JS_ReportError(cx, "oomTest() takes a single function argument."); |
| return false; |
| } |
| |
| if (disableOOMFunctions) { |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| MOZ_ASSERT(!cx->isExceptionPending()); |
| cx->runtime()->hadOutOfMemory = false; |
| |
| RootedFunction function(cx, &args[0].toObject().as<JSFunction>()); |
| |
| bool verbose = EnvVarIsDefined("OOM_VERBOSE"); |
| |
| unsigned threadStart = oom::THREAD_TYPE_MAIN; |
| unsigned threadEnd = oom::THREAD_TYPE_MAX; |
| |
| // Test a single thread type if specified by the OOM_THREAD environment variable. |
| int threadOption = 0; |
| if (EnvVarAsInt("OOM_THREAD", &threadOption)) { |
| if (threadOption < oom::THREAD_TYPE_MAIN || threadOption > oom::THREAD_TYPE_MAX) { |
| JS_ReportError(cx, "OOM_THREAD value out of range."); |
| return false; |
| } |
| |
| threadStart = threadOption; |
| threadEnd = threadOption + 1; |
| } |
| |
| #if defined(JS_GC_ZEAL) |
| JS_SetGCZeal(cx, 0, JS_DEFAULT_ZEAL_FREQ); |
| #endif |
| |
| for (unsigned thread = threadStart; thread < threadEnd; thread++) { |
| if (verbose) |
| fprintf(stderr, "thread %d\n", thread); |
| |
| HelperThreadState().waitForAllThreads(); |
| js::oom::targetThread = thread; |
| |
| unsigned allocation = 1; |
| bool handledOOM; |
| do { |
| if (verbose) |
| fprintf(stderr, " allocation %d\n", allocation); |
| |
| MOZ_ASSERT(!cx->isExceptionPending()); |
| MOZ_ASSERT(!cx->runtime()->hadOutOfMemory); |
| |
| OOM_maxAllocations = OOM_counter + allocation; |
| OOM_failAlways = false; |
| |
| RootedValue result(cx); |
| bool ok = JS_CallFunction(cx, cx->global(), function, |
| HandleValueArray::empty(), &result); |
| |
| handledOOM = OOM_counter >= OOM_maxAllocations; |
| OOM_maxAllocations = UINT32_MAX; |
| |
| MOZ_ASSERT_IF(ok, !cx->isExceptionPending()); |
| |
| // Note that it is possible that the function throws an exception |
| // unconnected to OOM, in which case we ignore it. More correct |
| // would be to have the caller pass some kind of exception |
| // specification and to check the exception against it. |
| |
| cx->clearPendingException(); |
| cx->runtime()->hadOutOfMemory = false; |
| |
| allocation++; |
| } while (handledOOM); |
| |
| if (verbose) { |
| fprintf(stderr, " finished after %d allocations\n", allocation - 2); |
| } |
| } |
| |
| js::oom::targetThread = js::oom::THREAD_TYPE_NONE; |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| #endif |
| |
| static const js::Class FakePromiseClass = { |
| "Promise", JSCLASS_IS_ANONYMOUS |
| }; |
| |
| static bool |
| MakeFakePromise(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| RootedObject obj(cx, NewObjectWithGivenProto(cx, &FakePromiseClass, nullptr)); |
| if (!obj) |
| return false; |
| |
| JS::dbg::onNewPromise(cx, obj); |
| args.rval().setObject(*obj); |
| return true; |
| } |
| |
| static bool |
| SettleFakePromise(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.requireAtLeast(cx, "settleFakePromise", 1)) |
| return false; |
| if (!args[0].isObject() || args[0].toObject().getClass() != &FakePromiseClass) { |
| JS_ReportError(cx, "first argument must be a (fake) Promise object"); |
| return false; |
| } |
| |
| RootedObject promise(cx, &args[0].toObject()); |
| JS::dbg::onPromiseSettled(cx, promise); |
| return true; |
| } |
| |
| static unsigned finalizeCount = 0; |
| |
| static void |
| finalize_counter_finalize(JSFreeOp* fop, JSObject* obj) |
| { |
| ++finalizeCount; |
| } |
| |
| static const JSClass FinalizeCounterClass = { |
| "FinalizeCounter", JSCLASS_IS_ANONYMOUS, |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| finalize_counter_finalize |
| }; |
| |
| static bool |
| MakeFinalizeObserver(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| JSObject* obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, nullptr); |
| if (!obj) |
| return false; |
| |
| args.rval().setObject(*obj); |
| return true; |
| } |
| |
| static bool |
| FinalizeCount(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| args.rval().setInt32(finalizeCount); |
| return true; |
| } |
| |
| static bool |
| DumpHeap(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| DumpHeapNurseryBehaviour nurseryBehaviour = js::IgnoreNurseryObjects; |
| FILE* dumpFile = nullptr; |
| |
| unsigned i = 0; |
| if (args.length() > i) { |
| Value v = args[i]; |
| if (v.isString()) { |
| JSString* str = v.toString(); |
| bool same = false; |
| if (!JS_StringEqualsAscii(cx, str, "collectNurseryBeforeDump", &same)) |
| return false; |
| if (same) { |
| nurseryBehaviour = js::CollectNurseryBeforeDump; |
| ++i; |
| } |
| } |
| } |
| |
| if (args.length() > i) { |
| Value v = args[i]; |
| if (v.isString()) { |
| if (!fuzzingSafe) { |
| JSString* str = v.toString(); |
| JSAutoByteString fileNameBytes; |
| if (!fileNameBytes.encodeLatin1(cx, str)) |
| return false; |
| const char* fileName = fileNameBytes.ptr(); |
| dumpFile = fopen(fileName, "w"); |
| if (!dumpFile) { |
| JS_ReportError(cx, "can't open %s", fileName); |
| return false; |
| } |
| } |
| ++i; |
| } |
| } |
| |
| if (i != args.length()) { |
| JS_ReportError(cx, "bad arguments passed to dumpHeap"); |
| if (dumpFile) |
| fclose(dumpFile); |
| return false; |
| } |
| |
| js::DumpHeap(JS_GetRuntime(cx), dumpFile ? dumpFile : stdout, nurseryBehaviour); |
| |
| if (dumpFile) |
| fclose(dumpFile); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| Terminate(JSContext* cx, unsigned arg, Value* vp) |
| { |
| #ifdef JS_MORE_DETERMINISTIC |
| // Print a message to stderr in more-deterministic builds to help jsfunfuzz |
| // find uncatchable-exception bugs. |
| fprintf(stderr, "terminate called\n"); |
| #endif |
| |
| JS_ClearPendingException(cx); |
| return false; |
| } |
| |
| #define SPS_PROFILING_STACK_MAX_SIZE 1000 |
| static ProfileEntry SPS_PROFILING_STACK[SPS_PROFILING_STACK_MAX_SIZE]; |
| static uint32_t SPS_PROFILING_STACK_SIZE = 0; |
| |
| static bool |
| EnableSPSProfiling(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| // Disable before re-enabling; see the assertion in |SPSProfiler::setProfilingStack|. |
| if (cx->runtime()->spsProfiler.installed()) |
| cx->runtime()->spsProfiler.enable(false); |
| |
| SetRuntimeProfilingStack(cx->runtime(), SPS_PROFILING_STACK, &SPS_PROFILING_STACK_SIZE, |
| SPS_PROFILING_STACK_MAX_SIZE); |
| cx->runtime()->spsProfiler.enableSlowAssertions(false); |
| cx->runtime()->spsProfiler.enable(true); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| EnableSPSProfilingWithSlowAssertions(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| args.rval().setUndefined(); |
| |
| if (cx->runtime()->spsProfiler.enabled()) { |
| // If profiling already enabled with slow assertions disabled, |
| // this is a no-op. |
| if (cx->runtime()->spsProfiler.slowAssertionsEnabled()) |
| return true; |
| |
| // Slow assertions are off. Disable profiling before re-enabling |
| // with slow assertions on. |
| cx->runtime()->spsProfiler.enable(false); |
| } |
| |
| // Disable before re-enabling; see the assertion in |SPSProfiler::setProfilingStack|. |
| if (cx->runtime()->spsProfiler.installed()) |
| cx->runtime()->spsProfiler.enable(false); |
| |
| SetRuntimeProfilingStack(cx->runtime(), SPS_PROFILING_STACK, &SPS_PROFILING_STACK_SIZE, |
| SPS_PROFILING_STACK_MAX_SIZE); |
| cx->runtime()->spsProfiler.enableSlowAssertions(true); |
| cx->runtime()->spsProfiler.enable(true); |
| |
| return true; |
| } |
| |
| static bool |
| DisableSPSProfiling(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (cx->runtime()->spsProfiler.installed()) |
| cx->runtime()->spsProfiler.enable(false); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| ReadSPSProfilingStack(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| args.rval().setUndefined(); |
| |
| // Return boolean 'false' if profiler is not enabled. |
| if (!cx->runtime()->spsProfiler.enabled()) { |
| args.rval().setBoolean(false); |
| return true; |
| } |
| |
| // Array holding physical jit stack frames. |
| RootedObject stack(cx, NewDenseEmptyArray(cx)); |
| if (!stack) |
| return false; |
| |
| RootedObject inlineStack(cx); |
| RootedObject inlineFrameInfo(cx); |
| RootedString frameKind(cx); |
| RootedString frameLabel(cx); |
| RootedId idx(cx); |
| |
| JS::ProfilingFrameIterator::RegisterState state; |
| uint32_t physicalFrameNo = 0; |
| const unsigned propAttrs = JSPROP_ENUMERATE; |
| for (JS::ProfilingFrameIterator i(cx->runtime(), state); !i.done(); ++i, ++physicalFrameNo) { |
| MOZ_ASSERT(i.stackAddress() != nullptr); |
| |
| // Array holding all inline frames in a single physical jit stack frame. |
| inlineStack = NewDenseEmptyArray(cx); |
| if (!inlineStack) |
| return false; |
| |
| JS::ProfilingFrameIterator::Frame frames[16]; |
| uint32_t nframes = i.extractStack(frames, 0, 16); |
| for (uint32_t inlineFrameNo = 0; inlineFrameNo < nframes; inlineFrameNo++) { |
| |
| // Object holding frame info. |
| inlineFrameInfo = NewBuiltinClassInstance<PlainObject>(cx); |
| if (!inlineFrameInfo) |
| return false; |
| |
| const char* frameKindStr = nullptr; |
| switch (frames[inlineFrameNo].kind) { |
| case JS::ProfilingFrameIterator::Frame_Baseline: |
| frameKindStr = "baseline"; |
| break; |
| case JS::ProfilingFrameIterator::Frame_Ion: |
| frameKindStr = "ion"; |
| break; |
| case JS::ProfilingFrameIterator::Frame_AsmJS: |
| frameKindStr = "asmjs"; |
| break; |
| default: |
| frameKindStr = "unknown"; |
| } |
| frameKind = NewStringCopyZ<CanGC>(cx, frameKindStr); |
| if (!frameKind) |
| return false; |
| |
| if (!JS_DefineProperty(cx, inlineFrameInfo, "kind", frameKind, propAttrs)) |
| return false; |
| |
| frameLabel = NewStringCopyZ<CanGC>(cx, frames[inlineFrameNo].label); |
| if (!frameLabel) |
| return false; |
| |
| if (!JS_DefineProperty(cx, inlineFrameInfo, "label", frameLabel, propAttrs)) |
| return false; |
| |
| idx = INT_TO_JSID(inlineFrameNo); |
| if (!JS_DefinePropertyById(cx, inlineStack, idx, inlineFrameInfo, 0)) |
| return false; |
| } |
| |
| // Push inline array into main array. |
| idx = INT_TO_JSID(physicalFrameNo); |
| if (!JS_DefinePropertyById(cx, stack, idx, inlineStack, 0)) |
| return false; |
| } |
| |
| args.rval().setObject(*stack); |
| return true; |
| } |
| |
| static bool |
| EnableOsiPointRegisterChecks(JSContext*, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| #ifdef CHECK_OSIPOINT_REGISTERS |
| jit::JitOptions.checkOsiPointRegisters = true; |
| #endif |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| DisplayName(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.get(0).isObject() || !args[0].toObject().is<JSFunction>()) { |
| RootedObject arg(cx, &args.callee()); |
| ReportUsageError(cx, arg, "Must have one function argument"); |
| return false; |
| } |
| |
| JSFunction* fun = &args[0].toObject().as<JSFunction>(); |
| JSString* str = fun->displayAtom(); |
| args.rval().setString(str ? str : cx->runtime()->emptyString); |
| return true; |
| } |
| |
| static JSObject* |
| ShellObjectMetadataCallback(JSContext* cx, JSObject*) |
| { |
| AutoEnterOOMUnsafeRegion oomUnsafe; |
| |
| RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx)); |
| if (!obj) |
| oomUnsafe.crash("ShellObjectMetadataCallback"); |
| |
| RootedObject stack(cx, NewDenseEmptyArray(cx)); |
| if (!stack) |
| oomUnsafe.crash("ShellObjectMetadataCallback"); |
| |
| static int createdIndex = 0; |
| createdIndex++; |
| |
| if (!JS_DefineProperty(cx, obj, "index", createdIndex, 0, |
| JS_STUBGETTER, JS_STUBSETTER)) |
| { |
| oomUnsafe.crash("ShellObjectMetadataCallback"); |
| } |
| |
| if (!JS_DefineProperty(cx, obj, "stack", stack, 0, |
| JS_STUBGETTER, JS_STUBSETTER)) |
| { |
| oomUnsafe.crash("ShellObjectMetadataCallback"); |
| } |
| |
| int stackIndex = 0; |
| RootedId id(cx); |
| RootedValue callee(cx); |
| for (NonBuiltinScriptFrameIter iter(cx); !iter.done(); ++iter) { |
| if (iter.isFunctionFrame() && iter.compartment() == cx->compartment()) { |
| id = INT_TO_JSID(stackIndex); |
| RootedObject callee(cx, iter.callee(cx)); |
| if (!JS_DefinePropertyById(cx, stack, id, callee, 0, |
| JS_STUBGETTER, JS_STUBSETTER)) |
| { |
| oomUnsafe.crash("ShellObjectMetadataCallback"); |
| } |
| stackIndex++; |
| } |
| } |
| |
| return obj; |
| } |
| |
| static bool |
| EnableShellObjectMetadataCallback(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| SetObjectMetadataCallback(cx, ShellObjectMetadataCallback); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| GetObjectMetadata(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 1 || !args[0].isObject()) { |
| JS_ReportError(cx, "Argument must be an object"); |
| return false; |
| } |
| |
| args.rval().setObjectOrNull(GetObjectMetadata(&args[0].toObject())); |
| return true; |
| } |
| |
| static bool |
| testingFunc_bailout(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| // NOP when not in IonMonkey |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| testingFunc_inJit(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (!jit::IsBaselineEnabled(cx)) { |
| JSString* error = JS_NewStringCopyZ(cx, "Baseline is disabled."); |
| if(!error) |
| return false; |
| |
| args.rval().setString(error); |
| return true; |
| } |
| |
| JSScript* script = cx->currentScript(); |
| if (script && script->getWarmUpResetCount() >= 20) { |
| JSString* error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up."); |
| if (!error) |
| return false; |
| |
| args.rval().setString(error); |
| return true; |
| } |
| |
| args.rval().setBoolean(cx->currentlyRunningInJit()); |
| return true; |
| } |
| |
| static bool |
| testingFunc_inIon(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (!jit::IsIonEnabled(cx)) { |
| JSString* error = JS_NewStringCopyZ(cx, "Ion is disabled."); |
| if (!error) |
| return false; |
| |
| args.rval().setString(error); |
| return true; |
| } |
| |
| ScriptFrameIter iter(cx); |
| if (iter.isIon()) { |
| // Reset the counter of the IonScript's script. |
| jit::JitFrameIterator jitIter(cx); |
| ++jitIter; |
| jitIter.script()->resetWarmUpResetCounter(); |
| } else { |
| // Check if we missed multiple attempts at compiling the innermost script. |
| JSScript* script = cx->currentScript(); |
| if (script && script->getWarmUpResetCount() >= 20) { |
| JSString* error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up."); |
| if (!error) |
| return false; |
| |
| args.rval().setString(error); |
| return true; |
| } |
| } |
| |
| args.rval().setBoolean(iter.isIon()); |
| return true; |
| } |
| |
| bool |
| js::testingFunc_assertFloat32(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 2) { |
| JS_ReportError(cx, "Expects only 2 arguments"); |
| return false; |
| } |
| |
| // NOP when not in IonMonkey |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| TestingFunc_assertJitStackInvariants(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| jit::AssertJitStackInvariants(cx); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| bool |
| js::testingFunc_assertRecoveredOnBailout(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 2) { |
| JS_ReportError(cx, "Expects only 2 arguments"); |
| return false; |
| } |
| |
| // NOP when not in IonMonkey |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| SetJitCompilerOption(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject callee(cx, &args.callee()); |
| |
| if (args.length() != 2) { |
| ReportUsageError(cx, callee, "Wrong number of arguments."); |
| return false; |
| } |
| |
| if (!args[0].isString()) { |
| ReportUsageError(cx, callee, "First argument must be a String."); |
| return false; |
| } |
| |
| if (!args[1].isInt32()) { |
| ReportUsageError(cx, callee, "Second argument must be an Int32."); |
| return false; |
| } |
| |
| JSFlatString* strArg = JS_FlattenString(cx, args[0].toString()); |
| |
| #define JIT_COMPILER_MATCH(key, string) \ |
| else if (JS_FlatStringEqualsAscii(strArg, string)) \ |
| opt = JSJITCOMPILER_ ## key; |
| |
| JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION; |
| if (false) {} |
| JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH); |
| #undef JIT_COMPILER_MATCH |
| |
| if (opt == JSJITCOMPILER_NOT_AN_OPTION) { |
| ReportUsageError(cx, callee, "First argument does not name a valid option (see jsapi.h)."); |
| return false; |
| } |
| |
| int32_t number = args[1].toInt32(); |
| if (number < 0) |
| number = -1; |
| |
| // Throw if disabling the JITs and there's JIT code on the stack, to avoid |
| // assertion failures. |
| if ((opt == JSJITCOMPILER_BASELINE_ENABLE || opt == JSJITCOMPILER_ION_ENABLE) && |
| number == 0) |
| { |
| js::jit::JitActivationIterator iter(cx->runtime()); |
| if (!iter.done()) { |
| JS_ReportError(cx, "Can't turn off JITs with JIT code on the stack."); |
| return false; |
| } |
| } |
| |
| JS_SetGlobalJitCompilerOption(cx->runtime(), opt, uint32_t(number)); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| GetJitCompilerOptions(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject info(cx, JS_NewPlainObject(cx)); |
| if (!info) |
| return false; |
| |
| RootedValue value(cx); |
| |
| #define JIT_COMPILER_MATCH(key, string) \ |
| opt = JSJITCOMPILER_ ## key; \ |
| value.setInt32(JS_GetGlobalJitCompilerOption(cx->runtime(), opt)); \ |
| if (!JS_SetProperty(cx, info, string, value)) \ |
| return false; |
| |
| JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION; |
| JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH); |
| #undef JIT_COMPILER_MATCH |
| |
| args.rval().setObject(*info); |
| |
| return true; |
| } |
| |
| static bool |
| SetIonCheckGraphCoherency(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| jit::JitOptions.checkGraphConsistency = ToBoolean(args.get(0)); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| class CloneBufferObject : public NativeObject { |
| static const JSPropertySpec props_[2]; |
| static const size_t DATA_SLOT = 0; |
| static const size_t LENGTH_SLOT = 1; |
| static const size_t NUM_SLOTS = 2; |
| |
| public: |
| static const Class class_; |
| |
| static CloneBufferObject* Create(JSContext* cx) { |
| RootedObject obj(cx, JS_NewObject(cx, Jsvalify(&class_))); |
| if (!obj) |
| return nullptr; |
| obj->as<CloneBufferObject>().setReservedSlot(DATA_SLOT, PrivateValue(nullptr)); |
| obj->as<CloneBufferObject>().setReservedSlot(LENGTH_SLOT, Int32Value(0)); |
| |
| if (!JS_DefineProperties(cx, obj, props_)) |
| return nullptr; |
| |
| return &obj->as<CloneBufferObject>(); |
| } |
| |
| static CloneBufferObject* Create(JSContext* cx, JSAutoStructuredCloneBuffer* buffer) { |
| Rooted<CloneBufferObject*> obj(cx, Create(cx)); |
| if (!obj) |
| return nullptr; |
| uint64_t* datap; |
| size_t nbytes; |
| buffer->steal(&datap, &nbytes); |
| obj->setData(datap); |
| obj->setNBytes(nbytes); |
| return obj; |
| } |
| |
| uint64_t* data() const { |
| return static_cast<uint64_t*>(getReservedSlot(DATA_SLOT).toPrivate()); |
| } |
| |
| void setData(uint64_t* aData) { |
| MOZ_ASSERT(!data()); |
| setReservedSlot(DATA_SLOT, PrivateValue(aData)); |
| } |
| |
| size_t nbytes() const { |
| return getReservedSlot(LENGTH_SLOT).toInt32(); |
| } |
| |
| void setNBytes(size_t nbytes) { |
| MOZ_ASSERT(nbytes <= UINT32_MAX); |
| setReservedSlot(LENGTH_SLOT, Int32Value(nbytes)); |
| } |
| |
| // Discard an owned clone buffer. |
| void discard() { |
| if (data()) |
| JS_ClearStructuredClone(data(), nbytes(), nullptr, nullptr); |
| setReservedSlot(DATA_SLOT, PrivateValue(nullptr)); |
| } |
| |
| static bool |
| setCloneBuffer_impl(JSContext* cx, const CallArgs& args) { |
| if (args.length() != 1 || !args[0].isString()) { |
| JS_ReportError(cx, |
| "the first argument argument must be maxBytes, " |
| "maxMallocBytes, gcStackpoolLifespan, gcBytes or " |
| "gcNumber"); |
| JS_ReportError(cx, "clonebuffer setter requires a single string argument"); |
| return false; |
| } |
| |
| if (fuzzingSafe) { |
| // A manually-created clonebuffer could easily trigger a crash |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>()); |
| obj->discard(); |
| |
| char* str = JS_EncodeString(cx, args[0].toString()); |
| if (!str) |
| return false; |
| obj->setData(reinterpret_cast<uint64_t*>(str)); |
| obj->setNBytes(JS_GetStringLength(args[0].toString())); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| is(HandleValue v) { |
| return v.isObject() && v.toObject().is<CloneBufferObject>(); |
| } |
| |
| static bool |
| setCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<is, setCloneBuffer_impl>(cx, args); |
| } |
| |
| static bool |
| getCloneBuffer_impl(JSContext* cx, const CallArgs& args) { |
| Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>()); |
| MOZ_ASSERT(args.length() == 0); |
| |
| if (!obj->data()) { |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| bool hasTransferable; |
| if (!JS_StructuredCloneHasTransferables(obj->data(), obj->nbytes(), &hasTransferable)) |
| return false; |
| |
| if (hasTransferable) { |
| JS_ReportError(cx, "cannot retrieve structured clone buffer with transferables"); |
| return false; |
| } |
| |
| JSString* str = JS_NewStringCopyN(cx, reinterpret_cast<char*>(obj->data()), obj->nbytes()); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static bool |
| getCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<is, getCloneBuffer_impl>(cx, args); |
| } |
| |
| static void Finalize(FreeOp* fop, JSObject* obj) { |
| obj->as<CloneBufferObject>().discard(); |
| } |
| }; |
| |
| const Class CloneBufferObject::class_ = { |
| "CloneBuffer", JSCLASS_HAS_RESERVED_SLOTS(CloneBufferObject::NUM_SLOTS), |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| Finalize |
| }; |
| |
| const JSPropertySpec CloneBufferObject::props_[] = { |
| JS_PSGS("clonebuffer", getCloneBuffer, setCloneBuffer, 0), |
| JS_PS_END |
| }; |
| |
| static bool |
| Serialize(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| JSAutoStructuredCloneBuffer clonebuf; |
| if (!clonebuf.write(cx, args.get(0), args.get(1))) |
| return false; |
| |
| RootedObject obj(cx, CloneBufferObject::Create(cx, &clonebuf)); |
| if (!obj) |
| return false; |
| |
| args.rval().setObject(*obj); |
| return true; |
| } |
| |
| static bool |
| Deserialize(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 1 || !args[0].isObject()) { |
| JS_ReportError(cx, "deserialize requires a single clonebuffer argument"); |
| return false; |
| } |
| |
| if (!args[0].toObject().is<CloneBufferObject>()) { |
| JS_ReportError(cx, "deserialize requires a clonebuffer"); |
| return false; |
| } |
| |
| Rooted<CloneBufferObject*> obj(cx, &args[0].toObject().as<CloneBufferObject>()); |
| |
| // Clone buffer was already consumed? |
| if (!obj->data()) { |
| JS_ReportError(cx, "deserialize given invalid clone buffer " |
| "(transferables already consumed?)"); |
| return false; |
| } |
| |
| bool hasTransferable; |
| if (!JS_StructuredCloneHasTransferables(obj->data(), obj->nbytes(), &hasTransferable)) |
| return false; |
| |
| RootedValue deserialized(cx); |
| if (!JS_ReadStructuredClone(cx, obj->data(), obj->nbytes(), |
| JS_STRUCTURED_CLONE_VERSION, &deserialized, nullptr, nullptr)) { |
| return false; |
| } |
| args.rval().set(deserialized); |
| |
| if (hasTransferable) |
| obj->discard(); |
| |
| return true; |
| } |
| |
| static bool |
| Neuter(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 2) { |
| JS_ReportError(cx, "wrong number of arguments to neuter()"); |
| return false; |
| } |
| |
| RootedObject obj(cx); |
| if (!JS_ValueToObject(cx, args[0], &obj)) |
| return false; |
| |
| if (!obj) { |
| JS_ReportError(cx, "neuter must be passed an object"); |
| return false; |
| } |
| |
| NeuterDataDisposition changeData; |
| RootedString str(cx, JS::ToString(cx, args[1])); |
| if (!str) |
| return false; |
| JSAutoByteString dataDisposition(cx, str); |
| if (!dataDisposition) |
| return false; |
| if (strcmp(dataDisposition.ptr(), "same-data") == 0) { |
| changeData = KeepData; |
| } else if (strcmp(dataDisposition.ptr(), "change-data") == 0) { |
| changeData = ChangeData; |
| } else { |
| JS_ReportError(cx, "unknown parameter 2 to neuter()"); |
| return false; |
| } |
| |
| if (!JS_NeuterArrayBuffer(cx, obj, changeData)) |
| return false; |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| HelperThreadCount(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| #ifdef JS_MORE_DETERMINISTIC |
| // Always return 0 to get consistent output with and without --no-threads. |
| args.rval().setInt32(0); |
| #else |
| if (CanUseExtraThreads()) |
| args.rval().setInt32(HelperThreadState().threadCount); |
| else |
| args.rval().setInt32(0); |
| #endif |
| return true; |
| } |
| |
| static bool |
| TimesAccessed(JSContext* cx, unsigned argc, Value* vp) |
| { |
| static int32_t accessed = 0; |
| CallArgs args = CallArgsFromVp(argc, vp); |
| args.rval().setInt32(++accessed); |
| return true; |
| } |
| |
| #ifdef JS_TRACE_LOGGING |
| static bool |
| EnableTraceLogger(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); |
| if (!TraceLoggerEnable(logger, cx)) |
| return false; |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| DisableTraceLogger(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); |
| args.rval().setBoolean(TraceLoggerDisable(logger)); |
| |
| return true; |
| } |
| #endif |
| |
| #ifdef DEBUG |
| static bool |
| DumpObject(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject obj(cx, ToObject(cx, args.get(0))); |
| if (!obj) |
| return false; |
| |
| DumpObject(obj); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| #endif |
| |
| #ifdef NIGHTLY_BUILD |
| static bool |
| ObjectAddress(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 1) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| if (!args[0].isObject()) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Expected object"); |
| return false; |
| } |
| |
| #ifdef JS_MORE_DETERMINISTIC |
| args.rval().setInt32(0); |
| #else |
| void* ptr = js::UncheckedUnwrap(&args[0].toObject(), true); |
| char buffer[64]; |
| JS_snprintf(buffer, sizeof(buffer), "%p", ptr); |
| |
| JSString* str = JS_NewStringCopyZ(cx, buffer); |
| if (!str) |
| return false; |
| |
| args.rval().setString(str); |
| #endif |
| |
| return true; |
| } |
| |
| static bool |
| SharedAddress(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 1) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Wrong number of arguments"); |
| return false; |
| } |
| if (!args[0].isObject()) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Expected object"); |
| return false; |
| } |
| |
| #ifdef JS_MORE_DETERMINISTIC |
| args.rval().setString(cx->staticStrings().getUint(0)); |
| #else |
| RootedObject obj(cx, CheckedUnwrap(&args[0].toObject())); |
| if (!obj) { |
| JS_ReportError(cx, "Permission denied to access object"); |
| return false; |
| } |
| if (!obj->is<SharedArrayBufferObject>()) { |
| JS_ReportError(cx, "Argument must be a SharedArrayBuffer"); |
| return false; |
| } |
| char buffer[64]; |
| uint32_t nchar = |
| JS_snprintf(buffer, sizeof(buffer), "%p", |
| obj->as<SharedArrayBufferObject>().dataPointerShared().unwrap(/*safeish*/)); |
| |
| JSString* str = JS_NewStringCopyN(cx, buffer, nchar); |
| if (!str) |
| return false; |
| |
| args.rval().setString(str); |
| #endif |
| |
| return true; |
| } |
| #endif |
| |
| static bool |
| DumpBacktrace(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| DumpBacktrace(cx); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| GetBacktrace(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| bool showArgs = false; |
| bool showLocals = false; |
| bool showThisProps = false; |
| |
| if (args.length() > 1) { |
| RootedObject callee(cx, &args.callee()); |
| ReportUsageError(cx, callee, "Too many arguments"); |
| return false; |
| } |
| |
| if (args.length() == 1) { |
| RootedObject cfg(cx, ToObject(cx, args[0])); |
| if (!cfg) |
| return false; |
| RootedValue v(cx); |
| |
| if (!JS_GetProperty(cx, cfg, "args", &v)) |
| return false; |
| showArgs = ToBoolean(v); |
| |
| if (!JS_GetProperty(cx, cfg, "locals", &v)) |
| return false; |
| showLocals = ToBoolean(v); |
| |
| if (!JS_GetProperty(cx, cfg, "thisprops", &v)) |
| return false; |
| showThisProps = ToBoolean(v); |
| } |
| |
| char* buf = JS::FormatStackDump(cx, nullptr, showArgs, showLocals, showThisProps); |
| if (!buf) |
| return false; |
| |
| RootedString str(cx); |
| if (!(str = JS_NewStringCopyZ(cx, buf))) |
| return false; |
| JS_smprintf_free(buf); |
| |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static bool |
| ReportOutOfMemory(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| JS_ReportOutOfMemory(cx); |
| cx->clearPendingException(); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| ReportLargeAllocationFailure(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| void* buf = cx->runtime()->onOutOfMemoryCanGC(AllocFunction::Malloc, JSRuntime::LARGE_ALLOCATION); |
| js_free(buf); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| namespace heaptools { |
| |
| typedef UniquePtr<char16_t[], JS::FreePolicy> EdgeName; |
| |
| // An edge to a node from its predecessor in a path through the graph. |
| class BackEdge { |
| // The node from which this edge starts. |
| JS::ubi::Node predecessor_; |
| |
| // The name of this edge. |
| EdgeName name_; |
| |
| public: |
| BackEdge() : name_(nullptr) { } |
| // Construct an initialized back edge, taking ownership of |name|. |
| BackEdge(JS::ubi::Node predecessor, EdgeName name) |
| : predecessor_(predecessor), name_(Move(name)) { } |
| BackEdge(BackEdge&& rhs) : predecessor_(rhs.predecessor_), name_(Move(rhs.name_)) { } |
| BackEdge& operator=(BackEdge&& rhs) { |
| MOZ_ASSERT(&rhs != this); |
| this->~BackEdge(); |
| new(this) BackEdge(Move(rhs)); |
| return *this; |
| } |
| |
| EdgeName forgetName() { return Move(name_); } |
| JS::ubi::Node predecessor() const { return predecessor_; } |
| |
| private: |
| // No copy constructor or copying assignment. |
| BackEdge(const BackEdge&) = delete; |
| BackEdge& operator=(const BackEdge&) = delete; |
| }; |
| |
| // A path-finding handler class for use with JS::ubi::BreadthFirst. |
| struct FindPathHandler { |
| typedef BackEdge NodeData; |
| typedef JS::ubi::BreadthFirst<FindPathHandler> Traversal; |
| |
| FindPathHandler(JSContext*cx, JS::ubi::Node start, JS::ubi::Node target, |
| AutoValueVector& nodes, Vector<EdgeName>& edges) |
| : cx(cx), start(start), target(target), foundPath(false), |
| nodes(nodes), edges(edges) { } |
| |
| bool |
| operator()(Traversal& traversal, JS::ubi::Node origin, const JS::ubi::Edge& edge, |
| BackEdge* backEdge, bool first) |
| { |
| // We take care of each node the first time we visit it, so there's |
| // nothing to be done on subsequent visits. |
| if (!first) |
| return true; |
| |
| // Record how we reached this node. This is the last edge on a |
| // shortest path to this node. |
| EdgeName edgeName = DuplicateString(cx, edge.name.get()); |
| if (!edgeName) |
| return false; |
| *backEdge = mozilla::Move(BackEdge(origin, Move(edgeName))); |
| |
| // Have we reached our final target node? |
| if (edge.referent == target) { |
| // Record the path that got us here, which must be a shortest path. |
| if (!recordPath(traversal)) |
| return false; |
| foundPath = true; |
| traversal.stop(); |
| } |
| |
| return true; |
| } |
| |
| // We've found a path to our target. Walk the backlinks to produce the |
| // (reversed) path, saving the path in |nodes| and |edges|. |nodes| is |
| // rooted, so it can hold the path's nodes as we leave the scope of |
| // the AutoCheckCannotGC. |
| bool recordPath(Traversal& traversal) { |
| JS::ubi::Node here = target; |
| |
| do { |
| Traversal::NodeMap::Ptr p = traversal.visited.lookup(here); |
| MOZ_ASSERT(p); |
| JS::ubi::Node predecessor = p->value().predecessor(); |
| if (!nodes.append(predecessor.exposeToJS()) || |
| !edges.append(p->value().forgetName())) |
| return false; |
| here = predecessor; |
| } while (here != start); |
| |
| return true; |
| } |
| |
| JSContext* cx; |
| |
| // The node we're starting from. |
| JS::ubi::Node start; |
| |
| // The node we're looking for. |
| JS::ubi::Node target; |
| |
| // True if we found a path to target, false if we didn't. |
| bool foundPath; |
| |
| // The nodes and edges of the path --- should we find one. The path is |
| // stored in reverse order, because that's how it's easiest for us to |
| // construct it: |
| // - edges[i] is the name of the edge from nodes[i] to nodes[i-1]. |
| // - edges[0] is the name of the edge from nodes[0] to the target. |
| // - The last node, nodes[n-1], is the start node. |
| AutoValueVector& nodes; |
| Vector<EdgeName>& edges; |
| }; |
| |
| } // namespace heaptools |
| |
| static bool |
| FindPath(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (argc < 2) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED, |
| "findPath", "1", ""); |
| return false; |
| } |
| |
| // We don't ToString non-objects given as 'start' or 'target', because this |
| // test is all about object identity, and ToString doesn't preserve that. |
| // Non-GCThing endpoints don't make much sense. |
| if (!args[0].isObject() && !args[0].isString() && !args[0].isSymbol()) { |
| ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, |
| JSDVG_SEARCH_STACK, args[0], nullptr, |
| "not an object, string, or symbol", NULL); |
| return false; |
| } |
| |
| if (!args[1].isObject() && !args[1].isString() && !args[1].isSymbol()) { |
| ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE, |
| JSDVG_SEARCH_STACK, args[0], nullptr, |
| "not an object, string, or symbol", NULL); |
| return false; |
| } |
| |
| AutoValueVector nodes(cx); |
| Vector<heaptools::EdgeName> edges(cx); |
| |
| { |
| // We can't tolerate the GC moving things around while we're searching |
| // the heap. Check that nothing we do causes a GC. |
| JS::AutoCheckCannotGC autoCannotGC; |
| |
| JS::ubi::Node start(args[0]), target(args[1]); |
| |
| heaptools::FindPathHandler handler(cx, start, target, nodes, edges); |
| heaptools::FindPathHandler::Traversal traversal(cx->runtime(), handler, autoCannotGC); |
| if (!traversal.init() || !traversal.addStart(start)) |
| return false; |
| |
| if (!traversal.traverse()) |
| return false; |
| |
| if (!handler.foundPath) { |
| // We didn't find any paths from the start to the target. |
| args.rval().setUndefined(); |
| return true; |
| } |
| } |
| |
| // |nodes| and |edges| contain the path from |start| to |target|, reversed. |
| // Construct a JavaScript array describing the path from the start to the |
| // target. Each element has the form: |
| // |
| // { |
| // node: <object or string or symbol>, |
| // edge: <string describing outgoing edge from node> |
| // } |
| // |
| // or, if the node is some internal thing that isn't a proper JavaScript |
| // value: |
| // |
| // { node: undefined, edge: <string> } |
| size_t length = nodes.length(); |
| RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length)); |
| if (!result) |
| return false; |
| result->ensureDenseInitializedLength(cx, 0, length); |
| |
| // Walk |nodes| and |edges| in the stored order, and construct the result |
| // array in start-to-target order. |
| for (size_t i = 0; i < length; i++) { |
| // Build an object describing the node and edge. |
| RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx)); |
| if (!obj) |
| return false; |
| |
| if (!JS_DefineProperty(cx, obj, "node", nodes[i], |
| JSPROP_ENUMERATE, nullptr, nullptr)) |
| return false; |
| |
| heaptools::EdgeName edgeName = Move(edges[i]); |
| |
| RootedString edgeStr(cx, NewString<CanGC>(cx, edgeName.get(), js_strlen(edgeName.get()))); |
| if (!edgeStr) |
| return false; |
| edgeName.release(); // edgeStr acquired ownership |
| |
| if (!JS_DefineProperty(cx, obj, "edge", edgeStr, JSPROP_ENUMERATE, nullptr, nullptr)) |
| return false; |
| |
| result->setDenseElement(length - i - 1, ObjectValue(*obj)); |
| } |
| |
| args.rval().setObject(*result); |
| return true; |
| } |
| |
| static bool |
| EvalReturningScope(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.requireAtLeast(cx, "evalReturningScope", 1)) |
| return false; |
| |
| RootedString str(cx, ToString(cx, args[0])); |
| if (!str) |
| return false; |
| |
| RootedObject global(cx); |
| if (args.hasDefined(1)) { |
| global = ToObject(cx, args[1]); |
| if (!global) |
| return false; |
| } |
| |
| AutoStableStringChars strChars(cx); |
| if (!strChars.initTwoByte(cx, str)) |
| return false; |
| |
| mozilla::Range<const char16_t> chars = strChars.twoByteRange(); |
| size_t srclen = chars.length(); |
| const char16_t* src = chars.start().get(); |
| |
| JS::AutoFilename filename; |
| unsigned lineno; |
| |
| DescribeScriptedCaller(cx, &filename, &lineno); |
| |
| JS::CompileOptions options(cx); |
| options.setFileAndLine(filename.get(), lineno); |
| options.setNoScriptRval(true); |
| |
| JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership); |
| RootedScript script(cx); |
| if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) |
| return false; |
| |
| if (global) { |
| global = CheckedUnwrap(global); |
| if (!global) { |
| JS_ReportError(cx, "Permission denied to access global"); |
| return false; |
| } |
| if (!global->is<GlobalObject>()) { |
| JS_ReportError(cx, "Argument must be a global object"); |
| return false; |
| } |
| } else { |
| global = JS::CurrentGlobalOrNull(cx); |
| } |
| |
| RootedObject varObj(cx); |
| RootedObject lexicalScope(cx); |
| |
| { |
| // If we're switching globals here, ExecuteInGlobalAndReturnScope will |
| // take care of cloning the script into that compartment before |
| // executing it. |
| AutoCompartment ac(cx, global); |
| |
| if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &lexicalScope)) |
| return false; |
| |
| varObj = lexicalScope->enclosingScope(); |
| } |
| |
| RootedObject rv(cx, JS_NewPlainObject(cx)); |
| if (!rv) |
| return false; |
| |
| RootedValue varObjVal(cx, ObjectValue(*varObj)); |
| if (!cx->compartment()->wrap(cx, &varObjVal)) |
| return false; |
| if (!JS_SetProperty(cx, rv, "vars", varObjVal)) |
| return false; |
| |
| RootedValue lexicalScopeVal(cx, ObjectValue(*lexicalScope)); |
| if (!cx->compartment()->wrap(cx, &lexicalScopeVal)) |
| return false; |
| if (!JS_SetProperty(cx, rv, "lexicals", lexicalScopeVal)) |
| return false; |
| |
| args.rval().setObject(*rv); |
| return true; |
| } |
| |
| static bool |
| ShellCloneAndExecuteScript(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.requireAtLeast(cx, "cloneAndExecuteScript", 2)) |
| return false; |
| |
| RootedString str(cx, ToString(cx, args[0])); |
| if (!str) |
| return false; |
| |
| RootedObject global(cx, ToObject(cx, args[1])); |
| if (!global) |
| return false; |
| |
| AutoStableStringChars strChars(cx); |
| if (!strChars.initTwoByte(cx, str)) |
| return false; |
| |
| mozilla::Range<const char16_t> chars = strChars.twoByteRange(); |
| size_t srclen = chars.length(); |
| const char16_t* src = chars.start().get(); |
| |
| JS::AutoFilename filename; |
| unsigned lineno; |
| |
| DescribeScriptedCaller(cx, &filename, &lineno); |
| |
| JS::CompileOptions options(cx); |
| options.setFileAndLine(filename.get(), lineno); |
| options.setNoScriptRval(true); |
| |
| JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership); |
| RootedScript script(cx); |
| if (!JS::Compile(cx, options, srcBuf, &script)) |
| return false; |
| |
| global = CheckedUnwrap(global); |
| if (!global) { |
| JS_ReportError(cx, "Permission denied to access global"); |
| return false; |
| } |
| if (!global->is<GlobalObject>()) { |
| JS_ReportError(cx, "Argument must be a global object"); |
| return false; |
| } |
| |
| AutoCompartment ac(cx, global); |
| |
| if (!JS::CloneAndExecuteScript(cx, script)) |
| return false; |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| IsSimdAvailable(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| #ifdef JS_CODEGEN_NONE |
| bool available = false; |
| #else |
| bool available = cx->jitSupportsSimd(); |
| #endif |
| args.rval().set(BooleanValue(available)); |
| return true; |
| } |
| |
| static bool |
| ByteSize(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| mozilla::MallocSizeOf mallocSizeOf = cx->runtime()->debuggerMallocSizeOf; |
| |
| { |
| // We can't tolerate the GC moving things around while we're using a |
| // ubi::Node. Check that nothing we do causes a GC. |
| JS::AutoCheckCannotGC autoCannotGC; |
| |
| JS::ubi::Node node = args.get(0); |
| if (node) |
| args.rval().setNumber(uint32_t(node.size(mallocSizeOf))); |
| else |
| args.rval().setUndefined(); |
| } |
| return true; |
| } |
| |
| static bool |
| ByteSizeOfScript(JSContext*cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.requireAtLeast(cx, "byteSizeOfScript", 1)) |
| return false; |
| if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) { |
| JS_ReportError(cx, "Argument must be a Function object"); |
| return false; |
| } |
| |
| JSFunction* fun = &args[0].toObject().as<JSFunction>(); |
| if (fun->isNative()) { |
| JS_ReportError(cx, "Argument must be a scripted function"); |
| return false; |
| } |
| |
| RootedScript script(cx, fun->getOrCreateScript(cx)); |
| if (!script) |
| return false; |
| |
| mozilla::MallocSizeOf mallocSizeOf = cx->runtime()->debuggerMallocSizeOf; |
| |
| { |
| // We can't tolerate the GC moving things around while we're using a |
| // ubi::Node. Check that nothing we do causes a GC. |
| JS::AutoCheckCannotGC autoCannotGC; |
| |
| JS::ubi::Node node = script; |
| if (node) |
| args.rval().setNumber(uint32_t(node.size(mallocSizeOf))); |
| else |
| args.rval().setUndefined(); |
| } |
| return true; |
| } |
| |
| static bool |
| ImmutablePrototypesEnabled(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| args.rval().setBoolean(JS_ImmutablePrototypesEnabled()); |
| return true; |
| } |
| |
| static bool |
| SetImmutablePrototype(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.get(0).isObject()) { |
| JS_ReportError(cx, "setImmutablePrototype: object expected"); |
| return false; |
| } |
| |
| RootedObject obj(cx, &args[0].toObject()); |
| |
| bool succeeded; |
| if (!js::SetImmutablePrototype(cx, obj, &succeeded)) |
| return false; |
| |
| args.rval().setBoolean(succeeded); |
| return true; |
| } |
| |
| #ifdef DEBUG |
| static bool |
| DumpStringRepresentation(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| RootedString str(cx, ToString(cx, args.get(0))); |
| if (!str) |
| return false; |
| |
| str->dumpRepresentation(stderr, 0); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| #endif |
| |
| static bool |
| SetLazyParsingDisabled(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| bool disable = !args.hasDefined(0) || ToBoolean(args[0]); |
| JS::CompartmentOptionsRef(cx->compartment()).setDisableLazyParsing(disable); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| SetDiscardSource(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| bool discard = !args.hasDefined(0) || ToBoolean(args[0]); |
| JS::CompartmentOptionsRef(cx->compartment()).setDiscardSource(discard); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| GetConstructorName(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.requireAtLeast(cx, "getConstructorName", 1)) |
| return false; |
| |
| if (!args[0].isObject()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, |
| "getConstructorName", "Object", |
| InformalValueTypeName(args[0])); |
| return false; |
| } |
| |
| RootedAtom name(cx); |
| if (!args[0].toObject().constructorDisplayAtom(cx, &name)) |
| return false; |
| |
| if (name) { |
| args.rval().setString(name); |
| } else { |
| args.rval().setNull(); |
| } |
| return true; |
| } |
| |
| static bool |
| AllocationMarker(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| bool allocateInsideNursery = true; |
| if (args.length() > 0 && args[0].isObject()) { |
| RootedObject options(cx, &args[0].toObject()); |
| |
| RootedValue nurseryVal(cx); |
| if (!JS_GetProperty(cx, options, "nursery", &nurseryVal)) |
| return false; |
| allocateInsideNursery = ToBoolean(nurseryVal); |
| } |
| |
| static const Class cls = { "AllocationMarker" }; |
| |
| auto newKind = allocateInsideNursery ? GenericObject : TenuredObject; |
| RootedObject obj(cx, NewObjectWithGivenProto(cx, &cls, nullptr, newKind)); |
| if (!obj) |
| return false; |
| |
| args.rval().setObject(*obj); |
| return true; |
| } |
| |
| namespace gcCallback { |
| |
| struct MajorGC { |
| int32_t depth; |
| int32_t phases; |
| }; |
| |
| static void |
| majorGC(JSRuntime* rt, JSGCStatus status, void* data) |
| { |
| auto info = static_cast<MajorGC*>(data); |
| if (!(info->phases & (1 << status))) |
| return; |
| |
| if (info->depth > 0) { |
| info->depth--; |
| JS::PrepareForFullGC(rt); |
| JS::GCForReason(rt, GC_NORMAL, JS::gcreason::API); |
| info->depth++; |
| } |
| } |
| |
| struct MinorGC { |
| int32_t phases; |
| bool active; |
| }; |
| |
| static void |
| minorGC(JSRuntime* rt, JSGCStatus status, void* data) |
| { |
| auto info = static_cast<MinorGC*>(data); |
| if (!(info->phases & (1 << status))) |
| return; |
| |
| if (info->active) { |
| info->active = false; |
| rt->gc.evictNursery(JS::gcreason::DEBUG_GC); |
| info->active = true; |
| } |
| } |
| |
| // Process global, should really be runtime-local. Also, the final one of these |
| // is currently leaked, since they are only deleted when changing. |
| MajorGC* prevMajorGC = nullptr; |
| MinorGC* prevMinorGC = nullptr; |
| |
| } /* namespace gcCallback */ |
| |
| static bool |
| SetGCCallback(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 1) { |
| JS_ReportError(cx, "Wrong number of arguments"); |
| return false; |
| } |
| |
| RootedObject opts(cx, ToObject(cx, args[0])); |
| if (!opts) |
| return false; |
| |
| RootedValue v(cx); |
| if (!JS_GetProperty(cx, opts, "action", &v)) |
| return false; |
| |
| JSString* str = JS::ToString(cx, v); |
| if (!str) |
| return false; |
| JSAutoByteString action(cx, str); |
| if (!action) |
| return false;
|