/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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 "debugger/Object-inl.h" #include "mozilla/Maybe.h" // for Maybe, Nothing, Some #include "mozilla/Range.h" // for Range #include "mozilla/Result.h" // for Result #include "mozilla/Vector.h" // for Vector #include #include // for size_t, strlen #include // for remove_reference<>::type #include // for move #include "jsapi.h" // for CallArgs, RootedObject, Rooted #include "builtin/Array.h" // for NewDenseCopiedArray #include "builtin/Promise.h" // for PromiseReactionRecordBuilder #include "debugger/Debugger.h" // for Completion, Debugger #include "debugger/Frame.h" // for DebuggerFrame #include "debugger/NoExecute.h" // for LeaveDebuggeeNoExecute #include "debugger/Script.h" // for DebuggerScript #include "debugger/Source.h" // for DebuggerSource #include "gc/Tracer.h" // for TraceManuallyBarrieredCrossCompartmentEdge #include "js/CompilationAndEvaluation.h" // for Compile #include "js/Conversions.h" // for ToObject #include "js/experimental/JitInfo.h" // for JSJitInfo #include "js/friend/ErrorMessages.h" // for GetErrorMessage, JSMSG_* #include "js/friend/WindowProxy.h" // for IsWindow, IsWindowProxy, ToWindowIfWindowProxy #include "js/HeapAPI.h" // for IsInsideNursery #include "js/Promise.h" // for PromiseState #include "js/PropertyAndElement.h" // for JS_GetProperty #include "js/Proxy.h" // for PropertyDescriptor #include "js/SourceText.h" // for SourceText #include "js/StableStringChars.h" // for AutoStableStringChars #include "js/String.h" // for JS::StringHasLatin1Chars #include "proxy/ScriptedProxyHandler.h" // for ScriptedProxyHandler #include "vm/ArgumentsObject.h" // for ARGS_LENGTH_MAX #include "vm/ArrayObject.h" // for ArrayObject #include "vm/AsyncFunction.h" // for AsyncGeneratorObject #include "vm/AsyncIteration.h" // for AsyncFunctionGeneratorObject #include "vm/BoundFunctionObject.h" // for BoundFunctionObject #include "vm/BytecodeUtil.h" // for JSDVG_SEARCH_STACK #include "vm/Compartment.h" // for Compartment #include "vm/EnvironmentObject.h" // for GetDebugEnvironmentForFunction #include "vm/ErrorObject.h" // for JSObject::is, ErrorObject #include "vm/GeneratorObject.h" // for AbstractGeneratorObject #include "vm/GlobalObject.h" // for JSObject::is, GlobalObject #include "vm/Interpreter.h" // for Call #include "vm/JSAtom.h" // for Atomize #include "vm/JSContext.h" // for JSContext, ReportValueError #include "vm/JSFunction.h" // for JSFunction #include "vm/JSObject.h" // for GenericObject, NewObjectKind #include "vm/JSScript.h" // for JSScript #include "vm/NativeObject.h" // for NativeObject, JSObject::is #include "vm/ObjectOperations.h" // for DefineProperty #include "vm/PlainObject.h" // for js::PlainObject #include "vm/PromiseObject.h" // for js::PromiseObject #include "vm/Realm.h" // for AutoRealm, ErrorCopier, Realm #include "vm/Runtime.h" // for JSAtomState #include "vm/SavedFrame.h" // for SavedFrame #include "vm/Scope.h" // for PositionalFormalParameterIter #include "vm/SelfHosting.h" // for GetClonedSelfHostedFunctionName #include "vm/Shape.h" // for Shape #include "vm/Stack.h" // for InvokeArgs #include "vm/StringType.h" // for JSAtom, PropertyName #include "vm/WellKnownAtom.h" // for js_apply_str #include "vm/WrapperObject.h" // for JSObject::is, WrapperObject #include "gc/StableCellHasher-inl.h" #include "vm/Compartment-inl.h" // for Compartment::wrap #include "vm/JSObject-inl.h" // for GetObjectClassName, InitClass, NewObjectWithGivenProtoAndKind, ToPropertyKey #include "vm/NativeObject-inl.h" // for NativeObject::global #include "vm/ObjectOperations-inl.h" // for DeleteProperty, GetProperty #include "vm/Realm-inl.h" // for AutoRealm::AutoRealm using namespace js; using JS::AutoStableStringChars; using mozilla::Maybe; using mozilla::Nothing; using mozilla::Some; const JSClassOps DebuggerObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve nullptr, // finalize nullptr, // call nullptr, // construct CallTraceMethod, // trace }; const JSClass DebuggerObject::class_ = { "Object", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS), &classOps_}; void DebuggerObject::trace(JSTracer* trc) { // There is a barrier on private pointers, so the Unbarriered marking // is okay. if (JSObject* referent = maybeReferent()) { TraceManuallyBarrieredCrossCompartmentEdge(trc, this, &referent, "Debugger.Object referent"); if (referent != maybeReferent()) { setReservedSlotGCThingAsPrivateUnbarriered(OBJECT_SLOT, referent); } } } static DebuggerObject* DebuggerObject_checkThis(JSContext* cx, const CallArgs& args) { JSObject* thisobj = RequireObject(cx, args.thisv()); if (!thisobj) { return nullptr; } if (!thisobj->is()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, "Debugger.Object", "method", thisobj->getClass()->name); return nullptr; } return &thisobj->as(); } /* static */ bool DebuggerObject::construct(JSContext* cx, unsigned argc, Value* vp) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, "Debugger.Object"); return false; } struct MOZ_STACK_CLASS DebuggerObject::CallData { JSContext* cx; const CallArgs& args; Handle object; RootedObject referent; CallData(JSContext* cx, const CallArgs& args, Handle obj) : cx(cx), args(args), object(obj), referent(cx, obj->referent()) {} // JSNative properties bool callableGetter(); bool isBoundFunctionGetter(); bool isArrowFunctionGetter(); bool isAsyncFunctionGetter(); bool isClassConstructorGetter(); bool isGeneratorFunctionGetter(); bool protoGetter(); bool classGetter(); bool nameGetter(); bool displayNameGetter(); bool parameterNamesGetter(); bool scriptGetter(); bool environmentGetter(); bool boundTargetFunctionGetter(); bool boundThisGetter(); bool boundArgumentsGetter(); bool allocationSiteGetter(); bool isErrorGetter(); bool errorMessageNameGetter(); bool errorNotesGetter(); bool errorLineNumberGetter(); bool errorColumnNumberGetter(); bool isProxyGetter(); bool proxyTargetGetter(); bool proxyHandlerGetter(); bool isPromiseGetter(); bool promiseStateGetter(); bool promiseValueGetter(); bool promiseReasonGetter(); bool promiseLifetimeGetter(); bool promiseTimeToResolutionGetter(); bool promiseAllocationSiteGetter(); bool promiseResolutionSiteGetter(); bool promiseIDGetter(); bool promiseDependentPromisesGetter(); // JSNative methods bool isExtensibleMethod(); bool isSealedMethod(); bool isFrozenMethod(); bool getPropertyMethod(); bool setPropertyMethod(); bool getOwnPropertyNamesMethod(); bool getOwnPropertyNamesLengthMethod(); bool getOwnPropertySymbolsMethod(); bool getOwnPrivatePropertiesMethod(); bool getOwnPropertyDescriptorMethod(); bool preventExtensionsMethod(); bool sealMethod(); bool freezeMethod(); bool definePropertyMethod(); bool definePropertiesMethod(); bool deletePropertyMethod(); bool callMethod(); bool applyMethod(); bool asEnvironmentMethod(); bool forceLexicalInitializationByNameMethod(); bool executeInGlobalMethod(); bool executeInGlobalWithBindingsMethod(); bool createSource(); bool makeDebuggeeValueMethod(); bool makeDebuggeeNativeFunctionMethod(); bool isSameNativeMethod(); bool isNativeGetterWithJitInfo(); bool unsafeDereferenceMethod(); bool unwrapMethod(); bool getPromiseReactionsMethod(); using Method = bool (CallData::*)(); template static bool ToNative(JSContext* cx, unsigned argc, Value* vp); }; template /* static */ bool DebuggerObject::CallData::ToNative(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); Rooted obj(cx, DebuggerObject_checkThis(cx, args)); if (!obj) { return false; } CallData data(cx, args, obj); return (data.*MyMethod)(); } bool DebuggerObject::CallData::callableGetter() { args.rval().setBoolean(object->isCallable()); return true; } bool DebuggerObject::CallData::isBoundFunctionGetter() { args.rval().setBoolean(object->isBoundFunction()); return true; } bool DebuggerObject::CallData::isArrowFunctionGetter() { if (!object->isDebuggeeFunction()) { args.rval().setUndefined(); return true; } args.rval().setBoolean(object->isArrowFunction()); return true; } bool DebuggerObject::CallData::isAsyncFunctionGetter() { if (!object->isDebuggeeFunction()) { args.rval().setUndefined(); return true; } args.rval().setBoolean(object->isAsyncFunction()); return true; } bool DebuggerObject::CallData::isGeneratorFunctionGetter() { if (!object->isDebuggeeFunction()) { args.rval().setUndefined(); return true; } args.rval().setBoolean(object->isGeneratorFunction()); return true; } bool DebuggerObject::CallData::isClassConstructorGetter() { if (!object->isDebuggeeFunction()) { args.rval().setUndefined(); return true; } args.rval().setBoolean(object->isClassConstructor()); return true; } bool DebuggerObject::CallData::protoGetter() { Rooted result(cx); if (!DebuggerObject::getPrototypeOf(cx, object, &result)) { return false; } args.rval().setObjectOrNull(result); return true; } bool DebuggerObject::CallData::classGetter() { RootedString result(cx); if (!DebuggerObject::getClassName(cx, object, &result)) { return false; } args.rval().setString(result); return true; } bool DebuggerObject::CallData::nameGetter() { if (!object->isFunction() && !object->isBoundFunction()) { args.rval().setUndefined(); return true; } RootedString result(cx, object->name(cx)); if (result) { args.rval().setString(result); } else { args.rval().setUndefined(); } return true; } bool DebuggerObject::CallData::displayNameGetter() { if (!object->isFunction() && !object->isBoundFunction()) { args.rval().setUndefined(); return true; } RootedString result(cx, object->displayName(cx)); if (result) { args.rval().setString(result); } else { args.rval().setUndefined(); } return true; } bool DebuggerObject::CallData::parameterNamesGetter() { if (!object->isDebuggeeFunction()) { args.rval().setUndefined(); return true; } RootedFunction referent(cx, &object->referent()->as()); ArrayObject* arr = GetFunctionParameterNamesArray(cx, referent); if (!arr) { return false; } args.rval().setObject(*arr); return true; } bool DebuggerObject::CallData::scriptGetter() { Debugger* dbg = object->owner(); if (!referent->is()) { args.rval().setUndefined(); return true; } RootedFunction fun(cx, &referent->as()); if (!IsInterpretedNonSelfHostedFunction(fun)) { args.rval().setUndefined(); return true; } RootedScript script(cx, GetOrCreateFunctionScript(cx, fun)); if (!script) { return false; } // Only hand out debuggee scripts. if (!dbg->observesScript(script)) { args.rval().setNull(); return true; } Rooted scriptObject(cx, dbg->wrapScript(cx, script)); if (!scriptObject) { return false; } args.rval().setObject(*scriptObject); return true; } bool DebuggerObject::CallData::environmentGetter() { Debugger* dbg = object->owner(); // Don't bother switching compartments just to check obj's type and get its // env. if (!referent->is()) { args.rval().setUndefined(); return true; } RootedFunction fun(cx, &referent->as()); if (!IsInterpretedNonSelfHostedFunction(fun)) { args.rval().setUndefined(); return true; } // Only hand out environments of debuggee functions. if (!dbg->observesGlobal(&fun->global())) { args.rval().setNull(); return true; } Rooted env(cx); { AutoRealm ar(cx, fun); env = GetDebugEnvironmentForFunction(cx, fun); if (!env) { return false; } } return dbg->wrapEnvironment(cx, env, args.rval()); } bool DebuggerObject::CallData::boundTargetFunctionGetter() { if (!object->isDebuggeeBoundFunction()) { args.rval().setUndefined(); return true; } Rooted result(cx); if (!DebuggerObject::getBoundTargetFunction(cx, object, &result)) { return false; } args.rval().setObject(*result); return true; } bool DebuggerObject::CallData::boundThisGetter() { if (!object->isDebuggeeBoundFunction()) { args.rval().setUndefined(); return true; } return DebuggerObject::getBoundThis(cx, object, args.rval()); } bool DebuggerObject::CallData::boundArgumentsGetter() { if (!object->isDebuggeeBoundFunction()) { args.rval().setUndefined(); return true; } Rooted result(cx, ValueVector(cx)); if (!DebuggerObject::getBoundArguments(cx, object, &result)) { return false; } RootedObject obj(cx, NewDenseCopiedArray(cx, result.length(), result.begin())); if (!obj) { return false; } args.rval().setObject(*obj); return true; } bool DebuggerObject::CallData::allocationSiteGetter() { RootedObject result(cx); if (!DebuggerObject::getAllocationSite(cx, object, &result)) { return false; } args.rval().setObjectOrNull(result); return true; } // Returns the "name" field (see js/public/friend/ErrorNumbers.msg), which may // be used as a unique identifier, for any error object with a JSErrorReport or // undefined if the object has no JSErrorReport. bool DebuggerObject::CallData::errorMessageNameGetter() { RootedString result(cx); if (!DebuggerObject::getErrorMessageName(cx, object, &result)) { return false; } if (result) { args.rval().setString(result); } else { args.rval().setUndefined(); } return true; } bool DebuggerObject::CallData::isErrorGetter() { args.rval().setBoolean(object->isError()); return true; } bool DebuggerObject::CallData::errorNotesGetter() { return DebuggerObject::getErrorNotes(cx, object, args.rval()); } bool DebuggerObject::CallData::errorLineNumberGetter() { return DebuggerObject::getErrorLineNumber(cx, object, args.rval()); } bool DebuggerObject::CallData::errorColumnNumberGetter() { return DebuggerObject::getErrorColumnNumber(cx, object, args.rval()); } bool DebuggerObject::CallData::isProxyGetter() { args.rval().setBoolean(object->isScriptedProxy()); return true; } bool DebuggerObject::CallData::proxyTargetGetter() { if (!object->isScriptedProxy()) { args.rval().setUndefined(); return true; } Rooted result(cx); if (!DebuggerObject::getScriptedProxyTarget(cx, object, &result)) { return false; } args.rval().setObjectOrNull(result); return true; } bool DebuggerObject::CallData::proxyHandlerGetter() { if (!object->isScriptedProxy()) { args.rval().setUndefined(); return true; } Rooted result(cx); if (!DebuggerObject::getScriptedProxyHandler(cx, object, &result)) { return false; } args.rval().setObjectOrNull(result); return true; } bool DebuggerObject::CallData::isPromiseGetter() { args.rval().setBoolean(object->isPromise()); return true; } bool DebuggerObject::CallData::promiseStateGetter() { if (!DebuggerObject::requirePromise(cx, object)) { return false; } RootedValue result(cx); switch (object->promiseState()) { case JS::PromiseState::Pending: result.setString(cx->names().pending); break; case JS::PromiseState::Fulfilled: result.setString(cx->names().fulfilled); break; case JS::PromiseState::Rejected: result.setString(cx->names().rejected); break; } args.rval().set(result); return true; } bool DebuggerObject::CallData::promiseValueGetter() { if (!DebuggerObject::requirePromise(cx, object)) { return false; } if (object->promiseState() != JS::PromiseState::Fulfilled) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_PROMISE_NOT_FULFILLED); return false; } return DebuggerObject::getPromiseValue(cx, object, args.rval()); ; } bool DebuggerObject::CallData::promiseReasonGetter() { if (!DebuggerObject::requirePromise(cx, object)) { return false; } if (object->promiseState() != JS::PromiseState::Rejected) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_PROMISE_NOT_REJECTED); return false; } return DebuggerObject::getPromiseReason(cx, object, args.rval()); } bool DebuggerObject::CallData::promiseLifetimeGetter() { if (!DebuggerObject::requirePromise(cx, object)) { return false; } args.rval().setNumber(object->promiseLifetime()); return true; } bool DebuggerObject::CallData::promiseTimeToResolutionGetter() { if (!DebuggerObject::requirePromise(cx, object)) { return false; } if (object->promiseState() == JS::PromiseState::Pending) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_PROMISE_NOT_RESOLVED); return false; } args.rval().setNumber(object->promiseTimeToResolution()); return true; } static PromiseObject* EnsurePromise(JSContext* cx, HandleObject referent) { // We only care about promises, so CheckedUnwrapStatic is OK. RootedObject obj(cx, CheckedUnwrapStatic(referent)); if (!obj) { ReportAccessDenied(cx); return nullptr; } if (!obj->is()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "Debugger", "Promise", obj->getClass()->name); return nullptr; } return &obj->as(); } bool DebuggerObject::CallData::promiseAllocationSiteGetter() { Rooted promise(cx, EnsurePromise(cx, referent)); if (!promise) { return false; } RootedObject allocSite(cx, promise->allocationSite()); if (!allocSite) { args.rval().setNull(); return true; } if (!cx->compartment()->wrap(cx, &allocSite)) { return false; } args.rval().set(ObjectValue(*allocSite)); return true; } bool DebuggerObject::CallData::promiseResolutionSiteGetter() { Rooted promise(cx, EnsurePromise(cx, referent)); if (!promise) { return false; } if (promise->state() == JS::PromiseState::Pending) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_PROMISE_NOT_RESOLVED); return false; } RootedObject resolutionSite(cx, promise->resolutionSite()); if (!resolutionSite) { args.rval().setNull(); return true; } if (!cx->compartment()->wrap(cx, &resolutionSite)) { return false; } args.rval().set(ObjectValue(*resolutionSite)); return true; } bool DebuggerObject::CallData::promiseIDGetter() { Rooted promise(cx, EnsurePromise(cx, referent)); if (!promise) { return false; } args.rval().setNumber(double(promise->getID())); return true; } bool DebuggerObject::CallData::promiseDependentPromisesGetter() { Debugger* dbg = object->owner(); Rooted promise(cx, EnsurePromise(cx, referent)); if (!promise) { return false; } Rooted> values(cx, GCVector(cx)); { JSAutoRealm ar(cx, promise); if (!promise->dependentPromises(cx, &values)) { return false; } } for (size_t i = 0; i < values.length(); i++) { if (!dbg->wrapDebuggeeValue(cx, values[i])) { return false; } } Rooted promises(cx); if (values.length() == 0) { promises = NewDenseEmptyArray(cx); } else { promises = NewDenseCopiedArray(cx, values.length(), values[0].address()); } if (!promises) { return false; } args.rval().setObject(*promises); return true; } bool DebuggerObject::CallData::isExtensibleMethod() { bool result; if (!DebuggerObject::isExtensible(cx, object, result)) { return false; } args.rval().setBoolean(result); return true; } bool DebuggerObject::CallData::isSealedMethod() { bool result; if (!DebuggerObject::isSealed(cx, object, result)) { return false; } args.rval().setBoolean(result); return true; } bool DebuggerObject::CallData::isFrozenMethod() { bool result; if (!DebuggerObject::isFrozen(cx, object, result)) { return false; } args.rval().setBoolean(result); return true; } bool DebuggerObject::CallData::getOwnPropertyNamesMethod() { RootedIdVector ids(cx); if (!DebuggerObject::getOwnPropertyNames(cx, object, &ids)) { return false; } JSObject* obj = IdVectorToArray(cx, ids); if (!obj) { return false; } args.rval().setObject(*obj); return true; } bool DebuggerObject::CallData::getOwnPropertyNamesLengthMethod() { size_t ownPropertiesLength; if (!DebuggerObject::getOwnPropertyNamesLength(cx, object, &ownPropertiesLength)) { return false; } args.rval().setNumber(ownPropertiesLength); return true; } bool DebuggerObject::CallData::getOwnPropertySymbolsMethod() { RootedIdVector ids(cx); if (!DebuggerObject::getOwnPropertySymbols(cx, object, &ids)) { return false; } JSObject* obj = IdVectorToArray(cx, ids); if (!obj) { return false; } args.rval().setObject(*obj); return true; } bool DebuggerObject::CallData::getOwnPrivatePropertiesMethod() { RootedIdVector ids(cx); if (!DebuggerObject::getOwnPrivateProperties(cx, object, &ids)) { return false; } JSObject* obj = IdVectorToArray(cx, ids); if (!obj) { return false; } args.rval().setObject(*obj); return true; } bool DebuggerObject::CallData::getOwnPropertyDescriptorMethod() { RootedId id(cx); if (!ToPropertyKey(cx, args.get(0), &id)) { return false; } Rooted> desc(cx); if (!DebuggerObject::getOwnPropertyDescriptor(cx, object, id, &desc)) { return false; } return JS::FromPropertyDescriptor(cx, desc, args.rval()); } bool DebuggerObject::CallData::preventExtensionsMethod() { if (!DebuggerObject::preventExtensions(cx, object)) { return false; } args.rval().setUndefined(); return true; } bool DebuggerObject::CallData::sealMethod() { if (!DebuggerObject::seal(cx, object)) { return false; } args.rval().setUndefined(); return true; } bool DebuggerObject::CallData::freezeMethod() { if (!DebuggerObject::freeze(cx, object)) { return false; } args.rval().setUndefined(); return true; } bool DebuggerObject::CallData::definePropertyMethod() { if (!args.requireAtLeast(cx, "Debugger.Object.defineProperty", 2)) { return false; } RootedId id(cx); if (!ToPropertyKey(cx, args[0], &id)) { return false; } Rooted desc(cx); if (!ToPropertyDescriptor(cx, args[1], false, &desc)) { return false; } if (!DebuggerObject::defineProperty(cx, object, id, desc)) { return false; } args.rval().setUndefined(); return true; } bool DebuggerObject::CallData::definePropertiesMethod() { if (!args.requireAtLeast(cx, "Debugger.Object.defineProperties", 1)) { return false; } RootedValue arg(cx, args[0]); RootedObject props(cx, ToObject(cx, arg)); if (!props) { return false; } RootedIdVector ids(cx); Rooted descs(cx, PropertyDescriptorVector(cx)); if (!ReadPropertyDescriptors(cx, props, false, &ids, &descs)) { return false; } Rooted ids2(cx, IdVector(cx)); if (!ids2.append(ids.begin(), ids.end())) { return false; } if (!DebuggerObject::defineProperties(cx, object, ids2, descs)) { return false; } args.rval().setUndefined(); return true; } /* * This does a non-strict delete, as a matter of API design. The case where the * property is non-configurable isn't necessarily exceptional here. */ bool DebuggerObject::CallData::deletePropertyMethod() { RootedId id(cx); if (!ToPropertyKey(cx, args.get(0), &id)) { return false; } ObjectOpResult result; if (!DebuggerObject::deleteProperty(cx, object, id, result)) { return false; } args.rval().setBoolean(result.ok()); return true; } bool DebuggerObject::CallData::callMethod() { RootedValue thisv(cx, args.get(0)); Rooted nargs(cx, ValueVector(cx)); if (args.length() >= 2) { if (!nargs.growBy(args.length() - 1)) { return false; } for (size_t i = 1; i < args.length(); ++i) { nargs[i - 1].set(args[i]); } } Rooted> completion( cx, DebuggerObject::call(cx, object, thisv, nargs)); if (!completion.get()) { return false; } return completion->buildCompletionValue(cx, object->owner(), args.rval()); } bool DebuggerObject::CallData::getPropertyMethod() { Debugger* dbg = object->owner(); RootedId id(cx); if (!ToPropertyKey(cx, args.get(0), &id)) { return false; } RootedValue receiver(cx, args.length() < 2 ? ObjectValue(*object) : args.get(1)); Rooted comp(cx); JS_TRY_VAR_OR_RETURN_FALSE(cx, comp, getProperty(cx, object, id, receiver)); return comp.get().buildCompletionValue(cx, dbg, args.rval()); } bool DebuggerObject::CallData::setPropertyMethod() { Debugger* dbg = object->owner(); RootedId id(cx); if (!ToPropertyKey(cx, args.get(0), &id)) { return false; } RootedValue value(cx, args.get(1)); RootedValue receiver(cx, args.length() < 3 ? ObjectValue(*object) : args.get(2)); Rooted comp(cx); JS_TRY_VAR_OR_RETURN_FALSE(cx, comp, setProperty(cx, object, id, value, receiver)); return comp.get().buildCompletionValue(cx, dbg, args.rval()); } bool DebuggerObject::CallData::applyMethod() { RootedValue thisv(cx, args.get(0)); Rooted nargs(cx, ValueVector(cx)); if (args.length() >= 2 && !args[1].isNullOrUndefined()) { if (!args[1].isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_APPLY_ARGS, js_apply_str); return false; } RootedObject argsobj(cx, &args[1].toObject()); uint64_t argc = 0; if (!GetLengthProperty(cx, argsobj, &argc)) { return false; } argc = std::min(argc, uint64_t(ARGS_LENGTH_MAX)); if (!nargs.growBy(argc) || !GetElements(cx, argsobj, argc, nargs.begin())) { return false; } } Rooted> completion( cx, DebuggerObject::call(cx, object, thisv, nargs)); if (!completion.get()) { return false; } return completion->buildCompletionValue(cx, object->owner(), args.rval()); } static void EnterDebuggeeObjectRealm(JSContext* cx, Maybe& ar, JSObject* referent) { // |referent| may be a cross-compartment wrapper and CCWs normally // shouldn't be used with AutoRealm, but here we use an arbitrary realm for // now because we don't really have another option. ar.emplace(cx, referent->maybeCCWRealm()->maybeGlobal()); } static bool RequireGlobalObject(JSContext* cx, HandleValue dbgobj, HandleObject referent) { RootedObject obj(cx, referent); if (!obj->is()) { const char* isWrapper = ""; const char* isWindowProxy = ""; // Help the poor programmer by pointing out wrappers around globals... if (obj->is()) { obj = js::UncheckedUnwrap(obj); isWrapper = "a wrapper around "; } // ... and WindowProxies around Windows. if (IsWindowProxy(obj)) { obj = ToWindowIfWindowProxy(obj); isWindowProxy = "a WindowProxy referring to "; } if (obj->is()) { ReportValueError(cx, JSMSG_DEBUG_WRAPPER_IN_WAY, JSDVG_SEARCH_STACK, dbgobj, nullptr, isWrapper, isWindowProxy); } else { ReportValueError(cx, JSMSG_DEBUG_BAD_REFERENT, JSDVG_SEARCH_STACK, dbgobj, nullptr, "a global object"); } return false; } return true; } bool DebuggerObject::CallData::asEnvironmentMethod() { Debugger* dbg = object->owner(); if (!RequireGlobalObject(cx, args.thisv(), referent)) { return false; } Rooted env(cx); { AutoRealm ar(cx, referent); env = GetDebugEnvironmentForGlobalLexicalEnvironment(cx); if (!env) { return false; } } return dbg->wrapEnvironment(cx, env, args.rval()); } // Lookup a binding on the referent's global scope and change it to undefined // if it is an uninitialized lexical, otherwise do nothing. The method's // JavaScript return value is true _only_ when an uninitialized lexical has been // altered, otherwise it is false. bool DebuggerObject::CallData::forceLexicalInitializationByNameMethod() { if (!args.requireAtLeast( cx, "Debugger.Object.prototype.forceLexicalInitializationByName", 1)) { return false; } if (!DebuggerObject::requireGlobal(cx, object)) { return false; } RootedId id(cx); if (!ValueToIdentifier(cx, args[0], &id)) { return false; } bool result; if (!DebuggerObject::forceLexicalInitializationByName(cx, object, id, result)) { return false; } args.rval().setBoolean(result); return true; } bool DebuggerObject::CallData::executeInGlobalMethod() { if (!args.requireAtLeast(cx, "Debugger.Object.prototype.executeInGlobal", 1)) { return false; } if (!DebuggerObject::requireGlobal(cx, object)) { return false; } AutoStableStringChars stableChars(cx); if (!ValueToStableChars(cx, "Debugger.Object.prototype.executeInGlobal", args[0], stableChars)) { return false; } mozilla::Range chars = stableChars.twoByteRange(); EvalOptions options; if (!ParseEvalOptions(cx, args.get(1), options)) { return false; } Rooted comp(cx); JS_TRY_VAR_OR_RETURN_FALSE( cx, comp, DebuggerObject::executeInGlobal(cx, object, chars, nullptr, options)); return comp.get().buildCompletionValue(cx, object->owner(), args.rval()); } bool DebuggerObject::CallData::executeInGlobalWithBindingsMethod() { if (!args.requireAtLeast( cx, "Debugger.Object.prototype.executeInGlobalWithBindings", 2)) { return false; } if (!DebuggerObject::requireGlobal(cx, object)) { return false; } AutoStableStringChars stableChars(cx); if (!ValueToStableChars( cx, "Debugger.Object.prototype.executeInGlobalWithBindings", args[0], stableChars)) { return false; } mozilla::Range chars = stableChars.twoByteRange(); RootedObject bindings(cx, RequireObject(cx, args[1])); if (!bindings) { return false; } EvalOptions options; if (!ParseEvalOptions(cx, args.get(2), options)) { return false; } Rooted comp(cx); JS_TRY_VAR_OR_RETURN_FALSE( cx, comp, DebuggerObject::executeInGlobal(cx, object, chars, bindings, options)); return comp.get().buildCompletionValue(cx, object->owner(), args.rval()); } // Copy a narrow or wide string to a vector, appending a null terminator. template static bool CopyStringToVector(JSContext* cx, JSString* str, Vector& chars) { JSLinearString* linear = str->ensureLinear(cx); if (!linear) { return false; } if (!chars.appendN(0, linear->length() + 1)) { return false; } CopyChars(chars.begin(), *linear); return true; } bool DebuggerObject::CallData::createSource() { if (!args.requireAtLeast(cx, "Debugger.Object.prototype.createSource", 1)) { return false; } if (!DebuggerObject::requireGlobal(cx, object)) { return false; } Debugger* dbg = object->owner(); if (!dbg->isDebuggeeUnbarriered(referent->as().realm())) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGEE, "Debugger.Object", "global"); return false; } RootedObject options(cx, ToObject(cx, args[0])); if (!options) { return false; } RootedValue v(cx); if (!JS_GetProperty(cx, options, "text", &v)) { return false; } RootedString text(cx, ToString(cx, v)); if (!text) { return false; } if (!JS_GetProperty(cx, options, "url", &v)) { return false; } RootedString url(cx, ToString(cx, v)); if (!url) { return false; } if (!JS_GetProperty(cx, options, "startLine", &v)) { return false; } uint32_t startLine; if (!ToUint32(cx, v, &startLine)) { return false; } if (!JS_GetProperty(cx, options, "startColumn", &v)) { return false; } uint32_t startColumn; if (!ToUint32(cx, v, &startColumn)) { return false; } if (!JS_GetProperty(cx, options, "sourceMapURL", &v)) { return false; } RootedString sourceMapURL(cx); if (!v.isUndefined()) { sourceMapURL = ToString(cx, v); if (!sourceMapURL) { return false; } } if (!JS_GetProperty(cx, options, "isScriptElement", &v)) { return false; } bool isScriptElement = ToBoolean(v); JS::CompileOptions compileOptions(cx); compileOptions.lineno = startLine; compileOptions.column = startColumn; if (!JS::StringHasLatin1Chars(url)) { JS_ReportErrorASCII(cx, "URL must be a narrow string"); return false; } Vector urlChars(cx); if (!CopyStringToVector(cx, url, urlChars)) { return false; } compileOptions.setFile((const char*)urlChars.begin()); Vector sourceMapURLChars(cx); if (sourceMapURL) { if (!CopyStringToVector(cx, sourceMapURL, sourceMapURLChars)) { return false; } compileOptions.setSourceMapURL(sourceMapURLChars.begin()); } if (isScriptElement) { // The introduction type must be a statically allocated string. compileOptions.setIntroductionType("inlineScript"); } AutoStableStringChars linearChars(cx); if (!linearChars.initTwoByte(cx, text)) { return false; } JS::SourceText srcBuf; if (!srcBuf.initMaybeBorrowed(cx, linearChars)) { return false; } RootedScript script(cx); { AutoRealm ar(cx, referent); script = JS::Compile(cx, compileOptions, srcBuf); if (!script) { return false; } } Rooted sso(cx, script->sourceObject()); RootedObject wrapped(cx, dbg->wrapSource(cx, sso)); if (!wrapped) { return false; } args.rval().setObject(*wrapped); return true; } bool DebuggerObject::CallData::makeDebuggeeValueMethod() { if (!args.requireAtLeast(cx, "Debugger.Object.prototype.makeDebuggeeValue", 1)) { return false; } return DebuggerObject::makeDebuggeeValue(cx, object, args[0], args.rval()); } bool DebuggerObject::CallData::makeDebuggeeNativeFunctionMethod() { if (!args.requireAtLeast( cx, "Debugger.Object.prototype.makeDebuggeeNativeFunction", 1)) { return false; } return DebuggerObject::makeDebuggeeNativeFunction(cx, object, args[0], args.rval()); } bool DebuggerObject::CallData::isSameNativeMethod() { if (!args.requireAtLeast(cx, "Debugger.Object.prototype.isSameNative", 1)) { return false; } return DebuggerObject::isSameNative(cx, object, args[0], args.rval()); } bool DebuggerObject::CallData::isNativeGetterWithJitInfo() { return DebuggerObject::isNativeGetterWithJitInfo(cx, object, args.rval()); } bool DebuggerObject::CallData::unsafeDereferenceMethod() { RootedObject result(cx); if (!DebuggerObject::unsafeDereference(cx, object, &result)) { return false; } args.rval().setObject(*result); return true; } bool DebuggerObject::CallData::unwrapMethod() { Rooted result(cx); if (!DebuggerObject::unwrap(cx, object, &result)) { return false; } args.rval().setObjectOrNull(result); return true; } struct DebuggerObject::PromiseReactionRecordBuilder : js::PromiseReactionRecordBuilder { Debugger* dbg; Handle records; PromiseReactionRecordBuilder(Debugger* dbg, Handle records) : dbg(dbg), records(records) {} bool then(JSContext* cx, HandleObject resolve, HandleObject reject, HandleObject result) override { Rooted record(cx, NewPlainObject(cx)); if (!record) { return false; } if (!setIfNotNull(cx, record, cx->names().resolve, resolve) || !setIfNotNull(cx, record, cx->names().reject, reject) || !setIfNotNull(cx, record, cx->names().result, result)) { return false; } return push(cx, record); } bool direct(JSContext* cx, Handle unwrappedPromise) override { RootedValue v(cx, ObjectValue(*unwrappedPromise)); return dbg->wrapDebuggeeValue(cx, &v) && push(cx, v); } bool asyncFunction( JSContext* cx, Handle unwrappedGenerator) override { return maybePushGenerator(cx, unwrappedGenerator); } bool asyncGenerator( JSContext* cx, Handle unwrappedGenerator) override { return maybePushGenerator(cx, unwrappedGenerator); } private: bool push(JSContext* cx, HandleObject record) { RootedValue recordVal(cx, ObjectValue(*record)); return push(cx, recordVal); } bool push(JSContext* cx, HandleValue recordVal) { return NewbornArrayPush(cx, records, recordVal); } bool maybePushGenerator(JSContext* cx, Handle unwrappedGenerator) { Rooted frame(cx); if (unwrappedGenerator->isClosed()) { // If the generator is closed, we can't generate a DebuggerFrame for it, // so we ignore it. return true; } return dbg->getFrame(cx, unwrappedGenerator, &frame) && push(cx, frame); } bool setIfNotNull(JSContext* cx, Handle obj, Handle name, HandleObject prop) { if (!prop) { return true; } RootedValue v(cx, ObjectValue(*prop)); if (!dbg->wrapDebuggeeValue(cx, &v) || !DefineDataProperty(cx, obj, name, v)) { return false; } return true; } }; bool DebuggerObject::CallData::getPromiseReactionsMethod() { Debugger* dbg = object->owner(); Rooted unwrappedPromise(cx, EnsurePromise(cx, referent)); if (!unwrappedPromise) { return false; } Rooted holder(cx, NewDenseEmptyArray(cx)); if (!holder) { return false; } PromiseReactionRecordBuilder builder(dbg, holder); if (!unwrappedPromise->forEachReactionRecord(cx, builder)) { return false; } args.rval().setObject(*builder.records); return true; } const JSPropertySpec DebuggerObject::properties_[] = { JS_DEBUG_PSG("callable", callableGetter), JS_DEBUG_PSG("isBoundFunction", isBoundFunctionGetter), JS_DEBUG_PSG("isArrowFunction", isArrowFunctionGetter), JS_DEBUG_PSG("isGeneratorFunction", isGeneratorFunctionGetter), JS_DEBUG_PSG("isAsyncFunction", isAsyncFunctionGetter), JS_DEBUG_PSG("isClassConstructor", isClassConstructorGetter), JS_DEBUG_PSG("proto", protoGetter), JS_DEBUG_PSG("class", classGetter), JS_DEBUG_PSG("name", nameGetter), JS_DEBUG_PSG("displayName", displayNameGetter), JS_DEBUG_PSG("parameterNames", parameterNamesGetter), JS_DEBUG_PSG("script", scriptGetter), JS_DEBUG_PSG("environment", environmentGetter), JS_DEBUG_PSG("boundTargetFunction", boundTargetFunctionGetter), JS_DEBUG_PSG("boundThis", boundThisGetter), JS_DEBUG_PSG("boundArguments", boundArgumentsGetter), JS_DEBUG_PSG("allocationSite", allocationSiteGetter), JS_DEBUG_PSG("isError", isErrorGetter), JS_DEBUG_PSG("errorMessageName", errorMessageNameGetter), JS_DEBUG_PSG("errorNotes", errorNotesGetter), JS_DEBUG_PSG("errorLineNumber", errorLineNumberGetter), JS_DEBUG_PSG("errorColumnNumber", errorColumnNumberGetter), JS_DEBUG_PSG("isProxy", isProxyGetter), JS_DEBUG_PSG("proxyTarget", proxyTargetGetter), JS_DEBUG_PSG("proxyHandler", proxyHandlerGetter), JS_PS_END}; const JSPropertySpec DebuggerObject::promiseProperties_[] = { JS_DEBUG_PSG("isPromise", isPromiseGetter), JS_DEBUG_PSG("promiseState", promiseStateGetter), JS_DEBUG_PSG("promiseValue", promiseValueGetter), JS_DEBUG_PSG("promiseReason", promiseReasonGetter), JS_DEBUG_PSG("promiseLifetime", promiseLifetimeGetter), JS_DEBUG_PSG("promiseTimeToResolution", promiseTimeToResolutionGetter), JS_DEBUG_PSG("promiseAllocationSite", promiseAllocationSiteGetter), JS_DEBUG_PSG("promiseResolutionSite", promiseResolutionSiteGetter), JS_DEBUG_PSG("promiseID", promiseIDGetter), JS_DEBUG_PSG("promiseDependentPromises", promiseDependentPromisesGetter), JS_PS_END}; const JSFunctionSpec DebuggerObject::methods_[] = { JS_DEBUG_FN("isExtensible", isExtensibleMethod, 0), JS_DEBUG_FN("isSealed", isSealedMethod, 0), JS_DEBUG_FN("isFrozen", isFrozenMethod, 0), JS_DEBUG_FN("getProperty", getPropertyMethod, 0), JS_DEBUG_FN("setProperty", setPropertyMethod, 0), JS_DEBUG_FN("getOwnPropertyNames", getOwnPropertyNamesMethod, 0), JS_DEBUG_FN("getOwnPropertyNamesLength", getOwnPropertyNamesLengthMethod, 0), JS_DEBUG_FN("getOwnPropertySymbols", getOwnPropertySymbolsMethod, 0), JS_DEBUG_FN("getOwnPrivateProperties", getOwnPrivatePropertiesMethod, 0), JS_DEBUG_FN("getOwnPropertyDescriptor", getOwnPropertyDescriptorMethod, 1), JS_DEBUG_FN("preventExtensions", preventExtensionsMethod, 0), JS_DEBUG_FN("seal", sealMethod, 0), JS_DEBUG_FN("freeze", freezeMethod, 0), JS_DEBUG_FN("defineProperty", definePropertyMethod, 2), JS_DEBUG_FN("defineProperties", definePropertiesMethod, 1), JS_DEBUG_FN("deleteProperty", deletePropertyMethod, 1), JS_DEBUG_FN("call", callMethod, 0), JS_DEBUG_FN("apply", applyMethod, 0), JS_DEBUG_FN("asEnvironment", asEnvironmentMethod, 0), JS_DEBUG_FN("forceLexicalInitializationByName", forceLexicalInitializationByNameMethod, 1), JS_DEBUG_FN("executeInGlobal", executeInGlobalMethod, 1), JS_DEBUG_FN("executeInGlobalWithBindings", executeInGlobalWithBindingsMethod, 2), JS_DEBUG_FN("createSource", createSource, 1), JS_DEBUG_FN("makeDebuggeeValue", makeDebuggeeValueMethod, 1), JS_DEBUG_FN("makeDebuggeeNativeFunction", makeDebuggeeNativeFunctionMethod, 1), JS_DEBUG_FN("isSameNative", isSameNativeMethod, 1), JS_DEBUG_FN("isNativeGetterWithJitInfo", isNativeGetterWithJitInfo, 1), JS_DEBUG_FN("unsafeDereference", unsafeDereferenceMethod, 0), JS_DEBUG_FN("unwrap", unwrapMethod, 0), JS_DEBUG_FN("getPromiseReactions", getPromiseReactionsMethod, 0), JS_FS_END}; /* static */ NativeObject* DebuggerObject::initClass(JSContext* cx, Handle global, HandleObject debugCtor) { Rooted objectProto( cx, InitClass(cx, debugCtor, nullptr, nullptr, "Object", construct, 0, properties_, methods_, nullptr, nullptr)); if (!objectProto) { return nullptr; } if (!DefinePropertiesAndFunctions(cx, objectProto, promiseProperties_, nullptr)) { return nullptr; } return objectProto; } /* static */ DebuggerObject* DebuggerObject::create(JSContext* cx, HandleObject proto, HandleObject referent, Handle debugger) { DebuggerObject* obj = IsInsideNursery(referent) ? NewObjectWithGivenProto(cx, proto) : NewTenuredObjectWithGivenProto(cx, proto); if (!obj) { return nullptr; } obj->setReservedSlotGCThingAsPrivate(OBJECT_SLOT, referent); obj->setReservedSlot(OWNER_SLOT, ObjectValue(*debugger)); return obj; } bool DebuggerObject::isCallable() const { return referent()->isCallable(); } bool DebuggerObject::isFunction() const { return referent()->is(); } bool DebuggerObject::isDebuggeeFunction() const { return referent()->is() && owner()->observesGlobal(&referent()->as().global()); } bool DebuggerObject::isBoundFunction() const { return referent()->is(); } bool DebuggerObject::isDebuggeeBoundFunction() const { return referent()->is() && owner()->observesGlobal( &referent()->as().global()); } bool DebuggerObject::isArrowFunction() const { MOZ_ASSERT(isDebuggeeFunction()); return referent()->as().isArrow(); } bool DebuggerObject::isAsyncFunction() const { MOZ_ASSERT(isDebuggeeFunction()); return referent()->as().isAsync(); } bool DebuggerObject::isGeneratorFunction() const { MOZ_ASSERT(isDebuggeeFunction()); return referent()->as().isGenerator(); } bool DebuggerObject::isClassConstructor() const { MOZ_ASSERT(isDebuggeeFunction()); return referent()->as().isClassConstructor(); } bool DebuggerObject::isGlobal() const { return referent()->is(); } bool DebuggerObject::isScriptedProxy() const { return js::IsScriptedProxy(referent()); } bool DebuggerObject::isPromise() const { JSObject* referent = this->referent(); if (IsCrossCompartmentWrapper(referent)) { // We only care about promises, so CheckedUnwrapStatic is OK. referent = CheckedUnwrapStatic(referent); if (!referent) { return false; } } return referent->is(); } bool DebuggerObject::isError() const { JSObject* referent = this->referent(); if (IsCrossCompartmentWrapper(referent)) { // We only check for error classes, so CheckedUnwrapStatic is OK. referent = CheckedUnwrapStatic(referent); if (!referent) { return false; } } return referent->is(); } /* static */ bool DebuggerObject::getClassName(JSContext* cx, Handle object, MutableHandleString result) { RootedObject referent(cx, object->referent()); const char* className; { Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); className = GetObjectClassName(cx, referent); } JSAtom* str = Atomize(cx, className, strlen(className)); if (!str) { return false; } result.set(str); return true; } JSAtom* DebuggerObject::name(JSContext* cx) const { if (isFunction()) { JSAtom* atom = referent()->as().explicitName(); if (atom) { cx->markAtom(atom); } return atom; } MOZ_ASSERT(isBoundFunction()); // Bound functions have a configurable `name` data property and currently // don't store the original name. Try a pure lookup to get this name and if // this fails use "bound". Rooted bound(cx, &referent()->as()); JSAtom* atom = nullptr; { Maybe ar; EnterDebuggeeObjectRealm(cx, ar, bound); Value v; bool found; if (GetOwnPropertyPure(cx, bound, NameToId(cx->names().name), &v, &found) && found && v.isString()) { atom = AtomizeString(cx, v.toString()); if (!atom) { return nullptr; } } else { atom = cx->names().bound; } } cx->markAtom(atom); return atom; } JSAtom* DebuggerObject::displayName(JSContext* cx) const { if (isFunction()) { JSAtom* atom = referent()->as().displayAtom(); if (atom) { cx->markAtom(atom); } return atom; } MOZ_ASSERT(isBoundFunction()); return name(cx); } JS::PromiseState DebuggerObject::promiseState() const { return promise()->state(); } double DebuggerObject::promiseLifetime() const { return promise()->lifetime(); } double DebuggerObject::promiseTimeToResolution() const { MOZ_ASSERT(promiseState() != JS::PromiseState::Pending); return promise()->timeToResolution(); } /* static */ bool DebuggerObject::getBoundTargetFunction( JSContext* cx, Handle object, MutableHandle result) { MOZ_ASSERT(object->isBoundFunction()); Rooted referent( cx, &object->referent()->as()); Debugger* dbg = object->owner(); RootedObject target(cx, referent->getTarget()); return dbg->wrapDebuggeeObject(cx, target, result); } /* static */ bool DebuggerObject::getBoundThis(JSContext* cx, Handle object, MutableHandleValue result) { MOZ_ASSERT(object->isBoundFunction()); Rooted referent( cx, &object->referent()->as()); Debugger* dbg = object->owner(); result.set(referent->getBoundThis()); return dbg->wrapDebuggeeValue(cx, result); } /* static */ bool DebuggerObject::getBoundArguments(JSContext* cx, Handle object, MutableHandle result) { MOZ_ASSERT(object->isBoundFunction()); Rooted referent( cx, &object->referent()->as()); Debugger* dbg = object->owner(); size_t length = referent->numBoundArgs(); if (!result.resize(length)) { return false; } for (size_t i = 0; i < length; i++) { result[i].set(referent->getBoundArg(i)); if (!dbg->wrapDebuggeeValue(cx, result[i])) { return false; } } return true; } /* static */ SavedFrame* Debugger::getObjectAllocationSite(JSObject& obj) { JSObject* metadata = GetAllocationMetadata(&obj); if (!metadata) { return nullptr; } MOZ_ASSERT(!metadata->is()); return metadata->is() ? &metadata->as() : nullptr; } /* static */ bool DebuggerObject::getAllocationSite(JSContext* cx, Handle object, MutableHandleObject result) { RootedObject referent(cx, object->referent()); RootedObject allocSite(cx, Debugger::getObjectAllocationSite(*referent)); if (!cx->compartment()->wrap(cx, &allocSite)) { return false; } result.set(allocSite); return true; } /* static */ bool DebuggerObject::getErrorReport(JSContext* cx, HandleObject maybeError, JSErrorReport*& report) { JSObject* obj = maybeError; if (IsCrossCompartmentWrapper(obj)) { /* We only care about Error objects, so CheckedUnwrapStatic is OK. */ obj = CheckedUnwrapStatic(obj); } if (!obj) { ReportAccessDenied(cx); return false; } if (!obj->is()) { report = nullptr; return true; } report = obj->as().getErrorReport(); return true; } /* static */ bool DebuggerObject::getErrorMessageName(JSContext* cx, Handle object, MutableHandleString result) { RootedObject referent(cx, object->referent()); JSErrorReport* report; if (!getErrorReport(cx, referent, report)) { return false; } if (!report || !report->errorMessageName) { result.set(nullptr); return true; } RootedString str(cx, JS_NewStringCopyZ(cx, report->errorMessageName)); if (!str) { return false; } result.set(str); return true; } /* static */ bool DebuggerObject::getErrorNotes(JSContext* cx, Handle object, MutableHandleValue result) { RootedObject referent(cx, object->referent()); JSErrorReport* report; if (!getErrorReport(cx, referent, report)) { return false; } if (!report) { result.setUndefined(); return true; } RootedObject errorNotesArray(cx, CreateErrorNotesArray(cx, report)); if (!errorNotesArray) { return false; } if (!cx->compartment()->wrap(cx, &errorNotesArray)) { return false; } result.setObject(*errorNotesArray); return true; } /* static */ bool DebuggerObject::getErrorLineNumber(JSContext* cx, Handle object, MutableHandleValue result) { RootedObject referent(cx, object->referent()); JSErrorReport* report; if (!getErrorReport(cx, referent, report)) { return false; } if (!report) { result.setUndefined(); return true; } result.setNumber(report->lineno); return true; } /* static */ bool DebuggerObject::getErrorColumnNumber(JSContext* cx, Handle object, MutableHandleValue result) { RootedObject referent(cx, object->referent()); JSErrorReport* report; if (!getErrorReport(cx, referent, report)) { return false; } if (!report) { result.setUndefined(); return true; } result.setNumber(report->column); return true; } /* static */ bool DebuggerObject::getPromiseValue(JSContext* cx, Handle object, MutableHandleValue result) { MOZ_ASSERT(object->promiseState() == JS::PromiseState::Fulfilled); result.set(object->promise()->value()); return object->owner()->wrapDebuggeeValue(cx, result); } /* static */ bool DebuggerObject::getPromiseReason(JSContext* cx, Handle object, MutableHandleValue result) { MOZ_ASSERT(object->promiseState() == JS::PromiseState::Rejected); result.set(object->promise()->reason()); return object->owner()->wrapDebuggeeValue(cx, result); } /* static */ bool DebuggerObject::isExtensible(JSContext* cx, Handle object, bool& result) { RootedObject referent(cx, object->referent()); Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); ErrorCopier ec(ar); return IsExtensible(cx, referent, &result); } /* static */ bool DebuggerObject::isSealed(JSContext* cx, Handle object, bool& result) { RootedObject referent(cx, object->referent()); Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); ErrorCopier ec(ar); return TestIntegrityLevel(cx, referent, IntegrityLevel::Sealed, &result); } /* static */ bool DebuggerObject::isFrozen(JSContext* cx, Handle object, bool& result) { RootedObject referent(cx, object->referent()); Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); ErrorCopier ec(ar); return TestIntegrityLevel(cx, referent, IntegrityLevel::Frozen, &result); } /* static */ bool DebuggerObject::getPrototypeOf(JSContext* cx, Handle object, MutableHandle result) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); RootedObject proto(cx); { Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); if (!GetPrototype(cx, referent, &proto)) { return false; } } return dbg->wrapNullableDebuggeeObject(cx, proto, result); } /* static */ bool DebuggerObject::getOwnPropertyNames(JSContext* cx, Handle object, MutableHandleIdVector result) { MOZ_ASSERT(result.empty()); RootedObject referent(cx, object->referent()); { Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); ErrorCopier ec(ar); if (!GetPropertyKeys(cx, referent, JSITER_OWNONLY | JSITER_HIDDEN, result)) { return false; } } for (size_t i = 0; i < result.length(); i++) { cx->markId(result[i]); } return true; } /* static */ bool DebuggerObject::getOwnPropertyNamesLength(JSContext* cx, Handle object, size_t* result) { RootedObject referent(cx, object->referent()); RootedIdVector ids(cx); { Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); ErrorCopier ec(ar); if (!GetPropertyKeys(cx, referent, JSITER_OWNONLY | JSITER_HIDDEN, &ids)) { return false; } } *result = ids.length(); return true; } static bool GetSymbolPropertyKeys(JSContext* cx, Handle object, JS::MutableHandleIdVector props, bool includePrivate) { RootedObject referent(cx, object->referent()); { Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); ErrorCopier ec(ar); unsigned flags = JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY; if (includePrivate) { flags = flags | JSITER_PRIVATE; } if (!GetPropertyKeys(cx, referent, flags, props)) { return false; } } return true; } /* static */ bool DebuggerObject::getOwnPropertySymbols(JSContext* cx, Handle object, MutableHandleIdVector result) { MOZ_ASSERT(result.empty()); if (!GetSymbolPropertyKeys(cx, object, result, false)) { return false; } for (size_t i = 0; i < result.length(); i++) { cx->markAtom(result[i].toSymbol()); } return true; } /* static */ bool DebuggerObject::getOwnPrivateProperties(JSContext* cx, Handle object, MutableHandleIdVector result) { MOZ_ASSERT(result.empty()); if (!GetSymbolPropertyKeys(cx, object, result, true)) { return false; } result.eraseIf([](PropertyKey key) { if (!key.isPrivateName()) { return true; } // Private *methods* create a Private Brand, a special private name // stamped onto the symbol, to indicate it is possible to execute private // methods from the class on this object. We don't want to return such // items here, so we check if we're dealing with a private property, e.g. // the Symbol description starts with a "#" character. JSAtom* privateDescription = key.toSymbol()->description(); if (privateDescription->length() == 0) { return true; } char16_t firstChar = privateDescription->latin1OrTwoByteChar(0); return firstChar != '#'; }); for (size_t i = 0; i < result.length(); i++) { cx->markAtom(result[i].toSymbol()); } return true; } /* static */ bool DebuggerObject::getOwnPropertyDescriptor( JSContext* cx, Handle object, HandleId id, MutableHandle> desc_) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); // Bug: This can cause the debuggee to run! { Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); cx->markId(id); ErrorCopier ec(ar); if (!GetOwnPropertyDescriptor(cx, referent, id, desc_)) { return false; } } if (desc_.isSome()) { Rooted desc(cx, *desc_); if (desc.hasValue()) { // Rewrap the debuggee values in desc for the debugger. if (!dbg->wrapDebuggeeValue(cx, desc.value())) { return false; } } if (desc.hasGetter()) { RootedValue get(cx, ObjectOrNullValue(desc.getter())); if (!dbg->wrapDebuggeeValue(cx, &get)) { return false; } desc.setGetter(get.toObjectOrNull()); } if (desc.hasSetter()) { RootedValue set(cx, ObjectOrNullValue(desc.setter())); if (!dbg->wrapDebuggeeValue(cx, &set)) { return false; } desc.setSetter(set.toObjectOrNull()); } desc_.set(mozilla::Some(desc.get())); } return true; } /* static */ bool DebuggerObject::preventExtensions(JSContext* cx, Handle object) { RootedObject referent(cx, object->referent()); Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); ErrorCopier ec(ar); return PreventExtensions(cx, referent); } /* static */ bool DebuggerObject::seal(JSContext* cx, Handle object) { RootedObject referent(cx, object->referent()); Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); ErrorCopier ec(ar); return SetIntegrityLevel(cx, referent, IntegrityLevel::Sealed); } /* static */ bool DebuggerObject::freeze(JSContext* cx, Handle object) { RootedObject referent(cx, object->referent()); Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); ErrorCopier ec(ar); return SetIntegrityLevel(cx, referent, IntegrityLevel::Frozen); } /* static */ bool DebuggerObject::defineProperty(JSContext* cx, Handle object, HandleId id, Handle desc_) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); Rooted desc(cx, desc_); if (!dbg->unwrapPropertyDescriptor(cx, referent, &desc)) { return false; } JS_TRY_OR_RETURN_FALSE(cx, CheckPropertyDescriptorAccessors(cx, desc)); Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); if (!cx->compartment()->wrap(cx, &desc)) { return false; } cx->markId(id); ErrorCopier ec(ar); return DefineProperty(cx, referent, id, desc); } /* static */ bool DebuggerObject::defineProperties(JSContext* cx, Handle object, Handle ids, Handle descs_) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); Rooted descs(cx, PropertyDescriptorVector(cx)); if (!descs.append(descs_.begin(), descs_.end())) { return false; } for (size_t i = 0; i < descs.length(); i++) { if (!dbg->unwrapPropertyDescriptor(cx, referent, descs[i])) { return false; } JS_TRY_OR_RETURN_FALSE(cx, CheckPropertyDescriptorAccessors(cx, descs[i])); } Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); for (size_t i = 0; i < descs.length(); i++) { if (!cx->compartment()->wrap(cx, descs[i])) { return false; } cx->markId(ids[i]); } ErrorCopier ec(ar); for (size_t i = 0; i < descs.length(); i++) { if (!DefineProperty(cx, referent, ids[i], descs[i])) { return false; } } return true; } /* static */ bool DebuggerObject::deleteProperty(JSContext* cx, Handle object, HandleId id, ObjectOpResult& result) { RootedObject referent(cx, object->referent()); Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); cx->markId(id); ErrorCopier ec(ar); return DeleteProperty(cx, referent, id, result); } /* static */ Result DebuggerObject::getProperty(JSContext* cx, Handle object, HandleId id, HandleValue receiver_) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); // Unwrap Debugger.Objects. This happens in the debugger's compartment since // that is where any exceptions must be reported. RootedValue receiver(cx, receiver_); if (!dbg->unwrapDebuggeeValue(cx, &receiver)) { return cx->alreadyReportedError(); } // Enter the debuggee compartment and rewrap all input value for that // compartment. (Rewrapping always takes place in the destination // compartment.) Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); if (!cx->compartment()->wrap(cx, &referent) || !cx->compartment()->wrap(cx, &receiver)) { return cx->alreadyReportedError(); } cx->markId(id); LeaveDebuggeeNoExecute nnx(cx); RootedValue result(cx); bool ok = GetProperty(cx, referent, receiver, id, &result); return Completion::fromJSResult(cx, ok, result); } /* static */ Result DebuggerObject::setProperty(JSContext* cx, Handle object, HandleId id, HandleValue value_, HandleValue receiver_) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); // Unwrap Debugger.Objects. This happens in the debugger's compartment since // that is where any exceptions must be reported. RootedValue value(cx, value_); RootedValue receiver(cx, receiver_); if (!dbg->unwrapDebuggeeValue(cx, &value) || !dbg->unwrapDebuggeeValue(cx, &receiver)) { return cx->alreadyReportedError(); } // Enter the debuggee compartment and rewrap all input value for that // compartment. (Rewrapping always takes place in the destination // compartment.) Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); if (!cx->compartment()->wrap(cx, &referent) || !cx->compartment()->wrap(cx, &value) || !cx->compartment()->wrap(cx, &receiver)) { return cx->alreadyReportedError(); } cx->markId(id); LeaveDebuggeeNoExecute nnx(cx); ObjectOpResult opResult; bool ok = SetProperty(cx, referent, id, value, receiver, opResult); return Completion::fromJSResult(cx, ok, BooleanValue(ok && opResult.ok())); } /* static */ Maybe DebuggerObject::call(JSContext* cx, Handle object, HandleValue thisv_, Handle args) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); if (!referent->isCallable()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, "Debugger.Object", "call", referent->getClass()->name); return Nothing(); } RootedValue calleev(cx, ObjectValue(*referent)); // Unwrap Debugger.Objects. This happens in the debugger's compartment since // that is where any exceptions must be reported. RootedValue thisv(cx, thisv_); if (!dbg->unwrapDebuggeeValue(cx, &thisv)) { return Nothing(); } Rooted args2(cx, ValueVector(cx)); if (!args2.append(args.begin(), args.end())) { return Nothing(); } for (size_t i = 0; i < args2.length(); ++i) { if (!dbg->unwrapDebuggeeValue(cx, args2[i])) { return Nothing(); } } // Enter the debuggee compartment and rewrap all input value for that // compartment. (Rewrapping always takes place in the destination // compartment.) Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); if (!cx->compartment()->wrap(cx, &calleev) || !cx->compartment()->wrap(cx, &thisv)) { return Nothing(); } for (size_t i = 0; i < args2.length(); ++i) { if (!cx->compartment()->wrap(cx, args2[i])) { return Nothing(); } } // Note whether we are in an evaluation that might invoke the OnNativeCall // hook, so that the JITs will be disabled. AutoNoteDebuggerEvaluationWithOnNativeCallHook noteEvaluation( cx, dbg->observesNativeCalls() ? dbg : nullptr); // Call the function. LeaveDebuggeeNoExecute nnx(cx); RootedValue result(cx); bool ok; { InvokeArgs invokeArgs(cx); ok = invokeArgs.init(cx, args2.length()); if (ok) { for (size_t i = 0; i < args2.length(); ++i) { invokeArgs[i].set(args2[i]); } ok = js::Call(cx, calleev, thisv, invokeArgs, &result); } } Rooted completion(cx, Completion::fromJSResult(cx, ok, result)); ar.reset(); return Some(std::move(completion.get())); } /* static */ bool DebuggerObject::forceLexicalInitializationByName( JSContext* cx, Handle object, HandleId id, bool& result) { if (!id.isString()) { JS_ReportErrorNumberASCII( cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "Debugger.Object.prototype.forceLexicalInitializationByName", "string", InformalValueTypeName(IdToValue(id))); return false; } MOZ_ASSERT(object->isGlobal()); Rooted referent(cx, &object->referent()->as()); // Shape::search can end up allocating a new BaseShape in Shape::cachify so // we need to be in the right compartment here. Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); RootedObject globalLexical(cx, &referent->lexicalEnvironment()); RootedObject pobj(cx); PropertyResult prop; if (!LookupProperty(cx, globalLexical, id, &pobj, &prop)) { return false; } result = false; if (prop.isFound()) { MOZ_ASSERT(prop.isNativeProperty()); PropertyInfo propInfo = prop.propertyInfo(); Value v = globalLexical->as().getSlot(propInfo.slot()); if (propInfo.isDataProperty() && v.isMagic() && v.whyMagic() == JS_UNINITIALIZED_LEXICAL) { globalLexical->as().setSlot(propInfo.slot(), UndefinedValue()); result = true; } } return true; } /* static */ Result DebuggerObject::executeInGlobal( JSContext* cx, Handle object, mozilla::Range chars, HandleObject bindings, const EvalOptions& options) { MOZ_ASSERT(object->isGlobal()); Rooted referent(cx, &object->referent()->as()); Debugger* dbg = object->owner(); RootedObject globalLexical(cx, &referent->lexicalEnvironment()); return DebuggerGenericEval(cx, chars, bindings, options, dbg, globalLexical, nullptr); } /* static */ bool DebuggerObject::makeDebuggeeValue(JSContext* cx, Handle object, HandleValue value_, MutableHandleValue result) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); RootedValue value(cx, value_); // Non-objects are already debuggee values. if (value.isObject()) { // Enter this Debugger.Object's referent's compartment, and wrap the // argument as appropriate for references from there. { Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); if (!cx->compartment()->wrap(cx, &value)) { return false; } } // Back in the debugger's compartment, produce a new Debugger.Object // instance referring to the wrapped argument. if (!dbg->wrapDebuggeeValue(cx, &value)) { return false; } } result.set(value); return true; } static JSFunction* EnsureNativeFunction(const Value& value, bool allowExtended = true) { if (!value.isObject() || !value.toObject().is()) { return nullptr; } JSFunction* fun = &value.toObject().as(); if (!fun->isNativeFun() || (fun->isExtended() && !allowExtended)) { return nullptr; } return fun; } /* static */ bool DebuggerObject::makeDebuggeeNativeFunction(JSContext* cx, Handle object, HandleValue value, MutableHandleValue result) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); // The logic below doesn't work with extended functions, so do not allow them. RootedFunction fun(cx, EnsureNativeFunction(value, /* allowExtended */ false)); if (!fun) { JS_ReportErrorASCII(cx, "Need native function"); return false; } RootedValue newValue(cx); { Maybe ar; EnterDebuggeeObjectRealm(cx, ar, referent); unsigned nargs = fun->nargs(); Rooted name(cx, fun->displayAtom()); if (name) { cx->markAtom(name); } JSFunction* newFun = NewNativeFunction(cx, fun->native(), nargs, name); if (!newFun) { return false; } newValue.setObject(*newFun); } // Back in the debugger's compartment, produce a new Debugger.Object // instance referring to the wrapped argument. if (!dbg->wrapDebuggeeValue(cx, &newValue)) { return false; } result.set(newValue); return true; } static JSAtom* MaybeGetSelfHostedFunctionName(const Value& v) { if (!v.isObject() || !v.toObject().is()) { return nullptr; } JSFunction* fun = &v.toObject().as(); if (!fun->isSelfHostedBuiltin()) { return nullptr; } return GetClonedSelfHostedFunctionName(fun); } /* static */ bool DebuggerObject::isSameNative(JSContext* cx, Handle object, HandleValue value, MutableHandleValue result) { RootedValue referentValue(cx, ObjectValue(*object->referent())); RootedValue nonCCWValue( cx, value.isObject() ? ObjectValue(*UncheckedUnwrap(&value.toObject())) : value); RootedFunction fun(cx, EnsureNativeFunction(nonCCWValue)); if (!fun) { Rooted selfHostedName(cx, MaybeGetSelfHostedFunctionName(nonCCWValue)); if (!selfHostedName) { JS_ReportErrorASCII(cx, "Need native function"); return false; } result.setBoolean(selfHostedName == MaybeGetSelfHostedFunctionName(referentValue)); return true; } RootedFunction referentFun(cx, EnsureNativeFunction(referentValue)); result.setBoolean(referentFun && referentFun->native() == fun->native()); return true; } static bool IsNativeGetterWithJitInfo(JSFunction* fun) { return fun->isNativeFun() && fun->hasJitInfo() && fun->jitInfo()->type() == JSJitInfo::Getter; } /* static */ bool DebuggerObject::isNativeGetterWithJitInfo(JSContext* cx, Handle object, MutableHandleValue result) { RootedValue referentValue(cx, ObjectValue(*object->referent())); RootedFunction referentFun(cx, EnsureNativeFunction(referentValue)); result.setBoolean(referentFun && IsNativeGetterWithJitInfo(referentFun)); return true; } /* static */ bool DebuggerObject::unsafeDereference(JSContext* cx, Handle object, MutableHandleObject result) { RootedObject referent(cx, object->referent()); if (!cx->compartment()->wrap(cx, &referent)) { return false; } // Wrapping should return the WindowProxy. MOZ_ASSERT(!IsWindow(referent)); result.set(referent); return true; } /* static */ bool DebuggerObject::unwrap(JSContext* cx, Handle object, MutableHandle result) { RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); RootedObject unwrapped(cx, UnwrapOneCheckedStatic(referent)); // Don't allow unwrapping to create a D.O whose referent is in an // invisible-to-Debugger compartment. (If our referent is a *wrapper* to such, // and the wrapper is in a visible compartment, that's fine.) if (unwrapped && unwrapped->compartment()->invisibleToDebugger()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_INVISIBLE_COMPARTMENT); return false; } return dbg->wrapNullableDebuggeeObject(cx, unwrapped, result); } /* static */ bool DebuggerObject::requireGlobal(JSContext* cx, Handle object) { if (!object->isGlobal()) { RootedObject referent(cx, object->referent()); const char* isWrapper = ""; const char* isWindowProxy = ""; // Help the poor programmer by pointing out wrappers around globals... if (referent->is()) { referent = js::UncheckedUnwrap(referent); isWrapper = "a wrapper around "; } // ... and WindowProxies around Windows. if (IsWindowProxy(referent)) { referent = ToWindowIfWindowProxy(referent); isWindowProxy = "a WindowProxy referring to "; } RootedValue dbgobj(cx, ObjectValue(*object)); if (referent->is()) { ReportValueError(cx, JSMSG_DEBUG_WRAPPER_IN_WAY, JSDVG_SEARCH_STACK, dbgobj, nullptr, isWrapper, isWindowProxy); } else { ReportValueError(cx, JSMSG_DEBUG_BAD_REFERENT, JSDVG_SEARCH_STACK, dbgobj, nullptr, "a global object"); } return false; } return true; } /* static */ bool DebuggerObject::requirePromise(JSContext* cx, Handle object) { RootedObject referent(cx, object->referent()); if (IsCrossCompartmentWrapper(referent)) { /* We only care about promises, so CheckedUnwrapStatic is OK. */ referent = CheckedUnwrapStatic(referent); if (!referent) { ReportAccessDenied(cx); return false; } } if (!referent->is()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "Debugger", "Promise", object->getClass()->name); return false; } return true; } /* static */ bool DebuggerObject::getScriptedProxyTarget( JSContext* cx, Handle object, MutableHandle result) { MOZ_ASSERT(object->isScriptedProxy()); RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); RootedObject unwrapped(cx, js::GetProxyTargetObject(referent)); return dbg->wrapNullableDebuggeeObject(cx, unwrapped, result); } /* static */ bool DebuggerObject::getScriptedProxyHandler( JSContext* cx, Handle object, MutableHandle result) { MOZ_ASSERT(object->isScriptedProxy()); RootedObject referent(cx, object->referent()); Debugger* dbg = object->owner(); RootedObject unwrapped(cx, ScriptedProxyHandler::handlerObject(referent)); return dbg->wrapNullableDebuggeeObject(cx, unwrapped, result); }