/* -*- 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 "jsapi.h" #include "jscntxt.h" #include "jsfriendapi.h" #include "jsgc.h" #include "jsobj.h" #ifndef JS_MORE_DETERMINISTIC #include "jsprf.h" #endif #include "jswrapper.h" #include "jit/AsmJS.h" #include "jit/AsmJSLink.h" #include "js/StructuredClone.h" #include "vm/ForkJoin.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" #include "vm/ProxyObject.h" #include "jscntxtinlines.h" using namespace js; using namespace JS; using mozilla::ArrayLength; // 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; static bool GetBuildConfiguration(JSContext *cx, unsigned argc, jsval *vp) { RootedObject info(cx, JS_NewObject(cx, nullptr, nullptr, nullptr)); if (!info) return false; RootedValue value(cx); #ifdef JSGC_ROOT_ANALYSIS value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "rooting-analysis", value)) return false; #ifdef JSGC_USE_EXACT_ROOTING value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "exact-rooting", value)) return false; #ifdef DEBUG value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "debug", 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 MOZ_ASAN value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "asan", 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_THREADSAFE value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "threadsafe", 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_TRACE_JSCALLS value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "trace-jscalls-api", value)) return false; #ifdef JSGC_INCREMENTAL value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "incremental-gc", value)) return false; #ifdef JSGC_GENERATIONAL value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "generational-gc", 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_PARALLEL_JS value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "parallelJS", value)) return false; #ifdef ENABLE_BINARYDATA value = BooleanValue(true); #else value = BooleanValue(false); #endif if (!JS_SetProperty(cx, info, "binary-data", value)) return false; *vp = ObjectValue(*info); return true; } static bool GC(JSContext *cx, unsigned argc, jsval *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 (argc == 1) { Value arg = vp[2]; if (arg.isString()) { if (!JS_StringEqualsAscii(cx, arg.toString(), "compartment", &compartment)) return false; } else if (arg.isObject()) { PrepareZoneForGC(UncheckedUnwrap(&arg.toObject())->zone()); compartment = true; } } #ifndef JS_MORE_DETERMINISTIC size_t preBytes = cx->runtime()->gcBytes; #endif if (compartment) PrepareForDebugGC(cx->runtime()); else PrepareForFullGC(cx->runtime()); GCForReason(cx->runtime(), 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()->gcBytes); #endif JSString *str = JS_NewStringCopyZ(cx, buf); if (!str) return false; *vp = STRING_TO_JSVAL(str); return true; } static bool MinorGC(JSContext *cx, unsigned argc, jsval *vp) { #ifdef JSGC_GENERATIONAL CallArgs args = CallArgsFromVp(argc, vp); if (args.get(0) == BooleanValue(true)) cx->runtime()->gcStoreBuffer.setAboutToOverflow(); MinorGC(cx->runtime(), gcreason::API); #endif 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} }; static bool GCParameter(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); JSString *str = JS_ValueToString(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 argument must be maxBytes, " "maxMallocBytes, gcStackpoolLifespan, gcBytes or " "gcNumber"); 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; } uint32_t value; if (!ToUint32(cx, args[1], &value)) { JS_ReportError(cx, "the second argument must be convertable to uint32_t " "with non-zero value"); 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 bool IsProxy(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc != 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()); return true; } static bool InternalConst(JSContext *cx, unsigned argc, jsval *vp) { if (argc != 1) { JS_ReportError(cx, "the function takes exactly one argument"); return false; } JSString *str = JS_ValueToString(cx, vp[2]); if (!str) return false; JSFlatString *flat = JS_FlattenString(cx, str); if (!flat) return false; if (JS_FlatStringEqualsAscii(flat, "MARK_STACK_LENGTH")) { vp[0] = UINT_TO_JSVAL(js::MARK_STACK_LENGTH); } else { JS_ReportError(cx, "unknown const name"); return false; } return true; } static bool GCPreserveCode(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc != 0) { RootedObject callee(cx, &args.callee()); ReportUsageError(cx, callee, "Wrong number of arguments"); return false; } cx->runtime()->alwaysPreserveCode = true; *vp = JSVAL_VOID; 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 (argc != 1) { RootedObject callee(cx, &args.callee()); ReportUsageError(cx, callee, "Wrong number of arguments"); return false; } 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()); } args.rval().setUndefined(); return true; } static bool SelectForGC(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); JSRuntime *rt = cx->runtime(); for (unsigned i = 0; i < args.length(); i++) { if (args[i].isObject()) { if (!rt->gcSelectedForMarking.append(&args[i].toObject())) return false; } } args.rval().setUndefined(); return true; } static bool VerifyPreBarriers(JSContext *cx, unsigned argc, jsval *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, jsval *vp) { if (argc) { RootedObject callee(cx, &JS_CALLEE(cx, vp).toObject()); ReportUsageError(cx, callee, "Too many arguments"); return false; } gc::VerifyBarriers(cx->runtime(), gc::PostBarrierVerifier); *vp = JSVAL_VOID; return true; } static bool GCState(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc != 0) { RootedObject callee(cx, &args.callee()); ReportUsageError(cx, callee, "Too many arguments"); return false; } const char *state; gc::State globalState = cx->runtime()->gcIncrementalState; if (globalState == gc::NO_INCREMENTAL) state = "none"; else if (globalState == gc::MARK) state = "mark"; else if (globalState == gc::SWEEP) state = "sweep"; else MOZ_ASSUME_UNREACHABLE("Unobserveable global GC state"); JSString *str = JS_NewStringCopyZ(cx, state); if (!str) return false; *vp = StringValue(str); return true; } static bool DeterministicGC(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc != 1) { RootedObject callee(cx, &args.callee()); ReportUsageError(cx, callee, "Wrong number of arguments"); return false; } gc::SetDeterministicGC(cx, ToBoolean(vp[2])); *vp = JSVAL_VOID; return true; } #endif /* JS_GC_ZEAL */ 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; } bool limit = true; uint32_t budget = 0; if (args.length() == 1) { if (!ToUint32(cx, args[0], &budget)) return false; } else { limit = false; } GCDebugSlice(cx->runtime(), limit, budget); args.rval().setUndefined(); return true; } static bool ValidateGC(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { RootedObject callee(cx, &args.callee()); ReportUsageError(cx, callee, "Wrong number of arguments"); return false; } gc::SetValidateGC(cx, ToBoolean(args[0])); args.rval().setUndefined(); return true; } static bool FullCompartmentChecks(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { RootedObject callee(cx, &args.callee()); ReportUsageError(cx, callee, "Wrong number of arguments"); return false; } gc::SetFullCompartmentChecks(cx, ToBoolean(args[0])); args.rval().setUndefined(); return true; } static bool NondeterministicGetWeakMapKeys(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc != 1) { RootedObject callee(cx, &args.callee()); ReportUsageError(cx, callee, "Wrong number of arguments"); return false; } if (!args[0].isObject()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "nondeterministicGetWeakMapKeys", "WeakMap", InformalValueTypeName(args[0])); return false; } RootedObject arr(cx); if (!JS_NondeterministicGetWeakMapKeys(cx, &args[0].toObject(), arr.address())) return false; if (!arr) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "nondeterministicGetWeakMapKeys", "WeakMap", args[0].toObject().getClass()->name); return false; } args.rval().setObject(*arr); return true; } struct JSCountHeapNode { void *thing; JSGCTraceKind kind; JSCountHeapNode *next; }; typedef HashSet, SystemAllocPolicy> VisitedSet; typedef struct JSCountHeapTracer { JSTracer base; VisitedSet visited; JSCountHeapNode *traceList; JSCountHeapNode *recycleList; bool ok; } JSCountHeapTracer; static void CountHeapNotify(JSTracer *trc, void **thingp, JSGCTraceKind kind) { JS_ASSERT(trc->callback == CountHeapNotify); JSCountHeapTracer *countTracer = (JSCountHeapTracer *)trc; void *thing = *thingp; if (!countTracer->ok) return; VisitedSet::AddPtr p = countTracer->visited.lookupForAdd(thing); if (p) return; if (!countTracer->visited.add(p, thing)) { countTracer->ok = false; return; } JSCountHeapNode *node = countTracer->recycleList; if (node) { countTracer->recycleList = node->next; } else { node = js_pod_malloc(); if (!node) { countTracer->ok = false; return; } } node->thing = thing; node->kind = kind; node->next = countTracer->traceList; countTracer->traceList = node; } static const struct TraceKindPair { const char *name; int32_t kind; } traceKindNames[] = { { "all", -1 }, { "object", JSTRACE_OBJECT }, { "string", JSTRACE_STRING }, }; static bool CountHeap(JSContext *cx, unsigned argc, jsval *vp) { jsval v; int32_t traceKind; JSString *str; JSCountHeapTracer countTracer; JSCountHeapNode *node; size_t counter; RootedValue startValue(cx, UndefinedValue()); if (argc > 0) { v = JS_ARGV(cx, vp)[0]; if (JSVAL_IS_TRACEABLE(v)) { startValue = v; } else if (!JSVAL_IS_NULL(v)) { JS_ReportError(cx, "the first argument is not null or a heap-allocated " "thing"); return false; } } traceKind = -1; if (argc > 1) { str = JS_ValueToString(cx, JS_ARGV(cx, vp)[1]); if (!str) return false; JSFlatString *flatStr = JS_FlattenString(cx, str); if (!flatStr) return false; for (size_t i = 0; ;) { if (JS_FlatStringEqualsAscii(flatStr, traceKindNames[i].name)) { traceKind = traceKindNames[i].kind; break; } if (++i == ArrayLength(traceKindNames)) { JSAutoByteString bytes(cx, str); if (!!bytes) JS_ReportError(cx, "trace kind name '%s' is unknown", bytes.ptr()); return false; } } } JS_TracerInit(&countTracer.base, JS_GetRuntime(cx), CountHeapNotify); if (!countTracer.visited.init()) { JS_ReportOutOfMemory(cx); return false; } countTracer.ok = true; countTracer.traceList = nullptr; countTracer.recycleList = nullptr; if (startValue.isUndefined()) { JS_TraceRuntime(&countTracer.base); } else { JS_CallValueTracer(&countTracer.base, startValue.address(), "root"); } counter = 0; while ((node = countTracer.traceList) != nullptr) { if (traceKind == -1 || node->kind == traceKind) counter++; countTracer.traceList = node->next; node->next = countTracer.recycleList; countTracer.recycleList = node; JS_TraceChildren(&countTracer.base, node->thing, node->kind); } while ((node = countTracer.recycleList) != nullptr) { countTracer.recycleList = node->next; js_free(node); } if (!countTracer.ok) { JS_ReportOutOfMemory(cx); return false; } *vp = JS_NumberValue((double) counter); return true; } #ifdef DEBUG static bool OOMAfterAllocations(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != 1) { JS_ReportError(cx, "count argument required"); return false; } int32_t count; if (!JS_ValueToInt32(cx, args[0], &count)) return false; if (count <= 0) { JS_ReportError(cx, "count argument must be positive"); return false; } OOM_maxAllocations = OOM_counter + count; return true; } #endif static unsigned finalizeCount = 0; static void finalize_counter_finalize(JSFreeOp *fop, JSObject *obj) { ++finalizeCount; } static const JSClass FinalizeCounterClass = { "FinalizeCounter", JSCLASS_IS_ANONYMOUS, JS_PropertyStub, /* addProperty */ JS_DeletePropertyStub, /* delProperty */ JS_PropertyStub, /* getProperty */ JS_StrictPropertyStub, /* setProperty */ JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, finalize_counter_finalize }; static bool MakeFinalizeObserver(JSContext *cx, unsigned argc, jsval *vp) { RootedObject scope(cx, JS::CurrentGlobalOrNull(cx)); if (!scope) return false; JSObject *obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, nullptr, scope); if (!obj) return false; *vp = OBJECT_TO_JSVAL(obj); return true; } static bool FinalizeCount(JSContext *cx, unsigned argc, jsval *vp) { *vp = INT_TO_JSVAL(finalizeCount); return true; } static bool DumpHeapComplete(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); DumpHeapNurseryBehaviour nurseryBehaviour = js::IgnoreNurseryObjects; FILE *dumpFile = nullptr; unsigned i = 0; if (argc > 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 (argc > i) { Value v = args[i]; if (v.isString()) { 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 != argc) { JS_ReportError(cx, "bad arguments passed to dumpHeapComplete"); return false; } js::DumpHeapComplete(JS_GetRuntime(cx), dumpFile ? dumpFile : stdout, nurseryBehaviour); if (dumpFile) fclose(dumpFile); JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } static bool Terminate(JSContext *cx, unsigned arg, jsval *vp) { JS_ClearPendingException(cx); return false; } static bool EnableSPSProfilingAssertions(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc == 0 || !args[0].isBoolean()) { RootedObject arg(cx, &args.callee()); ReportUsageError(cx, arg, "Must have one boolean argument"); return false; } static ProfileEntry stack[1000]; static uint32_t stack_size = 0; SetRuntimeProfilingStack(cx->runtime(), stack, &stack_size, 1000); cx->runtime()->spsProfiler.enableSlowAssertions(args[0].toBoolean()); cx->runtime()->spsProfiler.enable(true); JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } static bool DisableSPSProfiling(JSContext *cx, unsigned argc, jsval *vp) { if (cx->runtime()->spsProfiler.installed()) cx->runtime()->spsProfiler.enable(false); return true; } static bool EnableOsiPointRegisterChecks(JSContext *, unsigned, jsval *vp) { #if defined(JS_ION) && defined(CHECK_OSIPOINT_REGISTERS) jit::js_IonOptions.checkOsiPointRegisters = true; #endif JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } static bool DisplayName(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc == 0 || !args[0].isObject() || !args[0].toObject().is()) { RootedObject arg(cx, &args.callee()); ReportUsageError(cx, arg, "Must have one function argument"); return false; } JSFunction *fun = &args[0].toObject().as(); JSString *str = fun->displayAtom(); vp->setString(str == nullptr ? cx->runtime()->emptyString : str); return true; } bool js::testingFunc_inParallelSection(JSContext *cx, unsigned argc, jsval *vp) { // If we were actually *in* a parallel section, then this function // would be inlined to TRUE in ion-generated code. JS_ASSERT(!InParallelSection()); JS_SET_RVAL(cx, vp, JSVAL_FALSE); return true; } static const char *ObjectMetadataPropertyName = "__objectMetadataFunction__"; static bool ShellObjectMetadataCallback(JSContext *cx, JSObject **pmetadata) { RootedValue fun(cx); if (!JS_GetProperty(cx, cx->global(), ObjectMetadataPropertyName, &fun)) return false; RootedValue rval(cx); if (!Invoke(cx, UndefinedValue(), fun, 0, nullptr, &rval)) return false; if (rval.isObject()) *pmetadata = &rval.toObject(); return true; } static bool SetObjectMetadataCallback(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setUndefined(); if (argc == 0 || !args[0].isObject() || !args[0].toObject().is()) { if (!JS_DeleteProperty(cx, cx->global(), ObjectMetadataPropertyName)) return false; js::SetObjectMetadataCallback(cx, nullptr); return true; } if (!JS_DefineProperty(cx, cx->global(), ObjectMetadataPropertyName, args[0], nullptr, nullptr, 0)) return false; js::SetObjectMetadataCallback(cx, ShellObjectMetadataCallback); return true; } static bool SetObjectMetadata(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc != 2 || !args[0].isObject() || !args[1].isObject()) { JS_ReportError(cx, "Both arguments must be objects"); return false; } args.rval().setUndefined(); RootedObject obj(cx, &args[0].toObject()); RootedObject metadata(cx, &args[1].toObject()); return SetObjectMetadata(cx, obj, metadata); } static bool GetObjectMetadata(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); if (argc != 1 || !args[0].isObject()) { JS_ReportError(cx, "Argument must be an object"); return false; } args.rval().setObjectOrNull(GetObjectMetadata(&args[0].toObject())); return true; } bool js::testingFunc_bailout(JSContext *cx, unsigned argc, jsval *vp) { // NOP when not in IonMonkey JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } bool js::testingFunc_assertFloat32(JSContext *cx, unsigned argc, jsval *vp) { // NOP when not in IonMonkey JS_SET_RVAL(cx, vp, JSVAL_VOID); return true; } static bool SetJitCompilerOption(JSContext *cx, unsigned argc, jsval *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; JS_SetGlobalJitCompilerOption(cx, opt, uint32_t(number)); args.rval().setBoolean(true); return true; } static bool SetIonAssertGraphCoherency(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); #ifdef JS_ION jit::js_IonOptions.assertGraphConsistency = ToBoolean(args.get(0)); #endif args.rval().setUndefined(); return true; } class CloneBufferObject : public JSObject { 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_), nullptr, nullptr)); if (!obj) return nullptr; obj->setReservedSlot(DATA_SLOT, PrivateValue(nullptr)); obj->setReservedSlot(LENGTH_SLOT, Int32Value(0)); if (!JS_DefineProperties(cx, obj, props_)) return nullptr; return &obj->as(); } static CloneBufferObject *Create(JSContext *cx, JSAutoStructuredCloneBuffer *buffer) { Rooted 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(getReservedSlot(0).toPrivate()); } void setData(uint64_t *aData) { JS_ASSERT(!data()); setReservedSlot(DATA_SLOT, PrivateValue(aData)); } size_t nbytes() const { return getReservedSlot(LENGTH_SLOT).toInt32(); } void setNBytes(size_t nbytes) { JS_ASSERT(nbytes <= UINT32_MAX); setReservedSlot(LENGTH_SLOT, Int32Value(nbytes)); } // Discard an owned clone buffer. void discard() { if (data()) JS_ClearStructuredClone(data(), nbytes()); setReservedSlot(DATA_SLOT, PrivateValue(nullptr)); } static bool setCloneBuffer_impl(JSContext* cx, 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 obj(cx, &args.thisv().toObject().as()); obj->discard(); char *str = JS_EncodeString(cx, args[0].toString()); if (!str) return false; obj->setData(reinterpret_cast(str)); obj->setNBytes(JS_GetStringLength(args[0].toString())); args.rval().setUndefined(); return true; } static bool is(HandleValue v) { return v.isObject() && v.toObject().is(); } static bool setCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } static bool getCloneBuffer_impl(JSContext* cx, CallArgs args) { Rooted obj(cx, &args.thisv().toObject().as()); JS_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(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(cx, args); } static void Finalize(FreeOp *fop, JSObject *obj) { obj->as().discard(); } }; const Class CloneBufferObject::class_ = { "CloneBuffer", JSCLASS_HAS_RESERVED_SLOTS(CloneBufferObject::NUM_SLOTS), JS_PropertyStub, /* addProperty */ JS_DeletePropertyStub, /* delProperty */ JS_PropertyStub, /* getProperty */ JS_StrictPropertyStub, /* setProperty */ JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Finalize, nullptr, /* checkAccess */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ nullptr, /* trace */ JS_NULL_CLASS_EXT, JS_NULL_OBJECT_OPS }; const JSPropertySpec CloneBufferObject::props_[] = { JS_PSGS("clonebuffer", getCloneBuffer, setCloneBuffer, 0), JS_PS_END }; static bool Serialize(JSContext *cx, unsigned argc, jsval *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, jsval *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()) { JS_ReportError(cx, "deserialize requires a clonebuffer"); return false; } Rooted obj(cx, &args[0].toObject().as()); // 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, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx); if (!JS_ValueToObject(cx, args.get(0), &obj)) return false; if (!obj) { JS_ReportError(cx, "neuter must be passed an object"); return false; } void *contents; uint8_t *data; if (!JS_StealArrayBufferContents(cx, obj, &contents, &data)) return false; js_free(contents); return true; } static const JSFunctionSpecWithHelp TestingFunctions[] = { JS_FN_HELP("gc", ::GC, 0, 0, "gc([obj] | 'compartment')", " Run the garbage collector. When obj is given, GC only its compartment.\n" " If 'compartment' is given, GC any compartments that were scheduled for\n" " GC via schedulegc."), JS_FN_HELP("minorgc", ::MinorGC, 0, 0, "minorgc([aboutToOverflow])", " Run a minor collector on the Nursery. When aboutToOverflow is true, marks\n" " the store buffer as about-to-overflow before collecting."), JS_FN_HELP("gcparam", GCParameter, 2, 0, "gcparam(name [, value])", " Wrapper for JS_[GS]etGCParameter. The name is either maxBytes,\n" " maxMallocBytes, gcBytes, gcNumber, or sliceTimeBudget."), JS_FN_HELP("getBuildConfiguration", GetBuildConfiguration, 0, 0, "getBuildConfiguration()", " Return an object describing some of the configuration options SpiderMonkey\n" " was built with."), JS_FN_HELP("countHeap", CountHeap, 0, 0, "countHeap([start[, kind]])", " Count the number of live GC things in the heap or things reachable from\n" " start when it is given and is not null. kind is either 'all' (default) to\n" " count all things or one of 'object', 'double', 'string', 'function'\n" " to count only things of that kind."), #ifdef DEBUG JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 1, 0, "oomAfterAllocations(count)", " After 'count' js_malloc memory allocations, fail every following allocation\n" " (return NULL)."), #endif JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0, "makeFinalizeObserver()", " Get a special object whose finalization increases the counter returned\n" " by the finalizeCount function."), JS_FN_HELP("finalizeCount", FinalizeCount, 0, 0, "finalizeCount()", " Return the current value of the finalization counter that is incremented\n" " each time an object returned by the makeFinalizeObserver is finalized."), JS_FN_HELP("gcPreserveCode", GCPreserveCode, 0, 0, "gcPreserveCode()", " Preserve JIT code during garbage collections."), #ifdef JS_GC_ZEAL JS_FN_HELP("gczeal", GCZeal, 2, 0, "gczeal(level, [period])", " Specifies how zealous the garbage collector should be. Values for level:\n" " 0: Normal amount of collection\n" " 1: Collect when roots are added or removed\n" " 2: Collect when memory is allocated\n" " 3: Collect when the window paints (browser only)\n" " 4: Verify pre write barriers between instructions\n" " 5: Verify pre write barriers between paints\n" " 6: Verify stack rooting\n" " 7: Collect the nursery every N nursery allocations\n" " 8: Incremental GC in two slices: 1) mark roots 2) finish collection\n" " 9: Incremental GC in two slices: 1) mark all 2) new marking and finish\n" " 10: Incremental GC in multiple slices\n" " 11: Verify post write barriers between instructions\n" " 12: Verify post write barriers between paints\n" " 13: Purge analysis state when memory is allocated\n" " Period specifies that collection happens every n allocations.\n"), JS_FN_HELP("schedulegc", ScheduleGC, 1, 0, "schedulegc(num | obj)", " If num is given, schedule a GC after num allocations.\n" " If obj is given, schedule a GC of obj's compartment."), JS_FN_HELP("selectforgc", SelectForGC, 0, 0, "selectforgc(obj1, obj2, ...)", " Schedule the given objects to be marked in the next GC slice."), JS_FN_HELP("verifyprebarriers", VerifyPreBarriers, 0, 0, "verifyprebarriers()", " Start or end a run of the pre-write barrier verifier."), JS_FN_HELP("verifypostbarriers", VerifyPostBarriers, 0, 0, "verifypostbarriers()", " Start or end a run of the post-write barrier verifier."), JS_FN_HELP("gcstate", GCState, 0, 0, "gcstate()", " Report the global GC state."), JS_FN_HELP("deterministicgc", DeterministicGC, 1, 0, "deterministicgc(true|false)", " If true, only allow determinstic GCs to run."), #endif JS_FN_HELP("gcslice", GCSlice, 1, 0, "gcslice(n)", " Run an incremental GC slice that marks about n objects."), JS_FN_HELP("validategc", ValidateGC, 1, 0, "validategc(true|false)", " If true, a separate validation step is performed after an incremental GC."), JS_FN_HELP("fullcompartmentchecks", FullCompartmentChecks, 1, 0, "fullcompartmentchecks(true|false)", " If true, check for compartment mismatches before every GC."), JS_FN_HELP("nondeterministicGetWeakMapKeys", NondeterministicGetWeakMapKeys, 1, 0, "nondeterministicGetWeakMapKeys(weakmap)", " Return an array of the keys in the given WeakMap."), JS_FN_HELP("internalConst", InternalConst, 1, 0, "internalConst(name)", " Query an internal constant for the engine. See InternalConst source for\n" " the list of constant names."), JS_FN_HELP("isProxy", IsProxy, 1, 0, "isProxy(obj)", " If true, obj is a proxy of some sort"), JS_FN_HELP("dumpHeapComplete", DumpHeapComplete, 1, 0, "dumpHeapComplete(['collectNurseryBeforeDump'], [filename])", " Dump reachable and unreachable objects to the named file, or to stdout. If\n" " 'collectNurseryBeforeDump' is specified, a minor GC is performed first,\n" " otherwise objects in the nursery are ignored."), JS_FN_HELP("terminate", Terminate, 0, 0, "terminate()", " Terminate JavaScript execution, as if we had run out of\n" " memory or been terminated by the slow script dialog."), JS_FN_HELP("enableSPSProfilingAssertions", EnableSPSProfilingAssertions, 1, 0, "enableSPSProfilingAssertions(slow)", " Enables SPS instrumentation and corresponding assertions. If 'slow' is\n" " true, then even slower assertions are enabled for all generated JIT code.\n" " When 'slow' is false, then instrumentation is enabled, but the slow\n" " assertions are disabled."), JS_FN_HELP("disableSPSProfiling", DisableSPSProfiling, 1, 0, "disableSPSProfiling()", " Disables SPS instrumentation"), JS_FN_HELP("enableOsiPointRegisterChecks", EnableOsiPointRegisterChecks, 0, 0, "enableOsiPointRegisterChecks()", "Emit extra code to verify live regs at the start of a VM call are not\n" "modified before its OsiPoint."), JS_FN_HELP("displayName", DisplayName, 1, 0, "displayName(fn)", " Gets the display name for a function, which can possibly be a guessed or\n" " inferred name based on where the function was defined. This can be\n" " different from the 'name' property on the function."), JS_FN_HELP("isAsmJSCompilationAvailable", IsAsmJSCompilationAvailable, 0, 0, "isAsmJSCompilationAvailable", " Returns whether asm.js compilation is currently available or whether it is disabled\n" " (e.g., by the debugger)."), JS_FN_HELP("isAsmJSModule", IsAsmJSModule, 1, 0, "isAsmJSModule(fn)", " Returns whether the given value is a function containing \"use asm\" that has been\n" " validated according to the asm.js spec."), JS_FN_HELP("isAsmJSModuleLoadedFromCache", IsAsmJSModuleLoadedFromCache, 1, 0, "isAsmJSModule(fn)", " Return whether the given asm.js module function has been loaded directly\n" " from the cache. This function throws an error if fn is not a validated asm.js\n" " module."), JS_FN_HELP("isAsmJSFunction", IsAsmJSFunction, 1, 0, "isAsmJSFunction(fn)", " Returns whether the given value is a nested function in an asm.js module that has been\n" " both compile- and link-time validated."), JS_FN_HELP("inParallelSection", testingFunc_inParallelSection, 0, 0, "inParallelSection()", " True if this code is executing within a parallel section."), JS_FN_HELP("setObjectMetadataCallback", SetObjectMetadataCallback, 1, 0, "setObjectMetadataCallback(fn)", " Specify function to supply metadata for all newly created objects."), JS_FN_HELP("setObjectMetadata", SetObjectMetadata, 2, 0, "setObjectMetadata(obj, metadataObj)", " Change the metadata for an object."), JS_FN_HELP("getObjectMetadata", GetObjectMetadata, 1, 0, "getObjectMetadata(obj)", " Get the metadata for an object."), JS_FN_HELP("bailout", testingFunc_bailout, 0, 0, "bailout()", " Force a bailout out of ionmonkey (if running in ionmonkey)."), JS_FN_HELP("setJitCompilerOption", SetJitCompilerOption, 2, 0, "setCompilerOption(