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, ¶m_iid) || 867 !GetArraySizeFromParam(info, type, nativeParams, &array_count)) 868 goto pre_call_clean_up; 869 870 if (!XPCConvert::NativeData2JS(cx, &val, pv, type, ¶m_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 (¶m == 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 ¶m_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 (¶m == 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, ¶m_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 ¶m_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 }