diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/debugger/Object.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/debugger/Object.cpp')
-rw-r--r-- | js/src/debugger/Object.cpp | 2741 |
1 files changed, 2741 insertions, 0 deletions
diff --git a/js/src/debugger/Object.cpp b/js/src/debugger/Object.cpp new file mode 100644 index 0000000000..c5a4f1f6dc --- /dev/null +++ b/js/src/debugger/Object.cpp @@ -0,0 +1,2741 @@ +/* -*- 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 <algorithm> +#include <string.h> // for size_t, strlen +#include <type_traits> // for remove_reference<>::type +#include <utility> // 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/ColumnNumber.h" // JS::ColumnNumberOneOrigin +#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/JSAtomUtils.h" // for Atomize, AtomizeString +#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/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<DebuggerObject>, // 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<DebuggerObject>()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_INCOMPATIBLE_PROTO, "Debugger.Object", + "method", thisobj->getClass()->name); + return nullptr; + } + + return &thisobj->as<DebuggerObject>(); +} + +/* 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<DebuggerObject*> object; + RootedObject referent; + + CallData(JSContext* cx, const CallArgs& args, Handle<DebuggerObject*> 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 isSameNativeMethod(); + bool isNativeGetterWithJitInfo(); + bool unsafeDereferenceMethod(); + bool unwrapMethod(); + bool getPromiseReactionsMethod(); + + using Method = bool (CallData::*)(); + + template <Method MyMethod> + static bool ToNative(JSContext* cx, unsigned argc, Value* vp); +}; + +template <DebuggerObject::CallData::Method MyMethod> +/* static */ +bool DebuggerObject::CallData::ToNative(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<DebuggerObject*> 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<DebuggerObject*> 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; + } + + JS::Rooted<JSAtom*> result(cx); + if (!object->name(cx, &result)) { + return false; + } + + 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; + } + + JS::Rooted<JSAtom*> result(cx); + if (!object->displayName(cx, &result)) { + return false; + } + 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<JSFunction>()); + + 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<JSFunction>()) { + args.rval().setUndefined(); + return true; + } + + RootedFunction fun(cx, &referent->as<JSFunction>()); + 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<DebuggerScript*> 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<JSFunction>()) { + args.rval().setUndefined(); + return true; + } + + RootedFunction fun(cx, &referent->as<JSFunction>()); + 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*> 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<DebuggerObject*> 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<ValueVector> 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<DebuggerObject*> 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<DebuggerObject*> 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<PromiseObject>()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_NOT_EXPECTED_TYPE, "Debugger", "Promise", + obj->getClass()->name); + return nullptr; + } + return &obj->as<PromiseObject>(); +} + +bool DebuggerObject::CallData::promiseAllocationSiteGetter() { + Rooted<PromiseObject*> 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<PromiseObject*> 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<PromiseObject*> 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<PromiseObject*> promise(cx, EnsurePromise(cx, referent)); + if (!promise) { + return false; + } + + Rooted<GCVector<Value>> values(cx, GCVector<Value>(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<ArrayObject*> 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<Maybe<PropertyDescriptor>> 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<PropertyDescriptor> 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<PropertyDescriptorVector> descs(cx, PropertyDescriptorVector(cx)); + if (!ReadPropertyDescriptors(cx, props, false, &ids, &descs)) { + return false; + } + Rooted<IdVector> 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<ValueVector> 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<Maybe<Completion>> 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<Completion> 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<Completion> 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<ValueVector> nargs(cx, ValueVector(cx)); + if (args.length() >= 2 && !args[1].isNullOrUndefined()) { + if (!args[1].isObject()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_BAD_APPLY_ARGS, "apply"); + 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<Maybe<Completion>> 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<AutoRealm>& 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<GlobalObject>()) { + const char* isWrapper = ""; + const char* isWindowProxy = ""; + + // Help the poor programmer by pointing out wrappers around globals... + if (obj->is<WrapperObject>()) { + 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<GlobalObject>()) { + 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*> 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<const char16_t> chars = stableChars.twoByteRange(); + + EvalOptions options(EvalOptions::EnvKind::Global); + if (!ParseEvalOptions(cx, args.get(1), options)) { + return false; + } + + Rooted<Completion> 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<const char16_t> chars = stableChars.twoByteRange(); + + RootedObject bindings(cx, RequireObject(cx, args[1])); + if (!bindings) { + return false; + } + + EvalOptions options(EvalOptions::EnvKind::GlobalWithExtraOuterBindings); + if (!ParseEvalOptions(cx, args.get(2), options)) { + return false; + } + + Rooted<Completion> 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 <typename T> +static bool CopyStringToVector(JSContext* cx, JSString* str, Vector<T>& 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<GlobalObject>().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<CanGC>(cx, v)); + if (!text) { + return false; + } + + if (!JS_GetProperty(cx, options, "url", &v)) { + return false; + } + + RootedString url(cx, ToString<CanGC>(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 (startColumn == 0) { + startColumn = 1; + } + + if (!JS_GetProperty(cx, options, "sourceMapURL", &v)) { + return false; + } + + RootedString sourceMapURL(cx); + if (!v.isUndefined()) { + sourceMapURL = ToString<CanGC>(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 = JS::ColumnNumberOneOrigin(startColumn); + + if (!JS::StringHasLatin1Chars(url)) { + JS_ReportErrorASCII(cx, "URL must be a narrow string"); + return false; + } + + Vector<Latin1Char> urlChars(cx); + if (!CopyStringToVector(cx, url, urlChars)) { + return false; + } + compileOptions.setFile((const char*)urlChars.begin()); + + Vector<char16_t> 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<char16_t> 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<ScriptSourceObject*> 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::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<DebuggerObject*> result(cx); + if (!DebuggerObject::unwrap(cx, object, &result)) { + return false; + } + + args.rval().setObjectOrNull(result); + return true; +} + +struct DebuggerObject::PromiseReactionRecordBuilder + : js::PromiseReactionRecordBuilder { + Debugger* dbg; + Handle<ArrayObject*> records; + + PromiseReactionRecordBuilder(Debugger* dbg, Handle<ArrayObject*> records) + : dbg(dbg), records(records) {} + + bool then(JSContext* cx, HandleObject resolve, HandleObject reject, + HandleObject result) override { + Rooted<PlainObject*> 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<PromiseObject*> unwrappedPromise) override { + RootedValue v(cx, ObjectValue(*unwrappedPromise)); + return dbg->wrapDebuggeeValue(cx, &v) && push(cx, v); + } + + bool asyncFunction( + JSContext* cx, + Handle<AsyncFunctionGeneratorObject*> unwrappedGenerator) override { + return maybePushGenerator(cx, unwrappedGenerator); + } + + bool asyncGenerator( + JSContext* cx, + Handle<AsyncGeneratorObject*> 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<AbstractGeneratorObject*> unwrappedGenerator) { + Rooted<DebuggerFrame*> 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<PlainObject*> obj, + Handle<PropertyName*> 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<PromiseObject*> unwrappedPromise(cx, EnsurePromise(cx, referent)); + if (!unwrappedPromise) { + return false; + } + + Rooted<ArrayObject*> 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("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<GlobalObject*> global, + HandleObject debugCtor) { + Rooted<NativeObject*> 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<NativeObject*> debugger) { + DebuggerObject* obj = + IsInsideNursery(referent) + ? NewObjectWithGivenProto<DebuggerObject>(cx, proto) + : NewTenuredObjectWithGivenProto<DebuggerObject>(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<JSFunction>(); } + +bool DebuggerObject::isDebuggeeFunction() const { + return referent()->is<JSFunction>() && + owner()->observesGlobal(&referent()->as<JSFunction>().global()); +} + +bool DebuggerObject::isBoundFunction() const { + return referent()->is<BoundFunctionObject>(); +} + +bool DebuggerObject::isDebuggeeBoundFunction() const { + return referent()->is<BoundFunctionObject>() && + owner()->observesGlobal( + &referent()->as<BoundFunctionObject>().global()); +} + +bool DebuggerObject::isArrowFunction() const { + MOZ_ASSERT(isDebuggeeFunction()); + + return referent()->as<JSFunction>().isArrow(); +} + +bool DebuggerObject::isAsyncFunction() const { + MOZ_ASSERT(isDebuggeeFunction()); + + return referent()->as<JSFunction>().isAsync(); +} + +bool DebuggerObject::isGeneratorFunction() const { + MOZ_ASSERT(isDebuggeeFunction()); + + return referent()->as<JSFunction>().isGenerator(); +} + +bool DebuggerObject::isClassConstructor() const { + MOZ_ASSERT(isDebuggeeFunction()); + + return referent()->as<JSFunction>().isClassConstructor(); +} + +bool DebuggerObject::isGlobal() const { return referent()->is<GlobalObject>(); } + +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<PromiseObject>(); +} + +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<ErrorObject>(); +} + +/* static */ +bool DebuggerObject::getClassName(JSContext* cx, Handle<DebuggerObject*> object, + MutableHandleString result) { + RootedObject referent(cx, object->referent()); + + const char* className; + { + Maybe<AutoRealm> 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; +} + +bool DebuggerObject::name(JSContext* cx, + JS::MutableHandle<JSAtom*> result) const { + if (isFunction()) { + JSFunction* fun = &referent()->as<JSFunction>(); + if (!fun->isAccessorWithLazyName()) { + result.set(fun->fullExplicitName()); + if (result) { + cx->markAtom(result); + } + return true; + } + + { + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, fun); + + result.set(fun->getAccessorNameForLazy(cx)); + if (!result) { + return false; + } + } + cx->markAtom(result); + return true; + } + + 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<BoundFunctionObject*> bound(cx, + &referent()->as<BoundFunctionObject>()); + { + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, bound); + + Value v; + bool found; + if (GetOwnPropertyPure(cx, bound, NameToId(cx->names().name), &v, &found) && + found && v.isString()) { + result.set(AtomizeString(cx, v.toString())); + if (!result) { + return false; + } + } else { + result.set(cx->names().bound); + } + } + + cx->markAtom(result); + return true; +} + +bool DebuggerObject::displayName(JSContext* cx, + JS::MutableHandle<JSAtom*> result) const { + if (isFunction()) { + { + JS::Rooted<JSFunction*> fun(cx, &referent()->as<JSFunction>()); + + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, fun); + + if (!fun->getDisplayAtom(cx, result)) { + return false; + } + } + if (result) { + cx->markAtom(result); + } + return true; + } + + MOZ_ASSERT(isBoundFunction()); + return name(cx, result); +} + +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<DebuggerObject*> object, + MutableHandle<DebuggerObject*> result) { + MOZ_ASSERT(object->isBoundFunction()); + + Rooted<BoundFunctionObject*> referent( + cx, &object->referent()->as<BoundFunctionObject>()); + Debugger* dbg = object->owner(); + + RootedObject target(cx, referent->getTarget()); + return dbg->wrapDebuggeeObject(cx, target, result); +} + +/* static */ +bool DebuggerObject::getBoundThis(JSContext* cx, Handle<DebuggerObject*> object, + MutableHandleValue result) { + MOZ_ASSERT(object->isBoundFunction()); + + Rooted<BoundFunctionObject*> referent( + cx, &object->referent()->as<BoundFunctionObject>()); + Debugger* dbg = object->owner(); + + result.set(referent->getBoundThis()); + return dbg->wrapDebuggeeValue(cx, result); +} + +/* static */ +bool DebuggerObject::getBoundArguments(JSContext* cx, + Handle<DebuggerObject*> object, + MutableHandle<ValueVector> result) { + MOZ_ASSERT(object->isBoundFunction()); + + Rooted<BoundFunctionObject*> referent( + cx, &object->referent()->as<BoundFunctionObject>()); + 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<WrapperObject>()); + return metadata->is<SavedFrame>() ? &metadata->as<SavedFrame>() : nullptr; +} + +/* static */ +bool DebuggerObject::getAllocationSite(JSContext* cx, + Handle<DebuggerObject*> 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<ErrorObject>()) { + report = nullptr; + return true; + } + + report = obj->as<ErrorObject>().getErrorReport(); + return true; +} + +/* static */ +bool DebuggerObject::getErrorMessageName(JSContext* cx, + Handle<DebuggerObject*> 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<DebuggerObject*> 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<DebuggerObject*> 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<DebuggerObject*> 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.oneOriginValue()); + return true; +} + +/* static */ +bool DebuggerObject::getPromiseValue(JSContext* cx, + Handle<DebuggerObject*> 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<DebuggerObject*> 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<DebuggerObject*> object, + bool& result) { + RootedObject referent(cx, object->referent()); + + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, referent); + + ErrorCopier ec(ar); + return IsExtensible(cx, referent, &result); +} + +/* static */ +bool DebuggerObject::isSealed(JSContext* cx, Handle<DebuggerObject*> object, + bool& result) { + RootedObject referent(cx, object->referent()); + + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, referent); + + ErrorCopier ec(ar); + return TestIntegrityLevel(cx, referent, IntegrityLevel::Sealed, &result); +} + +/* static */ +bool DebuggerObject::isFrozen(JSContext* cx, Handle<DebuggerObject*> object, + bool& result) { + RootedObject referent(cx, object->referent()); + + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, referent); + + ErrorCopier ec(ar); + return TestIntegrityLevel(cx, referent, IntegrityLevel::Frozen, &result); +} + +/* static */ +bool DebuggerObject::getPrototypeOf(JSContext* cx, + Handle<DebuggerObject*> object, + MutableHandle<DebuggerObject*> result) { + RootedObject referent(cx, object->referent()); + Debugger* dbg = object->owner(); + + RootedObject proto(cx); + { + Maybe<AutoRealm> 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<DebuggerObject*> object, + MutableHandleIdVector result) { + MOZ_ASSERT(result.empty()); + + RootedObject referent(cx, object->referent()); + { + Maybe<AutoRealm> 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<DebuggerObject*> object, + size_t* result) { + RootedObject referent(cx, object->referent()); + + RootedIdVector ids(cx); + { + Maybe<AutoRealm> 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<DebuggerObject*> object, + JS::MutableHandleIdVector props, + bool includePrivate) { + RootedObject referent(cx, object->referent()); + + { + Maybe<AutoRealm> 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<DebuggerObject*> 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<DebuggerObject*> 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<DebuggerObject*> object, HandleId id, + MutableHandle<Maybe<PropertyDescriptor>> desc_) { + RootedObject referent(cx, object->referent()); + Debugger* dbg = object->owner(); + + // Bug: This can cause the debuggee to run! + { + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, referent); + + cx->markId(id); + + ErrorCopier ec(ar); + if (!GetOwnPropertyDescriptor(cx, referent, id, desc_)) { + return false; + } + } + + if (desc_.isSome()) { + Rooted<PropertyDescriptor> 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<DebuggerObject*> object) { + RootedObject referent(cx, object->referent()); + + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, referent); + + ErrorCopier ec(ar); + return PreventExtensions(cx, referent); +} + +/* static */ +bool DebuggerObject::seal(JSContext* cx, Handle<DebuggerObject*> object) { + RootedObject referent(cx, object->referent()); + + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, referent); + + ErrorCopier ec(ar); + return SetIntegrityLevel(cx, referent, IntegrityLevel::Sealed); +} + +/* static */ +bool DebuggerObject::freeze(JSContext* cx, Handle<DebuggerObject*> object) { + RootedObject referent(cx, object->referent()); + + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, referent); + + ErrorCopier ec(ar); + return SetIntegrityLevel(cx, referent, IntegrityLevel::Frozen); +} + +/* static */ +bool DebuggerObject::defineProperty(JSContext* cx, + Handle<DebuggerObject*> object, HandleId id, + Handle<PropertyDescriptor> desc_) { + RootedObject referent(cx, object->referent()); + Debugger* dbg = object->owner(); + + Rooted<PropertyDescriptor> desc(cx, desc_); + if (!dbg->unwrapPropertyDescriptor(cx, referent, &desc)) { + return false; + } + JS_TRY_OR_RETURN_FALSE(cx, CheckPropertyDescriptorAccessors(cx, desc)); + + Maybe<AutoRealm> 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<DebuggerObject*> object, + Handle<IdVector> ids, + Handle<PropertyDescriptorVector> descs_) { + RootedObject referent(cx, object->referent()); + Debugger* dbg = object->owner(); + + Rooted<PropertyDescriptorVector> 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<AutoRealm> 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<DebuggerObject*> object, HandleId id, + ObjectOpResult& result) { + RootedObject referent(cx, object->referent()); + + Maybe<AutoRealm> ar; + EnterDebuggeeObjectRealm(cx, ar, referent); + + cx->markId(id); + + ErrorCopier ec(ar); + return DeleteProperty(cx, referent, id, result); +} + +/* static */ +Result<Completion> DebuggerObject::getProperty(JSContext* cx, + Handle<DebuggerObject*> 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<AutoRealm> 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<Completion> DebuggerObject::setProperty(JSContext* cx, + Handle<DebuggerObject*> 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<AutoRealm> 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<Completion> DebuggerObject::call(JSContext* cx, + Handle<DebuggerObject*> object, + HandleValue thisv_, + Handle<ValueVector> 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<ValueVector> 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<AutoRealm> 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. + Maybe<AutoNoteExclusiveDebuggerOnEval> noteEvaluation; + if (dbg->isExclusiveDebuggerOnEval()) { + noteEvaluation.emplace(cx, dbg); + } + + // 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> completion(cx, Completion::fromJSResult(cx, ok, result)); + ar.reset(); + return Some(std::move(completion.get())); +} + +/* static */ +bool DebuggerObject::forceLexicalInitializationByName( + JSContext* cx, Handle<DebuggerObject*> 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<GlobalObject*> referent(cx, &object->referent()->as<GlobalObject>()); + + // Shape::search can end up allocating a new BaseShape in Shape::cachify so + // we need to be in the right compartment here. + Maybe<AutoRealm> 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<NativeObject>().getSlot(propInfo.slot()); + if (propInfo.isDataProperty() && v.isMagic() && + v.whyMagic() == JS_UNINITIALIZED_LEXICAL) { + globalLexical->as<NativeObject>().setSlot(propInfo.slot(), + UndefinedValue()); + result = true; + } + } + + return true; +} + +/* static */ +Result<Completion> DebuggerObject::executeInGlobal( + JSContext* cx, Handle<DebuggerObject*> object, + mozilla::Range<const char16_t> chars, HandleObject bindings, + const EvalOptions& options) { + MOZ_ASSERT(object->isGlobal()); + + Rooted<GlobalObject*> referent(cx, &object->referent()->as<GlobalObject>()); + 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<DebuggerObject*> 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<AutoRealm> 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) { + if (!value.isObject() || !value.toObject().is<JSFunction>()) { + return nullptr; + } + + JSFunction* fun = &value.toObject().as<JSFunction>(); + if (!fun->isNativeFun()) { + return nullptr; + } + + return fun; +} + +static JSAtom* MaybeGetSelfHostedFunctionName(const Value& v) { + if (!v.isObject() || !v.toObject().is<JSFunction>()) { + return nullptr; + } + + JSFunction* fun = &v.toObject().as<JSFunction>(); + if (!fun->isSelfHostedBuiltin()) { + return nullptr; + } + + return GetClonedSelfHostedFunctionName(fun); +} + +/* static */ +bool DebuggerObject::isSameNative(JSContext* cx, Handle<DebuggerObject*> 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<JSAtom*> 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<DebuggerObject*> 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<DebuggerObject*> 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<DebuggerObject*> object, + MutableHandle<DebuggerObject*> 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<DebuggerObject*> 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<WrapperObject>()) { + 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<GlobalObject>()) { + 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<DebuggerObject*> 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<PromiseObject>()) { + 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<DebuggerObject*> object, + MutableHandle<DebuggerObject*> 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<DebuggerObject*> object, + MutableHandle<DebuggerObject*> 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); +} |