tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

XPCWrappedJSClass.cpp (37680B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /* Sharable code and data for wrapper around JSObjects. */
      8 
      9 #include "xpcprivate.h"
     10 #include "js/CallAndConstruct.h"  // JS_CallFunctionValue
     11 #include "js/Object.h"            // JS::GetClass
     12 #include "js/Printf.h"
     13 #include "js/PropertyAndElement.h"  // JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_HasPropertyById, JS_SetProperty, JS_SetPropertyById
     14 #include "nsArrayEnumerator.h"
     15 #include "nsINamed.h"
     16 #include "nsIScriptError.h"
     17 #include "nsWrapperCache.h"
     18 #include "AccessCheck.h"
     19 #include "nsJSUtils.h"
     20 #include "nsPrintfCString.h"
     21 #include "mozilla/Attributes.h"
     22 #include "mozilla/dom/AutoEntryScript.h"
     23 #include "mozilla/dom/BindingUtils.h"
     24 #include "mozilla/dom/DOMException.h"
     25 #include "mozilla/dom/DOMExceptionBinding.h"
     26 #include "mozilla/dom/MozQueryInterface.h"
     27 
     28 #include "jsapi.h"
     29 #include "jsfriendapi.h"
     30 
     31 using namespace xpc;
     32 using namespace JS;
     33 using namespace mozilla;
     34 using namespace mozilla::dom;
     35 
     36 bool AutoScriptEvaluate::StartEvaluating(HandleObject scope) {
     37  MOZ_ASSERT(!mEvaluated,
     38             "AutoScriptEvaluate::Evaluate should only be called once");
     39 
     40  if (!mJSContext) {
     41    return true;
     42  }
     43 
     44  mEvaluated = true;
     45 
     46  mAutoRealm.emplace(mJSContext, scope);
     47 
     48  // Saving the exception state keeps us from interfering with another script
     49  // that may also be running on this context.  This occurred first with the
     50  // js debugger, as described in
     51  // http://bugzilla.mozilla.org/show_bug.cgi?id=88130 but presumably could
     52  // show up in any situation where a script calls into a wrapped js component
     53  // on the same context, while the context has a nonzero exception state.
     54  mState.emplace(mJSContext);
     55 
     56  return true;
     57 }
     58 
     59 AutoScriptEvaluate::~AutoScriptEvaluate() {
     60  if (!mJSContext || !mEvaluated) {
     61    return;
     62  }
     63  mState->restore();
     64 }
     65 
     66 // It turns out that some errors may be not worth reporting. So, this
     67 // function is factored out to manage that.
     68 bool xpc_IsReportableErrorCode(nsresult code) {
     69  if (NS_SUCCEEDED(code)) {
     70    return false;
     71  }
     72 
     73  switch (code) {
     74    // Error codes that we don't want to report as errors...
     75    // These generally indicate bad interface design AFAIC.
     76    case NS_ERROR_FACTORY_REGISTER_AGAIN:
     77    case NS_BASE_STREAM_WOULD_BLOCK:
     78      return false;
     79    default:
     80      return true;
     81  }
     82 }
     83 
     84 // A little stack-based RAII class to help management of the XPCJSContext
     85 // PendingResult.
     86 class MOZ_STACK_CLASS AutoSavePendingResult {
     87 public:
     88  explicit AutoSavePendingResult(XPCJSContext* xpccx) : mXPCContext(xpccx) {
     89    // Save any existing pending result and reset to NS_OK for this invocation.
     90    mSavedResult = xpccx->GetPendingResult();
     91    xpccx->SetPendingResult(NS_OK);
     92  }
     93  ~AutoSavePendingResult() { mXPCContext->SetPendingResult(mSavedResult); }
     94 
     95 private:
     96  XPCJSContext* mXPCContext;
     97  nsresult mSavedResult;
     98 };
     99 
    100 // static
    101 const nsXPTInterfaceInfo* nsXPCWrappedJS::GetInterfaceInfo(REFNSIID aIID) {
    102  const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID);
    103  if (!info || info->IsBuiltinClass()) {
    104    return nullptr;
    105  }
    106 
    107  return info;
    108 }
    109 
    110 // static
    111 JSObject* nsXPCWrappedJS::CallQueryInterfaceOnJSObject(JSContext* cx,
    112                                                       JSObject* jsobjArg,
    113                                                       HandleObject scope,
    114                                                       REFNSIID aIID) {
    115  js::AssertSameCompartment(scope, jsobjArg);
    116 
    117  RootedObject jsobj(cx, jsobjArg);
    118  RootedValue arg(cx);
    119  RootedValue retval(cx);
    120  RootedObject retObj(cx);
    121  RootedValue fun(cx);
    122 
    123  // In bug 503926, we added a security check to make sure that we don't
    124  // invoke content QI functions. In the modern world, this is probably
    125  // unnecessary, because invoking QI involves passing an IID object to
    126  // content, which will fail. But we do a belt-and-suspenders check to
    127  // make sure that content can never trigger the rat's nest of code below.
    128  // Once we completely turn off XPConnect for the web, this can definitely
    129  // go away.
    130  if (!AccessCheck::isChrome(jsobj) ||
    131      !AccessCheck::isChrome(js::UncheckedUnwrap(jsobj))) {
    132    return nullptr;
    133  }
    134 
    135  // OK, it looks like we'll be calling into JS code.
    136  AutoScriptEvaluate scriptEval(cx);
    137 
    138  // XXX we should install an error reporter that will send reports to
    139  // the JS error console service.
    140  if (!scriptEval.StartEvaluating(scope)) {
    141    return nullptr;
    142  }
    143 
    144  // check upfront for the existence of the function property
    145  HandleId funid =
    146      XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_QUERY_INTERFACE);
    147  if (!JS_GetPropertyById(cx, jsobj, funid, &fun) || fun.isPrimitive()) {
    148    return nullptr;
    149  }
    150 
    151  dom::MozQueryInterface* mozQI = nullptr;
    152  if (NS_SUCCEEDED(UNWRAP_OBJECT(MozQueryInterface, &fun, mozQI))) {
    153    if (mozQI->QueriesTo(aIID)) {
    154      return jsobj.get();
    155    }
    156    return nullptr;
    157  }
    158 
    159  if (!xpc::ID2JSValue(cx, aIID, &arg)) {
    160    return nullptr;
    161  }
    162 
    163  // Throwing NS_NOINTERFACE is the prescribed way to fail QI from JS. It is
    164  // not an exception that is ever worth reporting, but we don't want to eat
    165  // all exceptions either.
    166 
    167  bool success =
    168      JS_CallFunctionValue(cx, jsobj, fun, HandleValueArray(arg), &retval);
    169  if (!success && JS_IsExceptionPending(cx)) {
    170    RootedValue jsexception(cx, NullValue());
    171 
    172    if (JS_GetPendingException(cx, &jsexception)) {
    173      if (jsexception.isObject()) {
    174        // XPConnect may have constructed an object to represent a
    175        // C++ QI failure. See if that is the case.
    176        JS::Rooted<JSObject*> exceptionObj(cx, &jsexception.toObject());
    177        Exception* e = nullptr;
    178        UNWRAP_OBJECT(Exception, &exceptionObj, e);
    179 
    180        if (e && e->GetResult() == NS_NOINTERFACE) {
    181          JS_ClearPendingException(cx);
    182        }
    183      } else if (jsexception.isNumber()) {
    184        nsresult rv;
    185        // JS often throws an nsresult.
    186        if (jsexception.isDouble())
    187          // Visual Studio 9 doesn't allow casting directly from
    188          // a double to an enumeration type, contrary to
    189          // 5.2.9(10) of C++11, so add an intermediate cast.
    190          rv = (nsresult)(uint32_t)(jsexception.toDouble());
    191        else
    192          rv = (nsresult)(jsexception.toInt32());
    193 
    194        if (rv == NS_NOINTERFACE) JS_ClearPendingException(cx);
    195      }
    196    }
    197  } else if (!success) {
    198    NS_WARNING("QI hook ran OOMed - this is probably a bug!");
    199  }
    200 
    201  if (success) success = JS_ValueToObject(cx, retval, &retObj);
    202 
    203  return success ? retObj.get() : nullptr;
    204 }
    205 
    206 /***************************************************************************/
    207 
    208 namespace {
    209 
    210 class WrappedJSNamed final : public nsINamed {
    211  nsCString mName;
    212 
    213  ~WrappedJSNamed() = default;
    214 
    215 public:
    216  NS_DECL_ISUPPORTS
    217 
    218  explicit WrappedJSNamed(const nsACString& aName) : mName(aName) {}
    219 
    220  NS_IMETHOD GetName(nsACString& aName) override {
    221    aName = mName;
    222    aName.AppendLiteral(":JS");
    223    return NS_OK;
    224  }
    225 };
    226 
    227 NS_IMPL_ISUPPORTS(WrappedJSNamed, nsINamed)
    228 
    229 }  // anonymous namespace
    230 
    231 /***************************************************************************/
    232 
    233 // static
    234 nsresult nsXPCWrappedJS::DelegatedQueryInterface(REFNSIID aIID,
    235                                                 void** aInstancePtr) {
    236  if (aIID.Equals(NS_GET_IID(nsIXPConnectJSObjectHolder))) {
    237    // This needs to call NS_ADDREF directly instead of using nsCOMPtr<>,
    238    // because the latter does a QI in an assert, and we're already in a QI, so
    239    // it would cause infinite recursion.
    240    NS_ADDREF(this);
    241    *aInstancePtr = (void*)static_cast<nsIXPConnectJSObjectHolder*>(this);
    242    return NS_OK;
    243  }
    244 
    245  // Ensure that we are asking for a non-builtinclass interface, and avoid even
    246  // setting up our AutoEntryScript if we are.  Don't bother doing that check
    247  // if our IID is nsISupports: we know that's not builtinclass, and we QI to
    248  // it a _lot_.
    249  if (!aIID.Equals(NS_GET_IID(nsISupports))) {
    250    const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID);
    251    if (!info || info->IsBuiltinClass()) {
    252      MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupportsWeakReference)),
    253                 "Later code for nsISupportsWeakReference is being skipped");
    254      MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISimpleEnumerator)),
    255                 "Later code for nsISimpleEnumerator is being skipped");
    256      MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsINamed)),
    257                 "Later code for nsINamed is being skipped");
    258      *aInstancePtr = nullptr;
    259      return NS_NOINTERFACE;
    260    }
    261  }
    262 
    263  MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsWrapperCache)),
    264             "Where did we get non-builtinclass interface info for this??");
    265 
    266  // QI on an XPCWrappedJS can run script, so we need an AutoEntryScript.
    267  // This is inherently Gecko-specific.
    268  // We check both nativeGlobal and nativeGlobal->GetGlobalJSObject() even
    269  // though we have derived nativeGlobal from the JS global, because we know
    270  // there are cases where this can happen. See bug 1094953.
    271  RootedObject obj(RootingCx(), GetJSObject());
    272  nsIGlobalObject* nativeGlobal = NativeGlobal(js::UncheckedUnwrap(obj));
    273  NS_ENSURE_TRUE(nativeGlobal, NS_ERROR_FAILURE);
    274  NS_ENSURE_TRUE(nativeGlobal->HasJSGlobal(), NS_ERROR_FAILURE);
    275 
    276  AutoAllowLegacyScriptExecution exemption;
    277 
    278  AutoEntryScript aes(nativeGlobal, "XPCWrappedJS QueryInterface",
    279                      /* aIsMainThread = */ true);
    280  XPCCallContext ccx(aes.cx());
    281  if (!ccx.IsValid()) {
    282    *aInstancePtr = nullptr;
    283    return NS_NOINTERFACE;
    284  }
    285 
    286  // We now need to enter the realm of the actual JSObject* we are pointing at.
    287  // But that may be a cross-compartment wrapper and therefore not have a
    288  // well-defined realm, so enter the realm of the global that we grabbed back
    289  // when we started pointing to our JSObject*.
    290  RootedObject objScope(RootingCx(), GetJSObjectGlobal());
    291  JSAutoRealm ar(aes.cx(), objScope);
    292 
    293  // We support nsISupportsWeakReference iff the root wrapped JSObject
    294  // claims to support it in its QueryInterface implementation.
    295  if (aIID.Equals(NS_GET_IID(nsISupportsWeakReference))) {
    296    // We only want to expose one implementation from our aggregate.
    297    nsXPCWrappedJS* root = GetRootWrapper();
    298    RootedObject rootScope(ccx, root->GetJSObjectGlobal());
    299 
    300    // Fail if JSObject doesn't claim support for nsISupportsWeakReference
    301    if (!root->IsValid() || !CallQueryInterfaceOnJSObject(
    302                                ccx, root->GetJSObject(), rootScope, aIID)) {
    303      *aInstancePtr = nullptr;
    304      return NS_NOINTERFACE;
    305    }
    306 
    307    NS_ADDREF(root);
    308    *aInstancePtr = (void*)static_cast<nsISupportsWeakReference*>(root);
    309    return NS_OK;
    310  }
    311 
    312  // If we're asked to QI to nsISimpleEnumerator and the wrapped object does not
    313  // have a QueryInterface method, assume it is a JS iterator, and wrap it into
    314  // an equivalent nsISimpleEnumerator.
    315  if (aIID.Equals(NS_GET_IID(nsISimpleEnumerator))) {
    316    bool found;
    317    XPCJSContext* xpccx = ccx.GetContext();
    318    if (JS_HasPropertyById(aes.cx(), obj,
    319                           xpccx->GetStringID(xpccx->IDX_QUERY_INTERFACE),
    320                           &found) &&
    321        !found) {
    322      nsresult rv;
    323      nsCOMPtr<nsIJSEnumerator> jsEnum;
    324      if (!XPCConvert::JSObject2NativeInterface(
    325              aes.cx(), getter_AddRefs(jsEnum), obj,
    326              &NS_GET_IID(nsIJSEnumerator), nullptr, &rv)) {
    327        return rv;
    328      }
    329      nsCOMPtr<nsISimpleEnumerator> res = new XPCWrappedJSIterator(jsEnum);
    330      res.forget(aInstancePtr);
    331      return NS_OK;
    332    }
    333  }
    334 
    335  // Checks for any existing wrapper explicitly constructed for this iid.
    336  // This includes the current wrapper. This also deals with the
    337  // nsISupports case (for which it returns mRoot).
    338  // Also check if asking for an interface from which one of our wrappers
    339  // inherits.
    340  if (nsXPCWrappedJS* sibling = FindOrFindInherited(aIID)) {
    341    NS_ADDREF(sibling);
    342    *aInstancePtr = sibling->GetXPTCStub();
    343    return NS_OK;
    344  }
    345 
    346  // Check if the desired interface is a function interface. If so, we don't
    347  // want to QI, because the function almost certainly doesn't have a
    348  // QueryInterface property, and doesn't need one.
    349  const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID);
    350  if (info && info->IsFunction()) {
    351    RefPtr<nsXPCWrappedJS> wrapper;
    352    nsresult rv =
    353        nsXPCWrappedJS::GetNewOrUsed(ccx, obj, aIID, getter_AddRefs(wrapper));
    354 
    355    // Do the same thing we do for the "check for any existing wrapper" case
    356    // above.
    357    if (NS_SUCCEEDED(rv) && wrapper) {
    358      *aInstancePtr = wrapper.forget().take()->GetXPTCStub();
    359    }
    360    return rv;
    361  }
    362 
    363  // else we do the more expensive stuff...
    364 
    365  // check if the JSObject claims to implement this interface
    366  RootedObject jsobj(ccx,
    367                     CallQueryInterfaceOnJSObject(ccx, obj, objScope, aIID));
    368  if (jsobj) {
    369    // We can't use XPConvert::JSObject2NativeInterface() here
    370    // since that can find a XPCWrappedNative directly on the
    371    // proto chain, and we don't want that here. We need to find
    372    // the actual JS object that claimed it supports the interface
    373    // we're looking for or we'll potentially bypass security
    374    // checks etc by calling directly through to a native found on
    375    // the prototype chain.
    376    //
    377    // Instead, simply do the nsXPCWrappedJS part of
    378    // XPConvert::JSObject2NativeInterface() here to make sure we
    379    // get a new (or used) nsXPCWrappedJS.
    380    RefPtr<nsXPCWrappedJS> wrapper;
    381    nsresult rv =
    382        nsXPCWrappedJS::GetNewOrUsed(ccx, jsobj, aIID, getter_AddRefs(wrapper));
    383    if (NS_SUCCEEDED(rv) && wrapper) {
    384      // We need to go through the QueryInterface logic to make
    385      // this return the right thing for the various 'special'
    386      // interfaces; e.g.  nsISimpleEnumerator.
    387      rv = wrapper->QueryInterface(aIID, aInstancePtr);
    388      return rv;
    389    }
    390  }
    391 
    392  // If we're asked to QI to nsINamed, we pretend that this is possible. We'll
    393  // try to return a name that makes sense for the wrapped JS value.
    394  if (aIID.Equals(NS_GET_IID(nsINamed))) {
    395    nsCString name = GetFunctionName(ccx, obj);
    396    RefPtr<WrappedJSNamed> named = new WrappedJSNamed(name);
    397    *aInstancePtr = named.forget().take();
    398    return NS_OK;
    399  }
    400 
    401  // else...
    402  // no can do
    403  *aInstancePtr = nullptr;
    404  return NS_NOINTERFACE;
    405 }
    406 
    407 // static
    408 JSObject* nsXPCWrappedJS::GetRootJSObject(JSContext* cx, JSObject* aJSObjArg) {
    409  RootedObject aJSObj(cx, aJSObjArg);
    410  RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
    411  JSObject* result =
    412      CallQueryInterfaceOnJSObject(cx, aJSObj, global, NS_GET_IID(nsISupports));
    413  if (!result) {
    414    result = aJSObj;
    415  }
    416  return js::UncheckedUnwrap(result);
    417 }
    418 
    419 // static
    420 bool nsXPCWrappedJS::GetArraySizeFromParam(const nsXPTMethodInfo* method,
    421                                           const nsXPTType& type,
    422                                           nsXPTCMiniVariant* nativeParams,
    423                                           uint32_t* result) {
    424  if (type.Tag() != nsXPTType::T_LEGACY_ARRAY &&
    425      type.Tag() != nsXPTType::T_PSTRING_SIZE_IS &&
    426      type.Tag() != nsXPTType::T_PWSTRING_SIZE_IS) {
    427    *result = 0;
    428    return true;
    429  }
    430 
    431  uint8_t argnum = type.ArgNum();
    432  const nsXPTParamInfo& param = method->Param(argnum);
    433 
    434  // This should be enforced by the xpidl compiler, but it's not.
    435  // See bug 695235.
    436  if (param.Type().Tag() != nsXPTType::T_U32) {
    437    return false;
    438  }
    439 
    440  // If the length is passed indirectly (as an outparam), dereference by an
    441  // extra level.
    442  if (param.IsIndirect()) {
    443    *result = *(uint32_t*)nativeParams[argnum].val.p;
    444  } else {
    445    *result = nativeParams[argnum].val.u32;
    446  }
    447  return true;
    448 }
    449 
    450 // static
    451 bool nsXPCWrappedJS::GetInterfaceTypeFromParam(const nsXPTMethodInfo* method,
    452                                               const nsXPTType& type,
    453                                               nsXPTCMiniVariant* nativeParams,
    454                                               nsID* result) {
    455  result->Clear();
    456 
    457  const nsXPTType& inner = type.InnermostType();
    458  if (inner.Tag() == nsXPTType::T_INTERFACE) {
    459    // Directly get IID from nsXPTInterfaceInfo.
    460    if (!inner.GetInterface()) {
    461      return false;
    462    }
    463 
    464    *result = inner.GetInterface()->IID();
    465  } else if (inner.Tag() == nsXPTType::T_INTERFACE_IS) {
    466    // Get IID from a passed parameter.
    467    const nsXPTParamInfo& param = method->Param(inner.ArgNum());
    468    if (param.Type().Tag() != nsXPTType::T_NSID &&
    469        param.Type().Tag() != nsXPTType::T_NSIDPTR) {
    470      return false;
    471    }
    472 
    473    void* ptr = nativeParams[inner.ArgNum()].val.p;
    474 
    475    // If our IID is passed as a pointer outparameter, an extra level of
    476    // dereferencing is required.
    477    if (ptr && param.Type().Tag() == nsXPTType::T_NSIDPTR &&
    478        param.IsIndirect()) {
    479      ptr = *(nsID**)ptr;
    480    }
    481 
    482    if (!ptr) {
    483      return false;
    484    }
    485 
    486    *result = *(nsID*)ptr;
    487  }
    488  return true;
    489 }
    490 
    491 // static
    492 void nsXPCWrappedJS::CleanupOutparams(const nsXPTMethodInfo* info,
    493                                      nsXPTCMiniVariant* nativeParams,
    494                                      bool inOutOnly, uint8_t count) {
    495  // clean up any 'out' params handed in
    496  for (uint8_t i = 0; i < count; i++) {
    497    const nsXPTParamInfo& param = info->Param(i);
    498    if (!param.IsOut()) {
    499      continue;
    500    }
    501 
    502    MOZ_ASSERT(param.IsIndirect(), "Outparams are always indirect");
    503 
    504    // Don't try to clear optional out params that are not set.
    505    if (param.IsOptional() && !nativeParams[i].val.p) {
    506      continue;
    507    }
    508 
    509    // Call 'CleanupValue' on parameters which we know to be initialized:
    510    //  1. Complex parameters (initialized by caller)
    511    //  2. 'inout' parameters (initialized by caller)
    512    //  3. 'out' parameters when 'inOutOnly' is 'false' (initialized by us)
    513    //
    514    // We skip non-complex 'out' parameters before the call, as they may
    515    // contain random junk.
    516    if (param.Type().IsComplex() || param.IsIn() || !inOutOnly) {
    517      uint32_t arrayLen = 0;
    518      if (!GetArraySizeFromParam(info, param.Type(), nativeParams, &arrayLen)) {
    519        continue;
    520      }
    521 
    522      xpc::CleanupValue(param.Type(), nativeParams[i].val.p, arrayLen);
    523    }
    524 
    525    // Ensure our parameters are in a clean state. Complex values are always
    526    // handled by CleanupValue, and others have a valid null representation.
    527    if (!param.Type().IsComplex()) {
    528      param.Type().ZeroValue(nativeParams[i].val.p);
    529    }
    530  }
    531 }
    532 
    533 nsresult nsXPCWrappedJS::CheckForException(XPCCallContext& ccx,
    534                                           AutoEntryScript& aes,
    535                                           HandleObject aObj,
    536                                           const char* aPropertyName,
    537                                           const char* anInterfaceName,
    538                                           Exception* aSyntheticException) {
    539  JSContext* cx = ccx.GetJSContext();
    540  MOZ_ASSERT(cx == aes.cx());
    541  RefPtr<Exception> xpc_exception = aSyntheticException;
    542  /* this one would be set by our error reporter */
    543 
    544  XPCJSContext* xpccx = ccx.GetContext();
    545 
    546  // Get this right away in case we do something below to cause JS code
    547  // to run.
    548  nsresult pending_result = xpccx->GetPendingResult();
    549 
    550  RootedValue js_exception(cx);
    551  bool is_js_exception = JS_GetPendingException(cx, &js_exception);
    552 
    553  /* JS might throw an exception whether the reporter was called or not */
    554  if (is_js_exception) {
    555    if (!xpc_exception) {
    556      XPCConvert::JSValToXPCException(cx, &js_exception, anInterfaceName,
    557                                      aPropertyName,
    558                                      getter_AddRefs(xpc_exception));
    559    }
    560 
    561    /* cleanup and set failed even if we can't build an exception */
    562    if (!xpc_exception) {
    563      xpccx->SetPendingException(nullptr);  // XXX necessary?
    564    }
    565  }
    566 
    567  // Clear the pending exception now, because xpc_exception might be JS-
    568  // implemented, so invoking methods on it might re-enter JS, which we can't
    569  // do with an exception on the stack.
    570  aes.ClearException();
    571 
    572  if (xpc_exception) {
    573    nsresult e_result = xpc_exception->GetResult();
    574    // Figure out whether or not we should report this exception.
    575    bool reportable = xpc_IsReportableErrorCode(e_result);
    576    if (reportable) {
    577      // Ugly special case for GetInterface. It's "special" in the
    578      // same way as QueryInterface in that a failure is not
    579      // exceptional and shouldn't be reported. We have to do this
    580      // check here instead of in xpcwrappedjs (like we do for QI) to
    581      // avoid adding extra code to all xpcwrappedjs objects.
    582      if (e_result == NS_ERROR_NO_INTERFACE &&
    583          !strcmp(anInterfaceName, "nsIInterfaceRequestor") &&
    584          !strcmp(aPropertyName, "getInterface")) {
    585        reportable = false;
    586      }
    587 
    588      // More special case, see bug 877760.
    589      if (e_result == NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED) {
    590        reportable = false;
    591      }
    592    }
    593 
    594    // Try to use the error reporter set on the context to handle this
    595    // error if it came from a JS exception.
    596    if (reportable && is_js_exception) {
    597      // Note that we cleared the exception above, so we need to set it again,
    598      // just so that we can tell the JS engine to pass it back to us via the
    599      // error reporting callback. This is all very dumb.
    600      JS_SetPendingException(cx, js_exception);
    601 
    602      // Enter the unwrapped object's realm. This is the realm that was used to
    603      // enter the AutoEntryScript.
    604      JSAutoRealm ar(cx, js::UncheckedUnwrap(aObj));
    605      aes.ReportException();
    606      reportable = false;
    607    }
    608 
    609    if (reportable) {
    610      if (nsJSUtils::DumpEnabled()) {
    611        static const char line[] =
    612            "************************************************************\n";
    613        static const char preamble[] =
    614            "* Call to xpconnect wrapped JSObject produced this error:  *\n";
    615        static const char cant_get_text[] =
    616            "FAILED TO GET TEXT FROM EXCEPTION\n";
    617 
    618        fputs(line, stdout);
    619        fputs(preamble, stdout);
    620        nsCString text;
    621        xpc_exception->ToString(cx, text);
    622        if (!text.IsEmpty()) {
    623          fputs(text.get(), stdout);
    624          fputs("\n", stdout);
    625        } else
    626          fputs(cant_get_text, stdout);
    627        fputs(line, stdout);
    628      }
    629 
    630      // Log the exception to the JS Console, so that users can do
    631      // something with it.
    632      nsCOMPtr<nsIConsoleService> consoleService(
    633          do_GetService(XPC_CONSOLE_CONTRACTID));
    634      if (nullptr != consoleService) {
    635        nsCOMPtr<nsIScriptError> scriptError =
    636            do_QueryInterface(xpc_exception->GetData());
    637 
    638        if (nullptr == scriptError) {
    639          // No luck getting one from the exception, so
    640          // try to cook one up.
    641          scriptError = do_CreateInstance(XPC_SCRIPT_ERROR_CONTRACTID);
    642          if (nullptr != scriptError) {
    643            nsCString newMessage;
    644            xpc_exception->ToString(cx, newMessage);
    645            // try to get filename, lineno from the first
    646            // stack frame location.
    647            int32_t lineNumber = 0;
    648            nsAutoCString sourceName;
    649 
    650            nsCOMPtr<nsIStackFrame> location = xpc_exception->GetLocation();
    651            if (location) {
    652              // Get line number.
    653              lineNumber = location->GetLineNumber(cx);
    654 
    655              // get a filename.
    656              location->GetFilename(cx, sourceName);
    657            }
    658 
    659            nsresult rv = scriptError->InitWithWindowID(
    660                NS_ConvertUTF8toUTF16(newMessage), sourceName, lineNumber, 0, 0,
    661                "XPConnect JavaScript",
    662                nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx));
    663            if (NS_FAILED(rv)) {
    664              scriptError = nullptr;
    665            }
    666 
    667            rv = scriptError->InitSourceId(location->GetSourceId(cx));
    668            if (NS_FAILED(rv)) {
    669              scriptError = nullptr;
    670            }
    671          }
    672        }
    673        if (nullptr != scriptError) {
    674          consoleService->LogMessage(scriptError);
    675        }
    676      }
    677    }
    678    // Whether or not it passes the 'reportable' test, it might
    679    // still be an error and we have to do the right thing here...
    680    if (NS_FAILED(e_result)) {
    681      xpccx->SetPendingException(xpc_exception);
    682      return e_result;
    683    }
    684  } else {
    685    // see if JS code signaled failure result without throwing exception
    686    if (NS_FAILED(pending_result)) {
    687      return pending_result;
    688    }
    689  }
    690  return NS_ERROR_FAILURE;
    691 }
    692 
    693 NS_IMETHODIMP
    694 nsXPCWrappedJS::CallMethod(uint16_t methodIndex, const nsXPTMethodInfo* info,
    695                           nsXPTCMiniVariant* nativeParams) {
    696  // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread.
    697  MOZ_RELEASE_ASSERT(NS_IsMainThread(),
    698                     "nsXPCWrappedJS::CallMethod called off main thread");
    699 
    700  if (!IsValid()) {
    701    return NS_ERROR_UNEXPECTED;
    702  }
    703 
    704  // We need to reject an attempt to call a non-reflectable method before
    705  // we do anything like AutoEntryScript which might allocate in the JS engine,
    706  // because the method isn't marked with JS_HAZ_CAN_RUN_SCRIPT, and we want
    707  // to be able to take advantage of that in the GC hazard analysis.
    708  if (!info->IsReflectable()) {
    709    return NS_ERROR_FAILURE;
    710  }
    711 
    712  Value* sp = nullptr;
    713  Value* argv = nullptr;
    714  uint8_t i;
    715  nsresult retval = NS_ERROR_FAILURE;
    716  bool success;
    717  bool readyToDoTheCall = false;
    718  nsID param_iid;
    719  bool foundDependentParam;
    720 
    721  // We're about to call into script via an XPCWrappedJS, so we need an
    722  // AutoEntryScript. This is probably Gecko-specific at this point, and
    723  // definitely will be when we turn off XPConnect for the web.
    724  RootedObject obj(RootingCx(), GetJSObject());
    725  nsIGlobalObject* nativeGlobal = NativeGlobal(js::UncheckedUnwrap(obj));
    726 
    727  AutoAllowLegacyScriptExecution exemption;
    728 
    729  AutoEntryScript aes(nativeGlobal, "XPCWrappedJS method call",
    730                      /* aIsMainThread = */ true);
    731  XPCCallContext ccx(aes.cx());
    732  if (!ccx.IsValid()) {
    733    return retval;
    734  }
    735 
    736  JSContext* cx = ccx.GetJSContext();
    737 
    738  if (!cx) {
    739    return NS_ERROR_FAILURE;
    740  }
    741 
    742  // We now need to enter the realm of the actual JSObject* we are pointing at.
    743  // But that may be a cross-compartment wrapper and therefore not have a
    744  // well-defined realm, so enter the realm of the global that we grabbed back
    745  // when we started pointing to our JSObject*.
    746  RootedObject scope(cx, GetJSObjectGlobal());
    747  JSAutoRealm ar(cx, scope);
    748 
    749  const nsXPTInterfaceInfo* interfaceInfo = GetInfo();
    750  JS::RootedId id(cx);
    751  const char* name = info->NameOrDescription();
    752  if (!info->GetId(cx, id.get())) {
    753    return NS_ERROR_FAILURE;
    754  }
    755 
    756  // [optional_argc] has a different calling convention, which we don't
    757  // support for JS-implemented components.
    758  if (info->WantsOptArgc()) {
    759    const char* str =
    760        "IDL methods marked with [optional_argc] may not "
    761        "be implemented in JS";
    762    // Throw and warn for good measure.
    763    JS_ReportErrorASCII(cx, "%s", str);
    764    NS_WARNING(str);
    765    return CheckForException(ccx, aes, obj, name, interfaceInfo->Name());
    766  }
    767 
    768  RootedValue fval(cx);
    769  RootedObject thisObj(cx, obj);
    770 
    771  RootedValueVector args(cx);
    772  AutoScriptEvaluate scriptEval(cx);
    773 
    774  XPCJSRuntime* xpcrt = XPCJSRuntime::Get();
    775  XPCJSContext* xpccx = ccx.GetContext();
    776  AutoSavePendingResult apr(xpccx);
    777 
    778  // XXX ASSUMES that retval is last arg. The xpidl compiler ensures this.
    779  uint8_t paramCount = info->ParamCount();
    780  uint8_t argc = paramCount;
    781  if (info->HasRetval()) {
    782    argc -= 1;
    783  }
    784 
    785  if (!scriptEval.StartEvaluating(scope)) {
    786    goto pre_call_clean_up;
    787  }
    788 
    789  xpccx->SetPendingException(nullptr);
    790 
    791  // We use js_Invoke so that the gcthings we use as args will be rooted by
    792  // the engine as we do conversions and prepare to do the function call.
    793 
    794  // setup stack
    795 
    796  // if this isn't a function call then we don't need to push extra stuff
    797  if (!(info->IsSetter() || info->IsGetter())) {
    798    // We get fval before allocating the stack to avoid gc badness that can
    799    // happen if the GetProperty call leaves our request and the gc runs
    800    // while the stack we allocate contains garbage.
    801 
    802    // If the interface is marked as a [function] then we will assume that
    803    // our JSObject is a function and not an object with a named method.
    804 
    805    // In the xpidl [function] case we are making sure now that the
    806    // JSObject is callable. If it is *not* callable then we silently
    807    // fallback to looking up the named property...
    808    // (because jst says he thinks this fallback is 'The Right Thing'.)
    809    //
    810    // In the normal (non-function) case we just lookup the property by
    811    // name and as long as the object has such a named property we go ahead
    812    // and try to make the call. If it turns out the named property is not
    813    // a callable object then the JS engine will throw an error and we'll
    814    // pass this along to the caller as an exception/result code.
    815 
    816    fval = ObjectValue(*obj);
    817    if (!interfaceInfo->IsFunction() ||
    818        JS_TypeOfValue(ccx, fval) != JSTYPE_FUNCTION) {
    819      if (!JS_GetPropertyById(cx, obj, id, &fval)) {
    820        goto pre_call_clean_up;
    821      }
    822      // XXX We really want to factor out the error reporting better and
    823      // specifically report the failure to find a function with this name.
    824      // This is what we do below if the property is found but is not a
    825      // function. We just need to factor better so we can get to that
    826      // reporting path from here.
    827 
    828      thisObj = obj;
    829    }
    830  }
    831 
    832  if (!args.resize(argc)) {
    833    retval = NS_ERROR_OUT_OF_MEMORY;
    834    goto pre_call_clean_up;
    835  }
    836 
    837  argv = args.begin();
    838  sp = argv;
    839 
    840  // build the args
    841  // NB: This assignment *looks* wrong because we haven't yet called our
    842  // function. However, we *have* already entered the compartmen that we're
    843  // about to call, and that's the global that we want here. In other words:
    844  // we're trusting the JS engine to come up with a good global to use for
    845  // our object (whatever it was).
    846  for (i = 0; i < argc; i++) {
    847    const nsXPTParamInfo& param = info->Param(i);
    848    const nsXPTType& type = param.GetType();
    849    uint32_t array_count;
    850    RootedValue val(cx, NullValue());
    851 
    852    // Verify that null was not passed for a non-optional 'out' param.
    853    if (param.IsOut() && !nativeParams[i].val.p && !param.IsOptional()) {
    854      retval = NS_ERROR_INVALID_ARG;
    855      goto pre_call_clean_up;
    856    }
    857 
    858    if (param.IsIn()) {
    859      const void* pv;
    860      if (param.IsIndirect()) {
    861        pv = nativeParams[i].val.p;
    862      } else {
    863        pv = &nativeParams[i];
    864      }
    865 
    866      if (!GetInterfaceTypeFromParam(info, type, nativeParams, &param_iid) ||
    867          !GetArraySizeFromParam(info, type, nativeParams, &array_count))
    868        goto pre_call_clean_up;
    869 
    870      if (!XPCConvert::NativeData2JS(cx, &val, pv, type, &param_iid,
    871                                     array_count, nullptr))
    872        goto pre_call_clean_up;
    873    }
    874 
    875    if (param.IsOut()) {
    876      // create an 'out' object
    877      RootedObject out_obj(cx, NewOutObject(cx));
    878      if (!out_obj) {
    879        retval = NS_ERROR_OUT_OF_MEMORY;
    880        goto pre_call_clean_up;
    881      }
    882 
    883      if (param.IsIn()) {
    884        if (!JS_SetPropertyById(cx, out_obj,
    885                                xpcrt->GetStringID(XPCJSContext::IDX_VALUE),
    886                                val)) {
    887          goto pre_call_clean_up;
    888        }
    889      }
    890      *sp++ = JS::ObjectValue(*out_obj);
    891    } else
    892      *sp++ = val;
    893  }
    894 
    895  readyToDoTheCall = true;
    896 
    897 pre_call_clean_up:
    898  // clean up any 'out' params handed in
    899  CleanupOutparams(info, nativeParams, /* inOutOnly = */ true, paramCount);
    900 
    901  if (!readyToDoTheCall) {
    902    return retval;
    903  }
    904 
    905  // do the deed - note exceptions
    906 
    907  MOZ_ASSERT(!aes.HasException());
    908 
    909  RefPtr<Exception> syntheticException;
    910  RootedValue rval(cx);
    911  if (info->IsGetter()) {
    912    success = JS_GetProperty(cx, obj, name, &rval);
    913  } else if (info->IsSetter()) {
    914    rval = *argv;
    915    success = JS_SetProperty(cx, obj, name, rval);
    916  } else {
    917    if (!fval.isPrimitive()) {
    918      success = JS_CallFunctionValue(cx, thisObj, fval, args, &rval);
    919    } else {
    920      // The property was not an object so can't be a function.
    921      // Let's build and 'throw' an exception.
    922 
    923      static const nsresult code = NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED;
    924      static const char format[] = "%s \"%s\"";
    925      const char* msg;
    926      UniqueChars sz;
    927 
    928      if (nsXPCException::NameAndFormatForNSResult(code, nullptr, &msg) &&
    929          msg) {
    930        sz = JS_smprintf(format, msg, name);
    931      }
    932 
    933      XPCConvert::ConstructException(
    934          code, sz.get(), interfaceInfo->Name(), name, nullptr,
    935          getter_AddRefs(syntheticException), nullptr, nullptr);
    936      success = false;
    937    }
    938  }
    939 
    940  if (!success) {
    941    return CheckForException(ccx, aes, obj, name, interfaceInfo->Name(),
    942                             syntheticException);
    943  }
    944 
    945  xpccx->SetPendingException(nullptr);  // XXX necessary?
    946 
    947  // convert out args and result
    948  // NOTE: this is the total number of native params, not just the args
    949  // Convert independent params only.
    950  // When we later convert the dependent params (if any) we will know that
    951  // the params upon which they depend will have already been converted -
    952  // regardless of ordering.
    953 
    954  foundDependentParam = false;
    955  for (i = 0; i < paramCount; i++) {
    956    const nsXPTParamInfo& param = info->Param(i);
    957    MOZ_ASSERT(!param.IsShared(), "[shared] implies [noscript]!");
    958    if (!param.IsOut() || !nativeParams[i].val.p) {
    959      continue;
    960    }
    961 
    962    const nsXPTType& type = param.GetType();
    963    if (type.IsDependent()) {
    964      foundDependentParam = true;
    965      continue;
    966    }
    967 
    968    RootedValue val(cx);
    969 
    970    if (&param == info->GetRetval()) {
    971      val = rval;
    972    } else if (argv[i].isPrimitive()) {
    973      break;
    974    } else {
    975      RootedObject obj(cx, &argv[i].toObject());
    976      if (!JS_GetPropertyById(
    977              cx, obj, xpcrt->GetStringID(XPCJSContext::IDX_VALUE), &val)) {
    978        break;
    979      }
    980    }
    981 
    982    // setup allocator and/or iid
    983 
    984    const nsXPTType& inner = type.InnermostType();
    985    if (inner.Tag() == nsXPTType::T_INTERFACE) {
    986      if (!inner.GetInterface()) {
    987        break;
    988      }
    989      param_iid = inner.GetInterface()->IID();
    990    }
    991 
    992    MOZ_ASSERT(param.IsIndirect(), "outparams are always indirect");
    993    if (!XPCConvert::JSData2Native(cx, nativeParams[i].val.p, val, type,
    994                                   &param_iid, 0, nullptr))
    995      break;
    996  }
    997 
    998  // if any params were dependent, then we must iterate again to convert them.
    999  if (foundDependentParam && i == paramCount) {
   1000    for (i = 0; i < paramCount; i++) {
   1001      const nsXPTParamInfo& param = info->Param(i);
   1002      if (!param.IsOut()) {
   1003        continue;
   1004      }
   1005 
   1006      const nsXPTType& type = param.GetType();
   1007      if (!type.IsDependent()) {
   1008        continue;
   1009      }
   1010 
   1011      RootedValue val(cx);
   1012      uint32_t array_count;
   1013 
   1014      if (&param == info->GetRetval()) {
   1015        val = rval;
   1016      } else {
   1017        RootedObject obj(cx, &argv[i].toObject());
   1018        if (!JS_GetPropertyById(
   1019                cx, obj, xpcrt->GetStringID(XPCJSContext::IDX_VALUE), &val)) {
   1020          break;
   1021        }
   1022      }
   1023 
   1024      // setup allocator and/or iid
   1025 
   1026      if (!GetInterfaceTypeFromParam(info, type, nativeParams, &param_iid) ||
   1027          !GetArraySizeFromParam(info, type, nativeParams, &array_count))
   1028        break;
   1029 
   1030      MOZ_ASSERT(param.IsIndirect(), "outparams are always indirect");
   1031      if (!XPCConvert::JSData2Native(cx, nativeParams[i].val.p, val, type,
   1032                                     &param_iid, array_count, nullptr))
   1033        break;
   1034    }
   1035  }
   1036 
   1037  if (i != paramCount) {
   1038    // We didn't manage all the result conversions!
   1039    // We have to cleanup any junk that *did* get converted.
   1040    CleanupOutparams(info, nativeParams, /* inOutOnly = */ false, i);
   1041  } else {
   1042    // set to whatever the JS code might have set as the result
   1043    retval = xpccx->GetPendingResult();
   1044  }
   1045 
   1046  return retval;
   1047 }
   1048 
   1049 static const JSClass XPCOutParamClass = {"XPCOutParam", 0, JS_NULL_CLASS_OPS};
   1050 
   1051 bool xpc::IsOutObject(JSContext* cx, JSObject* obj) {
   1052  return JS::GetClass(obj) == &XPCOutParamClass;
   1053 }
   1054 
   1055 JSObject* xpc::NewOutObject(JSContext* cx) {
   1056  return JS_NewObject(cx, &XPCOutParamClass);
   1057 }
   1058 
   1059 // static
   1060 void nsXPCWrappedJS::DebugDumpInterfaceInfo(const nsXPTInterfaceInfo* aInfo,
   1061                                            int16_t depth) {
   1062 #ifdef DEBUG
   1063  depth--;
   1064  XPC_LOG_ALWAYS(("nsXPTInterfaceInfo @ %p = ", aInfo));
   1065  XPC_LOG_INDENT();
   1066  const char* name = aInfo->Name();
   1067  XPC_LOG_ALWAYS(("interface name is %s", name));
   1068  auto iid = aInfo->IID().ToString();
   1069  XPC_LOG_ALWAYS(("IID number is %s", iid.get()));
   1070  XPC_LOG_ALWAYS(("InterfaceInfo @ %p", aInfo));
   1071  uint16_t methodCount = 0;
   1072  if (depth) {
   1073    XPC_LOG_INDENT();
   1074    XPC_LOG_ALWAYS(("parent @ %p", aInfo->GetParent()));
   1075    methodCount = aInfo->MethodCount();
   1076    XPC_LOG_ALWAYS(("MethodCount = %d", methodCount));
   1077    XPC_LOG_ALWAYS(("ConstantCount = %d", aInfo->ConstantCount()));
   1078    XPC_LOG_OUTDENT();
   1079  }
   1080  XPC_LOG_ALWAYS(("method count = %d", methodCount));
   1081  if (depth && methodCount) {
   1082    depth--;
   1083    XPC_LOG_INDENT();
   1084    for (uint16_t i = 0; i < methodCount; i++) {
   1085      XPC_LOG_ALWAYS(("Method %d is %s%s", i,
   1086                      aInfo->Method(i).IsReflectable() ? "" : " NOT ",
   1087                      "reflectable"));
   1088    }
   1089    XPC_LOG_OUTDENT();
   1090    depth++;
   1091  }
   1092  XPC_LOG_OUTDENT();
   1093 #endif
   1094 }