summaryrefslogtreecommitdiffstats
path: root/js/src/debugger/Object.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/debugger/Object.cpp
parentInitial commit. (diff)
downloadfirefox-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.cpp2741
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);
+}