summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/XPCWrappedJSClass.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/xpconnect/src/XPCWrappedJSClass.cpp
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/xpconnect/src/XPCWrappedJSClass.cpp')
-rw-r--r--js/xpconnect/src/XPCWrappedJSClass.cpp1149
1 files changed, 1149 insertions, 0 deletions
diff --git a/js/xpconnect/src/XPCWrappedJSClass.cpp b/js/xpconnect/src/XPCWrappedJSClass.cpp
new file mode 100644
index 0000000000..072d0c06ed
--- /dev/null
+++ b/js/xpconnect/src/XPCWrappedJSClass.cpp
@@ -0,0 +1,1149 @@
+/* -*- 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/. */
+
+/* Sharable code and data for wrapper around JSObjects. */
+
+#include "xpcprivate.h"
+#include "js/Object.h" // JS::GetClass
+#include "js/Printf.h"
+#include "nsArrayEnumerator.h"
+#include "nsINamed.h"
+#include "nsIScriptError.h"
+#include "nsWrapperCache.h"
+#include "AccessCheck.h"
+#include "nsJSUtils.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/MozQueryInterface.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
+using namespace xpc;
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::dom;
+
+bool AutoScriptEvaluate::StartEvaluating(HandleObject scope) {
+ MOZ_ASSERT(!mEvaluated,
+ "AutoScriptEvaluate::Evaluate should only be called once");
+
+ if (!mJSContext) {
+ return true;
+ }
+
+ mEvaluated = true;
+
+ mAutoRealm.emplace(mJSContext, scope);
+
+ // Saving the exception state keeps us from interfering with another script
+ // that may also be running on this context. This occurred first with the
+ // js debugger, as described in
+ // http://bugzilla.mozilla.org/show_bug.cgi?id=88130 but presumably could
+ // show up in any situation where a script calls into a wrapped js component
+ // on the same context, while the context has a nonzero exception state.
+ mState.emplace(mJSContext);
+
+ return true;
+}
+
+AutoScriptEvaluate::~AutoScriptEvaluate() {
+ if (!mJSContext || !mEvaluated) {
+ return;
+ }
+ mState->restore();
+}
+
+// It turns out that some errors may be not worth reporting. So, this
+// function is factored out to manage that.
+bool xpc_IsReportableErrorCode(nsresult code) {
+ if (NS_SUCCEEDED(code)) {
+ return false;
+ }
+
+ switch (code) {
+ // Error codes that we don't want to report as errors...
+ // These generally indicate bad interface design AFAIC.
+ case NS_ERROR_FACTORY_REGISTER_AGAIN:
+ case NS_BASE_STREAM_WOULD_BLOCK:
+ return false;
+ default:
+ return true;
+ }
+}
+
+// A little stack-based RAII class to help management of the XPCJSContext
+// PendingResult.
+class MOZ_STACK_CLASS AutoSavePendingResult {
+ public:
+ explicit AutoSavePendingResult(XPCJSContext* xpccx) : mXPCContext(xpccx) {
+ // Save any existing pending result and reset to NS_OK for this invocation.
+ mSavedResult = xpccx->GetPendingResult();
+ xpccx->SetPendingResult(NS_OK);
+ }
+ ~AutoSavePendingResult() { mXPCContext->SetPendingResult(mSavedResult); }
+
+ private:
+ XPCJSContext* mXPCContext;
+ nsresult mSavedResult;
+};
+
+// static
+const nsXPTInterfaceInfo* nsXPCWrappedJS::GetInterfaceInfo(REFNSIID aIID) {
+ const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID);
+ if (!info || info->IsBuiltinClass()) {
+ return nullptr;
+ }
+
+ return info;
+}
+
+// static
+JSObject* nsXPCWrappedJS::CallQueryInterfaceOnJSObject(JSContext* cx,
+ JSObject* jsobjArg,
+ HandleObject scope,
+ REFNSIID aIID) {
+ js::AssertSameCompartment(scope, jsobjArg);
+
+ RootedObject jsobj(cx, jsobjArg);
+ RootedValue arg(cx);
+ RootedValue retval(cx);
+ RootedObject retObj(cx);
+ RootedValue fun(cx);
+
+ // In bug 503926, we added a security check to make sure that we don't
+ // invoke content QI functions. In the modern world, this is probably
+ // unnecessary, because invoking QI involves passing an IID object to
+ // content, which will fail. But we do a belt-and-suspenders check to
+ // make sure that content can never trigger the rat's nest of code below.
+ // Once we completely turn off XPConnect for the web, this can definitely
+ // go away.
+ if (!AccessCheck::isChrome(jsobj) ||
+ !AccessCheck::isChrome(js::UncheckedUnwrap(jsobj))) {
+ return nullptr;
+ }
+
+ // OK, it looks like we'll be calling into JS code.
+ AutoScriptEvaluate scriptEval(cx);
+
+ // XXX we should install an error reporter that will send reports to
+ // the JS error console service.
+ if (!scriptEval.StartEvaluating(scope)) {
+ return nullptr;
+ }
+
+ // check upfront for the existence of the function property
+ HandleId funid =
+ XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_QUERY_INTERFACE);
+ if (!JS_GetPropertyById(cx, jsobj, funid, &fun) || fun.isPrimitive()) {
+ return nullptr;
+ }
+
+ dom::MozQueryInterface* mozQI = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(MozQueryInterface, &fun, mozQI))) {
+ if (mozQI->QueriesTo(aIID)) {
+ return jsobj.get();
+ }
+ return nullptr;
+ }
+
+ if (!xpc::ID2JSValue(cx, aIID, &arg)) {
+ return nullptr;
+ }
+
+ // Throwing NS_NOINTERFACE is the prescribed way to fail QI from JS. It is
+ // not an exception that is ever worth reporting, but we don't want to eat
+ // all exceptions either.
+
+ bool success =
+ JS_CallFunctionValue(cx, jsobj, fun, HandleValueArray(arg), &retval);
+ if (!success && JS_IsExceptionPending(cx)) {
+ RootedValue jsexception(cx, NullValue());
+
+ if (JS_GetPendingException(cx, &jsexception)) {
+ if (jsexception.isObject()) {
+ // XPConnect may have constructed an object to represent a
+ // C++ QI failure. See if that is the case.
+ JS::Rooted<JSObject*> exceptionObj(cx, &jsexception.toObject());
+ Exception* e = nullptr;
+ UNWRAP_OBJECT(Exception, &exceptionObj, e);
+
+ if (e && e->GetResult() == NS_NOINTERFACE) {
+ JS_ClearPendingException(cx);
+ }
+ } else if (jsexception.isNumber()) {
+ nsresult rv;
+ // JS often throws an nsresult.
+ if (jsexception.isDouble())
+ // Visual Studio 9 doesn't allow casting directly from
+ // a double to an enumeration type, contrary to
+ // 5.2.9(10) of C++11, so add an intermediate cast.
+ rv = (nsresult)(uint32_t)(jsexception.toDouble());
+ else
+ rv = (nsresult)(jsexception.toInt32());
+
+ if (rv == NS_NOINTERFACE) JS_ClearPendingException(cx);
+ }
+ }
+ } else if (!success) {
+ NS_WARNING("QI hook ran OOMed - this is probably a bug!");
+ }
+
+ if (success) success = JS_ValueToObject(cx, retval, &retObj);
+
+ return success ? retObj.get() : nullptr;
+}
+
+/***************************************************************************/
+
+namespace {
+
+class WrappedJSNamed final : public nsINamed {
+ nsCString mName;
+
+ ~WrappedJSNamed() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit WrappedJSNamed(const nsACString& aName) : mName(aName) {}
+
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName = mName;
+ aName.AppendLiteral(":JS");
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(WrappedJSNamed, nsINamed)
+
+nsCString GetFunctionName(JSContext* cx, HandleObject obj) {
+ RootedObject inner(cx, js::UncheckedUnwrap(obj));
+ JSAutoRealm ar(cx, inner);
+
+ RootedFunction fun(cx, JS_GetObjectFunction(inner));
+ if (!fun) {
+ // If the object isn't a function, it's likely that it has a single
+ // function property (for things like nsITimerCallback). In this case,
+ // return the name of that function property.
+
+ Rooted<IdVector> idArray(cx, IdVector(cx));
+ if (!JS_Enumerate(cx, inner, &idArray)) {
+ JS_ClearPendingException(cx);
+ return nsCString("error");
+ }
+
+ if (idArray.length() != 1) {
+ return nsCString("nonfunction");
+ }
+
+ RootedId id(cx, idArray[0]);
+ RootedValue v(cx);
+ if (!JS_GetPropertyById(cx, inner, id, &v)) {
+ JS_ClearPendingException(cx);
+ return nsCString("nonfunction");
+ }
+
+ if (!v.isObject()) {
+ return nsCString("nonfunction");
+ }
+
+ RootedObject vobj(cx, &v.toObject());
+ return GetFunctionName(cx, vobj);
+ }
+
+ RootedString funName(cx, JS_GetFunctionDisplayId(fun));
+ RootedScript script(cx, JS_GetFunctionScript(cx, fun));
+ const char* filename = script ? JS_GetScriptFilename(script) : "anonymous";
+ const char* filenameSuffix = strrchr(filename, '/');
+
+ if (filenameSuffix) {
+ filenameSuffix++;
+ } else {
+ filenameSuffix = filename;
+ }
+
+ nsCString displayName("anonymous");
+ if (funName) {
+ RootedValue funNameVal(cx, StringValue(funName));
+ if (!XPCConvert::JSData2Native(cx, &displayName, funNameVal,
+ {nsXPTType::T_UTF8STRING}, nullptr, 0,
+ nullptr)) {
+ JS_ClearPendingException(cx);
+ return nsCString("anonymous");
+ }
+ }
+
+ displayName.Append('[');
+ displayName.Append(filenameSuffix, strlen(filenameSuffix));
+ displayName.Append(']');
+ return displayName;
+}
+
+} // anonymous namespace
+
+/***************************************************************************/
+
+// static
+nsresult nsXPCWrappedJS::DelegatedQueryInterface(REFNSIID aIID,
+ void** aInstancePtr) {
+ if (aIID.Equals(NS_GET_IID(nsIXPConnectJSObjectHolder))) {
+ // This needs to call NS_ADDREF directly instead of using nsCOMPtr<>,
+ // because the latter does a QI in an assert, and we're already in a QI, so
+ // it would cause infinite recursion.
+ NS_ADDREF(this);
+ *aInstancePtr = (void*)static_cast<nsIXPConnectJSObjectHolder*>(this);
+ return NS_OK;
+ }
+
+ // Ensure that we are asking for a non-builtinclass interface, and avoid even
+ // setting up our AutoEntryScript if we are. Don't bother doing that check
+ // if our IID is nsISupports: we know that's not builtinclass, and we QI to
+ // it a _lot_.
+ if (!aIID.Equals(NS_GET_IID(nsISupports))) {
+ const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID);
+ if (!info || info->IsBuiltinClass()) {
+ MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupportsWeakReference)),
+ "Later code for nsISupportsWeakReference is being skipped");
+ MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISimpleEnumerator)),
+ "Later code for nsISimpleEnumerator is being skipped");
+ MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsINamed)),
+ "Later code for nsINamed is being skipped");
+ *aInstancePtr = nullptr;
+ return NS_NOINTERFACE;
+ }
+ }
+
+ MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsWrapperCache)),
+ "Where did we get non-builtinclass interface info for this??");
+
+ // QI on an XPCWrappedJS can run script, so we need an AutoEntryScript.
+ // This is inherently Gecko-specific.
+ // We check both nativeGlobal and nativeGlobal->GetGlobalJSObject() even
+ // though we have derived nativeGlobal from the JS global, because we know
+ // there are cases where this can happen. See bug 1094953.
+ RootedObject obj(RootingCx(), GetJSObject());
+ nsIGlobalObject* nativeGlobal = NativeGlobal(js::UncheckedUnwrap(obj));
+ NS_ENSURE_TRUE(nativeGlobal, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(nativeGlobal->HasJSGlobal(), NS_ERROR_FAILURE);
+
+ AutoAllowLegacyScriptExecution exemption;
+
+ AutoEntryScript aes(nativeGlobal, "XPCWrappedJS QueryInterface",
+ /* aIsMainThread = */ true);
+ XPCCallContext ccx(aes.cx());
+ if (!ccx.IsValid()) {
+ *aInstancePtr = nullptr;
+ return NS_NOINTERFACE;
+ }
+
+ // We now need to enter the realm of the actual JSObject* we are pointing at.
+ // But that may be a cross-compartment wrapper and therefore not have a
+ // well-defined realm, so enter the realm of the global that we grabbed back
+ // when we started pointing to our JSObject*.
+ RootedObject objScope(RootingCx(), GetJSObjectGlobal());
+ JSAutoRealm ar(aes.cx(), objScope);
+
+ // We support nsISupportsWeakReference iff the root wrapped JSObject
+ // claims to support it in its QueryInterface implementation.
+ if (aIID.Equals(NS_GET_IID(nsISupportsWeakReference))) {
+ // We only want to expose one implementation from our aggregate.
+ nsXPCWrappedJS* root = GetRootWrapper();
+ RootedObject rootScope(ccx, root->GetJSObjectGlobal());
+
+ // Fail if JSObject doesn't claim support for nsISupportsWeakReference
+ if (!root->IsValid() || !CallQueryInterfaceOnJSObject(
+ ccx, root->GetJSObject(), rootScope, aIID)) {
+ *aInstancePtr = nullptr;
+ return NS_NOINTERFACE;
+ }
+
+ NS_ADDREF(root);
+ *aInstancePtr = (void*)static_cast<nsISupportsWeakReference*>(root);
+ return NS_OK;
+ }
+
+ // If we're asked to QI to nsISimpleEnumerator and the wrapped object does not
+ // have a QueryInterface method, assume it is a JS iterator, and wrap it into
+ // an equivalent nsISimpleEnumerator.
+ if (aIID.Equals(NS_GET_IID(nsISimpleEnumerator))) {
+ bool found;
+ XPCJSContext* xpccx = ccx.GetContext();
+ if (JS_HasPropertyById(aes.cx(), obj,
+ xpccx->GetStringID(xpccx->IDX_QUERY_INTERFACE),
+ &found) &&
+ !found) {
+ nsresult rv;
+ nsCOMPtr<nsIJSEnumerator> jsEnum;
+ if (!XPCConvert::JSObject2NativeInterface(
+ aes.cx(), getter_AddRefs(jsEnum), obj,
+ &NS_GET_IID(nsIJSEnumerator), nullptr, &rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsISimpleEnumerator> res = new XPCWrappedJSIterator(jsEnum);
+ res.forget(aInstancePtr);
+ return NS_OK;
+ }
+ }
+
+ // Checks for any existing wrapper explicitly constructed for this iid.
+ // This includes the current wrapper. This also deals with the
+ // nsISupports case (for which it returns mRoot).
+ // Also check if asking for an interface from which one of our wrappers
+ // inherits.
+ if (nsXPCWrappedJS* sibling = FindOrFindInherited(aIID)) {
+ NS_ADDREF(sibling);
+ *aInstancePtr = sibling->GetXPTCStub();
+ return NS_OK;
+ }
+
+ // Check if the desired interface is a function interface. If so, we don't
+ // want to QI, because the function almost certainly doesn't have a
+ // QueryInterface property, and doesn't need one.
+ const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID);
+ if (info && info->IsFunction()) {
+ RefPtr<nsXPCWrappedJS> wrapper;
+ nsresult rv =
+ nsXPCWrappedJS::GetNewOrUsed(ccx, obj, aIID, getter_AddRefs(wrapper));
+
+ // Do the same thing we do for the "check for any existing wrapper" case
+ // above.
+ if (NS_SUCCEEDED(rv) && wrapper) {
+ *aInstancePtr = wrapper.forget().take()->GetXPTCStub();
+ }
+ return rv;
+ }
+
+ // else we do the more expensive stuff...
+
+ // check if the JSObject claims to implement this interface
+ RootedObject jsobj(ccx,
+ CallQueryInterfaceOnJSObject(ccx, obj, objScope, aIID));
+ if (jsobj) {
+ // We can't use XPConvert::JSObject2NativeInterface() here
+ // since that can find a XPCWrappedNative directly on the
+ // proto chain, and we don't want that here. We need to find
+ // the actual JS object that claimed it supports the interface
+ // we're looking for or we'll potentially bypass security
+ // checks etc by calling directly through to a native found on
+ // the prototype chain.
+ //
+ // Instead, simply do the nsXPCWrappedJS part of
+ // XPConvert::JSObject2NativeInterface() here to make sure we
+ // get a new (or used) nsXPCWrappedJS.
+ RefPtr<nsXPCWrappedJS> wrapper;
+ nsresult rv =
+ nsXPCWrappedJS::GetNewOrUsed(ccx, jsobj, aIID, getter_AddRefs(wrapper));
+ if (NS_SUCCEEDED(rv) && wrapper) {
+ // We need to go through the QueryInterface logic to make
+ // this return the right thing for the various 'special'
+ // interfaces; e.g. nsISimpleEnumerator.
+ rv = wrapper->QueryInterface(aIID, aInstancePtr);
+ return rv;
+ }
+ }
+
+ // If we're asked to QI to nsINamed, we pretend that this is possible. We'll
+ // try to return a name that makes sense for the wrapped JS value.
+ if (aIID.Equals(NS_GET_IID(nsINamed))) {
+ nsCString name = GetFunctionName(ccx, obj);
+ RefPtr<WrappedJSNamed> named = new WrappedJSNamed(name);
+ *aInstancePtr = named.forget().take();
+ return NS_OK;
+ }
+
+ // else...
+ // no can do
+ *aInstancePtr = nullptr;
+ return NS_NOINTERFACE;
+}
+
+// static
+JSObject* nsXPCWrappedJS::GetRootJSObject(JSContext* cx, JSObject* aJSObjArg) {
+ RootedObject aJSObj(cx, aJSObjArg);
+ RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ JSObject* result =
+ CallQueryInterfaceOnJSObject(cx, aJSObj, global, NS_GET_IID(nsISupports));
+ if (!result) {
+ result = aJSObj;
+ }
+ return js::UncheckedUnwrap(result);
+}
+
+// static
+bool nsXPCWrappedJS::GetArraySizeFromParam(const nsXPTMethodInfo* method,
+ const nsXPTType& type,
+ nsXPTCMiniVariant* nativeParams,
+ uint32_t* result) {
+ if (type.Tag() != nsXPTType::T_LEGACY_ARRAY &&
+ type.Tag() != nsXPTType::T_PSTRING_SIZE_IS &&
+ type.Tag() != nsXPTType::T_PWSTRING_SIZE_IS) {
+ *result = 0;
+ return true;
+ }
+
+ uint8_t argnum = type.ArgNum();
+ const nsXPTParamInfo& param = method->Param(argnum);
+
+ // This should be enforced by the xpidl compiler, but it's not.
+ // See bug 695235.
+ if (param.Type().Tag() != nsXPTType::T_U32) {
+ return false;
+ }
+
+ // If the length is passed indirectly (as an outparam), dereference by an
+ // extra level.
+ if (param.IsIndirect()) {
+ *result = *(uint32_t*)nativeParams[argnum].val.p;
+ } else {
+ *result = nativeParams[argnum].val.u32;
+ }
+ return true;
+}
+
+// static
+bool nsXPCWrappedJS::GetInterfaceTypeFromParam(const nsXPTMethodInfo* method,
+ const nsXPTType& type,
+ nsXPTCMiniVariant* nativeParams,
+ nsID* result) {
+ result->Clear();
+
+ const nsXPTType& inner = type.InnermostType();
+ if (inner.Tag() == nsXPTType::T_INTERFACE) {
+ // Directly get IID from nsXPTInterfaceInfo.
+ if (!inner.GetInterface()) {
+ return false;
+ }
+
+ *result = inner.GetInterface()->IID();
+ } else if (inner.Tag() == nsXPTType::T_INTERFACE_IS) {
+ // Get IID from a passed parameter.
+ const nsXPTParamInfo& param = method->Param(inner.ArgNum());
+ if (param.Type().Tag() != nsXPTType::T_NSID &&
+ param.Type().Tag() != nsXPTType::T_NSIDPTR) {
+ return false;
+ }
+
+ void* ptr = nativeParams[inner.ArgNum()].val.p;
+
+ // If our IID is passed as a pointer outparameter, an extra level of
+ // dereferencing is required.
+ if (ptr && param.Type().Tag() == nsXPTType::T_NSIDPTR &&
+ param.IsIndirect()) {
+ ptr = *(nsID**)ptr;
+ }
+
+ if (!ptr) {
+ return false;
+ }
+
+ *result = *(nsID*)ptr;
+ }
+ return true;
+}
+
+// static
+void nsXPCWrappedJS::CleanupOutparams(const nsXPTMethodInfo* info,
+ nsXPTCMiniVariant* nativeParams,
+ bool inOutOnly, uint8_t count) {
+ // clean up any 'out' params handed in
+ for (uint8_t i = 0; i < count; i++) {
+ const nsXPTParamInfo& param = info->GetParam(i);
+ if (!param.IsOut()) {
+ continue;
+ }
+
+ MOZ_ASSERT(param.IsIndirect(), "Outparams are always indirect");
+
+ // Don't try to clear optional out params that are not set.
+ if (param.IsOptional() && !nativeParams[i].val.p) {
+ continue;
+ }
+
+ // Call 'CleanupValue' on parameters which we know to be initialized:
+ // 1. Complex parameters (initialized by caller)
+ // 2. 'inout' parameters (initialized by caller)
+ // 3. 'out' parameters when 'inOutOnly' is 'false' (initialized by us)
+ //
+ // We skip non-complex 'out' parameters before the call, as they may
+ // contain random junk.
+ if (param.Type().IsComplex() || param.IsIn() || !inOutOnly) {
+ uint32_t arrayLen = 0;
+ if (!GetArraySizeFromParam(info, param.Type(), nativeParams, &arrayLen)) {
+ continue;
+ }
+
+ xpc::CleanupValue(param.Type(), nativeParams[i].val.p, arrayLen);
+ }
+
+ // Ensure our parameters are in a clean state. Complex values are always
+ // handled by CleanupValue, and others have a valid null representation.
+ if (!param.Type().IsComplex()) {
+ param.Type().ZeroValue(nativeParams[i].val.p);
+ }
+ }
+}
+
+nsresult nsXPCWrappedJS::CheckForException(XPCCallContext& ccx,
+ AutoEntryScript& aes,
+ HandleObject aObj,
+ const char* aPropertyName,
+ const char* anInterfaceName,
+ Exception* aSyntheticException) {
+ JSContext* cx = ccx.GetJSContext();
+ MOZ_ASSERT(cx == aes.cx());
+ RefPtr<Exception> xpc_exception = aSyntheticException;
+ /* this one would be set by our error reporter */
+
+ XPCJSContext* xpccx = ccx.GetContext();
+
+ // Get this right away in case we do something below to cause JS code
+ // to run.
+ nsresult pending_result = xpccx->GetPendingResult();
+
+ RootedValue js_exception(cx);
+ bool is_js_exception = JS_GetPendingException(cx, &js_exception);
+
+ /* JS might throw an exception whether the reporter was called or not */
+ if (is_js_exception) {
+ if (!xpc_exception) {
+ XPCConvert::JSValToXPCException(cx, &js_exception, anInterfaceName,
+ aPropertyName,
+ getter_AddRefs(xpc_exception));
+ }
+
+ /* cleanup and set failed even if we can't build an exception */
+ if (!xpc_exception) {
+ xpccx->SetPendingException(nullptr); // XXX necessary?
+ }
+ }
+
+ // Clear the pending exception now, because xpc_exception might be JS-
+ // implemented, so invoking methods on it might re-enter JS, which we can't
+ // do with an exception on the stack.
+ aes.ClearException();
+
+ if (xpc_exception) {
+ nsresult e_result = xpc_exception->GetResult();
+ // Figure out whether or not we should report this exception.
+ bool reportable = xpc_IsReportableErrorCode(e_result);
+ if (reportable) {
+ // Ugly special case for GetInterface. It's "special" in the
+ // same way as QueryInterface in that a failure is not
+ // exceptional and shouldn't be reported. We have to do this
+ // check here instead of in xpcwrappedjs (like we do for QI) to
+ // avoid adding extra code to all xpcwrappedjs objects.
+ if (e_result == NS_ERROR_NO_INTERFACE &&
+ !strcmp(anInterfaceName, "nsIInterfaceRequestor") &&
+ !strcmp(aPropertyName, "getInterface")) {
+ reportable = false;
+ }
+
+ // More special case, see bug 877760.
+ if (e_result == NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED) {
+ reportable = false;
+ }
+ }
+
+ // Try to use the error reporter set on the context to handle this
+ // error if it came from a JS exception.
+ if (reportable && is_js_exception) {
+ // Note that we cleared the exception above, so we need to set it again,
+ // just so that we can tell the JS engine to pass it back to us via the
+ // error reporting callback. This is all very dumb.
+ JS_SetPendingException(cx, js_exception);
+
+ // Enter the unwrapped object's realm. This is the realm that was used to
+ // enter the AutoEntryScript.
+ JSAutoRealm ar(cx, js::UncheckedUnwrap(aObj));
+ aes.ReportException();
+ reportable = false;
+ }
+
+ if (reportable) {
+ if (nsJSUtils::DumpEnabled()) {
+ static const char line[] =
+ "************************************************************\n";
+ static const char preamble[] =
+ "* Call to xpconnect wrapped JSObject produced this error: *\n";
+ static const char cant_get_text[] =
+ "FAILED TO GET TEXT FROM EXCEPTION\n";
+
+ fputs(line, stdout);
+ fputs(preamble, stdout);
+ nsCString text;
+ xpc_exception->ToString(cx, text);
+ if (!text.IsEmpty()) {
+ fputs(text.get(), stdout);
+ fputs("\n", stdout);
+ } else
+ fputs(cant_get_text, stdout);
+ fputs(line, stdout);
+ }
+
+ // Log the exception to the JS Console, so that users can do
+ // something with it.
+ nsCOMPtr<nsIConsoleService> consoleService(
+ do_GetService(XPC_CONSOLE_CONTRACTID));
+ if (nullptr != consoleService) {
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_QueryInterface(xpc_exception->GetData());
+
+ if (nullptr == scriptError) {
+ // No luck getting one from the exception, so
+ // try to cook one up.
+ scriptError = do_CreateInstance(XPC_SCRIPT_ERROR_CONTRACTID);
+ if (nullptr != scriptError) {
+ nsCString newMessage;
+ xpc_exception->ToString(cx, newMessage);
+ // try to get filename, lineno from the first
+ // stack frame location.
+ int32_t lineNumber = 0;
+ nsString sourceName;
+
+ nsCOMPtr<nsIStackFrame> location = xpc_exception->GetLocation();
+ if (location) {
+ // Get line number.
+ lineNumber = location->GetLineNumber(cx);
+
+ // get a filename.
+ location->GetFilename(cx, sourceName);
+ }
+
+ nsresult rv = scriptError->InitWithWindowID(
+ NS_ConvertUTF8toUTF16(newMessage), sourceName, u""_ns,
+ lineNumber, 0, 0, "XPConnect JavaScript",
+ nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx));
+ if (NS_FAILED(rv)) {
+ scriptError = nullptr;
+ }
+
+ rv = scriptError->InitSourceId(location->GetSourceId(cx));
+ if (NS_FAILED(rv)) {
+ scriptError = nullptr;
+ }
+ }
+ }
+ if (nullptr != scriptError) {
+ consoleService->LogMessage(scriptError);
+ }
+ }
+ }
+ // Whether or not it passes the 'reportable' test, it might
+ // still be an error and we have to do the right thing here...
+ if (NS_FAILED(e_result)) {
+ xpccx->SetPendingException(xpc_exception);
+ return e_result;
+ }
+ } else {
+ // see if JS code signaled failure result without throwing exception
+ if (NS_FAILED(pending_result)) {
+ return pending_result;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsXPCWrappedJS::CallMethod(uint16_t methodIndex, const nsXPTMethodInfo* info,
+ nsXPTCMiniVariant* nativeParams) {
+ // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread.
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(),
+ "nsXPCWrappedJS::CallMethod called off main thread");
+
+ if (!IsValid()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ Value* sp = nullptr;
+ Value* argv = nullptr;
+ uint8_t i;
+ nsresult retval = NS_ERROR_FAILURE;
+ bool success;
+ bool readyToDoTheCall = false;
+ nsID param_iid;
+ bool foundDependentParam;
+
+ // We're about to call into script via an XPCWrappedJS, so we need an
+ // AutoEntryScript. This is probably Gecko-specific at this point, and
+ // definitely will be when we turn off XPConnect for the web.
+ RootedObject obj(RootingCx(), GetJSObject());
+ nsIGlobalObject* nativeGlobal = NativeGlobal(js::UncheckedUnwrap(obj));
+
+ AutoAllowLegacyScriptExecution exemption;
+
+ AutoEntryScript aes(nativeGlobal, "XPCWrappedJS method call",
+ /* aIsMainThread = */ true);
+ XPCCallContext ccx(aes.cx());
+ if (!ccx.IsValid()) {
+ return retval;
+ }
+
+ JSContext* cx = ccx.GetJSContext();
+
+ if (!cx || !info->IsReflectable()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const nsXPTInterfaceInfo* interfaceInfo = GetInfo();
+ JS::RootedId id(cx);
+ const char* name = info->NameOrDescription();
+ if (!info->GetId(cx, id.get())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // We now need to enter the realm of the actual JSObject* we are pointing at.
+ // But that may be a cross-compartment wrapper and therefore not have a
+ // well-defined realm, so enter the realm of the global that we grabbed back
+ // when we started pointing to our JSObject*.
+ RootedObject scope(cx, GetJSObjectGlobal());
+ JSAutoRealm ar(cx, scope);
+
+ // [optional_argc] has a different calling convention, which we don't
+ // support for JS-implemented components.
+ if (info->WantsOptArgc()) {
+ const char* str =
+ "IDL methods marked with [optional_argc] may not "
+ "be implemented in JS";
+ // Throw and warn for good measure.
+ JS_ReportErrorASCII(cx, "%s", str);
+ NS_WARNING(str);
+ return CheckForException(ccx, aes, obj, name, interfaceInfo->Name());
+ }
+
+ RootedValue fval(cx);
+ RootedObject thisObj(cx, obj);
+
+ RootedValueVector args(cx);
+ AutoScriptEvaluate scriptEval(cx);
+
+ XPCJSRuntime* xpcrt = XPCJSRuntime::Get();
+ XPCJSContext* xpccx = ccx.GetContext();
+ AutoSavePendingResult apr(xpccx);
+
+ // XXX ASSUMES that retval is last arg. The xpidl compiler ensures this.
+ uint8_t paramCount = info->GetParamCount();
+ uint8_t argc = paramCount;
+ if (info->HasRetval()) {
+ argc -= 1;
+ }
+
+ if (!scriptEval.StartEvaluating(scope)) {
+ goto pre_call_clean_up;
+ }
+
+ xpccx->SetPendingException(nullptr);
+
+ // We use js_Invoke so that the gcthings we use as args will be rooted by
+ // the engine as we do conversions and prepare to do the function call.
+
+ // setup stack
+
+ // if this isn't a function call then we don't need to push extra stuff
+ if (!(info->IsSetter() || info->IsGetter())) {
+ // We get fval before allocating the stack to avoid gc badness that can
+ // happen if the GetProperty call leaves our request and the gc runs
+ // while the stack we allocate contains garbage.
+
+ // If the interface is marked as a [function] then we will assume that
+ // our JSObject is a function and not an object with a named method.
+
+ // In the xpidl [function] case we are making sure now that the
+ // JSObject is callable. If it is *not* callable then we silently
+ // fallback to looking up the named property...
+ // (because jst says he thinks this fallback is 'The Right Thing'.)
+ //
+ // In the normal (non-function) case we just lookup the property by
+ // name and as long as the object has such a named property we go ahead
+ // and try to make the call. If it turns out the named property is not
+ // a callable object then the JS engine will throw an error and we'll
+ // pass this along to the caller as an exception/result code.
+
+ fval = ObjectValue(*obj);
+ if (!interfaceInfo->IsFunction() ||
+ JS_TypeOfValue(ccx, fval) != JSTYPE_FUNCTION) {
+ if (!JS_GetPropertyById(cx, obj, id, &fval)) {
+ goto pre_call_clean_up;
+ }
+ // XXX We really want to factor out the error reporting better and
+ // specifically report the failure to find a function with this name.
+ // This is what we do below if the property is found but is not a
+ // function. We just need to factor better so we can get to that
+ // reporting path from here.
+
+ thisObj = obj;
+ }
+ }
+
+ if (!args.resize(argc)) {
+ retval = NS_ERROR_OUT_OF_MEMORY;
+ goto pre_call_clean_up;
+ }
+
+ argv = args.begin();
+ sp = argv;
+
+ // build the args
+ // NB: This assignment *looks* wrong because we haven't yet called our
+ // function. However, we *have* already entered the compartmen that we're
+ // about to call, and that's the global that we want here. In other words:
+ // we're trusting the JS engine to come up with a good global to use for
+ // our object (whatever it was).
+ for (i = 0; i < argc; i++) {
+ const nsXPTParamInfo& param = info->GetParam(i);
+ const nsXPTType& type = param.GetType();
+ uint32_t array_count;
+ RootedValue val(cx, NullValue());
+
+ // Verify that null was not passed for a non-optional 'out' param.
+ if (param.IsOut() && !nativeParams[i].val.p && !param.IsOptional()) {
+ retval = NS_ERROR_INVALID_ARG;
+ goto pre_call_clean_up;
+ }
+
+ if (param.IsIn()) {
+ const void* pv;
+ if (param.IsIndirect()) {
+ pv = nativeParams[i].val.p;
+ } else {
+ pv = &nativeParams[i];
+ }
+
+ if (!GetInterfaceTypeFromParam(info, type, nativeParams, &param_iid) ||
+ !GetArraySizeFromParam(info, type, nativeParams, &array_count))
+ goto pre_call_clean_up;
+
+ if (!XPCConvert::NativeData2JS(cx, &val, pv, type, &param_iid,
+ array_count, nullptr))
+ goto pre_call_clean_up;
+ }
+
+ if (param.IsOut()) {
+ // create an 'out' object
+ RootedObject out_obj(cx, NewOutObject(cx));
+ if (!out_obj) {
+ retval = NS_ERROR_OUT_OF_MEMORY;
+ goto pre_call_clean_up;
+ }
+
+ if (param.IsIn()) {
+ if (!JS_SetPropertyById(cx, out_obj,
+ xpcrt->GetStringID(XPCJSContext::IDX_VALUE),
+ val)) {
+ goto pre_call_clean_up;
+ }
+ }
+ *sp++ = JS::ObjectValue(*out_obj);
+ } else
+ *sp++ = val;
+ }
+
+ readyToDoTheCall = true;
+
+pre_call_clean_up:
+ // clean up any 'out' params handed in
+ CleanupOutparams(info, nativeParams, /* inOutOnly = */ true, paramCount);
+
+ if (!readyToDoTheCall) {
+ return retval;
+ }
+
+ // do the deed - note exceptions
+
+ MOZ_ASSERT(!aes.HasException());
+
+ RefPtr<Exception> syntheticException;
+ RootedValue rval(cx);
+ if (info->IsGetter()) {
+ success = JS_GetProperty(cx, obj, name, &rval);
+ } else if (info->IsSetter()) {
+ rval = *argv;
+ success = JS_SetProperty(cx, obj, name, rval);
+ } else {
+ if (!fval.isPrimitive()) {
+ success = JS_CallFunctionValue(cx, thisObj, fval, args, &rval);
+ } else {
+ // The property was not an object so can't be a function.
+ // Let's build and 'throw' an exception.
+
+ static const nsresult code = NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED;
+ static const char format[] = "%s \"%s\"";
+ const char* msg;
+ UniqueChars sz;
+
+ if (nsXPCException::NameAndFormatForNSResult(code, nullptr, &msg) &&
+ msg) {
+ sz = JS_smprintf(format, msg, name);
+ }
+
+ XPCConvert::ConstructException(
+ code, sz.get(), interfaceInfo->Name(), name, nullptr,
+ getter_AddRefs(syntheticException), nullptr, nullptr);
+ success = false;
+ }
+ }
+
+ if (!success) {
+ return CheckForException(ccx, aes, obj, name, interfaceInfo->Name(),
+ syntheticException);
+ }
+
+ xpccx->SetPendingException(nullptr); // XXX necessary?
+
+ // convert out args and result
+ // NOTE: this is the total number of native params, not just the args
+ // Convert independent params only.
+ // When we later convert the dependent params (if any) we will know that
+ // the params upon which they depend will have already been converted -
+ // regardless of ordering.
+
+ foundDependentParam = false;
+ for (i = 0; i < paramCount; i++) {
+ const nsXPTParamInfo& param = info->GetParam(i);
+ MOZ_ASSERT(!param.IsShared(), "[shared] implies [noscript]!");
+ if (!param.IsOut() || !nativeParams[i].val.p) {
+ continue;
+ }
+
+ const nsXPTType& type = param.GetType();
+ if (type.IsDependent()) {
+ foundDependentParam = true;
+ continue;
+ }
+
+ RootedValue val(cx);
+
+ if (&param == info->GetRetval()) {
+ val = rval;
+ } else if (argv[i].isPrimitive()) {
+ break;
+ } else {
+ RootedObject obj(cx, &argv[i].toObject());
+ if (!JS_GetPropertyById(
+ cx, obj, xpcrt->GetStringID(XPCJSContext::IDX_VALUE), &val)) {
+ break;
+ }
+ }
+
+ // setup allocator and/or iid
+
+ const nsXPTType& inner = type.InnermostType();
+ if (inner.Tag() == nsXPTType::T_INTERFACE) {
+ if (!inner.GetInterface()) {
+ break;
+ }
+ param_iid = inner.GetInterface()->IID();
+ }
+
+ MOZ_ASSERT(param.IsIndirect(), "outparams are always indirect");
+ if (!XPCConvert::JSData2Native(cx, nativeParams[i].val.p, val, type,
+ &param_iid, 0, nullptr))
+ break;
+ }
+
+ // if any params were dependent, then we must iterate again to convert them.
+ if (foundDependentParam && i == paramCount) {
+ for (i = 0; i < paramCount; i++) {
+ const nsXPTParamInfo& param = info->GetParam(i);
+ if (!param.IsOut()) {
+ continue;
+ }
+
+ const nsXPTType& type = param.GetType();
+ if (!type.IsDependent()) {
+ continue;
+ }
+
+ RootedValue val(cx);
+ uint32_t array_count;
+
+ if (&param == info->GetRetval()) {
+ val = rval;
+ } else {
+ RootedObject obj(cx, &argv[i].toObject());
+ if (!JS_GetPropertyById(
+ cx, obj, xpcrt->GetStringID(XPCJSContext::IDX_VALUE), &val)) {
+ break;
+ }
+ }
+
+ // setup allocator and/or iid
+
+ if (!GetInterfaceTypeFromParam(info, type, nativeParams, &param_iid) ||
+ !GetArraySizeFromParam(info, type, nativeParams, &array_count))
+ break;
+
+ MOZ_ASSERT(param.IsIndirect(), "outparams are always indirect");
+ if (!XPCConvert::JSData2Native(cx, nativeParams[i].val.p, val, type,
+ &param_iid, array_count, nullptr))
+ break;
+ }
+ }
+
+ if (i != paramCount) {
+ // We didn't manage all the result conversions!
+ // We have to cleanup any junk that *did* get converted.
+ CleanupOutparams(info, nativeParams, /* inOutOnly = */ false, i);
+ } else {
+ // set to whatever the JS code might have set as the result
+ retval = xpccx->GetPendingResult();
+ }
+
+ return retval;
+}
+
+static const JSClass XPCOutParamClass = {"XPCOutParam", 0, JS_NULL_CLASS_OPS};
+
+bool xpc::IsOutObject(JSContext* cx, JSObject* obj) {
+ return JS::GetClass(obj) == &XPCOutParamClass;
+}
+
+JSObject* xpc::NewOutObject(JSContext* cx) {
+ return JS_NewObject(cx, &XPCOutParamClass);
+}
+
+// static
+void nsXPCWrappedJS::DebugDumpInterfaceInfo(const nsXPTInterfaceInfo* aInfo,
+ int16_t depth) {
+#ifdef DEBUG
+ depth--;
+ XPC_LOG_ALWAYS(("nsXPTInterfaceInfo @ %p = ", aInfo));
+ XPC_LOG_INDENT();
+ const char* name = aInfo->Name();
+ XPC_LOG_ALWAYS(("interface name is %s", name));
+ char* iid = aInfo->IID().ToString();
+ XPC_LOG_ALWAYS(("IID number is %s", iid ? iid : "invalid"));
+ if (iid) {
+ free(iid);
+ }
+ XPC_LOG_ALWAYS(("InterfaceInfo @ %p", aInfo));
+ uint16_t methodCount = 0;
+ if (depth) {
+ XPC_LOG_INDENT();
+ XPC_LOG_ALWAYS(("parent @ %p", aInfo->GetParent()));
+ methodCount = aInfo->MethodCount();
+ XPC_LOG_ALWAYS(("MethodCount = %d", methodCount));
+ XPC_LOG_ALWAYS(("ConstantCount = %d", aInfo->ConstantCount()));
+ XPC_LOG_OUTDENT();
+ }
+ XPC_LOG_ALWAYS(("method count = %d", methodCount));
+ if (depth && methodCount) {
+ depth--;
+ XPC_LOG_INDENT();
+ for (uint16_t i = 0; i < methodCount; i++) {
+ XPC_LOG_ALWAYS(("Method %d is %s%s", i,
+ aInfo->Method(i).IsReflectable() ? "" : " NOT ",
+ "reflectable"));
+ }
+ XPC_LOG_OUTDENT();
+ depth++;
+ }
+ XPC_LOG_OUTDENT();
+#endif
+}