fune/dom/ipc/jsactor/JSActorManager.cpp
Gabriele Svelto aa43fa218e Bug 1831092 - Use the new pull-based API for all crash annotations and remove the global annotations table r=jgilbert,necko-reviewers,media-playback-reviewers,profiler-reviewers,win-reviewers,padenot,handyman,afranchuk,valentin,alwu,sotaro
This changes comes with several different refactorings all rolled into one,
unfotunately I couldn't find a way to pull them apart:
- First of all annotations now can either recorded (that is, we copy the value
  and have the crash reporting code own the copy) or registered. Several
  annotations are changed to use this functionality so that we don't need to
  update them as their value change.
- The code in the exception handler is modified to read the annotations from
  the mozannotation_client crate. This has the unfortunate side-effect that
  we need three different bits of code to serialize them: one for annotations
  read from a child process, one for reading annotations from the main process
  outside of the exception handler and one for reading annotations from the
  main process within the exception handler. As we move to fully
  out-of-process crash reporting the last two methods will go away.
- The mozannotation_client crate now doesn't record annotation types anymore.
  I realized as I was working on this that storing types at runtime has two
  issues: the first one is that buggy code might change the type of an
  annotation (that is record it under two different types at two different
  moments), the second issue is that types might become corrupt during a
  crash, so better enforce them at annotation-writing time. The end result is
  that the mozannotation_* crates now only store byte buffers, track the
  format the data is stored in (null-terminated string, fixed size buffer,
  etc...) but not the type of data each annotation is supposed to contain.
- Which brings us to the next change: concrete types for annotations are now
  enforced when they're written out. If an annotation doesn't match the
  expected type it's skipped. Storing an annotation with the wrong type will
  also trigger an assertion in debug builds.

Differential Revision: https://phabricator.services.mozilla.com/D195248
2024-03-04 10:24:43 +00:00

268 lines
9 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "mozilla/dom/JSActorManager.h"
#include "mozilla/dom/AutoEntryScript.h"
#include "mozilla/dom/JSActorService.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/PWindowGlobal.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/ScopeExit.h"
#include "mozJSModuleLoader.h"
#include "jsapi.h"
#include "js/CallAndConstruct.h" // JS::Construct
#include "js/PropertyAndElement.h" // JS_GetProperty
#include "nsContentUtils.h"
namespace mozilla::dom {
already_AddRefed<JSActor> JSActorManager::GetActor(JSContext* aCx,
const nsACString& aName,
ErrorResult& aRv) {
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
// If our connection has been closed, return an error.
mozilla::ipc::IProtocol* nativeActor = AsNativeActor();
if (!nativeActor->CanSend()) {
aRv.ThrowInvalidStateError(nsPrintfCString(
"Cannot get actor '%s'. Native '%s' actor is destroyed.",
PromiseFlatCString(aName).get(), nativeActor->GetProtocolName()));
return nullptr;
}
// Check if this actor has already been created, and return it if it has.
if (RefPtr<JSActor> actor = mJSActors.Get(aName)) {
return actor.forget();
}
RefPtr<JSActorService> actorSvc = JSActorService::GetSingleton();
if (!actorSvc) {
aRv.ThrowInvalidStateError("JSActorService hasn't been initialized");
return nullptr;
}
// Check if this actor satisfies the requirements of the protocol
// corresponding to `aName`, and get the module which implements it.
RefPtr<JSActorProtocol> protocol =
MatchingJSActorProtocol(actorSvc, aName, aRv);
if (!protocol) {
return nullptr;
}
auto& side = nativeActor->GetSide() == mozilla::ipc::ParentSide
? protocol->Parent()
: protocol->Child();
// Load the module using mozJSModuleLoader.
// If the JSActor uses `loadInDevToolsLoader`, force loading in the DevTools
// specific's loader.
RefPtr loader = protocol->mLoadInDevToolsLoader
? mozJSModuleLoader::GetOrCreateDevToolsLoader(aCx)
: mozJSModuleLoader::Get();
MOZ_ASSERT(loader);
// We're about to construct the actor, so make sure we're in the loader realm
// while importing etc.
JSAutoRealm ar(aCx, loader->GetSharedGlobal());
// If a module URI was provided, use it to construct an instance of the actor.
JS::Rooted<JSObject*> actorObj(aCx);
if (side.mModuleURI || side.mESModuleURI) {
JS::Rooted<JSObject*> exports(aCx);
if (side.mModuleURI) {
// TODO: Remove this once m-c, c-c, and out-of-tree code migrations finish
// (bug 1866732).
JS::Rooted<JSObject*> global(aCx);
aRv = loader->Import(aCx, side.mModuleURI.ref(), &global, &exports);
if (aRv.Failed()) {
return nullptr;
}
} else {
aRv = loader->ImportESModule(aCx, side.mESModuleURI.ref(), &exports);
if (aRv.Failed()) {
return nullptr;
}
}
MOZ_ASSERT(exports, "null exports!");
// Load the specific property from our module.
JS::Rooted<JS::Value> ctor(aCx);
nsAutoCString ctorName(aName);
ctorName.Append(StringFromIPCSide(nativeActor->GetSide()));
if (!JS_GetProperty(aCx, exports, ctorName.get(), &ctor)) {
aRv.NoteJSContextException(aCx);
return nullptr;
}
if (NS_WARN_IF(!ctor.isObject())) {
aRv.ThrowNotFoundError(nsPrintfCString(
"Could not find actor constructor '%s'", ctorName.get()));
return nullptr;
}
// Invoke the constructor loaded from the module.
if (!JS::Construct(aCx, ctor, JS::HandleValueArray::empty(), &actorObj)) {
aRv.NoteJSContextException(aCx);
return nullptr;
}
}
// Initialize our newly-constructed actor, and return it.
RefPtr<JSActor> actor = InitJSActor(actorObj, aName, aRv);
if (aRv.Failed()) {
return nullptr;
}
mJSActors.InsertOrUpdate(aName, RefPtr{actor});
return actor.forget();
}
already_AddRefed<JSActor> JSActorManager::GetExistingActor(
const nsACString& aName) {
if (!AsNativeActor()->CanSend()) {
return nullptr;
}
return mJSActors.Get(aName);
}
#define CHILD_DIAGNOSTIC_ASSERT(test, msg) \
do { \
if (XRE_IsParentProcess()) { \
MOZ_ASSERT(test, msg); \
} else { \
MOZ_DIAGNOSTIC_ASSERT(test, msg); \
} \
} while (0)
void JSActorManager::ReceiveRawMessage(
const JSActorMessageMeta& aMetadata,
Maybe<ipc::StructuredCloneData>&& aData,
Maybe<ipc::StructuredCloneData>&& aStack) {
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
CrashReporter::AutoRecordAnnotation autoActorName(
CrashReporter::Annotation::JSActorName, aMetadata.actorName());
CrashReporter::AutoRecordAnnotation autoMessageName(
CrashReporter::Annotation::JSActorMessage,
NS_LossyConvertUTF16toASCII(aMetadata.messageName()));
// We're going to be running JS. Enter the privileged junk realm so we can set
// up our JS state correctly.
AutoEntryScript aes(xpc::PrivilegedJunkScope(), "JSActor message handler");
JSContext* cx = aes.cx();
// Ensure any errors reported to `error` are set on the scope, so they're
// reported.
ErrorResult error;
auto autoSetException =
MakeScopeExit([&] { Unused << error.MaybeSetPendingException(cx); });
// If an async stack was provided, set up our async stack state.
JS::Rooted<JSObject*> stack(cx);
Maybe<JS::AutoSetAsyncStackForNewCalls> stackSetter;
{
JS::Rooted<JS::Value> stackVal(cx);
if (aStack) {
aStack->Read(cx, &stackVal, error);
if (error.Failed()) {
error.SuppressException();
JS_ClearPendingException(cx);
stackVal.setUndefined();
}
}
if (stackVal.isObject()) {
stack = &stackVal.toObject();
if (!js::IsSavedFrame(stack)) {
CHILD_DIAGNOSTIC_ASSERT(false, "Stack must be a SavedFrame object");
error.ThrowDataError("Actor async stack must be a SavedFrame object");
return;
}
stackSetter.emplace(cx, stack, "JSActor query");
}
}
RefPtr<JSActor> actor = GetActor(cx, aMetadata.actorName(), error);
if (error.Failed()) {
return;
}
JS::Rooted<JS::Value> data(cx);
if (aData) {
aData->Read(cx, &data, error);
// StructuredCloneHolder populates an array of ports for MessageEvent.ports
// which we don't need, but which StructuredCloneHolder's destructor will
// assert on for thread safety reasons (that do not apply in this case) if
// we do not consume the array. It's possible for the Read call above to
// populate this array even in event of an error, so we must consume the
// array before processing the error.
nsTArray<RefPtr<MessagePort>> ports = aData->TakeTransferredPorts();
// Cast to void so that the ports will actually be moved, and then
// discarded.
(void)ports;
if (error.Failed()) {
CHILD_DIAGNOSTIC_ASSERT(CycleCollectedJSRuntime::Get()->OOMReported(),
"Should not receive non-decodable data");
return;
}
}
switch (aMetadata.kind()) {
case JSActorMessageKind::QueryResolve:
case JSActorMessageKind::QueryReject:
actor->ReceiveQueryReply(cx, aMetadata, data, error);
break;
case JSActorMessageKind::Message:
actor->ReceiveMessage(cx, aMetadata, data, error);
break;
case JSActorMessageKind::Query:
actor->ReceiveQuery(cx, aMetadata, data, error);
break;
default:
MOZ_ASSERT_UNREACHABLE();
}
}
void JSActorManager::JSActorWillDestroy() {
for (const auto& entry : mJSActors.Values()) {
entry->StartDestroy();
}
}
void JSActorManager::JSActorDidDestroy() {
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
CrashReporter::AutoRecordAnnotation autoMessageName(
CrashReporter::Annotation::JSActorMessage, "<DidDestroy>"_ns);
// Swap the table with `mJSActors` so that we don't invalidate it while
// iterating.
const nsRefPtrHashtable<nsCStringHashKey, JSActor> actors =
std::move(mJSActors);
for (const auto& entry : actors.Values()) {
CrashReporter::AutoRecordAnnotation autoActorName(
CrashReporter::Annotation::JSActorName, entry->Name());
// Do not risk to run script very late in shutdown
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) {
entry->AfterDestroy();
}
}
}
void JSActorManager::JSActorUnregister(const nsACString& aName) {
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
RefPtr<JSActor> actor;
if (mJSActors.Remove(aName, getter_AddRefs(actor))) {
actor->AfterDestroy();
}
}
} // namespace mozilla::dom